我想在master之前获取一些X提交的分支,并基本上快照当前差异并从中创建单个提交。
通常我使用git rebase -i
,压缩除最新提交之外的所有提交,这有效。
但是,如果中途变基存在冲突或问题,我必须手动完成它。我不一定想将每个提交相互应用——相反,我想获取我的分支和主节点的 HEAD 之间的当前差异,并从中创建单个提交。
我已经通过手动比较我的整个分支和 master 来完成的,创建mychanges.diff
,然后将其应用于一个干净的分支,但感觉 git 中应该有一种原生的、干净的方法,不是通过应用每个提交来变基,而是快照当前状态并创建与 master 的差异的单个提交。
我相信你想要的是git merge --squash
:
git checkout master
git merge --squash otherbranch
[resolve any conflicts if necessary, then]
git diff --cached # to see what you're about to commit
git commit # to make a single non-merge commit on master
长
差异不是快照,因此此处存在根本不匹配。 让我试试旧的温度类比。 假设我告诉你,今天,这里比昨天温暖了十度。 (虽然实际上差不多:-) 现在我有一个问题要问你:今天的温度是多少? 或者,等效地,昨天的温度是多少?
快照是绝对的。 如果我告诉你它是 68°F/20°C,要么这比昨天高 10°F,要么昨天是 58°F,这两个信息就足够了。一个快照或一个差异是不够的:您需要两个快照,或者一个快照和一个差异。 (请注意,无限数量的差异永远不够:您总是需要在序列中的某个地方至少有一个快照。 否则,可能会有一些东西在开始时是这样,并且从未改变过,你根本不会看到它。
不过,我认为您所描述的是这样的情况:
C--D--E--F--G <-- branch
/
....--A--B--H <-- master
甚至只是:
C--D--E--F--G <-- branch
/
....--A--B <-- master
在这两种情况下,您都可以:
- 比较
B
中的快照和G
中的快照,这会为您提供一组更改以将B
转换为G
;
然后 将这些 - 相同的更改应用于
H
中的快照(如果存在)或G
中的快照(如果不存在H
)。
现在,如果H
根本不存在,这种"应用更改"的事情完全是微不足道的。 结果是G
中的快照。 在这种情况下,你可以将 G 的快照复制到新快照 — 我们称它为G'
而不是H
以帮助提醒我们它与G
完全匹配 — 使用一些假设的 Git 命令1来做到这一点。 结果将是:
C--D--E--F--G <-- branch
/
....--A--B--G' <-- master (HEAD)
但是,如果H
确实存在,您可能希望将B
-vs-G
的更改与B
-vs-H
的更改结合起来,这样您就不会消除您最初进行H
时所做的更改的影响。 组合这些更改的命令是git merge
。
一个正常的和完全的合并,你会得到git checkout master; git merge branch
,将:
- 找到合并库,在本例中为 提交
B
; - 将合并基础提交与当前分支提示
H
进行比较,以查找"我们的"更改; - 将相同的合并基提交与"他们的"(仍然是我们的,但在合并期间
--theirs
)提交G
进行比较,以查找"他们的"更改; - 尝试合并这些更改。
如果存在合并冲突,此正常合并将停止并使您完成作业。 如果没有,此正常合并会使用两个父级进行新提交:
C--D--E--F--G <-- branch
/
....--A--B--H------------M <-- master (HEAD)
我们将跳过 Git 所谓的快进合并(根本不是合并)的概念,直接进入git merge --squash
的作用。
添加--squash
选项时,会告诉 Git 执行两项操作:
- 在合并步骤后停止,即使合并有效。 (您可以使用
git merge --no-commit
获得相同的效果,而无需项目 #2。 如果正常的合并会因冲突而停止,则此壁球合并也会因冲突而停止,因此这与--no-commit
标志完全相同。 当你(用户)通过运行
git commit
完成合并时,Git 将不合并提交M
,而是压缩提交S
:C--D--E--F--G <-- branch / ....--A--B--H------------S <-- master (HEAD)
M
和S
之间的主要区别在于,S
指向仅提交H
,而不是提交G
。 (S
的默认提交消息文本也不同,但由于git commit
在提交消息上打开编辑器时有机会替换此文本,因此这种差异并不重要。
如果提交H
不存在,则git merge --squash
操作仍执行完全相同的过程。 合并基仍然是提交B
:
C--D--E--F--G <-- branch
/
....--A--B <-- master (HEAD)
所以两个区别是:
B
vsB
,看看我们改变了什么:当然什么都没有;B
vsG
,看看它们改变了什么:当然,使下一个快照看起来像提交G
所需的一切。
通过结合这两个差异并进行新的提交S
,我们得到了一个与G
匹配的提交,如果我们愿意,我们可以调用G'
,但我仍然会调用S
:
C--D--E--F--G <-- branch
/
....--A--B--S <-- master (HEAD)
请注意,在使用这样的git merge --squash
后,分支branch
几乎肯定应该完全被杀死。 你不应该回到它并进行更多的提交,除非你从master
中删除提交S
。
(你可能想知道为什么git merge --squash
先停下来。 唯一明智的答案是,这是一次历史事故,其行为从那时起一直被保留下来。 如果默认情况下它没有停止,您可以随时运行git merge --squash --no-commit
,只要您希望它停止。 但是挤压合并的第一个实现是通过在运行自己的内部git commit
之前提前git merge
退出来实现的。 这样编码更容易。 冲突合并和--no-commit
情况再做一个步骤,即在内部状态文件中记录合并正在进行;然后他们退出。 当您运行git commit
时,Git 会注意到"正在进行合并"状态文件,或者不会注意到,具体取决于git merge
是否创建了它。 然后,它要么进行正常的非合并提交,因为没有记录的状态,要么进行合并提交,因为有记录的合并状态。
1有多个 Git 命令可以实现此结果。 没有一个命令可以在一个步骤中完成:至少需要两个 Git 命令来实现这一点。 即使有git merge --squash
也是如此.