[英]Git rebase when previous commit changed
I frequently find myself working on two different work tickets that are on different git branches, but one is dependent on another, like this: 我经常发现自己正在处理不同git分支上的两个不同的工作票,但是一个依赖于另一个,如下所示:
* later-branch
|
* earlier-branch
|
* some prior commit
|
(Each is a single commit here because we are using gerrit, but this question might apply to multiple commits for each as well.) The earlier branch may be going through review, and so I might have to go back and modify it at some point with a git commit --amend
. (每个都是一个提交,因为我们使用的是gerrit,但是这个问题也可能适用于每个提交的多个提交。)早期的分支可能正在进行审核,因此我可能必须返回并在某个时候修改它使用
git commit --amend
。 As must happen, this will fork the history: 必须发生的事情,这将分叉历史:
* earlier-branch
|
| * later-branch
| |
| * previous version of earlier-branch
| /
* some prior commit
|
At this point I want to rebase the later-branch
on top of the new version of earlier-branch
. 在这一点上,我要变基的
later-branch
上的新版本之上earlier-branch
。 But if I just do a git checkout later-branch
followed by a git rebase earlier-branch
, it always gets conflicts, because (I think) it must first apply the previous version of earlier-branch
commit to the most recent version of earlier-branch
. 但是,如果我只是做一个
git checkout later-branch
随后是git rebase earlier-branch
,它总是得到冲突,因为(我觉得)它必须先申请的previous version of earlier-branch
提交到最新版本的earlier-branch
。
What I end up doing is git checkout earlier-branch -b new-later-branch-name
followed by git cherry-pick later-branch
and git br -D later-branch
. 我最终做的是
git checkout earlier-branch -b new-later-branch-name
然后是git cherry-pick later-branch
和git br -D later-branch
。 Which is a pain. 这是一种痛苦。 Can anyone suggest a better way to handle this?
谁能建议一个更好的方法来处理这个?
I see two easy ways to do this. 我看到两种简单的方法来做到这一点。
The first, best option is to use git rebase
in interactive mode. 第一个,最好的选择是在交互模式下使用
git rebase
。 To do this, you would do 要做到这一点,你会这样做
git checkout later-branch
git rebase -i earlier-branch
In the screen that pops up, you would choose to drop
the previous version of 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
...
This will rebase later-branch
on top of earlier-branch
, providing the following tree: 这将在
earlier-branch
later-branch
的顶部重新earlier-branch
,提供以下树:
* later-branch
|
* earlier-branch
|
| * previous version of earlier-branch
| /
* some prior commit
|
Another option is to simply do a git cherry-pick
. 另一种选择是简单地做一个
git cherry-pick
。 If you do: 如果你这样做:
git checkout earlier-branch
git cherry-pick later-branch
You'll get the following tree: 你会得到以下树:
* 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
|
So, in effect, this will produce the result you want, but it will advance earlier-branch
by one. 因此,实际上,这将产生您想要的结果,但它将提前
earlier-branch
。 If the branch names are important to you, you could rename and reset them accordingly. 如果分支名称对您很重要,您可以相应地重命名和重置它们。
Aside from interactive rebase (as in houtanb's answer ), there are two more ways to do this somewhat, or much, more automatically: 除了交互式rebase(如houtanb的回答 ),还有两种方法可以更自然地执行此操作:
git rebase --onto
, or git rebase --onto
,或 To use the latter, you can run git rebase --fork-point earlier-branch
when on later-branch
. 要使用后者,可以在
later-branch
上运行git rebase --fork-point earlier-branch
later-branch
。
(You can instead set earlier-branch
as the upstream for later-branch
—presumably just temporarily, for the duration of the rebasing—and then just run git rebase
when on later-branch
. The reason is that --fork-point
is the default when using the automatic upstream mode, but must be explicitly requested when using an explicit <upstream>
argument to git rebase
.) (你可以改为将early
earlier-branch
设置为later-branch
earlier-branch
的上游 later-branch
可能只是暂时的,在git rebase
期间 - 然后在later-branch
时运行git rebase
。原因是--fork-point
是使用自动上游模式时的默认值 ,但在使用git rebase
的显式<upstream>
参数时必须显式请求 。)
Unfortunately, the last is especially magical-seeming, especially to those new to Git. 不幸的是,最后一个看起来特别神奇,特别是那些刚接触Git的人。 Fortunately, your diagram has in it the seeds to understanding it—and with it,
git rebase --onto
. 幸运的是,你的图表中有理解它的种子 - 以及
git rebase --onto
。
Let's take what you drew above and turn it sideways, then turn it even a little a bit more. 让我们把你上面绘制的东西转向侧面,然后再转动一点。 This gives me some room to draw in the branch names.
这给了我一些绘制分支名称的空间。 I'll replace the
*
s for each commit with round o
nodes or uppercase letters and numbers. 我会用圆
o
节点或大写字母和数字替换每次提交的*
s。 I'll add a third commit, C
, to the later branch as well just for illustration. 我将向后一个分支添加第三个提交
C
,以便进行说明。
:
.
\
o
\
A1 <-- earlier-branch
\
B--C <-- later-branch
Now you are forced, for whatever reason, to copy commit A1
to new commit A2
, and move the branch label earlier-branch
to point to the new copy: 现在,无论出于何种原因,您都被迫将提交
A1
复制到新提交A2
,并将分支标签earlier-branch
移动到指向新副本:
Let's take what you drew above and turn it sideways, then turn it even a little a bit more. 让我们把你上面绘制的东西转向侧面,然后再转动一点。 This gives me some room to draw in the branch names.
这给了我一些绘制分支名称的空间。 I'll replace the
*
s for each commit with round o
nodes or uppercase letters. 我将使用圆形
o
节点或大写字母替换每次提交的*
s。
:
.
\
o--A2 <-- earlier-branch
\
A1
\
B--C <-- later-branch
If only Git would remember that commit A1
exists because earlier-branch
used to contain commit A1
, we could tell Git: "when copying later-branch
, drop any commits that are still on it now, but used to be on it only because of earlier-branch
". 如果只有Git会记住提交
A1
存在,因为earlier-branch
用于包含提交A1
,我们可以告诉Git:“当复制later-branch
,删除现在仍在其上的任何提交,但过去仅仅是因为earlier-branch
“。
But Git does remember this, for 30 days at least, by default. 但Git 确实记得这个,至少30天,默认情况下。 Git has reflogs —logs of what used to be stored in each reference (including both regular branches and Git's so-called remote tracking branches ).
Git具有reflogs -logs,用于存储在每个引用中 (包括常规分支和Git的所谓远程跟踪分支 )。 If we add the reflog information to the drawing, it looks like this:
如果我们将reflog信息添加到绘图中,它看起来像这样:
:
.
\
o--A2 <-- earlier-branch
\
A1 <-- [earlier-branch@{1}]
\
B--C <-- later-branch
In fact, if for some reason you must copy A2
to A3
, the diagram just grows another reflog entry, renumbering the existing one: 实际上,如果由于某种原因你必须将
A2
复制到A3
,那么该图只会增加另一个reflog条目,重新编号现有的:
:
. A3 <-- earlier-branch
\ /
o--A2 <-- [earlier-branch@{1}]
\
A1 <-- [earlier-branch@{2}]
\
B--C <-- later-branch
What the fork-point code does is to scan the reflog for some other reference, such as earlier-branch
, and find these commits (in this case A1
—it actually finds both A1
and A2
, in the latter case, but then winnows it down to the A1
that's on both branches; see also Git rebase - commit select in fork-point mode ). fork-point代码的作用是扫描reflog以获取其他引用,例如early
earlier-branch
,并找到这些提交(在这种情况下, A1
-it实际上找到A1
和A2
,在后一种情况下,但是然后winnows它下到两个分支上的A1
;另见Git rebase - 在fork-point模式下提交select 。 It then runs git rebase --onto
for you, as if you had manually run: 然后它为你运行
git rebase --onto
,就像你手动运行一样:
git rebase --onto earlier-branch hash-of-A1
which gets us into how the --onto
argument works. 这让我们了解了
--onto
参数的工作原理。
--onto
--onto
Normally, you would run git rebase
with one argument, as in git rebase branch-name
, or even no arguments at all. 通常,您可以使用一个参数运行
git rebase
,如git rebase branch-name
,甚至根本没有参数。 With no arguments at all, git rebase
uses the current branch's upstream setting. 根本没有参数,
git rebase
使用当前分支的上游设置。 With a branch-name
argument, git rebase
calls that argument <upstream>
. 使用
branch-name
参数, git rebase
调用该参数<upstream>
。 (As an odd side effect, this also—since Git version 2.0 anyway—automatically enables or disables the --fork-point
option, requiring you to use an explicit --no-fork-point
or --fork-point
if you want the other mode.) (作为一个奇怪的副作用,这也是 - 因为Git版本2.0无论如何 - 自动启用或禁用
--fork-point
选项,要求你使用显式--no-fork-point
或--fork-point
如果你想另一种模式。)
In any case, Git uses the <upstream>
—selected automatically if you did not specify one—for two purposes. 在任何情况下,如果你没有指定一个用于两个目的,Git会自动使用
<upstream>
- 选择。 One is to limit the set of commits that will be copied: Git will consider copying the set of commits listed by running: 一种是限制将被复制的提交集:Git将考虑通过运行复制列出的提交集:
git rev-list <upstream>..HEAD
To see them in a more friendly fashion, use git log
, or my preferred method, git log --oneline --decorate --graph
, instead of git rev-list
here: 要以更友好的方式查看它们,请使用
git log
或我首选的方法git log --oneline --decorate --graph
,而不是git rev-list
here:
git log --oneline --decorate --graph earlier-branch..HEAD
Ideally, we would see commits B
and C
here, with C
listed first (Git has to use --reverse
to make sure it copies B
first). 理想情况下,我们会在这里看到提交
B
和C
,首先列出C
(Git必须使用--reverse
以确保它首先复制B
)。 If you copied A1
to A2
and/or on to A3
, though, and moved the branch earlier-branch
, we will see all of A1
, B
, and C
. 但是,如果您将
A1
复制到A2
和/或复制到A3
,并移动了分支earlier-branch
,我们将看到所有A1
, B
和C
(Git excludes A2
or A3
—whichever earlier-branch
points to—but they're not on the list anyway. It then uses the excluded A2
or A3
to exclude the commits before A1
, so that's why we don't see those.) (Git不包括
A2
或A3
以earlier-branch
点为准 - 但它们无论如何都不在列表中。然后使用排除的A2
或A3
排除A1
之前的提交,这就是为什么我们看不到这些。)
The other purpose for this <upstream>
branch name (or commit hash) is to select where the copies go . 此
<upstream>
分支名称(或提交哈希)的另一个目的是选择副本的位置 。 When we copy one or more commits, each copied commit has to go after some existing commit. 当我们复制一个或多个提交时,每个复制的提交必须在一些现有提交之后进行。 The
<upstream>
argument provides the ID of the commit that will be the parent of the first commit we copy. <upstream>
参数提供将作为我们复制的第一个提交的父级的提交的ID。
Hence, running git rebase earlier-branch
makes Git list commits A1
, B
, and C
, in that order. 因此,运行
git rebase earlier-branch
会使Git列表按顺序提交A1
, B
和C
It then—using the "detached HEAD" mode—copies A1
to go after earlier-branch
: 然后使用“分离的HEAD”模式 - 将
A1
复制到earlier-branch
:
:
. A1' <-- HEAD
\ /
o--A2 <-- earlier-branch
\
A1 <-- [earlier-branch@{1}]
\
B--C <-- later-branch
and then copies B
to go after A1'
: 然后将
B
复制到A1'
:
:
. A1'--B' <-- HEAD
\ /
o--A2 <-- earlier-branch
\
A1 <-- [earlier-branch@{1}]
\
B--C <-- later-branch
Rebase then copies C
to C'
and moves the branch label, later-branch
, to wherever HEAD
winds up, re-attaching your HEAD in the process: 调整基线然后复制
C
到C'
和移动分支标签, later-branch
,到哪里HEAD
卷起,再附上你的头在这个过程中:
:
. A1'--B'--C' <-- later-branch (HEAD)
\ /
o--A2 <-- earlier-branch
\
A1 <-- [earlier-branch@{1}]
\
B--C <-- [later-branch@{1}]
The --onto
argument lets you tell Git where the copies go . --onto
参数让你告诉Git 副本去哪里 。
--onto
with git rebase
--onto
与git rebase
When you add --onto
, you tell Git rebase where to put the copies. 当您添加
--onto
,您告诉Git rebase将副本放在何处。 This frees up the <upstream>
argument so that it now specifies only what not to copy! 这释放了
<upstream>
参数,现在它只指定不要复制的内容! So now you are free to tell Git: "copy everything that's after commit A1
" by writing: 所以现在你可以自由地告诉Git:“通过写入来复制提交
A1
之后的所有内容”:
git rebase --onto earlier-branch <hash-of-A1>
Git does its usual thing, listing the commits to be copied ( B
and C
), detaching your HEAD from later-branch
, copying the commits one at a time with the copies going after the tip of earlier-branch
, and finally moving the name later-branch
to reattach your HEAD. Git做了它常用的事情,列出要复制的提交(
B
和C
),从later-branch
分离你的HEAD,一次复制一个提交,副本跟在earlier-branch
的尖端之后,最后移动名字later-branch
重新连接你的HEAD。
This is exactly what we wanted, all done semi-automatically: we tell Git not to copy A1
itself, so that it copies just B
and C
. 这正是我们想要的,都是半自动完成的:我们告诉Git 不要复制
A1
本身,所以它只复制B
和C
When we specify an upstream, as in git rebase earlier-branch
, Git disables the fork-point mode. 当我们指定上游时,就像在
git rebase earlier-branch
,Git 禁用 fork-point模式。 If we explicitly enable it, Git will fish through the earlier-branch
reflog. 如果我们明确启用它,Git将通过
earlier-branch
reflog。 As long as the reflog entry for commit A1
has not yet expired, Git will discover that A1
used to be on earlier-branch
and will use --onto
for us to discard it from the to-copy list. 只要提交
A1
的reflog条目尚未到期,Git就会发现A1
曾经在earlier-branch
并且将使用--onto
为我们从to-copy列表中丢弃它。
Note that there is a bit of danger here. 请注意,这里有一点危险。 What if we really wanted
A1
after all, eg, what if we backed earlier-branch
up over A1
only because we realized A1
did not belong on the other branch? 如果我们真的想要
A1
会怎么样呢,例如,如果我们支持earlier-branch
到A1
只是因为我们意识到A1
不属于另一个分支? Git will still think we copied it to some other commit, and don't want it copied now, and will toss off the list. Git仍然认为我们将它复制到其他一些提交中,并且现在不想复制它,并且会抛弃列表。 Fortunately, you can always undo a rebase: rebase doesn't discard anything at all, it just copies .
幸运的是,你总是可以撤消一个rebase:rebase根本不丢弃任何东西,它只是复制 。 It then updates a branch, which saves the previous value in the branch's reflog.
然后它会更新一个分支,它将以前的值保存在分支的reflog中。 But fishing around through reflogs, trying to find one particular set of commits, in a twisty maze of commits that are all alike, is not a lot of fun—so it is wise to think a bit before running rebase, with or without
--fork-point
. 但是通过reflogs钓鱼,试图找到一组特定的提交,在一个完全相同的提交迷宫中,并不是很有趣 - 所以在运行rebase之前考虑一下是否明智
--fork-point
无论有没有--fork-point
。
In a few (rare) cases Git you don't have to do anything (no fork point mode, no manual --onto
separation, no --interactive
). 在一些(罕见)情况下,Git的你不必做任何事情(无分叉点模式,无需手动
--onto
分离,没有--interactive
)。 Specifically, if the patch itself did not change at all, but only the wording in the commit message changed, Git will detect the already-copied commit and skip it. 具体来说,如果补丁本身根本没有改变,但只有提交消息中的措辞发生了变化,Git将检测已经复制的提交并跳过它。 This happens because
git rebase
actually uses the symmetric difference mode of git rev-list
with the --cherry-pick --right-only --no-merges
options. 这是因为
git rebase
实际上使用git rev-list
的对称差异模式和--cherry-pick --right-only --no-merges
选项。 That is, rather than: 也就是说,而不是:
git rev-list <upstream>..HEAD
Git actually runs: Git实际上运行:
git rev-list --cherry-pick --right-only --no-merges <upstream>...HEAD
(note the three dots). (注意三个点)。 I don't have time to go into any more detail here, though.
不过,我没时间在这里详细介绍。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.