当我所做的只是压缩提交时,为什么 git-rebase 会给我合并冲突?
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/3133449/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
Why does git-rebase give me merge conflicts when all I'm doing is squashing commits?
提问by Ben Hocking
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.
我们有一个包含 400 多个提交的 Git 存储库,其中前几十个是大量反复试验。我们希望通过将许多提交压缩为单个提交来清理这些提交。自然, git-rebase 似乎是要走的路。我的问题是它以合并冲突结束,而这些冲突并不容易解决。我不明白为什么应该有任何冲突,因为我只是在压缩提交(而不是删除或重新排列)。很可能,这表明我并不完全理解 git-rebase 如何进行挤压。
Here's a modified version of the scripts I'm using:
这是我正在使用的脚本的修改版本:
repo_squash.sh (this is the script that is actually run):
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 (this script is used only by repo_squash.sh):
repo_squash_helper.sh(此脚本仅供 repo_squash.sh 使用):
if grep -q "pick "
then
# cp ../repo_squash_history.txt
# emacs -nw
sed -f ../repo_squash_list.txt < > .tmp
mv .tmp
else
if grep -q "initial import"
then
cp ../repo_squash_new_message1.txt
elif grep -q "fixing bad import"
then
cp ../repo_squash_new_message2.txt
else
emacs -nw
fi
fi
repo_squash_list.txt: (this file is used only by repo_squash_helper.sh)
repo_squash_list.txt:(此文件仅供repo_squash_helper.sh使用)
# Initial import
s/pick \(251a190\)/squash /g
# Leaving "Needed subdir" for now
# Fixing bad import
s/pick \(46c41d1\)/squash /g
s/pick \(5d7agf2\)/squash /g
s/pick \(3da63ed\)/squash /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 wouldbe 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.
我将把“新消息”的内容留给你想象。最初,我在没有“--strategy theirs”选项的情况下执行此操作(即,使用默认策略,如果我正确理解文档,则该策略是递归的,但我不确定使用的是哪种递归策略),并且它也没有工作。另外,我应该指出,使用 repo_squash_helper.sh 中注释掉的代码,我保存了 sed 脚本工作的原始文件,并针对它运行了 sed 脚本以确保它正在做我想要它做的事情(它是)。同样,我什至不知道为什么会发生冲突,所以使用哪种策略似乎并不重要。任何建议或见解都会有所帮助,但主要是我只想让这种挤压工作。
Updated with extra information from discussion with Jefromi:
更新了与 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:
这是第一次壁球提交后的第一个选择。运行会git status
产生一个干净的工作目录。如果我然后执行 a 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
If I then run git status
, I get:
如果我然后运行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
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.)
“两者都修改”的位对我来说听起来很奇怪,因为这只是选择的结果。还值得注意的是,如果我查看“冲突”,它可以归结为一行,其中一个版本以 [tab] 字符开头,另一个版本以四个空格开头。这听起来可能是我如何设置配置文件的问题,但其中没有任何类似的内容。(我确实注意到 core.ignorecase 设置为 true,但显然 git-clone 自动做到了这一点。考虑到原始源在 Windows 机器上,我并不完全感到惊讶。)
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.
如果我手动修复 file_X.cpp,它会在不久之后因另一次冲突而失败,这次是在一个版本认为应该存在的文件 (CMakeLists.txt) 和一个版本认为不应该存在的文件之间。如果我通过说我确实想要这个文件(我这样做)来解决这个冲突,那么几次提交之后我会得到另一个冲突(在同一个文件中),现在有一些相当重要的变化。它仍然只完成了大约 25% 的冲突。
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.
我还应该指出,因为这可能非常重要,所以这个项目是在 svn 存储库中开始的。最初的历史记录很可能是从那个 svn 存储库中导入的。
Update #2:
更新#2:
On a lark (influenced by Jefromi's comments), I decided to do the change my repo_squash.sh to be:
顺便说一句(受 Jefromi 评论的影响),我决定将 repo_squash.sh 更改为:
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.
然后,我就按原样接受了原始条目。即,“rebase”不应该改变任何事情。结果与之前描述的结果相同。
Update #3:
更新 #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.
我不再遇到“无事可提交”rebase 问题,但我仍然遇到其他冲突。
Update with toy repository that recreates problem:
使用重新创建问题的玩具存储库进行更新:
test_squash.sh (this is the file you actually run):
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 (used by test_sqash.sh):
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 "
then
sed -e "s/pick \(.*\) \(Meant for it to be two lines\)/squash /g" < > .tmp
mv .tmp
# Else, assume it's the commit message file
else
# Use our pre-canned message
echo "Created two line file" >
fi
P.S.: Yes, I know some of you cringe when you see me using emacs as a fall-back editor.
PS:是的,我知道当你看到我使用 emacs 作为后备编辑器时,你们中的一些人会畏缩。
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".)
PPS:我们确实知道在变基后我们必须删除现有存储库的所有克隆。(沿着“发布后不应重新设置存储库”的内容。)
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.
PPPS:谁能告诉我如何为此添加赏金?无论我处于编辑模式还是查看模式,我都没有在此屏幕上的任何地方看到该选项。
采纳答案by Cascabel
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 therewhen 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.)
您的玩具回购测试用例中有一个合并 - 更糟糕的是,它合并了冲突。并且您在合并中重新定位。如果没有-p
(它不能完全与 一起使用-i
),合并将被忽略。这意味着当 rebase 尝试挑选下一个提交时,您在冲突解决中所做的任何事情都不存在,因此它的补丁可能不适用。(我相信这显示为合并冲突,因为git cherry-pick
可以通过在原始提交、当前提交和共同祖先之间进行三向合并来应用补丁。)
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 believethat 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
beforemerging.)
不幸的是,正如我们在评论中所指出的,-i
和-p
(保留合并)相处得不是很好。我知道编辑/改写有效,而重新排序则无效。但是,我相信它适用于南瓜。这没有记录,但它适用于我在下面描述的测试用例。如果您的情况更加复杂,您可能会在做自己想做的事情时遇到很多麻烦,尽管它仍然是可能的。(故事的寓意:rebase -i
在合并之前清理。)
So, let's suppose we have a very simple case, where we want to squash together A, B, and C:
因此,假设我们有一个非常简单的案例,我们想将 A、B 和 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.
现在,就像我说的,如果 X 中没有冲突,git rebase -i -p
就会如你所愿。
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.)
如果有冲突,事情就会变得有点棘手。它会很好地压缩,但是当它尝试重新创建合并时,冲突将再次发生。您必须再次解析它们,将它们添加到索引中,然后使用git rebase --continue
继续前进。(当然,您可以通过从原始合并提交中检出版本来再次解决它们。)
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.*
如果你碰巧rerere
在您的回购启用(rerere.enabled
设置为true),这将是比较容易的方式-蹦就能重新使用再有绳再从您最初有冲突的解决方案,而你所要做的就是检查它以确保它正常工作,将文件添加到索引中,然后继续。(您甚至可以更进一步,打开rerere.autoupdate
,它会为您添加它们,因此合并甚至不会失败)。但是,我猜您从未启用过 rerere,因此您将不得不自己解决冲突。*
* 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.
* 或者,您可以尝试rerere-train.sh
git-contrib 中的脚本,该脚本尝试“从现有合并提交中对数据库进行预处理”——基本上,它会检查所有合并提交,尝试合并它们,如果合并失败,它获取结果并将它们显示给git-rerere
. 这可能很耗时,而且我从未真正使用过它,但它可能非常有帮助。
回答by hlidka
If you don't mind creating a new branch, this is how I dealt with the problem:
如果您不介意创建一个新分支,这就是我处理问题的方式:
Being on master:
在主上:
# 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"
回答by user28186
I was looking for a similar requirement , i.e. discarding intermeiate commits of my development branch , I've found this procedure worked for me.
on my working branch
我正在寻找类似的要求,即丢弃我的开发分支的中间提交,我发现这个过程对我有用。
在我的工作分支上
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”
viola we have connected the latest commit to the start commit of the branch! No merge conflict issues! In my learning practice I have come to this conclusion at this stage , Is there a better approach for the purpose .
viola 我们已经将最新的提交连接到分支的开始提交!没有合并冲突问题!在我的学习实践中,我在这个阶段得出了这个结论,有没有更好的方法来达到这个目的。
回答by JonoB
Building on @hlidka's great answer above which minimises manual intervention, I wanted to add a version that preserves any new commits on master that aren't in the branch to squash.
建立在@ hlidka上面的伟大答案的基础上,最大限度地减少了手动干预,我想添加一个版本,以保留 master 上不在分支中的任何新提交以进行压缩。
As I believe these could be easily lost in the git reset
step in that example.
因为我相信这些很容易git reset
在那个例子中的步骤中丢失。
# create a new branch
# ...from the commit in master original_messy_branch was originally based on. eg 5654da06
git checkout -b new_clean_branch 5654da06
# apply all changes
git merge original_messy_branch
# forget the commits but have the changes staged for commit
# ...base the reset on the base commit from Master
git reset --soft 5654da06
git commit -m "Squashed changes from original_messy_branch"
# Rebase onto HEAD of master
git rebase origin/master
# Resolve any new conflicts from the new commits
回答by abaldwinhunter
I was running into a simpler but similar issue, where I had 1) resolved a merge conflict on a local branch, 2) kept working adding lots more little commits, 3) wanted to rebase and hit merge conflicts.
我遇到了一个更简单但类似的问题,我有 1) 解决了本地分支上的合并冲突,2) 继续工作,添加了更多的小提交,3) 想要变基并遇到合并冲突。
For me, git rebase -p -i master
worked. It kept the original conflict resolution commit and allowed me to squash the others on top.
对我来说,git rebase -p -i master
工作。它保留了最初的冲突解决提交,并允许我将其他人压在上面。
Hope that helps someone!
希望对某人有所帮助!
回答by VonC
Note that -X
and strategy options were ignored when used in an interactive rebase.
请注意,-X
在交互式变基中使用和策略选项时会被忽略。
See commit db2b3b820e2b28da268cc88adff076b396392dfe(July 2013, git 1.8.4+),
参见提交 db2b3b820e2b28da268cc88adff076b396392dfe(2013 年 7 月,git 1.8.4+),
Do not ignore merge options in interactive rebase
Merge strategy and its options can be specified in
git rebase
, but with-- interactive
, they were completely ignored.Signed-off-by: Arnaud Fontaine
不要忽略交互式变基中的合并选项
可以在 中指定合并策略及其选项
git rebase
,但是使用 时-- interactive
,它们会被完全忽略。签字人:Arnaud Fontaine
That means -X
and strategy now work with interactive rebase, as well as plain rebase, and your initial script could now work better.
这意味着-X
和策略现在可以与交互式 rebase 以及普通 rebase 一起使用,并且您的初始脚本现在可以更好地工作。