使用差异(diffs)
本页面包含差异(diffs)的开发者文档。有关用户文档,请参阅 合并请求中的差异。
我们依赖不同的来源来展示差异。这些包括:
- Gitaly 服务
- 数据库(通过
merge_request_diff_files) - Redis(缓存的高亮差异)
架构概览
合并请求差异
当刷新合并请求(推送到源分支、强制推送到目标分支,或目标分支现在包含来自 MR 的任何提交)时,
我们使用 Gitlab::Git::Compare 获取比较信息,该信息通过 Gitaly 获取 base 和 head 数据,
并通过 Gitlab::Git::Diff.between 计算它们之间的差异。
差异获取过程通过一系列常数值限制单个文件差异的大小和整个差异的总大小。原始差异文件随后被持久化到 merge_request_diff_files 表中。
尽管大于 ApplicationSettings#diff_max_patch_bytes 值 10% 的差异会被折叠,
我们仍然将它们保存在 PostgreSQL 中。但是,大于定义的 安全限制 的差异文件
(请参阅 差异限制部分)不会 被持久化到数据库中。
为了在合并请求差异页面上展示差异信息,我们:
- 从数据库
merge_request_diff_files获取所有差异文件 - 批量获取 旧 和 新 文件 blob 以:
- 高亮显示新旧文件内容
- 知道应该为每个文件使用哪个查看器(文本、图像、已删除等)
- 知道文件内容是否已更改
- 知道是否存储在外部
- 知道是否有存储错误
- 如果差异文件可缓存(基于文本),则使用
Gitlab::Diff::FileCollection::MergeRequestDiff将其缓存在 Redis 中
注释差异
当对差异(任何比较)进行注释时,我们将一个截断的差异版本持久化到 NoteDiffFile 中
(与实际的 DiffNote 关联)。因此,每次需要文件的差异时,我们不会访问存储库,而是:
- 检查我们是否有持久化的
NoteDiffFile#diff并使用它 - 否则,如果是当前的 MR 版本,使用持久化的
MergeRequestDiffFile#diff - 在最后一种情况下,访问存储库并获取差异
差异限制
如上所述,我们限制单个差异文件和整个差异的大小。在某些情况下我们会折叠差异文件, 在某些情况下差异文件根本不显示,而是引导用户到 Blob 视图。
差异集合限制
适用于所有差异文件集合的限制。考虑文件数量、行数和文件大小。
Gitlab::Git::DiffCollection.collection_limits[:safe_max_files] = Gitlab::Git::DiffCollection::DEFAULT_LIMITS[:max_files] = 100如果已渲染 100 个文件,则文件差异会被折叠(但可展开)。
Gitlab::Git::DiffCollection.collection_limits[:safe_max_lines] = Gitlab::Git::DiffCollection::DEFAULT_LIMITS[:max_lines] = 5000如果已渲染 5000 行,则文件差异会被折叠(但可展开)。
Gitlab::Git::DiffCollection.collection_limits[:safe_max_bytes] = Gitlab::Git::DiffCollection.collection_limits[:safe_max_files] * 5.kilobytes = 500.kilobytes如果已渲染 500 千字节,则文件差异会被折叠(但可展开)。
Gitlab::Git::DiffCollection.collection_limits[:max_files] = Commit::DIFF_HARD_LIMIT_FILES = 1000如果已渲染 1000 个文件,则不再渲染任何文件。
Gitlab::Git::DiffCollection.collection_limits[:max_lines] = Commit::DIFF_HARD_LIMIT_LINES = 50000如果已渲染 50,000 行,则不再渲染任何文件。
Gitlab::Git::DiffCollection.collection_limits[:max_bytes] = Gitlab::Git::DiffCollection.collection_limits[:max_files] * 5.kilobytes = 5000.kilobytes如果已渲染 5 兆字节,则不再渲染任何文件。
所有集合限制参数都会发送并在 Gitaly 上应用。也就是说,超过限制后,
Gitaly 只返回安全数量的数据以持久化到 merge_request_diff_files 中。
单个差异文件限制
适用于集合中每个差异文件的限制。考虑文件数量、行数和文件大小。
可展开的补丁(已折叠)
当差异补丁超过 ApplicationSettings#diff_max_patch_bytes 设置值的 10% 时,会被折叠。
也就是说,如果最大允许值为 100kb,则相当于 10kb。
如果补丁大小不超过 ApplicationSettings#diff_max_patch_bytes,则差异会被持久化并可展开。
尽管这种术语(折叠)也在 Gitaly 上使用,但此限制仅在 GitLab 上使用(硬编码 - 不发送到 Gitaly)。
Gitaly 只在超过集合限制时返回 Diff.Collapsed(RPC)。
不可展开的补丁(过大)
如果补丁大于 ApplicationSettings#diff_max_patch_bytes,则不会渲染。
用户会看到一条 Changes are too large to be shown.(更改太大,无法显示)的消息和一个按钮,用于在该提交中仅查看该文件。
Commit::DIFF_SAFE_LINES = Gitlab::Git::DiffCollection::DEFAULT_LIMITS[:max_lines] = 5000如果文件差异超过 5000 行,则会被抑制(技术上与折叠不同,但行为相同,且可展开)。
此限制是硬编码的,仅应用于 GitLab。
查看器
差异查看器可在 models/diff_viewer/* 中找到,是用于映射每种差异文件元数据的类。它包含信息,
例如是否为二进制文件、应使用哪个部分来渲染它,或该类处理哪些文件扩展名。
DiffViewer::Base 验证 blob(旧版本和新版本)的内容、扩展名和文件类型,以检查是否可以渲染。
与目标分支 HEAD 的合并请求差异
历史上,合并请求差异是通过 git diff target...source 计算的,它比较源分支的 HEAD
与目标分支和源分支的合并基(或共同祖先)。
这个解决方案运行良好,直到目标分支开始包含源分支引入的某些更改:
考虑以下情况,其中源分支是 feature_a,目标是 main:
- 从
main检出一个新分支feature_a并在其中删除file_a和file_b。 - 添加一个删除
file_a的提交到main。
合并请求差异仍然包含 file_a 的删除,而与 main 的 HEAD 的实际差异只有 file_b 的删除。
包含此类冗余更改的差异更难审查。
为了显示最新的差异,我们
引入了 与目标分支 HEAD 比较的合并请求差异:
目标分支被人工合并到源分支,然后将生成的合并引用与源分支比较以计算准确差异。
为了支持两种选项的注释,差异注释位置同时存储在 main (base) 和 main (HEAD) 版本中
(在 12.10 中引入)。
main (base) 版本的位置存储在 Note#position 和 Note#original_position 列中,
对于 main (HEAD) 版本,引入了 DiffNotePosition。