本文使用「署名 4.0 国际 (CC BY 4.0)」许可协议,欢迎转载、或重新修改使用,但需要注明来源。 [署名 4.0 国际 (CC BY 4.0)](https://creativecommons.org/licenses/by/4.0/deed.zh) 本文作者: 苏洋 创建时间: 2022年05月10日 统计字数: 8879字 阅读时间: 18分钟阅读 本文链接: https://soulteary.com/2022/05/10/lets-talk-about-code-warehouse-visualization-with-gource.html ----- # 聊聊代码仓库可视化:gource 篇 本篇文章将聊聊如何高效的将代码仓库中的提交记录和目录结构,快速转变为“酷炫的视频”。分享如何使用 Docker 在不同 CPU 架构的设备上运行 gource,以及如何基于最新的 M1 Pro 芯片的设备,让制作可视化视频的效率成倍提升。 ## 写在前面 前一阵为了庆祝社区项目 Milvus 在 GitHub 获得了一万颗星星,我制作了[一个视频](https://www.zhihu.com/zvideo/1497180653129469955),用来动态的展示在过去的时间里,这个项目的具体提交状况和项目组织架构变化状况。 ![使用 Gource 对代码仓库进行可视化](https://attachment.soulteary.com/2022/05/10/preview.jpg) 最近有同事聊开源项目的“[维护血泪史](https://www.zhihu.com/question/36292298/answer/2474651344)”时,又提到了这件事。勾起了我当时制作视频的痛苦回忆:当时的视频制作方案是使用 docker 运行 `gource`。在针对诸如 Milvus 仓库这种提交量比较大的仓库时(1.4万提交),想要生成可视化视频,使用我手头的 i9 处理器的设备,至少需要跑个把小时。而当我将相同的操作换到 M1 设备(M1 Pro)运行后,或许是因为 docker 中的应用并未针对 ARM 芯片做优化、又或许是 docker 中的程序版本不够新,相同的工作量,甚至需要跑半天才能搞定! 不论如何,这个结果未免太不科学了。 ![上万次提交的开源项目背后的代码变动](https://attachment.soulteary.com/2022/05/10/milvus-repo.jpg) 且不说 M1 的运行结果“出乎意料”,但就是个把小时的视频生成时间,也让我感觉挺不舒服的。作为一个追求效率的老程序员,我花了一些时间,终于摸索出了这个问题的“正确答案”:如果使用针对 M1 芯片而编译的程序,整个视频的生成时间可以缩短到半个小时左右,相比较之前提升效果颇为明显。 在展开聊聊我是如何做的之前,我想先介绍一下 `gource` 这款开源软件。 ## 关于 Gource 2009 年,来自新西兰的工程师 [Andrew Caudwell](https://github.com/acaudwell),希望能够将各种代码版本管理软件的信息可视化,于是他使用 C++ 编写了 [Gource 这个程序](https://github.com/acaudwell/Gource)。2011 年,项目从 Google Code 迁移至 GitHub 后,项目开启了年更模式。 比较幸运的是,截止本文成文写出的时候,软件已经发布了今年的两个重要更新:包含视网膜屏幕的支持,以及针对字体缩放功能进行了大量修正,并将软件使用的正则库升级为了 PCRE2,程序版本更新到了 0.53 。 因为项目在 GitHub [发布页面](https://github.com/acaudwell/Gource/releases)中只提供了 Windows 版本的程序,所以如果我们想获取 Linux / macOS 的**新版本程序**,就只能自己进行编译啦。(Ubuntu APT 仓库中的版本还停留在 2019 年发布的 0.51) 接下来,我们先来聊聊如何进行编译,如果你希望使用 Docker 或 x86 设备,可以阅读本文后面的章节。 ## 在 M1 设备上进行 Gource 的编译 为了能够在 macOS 上完成新版本的程序编译,我们需要先完成 gource 的依赖安装: ```bash brew install pkg-config freetype2 pcre2 glow sdl2 sdl2_image boost glm autoconf ``` 如果你没有完成上述依赖的安装,那么在执行 `./configure` 的时候,一定会遇到诸如下面的问题: ```bash checking for FT2... configure: error: in `/Users/soulteary/lab/gource-0.53': configure: error: The pkg-config script could not be found or is too old. Make sure it is in your PATH or set the PKG_CONFIG environment variable to the full path to pkg-config. ... No package 'libpcre2-8' found ... ``` 当我们安装完毕依赖之后,还需要配置一下编译参数,让程序在编译的时候能够找到我们刚刚安装的依赖。不然,就会出现类似下面的错误: ```bash checking for boostlib >= 1.46 (104600)... configure: We could not detect the boost libraries (version 1.46 or higher). If you have a staged boost library (still not installed) please specify $BOOST_ROOT in your environment and do not give a PATH to --with-boost option. If you are sure you have boost installed, then check your version number looking in . See http://randspringer.de/boost for more documentation. configure: error: Boost Filesystem >= 1.46 is required. Please see INSTALL ... configure: error: GLM headers are required. Please see INSTALL ... ``` 对于 boost 框架,我们可以通过简单使用 `--with-boost` 参数来指定依赖的目录,而对于 [glm](https://github.com/g-truc/glm)(OpenGL Mathematics),因为它是一个仅包含头文件的数学库,所以我们必须使用 `CPPFLAGS` 等参数,将路径传递给 `configure`。 但是我们要如何在 macOS 中获得由 `brew` 安装的 glm 或 boost 路径呢?这里可以将下面两种方法进行组合使用。 第一种查找路径的方法是使用 `brew list` 命令,获取我们安装的某个软件的详细目录列表,在输出日志中寻找或尝试出正确的目录。以 boost 举例,当我们执行完毕 `brew list boost` 之后,能够看到类似下面的输出结果: ```bash /opt/homebrew/Cellar/boost/1.78.0_1/include/boost/ (15026 files) /opt/homebrew/Cellar/boost/1.78.0_1/lib/libboost_atomic-mt.dylib /opt/homebrew/Cellar/boost/1.78.0_1/lib/libboost_chrono-mt.dylib ... ``` 其中 `/opt/homebrew/Cellar/boost/1.78.0_1/` 就是 boost 的根目录,将这个路径拼合为 `--with-boost=/opt/homebrew/Cellar/boost/1.78.0_1/` 参数,然后就能够在编译中使用 `boost` 啦。 第二种路径的查找方法,是使用 `pkg-config` 工具,输出 C++ 项目编译可以使用的具体目录参数。特别适合 glm 这类项目。我们通过为 `pkg-config` 添加参数,可以得到命令 `pkg-config glm --libs --cflags`,当命令执行完毕,就能够得到编译时可以直接使用的目录地址了: ```bash -I/opt/homebrew/Cellar/glm/0.9.9.8/include ``` 将上面的参数进行整合,不难得到在 M1 设备上的进行编译配置的完整命令: ```bash ./configure --with-boost=/opt/homebrew/Cellar/boost/1.78.0_1/ CPPFLAGS="-I/opt/homebrew/Cellar/glm/0.9.9.8/include" ``` 当命令执行完毕,不出意外,我们将看到类似下面的日志输出: ```bash checking for a BSD-compatible install... /usr/bin/install -c checking whether build environment is sane... yes checking for a thread-safe mkdir -p... build-aux/install-sh -c -d checking for gawk... no checking for mawk... no checking for nawk... no checking for awk... awk checking whether make sets $(MAKE)... yes checking whether make supports nested variables... yes checking build system type... arm-apple-darwin21.4.0 checking host system type... arm-apple-darwin21.4.0 checking for g++... g++ checking whether the C++ compiler works... yes checking for C++ compiler default output file name... a.out checking for suffix of executables... checking whether we are cross compiling... no checking for suffix of object files... o checking whether we are using the GNU C++ compiler... yes checking whether g++ accepts -g... yes checking for style of include used by make... GNU checking dependency style of g++... gcc3 checking for timegm... yes checking for unsetenv... yes checking how to run the C++ preprocessor... g++ -E checking for X... disabled checking for a sed that does not truncate output... /usr/bin/sed checking for gcc... gcc checking whether we are using the GNU C compiler... yes checking whether gcc accepts -g... yes checking for gcc option to accept ISO C89... none needed checking whether gcc understands -c and -o together... yes checking dependency style of gcc... gcc3 checking for the pthreads library -lpthreads... no checking whether pthreads work without any flags... yes checking for joinable pthread attribute... PTHREAD_CREATE_JOINABLE checking if more special flags are required for pthreads... -D_THREAD_SAFE checking how to run the C preprocessor... gcc -E checking for grep that handles long lines and -e... /usr/bin/grep checking for egrep... /usr/bin/grep -E checking for ANSI C header files... yes checking for sys/types.h... yes checking for sys/stat.h... yes checking for stdlib.h... yes checking for string.h... yes checking for memory.h... yes checking for strings.h... yes checking for inttypes.h... yes checking for stdint.h... yes checking for unistd.h... yes checking whether we are using the Microsoft C compiler... no checking windows.h usability... no checking windows.h presence... no checking for windows.h... no checking for GL/gl.h... no checking for OpenGL/gl.h... yes checking for OpenGL library... -framework OpenGL checking for GL/glu.h... no checking for OpenGL/glu.h... yes checking for OpenGL Utility library... yes checking for varargs GLU tesselator callback function type... no checking for pkg-config... /opt/homebrew/bin/pkg-config checking pkg-config is at least version 0.9.0... yes checking for FT2... yes checking for PCRE2... yes checking for GLEW... yes checking for SDL2... yes checking for PNG... yes checking for IMG_LoadPNG_RW... yes checking for IMG_LoadJPG_RW... yes checking for boostlib >= 1.46 (104600) includes in "/opt/homebrew/Cellar/boost/1.78.0_1//include"... yes checking for boostlib >= 1.46 (104600) lib path in "/opt/homebrew/Cellar/boost/1.78.0_1//lib/arm-darwin21.4.0"... no checking for boostlib >= 1.46 (104600) lib path in "/opt/homebrew/Cellar/boost/1.78.0_1//lib"... yes checking for boostlib >= 1.46 (104600)... yes checking whether the Boost::System library is available... yes checking for exit in -lboost_system... yes checking whether the Boost::Filesystem library is available... yes checking for exit in -lboost_filesystem... yes checking glm/glm.hpp usability... yes checking glm/glm.hpp presence... yes checking for glm/glm.hpp... yes checking that generated files are newer than configure... done configure: creating ./config.status config.status: creating Makefile config.status: executing depfiles commands ``` 接下来,执行 `make` ,系统就会开始对 gource 进行编译,在编译完成之后,接着执行 `sudo make install`,gource 的编译安全就结束啦。 ## 在 M1 设备上使用 Gource 进行代码仓库可视化 在使用 gource 制作视频前,我们需要评估项目所需的硬盘空间,生成视频的尺寸和仓库的提交量(commits)、总的文件目录数量、项目维护时间长,都有很大关系,这里以前文中提到的 [Milvus 仓库](https://github.com/milvus-io/milvus)为例。 这个仓库从 2019 年开始维护,截止当前有 1.4 万次提交,如果想我们生成 `1280x720` 尺寸的视频内容,假设将项目每天的提交数据展示的时间设置为 1 秒,过程中将输出 370 多 GB 的 临时文件(PPM 截图文件),所以**在开始进行仓库可视化之前,请确认你的硬盘留有足够的空间**。 ### 下载要进行可视化的代码仓库 可视化的第一步,是将我们要可视化的仓库下载到本地,比如: ```bash git clone https://github.com/milvus-io/milvus.git ``` ### 使用 Gource 进行可视化渲染 接下来,我们需要使用 `gource` 指定我们未来希望得到的视频的最大分辨率,以及一些关键细节: - 我们希望这个视频中,每一天的展示时间为多久(本例为1秒) - 我们希望这个视频中,视频的最大帧率是多少(本例为30帧) - 我们希望输出的文件名和刚刚使用 `git clone` 下载好的仓库的目录名是什么 ```bash gource --viewport 1280x720 \ --high-dpi \ --seconds-per-day 1 \ --output-ppm-stream milvus.ppm \ --output-framerate 30 \ milvus ``` 执行上面的命令,程序将会打开一个预览界面,开始将仓库的每一次提交记录和当时的目录结构进行可视化绘制。 ![使用 Gource 进行逐帧绘制](https://attachment.soulteary.com/2022/05/10/gource-encode.jpg) 经过相对漫长的等待之后(19分钟左右),当命令执行完毕,我们就得到了包含所有代码仓库提交信息、目录变化信息的临时文件:`milvus.ppm`。 ### 使用 ffmpeg 生成最终的视频文件 我们在上一步得到的文件,足足有 370 GB 之大。为了得到一个方便后续剪辑或在各种网络平台上传播的文件,我们还需要使用 ffmpeg 对其进行格式转换。 如果你没有安装过 `ffmpeg`,可以考虑使用下面的命令完成安装(本文使用版本为 `5.0.1`)。 ```bash brew install ffmpeg ``` 想要生成一个兼容性比较好的 H264 格式的 `milvus.mp4` 文件,可以使用下面的命令: ```bash ffmpeg -y -r 30 -f image2pipe -loglevel info -vcodec ppm -i ./milvus.ppm -vcodec libx264 -preset medium -pix_fmt yuv420p -crf 1 -threads 0 -bf 0 ./milvus.mp4 ``` ![在 M1 设备上火力全开的 ffmpeg](https://attachment.soulteary.com/2022/05/10/ffmpeg-encode.jpg) 耐心等待命令执行完毕(14分钟左右),我们就能够得到包含酷炫结果的视频文件啦。相比较上一步骤中的 370GB 临时文件,视频文件显得相对小巧,只需要 12GB 左右的空间。 ## 使用 Docker 进行代码仓库的可视化 如果你不追求更高的转换效率,可以接受“离线任务”的执行方式,可以考虑使用开源项目 [sandrokeil/docker-files/](https://github.com/sandrokeil/docker-files/tree/master/gource) 中的 gource 镜像。 使用方法非常简单,只需要一条命令: ```bash docker run --rm -it -v `pwd`/repo:/repos -v `pwd`/results:/results -v `pwd`/avatars:/avatars -v `pwd`/mp3s:/mp3s sandrokeil/gource:latest ``` 在上面的命令中,我们需要做一些简单的准备: - 把我们的代码仓库放置于当前目录下的 `repo` 目录中。 - 将我们计划进行替代的用户头像放在 `avatars` 目录中。 - 如果你希望程序生成视频的过程中,顺带完成背景音的配乐,可以将 `mp3` 文件放在 `mp3s` 目录中。 当命令执行完毕之后,我们就能够在本地的 `results` 目录中找到我们的可视化视频文件了。 ## 其他 除了忠实还原仓库中的每一次提交之外,Gource 还支持根据参数筛选时间启止、筛选生成指定用户的贡献记录、甚至搭配 shell 可以筛选生成指定目录的变化记录。 所以,当我们想进行某个大版本回顾,或者庆祝某位开源社区的用户成为项目 maintainer 时,把这些“混杂着代码的时间碎片”通过视频进行还原呈现,或许是一个不错的主意。 ## 最后 希望这篇内容能够帮到有相同需求的你。 下一篇相同主题的内容,我将分享 gource 之外的项目可视化方案,一个相对轻量的方案。 --EOF