在`git log -G foo`的输出中显示分支。

6
如何在 git log -G foo 的输出中显示包含此提交的分支?目前看起来是这样的:
commit a24dc0cd5403b697634976f2f7eef4aa7af61b3d
Author: Thomas Guettler <foo@thomas-guettler.de>
Date:   Mon Aug 8 11:16:30 2022 +0200

    use timezone, and two tests for one day.

commit 8ffe418ff64b899958cf9da594852a13dc993673
Author: Thomas Guettler <foo@thomas-guettler.de>
Date:   Mon Aug 8 11:11:29 2022 +0200

    removed debug code.


我很愿意它看起来像这样:

commit a24dc0cd5403b697634976f2f7eef4aa7af61b3d
Author: Thomas Guettler <foo@thomas-guettler.de>
Date:   Mon Aug 8 11:16:30 2022 +0200
Branches: feature-foo

    use timezone, and two tests for one day.

commit 8ffe418ff64b899958cf9da594852a13dc993673
Author: Thomas Guettler <foo@thomas-guettler.de>
Date:   Mon Aug 8 11:11:29 2022 +0200
Branches: main feature-foo

    removed debug code.


你需要编写该格式。关于输出分支的部分,你可能需要启动一个子命令来检索git branch --contains <hash>。你目前有什么进展? - Romain Valeri
@RomainValeri 到目前为止,我查看了手册并在谷歌上搜索解决方案。我曾经使用过 Git 的 Python 绑定,因此我可以用脚本来解决这个问题。但也许已经有一个解决方案,我还没有找到。 - guettli
我觉得你在这个问题上设置赏金,但没有给一些回答者任何反馈,有点遗憾。通常,反馈可以指导他们改进答案,并更精确地关注你需要的内容。你还让宽限期过期了,这有点奇怪。我只能得出结论,你对自己的Python解决方案非常满意,所以这些都不再有任何兴趣。但也许你可以这么说,那么其他人就可以把自己的时间节省下来做其他事情了。没有什么损失,我只是感到困惑。 - kriegaex
@kriegaex 是的,你说得对。我感到内疚。在过去的几天里我一直不在。抱歉。 - guettli
3个回答

3

Git没有这个功能,但是你可以通过在每个提交中执行git show来实现与git log基本相同的功能。之所以要按提交进行操作,是因为git log可能需要一些时间才能找到每个提交,而与此同时,您可以查看已经找到的提交的输出。此外,您可以使用less并在滚动时查看它们。

我们只需要按git show本身的格式添加所需的信息即可:

git log --format='%H' "$@" | while read -r id; do
    IFS=$'\n' read -r -d '' -a branches < <(git branch --format='%(refname:short)' --contains "$id")
    git --no-pager show --format="commit %H%nAuthor: %an <%ae>%nDate: %ad%nBranches: ${branches[*]}%n%n%w(76,4,4)%B" --quiet "$id"
done

为了让它成为一个正式的命令,您可以在任何 $PATH 中创建一个二进制文件 git-log-branches,然后可以使用 git log-branches 调用它。此外,您可以在 less <<(...) 中包装该命令,以便像其他 git 命令一样分页显示。
#!/bin/bash

less -FRX < <(
...
)

我看到了kriegaex的时间比较,但这并不现实,问题的重点是要寻找一个带有-G的正则表达式。如果我们在一个真实的代码库中实际使用它,很大一部分时间将花费在等待输入上,并且解决方案的工作方式也会非常不同。
在一个有11874个提交的真实代码库中,进行-G搜索返回28个提交,但获取所有提交需要一段时间(约10秒)。使用分页器,我们无需等待,可以在第一个提交可用时立即看到它,而在这种情况下,从启动到第一个提交可用的时间,这两个解决方案提供的结果非常不同:
- kriegaex:4.688秒 - felipec:0.167秒
即使没有提交限制和分页器,kriegaex的版本也慢11%。
我创建了一个测试脚本,该脚本比较了专门为此构建的具有1000个提交的代码库中的所有版本。
id seconds diff
felipec 5.72 0%
felipec (原始版本) 5.02 14%
kriegaex 6.44 -11%
kriegaex (新版本) 5.10 12%

即使是kriegaex的新版本也不如我的原始版本快。我没有提交我的原始版本的唯一原因是代码更加复杂,而优势并不是很大(只有14%的速度提升),但这里提供给您供参考:

while read -r -d $'\0' commit; do
    id=${commit#commit }
    id=${id%%$'\n'*}
    IFS=$'\n' read -r -d '' -a branches < <(git branch --format='%(refname:short)' --contains "$id")
    echo -e "${commit/BRANCHES/${branches[*]}}\n"
done < <(git log --format="tformat:commit %H%nAuthor: %an <%ae>%nDate: %ad%nBranches: BRANCHES%n%n%w(76,4,4)%B%w()%x00" "$@")

抱歉,我不同意。问题不是关于如何使用“-G”,OP已经知道如何做了。问题是如何将每个提交所属的分支信息合并到日志输出中。这就是为什么我在我的答案中没有使用“-G”。对于许多日志条目,我的解决方案比你的更快,并且我还在一个具有许多提交的实际开源项目中进行了测试。我还在我的答案中写道,根据您的用例,您的解决方案和我的解决方案都可以进行性能调整。您的解决方案针对“-G”进行了调整,这解释了更好的性能 - 毫不奇怪。 - kriegaex
我并没有在“抨击”你的解决方案,你的方案是可行的,但我的更好。原因在于它考虑到在实际仓库中查找相关提交可能需要时间,并且提交限制是 git log 的主要目的之一。 - FelipeC
我同意你的解决方案在页面可分性方面更好,因为它逐个显示结果。相对于所有找到的提交的总处理时间,我的速度比你的快约20%。如果从我们两个脚本的整体结果中减去最后一个测量中git -G所花费的时间,这也是正确的。因此,“更好”的定义取决于使用情况。如果用户想要分页结果,则你的解决方案肯定更好。如果她想要更好的性能,则我的略微领先。20%并不是很大,但也不是没有任何作用。 - kriegaex
抱歉重复评论,但我不确定您是否在阅读我的答案下的评论:我想出了一个最佳方案,当测量所有已处理提交的总执行时间并启用逐个提交显示输出时,它的表现与我的原始方案一样好。 - kriegaex
有趣的是,升级到git版本2.37.3.windows.1、GNU bash版本5.1.16(1)-release (x86_64-pc-msys)、less 590(PCRE正则表达式)似乎已经解决了这个问题。 - kriegaex
显示剩余14条评论

2

更新的答案

我有一个简单的想法,既包含了原始答案相对于FelipeC的 ~20% 的性能优势(避免了对 git log(一次)和 git show(每个提交)的调用),又具备了FelipeC更好的页面处理能力(找到的提交一个接一个地处理,而不是在处理整个输出后再进行处理,这是我的原始答案所不具备的)。

这通过不再使用GNU sed的子shell执行模式来实现,而是逐行读取输出,在“分支:8ba14...”一行中替换Git哈希为git branch --contains 8ba14...的结果。在这个简单的解决方案中,我甚至不再使用sed或awk,只使用Bash内置的子字符串功能:

#!/usr/bin/bash

#less -FRX < <(
git --no-pager log --quiet --pretty=format:"commit %H%nAuthor:   %an <%ae>%nDate:     %ad%nBranches: %H%n%n%w(76,4,4)%B" "$@" | while read -r LINE; do
  if [ "${LINE:0:10}" = "Branches: " ]; then
    echo "Branches: $(git branch --contains ${LINE:10} --format='%(refname:short)' | tr '\n' ' ')"
  else
    echo "$LINE"
  fi
done
#)

根据您自己的喜好取消注释less分页。感谢FelipeC与我讨论并比较我们各自的原始解决方案,帮助我提出了这个改进版本。

原始回答

默认情况下,Git无法在其漂亮的格式字符串中嵌入shell脚本。这是一个快速且简单的解决方案。它有点像您想要的格式,但没有颜色,也没有Git在某些情况下可能打印的一些额外信息。但我希望它能给您提供继续和完善它的线索。

前提条件:

  • 您使用类UNIX的shell,我在Windows上尝试了Git Bash。
  • 您使用GNU sed。

在您的配置文件或直接在控制台中定义此shell函数:

git_log_branches() {
  git log --pretty=format:"commit %H%nAuthor:   %an <%ae>%nDate:     %ad%nBranches: %H%n%n%w(76,4,4)%B" "$@" |
    sed -E "s/^(Branches: )(.*)/echo -n '\1'; git branch --contains \2 --format='%(refname:short)' | tr '\n' ' '/e"
}

日志输出将类似于以下内容:
$ git_log_branches -2 HEAD~100
commit d81a845b61f5b98b217722122c6005cb51f9e160
Author:   Alexander Kriegisch <aaaa@bbb.xy>
Date:     Sun Jun 6 13:27:03 2021 +0700
Branches: main openj9-jit openj9-jit-exclude

    Integration test POM (group ID) + UML whitespace cosmetics

commit 25eafcc93340ee2ee6ce05d0ec1a2139e20d45d8
Author:   Florian Lasinger <ggg@hhh.yz>
Date:     Fri Feb 19 13:57:05 2021 +0100
Branches: main openj9-jit openj9-jit-exclude

    [#92] Dependency artifacts have higher precedence than reactor artifacts

    (cherry picked from commit f32367b3 + additional comment)

输出结果不会自动分页。我不太喜欢这个解决方案,但是在玩了一会儿后,这是我能想到的最好的解决方案。


更新:FelipeC在他的答案中提到的那样,你当然也可以将shell函数转换为独立的shell脚本,命名为git-log-branches并将其放置在PATH中的任何位置,这样Git就可以找到它:

#!/usr/bin/bash

git log --pretty=format:"commit %H%nAuthor:   %an <%ae>%nDate:     %ad%nBranches: %H%n%n%w(76,4,4)%B" "$@" |
  sed -E "s/^(Branches: )(.*)/echo -n '\1'; git branch --contains \2 --format='%(refname:short)' | tr '\n' ' '/e"

然后,您可以使用git log-branches -2 HEAD~100进行调用,以获取与调用相应的shell函数时完全相同的日志输出。


我还比较了日志调用的时间:

# OK, we have a history of 577 commits
$ git log --oneline | wc -l
577

# Generating a standard Git log is really quick!
$ time (git log | wc -l)
5471

real    0m0.100s

# As expected, kriegaex's solution is way slower. This is the
# price you pay if you want the branches for each commit.
$ time (git log-branches | wc -l)
6591

real    0m56.953s

# The performance of FelipeC's solution is in the same order of magnitude,
# just slightly slower. No big deal. Both solutions could be tweaked
# here or there.
$ time (git log-branches2 | wc -l)
6592

real    1m8.982s

关于 -G foo 的更新:当使用 -G 时,Git 中的筛选过程占用了大部分时间,结果要处理的提交较少。因此,衡量整体性能 - 不是在这里讨论显示第一个匹配项所需的时间 - 并没有真正帮助。我的上面的测量更有意义,因为它们影响到将在具有更多条目的日志中处理的所有提交。但是,值得一提的是,现在数字看起来像这样:

$ time (git log -G foo --oneline | wc -l)
19

real    0m0.401s

$ time (git log-branches -G foo | wc -l)
216

real    0m2.683s

$ time (git log-branches2 -G foo | wc -l)
217

real    0m2.691s

再次强调,这两种解决方案所需时间相近,但比普通的git log更多。这并不意外。

现在我们切换到一个更大的开源项目 - 我选择了Eclipse AspectJ - 并在其中搜索-G foo。幸运的是,在主分支的8607个提交中,有707个包含“foo”,即不仅仅是一小部分。因此,性能测试实际上可以对比两个脚本与Git本身的性能:

# 8,607 commits in total
$ time (git log --oneline | wc -l)
8607

real    0m0.464s

# 707 commits contain "foo"
$ time (git log -G foo --oneline | wc -l)
707

real    0m55.196s

# Script by kriegaex
$ time (git log-branches -G foo | wc -l)
5450

real    1m15.749s

# Script by FelipeC
$ time (git log-branches2 -G foo | wc -l)
5451

real    1m19.283s

在这里,我们可以看到处理所有707个提交所需的75(kriegaex)或79(FelipeC)秒中,实际上几乎相同,有55秒仅用于Git过滤包含8607个提交的大型存储库。也就是说,55/75或73.3%的时间被Git使用,我们不再仅比较实际脚本性能。当通过-G找到更少的提交时,该比率将渐近地提高到100%。这就是为什么最初我没有使用昂贵的-G选项进行测量,因为它只会污染结果。


一个通用的Bash建议:当您想传递参数列表时,请使用双引号"$@" - LeGEC
好观点。就像我在回答开始时所说的那样,这只是一个快速而简单的解决方案,而不是一个复杂的解决方案。但我会进行更新。 - kriegaex
请注意我的更新,关于如何将脚本作为Git子命令调用(感谢_FelipeC_),以及两种答案的性能比较。 - kriegaex
我再次更新了答案,添加了更多的日志和时间记录。此外,我调整了输出格式以符合FelipeC的解决方案,以便在比较日志文件时更容易进行比较,这是我在本地所做的。例如,现在我的日志文本也有缩进,我的分支列表是用空格分隔而不是逗号分隔,尽管我更喜欢后者。但现在脚本的输出是同步的。 - kriegaex
还有一个更新:我想出了一个最佳方案,当测量所有已处理提交的总执行时间时,它的表现与我的原始方案一样好,并且使输出能够像_FelipeC_的解决方案一样逐个提交显示。 - kriegaex
我的原始版本(我没有提交)确实将git log的输出逐个提交进行了转换,但我没有提交它,因为我找到了一个更简单的代码(而且性能从来不是要求)。我编写了一个脚本,创建了一个存储库来运行测试,并将所有版本与100个提交进行比较。即使您的新版本,也比我的原始版本慢1.6%。 - FelipeC

1

获取包含这些提交的分支的一种方法是添加--simplify-by-decoration

# to make sense of how branches relate one to another :
git log --graph --oneline --simplify-by-decoration -G foo

# you can also add '-p' to see the content of selected commits, and you will see that
# the commits are not named individually

# to get just the ref names :
git log --format="%D" --simplify-by-decoration -G foo

上述命令会列出出现在你的历史记录中的任何 ref(本地分支、远程分支、标签、stash ...)。
你可以添加 --decorate-refs=refs/heads 来仅列出本地分支(或者是 --decorate-refs=whatever/suits/your/needs)。

[更新]

我刚刚发现 git log 有一个 --source 选项(自 1.6.1 版以来就存在...):

--source
打印每个提交所达到的命令行中给定的 ref 名称。

所以:

# to list local branches :
git log --branches -G foo

# or, to list remote branches :
git log -G foo $(git branch -r --format="%(refname:short)")

可能会给您带来有趣的结果。

再次提醒,正如@kriegaex所指出的那样,这并不列出包含每个提交的所有分支,它让git选择要显示的一个分支名称,并且这个选择可能与您的预期不符。

通过在git log中提供显式的分支列表(或标签等),您可以缩小git可选择的名称列表。


这并没有回答楼主的问题。他想要包含某个提交的所有分支。 - kriegaex
@kriegax:我同意你所说的技术部分。实际上,每个分支都包含了它之后的所有提交,因此如果一个提交足够旧,所有的分支都会被列出来。也许楼主会对知道git log内置的一些过滤列出的分支的功能感兴趣,而不是列出“属于feature-33、feature-34、feature-35、……feature-95”。请注意,这种过滤也并不完美。 - LeGEC

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接