繁体   English   中英

上次提交更改时Git rebase

[英]Git rebase when previous commit changed

我经常发现自己正在处理不同git分支上的两个不同的工作票,但是一个依赖于另一个,如下所示:

* later-branch
|
* earlier-branch
|
* some prior commit
|

(每个都是一个提交,因为我们使用的是gerrit,但是这个问题也可能适用于每个提交的多个提交。)早期的分支可能正在进行审核,因此我可能必须返回并在某个时候修改它使用git commit --amend 必须发生的事情,这将分叉历史:

* earlier-branch
|
|  * later-branch
|  |
|  * previous version of earlier-branch
| /
* some prior commit
|

在这一点上,我要变基的later-branch上的新版本之上earlier-branch 但是,如果我只是做一个git checkout later-branch随后是git rebase earlier-branch ,它总是得到冲突,因为(我觉得)它必须先申请的previous version of earlier-branch提交到最新版本的earlier-branch

我最终做的是git checkout earlier-branch -b new-later-branch-name然后是git cherry-pick later-branchgit br -D later-branch 这是一种痛苦。 谁能建议一个更好的方法来处理这个?

我看到两种简单的方法来做到这一点。

第一个,最好的选择是在交互模式下使用git rebase 要做到这一点,你会这样做

git checkout later-branch
git rebase -i earlier-branch

在弹出的屏幕中,您将选择drop previous version of earlier-branch

drop efb1c19 previous version of earlier-branch
pick a25ba16 later-branch

# Rebase 65f3afc..a25ba16 onto 65f3afc (2 commands)
#
# Commands:
# p, pick = use commit
# r, reword = use commit, but edit the commit message
# e, edit = use commit, but stop for amending
# s, squash = use commit, but meld into previous commit
# f, fixup = like "squash", but discard this commit's log message
# x, exec = run command (the rest of the line) using shell
# d, drop = remove commit
...

这将在earlier-branch later-branch的顶部重新earlier-branch ,提供以下树:

* later-branch
|
* earlier-branch
|
|  * previous version of earlier-branch
| /
* some prior commit
|

另一种选择是简单地做一个git cherry-pick 如果你这样做:

git checkout earlier-branch
git cherry-pick later-branch

你会得到以下树:

* earlier-branch -> cherry-picked commit 1
|
* earlier-branch -> amended commit 0, now commit 2
|
|  * later-branch -> commit 1
|  |
|  * previous version of earlier-branch -> commit 0
| /
* some prior commit
|

因此,实际上,这将产生您想要的结果,但它将提前earlier-branch 如果分支名称对您很重要,您可以相应地重命名和重置它们。

除了交互式rebase(如houtanb的回答 ),还有两种方法可以更自然地执行此操作:

  • 使用git rebase --onto ,或
  • 使用“fork-point”代码(自Git 2.0版以来在Git中)。

要使用后者,可以在later-branch上运行git rebase --fork-point earlier-branch later-branch

(你可以改为将early earlier-branch设置为later-branch earlier-branch上游 later-branch可能只是暂时的,在git rebase期间 - 然后在later-branch时运行git rebase 。原因是--fork-point是使用自动上游模式时的默认值 ,但在使用git rebase的显式<upstream>参数时必须显式请求 。)

不幸的是,最后一个看起来特别神奇,特别是那些刚接触Git的人。 幸运的是,你的图表中有理解它的种子 - 以及git rebase --onto

定义叉点

让我们把你上面绘制的东西转向侧面,然后再转动一点。 这给了我一些绘制分支名称的空间。 我会用圆o节点或大写字母和数字替换每次提交的* s。 我将向后一个分支添加第三个提交C ,以便进行说明。

:
 .
  \
   o
    \
     A1   <-- earlier-branch
      \
       B--C   <-- later-branch

现在,无论出于何种原因,您都被迫将提交A1复制到新提交A2 ,并将分支标签earlier-branch移动到指向新副本:

让我们把你上面绘制的东西转向侧面,然后再转动一点。 这给了我一些绘制分支名称的空间。 我将使用圆形o节点或大写字母替换每次提交的* s。

:
 .
  \
   o--A2  <-- earlier-branch
    \
     A1
      \
       B--C   <-- later-branch

如果只有Git会记住提交A1存在,因为earlier-branch 用于包含提交A1 ,我们可以告诉Git:“当复制later-branch ,删除现在仍在其上的任何提交,但过去仅仅是因为earlier-branch “。

但Git 确实记得这个,至少30天,默认情况下。 Git具有reflogs -logs,用于存储在每个引用中 (包括常规分支和Git的所谓远程跟踪分支 )。 如果我们将reflog信息添加到绘图中,它看起来像这样:

:
 .
  \
   o--A2   <-- earlier-branch
    \
     A1    <-- [earlier-branch@{1}]
      \
       B--C   <-- later-branch

实际上,如果由于某种原因你必须将A2复制到A3 ,那么该图只会增加另一个reflog条目,重新编号现有的:

:
 .   A3    <-- earlier-branch
  \ /
   o--A2   <-- [earlier-branch@{1}]
    \
     A1    <-- [earlier-branch@{2}]
      \
       B--C   <-- later-branch

fork-point代码的作用是扫描reflog以获取其他引用,例如early earlier-branch ,并找到这些提交(在这种情况下, A1 -it实际上找到A1A2 ,在后一种情况下,但是然后winnows它下到两个分支上的A1 ;另见Git rebase - 在fork-point模式下提交select 然后它为你运行git rebase --onto ,就像你手动运行一样:

git rebase --onto earlier-branch hash-of-A1

这让我们了解了--onto参数的工作原理。

定期rebase,没有--onto

通常,您可以使用一个参数运行git rebase ,如git rebase branch-name ,甚至根本没有参数。 根本没有参数, git rebase使用当前分支的上游设置。 使用branch-name参数, git rebase调用该参数<upstream> (作为一个奇怪的副作用,这也是 - 因为Git版本2.0无论如何 - 自动启用或禁用--fork-point选项,要求你使用显式--no-fork-point--fork-point如果你想另一种模式。)

在任何情况下,如果你没有指定一个用于两个目的,Git会自动使用<upstream> - 选择。 一种是限制将被复制的提交集:Git将考虑通过运行复制列出的提交集:

git rev-list <upstream>..HEAD

要以更友好的方式查看它们,请使用git log或我首选的方法git log --oneline --decorate --graph ,而不是git rev-list here:

git log --oneline --decorate --graph earlier-branch..HEAD

理想情况下,我们会在这里看到提交BC ,首先列出C (Git必须使用--reverse以确保它首先复制B )。 但是,如果您将A1复制到A2和/或复制到A3 ,并移动了分支earlier-branch ,我们将看到所有A1BC (Git不包括A2A3earlier-branch点为准 - 但它们无论如何都不在列表中。然后使用排除的A2A3排除A1 之前的提交,这就是为什么我们看不到这些。)

<upstream>分支名称(或提交哈希)的另一个目的是选择副本的位置 当我们复制一个或多个提交时,每个复制的提交必须在一些现有提交之后进行。 <upstream>参数提供将作为我们复制的第一个提交的父级的提交的ID。

因此,运行git rebase earlier-branch会使Git列表按顺序提交A1BC 然后使用“分离的HEAD”模式 - 将A1复制到earlier-branch

:
 .       A1'  <-- HEAD
  \     /
   o--A2   <-- earlier-branch
    \
     A1    <-- [earlier-branch@{1}]
      \
       B--C   <-- later-branch

然后将B复制到A1'

:
 .       A1'--B'  <-- HEAD
  \     /
   o--A2   <-- earlier-branch
    \
     A1    <-- [earlier-branch@{1}]
      \
       B--C   <-- later-branch

调整基线然后复制CC'和移动分支标签, later-branch ,到哪里HEAD卷起,再附上你的头在这个过程中:

:
 .       A1'--B'--C'  <-- later-branch (HEAD)
  \     /
   o--A2   <-- earlier-branch
    \
     A1    <-- [earlier-branch@{1}]
      \
       B--C   <-- [later-branch@{1}]

--onto参数让你告诉Git 副本去哪里

使用--ontogit rebase

当您添加--onto ,您告诉Git rebase将副本放在何处。 这释放了<upstream>参数,现在它只指定不要复制的内容! 所以现在你可以自由地告诉Git:“通过写入来复制提交A1 之后的所有内容”:

git rebase --onto earlier-branch <hash-of-A1>

Git做了它常用的事情,列出要复制的提交( BC ),从later-branch分离你的HEAD,一次复制一个提交,副本跟在earlier-branch的尖端之后,最后移动名字later-branch重新连接你的HEAD。

这正是我们想要的,都是半自动完成的:我们告诉Git 不要复制A1本身,所以它只复制BC

当我们指定上游时,就像在git rebase earlier-branch ,Git 禁用 fork-point模式。 如果我们明确启用它,Git将通过earlier-branch reflog。 只要提交A1的reflog条目尚未到期,Git就会发现A1曾经在earlier-branch并且将使用--onto为我们从to-copy列表中丢弃它。

请注意,这里有一点危险。 如果我们真的想要 A1会怎么样呢,例如,如果我们支持earlier-branchA1只是因为我们意识到A1不属于另一个分支? Git仍然认为我们将它复制到其他一些提交中,并且现在不想复制它,并且会抛弃列表。 幸运的是,你总是可以撤消一个rebase:rebase根本不丢弃任何东西,它只是复制 然后它会更新一个分支,它将以前的值保存在分支的reflog中。 但是通过reflogs钓鱼,试图找到一组特定的提交,在一个完全相同的提交迷宫中,并不是很有趣 - 所以在运行rebase之前考虑一下是否明智--fork-point无论有没有--fork-point

边注

在一些(罕见)情况下,Git的你不必做任何事情(无分叉点模式,无需手动--onto分离,没有--interactive )。 具体来说,如果补丁本身根本没有改变,但只有提交消息中措辞发生了变化,Git将检测已经复制的提交并跳过它。 这是因为git rebase实际上使用git rev-list对称差异模式和--cherry-pick --right-only --no-merges选项。 也就是说,而不是:

git rev-list <upstream>..HEAD

Git实际上运行:

git rev-list --cherry-pick --right-only --no-merges <upstream>...HEAD

(注意三个点)。 不过,我没时间在这里详细介绍。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM