为什么git-rebase在我所做的只是压缩提交时给了我合并冲突?

我们有一个超过400个提交的Git仓库,其中的第一个是很多的反复试验。 我们希望通过将许多压缩成单个提交来清理这些提交。 当然,git-rebase似乎是要走的路。 我的问题是,它最终与合并冲突,这些冲突并不容易解决。 我不明白为什么应该有任何冲突,因为我只是压制承诺(不删除或重新排列)。 很可能,这表明我不完全理解git-rebase如何做它的南瓜。

以下是我正在使用的脚本的修改版本:


repo_squash.sh(这是实际运行的脚本):


rm -rf repo_squash git clone repo repo_squash cd repo_squash/ GIT_EDITOR=../repo_squash_helper.sh git rebase --strategy theirs -i bd6a09a484b8230d0810e6689cf08a24f26f287a 

repo_squash_helper.sh(此脚本仅用于repo_squash.sh):


 if grep -q "pick " $1 then # cp $1 ../repo_squash_history.txt # emacs -nw $1 sed -f ../repo_squash_list.txt < $1 > $1.tmp mv $1.tmp $1 else if grep -q "initial import" $1 then cp ../repo_squash_new_message1.txt $1 elif grep -q "fixing bad import" $1 then cp ../repo_squash_new_message2.txt $1 else emacs -nw $1 fi fi 

repo_squash_list.txt :(此文件仅用于repo_squash_helper.sh)


 # Initial import s/pick \(251a190\)/squash \1/g # Leaving "Needed subdir" for now # Fixing bad import s/pick \(46c41d1\)/squash \1/g s/pick \(5d7agf2\)/squash \1/g s/pick \(3da63ed\)/squash \1/g 

我会把“新消息”的内容留给你的想象。 最初,我没有使用“–strategy theirs”选项(即使用默认策略,如果我正确理解文档是recursion的,但我不确定使用哪种recursion策略),而且没有使用“工作。 另外,我应该指出,使用repo_squash_helper.sh中注释掉的代码,我保存了sed脚本运行的原始文件,并运行sed脚本来确保它正在做我想做的事情(它是)。 再次,我甚至不知道为什么会有冲突,所以使用哪种策略似乎并不重要。 任何build议或见解都会有所帮助,但大多数情况下,我只是想让这个挤压的工作。

与Jefromi讨论的额外信息更新:

在处理我们庞大的“真实”存储库之前,我在testing库上使用了类似的脚本。 这是一个非常简单的存储库,testing工作干净。

我失败时得到的消息是:

 Finished one cherry-pick. # Not currently on any branch. nothing to commit (working directory clean) Could not apply 66c45e2... Needed subdir 

这是第一次壁球提交后的第一个select。 运行git status产生一个干净的工作目录。 如果我然后做一个git rebase --continue ,在几次提交之后我收到了一个类似的消息。 如果我再做一遍,在几十次提交之后,我又得到了另一个非常类似的信息。 如果我再次这样做,这一次它经历了大约一百次提交,并产生这样的信息:

 Automatic cherry-pick failed. After resolving the conflicts, mark the corrected paths with 'git add <paths>', and run 'git rebase --continue' Could not apply f1de3bc... Incremental 

如果我然后运行git status ,我会得到:

 # Not currently on any branch. # Changes to be committed: # (use "git reset HEAD <file>..." to unstage) # # modified: repo/file_A.cpp # modified: repo/file_B.cpp # # Unmerged paths: # (use "git reset HEAD <file>..." to unstage) # (use "git add/rm <file>..." as appropriate to mark resolution) # # both modified: repo/file_X.cpp # # Changed but not updated: # (use "git add/rm <file>..." to update what will be committed) # (use "git checkout -- <file>..." to discard changes in working directory) # # deleted: repo/file_Z.imp 

“这两个修改”位听起来很奇怪,因为这只是一个select的结果。 还有一点值得注意的是,如果我看一下“冲突”,就可以归结为一个版本,一个版本以[tab]字符开头,另一个版本则有四个空格。 这听起来像是我已经build立了我的configuration文件可能是一个问题,但没有什么类似的。 (我注意到core.ignorecase设置为true,但显然git-clone是自动完成的,考虑到原始源代码是在Windows机器上的,我并不完全感到惊讶。

如果我手动修复file_X.cpp,那么随后会出现另一个冲突,这次是一个版本认为应该存在的文件(CMakeLists.txt),一个版本认为不应该。 如果我通过说我想要这个文件(我这样做)来解决这个冲突,稍后我会得到另一个冲突(在同一个文件中),现在有一些相当不重要的变化。 冲突的方式仍然只有25%左右。

我也应该指出,因为这可能是非常重要的,这个项目开始在一个svn库。 最初的历史很可能是从该svn库导入的。

更新#2:

在百灵(Jefromi的意见的影响),我决定做我的repo_squash.sh更改为:

 rm -rf repo_squash git clone repo repo_squash cd repo_squash/ git rebase --strategy theirs -i bd6a09a484b8230d0810e6689cf08a24f26f287a 

然后,我只是接受原来的条目。 也就是说,“基地”不应该改变一件事情。 它结束了以前描述的相同的结果。

更新#3:

或者,如果我省略了策略,并用以下命令replace最后一个命令:

 git rebase -i bd6a09a484b8230d0810e6689cf08a24f26f287a 

我不再得到“没有任何承诺”的基调问题,但我仍然与其他冲突。

更新玩具资源库,重现问题:

test_squash.sh(这是你实际运行的文件):

 #======================================================== # Initialize directories #======================================================== rm -rf test_squash/ test_squash_clone/ mkdir -p test_squash mkdir -p test_squash_clone #======================================================== #======================================================== # Create repository with history #======================================================== cd test_squash/ git init echo "README">README git add README git commit -m"Initial commit: can't easily access for rebasing" echo "Line 1">test_file.txt git add test_file.txt git commit -m"Created single line file" echo "Line 2">>test_file.txt git add test_file.txt git commit -m"Meant for it to be two lines" git checkout -b dev echo Meaningful code>new_file.txt git add new_file.txt git commit -m"Meaningful commit" git checkout master echo Conflicting meaningful code>new_file.txt git add new_file.txt git commit -m"Conflicting meaningful commit" # This will conflict git merge dev # Fixes conflict echo Merged meaningful code>new_file.txt git add new_file.txt git commit -m"Merged dev with master" cd .. #======================================================== # Save off a clone of the repository prior to squashing #======================================================== git clone test_squash test_squash_clone #======================================================== #======================================================== # Do the squash #======================================================== cd test_squash GIT_EDITOR=../test_squash_helper.sh git rebase -i HEAD@{7} #======================================================== #======================================================== # Show the results #======================================================== git log git gc git reflog #======================================================== 

test_squash_helper.sh(由test_sqash.sh使用):

 # If the file has the phrase "pick " in it, assume it's the log file if grep -q "pick " $1 then sed -e "s/pick \(.*\) \(Meant for it to be two lines\)/squash \1 \2/g" < $1 > $1.tmp mv $1.tmp $1 # Else, assume it's the commit message file else # Use our pre-canned message echo "Created two line file" > $1 fi 

PS:是的,当你看到我使用emacs作为后退编辑器时,我知道你们中有些人会畏缩。

PPS:我们确实知道在重新绑定之后,我们将不得不将现有存储库中的所有克隆都删除。 (沿着“你不应该在发布之后重新发布一个版本库”)。

PPPS:有谁能告诉我如何添加赏金呢? 我在这个屏幕上的任何地方都看不到选项,无论我处于编辑模式还是视图模式。

好的,我有足够的信心抛出一个答案。 也许将不得不编辑它,但我相信我知道你的问题是什么。

你的玩具回购testing案例有一个合并 – 更糟的是,它与冲突合并。 而你正在重组合并。 没有-p (不完全与-i工作),合并被忽略。 这意味着,无论你在冲突解决scheme中做了什么, 都不会在rebase尝试挑选下一个提交时出现,所以它的补丁可能不适用。 (我认为这是一个合并冲突,因为git cherry-pick可以通过在原始提交,当前提交和共同祖先之间进行三方合并来应用修补程序。)

不幸的是,正如我们在评论中指出的那样, -i-p (保留合并)不会相处得很好。 我知道编辑/改写工作,而重新sorting工作则不行。 不过,我相信它可以很好地用南瓜。 这没有logging,但它适用于我下面描述的testing案例。 如果你的情况更复杂,你可能会遇到很多麻烦,尽pipe它仍然是可能的。 (故事的道德: 合并之前rebase -i清理。)

那么,假设我们有一个非常简单的例子,我们想把A,B和C压在一起:

 - o - A - B - C - X - D - E - F (master) \ / Z ----------- 

现在,就像我说的,如果在X中没有冲突,那么git rebase -i -p就像你期望的那样工作。

如果有冲突,事情会变得棘手。 它会做很好的压缩,但是当它试图重新合并时,冲突将再次发生。 你将不得不再次解决它们,将它们添加到索引,然后使用git rebase --continue继续前进。 (当然,您可以通过从原始合并提交中检出版本来再次解决它们。)

如果你碰巧在你的repo( rerere.enabled设置为true)时启用了rerere ,那么这将会更容易 – git将能够重新使用re- re-re解决scheme,当你最初发生冲突的时候,所有你必须做是检查它,以确保它正常工作,将文件添加到索引,并继续。 (你甚至可以更进一步,打开rerere.autoupdate ,它会为你添加它们,所以合并甚至不会失败)。 不过,我猜测,你没有启用rerere,所以你将不得不自己解决冲突。

*或者,你可以尝试git-contrib的rerere-train.sh脚本,它试图“从现有的合并提交中提取数据库” – 基本上,它检查所有的合并提交,试图合并它们,如果合并失败,则抓取结果并将其显示为git-rerere 这可能是耗时的,我从来没有真正使用它,但它可能是非常有帮助的。

如果你不介意创build一个新的分支,这是我如何处理这个问题:

作为主人:

 # create a new branch git checkout -b new_clean_branch # apply all changes git merge original_messy_branch # forget the commits but have the changes staged for commit git reset --soft master git commit -m "Squashed changes from original_messy_branch" 

我正在寻找一个类似的要求,即放弃我的开发分支interjiate承诺,我发现这个程序为我工作。
在我的工作分支

 git reset –hard mybranch-start-commit git checkout mybranch-end-commit . // files only of the latest commit git add -a git commit -m”New Message intermediate commits discarded” 

中提琴我们已经连接了最新的提交到分支的开始提交! 没有合并冲突的问题! 在我的学习实践中,我已经在这个阶段得出了这个结论,有没有更好的方法来达到这个目的。

请注意,在交互式底图中使用-X和策略选项时将被忽略。

参见提交db2b3b820e2b28da268cc88adff076b396392dfe (2013年7月,git 1.8.4+),

不要忽略交互式rebase中的合并选项

合并策略及其选项可以在git rebase指定,但是在-- interactive ,它们完全被忽略。

签名:Arnaud Fontaine

这意味着-X和策略现在可以在交互式底图和简单的底图上工作,而且现在您的初始脚本可以更好地工作。

我遇到了一个更简单但相似的问题,我在那里解决了一个本地分支上的合并冲突,2)继续工作,增加了更多的小提交,3)想要重组,并发生合并冲突。

对我来说, git rebase -p -i master工作。 它保留了最初的冲突解决scheme,让我把其他人压在了最前面。

希望帮助别人!