当我正在压缩提交时,为什么git-rebase给我合并冲突? [英] Why does git-rebase give me merge conflicts when all I'm doing is squashing commits?

查看:105
本文介绍了当我正在压缩提交时,为什么git-rebase给我合并冲突?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我们有一个包含400多个提交的Git存储库,其中前几十个是反复试验的.我们希望通过将许多压缩成一个提交来清理这些提交.自然地,git-rebase似乎是要走的路.我的问题是它最终会导致合并冲突,并且这些冲突不容易解决.我不明白为什么根本不应该有任何冲突,因为我只是挤压提交(而不是删除或重新排列).很有可能,这表明我并不完全了解git-rebase如何进行南瓜操作.

这是我使用的脚本的修改版本:


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


  rm -rf repo_squashgit clone repo repo_squashcd repo_squash/GIT_EDITOR = ../repo_squash_helper.sh git rebase --strategy他们的-i bd6a09a484b8230d0810e6689cf08a24f26f287a 


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


 如果grep -q"pick" $ 1然后#cp $ 1 ../repo_squash_history.txt#emacs -nw $ 1sed -f ../repo_squash_list.txt<$ 1>$ 1.tmpmv $ 1.tmp $ 1别的如果grep -q初始导入" $ 1然后cp ../repo_squash_new_message1.txt $ 1elif grep -q修复错误的导入" $ 1然后cp ../repo_squash_new_message2.txt $ 1别的emacs -nw $ 1科幻科幻 


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


 #初始导入s/pick \(251a190 \)/壁球\ 1/g#暂时离开"Needed subdir"#修复错误的导入s/pick \(46c41d1 \)/壁球\ 1/gs/pick \(5d7agf2 \)/壁球\ 1/gs/pick \(3da63ed \)/壁球\ 1/g 


我将新消息"的内容留给您想象.最初,我没有使用"--strategy他们的"选项(例如,使用默认策略,如果我正确地理解了文档,则使用递归策略,但是不确定使用哪种递归策略),并且也没有这样做.工作.另外,我应该指出,使用repo_squash_helper.sh中注释掉的代码,我保存了sed脚本可以使用的原始文件,并针对它运行sed脚本,以确保它正在执行我想要的操作(它是).再说一次,我什至不知道为什么会出现冲突,因此使用哪种策略似乎无关紧要.任何建议或见解都会有所帮助,但大多数情况下,我只是想使这种挤压工作正常进行.

更新了与Jefromi的讨论中的其他信息:

在处理庞大的真实"存储库之前,我在测试存储库上使用了类似的脚本.这是一个非常简单的存储库,并且测试工作正常.

失败时收到的消息是:

 完成了一次樱桃摘.#当前不在任何分支上.无需提交(工作目录干净)无法应用66c45e2 ...所需的子目录 

这是第一次壁球犯规后的第一顺位.运行 git status 会产生一个干净的工作目录.如果然后执行 git rebase --continue ,则再进行几次提交后,也会得到非常相似的消息.如果再次执行此操作,则在几次提交后会收到另一条非常相似的消息.如果我再次执行此操作,则这次它经过了大约一百次提交,并产生以下消息:

 自动挑选樱桃失败.解决冲突后,用'git add< paths>'标记更正的路径,然后运行'git rebase --continue'无法应用f1de3bc ...增量 

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

 #当前不在任何分支上.#要提交的更改:#(使用"git reset HEAD< file> ..."取消登台)##修改:repo/file_A.cpp#修改:repo/file_B.cpp##未合并的路径:#(使用"git reset HEAD< file> ..."取消登台)#(适当使用"git add/rm< file> ..."标记分辨率)##均已修改:repo/file_X.cpp##已更改但未更新:#(使用"git add/rm< file> ..."更新将提交的内容)#(使用"git checkout-< file> ..."放弃工作目录中的更改)##删除:repo/file_Z.imp 

两个修改的"位对我来说听起来很奇怪,因为这只是一个选择的结果.还值得注意的是,如果我查看冲突",它会简化为一行,其中一个版本以[tab]字符开头,另一版本以4个空格开头.听起来这可能与我如何配置配置文件有关,但其中没有任何内容.(我确实注意到将core.ignorecase设置为true,但显然git-clone会自动做到这一点.考虑到原始源在Windows计算机上,我对此并不感到完全惊讶.)

如果我手动修复file_X.cpp,它随后很快就会失败,并出现另一个冲突,这一次是一个版本认为应该存在的文件(CMakeLists.txt)和一个版本认为不应存在的文件.如果我通过说我确实想要此文件来解决此冲突(我确实要这样做),则在稍后进行一些提交后,我又遇到了另一个冲突(在同一文件中),现在发生了一些相当不平凡的更改.仍然只有20%的冲突得以解决.

我还应该指出,由于这可能非常重要,因此该项目始于svn存储库.最初的历史很可能是从svn存储库中导入的.

更新#2:

在百灵鸟上(受Jefromi的评论影响),我决定将repo_squash.sh更改为:

  rm -rf repo_squashgit clone repo repo_squashcd repo_squash/git rebase --strategy他们的-i bd6a09a484b8230d0810e6689cf08a24f26f287a 

然后,我照原样接受了原始条目.也就是说,重新设置"不应该改变任何事情.最后得到的结果与之前描述的相同.

更新#3:

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

  git rebase -i bd6a09a484b8230d0810e6689cf08a24f26f287a 

我不再遇到什么都没做"的变基问题,但是我仍然遇到其他冲突.

使用玩具存储库进行更新,从而重新产生问题:

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

 #=========================================================#初始化目录#========================================================rm -rf test_squash/test_squash_clone/mkdir -p test_squashmkdir -p test_squash_clone#========================================================#========================================================#使用历史记录创建存储库#========================================================cd test_squash/git init回声自述文件">自述文件git添加自述文件git commit -m初始提交:无法轻松访问以进行基础调整"回显第1行"> test_file.txtgit添加test_file.txtgit commit -m创建的单行文件"回声第2行">> test_file.txtgit添加test_file.txtgit commit -m意味着要成为两行"git checkout -b开发回声有意义的代码> new_file.txtgit添加new_file.txtgit commit -m有意义的提交"git checkout主回声冲突的有意义的代码> new_file.txtgit添加new_file.txtgit commit -m冲突的有意义的提交"#这会发生冲突git merge开发#解决冲突回声合并有意义的代码> new_file.txtgit添加new_file.txtgit commit -m将开发人员与主用户合并"光盘..#========================================================#在压扁之前保存存储库的副本#========================================================git clone test_squash test_squash_clone#========================================================#========================================================#做壁球#========================================================cd test_squashGIT_EDITOR = ../test_squash_helper.sh git rebase -i HEAD @ {7}#========================================================#========================================================#显示结果#========================================================git日志git gcgit reflog#======================================================== 

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

 #如果文件中包含短语"pick",则假定它是日志文件如果grep -q"pick" $ 1然后sed -e"s/pick \(.* \)\(意味着要成为两行\)/squash \ 1 \ 2/g"<$ 1>$ 1.tmpmv $ 1.tmp $ 1#否则,假设它是提交消息文件别的#使用我们的预设消息回声创建的两行文件">$ 1科幻 

P.S .:是的,当您看到我使用emacs作为后备编辑器时,我知道有些人畏缩.

P.P.S .:我们知道,在重新设置基准之后,我们必须销毁现有存储库的所有克隆.(沿您在发布存储库后不要对其重新建立基础"的思路.)

P.P.P.S:谁能告诉我如何添加赏金吗?无论是处于编辑模式还是查看模式,我都不会在该屏幕上的任何位置看到该选项.

解决方案

好的,我有足够的信心提出答案.也许需要对其进行编辑,但是我相信我知道您的问题是什么.

您的玩具回购测试用例包含一个合并-更糟糕的是,它具有冲突合并.您将在合并中重新部署.如果没有 -p (对 -i 并不完全有效),则合并将被忽略.这意味着,当您的解决方案尝试重新选择下一个提交时,您在冲突解决方案中所做的所有操作都不存在,因此其补丁可能不适用.(我相信这被显示为合并冲突,因为 git cherry-pick 可以通过在原始提交,当前提交和公共祖先之间进行三向合并来应用补丁.)

不幸的是,正如我们在评论中指出的那样, -i -p (保留合并)的相处不是很好.我知道编辑/重新编写字词是可行的,而重新排序则行不通.但是,我相信它可以和南瓜一起使用.这没有记录,但是它适用于下面描述的测试用例.如果您的情况是复杂的,尽管您仍然可以做到,但是您可能会遇到很多麻烦.(故事的寓意:在合并之前使用 rebase -i 进行清理.)

因此,假设我们有一个非常简单的情况,我们要一起挤压A,B和C:

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

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

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

如果您碰巧拥有 rerere 在您的存储库中启用( rerere.enabled 设置为true),这将更加容易-git将能够 re 使用 rerere.autoupdate ,它将为您添加它们,因此合并甚至不会失败).但是,我猜测您从未启用过rerere,因此您将不得不自己解决冲突.*

*或者,您可以尝试 git-rerere .这可能很耗时,但我从未真正使用过它,但可能会很有帮助.

We have a Git repository with over 400 commits, the first couple dozen of which were a lot of trial-and-error. We want to clean up these commits by squashing many down into a single commit. Naturally, git-rebase seems the way to go. My problem is that it ends up with merge conflicts, and these conflicts are not easy to resolve. I don't understand why there should be any conflicts at all, since I'm just squashing commits (not deleting or rearranging). Very likely, this demonstrates that I'm not completely understanding how git-rebase does its squashes.

Here's a modified version of the scripts I'm using:


repo_squash.sh (this is the script that is actually run):


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 (this script is used only by 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: (this file is used only by 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


I'll leave the "new message" contents to your imagination. Initially, I did this without the "--strategy theirs" option (i.e., using the default strategy, which if I understand the documentation correctly is recursive, but I'm not sure which recursive strategy is used), and it also didn't work. Also, I should point out that, using the commented out code in repo_squash_helper.sh, I saved off the original file that the sed script works on and ran the sed script against it to make sure it was doing what I wanted it to do (it was). Again, I don't even know why there would be a conflict, so it wouldn't seem to matter so much which strategy is used. Any advice or insight would be helpful, but mostly I just want to get this squashing working.

Updated with extra information from discussion with Jefromi:

Before working on our massive "real" repository, I used similar scripts on a test repository. It was a very simple repository and the test worked cleanly.

The message I get when it fails is:

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

This is the first pick after the first squash commit. Running git status yields a clean working directory. If I then do a git rebase --continue, I get a very similar message after a few more commits. If I then do it again, I get another very similar message after a couple dozen commits. If I do it yet again, this time it goes through about a hundred commits, and yields this message:

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

If I then run git status, I get:

# 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

The "both modified" bit sounds weird to me, since this was just the result of a pick. It's also worth noting that if I look at the "conflict", it boils down to a single line with one version beginning it with a [tab] character, and the other one with four spaces. This sounded like it might be an issue with how I've set up my config file, but there's nothing of the sort in it. (I did note that core.ignorecase is set to true, but evidently git-clone did that automatically. I'm not completely surprised by that considering that the original source was on a Windows machine.)

If I manually fix file_X.cpp, it then fails shortly afterward with another conflict, this time between a file (CMakeLists.txt) that one version thinks should exist and one version thinks shouldn't. If I fix this conflict by saying I do want this file (which I do), a few commits later I get another conflict (in this same file) where now there's some rather non-trivial changes. It's still only about 25% of the way through the conflicts.

I should also point out, since this might be very important, that this project started out in an svn repository. That initial history very likely was imported from that svn repository.

Update #2:

On a lark (influenced by Jefromi's comments), I decided to do the change my repo_squash.sh to be:

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

And then, I just accepted the original entries, as is. I.e., the "rebase" shouldn't have changed a thing. It ended up with the same results describe previously.

Update #3:

Alternatively, if I omit the strategy and replace the last command with:

git rebase -i bd6a09a484b8230d0810e6689cf08a24f26f287a

I no longer get the "nothing to commit" rebase problems, but I'm still left with the other conflicts.

Update with toy repository that recreates problem:

test_squash.sh (this is the file you actually run):

#========================================================
# 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 (used by 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

P.S.: Yes, I know some of you cringe when you see me using emacs as a fall-back editor.

P.P.S.: We do know we'll have to blow away all of our clones of the existing repository after the rebase. (Along the lines of "thou shalt not rebase a repository after it's been published".)

P.P.P.S: Can anyone tell me how to add a bounty to this? I'm not seeing the option anywhere on this screen whether I'm in edit mode or view mode.

解决方案

All right, I'm confident enough to throw out an answer. Maybe will have to edit it, but I believe I know what your problem is.

Your toy repo test case has a merge in it - worse, it has a merge with conflicts. And you're rebasing across the merge. Without -p (which doesn't totally work with -i), the merges are ignored. This means that whatever you did in your conflict resolution isn't there when the rebase tries to cherry-pick the next commit, so its patch may not apply. (I believe this is shown as a merge conflict because git cherry-pick can apply the patch by doing a three-way merge between the original commit, the current commit, and the common ancestor.)

Unfortunately, as we noted in the comments, -i and -p (preserve merges) don't get along very well. I know that editing/rewording work, and that reordering doesn't. However, I believe that it works fine with squashes. This is not documented, but it worked for the test cases I describe below. If your case is way, way more complex, you may have a lot of trouble doing what you want, though it'll still be possible. (Moral of the story: clean up with rebase -i before merging.)

So, let's suppose we have a very simple case, where we want to squash together A, B, and C:

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

Now, like I said, if there were no conflicts in X, git rebase -i -p works as you'd expect.

If there are conflicts, things get a little trickier. It'll do fine squashing, but then when it tries to recreate the merge, the conflicts will happen again. You'll have to resolve them again, add them to the index, then use git rebase --continue to move on. (Of course, you can resolve them again by checking out the version from the original merge commit.)

If you happen to have rerere enabled in your repo (rerere.enabled set to true), this will be way easier - git will be able to reuse the recorded resolution from when you originally had the conflicts, and all you have to do is inspect it to make sure it worked right, add the files to the index, and continue. (You can even go one step farther, turning on rerere.autoupdate, and it'll add them for you, so the merge won't even fail). I'm guessing, however, that you didn't ever enable rerere, so you're going to have to do the conflict resolution yourself.*

* Or, you could try the rerere-train.sh script from git-contrib, which attempts to "Prime [the] rerere database from existing merge commits" - basically, it checks out all the merge commits, tries to merge them, and if the merge fails, it grabs the results and shows them to git-rerere. This could be time-consuming, and I've never actually used it, but it might be very helpful.

这篇关于当我正在压缩提交时,为什么git-rebase给我合并冲突?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆