8

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

不知何故,发布分支得到了hyper-merged,或者什么。老实说,我想不出比“穿越溪流”更好的方式来形容它。这是来自 Git Extensions 的视觉效果:

跨流 GitEx

对不起,我混淆了大多数提交评论。但是从底部开始,你可以清楚的看到左边的开发分支和右边的发布分支周期性的合并到里面。直到大约进行到一半,当我必须在合并中做错了什么时,因为那时发布分支确实与开发分支合并 - 它们实际上似乎共享了一个应该只在开发分支上的合并提交。

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

跨流 GitHub

这看起来并不奇怪,除了蓝线和黑线应该是同一个分支。只是为了打勾:

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

  • 提交消息都说同样的事情:“将分支'release-1.3'合并到开发中。” 所以我知道我没有,例如,不小心将开发分支合并到发布分支中。

  • git pull --rebase我从未创建任何新分支或使用除、git commit和之外的任何命令git merge。我没有尝试或尝试对存储库做任何有创意的事情。

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

所以我有两个问题:

  1. 这怎么可能?从技术上理解它是如何可能的,git 提交是图形节点等等,但我不太清楚从程序的角度来看这是如何发生的。我可能犯了什么错误会导致这种情况,我怎样才能避免再次这样做?

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

4

2 回答 2

4

可能性

我能想到这可能发生的几种方式。

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

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


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

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

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

  • 偶然git branch而不是git checkout在我正在处理的一个项目中,另一位刚接触 Git 的开发人员犯了git branch foo一个错误,试图切换到 branch 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. 因为当前分支仍然develop不是release-1.3,Git 会打印“On branch develop”。
    6. 用户大吃一惊,心想:“我不是很久以前切换到release-1.3了吗?哦,我一定忘记切换分支了。”
    7. 用户运行git checkout release-1.3,这次记住了正确的命令。
    8. 用户创建提交并运行git push.
    9. Git 的默认推送行为(参见 参考资料push.defaultgit help configmatching,因此即使release-1.3没有配置上游分支,也会git push选择release-1.3要推送到的上游分支。
    10. 新版本release-1.3是前一个的后代release-1.3,所以远程仓库很乐意接受推送。


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

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

如果 into 的合并developrelease-1.3故意的(有人做出了一个develop足以发货的有意识的决定),这是完全正常和正确的。尽管有视觉上的差异,但蓝色和黑色的线条都在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...

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

如果 into 的合并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...
    

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

  4. 运行git checkout develop
  5. 运行git branch temp以充当此提交的临时占位符。
  6. 运行git reset --hard HEAD^^以从develop分支中删除两个提交:提示develop提交和合并旧版本release-1.3into的提交develop。我们稍后会恢复该提示提交。
  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...
    
  10. 运行git push -f origin release-1.3 develop以强制更新上游分支。

防止这种情况在未来再次发生

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

于 2013-07-11T04:20:54.023 回答
2
  1. 不确定,除了纯粹的猜测。如果您可以提供 agit log --decorate --graph --all --oneline和 a git reflog release-1.3(也许还有 shell 历史记录),我们也许可以提供帮助。

  2. 这确实涉及强制推动。和你的队友谈谈这件事。我不知道您对这些提交的哈希值,因为您没有提供它们。替换<blah>为相应的哈希。

    git branch -m release-1.3 old-release-1.3
    git checkout -b release-1.3 <Un-ign> # the last good commit on the release branch
    # This will replay those five commits onto our new release-1.3 branch
    git rebase --onto release-1.3 <Merge> old-release-1.3 # the merge just above "Un-ign"
    # Verify that release-1.3 now looks correct
    # You'll need the -f. Be careful with force pushes, run -n first
    git push [-n|-f] origin release-1.3
    
于 2013-07-11T00:15:02.873 回答