git:限制提交次数的累积差异

8

git log有一些非常有用的提交限制选项,例如--no-merges--first-parent。我希望在生成一系列提交的累积差异补丁/统计信息/数量统计时能够使用这些选项。

使用以下命令:

git log --oneline --first-parent --no-merges --patch   29665b0..0b76a27
git log --oneline --first-parent --no-merges --stat    29665b0..0b76a27
git log --oneline --first-parent --no-merges --numstat 29665b0..0b76a27

差异不是累积的(更改针对每个提交单独列出)。

使用以下命令:

git diff --patch   29665b0..0b76a27
git diff --stat    29665b0..0b76a27
git diff --numstat 29665b0..0b76a27

这个差异是累积的,但不幸的是git diff不支持限制提交选项。

所以我想要的是git diff的累积差异功能和git log的提交限制功能相结合。

我有一个想法是使用git log生成一系列提交哈希值,然后将该列表传输到git diff中,以生成指定提交的累积差异。类似于以下内容(显然,将哈希传输到git diff的方法实际上是行不通的):

git log --pretty=format:%h --first-parent --no-merges 29665b0..0b76a27 | git diff

其中--pretty=format:%h会输出匹配提交的哈希值。


更新

感谢@torek和@twalberg,我现在更清楚地理解了git diff的操作。范围语法29665b0..0b76a27确实是误导性的,我现在明白它实际上并没有对一系列提交进行累积差异比较。通过查看文档,我找到了这个:

"diff"是关于比较两个终点,而不是范围,而范围符号(<commit>..<commit><commit>...<commit>)并不意味着范围,如在gitrevisions(7)中定义的“指定范围”部分。

考虑到这一点,我将重新表述我的问题。使用以下命令:

git log --oneline --first-parent --no-merges --patch   29665b0..0b76a27
git log --oneline --first-parent --no-merges --stat    29665b0..0b76a27
git log --oneline --first-parent --no-merges --numstat 29665b0..0b76a27

每个匹配的提交都列出了单独的更改。如何将这些单独的更改组合起来,以生成累积补丁/状态/ numstat?
链接可能的重复问题的答案很有帮助,建议解决方案:创建一个临时分支,挑选相关提交,然后生成差异。
我刚刚发布了一个使用此技术的答案,但我仍然想知道是否有不需要临时分支的解决方案?

1
差异是成对的。为了改变一个流行的说法: “重要的不是旅程, 而是目的地” (和起点)。 - jub0bs
2个回答

3
这里至少有一个基本误解。具体来说,git diff并不是真正的累加:相反,它只是成对出现的。
具体而言,这两个命令执行的是相同的操作:
git diff rev1 rev2
git diff rev1..rev2

git diff 中,实际上根本不存在所谓的范围。

话说回来,让我们来看看git log的幕后。实际上,git log对于一个范围的处理是将其交给git rev-list,然后在处理过程中应用修饰符,生成该范围内每个版本的列表:

git rev-list 29665b0..0b76a27

0b76a27开始,输出所有可达的提交记录,但是这些提交记录不可由29665b0到达。添加--first-parent--max-parents=1(也就是--no-merges)等参数可以过滤掉一部分将会列出的提交记录。
最终结果返回给git log,然后按照git rev-list所输出的顺序依次查看每个提交记录,这也可以通过--date-order--topo-order等参数进行控制;详见git rev-list文档。同时,可能会显示每个日志条目以及由git diff-tree生成的差异(对于单父提交,则比较该提交与其父提交之间的差异)。
你可以直接调用 git rev-list 命令,并从输出中提取出顶部和底部的修订版本。在这种特殊情况下,你可能还需要使用 --topo-order 参数,以确保最后一个修订版本在图形上真正是最早的,而与日期无关。例如,在脚本中:
#! /bin/sh
tempfile=$(mktemp -t mydiff)
trap "rm -f $tempfile" 1 2 3 15
git rev-list 29665b0..0b76a27 --first-parent --no-merges --topo-order > $tempfile
# remember that the first rev listed is the last rev in the range
last=$(head -1 $tempfile)
first=$(tail -1 $tempfile)
rm -f $tempfile # done with it, don't leave it around while showing diff
git diff $first $last

你可以使用git rev-parse解析选项并将其拆分为diff选项和rev-list选项,这样可以更加高级,但这已经超出了你在这里所需的范围。要改进的主要是摆脱硬编码的修订范围。

1一些 git 命令确实会将参数传递给 git rev-list,因为它们只是使用 git rev-list 和其他 git 命令处理的 shell 脚本。其他命令是构建在一起的,所以 git loggit rev-list 实际上是一个单独的二进制文件,其中一部分将任务交给另一部分,但不会调用新程序。

无论如何,请注意,git log master 只是将 master 传递给 git rev-list,后者会生成从分支标签 master 可达的所有修订版本的列表。如果添加 --no-walkgit rev-list 只生成一个修订版本,因此 git log 仅显示该修订版本。


3
除了会破坏许多既有的(但格式不正确)代码外,就我个人而言,我宁愿 git 的开发人员禁止在 git diff (以及其他几个不适用的地方)中使用范围符号,并避免这种混淆。 - twalberg

0
# Create a temporary branch to mark the start of the cherry-picked commits
git branch tmpstart

# Create and checkout a temporary branch for the cherry-picked commits
git checkout -b tmpend

# Use git log to filter the range of commits with the desired
# commit-limiting options, and then cherry-pick each matching commit
git log \
    --first-parent \      # Commit-limiting
    --no-merges \         # Commit-limiting
    --reverse \           # Reverse the order (ascending chronological order)
    --pretty=format:%h \  # Output the abbreviated hash of each matching commit
    29665b0..0b76a27 \    # Range of commits
  | xargs -n 1 git cherry-pick

# Generate the patch/stat/numstat of the cherry-picked commits
git diff --patch   tmpstart tmpend
git diff --stat    tmpstart tmpend
git diff --numstat tmpstart tmpend

我现在才看到更新...... 如果你只想应用那些特定的提交(然后显示补丁、统计等),你几乎必须这样做(重复 cherry-picks)。你可以通过直接使用 git rev-list 而不是 git log 来稍微改进这个过程,也可以通过使用“分离 HEAD”模式来构建分支来避免临时分支,但实际上它们都是一样的。 - torek

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