我有一个分支机构,过去几个月我一直在几台计算机上亲自工作。结果是一个很长的历史链,我想在将它合并到主分支之前进行清理。最终目标是摆脱我在处理服务器代码时经常做的所有wip提交。
以下是gitk历史可视化的屏幕截图:
在这个底部的方式是我从主人分支的点。自从我开始这个分支以来,Master已经改变了一点,但是这些变化是不相交的,所以合并应该是小菜一碟。我通常的工作流程是重新加入master,然后压缩wip提交。
我试图执行一个简单的
git rebase -i master
我编辑了sqush的提交。
它似乎开始很好,但后来失败了,并希望我解决冲突。然而,似乎没有好办法通过观察差异来解决它。每个部分都使用范围中未定义的变量,因此我不确定如何解决它们。
我也尝试使用git rebase -i -s recursive -X theirs master
,它没有导致冲突,但是它改变了修改后的分支的HEAD状态(我想以最终结果的方式编辑历史记录) HEAD不会改变)。
我相信这些冲突来自链条中可以看到钻石图案的部分。 (例如,在重新分类的分类器......和Merge分支iccv之间)。
要更好地表达我的问题,请A
="合并分支iccv"和B
="重写分类器"请参阅图像中的示例。其间的提交将是X
和Y
。
...
|
|
A
/ \
| X
Y |
\ /
B
|
|
...
我想重写历史记录,以便A
的状态完全正确,并有效地破坏中间表示X
和Y
,因此生成的历史记录如下所示
...
|
|
A
|
|
B
|
|
...
有没有办法将已解决的A
,X
和Y
状态压缩到像这样的历史链中间的单个提交中?
如果A
和B
是提交的SHAID,那么我可以运行一个简单的命令(或者可能是脚本)来实现我想要的结果吗?
如果A
是HEAD,我相信我能做到
git reset B
git commit -am "recreating the A state"
创建一个新头,但如果A
位于这样的历史链中间,我怎么能这样做呢?我想保留它之后的所有节点的历史记录。
答案 0 :(得分:10)
首先清除当前工作树,然后运行以下命令:
#initial state
git branch backup thesis4
git checkout -b tmp thesis4
git reset A --hard
git reset B --soft
git commit
git cherry-pick A..thesis4
git checkout thesis4
git reset tmp --hard
git branch -D tmp
S
是X,Y,A
的壁球。 M'
相当于M
和N'
到N
。如果要恢复初始状态,请运行
git checkout thesis4
git reset backup --hard
答案 1 :(得分:4)
这可以做到,但是通过常规机制,它可以从痛苦到痛苦,到很多痛苦。
根本问题在于,每当您想要更改内容时,您必须复制提交新的(略有不同的)提交。原因是没有提交可以改变。 1 原因是提交的哈希ID是提交,非常真实感觉:Git的哈希ID是Git如何找到底层对象的。更改对象中的任何位,它将获得一个新的,不同的哈希ID。 2 因此,当您想要来自:
X
/ \
...--B A--C--D--E <-- branch
\ /
Y
看起来像:
...--B--A--C--D--E <-- branch
B
之后的事物不能<{1}},它必须是一个不同的提交,只是闻起来像A
。我们可以调用此提交A
来区分它们:
A'
但是,如果我们将...--B--A'-...
复制到一个新的,更新鲜的(但同一棵树)A
,其历史记录中不再有中间内容 - 即A'
直接连接到A'
- 然后我们必须还复制 B
之后的第一个提交。一旦我们这样做,我们必须在那之后复制提交,依此类推。结果是:
A'
1 心理学家喜欢说change is hard,但对于Git来说,它几乎是不可能的! : - )
2 Hash collisions are technically possible,但如果它们发生,则表示您的存储库停止添加新内容。也就是说,如果你设法提出了一个类似旧的提交但是有了你想要的更改,和具有相同的哈希ID,Git会禁止你添加它!
...--B--A'-C'-D'-E' <-- branch
注意:如果可能,请使用此方法;它更容易理解和正确。
复制此类提交的标准命令是git rebase -i
。但是,rebase与git rebase
之类的合并提交交易非常糟糕。实际上,它通常会完全抛弃它们,而是倾向于将所有内容线性化:
A
例如。
现在,如果合并提交...--B--X--Y'-C'-D'-E' <-- branch
进展顺利,即A
中的任何内容都不依赖于X
,反之亦然,那么简单的Y
就足够了。您可以更改提交git rebase -i <hash-of-B>
和pick
的{{1}}中的第一个,这可能实际上是很多提交到X
,一切都很顺利你完成了:Git完全支持Y
和squash
一个合并提交X
具有相同树的单个组合Y'
提交。结果是:
XY'
如果我们调用A
...--B--XY'-C'-D'-E' <-- branch
,然后通过忘记原始哈希ID来删除所有刻度标记,我们就会得到您想要的内容。
XY'
如果合并很困难,那么你想要的是保留合并中的树,同时删除所有A'
和git replace
提交。这里git replace
is the (or a) right solution。 Git的替换有点复杂,但是你可以指示Git进行一个新的提交X
,就像Y
一样,但A'
作为其单父哈希ID& #34 ;. Git现在将具有此提交图结构:
A
这个特殊的B
名称告诉Git,当它执行 X
/ \
...--B A--C--D--E <-- branch
|\ /
| Y
\
A' <-- refs/replace/<complicated-thing>
和其他使用提交ID的命令时,Git应该将其隐喻的眼睛从提交refs/replace
转移到外观而是在提交git log
。由于A
是A'
的副本,A'
会让Git查看A
并查看同一棵树;并且git checkout <hash of A>
在A'
而不是git log
旁边显示相同的日志消息。
请注意,此时存储库中都存在A'
和A
。它们是并排的,Git只是向您展示{ {1}}代替A
,除非您使用特殊的A'
标记。一旦Git向您显示(并使用)A'
而不是A
,它就会跟随从--no-replace-objects
到A'
的向后链接,跳过所有A
和A'
。
B
和X
一旦您对替换感到满意,您可能希望将其永久化。您可以使用Y
执行此操作,它只是复制提交。它从一些起点开始复制并在历史中移动前进,与Git的正常向后相反&#34;从今天开始,在历史中向后工作&#34;方式。
当filter-branch正在制作副本时 - 以及它的副本列表 - 它通常会像Git的其余部分一样避免这种情况。因此,如果我们有上面显示的历史记录,并且我们告诉filter-branch在X
结束并在commit Y
之后开始,它将收集现有的提交列表:
git filter-branch
然后颠倒顺序。 (事实上,如果我们愿意的话,我们可以在branch
停留,正如我们所见。)
接下来,filter-branch会将B
复制到新的提交。这个新提交将以E, D, C, A'
作为其父项,与A'
相同的日志消息,相同的树,相同的作者和日期戳等等 - 简而言之,它将完全成为与A'
相同。因此它将获得与B
相同的哈希ID,并且实际上是提交A'
。
接下来,A'
会将A'
复制到新提交。此新提交将以A'
作为其父项,与filter-branch
相同的日志消息,以及相同的树等。这与原始C
略有不同,原始A'
的父级为C
,而非C
。因此,这个新提交会获得一个不同的哈希ID:它变为提交A
。
接下来,A'
将复制C'
。这将成为filter-branch
,与D
的副本D'
相同。
最后,C
会将C'
复制到filter-branch
,并E
指向E'
,告诉我们:
branch
我们现在可以删除E'
名称以及用于保存原始 X
/ \
...--B A--C--D--E <-- refs/original/refs/heads/branch
|\ /
| Y
\
A' <-- refs/replace/<complicated-thing>
\
C'-D'-E' <-- branch
的过滤分支refs/replace/
的备份副本。当我们这样做时,名字就会消失,我们可以重新绘制图表:
refs/heads/branch
这正是我们想要(并获得)使用E
的原因,但无需再次进行合并。
要告诉...--B--A'-C'-D'-E' <-- branch
停止的位置,请使用git rebase -i
或git filter-branch
。否则^<hash-id>
不会停止列出要复制的提交,直到它用完提交:它将跟随提交^<name>
到其父级,以及父级的父级,依此类推回溯历史的方式。这些提交的副本将与原始文件一点一点地相同,这意味着它们实际上是原始文件,相同的哈希ID和所有文件;但是他们需要很长时间才能完成。
由于我们可以停在git filter-branch
甚至B
,我们可以使用<hash-id-of-B>
来识别提交<hash-id-of-A'>
。或者我们可以使用^refs/replace/<hash>
,这可能实际上更容易。
此外,我们可以写A
或^<hash-id>
。两者的意思相同(详见the gitrevisions documentation)。所以:
^<hash> branch
足以进行过滤以将替换粘合到位。
如果一切顺利,请删除the git filter-branch
documentation末尾附近显示的<hash>..branch
引用,并删除替换引用,您就完成了。
作为git filter-branch -- <hash>..branchname
的替代方法,您还可以使用refs/original/
复制提交。有关详细信息,请参阅ElpieKay's answer。这基本上和以前一样,但是使用&#34;复制提交&#34;工具而不是&#34; rebase来复制提交然后隐藏原件&#34;工具。它有一个棘手的步骤,使用git replace
来设置索引以匹配提交git cherry-pick
以进行提交git reset --soft
。