git - 在变基之前进行合并压缩

25
我有两个分支:mastertest-branch(它是从master分支派生的)。我的工作看起来像以下内容:
  1. git checkout master
  2. git checkout -b test-branch
  3. 进行一堆更改并提交它们
  4. 进行更多的更改并再次提交
  5. git checkout master
  6. git pull-> 其他人已经在master上进行了更改
  7. git checkout test-branch
  8. git rebase -i master
  9. 将交互式控制台中除第一个 pick 以外的所有命令改为 s
  10. 我需要解决两个合并冲突,一个是第一个提交的合并冲突,另一个是第二个提交的合并冲突

我想要做的是,在变基之前压缩test-branch上的所有提交,以便我只需解决一次合并冲突。这是可能的吗?如果可以,怎么办?

答:在重新基于主分支之前,将test-branch上的所有提交压缩到一个提交中,可以使用以下命令: git rebase -i master,将交互式窗口中的所有pick除第一个外的命令改为squashs。然后保存并退出编辑器。Git会自动合并提交并打开另一个编辑器让你输入一个新的提交消息。在这里,您可以编写一个新的、有意义的提交消息,以便在需要时更好地跟踪更改历史记录。完成后,请保存并关闭编辑器,并使用git push --force命令强制将更改推送到远程分支上。

你可以在test-branch中使用git rebase -i master,然后选择压缩除最近的提交以外的所有提交。这是你要找的吗? - larsks
1
糟糕,那应该是“master”。我的错。已经修复了。@larsks,我确实做到了。我压缩了除第一个提交以外的所有内容,但仍然需要解决超过1个合并冲突。 - Paymahn Moghadasian
2个回答

19
“这是可能的,甚至很容易。Git有很多不同的方法来实现它。”
“按照你最初建议的字面意思:”
“......在变基之前压缩测试分支上的所有提交”
“如果您在分支master上运行git merge之前进行操作,那么这将是最简单的方法。(我知道您没有将git merge列为命令,但是在您的第6步中运行了git merge:”
“因为git pull只是git fetch后跟git merge。) 但是在之后做这件事还是相当容易的; 我们只需要针对正确的提交ID进行操作。”
“让我们画出您在第4步的提交图形:”
...<- o <- *            <-- master, origin/master
             \
               A <- B   <-- HEAD=test-branch

这幅图表明有两个标签1指向我标记为*的提交,即masterorigin/master。提交*回指到提交o,它又指回更多的提交:这就是分支master的历史。
你创建的分支的标签(现在你正在该分支上,因此有HEAD=部分)指向提交B。提交B然后指向提交A,它再指向提交*。这就是分支test-branch的历史:你在*处创建了它,然后添加了A,再添加了B
以下是两种简单合并提交AB的方法:
  1. git rebase -i master

    这将为您提供一个交互式编辑会话,您可以在其中“选择”第一个提交,然后“压缩”第二个提交,并将两个提交消息汇集在一起,并让您按照通常的方式编辑结果。然后它将创建一个(单一的)新提交,其树是提交B的树。

  2. git reset --soft master; git commit

    这不会为rebase打开交互式编辑会话:它只会保留从提交B中的暂存区和树(这是git reset --soft的一部分),直接将标签test-branch移回到指向提交*(这是git reset的一部分),并像往常一样创建新提交(git commit)。

    缺点是您必须组合一个新的提交消息,但是您可以以任何多种方式从提交AB中恢复提交消息。例如,您可以使用-c-C标志来执行git commit(您必须识别提交AB,例如使用@{1}@{1}^@{yesterday}或其他任何reflog说明符)。或者,在执行git reset --soft之前,您可以使用git log并将日志消息保存在文件中,或者采取其他措施。

    (当您不仅需要压缩两个提交而是需要压缩42个提交时,此第二种方法非常有用。)

这两种方法实际上做的是同样的事情:它们添加了一个新的提交(我们称之为AB),使AB变得灰色,但留下来了。我无法正确绘制:
              AB        <-- HEAD=test-branch
             /
...<- o <- *            <-- master, origin/master
             \
               A <- B   [ghost version of test-branch]

您的分支的幽灵版本对大多数正常使用者是不可见的,并且最终(默认情况下约30天后)会被垃圾回收。 (在此之前,它仍然位于您的存储库和引用日志中,以便您可以找到原始提交AB,以防您需要它们。)

如果您已经完成了第6步怎么办? 在这种情况下,您仍然必须确定提交*。 您可以按照我撰写本文时jsexpert建议的方法进行操作,或者您可以使用git merge-base找到它:

$ mergebase=$(git merge-base HEAD master)
# then pick just ONE of the next two
$ git rebase -i $mergebase
$ git reset --soft $mergebase; git commit

这是如何操作的。在执行git checkout master; git fetch; git merge; git checkout test-branch之后(步骤5和6左右),您的提交图现在看起来更像这样:
...<- o <- * <- o       <-- master, origin/master
             \
               A <- B   <-- HEAD=test-branch

那个新的o提交,即master和origin/master指向的一个或多个提交,“挡住了路”,但是test-branch(你现在所在的分支)和master的“合并基础”是提交*:它们两个分支分叉之前最近的共享提交。
然后,我们只需将rebase或reset --soft目标设置为该提交。完成后,我们会得到一个新的AB提交,如下所示:
              AB        <-- HEAD=test-branch
             /
...<- o <- * <- o       <-- master, origin/master
             \
               A <- B   [ghost version of test-branch]

一旦您有了压缩的 AB 提交,您就可以像往常一样将其 git rebasemaster 上。
请注意,另一个答案正在做完全相同的事情,只是通过计算提交来识别提交 *。如果您知道在 test-branch 的末尾和“有趣”的提交*之间有两个提交,则HEAD~2$(git merge-base HEAD master)标识相同的提交。使用git merge-base 只是避免了计数。
"1参考文献"是git的通用术语。在这种情况下,它们是分支名称,并且我使用单词"label"来区分它们与此提交图形形成的分支历史不同。在git中,“分支”一词用于至少这两个不同的事物,这很容易混淆。"

这个压缩解决方案非常适合在rebase期间避免来自多个提交的相同冲突重复。我想知道是否有一种方法可以保留原始提交而不将它们压缩成一个,以避免在rebase期间重复相同的冲突? - Johnson
@Johnson:有一种替代方案:Git可以记录并重复使用合并冲突的解决方案。执行此操作的命令是git rerere,通常您根本不直接运行它。 在使用时要小心:Git只通过文本字符串工作,而不是实际知识,并且可能会重复使用不适当的记录解决方案。但这仍然比反复解决相同冲突要好得多。 - torek

5
当然可以,你只需要输入以下命令:git rebase -i HEAD~<要合并的提交次数>。其中-i选项表示交互式rebase。执行该命令后,你将看到一个vi编辑器窗口,其中包含按提交进行操作的说明。有关此功能的详细信息,请参见 Rewriting history。请注意不要改动html标签。

所以我会运行git rebase -i HEAD~<# of commits to squash>,这可以用来修改test-branch的历史记录,使其只有一个提交(通过压缩),然后运行git rebase master将单一提交的功能分支重新基于主分支。正确吗? - Paymahn Moghadasian
没错。压缩您想要提交的更改,然后执行 pull --rebase 命令以重新设置您的更改。请阅读此文章以了解如何操作。http://gitready.com/advanced/2009/02/11/pull-with-rebase.html - CodeWizard
据我所知,这里的答案对于更复杂的Git历史记录并不适用,因为它包含了自原始分支点以来的合并。这个答案可能是相关的,但唯一不需要使用zsh的解决方案涉及手动查看图形可视化并复制正确的提交哈希值。唉! - 75th Trombone

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