注意:对不起标题。我不确定如何简明扼要地说出我的挑战,因此将这个问题标记为重复(准确)将非常有帮助。
以下是相关行动的时间表,它将构成我的问题的基础:
问题:功能B的PR显示来自功能A的所有先前(未撤销)提交。
我如何,最好不要手动删除所有功能A提交或挑选功能B提交,在主服务器上重新设置功能B并在PR中仅显示来自A..B的提交?
你必须挑选,即使你不想。您可以使用git rebase --onto
以高度自动化且经常但并非总是轻松无痛的方式进行挑选。
据我所知,GitHub本身(这不是那么远),在这里完全没用。但是,你可以在shell级别的Git中做你需要做的事情。
简要背景回顾:当你在Git中构建一个分支时,你真正在做的是添加提交,通常一次一个。提交的基本单位和raison d'être是提交。每个提交都由像95ec6b1b3393eb6e26da40c565520a8db9796e9f
这样的哈希ID唯一标识。 No two different Git objects ever have the same hash ID.每个提交几乎都是一个独立的实体,包含源代码的完整快照。 “几乎”部分是因为大多数提交包含,作为其metadata的一部分,一个先前提交的哈希ID,我们称之为提交的父提交。像feature-A
这样的分支名称包含一个提交的哈希ID,Git称之为分支的提示提交。
当你git checkout feature-A
,编辑,git add
文件,和git commit
结果,你创建一个新的提交。新提交的父级是提交,这是提示,你有git checkout
-ed。它的快照是原始提交中的所有文件,除了那些git add
用您编辑的新内容覆盖的文件。作为一个全新的提交,它获得一个新的,唯一的哈希ID,然后Git将新ID存储到分支名称中,这样您刚刚提交的新提交现在是feature-A
的提示。
到目前为止,这并不是非常有趣,但我们应该注意这些提交是如何链接在一起的,一次一个,建立在之前的提交上:
1 <-- feature-A (HEAD)
/
...--o--o <-- master
成为:
1--2 <-- feature-A (HEAD)
/
...--o--o <-- master
最终成为:
1--2--3--4--5 <-- feature-A (HEAD)
/
...--o--o <-- master
然后你提出了一个拉取请求:“请获取这些新的提交1-2-3-4-5
并做一些事情来合并它们。”谁是你的上游最终确实获得了这些提交并合并它们,但是 - 这就是问题 - 他们使用GitHub的“壁球和合并”功能按钮,内部运行git merge --squash
,它根本不包含这些提交。
相反,git merge --squash
所做的是使用Git的合并机制来进行“合并作为动词”组合变化的过程,但随后进行全新的提交。在他们的上游,他们可能已经添加了一些其他新的提交,所以当他们提交你的提交1-2-3-4-5
他们有:
1--2--3--4--5 [imported - no name]
/
...--o--*--A--B <-- master
他们让他们的Git(和GitHub)结合了从提交*
(合并基础)到B
的变化,即他们做了什么,从*
到5
的变化,即你做了什么,并从中做了一个新的提交C
结果。因为这是--squash
操作,所以新提交不会记录其第二个父级,使图形看起来像这样:
1--2--3--4--5
/
...--o--*--A--B---------C <-- master
当你可能希望它看起来像这样:
1--2--3--4--5
/ \
...--o--*--A--B---------C <-- master
但它没有额外的联系,所以现在你必须处理这个问题。
您继续在自己的存储库中创建了一个feature-B
分支:
1--2--3--4--5 <-- feature-A, feature-B (HEAD)
/
...--o--o <-- master
你现在做了一些提交:
6 <-- feature-B (HEAD)
/
1--2--3--4--5 <-- feature-A
/
...--o--o <-- master
最终导致:
6--7--8 <-- feature-B (HEAD)
/
1--2--3--4--5 <-- feature-A
/
...--o--o <-- master
在某些时候,您甚至可能从您的上游(他们的Git存储库)获得了他们的提交A-B-C
。如果你还没有这样做,你现在应该这样做:
6--7--8 <-- feature-B (HEAD)
/
1--2--3--4--5 <-- feature-A
/
...--o--o <-- master
\
A--B--C <-- upstream/master
请注意,他们的提交C
大致相当于添加你的提交1-2-3-4-5
,除了C
的父亲是B
,而不是那个(并且在这张图中仍然是)你的master
的提示。
您现在要做的是复制提交链6-7-8
,除了您希望将这些副本基于提交C
,而不是提交5
。也就是说,您想要的结果如下所示:
6--7--8 [old feature-B, to be abandoned]
/
1--2--3--4--5 <-- feature-A
/
...--o--o <-- master
\
A--B--C <-- upstream/master
\
C6-C7-C8 <-- feature-B
Git命令集复制提交,并在此过程中使副本有一个新的基础,是git rebase
。但是,如果你只是运行:
git checkout feature-B && git rebase upstream/master
Git将选择复制那些可以从名称feature-B
而不是名称upstream/master
访问的提交。这里可达到的单词意味着如果我们从提示开始,并按照Git的方式向后工作,我们将遇到哪些提交?我们将从提交8
开始,然后到达(通过其父哈希)提交7
,然后6
,依此类推向左边的链。最终我们将达到你的master
的提示,并继续向左。但是如果我们从upstream/master
开始并向后工作,我们将达到你的master
的提示并继续向左 - 所以这些提交是那些没有复制的提交。离开提交1-2-3-4-5-6-7-8
被复制。
再次,这就是问题:这里有太多的提交。我们想要提前停止提交5
,以便我们只复制6-7-8
链。这是我们使用git rebase --onto
而不仅仅是git rebase
的地方。
--onto
当git rebase
完成它的工作时,它必须选择两件事,而不仅仅是一件事:
通常我们只是说git rebase upstream/master
,它从这个名字中找出了这两个。副本位于命名提交之后,我们复制的提交是我们无法从命名提交中获取的提交。
使用git rebase --onto upstream/master
,我们明确地告诉Git:在upstream/master
的提示之后放置副本。这留下了另一个参数来指定限制:不要复制。我们想告诉Git:不要先复制提交5
或其他任何东西。所以我们需要找到提交5
的哈希ID,或者找到提交5
的东西。
分支名称feature/A
指向提交5
。看看我们上面绘制的图表:它就在那里!或者,运行git log --all --decorate --online --graph
并查看Git将绘制的图形。是否存在结束Git不应复制的链的提交的名称?如果是这样,您可以使用该名称。如果没有,您只需输入原始哈希ID即可。
在我们的例子中,只要没有名称改变了他们指向的提交,我们就可以运行:
git checkout feature-B
git rebase --onto upstream/master feature-A
这告诉Git要检查(进入提示,并记录名称)feature-B
;然后,在当前提交结束时,复制一些提交,在upstream/master
指向的提交之后放置副本。副本以当前提交结束,并以删除以feature-A
结尾的提交后的任何内容开始。
那当然是提交6-7-8
。因此Git将git checkout --detach upstream/master
,使HEAD
直接指向(没有分支名称)提交:
6--7--8 <-- feature-B
/
1--2--3--4--5 <-- feature-A
/
...--o--o <-- master
\
A--B--C <-- upstream/master, HEAD
然后Git将复制提交6
,就像在其哈希ID上执行git cherry-pick
一样:
6--7--8 <-- feature-B
/
1--2--3--4--5 <-- feature-A
/
...--o--o <-- master
\
A--B--C <-- upstream/master
\
C6 <-- HEAD
如果情况顺利,Git将挑选提交7
:
6--7--8 <-- feature-B
/
1--2--3--4--5 <-- feature-A
/
...--o--o <-- master
\
A--B--C <-- upstream/master
\
C6-C7 <-- HEAD
然后重复8
成为C8
;最后,Git将撕下旧链feature-B
上的标签6-7-8
并将其粘贴到新副本C6-C7-C8
的末尾:
6--7--8 [abandoned]
/
1--2--3--4--5 <-- feature-A
/
...--o--o <-- master
\
A--B--C <-- upstream/master
\
C6-C7-C8 <-- feature-B (HEAD)
有标签feature-B
指向C8
,Git将重新附加HEAD
到该标签,并且rebase现在已完成,您可以发出一个拉取请求,要求您的上游人员将提交C6-C7-C8
合并到他们的存储库中。