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