Git:如何列出从特定分支创建的所有分支?

4
我需要生成一份报告,其中包含所有从develop分支(在过去的某个时间点从master分支中派生)创建的分支名称。我的方法是使用git log --pretty=format:"%h" master..develop获取develop中的第一个和最后一个提交,然后使用带有git branch --contains <hash>的提交范围。
但是,以上方法存在一些问题:
  1. 我需要运行两个命令才能知道要在查询中考虑的提交范围。
  2. 我将不得不重复执行git branch --contains命令以获取范围内的每个哈希。
  3. git branch --contains命令的输出将因大多数提交在分支之间共享而包含大量冗余结果。
我希望有更好、更直接的方法来完成这个任务。下面我将尝试说明感兴趣的情况:
  master
    ^
    |                      develop
a---b                        ^
     \                       |
      --c--d---e---f---g--h--i
           \      \    \      \
            k-l    m    n      o--p
              ^    ^    ^         ^
              |    |    |         |
         branch_1  | branch_3     |
                 branch_2       branch_4

// Desired output:
branch_1
branch_2
branch_3
branch_4

请注意,分支develop也包含提交ab。根据您的图表,我猜测您的意思是develop指向提交i,因此包含i及其所有祖先(一直到a)。 - torek
是的,没错。但我以为 git log --pretty=format:"%h" master..develop 会给我显示从 c 开始的提交记录。也许我误解了这个命令。 - mbadawi23
是的,master..develop 的意思是 所有从 develop 可达的提交,减去所有从 master 可达的提交。 我只是想说我会用不同的方式来表示它。 - torek
为什么你不能直接运行 git branch --contains c 呢?所有的 branch_1 到 branch_4 都包含它。 - tkruse
1
如果这是一个例行任务,我建议您通过post-update钩子记录分支的创建。 - ElpieKay
2个回答

6
分支名称仅指向一个提交。该分支中较早的所有提交可以通过从该点向后遍历图形来找到。这就是例如 master..develop 的工作原理:它选择是develop祖先的提交,但不包括是master祖先的提交。

(注意,在例如以下情况中:

          o--o   <-- master
         /
...--o--o
         \
          *--*--*   <-- develop

两个点符号的 master..develop 语法包括三个带星号的提交。我认为你想要问的是:
for each branch name $branch:
    if $branch identifies a commit that is a descendant of any
               of the commits in the set `master..develop`:
        print $branch

但是任何一个作为后代的提交,比如提交i,根据定义都是提交c的后代。因此,正如tkruse 在评论中指出的那样:
git branch --contains <hash-of-c>

应该足够。要在此处找到要使用的提交,您可以运行git rev-list --topo-order master..develop | tail -1。(如果使用--reversehead -1,则可能会出现管道破裂失败,因此tail -1方法更好。令人恼火的是,您无法将--reverse-n 1组合使用。)

您可能担心这种情况会出现故障:

          o--o   <-- master
         /
...--o--o--1--o--o   <-- branch-X
         \  \
          2--*--*   <-- develop
           \
            o   <-- branch-Y

您可能希望包括branch-Xbranch-Y,但是如果它们是master..develop范围内两个不同提交的后代,则无法通过单个git branch --contains操作找到它们,这里分别表示为12。在这种情况下,您确实需要多个git branch --contains操作,或者可以选择另一种方法:
set -- $(git rev-list master..develop)
git for-each-ref --format='%(refname)' refs/heads | while read ref; do
    branch=${ref#refs/heads/}
    tip=$(git rev-parse $ref)
    take=false
    for i do
        if git merge-base --is-ancestor $i $tip; then
            take=true
            break
        fi
    done
    $take && echo $branch
done

(未经测试且至少修复了一个错误)。但是这比简单的git branch --contains测试要慢得多。这里的逻辑是确定输出的rev-list中的任何提交哈希ID是否是所讨论的分支末端的祖先。

(请注意,这将打印develop——git merge-base --is-ancestor认为提交是自己的祖先——因此您可能需要显式跳过该提交。)

(您可以通过找到“仅”适当的基础提交来大大加快此过程。例如,在上面的示例中,只有两个。然后,您可以在这些提交上使用--contains并联合命名的分支。要使用的基数是master..develop中的任何合并提交的数量和结构的函数。可能可以使用git rev-list --boundary找到“仅”边界提交;我没有尝试过。)

补充

我意识到在午餐时有一种简单的方法可以找到用于--contains--is-ancestor测试的最小提交集。从范围中的所有提交的完整列表开始,按拓扑顺序与父项排序,例如:

set -- $(git rev-list --reverse --topo-order master..develop)

那么我们从一个空的“必须检查的提交”列表开始:
hashes=

现在,对于所有可能哈希值列表中的每个哈希ID进行测试,例如:

if ! is_descendant $i $hashes; then
    hashes="$hashes $i"
fi

其中is_descendant指:

# return true (i.e., 0) if $1 is a descendant of any of
# $2, $3, ..., $n
is_descendant() {
    local i c=$1
    shift
    for i do
        # $i \ancestor $c => $c \descendant $i
        git merge-base --is-ancestor $i $commit_to_test && return 0
    done
    return 1
}

循环完成后,$hashes 包含最小的提交集,可用于 --contains--is-ancestor 测试。


我不确定$i$tip是否总是有效的提交。因为我经常遇到“fatal: --is-ancestor takes exactly two commits”错误。 - mbadawi23
git rev-listgit rev-parse 的输出是一个有效的提交(或一组有效的提交),尽管 git rev-list 可以添加修饰符(例如 --boundary--left-right)。使用 -x 来观察脚本的运行。 - torek
哦,git for-each-ref 打印了三个项目,我会修复的。 - torek

6
您可以使用一个命令轻松实现这一点:
git log --ancestry-path  --branches --not $(git merge-base --all master develop) \
        --pretty=%D  --simplify-by-decoration --decorate-refs=refs/heads

那就是“只显示所有跟踪到master和develop的当前合并基础的分支装饰”。可以通过使用类似于awk '{print $NF}' RS=', |\n'的内容来使结果更美观。请注意,这个(像@torek的答案一样)假定你在管理存档级别稳定性的repo中执行此操作,分支名称与提交之间没有任何必要的关系,更不用说永久的、全局的或唯一的关系了。如果您想将提交永久地与某些外部管理记录联系起来,请在提交消息中执行,而不是在分支名称中。编辑:如果您想快速查看自从master-develop拆分以来的当前分支结构的概述。
git log --graph --decorate-refs=refs/heads --oneline \
         --branches --simplify-by-decoration \
         --ancestry-path --boundary --not `git merge-base master develop`

啊,我喜欢这个。 - torek

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