如何列出包含相同提交的分支

27
在之前的问题中,有人提供了一个关于如何找到包含完全相同提交的分支的答案: 如何列出包含给定提交的分支 接受的答案强调了这仅适用于完全相同的提交ID,而不适用于相同的提交。进一步说明了Git Cherry可以用来解决这个问题。
Git Cherry似乎是为了反向操作而设计的,用于找到未推送到上游的提交。如果我不知道是哪个分支创建了它,以及什么是上游,那么这个功能就没什么用了。所以我不明白它如何帮助解决这个问题。
有人能解释一下/提供一个使用git cherry来找到包含特定提交的'等效'的所有分支的示例吗?

2
我建议编写一个脚本,使用 git rev-listgit patch-id 来确定。你可能还想解析 git cherry-pick 在提交消息中留下的注释,因为 patch-id(也是 git cherry 的基础)并不完美,如果你解决了任何冲突,它将会失效。 - Chronial
1
那么,你就完了。如果你想确切地知道哪些分支包含哪些提交,只使用合并而不是挑选。你可以假设具有相同提交消息的提交可能是相同的,并对它们的差异进行一些奇怪的启发式验证该假设。但这个过程容易出错,你必须自己编写代码。 - Chronial
3
这虽然与主题无关,但我认为这很重要:关于rebase存在一个常见误解。如果你想让分支容易合并,只需要让拥有者将主干(master)合并到它上面。当你进行rebase时,你不仅得到了一个线性的历史记录,而且还会得到一个谎言。中间的提交可能已经不能正常工作了,因为在rebase期间没有人测试。这可能会使你的历史记录变得非常无用。另一方面,真正的合并可以告诉你哪些部分是独立开发的,这也是有用的信息。Git不是svn,Git根本不需要线性。 - Chronial
一种可能性是仅通过提交的主题进行比较,这样“几乎等价”的提交就会显示出来(即使使用git cherry也不行)。在这种情况下,使用差异查看器的git log是一个选项:https://stackoverflow.com/a/46127413/1959808 - 0 _
@IoannisFilippidis,说实话,我不知道你所说的“只有通过变基才能区分”的意思是什么,但这非常依赖于上下文,需要知道你从哪个分支进行了分支等等。我大致明白你的意思,但对我来说,更简单的方法似乎是只做变基,然后无关紧要的编辑/重复提交将被丢弃或产生冲突,您可以跳过它们或接受它们...祝你好运,无论你想做什么 :-) - UpAndAdam
显示剩余9条评论
4个回答

22

在回答哪些分支包含等效提交的问题之前,您需要确定“哪些提交是等效的”。一旦确定了这一点,您只需在每个提交上使用git branch --contains即可。

不幸的是,没有100%可靠的方法来确定等效提交。

最可靠的方法是检查提交引入的变更集的补丁 ID。这就是git cherrygit log --cherrygit log --cherry-mark所依赖的东西。在内部,它们都调用了git patch-id。补丁 ID 只是规范化差异的 SHA1。引入相同变更的任何提交都将具有相同的补丁 ID。此外,引入大部分相同变更的任何提交(仅在空格或文件中应用的行号方面存在差异)都将具有相同的补丁 ID。如果两个提交具有相同的 Patch ID,则几乎可以保证它们是等效的-您几乎永远不会通过补丁 ID 获得虚假正面结果。然而,虚假阴性经常发生。每当您执行git cherry-pick并手动解决合并冲突时,您可能会引入变更集中的差异。即使更改了一个字符,也会导致生成不同的补丁 ID。

检查补丁 ID 需要编写脚本,如 Chronial 所建议。首先使用类似下面的内容计算原始提交的补丁 ID

(注意-脚本未经测试,但应该足够接近工作)

origCommitPatchId=$(git diff ORIG_COMMIT^! | git patch-id | awk '{print $1}')

现在你需要搜索你的历史中的所有其他提交,并计算它们的补丁ID,看看是否有相同的。

for rev in $(git rev-list --all)
do
   testPatchId=$(git diff ${rev}^1..${rev} | git patch-id | awk '{print $1}')
   if [ "${origCommitPatchId}" = "${testPatchId}" ]; then
      echo "${rev}"
   fi
done
现在你已经有了SHA列表,可以将它们传递给git branch -a --contains
如果由于合并冲突而上述方法对你无效怎么办?
好的,你还可以尝试几件事情。通常情况下,当你挑选一个提交时,提交中的原始作者姓名、电子邮件和日期字段会被保留。因此,你将得到一个新的提交,但作者信息将是相同的。
所以你可以从你的原始提交中获取这些信息。
git log -1 --pretty="%an %ae %ad" ORIG_COMMIT

与之前一样,您需要浏览历史记录中的每个提交,将相同的信息打印出来并进行比较。这可能会给您带来一些额外的匹配。

您还可以使用 git log --grep=ORIG_COMMIT,它会查找引用了 ORIG_COMMIT 的提交消息的任何提交。

如果以上都不起作用,您可以尝试查找使用 pickaxe 引入的特定行,或者使用 git log --grep 查找在提交消息中可能是唯一的其他内容。

如果这听起来很复杂,那么确实如此。这就是为什么我告诉人们尽可能避免使用 cherry-pick。 git branch --contains非常有价值、易于使用且100%可靠。没有其他解决方案能够接近。


谢谢,这正是我所期望和寻找的东西。 - UpAndAdam

8
以下代码似乎可行(但未经过多次测试)。它运行git cherry命令对每个本地的git分支进行检查,并在git cherry未将提交列为缺失时打印分支名称。
# USAGE: git-cherry-contains <commit> [refs]
# Prints each local branch containing an equivalent commit.
git-cherry-contains() {
    local sha; sha=$(git rev-parse --verify "$1") || return 1
    local refs; refs=${2:-refs/heads/}
    local branch
    while IFS= read -r branch; do
        if ! git cherry "$branch" "$sha" "$sha^" | grep -qE "^\+ $sha"; then
            echo "$branch"
        fi
    done < <(git for-each-ref --format='%(refname:short)' $refs)
}

请查看Andrew C的帖子,了解 git cherry 实际工作原理的精彩解释(使用git patch-id)。


1
谢谢!脚本运行良好,更新了它以允许指定要搜索的 ref 文件夹(这样我们就可以通过上游 refs 进行搜索):git-cherry-contains 0adbcd refs/remotes/origin/ - odinho - Velmont
正是我想要的。这里是你的.gitconfig文件的别名等效项:cherry-contains = "!f(){ local sha=$(git rev-parse --verify \"$1\") || return 1; local refs=${2:-refs/heads/}; local branch; git for-each-ref --format='%(refname:short)' $refs | while IFS= read -r branch; do if ! git cherry \"$branch\" \"$sha\" \"$sha^\" | grep -qE \"^\\+ $sha\"; then echo \"$branch\"; fi; done; };f" - Andy

1

命令

使用以下Bash命令(将<COMMIT HASH>替换为您要查找的提交哈希):

PATCH_ID=$(git show <COMMIT HASH> | git patch-id | cut -d' ' -f1) \
&& ALL_MATCHING_COMMIT_HASHES=$(git log --all -p | git patch-id | grep $PATCH_ID | cut -d' ' -f2) \
&& for HASH in $ALL_MATCHING_COMMIT_HASHES; do echo "$(git branch -a --contains $HASH) (commit $HASH)"; done 

示例输出
user@host test_cherry_picking $ PATCH_ID=$(git show 59faabb91cfc8e449737f93be8c7df3825491674 | git patch-id | cut -d' ' -f1) \
&& ALL_MATCHING_COMMIT_HASHES=$(git log --all -p | git patch-id | grep $PATCH_ID | cut -d' ' -f2) \
&& for HASH in $ALL_MATCHING_COMMIT_HASHES; do echo "$(git branch -a --contains $HASH) (commit $HASH)"; done

* hotfix (commit 59faabb91cfc8e449737f93be8c7df3825491674)
master (commit bb5fa0d16931fa1d5fa9f5e9ee5c27634fad7da8)

user@host test_cherry_picking $

描述

计算给定GIT修订参数(例如提交的哈希值)的PATCH ID。然后找到所有具有计算PATCH ID的提交。最后将包含这些提交的所有分支名称打印到控制台中。

当然,只要PATCH ID对于所有(cherry-pick)提交相同,此方法才有效。每次进行cherry-pick并手动解决合并冲突时,都可能引入变更集中的差异。这会导致不同的PATCH ID。


我发现对于相同的挑选变更,git show <COMMIT_HASH> 不会产生相同的补丁 ID 输出。根据 https://dev59.com/9Ggu5IYBdhLWcg3wEzDP#45373500 的建议,我尝试使用 git diff-tree -p 作为替代方案,并获得了确定性的相同结果。 - Chris Cleeland

0
$ for i in `git rev-list --all --grep="something unique in the commit message"`; do git branch --all --contains $i; done | sort | uniq

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