从 master 获取当前差异并将其转换为单个提交,而无需将每个提交相互应用

  • 本文关键字:提交 应用 单个 获取 master 转换 git
  • 更新时间 :
  • 英文 :


我想在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 执行两项操作:

  1. 在合并步骤后停止,即使合并有效。 (您可以使用git merge --no-commit获得相同的效果,而无需项目 #2。 如果正常的合并会因冲突而停止,则此壁球合并也会因冲突而停止,因此这与--no-commit标志完全相同
  2. 当你(用户)通过运行git commit完成合并时,Git 将不合并提交M,而是压缩提交S

    C--D--E--F--G   <-- branch
    /
    ....--A--B--H------------S   <-- master (HEAD)
    

    MS之间的主要区别在于,S指向提交H,而不是提交G。 (S的默认提交消息文本也不同,但由于git commit在提交消息上打开编辑器时有机会替换此文本,因此这种差异并不重要。

如果提交H不存在,则git merge --squash操作仍执行完全相同的过程。 合并基仍然是提交B

C--D--E--F--G   <-- branch
/
....--A--B   <-- master (HEAD)

所以两个区别是:

  • BvsB,看看我们改变了什么:当然什么都没有;
  • BvsG,看看它们改变了什么:当然,使下一个快照看起来像提交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也是如此.

最新更新