git - 从 master 获取当前差异并将其转换为单个提交,而无需将每个提交应用在彼此之上
问题描述
我想在 master 之前采取一些 X 提交的分支,基本上快照当前的差异并从中创建一个单一的提交。
通常我使用git rebase -i
, squash 除最新提交之外的所有提交,这很有效。
但是,如果在修复中途存在冲突或问题,我必须手动完成它。我不一定要在彼此之上应用每个提交 - 相反,我想获取我的分支的 HEAD 和 master 之间的当前差异,并从中创建一个单一的提交。
我已经通过手动区分我的整个分支和 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
确实存在,您可能希望将-vs-的更改与-vs-的更改结合起来,这样您就不会消除一开始所做更改的影响。结合这些更改的命令是.B
G
B
H
H
git merge
一个正常的和完全的合并,你会得到git checkout master; git merge branch
:
- 找到合并基础,在这种情况下是 commit
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
操作仍然执行完全相同的过程。合并基础仍然是 commit B
:
C--D--E--F--G <-- branch
/
....--A--B <-- master (HEAD)
所以这两个差异是:
B
vsB
,看看我们改变了什么:当然没有;和B
vsG
,看看它们发生了什么变化:G
当然,使下一个快照看起来像 commit 所需的一切。
通过结合这两个差异并进行新的提交S
,我们得到了一个匹配的提交,如果我们愿意G
,我们可以调用G'
它,但我仍然会调用S
:
C--D--E--F--G <-- branch
/
....--A--B--S <-- master (HEAD)
请注意,在git merge --squash
这样使用之后,几乎可以肯定分支branch
应该完全被杀死。除非您将commitS
从master
.
(您可能想知道为什么git merge --squash
要先停止。唯一明智的答案是,这是一个历史事故,其行为从那时起就一直保留下来。如果默认情况下它没有停止,那么您可以git merge --squash --no-commit
在您确实希望它停止时运行。但第一个squash-merge 的实现通过git merge
提前退出来工作,就在它运行自己的 internal 之前git commit
。这样编码更容易。冲突的合并和--no-commit
案例再做一步,即在内部记录合并正在进行状态文件;然后它们退出。当你运行时git commit
,Git 要么注意到“合并进行中”状态文件,要么没有,这取决于是否git merge
创造了它。然后它要么进行正常的非合并提交,因为没有记录状态,要么进行合并提交,因为记录了合并状态。)
1有多个 Git 命令可以实现这个结果。没有一个命令可以一步完成:至少需要两个 Git 命令才能做到这一点。即使使用 也是如此git merge --squash
。
推荐阅读
- sql - 将 nvarchar 值“”转换为数据类型 int 时 SQL Server 数据库错误转换失败
- powershell - PowerShell 为 cmdlet 实现 -AsJob
- php - 如何使用 PHP 根据下拉列表中的选择从数据库中获取数据?
- command - “代码”不是内部或外部命令、可运行程序或批处理文件
- reactjs - 将 React 应用程序转换为 PWD 后,网址发生了变化
- python - 根据另一个数组中指示的位置提取数组
- javascript - 如何在网络面板中部署 node.js 应用程序?
- c++ - 检查链表是否为回文
- javascript - 在javascript中使用双反斜杠解析json
- php - 如何在 PHP 中重置数组键