我在同一分支上为某个模块进行了一些代码的共同开发,而在同一分支上我也与同事的模块进行了集成。基本上,我最终使两个模块相互交谈时,同时修改了两个模块的代码。现在,我想将所有代码一起提交,但不希望从分支中合并内容。
所以基本上,我在A上进行了开发,从B挑选了我想要的更改,并且已经进行了测试。现在,我想分开提交,以便分支A仅包含“ Apple”,分支B仅包含“ Banana”,而分支C包含集成代码。我不想重命名分支,因此我已经通过cherry-pick将所有更改从分支A复制到了C。另外,我已经挑选了从分支A到B的相关更改。
现在剩下要做的就是从分支A中减去提交,这样就只剩下“ Apple”了。我正在寻找做到这一点的最佳方法。我不相信git revert
是正确的选择,而且我想我可以做一个交互式的变基并删除不需要的A提交,但是我正在寻找验证这是正确的方向,或者需要其他条件。
分支名称并不重要,除了人类。 (您是人吗?😀如果是这样,我们可能会遇到一点障碍。)
对Git重要的是commits。在存储库中提交历史记录。每个提交都有一个唯一的哈希ID,这些哈希ID是Git查找提交的方式(我们将在稍后看到一个重要的例外)。哈希ID大而丑陋,并且look是随机的,基本上是人类无法使用的,这就是为什么我们使用分支名称的原因。每个提交都包含所有文件的完整快照(嗯,所有显示在该提交中的所有文件)。而且,每个提交都保存一些元数据(有关提交的信息),例如创建者,创建时间以及对它的直接父提交的原始哈希ID(对于Git本身而言非常重要)。因此,这使Git从 last lastH
的提交是某个链中的branch-A
的分支名称:... <-F <-G <-H <--branch-A
<< [name
branch-A
保存链中最后一次提交的哈希ID,即,提交H
。提交H
本身保存较早提交G
的哈希ID,该哈希ID保存较早提交F
的哈希ID,依此类推。诀窍是name包含
last
提交的哈希ID。没有其他简单的方法可以找到最后一个!最后一个找到倒数第二个,找到倒数第二个,依此类推,但不仅仅是人需要名称:Git也需要它来查找last提交。 因此,基本上,我在A上进行了开发,从B挑选了我想要的更改,并且已经进行了测试集成。当您使用git cherry-pick
时,您正在告诉Git进行一次[[copy(其影响以及其中的某些元数据)。因此,您和从事B工作的人都是从某个常见的起点开始的,例如commit H:
I--J <-- branch-A
/
...--G--H <-- common-starting-point
\
K <-- branch-B
您提到您使用git cherry-pick
从另一个分支copy
K
复制到一个新的提交中,我将其称为K'
,以表明它与K
的相似程度,从而得出:I--J--K' <-- branch-A / ...--G--H <-- common-starting-point \ K <-- branch-B
请注意,name
branch-A
现在如何指向新的last提交,即现在的K'
。提交K'
具有所有内容的完整快照,但是将K'
与J
进行比较,您会看到与K
与H
进行比较时看到的相同的[[change。除非您告诉Git进行更改,否则K'
的作者和日志消息也将与H
的作者和日志消息匹配。当然,K'
的父哈希是J
,而K
的父哈希是H
。您也可以添加更多提交,就像您可能做的一样:
I--J--K'--L <-- branch-A / ...--G--H <-- common-starting-point \ K <-- branch-B
现在剩下要做的就是从分支A中减去提交,这样就只剩下“ Apple”了。我正在寻找执行此操作的最佳方法。不一定有
best方式。但是,Git很大程度上是为add提交到分支而构建的,而为从分支提交则更少。如果确实要删除提交,则可以告诉Git强制remove
name向后移动。不再是last的提交现在无法找到。例如,如果我们强制名称
提交(按分支名称找到)开始,然后向后工作。branch-A
向后移动以指向提交I
,则会得到:J--K'--L [abandoned] / I <-- branch-A / ...--G--H <-- common-starting-point \ K <-- branch-B
使用Git的常规提交查看工具,例如git log
,我们将不再seeJ
以及K'
和L
,所以看起来它们已经消失了。1] >查看者从last
无论如何,这里最大的问题是Git是为add提交而构建的。您可以使自己的Git向后移动自己的分支名称,例如使用 1git reset
或git branch -f
,但这不会使提交了提交的<< other >> Git,将其向后命名。J
和K'
最终将成为垃圾收集。 Git偶尔会自行运行的git gc
命令通常会在这种情况下运行至少30天,因此您至少需要30天,再加上Git运行需要多长时间git gc
本身,可以改变主意并让他们回来。
一个简单的解决方案
处理此问题的简单方法是使用新名称。由于Git不在乎名称,因此我们可以将其称为neo-A
而不是branch-A
。我们将设置neo-A
指向您最初开始的现有提交H
: I--J--K'--L <-- branch-A
/
...--G--H <-- common-starting-point, neo-A
\
K <-- branch-B
现在,到neo-A
,我们将
添加
I
是我们喜欢的一个,因此我们可以复制它,也可以直接使用它,因为它很好。让我们直接使用后者,方法是使neo-a
向L
方向前进一步,这是Git“喜欢”分支名称的方式。 (当然,向L
而不是K
方向移动很重要,但这很容易,因为我们处于控制之中。) J--K'--L <-- branch-A
/
I <-- neo-A
/
...--G--H <-- common-starting-point
\
K <-- branch-B
H
到L
方向的下一个提交是提交J
。那也很好:我们可以通过将名称neo-A
前进到J
一步,给出:
K'--L <-- branch-A
/
I--J <-- neo-A
/
...--G--H <-- common-starting-point
\
K <-- branch-B
复制它,因为现有的K'
是个问题:我们不想要它。所以我们只是不前进。我们确实需要L
,现在我们必须
L
指向K'
,而我们想要一个新的指向J
。因此,我们这次需要使用git cherry-pick
来生成: K'--L <-- branch-A
/
I--J--L' <-- neo-A
/
...--G--H <-- common-starting-point
\
K <-- branch-B
我们会继续,但是我们已经完成了所有提交,也就是说,我们已经完成了。名称。更容易做到这一点我们现在可以安全地
git push
neo-A
名称。然后,我们只需要说服所有人停止使用old
上面的缺点是我们必须一次一步地“向前移动名称”。如果我们可以创建neo-A
并让Git完成所有工作,那就太好了。事实证明,我们can
。git rebase
命令具有我们喜欢的所有设备。
我们要做的是:创建指向相同
neo-A
并提交为branch-A
运行git rebase -i <hash of common starting point H>
将我们[[不需要
pick
更改为drop
并且Git将自动倒带neo-A
,然后向前或向后跳过提交。最终结果就是我们得出的结果:
K'--L <-- branch-A
/
I--J--L' <-- neo-A (HEAD)
/
...--G--H <-- common-starting-point
\
K <-- branch-B
neo-A
移动到最后复制的提交,在这种情况下为L'
。neo-A
的git push
是安全的。我们会将新的提交(例如L'
)发送到另一个Git,然后要求他们将名称neo-A
设置为指向链中的最后一个提交L'
,就像我们的名字一样。只要没有人参与,我们就不会需要新名称
[如果愿意,我们可以直接用如果没有人在这里造成混淆,或者如果所有其他人都事先知道可能会发生这种情况,请随时倒带并强制推送现有的分支名称。git rebase -i
本身执行branch-A
。结果将是:
K'--L [abandoned] / I--J--L' <-- branch-A (HEAD) / ...--G--H <-- common-starting-point \ K <-- branch-B
然后我们可以使用git push --force
或等价于demand
,另一个Git放弃其提交L
和K'
,并使其名称branch-A
指向提交L'
。假设他们服从了-他们[[可以拒绝了-我们现在将他们的branch-A
移动了。这里唯一真正的问题是,某些讨厌的人可能已经将[
branch-A
]复制到了另一个Git存储库。该人可能希望他们的branch-A
副本仅在正常的正向添加-新提交方向上移动。这是使用不同分支名称的真正原因:避免混淆对分支名称有期望的人。