18

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

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

如果设置很简单,并且源自 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 获取它们的哈希。

我该如何处理这种情况?

4

4 回答 4

18

可以克隆存储库,--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-04-02T15:36:02.963 回答
4

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

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

这是我的意思的一个例子。您说 commita1与 相关联branch1,但这不能仅通过查看示例图的节点来确定。曾几何时,您的示例存储库历史可能看起来像这样:

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

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

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

一段时间后,user#1 创建branch1offa1并创建a2,而 user#2 快进合并m1master,导致以下提交历史记录:

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

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

解析合并提交消息

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

如果合并总是有意的

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

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

此命令列出可访问的提交的 SHA1 标识符,branch-ref排除可访问的提交parent-branch-ref和从子分支合并的提交。

在上面的示例图中,假设父顺序由您的注释确定,而不是由进入合并提交的行的顺序决定,git rev-list --first-parent --no-merges master..branch1将打印提交 a4、a3、a2 和 a1 的 SHA1 标识符(按该顺序;使用--reverseif您想要相反的顺序),并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. 禁止使用git pull
  4. 除非所有子分支都已合并到其中,否则不得将子分支合并到其父分支中。
  5. 如果分支没有任何子分支,请考虑在合并之前将其重新定位到父分支--no-ff。如果它确实有子分支,您仍然可以变基,但请保留--no-ff子分支的合并(这比应有的更棘手)。
  6. 经常将父分支合并到子分支中,使合并冲突更容易解决。
  7. 避免将祖父分支直接合并到其孙分支中——先合并到子分支中,然后将子分支合并到孙子分支中。

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

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

您只需要查看在该分支上所做的提交减去在其子分支上所做的提交。

于 2013-03-30T14:49:47.870 回答
4

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

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

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

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

并格式化报告以满足您的需求?

于 2013-03-28T15:26:20.210 回答
3

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

  1. 从输出中构建适当的树结构,标记父母和孩子。
  2. 从头开始走(从 获取他们的 SHA git rev-parse)。用你来自的头部的名称和距离标记每个提交。
    • 对于非第一父步骤(合并的另一部分),我会在距离上加 100。
    • 如果您遇到合并提交,请检查它对哪个分支合并到哪个分支的说明。在跟踪两个父链接时使用此信息:如果您要访问的分支的解析名称与您当前的 HEAD 不匹配,则将距离添加 10000。
    • 对于父母双方:你现在知道他们的名字了。将他们作为第一父母的所有孩子添加到 dict: 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 日志树)。

于 2013-03-30T03:47:44.863 回答