当我只压缩提交时,为什么git-rebase给我合并冲突?


146

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

更新了与​​Jefromi讨论时获得的更多信息:

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

失败时收到的消息是:

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

这是第一次壁球犯规之后的第一选择。运行将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

对我来说,“两者都修改了”的声音听起来很奇怪,因为这只是选择的结果。还值得注意的是,如果我查看“冲突”,它会简化为一行,其中一个版本以[tab]字符开头,另一版本以4个空格开头。听起来这可能与我如何设置配置文件有关,但其中没有任何内容。(我确实注意到将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:

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

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:谁能告诉我如何为此增加赏金?无论是处于编辑模式还是查看模式,我都不会在此屏幕上的任何位置看到该选项。


比尝试使用的脚本更有意义的是最终尝试的操作-看起来可以肯定是混杂的壁球和南瓜的列表,对吗?并且在基础分支中是否有任何合并提交?(尽管您仍然不使用rebase -p
Cascabel 2010年

我不知道你所说的“最后的尝试行动”,但它只是混杂挑选和壁球的名单,随着最后400个左右是所有选秀权。尽管变基本身正在执行自己的合并,但该列表中没有任何合并。根据我所读的内容,不建议在交互模式下使用“ rebase -p”(当然,在我的情况下,并不是所有的交互方式)。来自kernel.org/pub/software/scm/git/docs/git-rebase.html:“这在内部使用--interactive机制,但是将它与--interactive选项明确结合通常不是一个好主意”
Ben霍金

“最终尝试动作”是指传递回的选择/压弯列表rebase --interactive-这些都是git尝试尝试的动作列表。我希望您可以将其减少到引起冲突的单个壁球,并避免使用辅助脚本的所有额外复杂性。其他丢失的信息是何时发生冲突-git何时应用补丁形成壁球,或者当它试图越过壁球并应用下一个补丁时?(您确定您的GIT_EDITOR垃圾邮件不会发生任何不良情况吗?对简单测试用例进行另一次投票。)
Cascabel,2010年

感谢您的有用评论。我已经更新了问题以反映我的答案。
Ben Hocking 2010年

2
玩具存储库的情况比您的脚本要容易得多,并且说明这些脚本与它无关,只是事实是您试图为包含冲突解决合并(轻微合并)的分支建立基础。 )。
卡斯卡贝尔

Answers:


69

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

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

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

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

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

现在,就像我说的那样,如果X中没有冲突,则git rebase -i -p可以按预期工作。

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

如果你碰巧rerere在您的回购启用(rerere.enabled设置为true),这将是比较容易的方式- git会能够重新使用重新从您最初有冲突的解决方案,而你所要做的就是检查它为确保其正常工作,请将文件添加到索引,然后继续。(您甚至可以再走一步,打开rerere.autoupdate,它将为您添加它们,因此合并甚至不会失败)。但是,我猜测您从未启用过rerere,因此您将不得不自己解决冲突。*

*或者,您可以尝试使用rerere-train.shgit-contrib中的脚本,该脚本尝试“从现有的合并提交中抢占数据库”-基本上,它会检查所有合并提交,尝试合并它们,如果合并失败,它获取结果并将其显示给git-rerere。这可能很耗时,但我从未真正使用过它,但可能会很有帮助。


PS回首我的评论,我认为我应该早点抓住这一点。我问您是否正在对包含合并的分支进行重新设置,并且您说在交互式rebase列表中没有合并,这是不一样的-那里没有合并,因为您没有提供该-p选项。
卡斯卡贝尔

我一定会去的。自发布以来,我还注意到在某些情况下,我可以简单地键入git commit -a -m"Some message"git rebase --continue,它将继续进行。即使没有-p选项,此方法也可以使用,但使用该-p选项则效果更好(因为我没有进行任何重新排序,所以似乎-p很好)。无论如何,我会及时通知您。
Ben Hocking 2010年

设置git config --global rerere.enabled truegit config --global rerere.autoupdate true运行测试示例之前,确实可以解决主要问题。有趣的是,即使指定,它也不会保留合并--preserve-merges。但是,如果我没有设置这些设置,而是在输入git commit -a -m"Meaningful commit"git rebase --continue时指定--preserve-merges,则会保留合并。无论如何,感谢您帮助我解决了这个问题!
Ben Hocking 2010年

84

如果您不介意创建新分支,这就是我处理问题的方式:

在主要:

# 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 main        

git commit -m "Squashed changes from original_messy_branch"

3
最快,最有效的解决方案:)
IslamTaha '17

2
这是一个很好的建议!为快速合并多个提交工作非常出色。
philidem '17

3
这是一个更安全的解决方案。我还将--squash添加到了合并中。正在:git merge --squash original_messy_branch
jezpez '18

1
感谢您挽救我的生活和生命:)所有人都能得到的最佳解决方案

当您这样做时git diff new_clean_branch..original_messy_branch,它们应该相同吗?对我来说,他们是不同的。
LeonardChallis

5

我一直在寻找类似的要求,即丢弃我的开发分支的中介提交,我发现此过程对我有用。
在我的工作分支上

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”

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


仅供参考-我只是尝试这样做,发现执行mybranch-end-commit的签出并不能使我获得在中间提交中发生的删除。因此,它仅检出mybranch-end-commit中存在的文件。
ahains

1

在@ hlidka构建的最佳答案,该方法最大程度地减少了手动干预,我想添加一个版本,该版本可以保留master上不在分支中的所有新提交。

我相信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

0

请注意,-X在交互式基础中使用和策略选项时,它们会被忽略。

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

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

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

签字人:Arnaud Fontaine

这意味着-X,策略现在可以与交互式rebase以及普通rebase一起使用,并且您的初始脚本现在可以更好地工作。


0

我遇到了一个更简单但类似的问题,其中我有1)解决了本地分支上的合并冲突,2)继续努力添加了更多的小提交,3)想重新建立基础并解决合并冲突。

对我来说,git rebase -p -i master工作了。它保留了最初的冲突解决方案,并允许我将其他人压在最上面。

希望能帮助到某人!


在尝试使用-p进行重新设置时,仍然有一些冲突需要手动解决。诚然,冲突要少得多,它们只限于项目文件,而不是我以前遇到的所有代码文件。显然,“未保留合并冲突解决方案或手动修订以合并提交。” - stackoverflow.com/a/35714301/727345
JonoB
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.