git - 变基前压缩

git - squash before rebasing

我有两个分支:mastertest-branchmaster 的分支)。我的工作看起来像下面这样:

  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 上的所有提交,这样我只需要解决一次合并冲突。这可能吗?如果是,怎么做?

当然可以,yoiu只需要输入:git rebase -i HEAD~<# of commits to squash>

-i 用于交互式变基。执行此操作后,您将看到 vi,其中包含每次提交的下一步操作说明。

有关它的详细信息 post 可以在此处找到 Rewriting history:

这是可能的,甚至很容易。 Git 就是这样,有很多不同的方法可以做到。

按照你最初的建议去做:

... squash all of the commits on test-branch before rebasing

如果你在之前运行宁git merge在分支master上这样做是最简单的。 (我知道您没有将 git merge 列为命令,但您在第 6 步中将 运行 git merge 列为命令:

  1. git pull -> out other people have made changes master

因为 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

    这为您提供了一个交互式编辑会话,您可以在其中 "pick" 第一个提交然后 "squash" 第二个,它会将两个提交消息聚集在一起并让您编辑结果,在通常的方式。然后它进行一个(单个)新提交,其树是提交 B.

  2. git reset --soft master; git commit

    不会为rebase打开一个交互式编辑会话:它只是防止staging-area-and-tree提交B(那是[= git reset --soft 的 44=] 部分),将标签 test-branch 移回指向直接提交 *(即 git reset --softgit reset 部分),并使像往常一样新提交 (git commit).

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

    (当您有 42 个左右的 squash 而不是只有两个提交时,第二种方法会大放异彩。)

这两种方法实际上做同样的事情:它们添加一个 new 提交(我们称之为 AB),留下 AB 后面有点灰了,我真的画不对:

              AB        <-- HEAD=test-branch
             /
...<- o <- *            <-- master, origin/master
             \
               A <- B   [ghost version of test-branch]

你的分支的幽灵版本对大多数正常使用是不可见的,最终(默认情况下 30 天左右)被垃圾收集掉。 (在那之前它仍然在你的存储库和你的引用日志中,这样你就可以找到原始提交 AB 以备不时之需。)

如果您已经完成了第 6 步怎么办?在那种情况下,您仍然必须确定提交 *。你可以在我写这篇文章的时候 ,或者你可以用 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

masterorigin/master 指向的新 o 提交——或者它可能是整个提交链——是 "in the way",但是 [= test-branch(你现在所在的位置)和 master 的 158=] 是提交 *:两个分支分歧之前最近的共享提交。

然后我们简单地在该提交中定位 rebasereset --soft。当我们完成并有一个新的 AB 提交时,它看起来像这样:

              AB        <-- HEAD=test-branch
             /
...<- o <- * <- o       <-- master, origin/master
             \
               A <- B   [ghost version of test-branch]

一旦你压缩了 AB 提交,你就可以 git rebase 以通常的方式把它放到 master 上。

请注意,另一个答案做的是完全相同的事情,它只是通过计算提交来识别提交 *。如果您知道 test-branch 的提示和 "interesting" 提交 * 之间有两个提交,那么 HEAD~2 标识与 $(git merge-base HEAD master) 相同的提交。使用 git merge-base 只是让你避免计数。


1"References" 是实际的通用 git 术语。在这种情况下,它们是分支名称,我使用 "label" 一词来将它们与该提交图形成的分支历史区分开来。 git 中的 "branch" 一词至少用来指代这两个不同的事物,这让人很困惑。