查找包含特定提交的合并提交

251
想象以下的历史:
       c---e---g--- feature
      /         \
-a---b---d---f---h--- master

如何找到提交“c”何时合并到主分支(即找到合并提交“h”)?
14个回答

227

将以下内容添加到~/.gitconfig文件中:

[alias]
    find-merge = "!sh -c 'commit=$0 && branch=${1:-HEAD} && (git rev-list $commit..$branch --ancestry-path | cat -n; git rev-list $commit..$branch --first-parent | cat -n) | sort -k2 -s | uniq -f1 -d | sort -n | tail -1 | cut -f2'"
    show-merge = "!sh -c 'merge=$(git find-merge $0 $1) && [ -n \"$merge\" ] && git show $merge'"

然后您可以像这样使用别名:

# current branch
git find-merge <SHA-1>
# specify master
git find-merge <SHA-1> master

要查看合并的提交消息和其他详细信息,请使用相同参数的git show-merge命令。

(基于Gauthier的答案。感谢Rosen Matevjavabrett指出与sort有关的问题。)


8
太棒了!这是最好的即插即用解决方案。 - N Jones
1
请注意,sort -k2 | uniq -f1 -d | sort -n | tail -1 | cut -f2 无法正确找到最后一行的共同点。这里是一个失败的示例。 - Rosen Matev
1
@RosenMatev 当我在你的粘贴上运行它时,它会找到16db9fef5c581ab0c56137d04ef08ef1bf82b0b7,这不是预期的吗?你用的是什么操作系统? - robinst
2
@robinst 这个问题是由于第一个排序(-k2)应用了基于字节的决策器,当第二个字段(提交哈希)相等时,它不会保留输入顺序所导致的。可以通过多种方式停止这种行为:-s|--stable 开关可以实现此目的 - 如果将其添加到 @RosenMatev 的粘贴的排序中,则会按预期进行排序,即结果为 16db9fef5c581ab0c56137d04ef08ef1bf82b0b7 而不是 29c40c3a3b33196d4e79793bd8e503a03753bad1。也可以通过将该排序更改为 sort -k2,2 -k1,1n 来实现此结果。哪个是正确的 - 原始 cat+cat 顺序还是按数字排序? - javabrett
3
@powlo:没什么,这只是为了方便而将两个命令合并成一个。 - robinst
显示剩余14条评论

185

你的示例显示分支 feature 仍然可用。

在这种情况下,h 是以下内容的最终结果:

git log master ^feature --ancestry-path
如果分支feature不再可用,则可以在 cmaster 之间的历史线路中显示合并提交记录:
git log <SHA-1_for_c>..master --ancestry-path --merges

然而,这也将显示在feature分支上从eg和在h之后发生的所有合并。


比较以下命令的结果:

git rev-list <SHA-1_for_c>..master --ancestry-path

git rev-list <SHA-1_for_c>..master --first-parent

将会给您一个SHA-1值,它作为在common中的最后一行。

如果您有可用的话,可以对这些结果使用comm -1 -2。如果您正在使用msysgit,则可以使用以下perl代码进行比较:

perl -ne 'print if ($seen{$_} .= @ARGV) =~ /10$/'  file1 file2

(这段代码来自于http://www.cyberciti.biz/faq/command-to-display-lines-common-in-files/,原始作者是“comp.unix.shell新闻组”的某位成员)。

如果你想将其转换为单行代码,请查看进程替换


5
即使你拥有 comm 命令,也无法使用它,因为 "git rev-list" 命令的输出未按字典顺序排序。当然,你可以在查找共同行之前对每个命令的输出进行排序,但是所需的提交不一定是最后一个。因此,我认为需要类似 perl 命令(尽管晦涩)的东西。 - mhagger
17
我刚刚写了一个名为git-when-merged的脚本,实现了这个建议(还有其他很多功能)。请参见https://github.com/mhagger/git-when-merged。 - mhagger
3
假设某个时刻 master 分支被合并到了 feature 分支,然后立即将 feature 分支以快进模式合并回 master 分支(即 feature 的指针直接替换了 master 的指针)。这会导致 --first-parent 返回错误的父提交吗? - Kelvin
5
我尝试了comm -1 -2,但它没有起作用。comm只能用于排序后的行。(这个Perl一行代码有效,但我看不懂。) - Domon
2
这有点晚了,但是我还是添加一下,以便人们可以发现它的用处,因为git本身不提供这个功能。我认为这个答案没有处理一些特殊情况(请参见我的答案https://dev59.com/q2oy5IYBdhLWcg3wkOsK#43716029,它处理了大多数特殊情况)。以下是一些特殊情况:`git find-merge h master(什么也不返回,但应该返回h),git find-merge d master(返回f,但应该返回d),git find-merge c feature`(返回e,但应该返回g)。 - hIpPy
显示剩余4条评论

29

git-get-merge会定位并显示您正在查找的合并提交:

pip install git-get-merge
git get-merge <SHA-1>

该命令会跟随给定提交的子提交,直到找到一个合并到其他分支(可能是主分支)的合并。

1
对我来说没有起作用,似乎期望一个我们没有的“主”分支。 - s29
@s29 分支可以作为第二个参数传递:git get-merge <SHA-1> origin/master - phd

24
那就是,总结一下Gauthier的帖子:
perl -ne 'print if ($seen{$_} .= @ARGV) =~ /10$/' <(git rev-list --ancestry-path <SHA-1_for_c>..master) <(git rev-list --first-parent <SHA-1_for_c>..master) | tail -n 1

编辑:由于这里使用了进程替换 "<()",它不符合POSIX标准,并且可能无法在您的shell中正常工作。但它可以在bashzsh中工作。


4
我不经常从互联网复制/粘贴代码,但每当我这么做时,它都能完美运行!感谢 Totor 让我无需思考。 - Ben
2
@TheodoreR.Smith 很遗憾,语法<()不兼容POSIX。您需要使用bashzsh或支持进程替换的shell。我已相应地编辑了我的答案。 - Totor
1
对我没用。我想我的特性分支在合并到主分支之前先被合并到了另一个分支。这可能解释了为什么你的命令结果是“合并分支'release_branch'”而不是“合并分支'feature_branch'”。 - Tim Kuipers

18

我需要做这件事,然后不知怎么地找到了git-when-merged(它实际上引用了这个stackoverflow问题,但是Michael Haggerty从未在此处添加他非常好的Python脚本的引用)。所以现在我有了。


我实际上是从他的页面来到这个问题的。 - Flavius

15

在Gauthier的优秀答案基础上,我们不需要使用comm来比较这些列表。由于我们正在寻找--ancestry-path中最后一个结果,该结果也在--first-parent中,因此我们可以直接在前者的输出中使用grep查询后者:

git rev-list <SHA>..master --ancestry-path | grep -f <(git rev-list <SHA>..master --first-parent) | tail -1

或者为了方便和重复使用,这里有一个函数可以插入到.bashrc中:

function git-find-merge() {
  git rev-list $1..master --ancestry-path | grep -f <(git rev-list $1..master --first-parent) | tail -1
}

这对我有用。当输入未排序时,comm 不起作用。 - hIpPy

7
git log --topo-order

然后查找提交之前的第一个合并。


谢谢。这比其他(早期的)答案简单得多! - Noah Sussman

6
我使用下面的 bash 脚本,并将其放在路径 ~/bin/git-find-merge。它基于 Gauthier 的答案evilstreak 的答案,经过一些调整以处理边角情况。当输入未排序时,comm 报错,而 grep -f 则可以正常工作。

边角情况:

  • 如果提交是分支的第一父级路径上的合并提交,则返回提交。
  • 如果提交是分支的第一父级路径上的非合并提交,则返回分支。这要么是快进合并(ff merge),要么是提交只在分支上且没有好的方法来确定正确的提交。
  • 如果提交和分支相同,则返回提交。

~/bin/git-find-merge 脚本:

#!/bin/bash

commit=$1
if [ -z $commit ]; then
    echo 1>&2 "fatal: commit is required"
    exit 1
fi
commit=$(git rev-parse $commit)
branch=${2-@}

# if branch points to commit (both are same), then return commit
if [ $commit == $(git rev-parse $branch) ]; then
    git log -1 $commit
    exit
fi

# if commit is a merge commit on first-parent path of branch,
# then return commit
# if commit is a NON-merge commit on first-parent path of branch,
# then return branch as it's either a ff merge or commit is only on branch
# and there is not a good way to figure out the right commit
if [[ $(git log --first-parent --pretty='%P' $commit..$branch | \
    cut -d' ' -f1 | \
    grep $commit | wc -l) -eq 1 ]]; then
    if [ $(git show -s --format="%P" $commit | wc -w) -gt 1 ]; then
        # if commit is a merge commit
        git log -1 $commit
    else
        # if commit is a NON-merge commit
        echo 1>&2 ""
        echo 1>&2 "error: returning the branch commit (ff merge or commit only on branch)"
        echo 1>&2 ""
        git log -1 $branch
    fi
    exit
fi

# 1st common commit from bottom of first-parent and ancestry-path
merge=$(grep -f \
    <(git rev-list --first-parent  $commit..$branch) \
    <(git rev-list --ancestry-path $commit..$branch) \
        | tail -1)
if [ ! -z $merge ]; then
    git log -1 $merge
    exit
fi

# merge commit not found
echo 1>&2 "fatal: no merge commit found"
exit 1

这让我能够做到这一点:

(master)
$ git find-merge <commit>    # to find when commit merged to current branch
$ git find-merge <branch>    # to find when branch merged to current branch
$ git find-merge <commit> pu # to find when commit merged to pu branch

这个脚本也可以在我的Github上找到。


6

对于Ruby开发者,有git-whence。非常简单。

$ gem install git-whence
$ git whence 1234567
234557 Merge pull request #203 from branch/pathway

1

我使用 Ruby 语言实现了 @robinst 的想法,速度比原来快了两倍(在查找非常旧的提交时尤为重要)。

find-commit.rb

commit = ARGV[0]
master = ARGV[1] || 'origin/master'

unless commit
  puts "Usage: find-commit.rb commit [master-branch]"
  puts "Will show commit that merged <commit> into <master-branch>"
  exit 1
end

parents = `git rev-list #{commit}..#{master} --reverse --first-parent --merges`.split("\n")
ancestry = `git rev-list #{commit}..#{master} --reverse --ancestry-path --merges`.split("\n")
merge = (parents & ancestry)[0]

if merge
  system "git show #{merge}"
else
  puts "#{master} doesn't include #{commit}"
  exit 2
end

您可以像这样使用它:

ruby find-commit.rb SHA master

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