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

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

问题描述

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



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






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

< hr>

  rm -rf repo_squash 
git克隆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 -qpick$ 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 -qinitial import$ 1
then
cp ../repo_squash_new_message1.txt $ 1
elif grep -q修复坏的导入$ 1
然后
cp ../repo_squash_new_message2.txt $ 1
else
emacs -nw $ 1
fi
fi






repo_squash_list.txt :(使用此文件只有通过repo_squash_helper.sh)






 #初始导入
s / pick \(251a190 \)/ squash \ 1 / g
#现在离开Needed subdir
#修复坏的导入
s / pick \(46c41d1\)/ squash \\ \\ 1 / g
s / pick \(5d7agf2 \)/ squash \1 / g
s / pick \(3da63ed \)/ squash \1 / g






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

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



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



我失败时收到的消息是:

 完成了一个樱桃选择。 
#目前没有任何分支。
没有提交(工作目录清理)
无法应用66c45e2 ...需要的子目录

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

 自动樱桃采摘失败。解决冲突后,
用'git add <路径>'标记修正路径,
运行'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> .. 。放弃工作目录中的更改)

#deleted:repo / file_Z.imp

两个修改过的位听起来都很奇怪,因为这只是一个选择的结果。另外值得一提的是,如果我看一下冲突,可以归结为一行,其中一个版本以[tab]字符开始,另一个版本以四个空格开始。这听起来像是我设置我的配置文件可能是一个问题,但它没有任何类型的。 (我注意到,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 theirirs -i bd6a09a484b8230d0810e6689cf08a24f26f287a

然后,我就按原样接受原始条目。也就是说,改版不应该改变一件事情。它以前面描述的相同的结果结束。

更新#3:



另外,如果我省略战略,并用以下命令替换上一条命令:

  git rebase -i bd6a09a484b8230d0810e6689cf08a24f26f287a 

我不再获得没有任何提交的变身问题,但我仍然留下其他冲突。



使用玩具资料库更新重新创建问题:

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

 #================================== ====================== 
#初始化目录
#================ ========================================
rm -rf test_squash / test_squash_clone /
mkdir -p test_squash
mkdir -p test_squash_clone
#============================ ============================

#============= ===========================================
#创建存储库与历史
#====== ==================================================
cd test_squash /
git init
echoREADME> README
git add README
git commit -m初始提交:无法轻松访问
echoLine 1> test_file.txt
git add test_file.txt
git commit -m创建单行文件
echoLine 2>>> ; test_file.txt
git add test_file.txt
git commit -m表示它是两行
git checkout -b dev
echo有意义的代码> new_file.txt
git add new_file.txt
git commit -m有意义的提交
git checkout master
echo有意义的代码冲突> new_file.txt
git add new_file.txt
git commit -m冲突有意义的提交
#这将冲突
git合并开发
#修复冲突
回声合并有意义的代码> new_file.txt
git add new_file.txt
git commit -m与主合并的开发者
cd ..

#=============== =========================================
#保存在压扁
之前存储库的克隆===================================== ===================
git clone test_squash test_squash_clone
#================== ======================================

=== ================================================== ===
#做壁球
#================================== ======================
cd test_squash
GIT_EDITOR = .. / test_squash_helper.sh git rebase -i HEAD @ {7}
#============================================== ==========

#=============================== =========================
#显示结果
#============ ============================================
git log
git gc
git reflog
#================================= =======================

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

 #如果文件中有短语pick假设它是日志文件
if grep -qpick$ 1
then
sed -es / pick \(。* \)\(表示它是两行\)/ squash \ 1 \ 2 / g< $ 1> $ 1.tmp
mv $ 1.tmp $ 1
#否则,假设它是提交消息文件
else
#使用我们的预先消息
echo创建两行文件> $ 1

$ / code>

PS:是的,当我看到我使用emacs作为后备编辑器。



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

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 使用 re 从最初发生冲突的时候开始解决问题,你所要做的就是检查它,确保它正常工作,将文件添加到索引中,然后继续。 (你甚至可以更进一步,打开 rerere.autoupdate ,它会为你添加它们,所以合并甚至不会失败)。然而,我猜测你没有启用rerere,所以你将不得不自己解决冲突。*




*或者,您可以尝试 rerere-train.sh 脚本,它试图将[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天全站免登陆