【问题标题】:Git commit, how to break one commit into twoGit提交,如何将一个提交分成两个
【发布时间】:2019-01-04 23:42:06
【问题描述】:

在分支 temp 中工作,我创建了一个新分支 temp2。我大量重新排列了一个文件,然后对其进行了一些清理。我答应了。

后来我意识到,如果我先进行重新排列、提交、清理、然后提交,我会有更清晰的历史记录。

也就是说,我只想把这个commit变成两个stage,两个commit。

嗯,我记得重新排列的部分。所以我切换到分支 temp 并再次进行了重新排列,没有进行清理。我的想法是,然后我可以将提交从 temp2 带到 temp。

我有什么:

------ before ----- rearrangement <-- temp
              \
               \ 
                \
                 ------- rearrangement plus cleanup <-- temp2

我想要什么:

------ before ----- rearrangement ----- rearrangement plus cleanup <-- temp

首先我尝试将 temp2 重新设置为 temp,但我遇到了合并冲突。这让我感到惊讶,因为没有要求合并任何东西。我认为变基只是改变了提交的附加位置。所以我放弃了。

然后我尝试将 temp2 中的“重排加清理”挑选到 temp 中。但我仍然遇到了合并冲突。这真的让我感到惊讶,因为我认为这完全不可能。所以我也放弃了。

我被困住了。我不明白这里发生了什么。 git 不基于差异。提交只是快照。那么为什么会有冲突呢?我要求做的就是按照一定的顺序排列这些快照。为什么这么难,我该怎么做?

【问题讨论】:

  • 我认为您正在寻找交互式变基。 stackoverflow.com/questions/2740537/reordering-of-commits
  • @intboolstring 我不明白为什么。我不明白为什么它需要是交互式的,我不明白它与我过去所做的交互式 rebase 有什么关系,这涉及到我所做的相反 m 描述,即挤压。
  • 交互式变基具有更多功能,而不仅仅是压缩(改写、重新排序、修复、删除等)
  • @intboolstring 这些名字对我来说都没有任何意义。你说我这里需要哪个?
  • 我认为重新排序...

标签: git


【解决方案1】:

TL;DR

用途:

git checkout temp
hash=$(git log --no-walk --pretty=format:%B temp2 | git commit-tree -p temp temp2^{tree})
git merge --ff-only $hash

将commit C复制到一个新的commit C',它会重用C的日志消息和树,然后调整分支名称temp指向新的commit。

我认为变基只是改变了提交的附加位置...

这是关键错误!

如果我把你的图表缩小,我们开始:

A--B  <-- temp
 \
  C   <-- temp2

git checkout temp2; git rebase temp 所做或尝试做的是将提交 C 复制到新提交 C',这在某些方面“类似于 C”,但将 B 作为其父级。如果成功,最终的结果将是:

     C'  <-- temp2
    /
A--B   <-- temp
 \
  C   [abandoned]

我想你已经知道了;你出错的地方是如何复制发生的想法。

git rebase 所做的本质上是运行git cherry-pickgit cherry-pick 所做的是——嗯,从逻辑的角度来看;底层机制是使用 Git 的合并机制——将提交变成更改,然后将该更改应用到其他地方。因此,Git 将比较(diff)commit C 与 commit A,然后还将 diff commit BA 进行比较,并尝试组合这两个变更集以在 B 上生成 commit C'。当然,这两个变更集重叠,这就是您遇到合并冲突的原因。

您没有使用git rebase 来实现这一点。由于C 已经是您想要的最终结果,您可以简单地将C 的快照——也许还有它的提交消息——复制到一个新的提交C'。没有面向用户的 Git 命令可以做到这一点,但有一个低级命令可以做到这一点。这些低级命令就是 Git 所称的 plumbing 命令。

特别是,git commit-tree 写入了一个提交对象。为此,它需要一个现有的 tree 对象(快照)加上一些父哈希。通常我们可能会使用管道命令git write-tree 创建一棵树,它会写出当前索引,但我们已经在现有提交C 中获得了非常合适的最终结果。我们只需要获取它的树哈希 ID。 gitrevisions syntaxtemp2^{tree},因为名称 temp2 指向提交 C

我们想要的提交副本C 的父级当然是提交Btemp 这个名字就足够了,因为它现在指向提交 B。我们还必须在标准输入上或作为参数或文件向git commit-tree 提供提交日志消息。 git log --no-walk --pretty=format:%B 将从给定的提交中获取日志消息。

一旦我们创建了新的提交对象,我们必须在我们的宽限期内(git gc/git prune)快点让一些名称记住那个对象,也就是 14 天默认。为此,我们可以使用git merge --ff-only 推进当前分支名称(即temp)。

(请注意,只要您确定 git log ... | git commit-tree 可以正常工作,您就可以将整个事情作为一行表达式来完成。)

【讨论】:

  • 感谢您的解释!我太害怕了,不敢按照你的建议去做,但我相信它会帮助其他人。
  • @matt:除了最后的快进操作外,这只是复制一个提交。如果您愿意,可以将分支名称指向副本,而不是快进:git branch temp3 $hash。然后git log temp3 看看你是否喜欢这个结果。
  • 花了一段时间,但我终于明白你在告诉我什么。正如您所说的那样,我的问题实际上是我不理解樱桃采摘和变基都不会移动提交(即它们不会重新排列对象图)-它们重放 提交,即他们尝试将相同的更改修补到图表中不同位置的末尾,作为完全不同的提交。我从来都不知道“重播”是什么意思。
【解决方案2】:

没有在命令行中尝试过,但步骤应该类似:

  1. 硬重置为 temp2。只是为了确保您在 temp2 上没有额外的差异。
  2. 混合重置为 temp1。现在您在 temp1 上,但使用 temp2 的数据作为差异。
  3. 将当前差异提交为 temp1 上的新提交 rearrangement plus cleanup
  4. 中提琴。

【讨论】:

  • 嗯,好吧,但有件事我没有告诉你。 “重新排列加清理”现在是 temp2 中的几个提交。因此,在索引空间中存在“差异”是不够的;我实际上想要提交自己。
  • 基线方法,您可以对 temp2 中的每个提交重复上述 step1,重复几次并将整个提交树重新创建到 temp1。但是,您也可以只为引发冲突的提交执行这些步骤,并简单地将其他干净的提交挑选到 temp1
  • 好的,我明白你的意思了,谢谢。不过,我仍然不明白为什么我正在做的事情需要“引发冲突”。我不想混合两个提交,我只是想将一个提交从一个分支移动到另一个分支的末尾。
  • cherrypicking 和 rebase 等都是基于差异的,尽管概念上的提交是 snapshots
  • 这看起来很有希望,我试过了,但由于某种原因,我最终得到了所有重新排列的材料的两份副本。最后我还是放弃了。
猜你喜欢
  • 1970-01-01
  • 2012-12-18
  • 2013-10-11
  • 2012-09-13
  • 2019-11-24
  • 1970-01-01
  • 2015-08-06
  • 2018-04-28
  • 2012-03-02
相关资源
最近更新 更多