让我们说你的主人看起来像
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
答案 0 :(得分:2)
一般来说,这很困难,有时甚至是不可能的。事实上,如果添加一些约束,有时很多更容易。
如果您有合适的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'
实际上是D
和E
的副本。原始文件仍保留在存储库中,仍可从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
,我们必须将 D
和E
复制到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...branchX
或branchX...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'
= D
和E'
= E
,并以某种方式从此集合中选择E
。现在,我们将--cherry-mark
添加到git rev-list
命令:这会将D'
和E'
以及D
和E
标记为=
个字符,并使用C
个字符标记F
,G
和+
。在这里,我已经在一个非常详细的回购中运行它:实际上我只有E
和E'
加上一个唯一的提交。
$ git rev-list --cherry-mark master...two
=dcbcb2774954437ef0906c6770c7deb924d9286e
+0af7c6a3cf5e49928de132c341c848be80ab84c7
=643b37ef242fdc35dfdd4551b42393af3eb91a85
好了到目前为止,但是有一个明显的问题:这列出了E
和E'
,我们只想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
因此,我们可以运行此命令并选择=
- 标记的提交,以查找D
和E
。
事实上,--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'
这将列出提交D
和E
的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
及更早 - 随后的副本( --onto
)master
。
但请注意,对称差异中可能没有与补丁等效的提交。在这种情况下,如果您确定已删除提交,则必须通过其他非自动方法自行提交限制(在我们的示例中为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 顽固和/或顽固。