显示所有分支(包括远程分支)的 git 前后信息

发布于 2024-12-09 23:19:18 字数 290 浏览 0 评论 0 原文

在 github 项目上,您可以转到 /branches 页面并查看像这样的漂亮图表,每个分支显示每个分支相对于 master 落后多远以及领先多远。

git Branch advance Behind

是否有命令行工具可以执行类似的操作?有什么东西也可以与遥控器一起使用吗?例如,

git branch -v -v

接近我正在寻找的内容,但仅适用于本地分支机构。

On a github project you can go to a /branches page and se pretty graphs like this one that for each branch show how far behind and how far ahead each branch is with respect to master.

git branch ahead behind

Is there a command line tool that does something similar? Something that works with remotes as well? For example,

git branch -v -v

is close to what I am looking for, but only works for local branches.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(3

别念他 2024-12-16 23:19:18

我也对此感到好奇,所以我刚刚制作了一个 gitbranch-status 脚本使用 git for-each-ref 提供此信息

#!/bin/bash
# by http://github.com/jehiah
# this prints out some branch status (similar to the '... ahead' info you get from git status)
 
# example:
# $ git branch-status
# dns_check (ahead 1) | (behind 112) origin/master
# master (ahead 2) | (behind 0) origin/master
 
git for-each-ref --format="%(refname:short) %(upstream:short)" refs/heads | \
while read local remote
do
    [ -z "$remote" ] && continue
    git rev-list --left-right "${local}...${remote}" -- 2>/dev/null >/tmp/git_upstream_status_delta || continue
    LEFT_AHEAD=$(grep -c '^<' /tmp/git_upstream_status_delta)
    RIGHT_AHEAD=$(grep -c '^>' /tmp/git_upstream_status_delta)
    echo "$local (ahead $LEFT_AHEAD) | (behind $RIGHT_AHEAD) $remote"
done

用法:

$ git branch-status
dns_check (ahead 1) | (behind 112) origin/master
master (ahead 2) | (behind 0) origin/master

I've been curious about this as well, so i just whipped up a git branch-status script that gives this information using git for-each-ref

#!/bin/bash
# by http://github.com/jehiah
# this prints out some branch status (similar to the '... ahead' info you get from git status)
 
# example:
# $ git branch-status
# dns_check (ahead 1) | (behind 112) origin/master
# master (ahead 2) | (behind 0) origin/master
 
git for-each-ref --format="%(refname:short) %(upstream:short)" refs/heads | \
while read local remote
do
    [ -z "$remote" ] && continue
    git rev-list --left-right "${local}...${remote}" -- 2>/dev/null >/tmp/git_upstream_status_delta || continue
    LEFT_AHEAD=$(grep -c '^<' /tmp/git_upstream_status_delta)
    RIGHT_AHEAD=$(grep -c '^>' /tmp/git_upstream_status_delta)
    echo "$local (ahead $LEFT_AHEAD) | (behind $RIGHT_AHEAD) $remote"
done

Usage:

$ git branch-status
dns_check (ahead 1) | (behind 112) origin/master
master (ahead 2) | (behind 0) origin/master
栀梦 2024-12-16 23:19:18

2015 更新

我下面的最初答案并不理想,因为上游分支不一定是您要推送的分支,它可能与您从中拉取的分支不同。

对于 Git 2.5+,正确的命令是:

git for-each-ref --format="%(refname:short) %(upstream:track) %(upstream:remotename)" refs/heads

请参阅“查看未推送的 Git 提交”了解更多信息。

(正如 void.pointer评论,< code>upstream:track 比 push:track 更精确,具体取决于 默认推送策略

(upstream:remotename)部分来自HankCA评论,查看如果分支已被推送(或者通常有一个上游等效分支)。)


Git 2.13(第二季度) 2017)使用更通用的 ref-filter API 和更完整的 git for-each-ref推送

参见提交3d9e4ce ,<一href="https://github.com/git/git/commit/949af0684c1f84587504e2143fca6f9bb8504e31" rel="nofollow noreferrer">提交 949af06, 提交 56b4360, 提交 6eac70f, 提交 1a34728, 提交 1a0ca5e, 提交 3a42980, 提交 17938f1, 提交 3ba308c, 提交a798410提交 b180e6f, 提交 01f9582, 提交 7743fcc, 提交 ffd921d, 提交 99c6a71, 提交 d4919bb, 提交 42d0eb0, 提交 4f3e3b3, 提交 c58fc85(2017 年 1 月 10 日),作者:Karthik Nayak (KarthikNayak)
(由 Junio C Hamano -- gitster -- 合并于 提交 93e8cd8,2017 年 2 月 27 日)

推送

本地引用的名称,代表所显示引用的 @{push} 位置。
尊重 :short:lstrip:rstrip:track:trackshort > 选项与upstream 相同。
如果没有配置 @{push} ref,则生成空字符串。

如果附加 lstrip= (rstrip=),则删除以斜线分隔的 引用名称前面(后面)的路径组件
(例如 %(refname:lstrip=2)refs/tags/foo 转换为 foo%(refname:rstrip=2 )refs/tags/foo 转换为 refs)。

如果 是负数,则从指定端剥离尽可能多的路径组件,留下 - 路径组件
(例如 %(refname:lstrip=-2)refs/tags/foo 转换为 tags/foo%(refname: rstrip=-1)refs/tags/foo 转换为 refs)


原始答案 (2014)

另一种方法将在 Git 1.9/2/0 中提供( Q1 2014)。
请参阅 提交 b28061c 来自 拉姆库马尔·拉马钱德拉(artagnon):

for-each-ref: 引入%(upstream:track[short])

介绍:

  • %(upstream:track) 显示“[前方 M,后方 N]”和
  • %(upstream:trackshort) 显示“=”、“>”、“<”,或相应的“<>”(灵感来自 contrib/completion/git-prompt.sh)。

现在您可以在 for-each-ref 中使用以下格式:

%(refname:short) %(upstream:trackshort)

显示带有简洁跟踪信息的参考。

请注意,:track:trackshort 仅适用于“upstream”,与其他任何内容一起使用时会出错。


在 Git 2.30(2021 年第一季度)之前,提交和标记对象可能在每一行的末尾都有 CR(您可以使用 hash-object 或使用 --cleanup=verbatim 创建这样的对象拒绝默认的清理操作),但这将导致不可能用空行来分隔邮件的标题和正文。

宽容一点,也接受带有单独 CR 的行作为空行。

请参阅 提交 e2f8958提交 9f75ce3(2020 年 10 月 29 日),作者:菲利普布莱恩 (phil-blain)
(由 Junio C Hamano -- gitster -- 合并于 提交 4c7eb63,2020 年 11 月 9 日)

ref-filter:在末尾处理 CRLF更优雅地行内

帮助者:Junio C Hamano
帮助者:Eric Sunshine
签字人:Philippe Blain

引用过滤器代码无法正确处理使用 CRLF 作为行终止符的提交或标记消息。

可以使用 git提交(man)git 标签 (man),或使用 git提交树(man )直接

中的函数find_subpos >ref-filter.c 查找两个连续的 LF 以找到结尾主题行,使用 CRLF 的消息中不存在的序列。
这会导致整个邮件被解析为主题行 (%(contents:subject)),邮件正文 (%(contents:body)) 被解析为空。

此外,在 copy_subject 中,希望将主题作为单行返回,'\n' 被空格替换,但 '\r< /code>' 未受影响。

这会影响 gitbranch< 的输出/代码>(man), git 标签(man< /a>)git for-each-ref(man) `。

此行为是 的回归git 分支 --verbose(man),其中一分为二到 949af0684c ("branch: 使用 ref-filter 打印 API", 2017-01-10, Git v2.13.0-rc0 -- 合并 第 1 批)。

通过强化 copy_subjectfind_subpos 中的逻辑,将引用过滤器代码调整得更加宽松,以正确解析包含 CRLF 的消息。

注意:这是在 Git 2.39 中改进/修复/修改的。
不再有 ^M: -----BEGIN PGP SIGNATURE-----^M
不再需要额外的线路

  -----BEGIN PGP SIGNATURE-----

  ...some stuff...

Update 2015

My initial answer below is not ideal as the upstream branch is not necessarily the branch you are pushing t which could be different from the branch you are pulling from.

With Git 2.5+, the correct command is:

git for-each-ref --format="%(refname:short) %(upstream:track) %(upstream:remotename)" refs/heads

See more at "Viewing Unpushed Git Commits".

(As pointed out by void.pointer in the comments, upstream:track is more precise than push:track, depending on the default push policy)

(The (upstream:remotename) part comes from HankCA's comment, to see if a branch had been pushed (or generally had an upstream equivalent).)


Git 2.13 (Q2 2017) uses a more generic ref-filter API with a more complete git for-each-ref push:

See commit 3d9e4ce, commit 949af06, commit 56b4360, commit 6eac70f, commit 1a34728, commit 1a0ca5e, commit 3a42980, commit 17938f1, commit 3ba308c, commit a798410, commit b180e6f, commit 01f9582, commit 7743fcc, commit ffd921d, commit 99c6a71, commit d4919bb, commit 42d0eb0, commit 4f3e3b3, commit c58fc85 (10 Jan 2017) by Karthik Nayak (KarthikNayak).
(Merged by Junio C Hamano -- gitster -- in commit 93e8cd8, 27 Feb 2017)

push:

The name of a local ref which represents the @{push} location for the displayed ref.
Respects :short, :lstrip, :rstrip, :track, and :trackshort options as upstream does.
Produces an empty string if no @{push} ref is configured.

If lstrip=<N> (rstrip=<N>) is appended, strips <N> slash-separated path components from the front (back) of the refname
(e.g. %(refname:lstrip=2) turns refs/tags/foo into foo and %(refname:rstrip=2) turns refs/tags/foo into refs).

If <N> is a negative number, strip as many path components as necessary from the specified end to leave -<N> path components
(e.g. %(refname:lstrip=-2) turns refs/tags/foo into tags/foo and %(refname:rstrip=-1) turns refs/tags/foo into refs)


Original answer (2014)

Another way will be available with Git 1.9/2/0 (Q1 2014).
See commit b28061c from Ramkumar Ramachandra (artagnon):

for-each-ref: introduce %(upstream:track[short])

Introduce:

  • %(upstream:track) to display "[ahead M, behind N]" and
  • %(upstream:trackshort) to display "=", ">", "<", or "<>" appropriately (inspired by contrib/completion/git-prompt.sh).

Now you can use the following format in for-each-ref:

%(refname:short) %(upstream:trackshort)

to display refs with terse tracking information.

Note that :track and :trackshort only work with "upstream", and error out when used with anything else.


Before Git 2.30 (Q1 2021), a commit and tag object may have CR at the end of each and every line (you can create such an object with hash-object or using --cleanup=verbatim to decline the default clean-up action), but it would make it impossible to have a blank line to separate the title from the body of the message.

Be lenient and accept a line with lone CR on it as a blank line, too.

See commit e2f8958, commit 9f75ce3 (29 Oct 2020) by Philippe Blain (phil-blain).
(Merged by Junio C Hamano -- gitster -- in commit 4c7eb63, 09 Nov 2020)

ref-filter: handle CRLF at end-of-line more gracefully

Helped-by: Junio C Hamano
Helped-by: Eric Sunshine
Signed-off-by: Philippe Blain

The ref-filter code does not correctly handle commit or tag messages that use CRLF as the line terminator.

Such messages can be created with the --cleanup=verbatim option of git commit(man) and git tag(man), or by using git commit-tree(man) directly.

The function find_subpos in ref-filter.c looks for two consecutive LFs to find the end of the subject line, a sequence which is absent in messages using CRLF.
This results in the whole message being parsed as the subject line (%(contents:subject)), and the body of the message (%(contents:body)) being empty.

Moreover, in copy_subject, which wants to return the subject as a single line, '\n' is replaced by space, but '\r' is untouched.

This impacts the output of git branch(man), git tag(man) and git for-each-ref(man) `.

This behaviour is a regression for git branch --verbose(man), which bisects down to 949af0684c ("branch: use ref-filter printing APIs", 2017-01-10, Git v2.13.0-rc0 -- merge listed in batch #1).

Adjust the ref-filter code to be more lenient by hardening the logic in copy_subject and find_subpos to correctly parse messages containing CRLF.

Note: this is improved/fixed/amended in Git 2.39.
No more ^M: -----BEGIN PGP SIGNATURE-----^M.
No more extra line

  -----BEGIN PGP SIGNATURE-----

  ...some stuff...
蓝咒 2024-12-16 23:19:18

另一种选择是,使用 Git 2.41+(2023 年第二季度),适用于所有分支/标签:

git for-each-ref --format="%(refname)" "refs/heads/*" "refs/tags/*" >allrefs &&
sort -r allrefs | head -n 50 >refs
git for-each-ref --format="%(ahead-behind:HEAD)" --stdin <refs

使用 Git 2.41(2023 年第二季度),“git for-each-ref"(man) 学习 '%(ahead-behind:)' 来计算与单个参考的距离点在具有大量批量提交的历史记录。

它显示了两个新功能。


首先: git for-each-ref --stdin

请参阅 提交cbfe360提交 49abcd2, 提交 fd67d14, 提交 2ee11f7, 提交 80c928d, 提交 368d19b, 提交 b2c51b7, 提交 b73dec5(2023 年 3 月 20 日),作者:德里克·斯托利(derrickstolee
请参阅 提交 c08645b(2023 年 3 月 20 日),作者:泰勒·布劳 (ttaylorr)
(由 Junio C Hamano -- gitster -- 合并于 提交 7727da9,2023 年 4 月 6 日)

for-each-ref:添加 <代码>--stdin选项

帮助者:菲利普·伍德
签字人:Derrick Stolee

当用户希望将大量模式输入到 'git for-each-ref'( man) (可能是一长串精确引用)系统对命令行参数的数量经常有限制。

添加新的 --stdin 选项来代替从标准输入读取模式。
添加测试,检查提供 --stdin 时是否将任何无法识别的参数视为错误。
此外,空模式列表会被解释为完整的参考集。

从标准输入读取时,我们动态填充 filter.name_patterns 数组,而不是直接指向“argv”数组。
使用 strvec 时这很简单,因为它以相同的方式以 NULL 结尾。
然后我们直接从 strvec 释放内存。

git for-each-ref 现在包含在其 手册页

--stdin

如果提供了--stdin,则从以下位置读取模式列表
标准输入而不是来自参数列表。


第二: git for-each-ref --format="%(ahead-behind:HEAD)"

for-each-ref:提前添加- 后面的格式原子

签字人:Derrick Stolee

之前的更改实现了 ahead_behind() 方法,包括计算相对于多个提交基础的多个提交提示的提前/落后值的算法。
现在,将该算法集成为 '< 的一部分代码>git for-each-ref'(man) 隐藏在新格式原子后面,前置.
这自然延伸到 'git 分支 '(man) 和 'git 标签'(man ) 内置函数也是如此。

如果需要,此格式允许指定多个碱基,并且所有匹配的引用都会与所有这些碱基进行比较。
因此,无法读取这些原子提供的引用会导致错误。

为了将ahead_behind()方法信息翻译成ref-filter.c,我们必须填充 ahead_behind_count 结构数组。
在 struct ref_array 中,我们存储将传递给 ahead_behind() 的完整数组。
在 ref_array_item 结构体中,我们存储一个指针数组,这些指针指向整个数组中的相关项。
通过这种方式,我们可以在格式化特定项目的输出时直接提取所有相关的前/后值。
它还确保 ahead_behind_count 结构的生命周期与数组的使用时间相匹配。

还在新的 p1300-graph-walks.sh 脚本中添加性能测试。
这对于将来的更多用途很有用,但现在将“git for-each-ref”中的前置计数算法与运行“git rev-list --count'() 处理每个输入。


git for-each-ref 现在包含在其 手册页

前后:

两个整数,用空格分隔,表示数量
比较输出时分别提前和落后提交
引用格式中指定的


它还说明/解释了如何计算提前/落后

commit-reach:实现 advance_behind() 逻辑

合着:Taylor Blau
签字人:Taylor Blau
签字人:Derrick Stolee

完全实现确定一批提交对的提前/落后计数所需的提交计数逻辑。
这是 commit-reach.h< 中的新库方法/代码>

ahead_behind() 的接口使用两个数组。

  • 第一个提交数组包含步行的所有起点的列表。
    这包括所有提示提交基本提交。
  • 第二个数组通过索引指向第一个数组中的提交来指定基本/提示对。
    第二个数组还存储每个对的前后计数结果。

如果需要,ahead_behind() 的此实现允许多个碱基。
即使有多个基础,也只有一次提交行走用于计算提前/落后值,当基础/尖端范围显着重叠时可以节省时间。

这个用于 ahead_behind() 的接口还使得在整个碱基和提示数组上调用 ensure_ Generations_valid() 变得非常容易。
此调用是必要的,因为对提前/落后值进行计数的遍历绝不会多次遍历提交,这一点至关重要。
如果每次提交都没有生成编号,则提交日期偏差可能会导致步行重新访问提交,然后对其进行双重计数。
因此,强烈建议 git advance-behind 仅在具有提交图文件的存储库中运行,该文件涵盖了大多数可到达的提交,并存储了预先计算的生成编号。
如果不存在提交图,则此遍历会慢得多,因为它必须在执行计数逻辑之前遍历 ensure_ Generations_valid() 中的所有可到达的提交。

可以检测生成编号在运行时是否可用,并将实现重定向到不需要此属性的另一个算法。
但是,该实现需要每个基本/尖端对进行一次提交行走,并且由于需要提交日期启发,可能会较慢。
如果有理由包含的话,将来可以考虑这样的实现,但大多数 Git 主机应该已经生成提交图文件作为存储库维护的一部分。
大多数 Git 客户端还应该生成提交图文件作为后台维护或自动 GC 的一部分。

现在,我们来讨论一下提前/落后计数算法。

第一个提交数组被视为起始提交。
该数组中的索引将发挥关键作用。

我们创建一个新的提交板,将提交映射到位图。
对于给定的提交(历史记录中的任何位置),其位图存储与哪些输入提交可以到达该提交相关的信息。
如果起始列表中的第 i 个提交可以到达该提交,则第 i 位将打开。
值得注意的是,这些位图不是存储在 .bitmap 文件中的典型“可达性位图”。
它们不是发出信号表明当前提交可以访问哪些对象,而是发出信号“哪些起始提交可以到达我?”同样重要的是要知道,在我们执行该提交之前,位图不一定是“完整的”。
我们将按代号执行提交遍历,这样我们就可以保证在访问该提交时位图是正确的。

ahead_behind() 方法的开头,我们为每个起始提交初始化位图。
通过启用第 i 个起始提交的第 i 位,我们发出信号“第 i 个提交可以到达自身。”

我们通过将具有最大代数的提交从队列中弹出来遍历提交,保证我们在任何未来的步骤中都不会遍历该提交的子代。

当我们行走时,我们加载当前提交的位图并执行两个主要步骤。

第二步检查当前提交的每个父级,并将当前提交的位图位添加到每个父级的位图。
(如果这是我们第一次看到该父级,我们会为该父级创建一个新的位图。)将这些位添加到父级的位图后,该父级将被添加到遍历队列中。
由于将位传递给父级,当前提交可以保证当且仅当第 i 个提交可以到达当前提交时,在其位图上启用第 i 个位。

步行的第一步是检查当前提交的位掩码并确定提交位于或不位于哪个范围。
由于第二步中的“位推送”,我们保证当前提交位图的第 i 位当且仅当第 i 个起始提交可以到达它时才打开。
对于每个 ahead_behind_count 结构,检查 base_indextip_index 以查看当前位图上是否启用了这些位。
如果恰好启用了一位,则增加相应的“提前”或“落后”计数。
这个增量是我们绝对需要最多遍历一次提交的原因。

与此遍历相关的唯一微妙的事情是检查父级的位图中是否已打开所有位,在这种情况下,它会变得“陈旧”并标有 STALE 位。
这允许queue_has_nonstale()
成为遍历的终止条件,如果所有提交都在历史记录中附近,这将大大减少遍历的提交数量。
当历史记录很深时,它可以避免遍历大量常见提交。
我们还使用辅助方法 insert_no_dup() 将提交添加到优先级队列,而无需多次添加。
这使用了 PARENT2 标志。
因此,我们必须清除所有提交的 STALE 和 PARENT2 位,以防在同一进程中多次调用 ahead_behind()

Another option, with Git 2.41+ (Q2 2023), for all branches/tags:

git for-each-ref --format="%(refname)" "refs/heads/*" "refs/tags/*" >allrefs &&
sort -r allrefs | head -n 50 >refs
git for-each-ref --format="%(ahead-behind:HEAD)" --stdin <refs

With Git 2.41 (Q2 2023), "git for-each-ref"(man) learns '%(ahead-behind:<base>)' that computes the distances from a single reference point in the history with a bunch of commits in bulk.

It shows two new features.


First: git for-each-ref --stdin <refs

See commit cbfe360, commit 49abcd2, commit fd67d14, commit 2ee11f7, commit 80c928d, commit 368d19b, commit b2c51b7, commit b73dec5 (20 Mar 2023) by Derrick Stolee (derrickstolee).
See commit c08645b (20 Mar 2023) by Taylor Blau (ttaylorr).
(Merged by Junio C Hamano -- gitster -- in commit 7727da9, 06 Apr 2023)

for-each-ref: add --stdin option

Helped-by: Phillip Wood
Signed-off-by: Derrick Stolee

When a user wishes to input a large list of patterns to 'git for-each-ref'(man) (likely a long list of exact refs) there are frequently system limits on the number of command-line arguments.

Add a new --stdin option to instead read the patterns from standard input.
Add tests that check that any unrecognized arguments are considered an error when --stdin is provided.
Also, an empty pattern list is interpreted as the complete ref set.

When reading from stdin, we populate the filter.name_patterns array dynamically as opposed to pointing to the 'argv' array directly.
This is simple when using a strvec, as it is NULL-terminated in the same way.
We then free the memory directly from the strvec.

git for-each-ref now includes in its man page:

--stdin

If --stdin is supplied, then the list of patterns is read from
standard input instead of from the argument list.


Second: git for-each-ref --format="%(ahead-behind:HEAD)"

for-each-ref: add ahead-behind format atom

Signed-off-by: Derrick Stolee

The previous change implemented the ahead_behind() method, including an algorithm to compute the ahead/behind values for a number of commit tips relative to a number of commit bases.
Now, integrate that algorithm as part of 'git for-each-ref'(man) hidden behind a new format atom, ahead-behind.
This naturally extends to 'git branch'(man) and 'git tag'(man) builtins, as well.

This format allows specifying multiple bases, if so desired, and all matching references are compared against all of those bases.
For this reason, failing to read a reference provided from these atoms results in an error.

In order to translate the ahead_behind() method information to the format output code in ref-filter.c, we must populate arrays of ahead_behind_count structs.
In struct ref_array, we store the full array that will be passed to ahead_behind().
In struct ref_array_item, we store an array of pointers that point to the relvant items within the full array.
In this way, we can pull all relevant ahead/behind values directly when formatting output for a specific item.
It also ensures the lifetime of the ahead_behind_count structs matches the time that the array is being used.

Also add performance tests in a new p1300-graph-walks.sh script.
This will be useful for more uses in the future, but for now compare the ahead-behind counting algorithm in 'git for-each-ref' to the naive implementation by running 'git rev-list --count'(man) processes for each input.

git for-each-ref now includes in its man page:

ahead-behind:<committish>

Two integers, separated by a space, demonstrating the number of
commits ahead and behind, respectively, when comparing the output
ref to the <committish> specified in the format.


It also illustrates/explains how ahead/behind is computed

commit-reach: implement ahead_behind() logic

Co-authored-by: Taylor Blau
Signed-off-by: Taylor Blau
Signed-off-by: Derrick Stolee

Fully implement the commit-counting logic required to determine ahead/behind counts for a batch of commit pairs.
This is a new library method within commit-reach.h.

The interface for ahead_behind() uses two arrays.

  • The first array of commits contains the list of all starting points for the walk.
    This includes all tip commits and base commits.
  • The second array specifies base/tip pairs by pointing to commits within the first array, by index.
    The second array also stores the resulting ahead/behind counts for each of these pairs.

This implementation of ahead_behind() allows multiple bases, if desired.
Even with multiple bases, there is only one commit walk used for counting the ahead/behind values, saving time when the base/tip ranges overlap significantly.

This interface for ahead_behind() also makes it very easy to call ensure_generations_valid() on the entire array of bases and tips.
This call is necessary because it is critical that the walk that counts ahead/behind values never walks a commit more than once.
Without generation numbers on every commit, there is a possibility that a commit date skew could cause the walk to revisit a commit and then double-count it.
For this reason, it is strongly recommended that git ahead-behind is only run in a repository with a commit-graph file that covers most of the reachable commits, storing precomputed generation numbers.
If no commit-graph exists, this walk will be much slower as it must walk all reachable commits in ensure_generations_valid() before performing the counting logic.

It is possible to detect if generation numbers are available at run time and redirect the implementation to another algorithm that does not require this property.
However, that implementation requires a commit walk per base/tip pair and can be slower due to the commit date heuristics required.
Such an implementation could be considered in the future if there is a reason to include it, but most Git hosts should already be generating a commit-graph file as part of repository maintenance.
Most Git clients should also be generating commit-graph files as part of background maintenance or automatic GCs.

Now, let's discuss the ahead/behind counting algorithm.

The first array of commits are considered the starting commits.
The index within that array will play a critical role.

We create a new commit slab that maps commits to a bitmap.
For a given commit (anywhere in the history), its bitmap stores information relative to which of the input commits can reach that commit.
The ith bit will be on if the ith commit from the starting list can reach that commit.
It is important to notice that these bitmaps are not the typical "reachability bitmaps" that are stored in .bitmap files.
Instead of signalling which objects are reachable from the current commit, they instead signal "which starting commits can reach me?" It is also important to know that the bitmap is not necessarily "complete" until we walk that commit.
We will perform a commit walk by generation number in such a way that we can guarantee the bitmap is correct when we visit that commit.

At the beginning of the ahead_behind() method, we initialize the bitmaps for each of the starting commits.
By enabling the ith bit for the ith starting commit, we signal "the ith commit can reach itself."

We walk commits by popping the commit with maximum generation number out of the queue, guaranteeing that we will never walk a child of that commit in any future steps.

As we walk, we load the bitmap for the current commit and perform two main steps.

The second step examines each parent of the current commit and adds the current commit's bitmap bits to each parent's bitmap.
(We create a new bitmap for the parent if this is our first time seeing that parent.) After adding the bits to the parent's bitmap, the parent is added to the walk queue.
Due to this passing of bits to parents, the current commit has a guarantee that the ith bit is enabled on its bitmap if and only if the ith commit can reach the current commit.

The first step of the walk is to examine the bitmask on the current commit and decide which ranges the commit is in or not.
Due to the "bit pushing" in the second step, we have a guarantee that the ith bit of the current commit's bitmap is on if and only if the ith starting commit can reach it.
For each ahead_behind_count struct, check the base_index and tip_index to see if those bits are enabled on the current bitmap.
If exactly one bit is enabled, then increment the corresponding 'ahead' or 'behind' count.
This increment is the reason we absolutely need to walk commits at most once.

The only subtle thing to do with this walk is to check to see if a parent has all bits on in its bitmap, in which case it becomes "stale" and is marked with the STALE bit.
This allows queue_has_nonstale() to be the terminating condition of the walk, which greatly reduces the number of commits walked if all of the commits are nearby in history.
It avoids walking a large number of common commits when there is a deep history.
We also use the helper method insert_no_dup() to add commits to the priority queue without adding them multiple times.
This uses the PARENT2 flag.
Thus, we must clear both the STALE and PARENT2 bits of all commits, in case ahead_behind() is called multiple times in the same process.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文