[英]Rebase a branch with master with commits removed from master
Let's say you master looked like 假设您的主人看起来像
1 2 3 4 5
Where 1 ~ 5 are separate revisions. 其中1〜5是单独的修订版。 Now branchX
looks like 现在branchX
看起来像
1 2 3 4 5 6 7
Then, due to some reason some commits were removed from master, So now 然后,由于某种原因,某些提交已从master删除,所以现在
1 2 4 5
is how the master looks ( 3
was removed). 母版的外观(删除了3
)。
I want to rebase branchX
with master
我想用master
重新建立branchX
It should look like 它看起来像
1 2 4 5 6 7
Edit: Here in this simple example just 6 7
, just two commits were added, but in my real scenario, I have 200 commits added to branchX
编辑:在这个简单的示例中,这里仅6 7
,仅添加了两个提交,但是在我的实际情况下,我向branchX
添加了200个提交
This is difficult, or sometimes even impossible, in general. 通常,这是困难的,有时甚至是不可能的。 It's easier—in fact, sometimes much easier—if you add some constraints. 如果添加一些约束,则更容易(实际上有时更容易)。
If you have a suitable reflog entry, such as master@{1}
, the command sequence is just: 如果您有合适的引用日志条目,例如master@{1}
,则命令序列如下:
$ git checkout branchX
$ git rebase --onto master master@{1}
If not, we must find an appropriate upstream limit commit: 如果没有,我们必须找到合适的上游极限提交:
$ limit=$(git rev-list --topo-order --cherry master...branchX |
sed -n -e 's/=//p' | head -1)
$ echo $limit # if this is empty, there's no equivalent commit and you are SOL
$ git checkout branchX # same as before
$ git rebase --onto master $limit
First, remember that a branch name, in Git, names every commit reachable from the branch tip (the tip being the commit to which the branch name itself points). 首先,请记住,在Git中,分支名称会命名从分支提示可到达的每个提交(该提示是分支名称本身指向的提交)。 Reachability here is determined by the arcs in the DAG, ie, which commits are considered ancestors of which later commits. 在此,可到达性由DAG中的弧确定,即哪些提交被认为是其后提交的祖先。
Remember also that the true name of each commit is its SHA-1 ID, and these are all unique, and determined by the read-only contents of the commit object. 还要记住,每个提交的真实名称是其SHA-1 ID,并且它们都是唯一的,并且由提交对象的只读内容确定。 It's impossible to remove a commit: you can only copy all its children, to new (different) commits, with the original children pointing back to the commit's parent(s) and their copied descendants pointing to the corresponding copied parent(s). 删除提交是不可能的:您只能将其所有子级复制到新的 (不同的)提交中,原始的子级指向该提交的父级,而其复制的后代则指向相应的复制的父级。
Your scenario says that you actually had this: 您的场景表明您实际上有以下几点:
A--B--C--D--E <-- master
\
F--G <-- branchX
where each commit's parent is found by following the direct links in a generally-leftward direction. 通过沿大体向左的方向跟随直接链接来找到每个提交的父级。 The (single) parent of G
is F
; G
的(单)父母是F
; F
's parent is E
; F
的父母是E
; E
's parent is D
, and so on, back to A
, which has no parents at all (is a root commit). E
的父母是D
,依此类推,回到A
,后者根本没有父母(是根提交)。
The set of commits reachable from master
is ABCDE
. master
可以到达的提交集是ABCDE
。 The set of commits reachable from branchX
is ABCDEFG
. 从branchX
可以到达的提交branchX
是ABCDEFG
。 The way you and Git can talk about "commits on branchX" without getting ABCDE
is to use, not just branchX
, but rather master..branchX
. 您和Git可以在不获取ABCDE
情况下谈论“在branchX上提交”的方式,不仅可以使用branchX
,还可以使用master..branchX
。 This is the set of commits reachable from branchX
minus the set reachable from master
. 这是从branchX
可以到达的提交branchX
减去从master
可以到达的提交集。
Then, to "delete" commit C
from master
, this must have happened: 然后,要从master
“删除”提交C
,这肯定已经发生:
D'-E' <-- master
/
A--B--C--D--E [master was this before the copies]
\
F--G <-- branchX
Here D'
and E'
are actually copies of D
and E
. D'
和E'
实际上是D
和E
副本 。 The originals remain in the repository, and are still reachable from branchX
. 原始文件保留在存储库中,并且仍然可以从branchX
。 The expression master..branchX
no longer works, though, because master
now names E'
and ancestors, ie, AB-D'-E'
. 但是,表达式master..branchX
不再起作用,因为master
现在命名为E'
和祖先,即AB-D'-E'
。 This subtracts those commits—it's allowed to subtract away something that was not there in the first place, in set algebra—giving CDEFG
, which is not what you want. 这样就减去了那些提交,这允许减去集合代数中最初不存在的内容,从而得到CDEFG
,这不是您想要的。
The basic problem comes down to identifying commit E
. 基本问题归结为确定提交E
If we can find commit E
, we can write E..branchX
, ie, the set of all commits reachable from branchX
, minus the set reachable from commit E
. 如果可以找到提交E
,则可以编写E..branchX
,即从branchX
可以到达的所有提交的集合减去从提交E
可以到达的集合。 But how shall we find E
? 但是我们怎么找到E
呢?
If you are the one who re-pointed the name master
to commit E'
, this could be very easy. 如果您是重新指定名称master
以提交E'
,这可能非常容易。 All you have to do is save the SHA-1 hash of E
somewhere first—and in fact, if you're the one who rewrote master
this way, you did save it, in the reflog you have for your master
. 您要做的就是首先将E
的SHA-1散列保存在某个位置,并且实际上,如果您是以此方式重写master
那个,则您确实将其保存在master
的reflog中。 The reflog entries are master@{1}
, master@{2}
, and so on. reflog条目为master@{1}
, master@{2}
等。 You can view these with git reflog master
. 您可以使用git reflog master
查看这些。 1 Each reflog entry also has a date-and-time stamp, so you can write master@{yesterday}
or master@{1.week.ago}
to look up the appropriate numbered entry based on a relative date. 1每个reflog条目还具有日期和时间戳,因此您可以写master@{yesterday}
或master@{1.week.ago}
来根据相对日期查找适当的编号条目。
That's the easiest way by far, and it works in all cases, even if E
is the commit that was "removed". 到目前为止,这是最简单的方法,即使E
是被“删除”的提交,它也适用于所有情况。 Note that when we "remove" commit C
, we must copy D
and E
to D'
and E'
. 请注意,当我们“删除”提交C
,必须将 D
和E
复制到D'
和E'
。 That is because those two commits were descendants of C
that were reachable from master
. 那是因为那两个提交是C
后代 ,可以从master
。 Should we decide to remove E
, though ... well, what are the children of E
that are reachable from master
? 但是,我们是否应该决定删除E
,那么,从master
那里可以得到E
的孩子吗?
That's right: there are no such commits. 没错: 没有这样的提交。 We can simply point master
back at commit D
, leaving ABCD
on master
, and E
apparently unique to branchX
now. 我们可以简单地将master
指向commit D
,将ABCD
保留在master
,而E
现在显然是branchX
唯一的。 Any time we adjust our master like this, though, we make a reflog entry to keep the previous value, so once again, we can simply look in the reflog to discover that E
is the interesting commit. 我们调整我们的主这样任何时候,虽然,我们做一个引用日志条目保留以前的值,所以再次,我们可以简单地看在引用日志发现, E
是有趣的承诺。
The problem here comes in if (a) we didn't adjust master
ourselves or (b) we did do it, but so long ago that our reflog entries have expired . 如果(a)我们自己没有调整master
或(b)我们做到了,但是很久以前我们的reflog条目已经过期,就会出现问题 。 (This occurs after 30 days by default for cases like commit E
.) In this case, we can only find E
if there is some copy E'
in the new chain. (后30天默认情况下,像承诺的情况下出现这种情况E
)。在这种情况下,我们只能找E
如果有一些副本E'
在新的链。 Even then, we can still only find it if the copy E'
has the same patch ID as E
. 即使这样,我们仍然只能找到它,如果副本E'
具有相同的修补程序ID为E
。
Patch IDs are how git cherry
, and hence git rev-list
's --cherry-pick
and --cherry-mark
options, work. 补丁ID是git cherry
和git rev-list
的--cherry-pick
和--cherry-mark
选项的工作方式。 We make (or Git makes) the assumption that when a commit is copied, usually it's copied with no significant changes, such that a hash ID computed by examining a slightly stripped-down git show
of the commit will come up with the same hash ID for the original and for the copy. 我们(或Git做出)这样的假设:复制提交时,通常复制时没有重大更改,因此通过检查提交的略微简化的git show
计算得出的哈希ID将具有相同的哈希ID。原件和副本。 These patches are called patch equivalent and mark the paired-up commits as, in some sense, "equal". 这些补丁被称为补丁等效,并且在某种意义上将配对的提交标记为“相等”。
We also must 2 make use of the symmetric difference notation, master...branchX
or branchX...master
. 我们还必须2使用对称差异表示法master...branchX
或branchX...master
。 Because it's symmetric, it doesn't really matter which order we use (except for the whole left vs right part in --left-right
in git rev-list
, which we will generally want). 因为它是对称的,所以使用哪个顺序并不重要(除了git rev-list
中的--left-right
中的整个左与右部分,我们通常会想要这样)。 What it does, in any case, is to produce the following set algebra operation: 无论如何,它的作用是产生以下集合代数运算:
A..B = (reachable(A) | reachable(B)) - (reachable(A) & reachable(B))
That is, produce the set of commits reachable from either branch tip, excluding those commits that are reachable from both branch tips. 也就是说,产生从任一分支提示均可到达的提交集合,但不包括从两个分支提示均可到达的那些提交。 Hence, given: 因此,给出:
D'-E' <-- master
/
A--B--C--D--E--F--G <-- branchX
the symmetric difference gives us D', E', C, D, E, F, G
. 对称差使我们得到D', E', C, D, E, F, G
。
Hence, if we run git rev-list master...branchX
, we will get this complete set of commits. 因此,如果我们运行git rev-list master...branchX
,我们将获得完整的提交集。 All we have to do now is see that D'
= D
and E'
= E
, and somehow choose E
from this set. 现在我们要做的就是看到D'
= D
和E'
= E
,并从某种意义上选择E
So now we add --cherry-mark
to the git rev-list
command: this marks D'
and E'
and D
and E
with =
characters, and marks C
, F
, and G
with +
characters. 因此,现在我们在git rev-list
命令中添加--cherry-mark
:这用=
字符标记D'
和E'
以及D
和E
,并用+
字符标记C
, F
和G
Here I have run it on a repo that isn't quite as detailed: in effect I just have E
and E'
plus one unique commit. 在这里,我在一个未详细描述的仓库上运行它:实际上,我只有E
和E'
以及一个唯一的提交。
$ git rev-list --cherry-mark master...two
=dcbcb2774954437ef0906c6770c7deb924d9286e
+0af7c6a3cf5e49928de132c341c848be80ab84c7
=643b37ef242fdc35dfdd4551b42393af3eb91a85
OK so far, but there's an obvious problem: this lists both E
and E'
, and we only wanted E
. 到目前为止,还可以,但是有一个明显的问题:它同时列出了E
和E'
,而我们只需要E
Well, let's back up a moment and do this other rev-list variant: 好吧,让我们备份一下,然后做另一个修订列表:
$ git rev-list --left-right master...two
>dcbcb2774954437ef0906c6770c7deb924d9286e
<0af7c6a3cf5e49928de132c341c848be80ab84c7
<643b37ef242fdc35dfdd4551b42393af3eb91a85
This marks each commit, not with +
or =
, but rather with <
(left) or >
(right). 这标记每个提交,而不是用+
或=
标记,而是用<
(左)或>
(右)标记。 The commit that's on branch two
, that is "the same as" the one on master
, is in fact dcbcb27...
. 在分支two
上的提交,即与master
上的提交“相同”,实际上是dcbcb27...
The commit that's on master
that is the same as the one on two
is 643b37e...
. 在master
上与在two
上的提交相同的提交是643b37e...
This left/right distinction gives us a way to identify which commit is E
and which one is E'
: the one we care about, for the sake of discarding, is the one on branchX
, so whichever side of the symmetric difference we put branchX
on, that's the side to take. 这种左/右区别为我们提供了一种方法来识别哪个提交是E
,哪个提交是E
E'
:为了舍弃,我们关心的那个是branchX
上的branchX
,因此,将对称差异的哪一侧放到branchX
继续,这就是需要采取的措施。
Now we can make use of one more rev-list
option: --left-only
or --right-only
. 现在,我们可以再使用一个rev-list
选项:-- --left-only
或--left-only
--right-only
。 These may be used in combination with --cherry-mark
, hence: 这些可以与--cherry-mark
结合使用,因此:
$ git rev-list --left-only --cherry-mark master...two
+0af7c6a3cf5e49928de132c341c848be80ab84c7
=643b37ef242fdc35dfdd4551b42393af3eb91a85
or: 要么:
$ git rev-list --right-only --cherry-mark master...two
=dcbcb2774954437ef0906c6770c7deb924d9286e
Thus, we can run this command and pick out just the =
-marked commit(s) to find D
and E
. 因此,我们可以运行此命令并仅选择=
标记的提交以找到D
和E
In fact, there's a shorthand for --right-only --cherry-mark
(though it also adds --no-merges
), spelled --cherry
. 实际上,拼写--cherry
--right-only --cherry-mark
有一个简写(尽管它也添加了--no-merges
--cherry
。 We can put the branch we want ( branchX
) on the right and use this: 我们可以将所需的分支( branchX
)放在右侧,并使用以下代码:
$ git rev-list --cherry master...branchX
Again, this spits out both +
and =
commits. 同样,这会吐出+
和=
提交。 We want to find the =
ones, so we run this through sed
, telling it to remove =
and print lines, or not print lines if there is no =
to remove: 我们想找到=
,因此我们通过sed
进行此操作,告诉它删除=
并打印行,如果没有=
删除,则不打印行:
$ git rev-list --cherry master...branchX | sed -n -e 's/=//p'
and this will list the IDs of commits D
and E
. 这将列出提交D
和E
的ID。
We only really want E
(and we can use head -1
to get it, provided we make sure we get the commits in topological-sort order), but in fact, it doesn't entirely hurt to exclude D
as well. 我们只真正想要E
(并且可以使用head -1
来获取它,只要我们确保以拓扑排序的方式提交即可),但实际上,排除D
也并不完全有害。 But if we're going to use git rebase
to copy the branchX
commits, we really do want to find just E
, so our final command is: 但是,如果我们要使用git rebase
复制branchX
提交,我们确实确实只想找到E
,所以我们的最终命令是:
$ limit=$(git rev-list --topo-order --cherry master...branchX |
sed -n -e 's/=//p' | head -1)
Now we can run our final git rebase
command: 现在我们可以运行最终的git rebase
命令:
$ git checkout branchX # if needed
$ git rebase --onto master $limit
This rebases, ie, copies, commits that are on the current branch, ie, branchX
, excluding the limiting commit and anything earlier—hence excluding E
and earlier—with the copies going after ( --onto
) master
. 这将重新建立(即复制)当前分支(即branchX
)上的提交(不包括限制提交)和任何更早的内容(因此不包括E
和更早的内容),并且副本位于( --onto
) master
。
Note, though, that it's possible that there are no patch-equivalent commits in the symmetric difference. 但是请注意,对称差异中可能没有补丁等效的提交。 In this case, if you are quite certain there was a removed commit, you will have to find the limiting ( E
in our example) commit yourself, through some other non-automated method. 在这种情况下,如果您确定已经删除了提交,则必须通过其他一些非自动化的方法来查找限制(在我们的示例中为E
)提交自己。 Once you find the "commit E
", the rest goes just as before, using the hash ID as the limit for the --onto master
rebase. 找到“ commit E
”后,其余部分将像以前一样使用哈希ID作为--onto master
rebase的限制。
1 Note that git reflog branch
actually just runs git log -g --oneline branch
. 1请注意, git reflog branch
实际上只运行git log -g --oneline branch
。 This means you can run the same git log
command but omit --oneline
, or replace it with a --pretty=format:...
or --format=...
directive to make up your own format, vs the standard --oneline
format. 这意味着您可以运行相同的git log
命令,但省略--oneline
,或将其替换为--pretty=format:...
或--format=...
指令以形成自己的格式,而不是标准格式--oneline
格式。
2 OK, "should". 2 OK,“应该”。 :-) It is technically possible to do this manually, running git patch-id
on each commit yourself. :-)从技术上讲,可以手动执行此操作,并在每次提交时运行git patch-id
。 But given that git rev-list
does it for you, automatically, why bother? 但是考虑到git rev-list
会自动为您解决这个问题,为什么要麻烦呢? 3 3
3 Stubbornness and/or obstinacy. 3固执和/或固执。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.