我是如何穿越溪流的,我怎么能穿越它们呢?

Aar*_*ght 8 git

我一直在愉快地使用git一年的大部分时间,我们的团队正在使用git-flow模型(尽管不是git-flow本身).上周我创建了一个发布分支,并且很乐意将这些更改合并到开发/不稳定分支中 - 直到今天发生了一些非常奇怪的事情.

不知何故,发布分支得到了超合并,或者其他什么.老实说,我不能想到一个更好的方式来描述它而不是"穿越溪流".这是来自Git Extensions的视觉效果:

交叉流GitEx

抱歉,我正在混淆大部分提交评论.但是,从底部开始,你可以清楚地看到在开发分支和发布分支的右边是定期合并到它.直到大约一半时,我必须对合并做错了,因为在那一点上,发布分支实际上与开发分支合并 - 它们实际上似乎共享一个应该只在开发分支上的合并提交.

GitHub提供了一个更清晰的视图:

交叉流GitHub

这看起来并不那么不寻常,除了蓝线和黑线应该是同一个分支.只是为了勾勒出明显的:

  • 有来自其他贡献者的提交,但有关的具体提交来自我.我知道我可以信任提交消息.

  • 提交信息说着同样的事情:"合并分支'释放-1.3’进发展." 所以我知道我没有,例如,意外地将开发分支合并到发布分支中.

  • 我从来没有创建的任何新的分支机构或使用比任何其他的命令git pull --rebase,git commitgit merge.我没有尝试或尝试使用存储库做任何有创意的事情.

看起来像我放弃了原来的release-1.3分支,创建从一个新的develop中间附近分支.但我没有这样做.该release-1.3分支一直活跃.我发誓蓝色和黑色提交确实是同一个分支,除了将它们合并到开发(粉红色)分支之外,我从未做过任何合并.

所以我有两个问题:

  1. 这怎么可能呢?我从技术上理解它是如何可能的,git提交是图形节点和所有这些,但我无法从程序的角度弄清楚这是如何发生的.我可能犯了什么错误导致这种情况,我怎么能避免再次这样做?

  2. 我如何解开这些分支,以便在我的干净发布分支中没有来自不稳定分支的一堆半完成代码?它应该看起来像偶尔通过合并连接的两条平行线.我希望在没有强制推送或破坏性rebase的情况下执行此操作,因为这是一个共享存储库,尽管它是一个小团队和一个私有存储库,因此如果绝对必要,我可以让人们重新克隆.

Ric*_*sen 4

可能性

我可以想到几种可能发生这种情况的方法。

  • 快进合并。 我认为这是最有可能的原因。这就是我相信发生的事情:

    1. release-1.3被合并到develop
    2. 紧接着,develop被快进合并到release-1.3


    就这样。 release-1.3被合并到,然后在对合并到的某人develop进行任何额外提交之前。如果可能,默认情况下会进行快进合并(一个“功能”,一半时间会做错误的事情),这就是图表看起来令人困惑的原因。release-1.3developrelease-1.3git

    请注意,快进合并在结果图中没有留下直接证据。与常规合并不同,快进合并不会创建新的提交对象,并且不会修改现有提交。快进合并只是调整分支引用以指向现有提交。

    尽管没有直接证据表明快进合并,但您可以通过遵循每个分支的第一个父路径来到达一次合并提交,这一事实强烈表明这是快进合并的结果。

  • 偶然git branch而不是git checkout在我正在从事的一个项目中,另一位刚接触 Git 的开发人员git branch foo在尝试切换到分支时犯了一个错误foo。这是一个完全自然的错误,即使是专家在疲劳时也会犯这个错误。不管怎样,这个错误最终导致了一些看起来像快进合并的东西,尽管git merge从未输入过。这里也可能发生类似的事情。场景是这样的:

    1. 用户已develop签出分支,并且develop恰好指向处于这个谜团中心的合并提交。
    2. 用户希望切换到release-1.3分支做一些工作,但不小心输入git branch release-1.3git checkout release-1.3.
    3. 因为它是一个新的克隆,所以还没有本地release-1.3分支(仅origin/release-1.3)。因此,Git 愉快地创建了一个名为 的新本地分支release-1.3,该分支指向同一个合并提交。
    4. 当用户编辑某些文件时,会过去一段时间。
    5. 准备提交时,用户运行git status. 因为当前分支 is stilldevelop而不是release-1.3,Git 打印“Onbranchdevelop”。
    6. 用户吃了一惊,心想:“我不是早就切换到release-1.3了吗?哦,好吧,我一定是忘记切换分支了。”
    7. 用户运行git checkout release-1.3,这次记住了正确的命令。
    8. 用户创建提交并运行git push
    9. Git 的默认推送行为(参见push.default参考资料git help config)是matching,因此即使release-1.3没有配置上游分支,也会选择要推送到的git push上游分支。release-1.3
    10. 新版本release-1.3是前一个版本的后代release-1.3,因此远程存储库愉快地接受了推送。


    这就是生成您在问题中提供的图表所需的全部内容。

假设develop被有意合并到release-1.3

如果合并是有意的(有人有意识地做出了足以发布的决定),那么这是完全正常和正确developrelease-1.3develop尽管视觉上存在差异,但蓝线和黑线都在树枝上release-1.3;蓝线恰好也在树枝develop

唯一错误的是,对于回顾历史来弄清楚发生了什么的人来说有点尴尬(否则你不会有这个问题)。为了防止这种情况再次发生,请遵循以下经验法则:

  • 如果您要合并两个不同名称的分支(例如,developrelease-1.3,则始终执行git merge --no-ff.
  • 如果您要合并同一分支的两个版本(例如,developorigin/develop),则始终执行git merge --ff-only。如果由于无法快进而失败,那么就该进行了git rebase

如果您遵循上述经验法则,那么图表将如下所示:

*   (develop)
| * (release-1.3)
* | Merge...
|\|
| * Added...
| * using ...
* | adding...
| * Hide s...
* | Date ...
* | updati...
* | Candi...
| * Locali...
| * <---- merge commit that would have been created by
|/|      'git merge' had you used the '--no-ff' option
* | Merge...
|\|
| * Un-ign...
| * Added...
* | Merge...
|\|
| * Remov...
| * Move...
* | Fixed...
Run Code Online (Sandbox Code Playgroud)

请注意额外的合并提交如何使历史记录更具可读性。

如果合并developrelease-1.3是一个错误

哎哟!看起来你需要做一些重新调整和强制推动的事情。这不会让该存储库的其他用户感到高兴。

修复方法如下:

  1. 跑步git checkout release-1.3
  2. 找到中间提交的 sha1(两个分支汇聚在一起的地方)。我们就这样称呼它吧X
  3. 跑步git rebase --onto X^2 X。生成的图表将如下所示:

    *     (develop)
    |   * (release-1.3)
    *   | Merge...
    |\  |
    | * | Added...
    | | * Added...
    | * | using ...
    | | * using ...
    * | | adding...
    | * | Hide s...
    | | * Hide s...
    * | | Date ...
    * | | updati...
    * | | Candi...
    | * | Locali...
    |/  * Locali...
    *  / Merge...
    |\|
    | * Un-ign...
    | * Added...
    * | Merge...
    |\|
    | * Remov...
    | * Move...
    * | Fixed...
    
    Run Code Online (Sandbox Code Playgroud)

    这修复了release-1.3分支,但请注意您现在有两个版本的release-1.3提交。接下来的步骤将从develop分支中删除这些重复项。

  4. 跑步git checkout develop
  5. 运行git branch temp以充当此提交的临时占位符。
  6. 运行git reset --hard HEAD^^以从分支中删除两个提交develop:提示提交和将旧版本合并到develop的提交。我们稍后将恢复该提示提交。release-1.3develop
  7. 运行git merge --no-ff release-1.3^以将新release-1.3分支及其祖先上的第二次提交合并到develop.
  8. 运行git cherry-pick temp以恢复在步骤 #6 中删除的提示提交。
  9. 运行git branch -D temp以删除临时占位分支。您的图表现在应如下所示:

    *   (develop)
    | * (release-1.3)
    * | Merge...
    |\|
    | * Added...
    | * using ...
    * | adding...
    | * Hide s...
    * | Date ...
    * | updati...
    * | Candi...
    | * Locali...
    * | Merge...
    |\|
    | * Un-ign...
    | * Added...
    * | Merge...
    |\|
    | * Remov...
    | * Move...
    * | Fixed...
    
    Run Code Online (Sandbox Code Playgroud)
  10. 运行git push -f origin release-1.3 develop以强制更新上游分支。

防止将来再次发生这种情况

如果您可以控制上游存储库并且可以安装一些钩子,则可以创建一个钩子,通过从新版本的分支开始并遍历第一个父路径来拒绝无法访问旧版本分支的任何推送。这还有一个优点,可以拒绝 .创建的那些愚蠢的提交git pull