【问题标题】:How to find all unmerged commits in master grouped by the branches they were created in?如何在 master 中找到所有未合并的提交,这些提交按创建它们的分支分组?
【发布时间】:2013-03-13 11:05:04
【问题描述】:

我必须从未合并的分支创建一些代码审查。

在寻找解决方案时,我们不要去解决本地分支上下文问题,因为这将在服务器上运行;将只有 origin 远程,我总是会在其他命令之前运行 git fetch origin 命令,当我们谈论分支时,我们将参考 origin /分支名称

如果设置很简单,并且源自 master 的每个分支都以自己的方式继续,我们可以运行:

git rev-list origin/branch-name --not origin/master --no-merges

对于每个未合并的分支,并将生成的提交添加到每个分支的每个审查中。

当 2-3 个分支之间存在合并并且其中一些分支继续工作时,就会出现问题。正如我所说,对于每个分支,我想以编程方式创建代码审查,并且我不想在多个审查中包含提交。

主要是减少为每次提交查找原始分支的问题。
或者更简单地说...查找所有未合并的提交,这些提交按它们最有可能创建的分支分组。

让我们专注于一个简单的例子:

      *    b4 - branch2's head
   *  |    a4 - branch1's head
   |  *    b3
   *  |    merge branch2 into branch1
*  |\ |    m3 - master's head
|  * \|    a3
|  |  |
|  |  *    b2
|  *  |    merge master into branch1
* /|  |    m2
|/ |  *    merge branch1 into branch2
|  * /|    a2
|  |/ |
|  |  *    b1
|  | /
|  |/
| /|
|/ |
|  *       a1
* /        m1
|/
|
*          start

而我想要得到的是:

  • 分支 1:a1、a2、a3、a4
  • 分支 2:b1、b2、b3、b4

目前我发现的最佳解决方案是运行:

git show-branch --topo-order --topics origin/master origin/branch1 origin/branch2

并解析结果:

* [master] m3
 ! [branch1] a4
  ! [branch2] b4
---
  + [branch2] b4
  + [branch2^] b3
 +  [branch1] a4
 ++ [branch2~2] b2
 -- [branch2~3] Merge branch 'branch1' into branch2
 ++ [branch2~4] b1
 +  [branch1~2] a3
 +  [branch1~4] a2
 ++ [branch1~5] a1
*++ [branch2~5] m1

输出解释是这样的:

  1. n 行是分析的 n 个分支
  2. 一行与----
  3. 如果提交在第 n 个分支上,则每个提交在第 n 个缩进字符上带有一个加号(或在合并提交的情况下为减号)。
  4. 最后一行是所有分析分支的合并基础

对于第 3 点,提交名称解析以分支名称开头,据我所见,此分支对应于创建提交的分支,可能是通过提升第一父级到达的路径。

由于我对合并提交不感兴趣,因此我将忽略它们。

然后我将解析每个分支路径提交以使用 rev-parse 获取它们的哈希。

我该如何处理这种情况?

【问题讨论】:

标签: git branch commit branching-and-merging


【解决方案1】:

如果我掌握了你的问题空间,认为你可以使用 --sha1-name

git show-branch --topo-order --topics --sha1-name origin/master 原点/分支1 原点/分支2

列出您感兴趣的内容,然后通过 git-what-branch

运行提交

git-what-branch:发现提交在哪个分支上,或者它是如何到达指定分支的。这是来自Seth RobertsonPerl script

并根据您的需要格式化报告?

【讨论】:

  • git-what-branch 已经是一个外部工具。我对构建自己的非常简单的工具/命令很感兴趣。
  • 所以分叉它,然后添加你想要的东西。 ;-) 或者,采用它的功能并将您需要的东西移植到您自己的工具中。
【解决方案2】:

我建议按照您描述的方式进行操作。但我会处理git log --format="%H:%P:%s" ^origin/master origin/branch1 origin/branch2 的输出,这样你就可以更好地进行tree-walking。

  1. 根据输出构建适当的树结构,标记父母和孩子。
  2. 从头部开始(从git rev-parse 获取他们的SHA)。用你来自的头部的名称和距离标记每个提交。
    • 对于非第一父步骤(合并的另一部分),我会将距离加 100。
    • 如果遇到合并提交,请检查它所说的哪个分支合并到哪个分支。在跟踪两个父链接时使用此信息:如果您要转到的分支的解析名称与您当前的 HEAD 不匹配,则将距离添加 10000。
    • 对于双方的父母:您现在知道他们的名字了。将他们作为第一父母的所有孩子添加到字典中:commit -> known-name
  3. 获取已知提交的字典并开始向上走(朝向孩子,而不是父母)。从合并到分支的距离中减去 10000。在进行此步行时,不要进行您不是第一父母的提交,并在您到达分支点(有两个孩子的提交)时立即停止。如果你碰到你的一个分支头,也要停下来。

现在,对于您的每个提交,您将获得到分支头的距离值列表(可能为负数)。对于每次提交,距离最短的分支是最有可能创建提交的分支。

如果你有时间,你可能想遍历整个历史,然后减去 master 的历史——如果你的分支之前已经合并到 master 中,结果可能会稍微好一些。


无法抗拒:制作了一个 Python 脚本,可以按照我的描述进行操作。但是有一个变化:每正常一步,距离不是增加,而是减少。这样做的效果是,在合并点之后寿命更长的分支是首选,我个人更喜欢这一点。这里是:https://gist.github.com/Chronial/5275577

用法:只需运行git-annotate-log.py ^origin/master origin/branch1 origin/branch2检查结果的质量(将输出带有注释的git日志树)。

【讨论】:

  • 感谢@Chronial 为这个问题付出的努力。这不是我所需要的,但赏金归你所有,因为你帮助了赏金的用途。
  • 谢谢,但请注意:我的 python 脚本与name-rev 的作用相同,只是以更复杂的方式适用于更多情况。例如:feature1 上的 8 次提交 - 在提交 4 时,您的 feature2 分支并在该分支上执行 1 次提交。您的方法会将 feature1 上的前 4 次提交提交给 feature2
【解决方案3】:

这个问题没有正确答案,因为它没有详细说明。

Git 历史只是一个有向无环图 (DAG),通常不可能确定 DAG 中两个任意节点之间的语义关系,除非这些节点被充分标记。除非您可以保证示例图中的提交消息遵循可靠的、机器可解析的模式,否则提交没有充分标记——如果没有额外的上下文(例如,保证您的开发人员遵循),不可能自动识别您感兴趣的提交某些最佳做法)。

这是我的意思的一个例子。您说提交a1branch1 相关联,但这不能仅通过查看示例图的节点来确定。曾几何时,您的示例存储库历史可能如下所示:

      *    merge branch1 into branch2 - branch2's head
      |\
     _|/
    / *    b1
   |  |
   |  |
  _|_/
 / |
|  *       a1
* /        m1
|/
|
*          start - master's head

请注意,branch1 在上图中还不存在。上图可能源于以下一系列事件:

  1. branch2 是在共享存储库中的 start 创建的
  2. 用户#1 在他/她的本地 branch2 分支上创建 a1
  3. 同时,用户#2 在他/她的本地 branch2 分支上创建 m1b1
  4. user#1 将他/她的本地branch2 分支推送到共享存储库,导致共享存储库中的branch2 引用指向a1
  5. user#2 尝试将他/她的本地 branch2 分支推送到共享存储库,但这失败并出现非快进错误(branch2 当前指向 a1 并且不能快速-转发到b1)
  6. user#2 运行git pull,将a1 合并到b1
  7. user#2 出于某种莫名其妙的原因运行 git commit --amend -m "merge branch1 into branch2"
  8. user#2 推送,共享存储库历史最终看起来像上面的 DAG

一段时间后,用户#1 从a1 创建branch1 并创建a2,而用户#2 快进将m1 合并到master,导致以下提交历史记录:

      *    merge a1 into b1 - branch2's head
   *  |\   a2 - branch1's head
   | _|/
   |/ *    b1
   |  |
   |  |
  _|_/
 / |
|  *       a1
* /        m1 - master's head
|/
|
*          start

鉴于这一系列事件在技术上是可能的(尽管不太可能),人类怎么能更不用说 Git 告诉你哪些提交“属于”哪个分支?

解析合并提交消息

如果你可以保证用户不会更改合并提交消息(他们总是接受 Git 默认的),并且 Git 从来没有也永远不会更改默认的合并提交消息格式,那么可以使用合并提交的提交消息作为a1 开始于branch1 的线索。您必须编写一个脚本来解析提交消息 - 没有简单的 Git 单行程序可以为您执行此操作。

如果合并总是有意的

或者,如果您的开发人员遵循最佳实践(每次合并都是有意的,并且旨在引入不同名称的分支,从而导致存储库没有 those stupid merge commits created by git pull),并且您对来自已完成子项的提交不感兴趣分支,然后您感兴趣的提交位于第一父路径上。如果您知道哪个分支是您正在分析的分支的父分支,则可以执行以下操作:

git rev-list --first-parent --no-merges parent-branch-ref..branch-ref

此命令列出可从 branch-ref 访问的提交的 SHA1 标识符,不包括可从 parent-branch-ref 访问的提交以及从子分支合并的提交。

在上面的示例图中,假设父顺序由您的注释决定,而不是由进入合并提交的行的顺序决定,git rev-list --first-parent --no-merges master..branch1 将打印提交 a4、a3、a2 和 a1 的 SHA1 标识符(按该顺序;如果您想要相反的顺序,请使用--reverse),git rev-list --first-parent --no-merges master..branch2 将打印提交 b4、b3、b2 和 b1 的 SHA1 标识符(同样,按该顺序)。

如果分支有明确的父子关系

如果您的开发人员没有遵循最佳实践,并且您的分支中到处都是由 git pull(或等效操作)创建的愚蠢合并,但您有明确的父/子分支关系,那么编写一个脚本来执行以下操作算法可能对你有用:

  1. 查找所有可以从感兴趣的分支到达的提交,不包括来自其父分支、其父的父分支、其父的父分支等的所有提交,并保存结果。例如:

    git rev-list master..branch1 >commit-list
    
  2. 对感兴趣的分支的所有子、孙等分支执行相同的操作。例如,假设branch2 被认为是branch1 的孩子:

    git rev-list ^master ^branch1 branch2 >commits-to-filter-out
    
  3. 从步骤#1的结果中过滤掉步骤#2的结果。例如:

    grep -Fv -f commits-to-filter-out commit-list
    

这种方法的问题在于,一旦子分支合并到其父分支中,即使子分支上的开发继续进行,这些提交也会被视为父分支的一部分。虽然这在语义上是有意义的,但它不会产生你想要的结果。

一些最佳实践

这里有一些最佳做法可以让这个特定问题在未来更容易解决。大多数(如果不是全部)这些都可以通过巧妙地使用共享存储库中的挂钩来实施。

  1. 每个分支只有一个任务。禁止多项任务。
  2. 绝不允许在子分支合并到其父分支后继续开发。合并意味着任务完成,故事结束。对预期问题的回答:
    • 问:如果我在子分支中发现错误怎么办? A:从父级开始一个新的分支。不要在子分支上继续开发。
    • 问:如果新功能还没有完成怎么办? A:那你为什么要合并分支?也许你合并了一个完整的子任务;如果是这样,剩余的子任务应该在他们自己的分支上离开父分支。不要在子分支上继续开发。
  3. Forbid the use of git pull
  4. 除非所有子分支都已合并到其中,否则不得将子分支合并到其父分支中。
  5. 如果分支没有任何子分支,请考虑rebasing it onto the parent branch before merging with --no-ff。如果它确实有子分支,您仍然可以变基,但请保留子分支的 --no-ff 合并(这比应有的更棘手)。
  6. 经常将父分支合并到子分支,使合并冲突更容易解决。
  7. 避免将祖父分支直接合并到其孙分支 - 先合并到子分支,然后将子分支合并到孙子分支。

如果您的所有开发人员都遵循这些规则,那么很简单:

git rev-list --first-parent --no-merges parent-branch..child-branch

您只需要查看在该分支上进行的提交减去在其子分支上进行的提交即可。

【讨论】:

  • 这是一个想法,但遗憾的是,与我合作的开发人员根本没有遵循最佳实践。
  • @AlexandruPătrănescu:那么不幸的是,你运气不好——Git 没有万无一失的方法来自动确定合并是否会带来来自不同“真实”分支的提交(应该排除的提交)来自git pull 或等效操作(应包括的提交)创建的愚蠢分支。这需要人工判断,因此您必须手动对异常进行硬编码(例如,通过使用 git notes 注释提交)。
【解决方案4】:

可以使用--mirror 克隆存储库,这会创建一个裸存储库,可以用作原始存储库的镜像,并且可以使用git remote update --prune 进行更新,之后应删除此功能的所有标签。

我是这样实现的:
1.获取未合并到master的分支列表

git branch --no-merged master

2。对于每个分支,获取该分支而不是主分支的修订列表

git rev-list branch1 --not master --no-merges

如果列表为空,则从分支列表中删除分支
3.对于每个修订,确定原始分支

git name-rev --name-only revisionHash1

并匹配^([^\~\^]*)([\~\^].*)?$ 的正则表达式。第一个模式是分支名称,第二个是分支的相对路径。
如果找到的分支名称不等于初始分支,则从列表中删除修订。

最后我得到了一个分支列表,每个分支都有一个提交列表。


经过更多的 bash 研究,它可以通过以下方式全部完成:

git rev-list --all --not master --no-merges | xargs -L1 git name-rev | grep -oE '[0-9a-f]{40}\s[^\~\^]*'

结果是表单中的输出

hash branch

可以读取、解析、排序、分组或其他。

【讨论】:

    猜你喜欢
    • 2013-09-21
    • 2020-05-04
    • 1970-01-01
    • 1970-01-01
    • 2017-04-26
    • 1970-01-01
    • 2018-04-03
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多