使用master删除一个分支,并从master中删除提交

时间:2017-01-17 18:58:43

标签: git

让我们说你的主人看起来像

1 2 3 4 5

其中1~5是单独的修订。 现在branchX看起来像

1 2 3 4 5 6 7

然后,由于某些原因,一些提交已从master中删除, 所以现在

1 2 4 5

是大师的样子(3被移除)。

我想用branchX

重新定义master

应该看起来像

1 2 4 5 6 7

修改 这个简单的例子只是6 7,只添加了两个提交,但在我的实际场景中,我有200个提交添加到branchX

1 个答案:

答案 0 :(得分:2)

一般来说,这很困难,有时甚至是不可能的。事实上,如果添加一些约束,有时很多更容易。

TL; DR:结论

如果您有合适的reflog条目,例如master@{1},则命令序列只是:

$ git checkout branchX
$ git rebase --onto master master@{1}

如果没有,我们必须找到合适的上游限制提交:

$ 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

我们如何到达

首先,请记住,Git中的分支名称命名从分支提示可到达的每个提交(提示是分支名称本身指向的提交)。这里的可达性由DAG中的弧确定,即哪些提交被认为是后来提交的祖先。

还要记住每个提交的真实名称是它的SHA-1 ID,它们都是唯一的,并且由提交对象的只读内容决定。删除提交是不可能的:您只能复制所有子项, new (不同)提交,原始子项指向提交&# 39; s父母及其复制的后代指向相应的复制父母。

你的情景说你实际上有这个:

A--B--C--D--E   <-- master
             \
              F--G   <-- branchX

其中通过沿大致向左的方向跟随直接链接找到每个提交的父级。 G的(单个)父级是F; F的父母是E; E的父级是D,依此类推,回到A,根本没有父级(是根提交)。

master可以访问的提交集A-B-C-D-E。可以从branchX到达的提交集是A-B-C-D-E-F-G。你和Git可以谈论的方式&#34;提交branchX&#34;不使用A-B-C-D-E即可使用,而不仅仅是branchX,而是master..branchX。这是从branchX 减去可从master到达的集合可以访问的提交。

然后,去&#34;删除&#34;从C提交master,这肯定发生了:

     D'-E'  <-- master
    /
A--B--C--D--E   [master was this before the copies]
             \
              F--G   <-- branchX

此处D'E'实际上是DE副本。原始文件仍保留在存储库中,仍可从branchX访问。 表达式master..branchX不再有效, ,因为master现在命名为E'和祖先,即A-B-D'-E' 。这会减去那些提交 - 它允许在集合代数C-D-E-F-G中减去首先不存在的东西,这不是你想要的。

如何得到你想要的东西

基本问题归结为识别提交E。如果我们可以找到commit E,我们可以编写E..branchX,即可以从branchX到达的所有提交的集合,减去可以从提交E到达的集合。但我们如何找到E

如果您是重新指定名称master以提交E'的人,那么这可能非常简单。您所要做的就是先将E的SHA-1哈希值保存在某处 - 事实上,如果您是以这种方式重写master的人,那么做了保存,在您master的reflog中。 reflog条目为master@{1}master@{2},依此类推。您可以使用git reflog master查看这些内容。 1 每个reflog条目也都有一个日期和时间戳记,因此您可以编写master@{yesterday}master@{1.week.ago}来查找基于相对日期的适当编号条目。

这是迄今为止最简单的方法,并且它适用于所有情况,即使E是已删除&#34;的提交。请注意,当我们&#34;删除&#34;提交C,我们必须 DE复制到D'E'。这是因为这两个提交是C后代,可以从master到达。我们是否应该决定删除E,但是...... E可以从master访问哪些master的孩子?

没错。没有这样的提交。我们可以简单地将D指向提交A-B-C-D,将master留在{{} 1}}和E现在显然是branchX独有的。但是,无论何时我们调整我们的主人,我们都会创建一个reflog条目来保留之前的值,所以我们再简单一次查看reflog,发现E是有趣的提交。

如果(a)我们自己没有调整master或(b)我们确实这样做了,但是很久以前我们的reflog条目已过期。 (对于像提交E这样的情况,默认情况下会在30天后发生这种情况。)在这种情况下,我们只能找到E ,如果有一些副本E'新的连锁店。即便如此,如果副本E'E具有相同的patch ID,我们仍然只能找到它。

补丁ID是git cherry以及git rev-list --cherry-pick--cherry-mark选项的工作方式。我们使(或Git做出)假设当复制提交时,通常它被复制而没有重大改变,这样通过检查提交的略微精简git show计算的哈希ID将为原件和副本提供相同的哈希ID。这些补丁称为补丁等效,并在某种意义上将配对提交标记为&#34;等于&#34;。

我们还必须 2 使用对称差异表示法master...branchXbranchX...master。因为它是对称的,所以我们使用哪个顺序并不重要(除了--left-right git rev-list中的整个左对比部分,我们通常会想要这样做。无论如何,它的作用是产生以下集合代数运算:

A..B = (reachable(A) | reachable(B)) - (reachable(A) & reachable(B))

即,生成可从 分支提示访问的提交集,不包括可从两个分支提示访问的提交。因此,给定:

     D'-E'  <-- master
    /
A--B--C--D--E--F--G   <-- branchX

对称差异给了我们D', E', C, D, E, F, G

因此,如果我们运行git rev-list master...branchX,我们将获得这套完整的提交。我们现在要做的就是看到D' = DE' = E,并以某种方式从此集合中选择E。现在,我们将--cherry-mark添加到git rev-list命令:这会将D'E'以及DE标记为=个字符,并使用C个字符标记FG+。在这里,我已经在一个非常详细的回购中运行它:实际上我只有EE'加上一个唯一的提交。

$ git rev-list --cherry-mark master...two
=dcbcb2774954437ef0906c6770c7deb924d9286e
+0af7c6a3cf5e49928de132c341c848be80ab84c7
=643b37ef242fdc35dfdd4551b42393af3eb91a85

好了到目前为止,但是有一个明显的问题:这列出了EE',我们只想E。好吧,让我们回顾一下,做另一个转发列表变种:

$ git rev-list --left-right master...two
>dcbcb2774954437ef0906c6770c7deb924d9286e
<0af7c6a3cf5e49928de132c341c848be80ab84c7
<643b37ef242fdc35dfdd4551b42393af3eb91a85

这标记了每次提交,而不是+=,而是<(左)或>(右)。分支two上的提交,即&#34;与&#34;相同; master上的那个实际上是dcbcb27...master上与two上的提交相同的提交是643b37e...。这种左/右区分为我们提供了一种方法来识别哪个提交是E,哪个是E':我们关心的是为了丢弃,是branchX上的提交,无论我们将branchX置于对称差异的哪一边,都是要采取的一面。

现在我们可以使用另外一个rev-list选项:--left-only--right-only。这些可以与--cherry-mark结合使用,因此:

$ git rev-list --left-only --cherry-mark master...two
+0af7c6a3cf5e49928de132c341c848be80ab84c7
=643b37ef242fdc35dfdd4551b42393af3eb91a85

或:

$ git rev-list --right-only --cherry-mark master...two
=dcbcb2774954437ef0906c6770c7deb924d9286e

因此,我们可以运行此命令并选择= - 标记的提交,以查找DE

事实上,--right-only --cherry-mark(虽然它也添加了--no-merges)的拼写为--cherry。我们可以将我们想要的分支(branchX)放在右边并使用它:

$ git rev-list --cherry master...branchX

同样,这会吐出+=次提交。我们希望找到=个,因此我们通过sed执行此操作,告诉它删除=并打印行,如果没有=则不打印行删除:

$ git rev-list --cherry master...branchX | sed -n -e 's/=//p'

这将列出提交DE的ID。

我们真的只想要E(我们可以使用head -1来获取它,前提是我们确保以拓扑排序顺序获取提交),但事实上,它并没有这样做。完全伤害以排除D。但是,如果我们要使用git rebase复制branchX提交,我们确实只想查找E,所以我们的最终命令是:

$ limit=$(git rev-list --topo-order --cherry master...branchX | 
  sed -n -e 's/=//p' | head -1)

现在我们可以运行最终的git rebase命令:

$ git checkout branchX    # if needed
$ git rebase --onto master $limit

这个rebase,即副本,提交在当前分支上,即branchX,不包括限制提交和任何更早 - 因此排除E及更早 - 随后的副本( --ontomaster

但请注意,对称差异中可能没有与补丁等效的提交。在这种情况下,如果您确定已删除提交,则必须通过其他非自动方法自行提交限制(在我们的示例中为E)。一旦找到&#34; commit E&#34;,其余的就像以前一样,使用哈希ID作为--onto master rebase的限制。

1 请注意,git reflog branch实际上只运行git log -g --oneline branch。这意味着您可以运行相同的git log命令但省略--oneline,或将其替换为--pretty=format:...--format=...指令以构成您自己的格式,而不是标准{{ 1}}格式。

2 好的,&#34;应该&#34;。 :-)技术上可以手动执行此操作,自己在每次提交时运行--oneline。但鉴于git patch-id

3 顽固和/或顽固。