简体   繁体   English

使用master从master删除了提交的基础为分支建立基础

[英]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. 如果添加一些约束,则更容易(实际上有时容易)。

TL;DR: conclusions TL; DR:结论

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

How we got here 我们怎么到这里

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可以到达的提交branchXABCDEFG 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'实际上是DE 副本 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 ,这不是您想要的。

How to get what you want 如何得到你想要的

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 ,必须 DE 复制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'具有相同的修补程序IDE

Patch IDs are how git cherry , and hence git rev-list 's --cherry-pick and --cherry-mark options, work. 补丁ID是git cherrygit 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...branchXbranchX...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' = DE' = 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'以及DE ,并用+字符标记CFG 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. 在这里,我在一个未详细描述的仓库上运行它:实际上,我只有EE'以及一个唯一的提交。

$ 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 . 到目前为止,还可以,但是有一个明显的问题:它同时列出了EE' ,而我们只需要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 . 因此,我们可以运行此命令并仅选择=标记的提交以找到DE

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 . 这将列出提交DE的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和更早的内容),并且副本位于( --ontomaster

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.

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