草庐IT

git - 当我所做的只是压缩提交时,为什么 git-rebase 会给我 merge 冲突?

coder 2023-06-23 原文

我们有一个包含 400 多个提交的 Git 存储库,其中前几十个是大量反复试验。我们希望通过将许多提交压缩为单个提交来清理这些提交。自然, git-rebase 似乎是要走的路。我的问题是它以 merge 冲突结束,而这些冲突并不容易解决。我不明白为什么应该有任何冲突,因为我只是在压缩提交(而不是删除或重新排列)。很可能,这表明我并不完全理解 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”选项的情况下执行此操作(即,使用默认策略,如果我正确理解文档,则该策略是递归的,但我不确定使用的是哪种递归策略),并且它也没有工作。另外,我应该指出,使用 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

这是第一次 Squash 提交后的第一个选择。运行 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] 字符开头,另一个版本以四个空格开头。这听起来可能是我如何设置配置文件的问题,但其中没有任何类似的内容。 (我确实注意到 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

然后,我就按原样接受了原始条目。即,“rebase”不应该改变任何事情。结果与之前描述的结果相同。

更新 #3:

或者,如果我省略该策略并将最后一个命令替换为:
git rebase -i bd6a09a484b8230d0810e6689cf08a24f26f287a

我不再遇到“无事可提交”rebase 问题,但我仍然遇到其他冲突。

使用重新创建问题的玩具存储库进行更新:

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

P.S.:是的,我知道你们中的一些人看到我使用 emacs 作为后备编辑器时会感到畏缩。

P.P.S.:我们知道我们必须在 rebase 之后销毁现有存储库的所有克隆。 (沿着“发布后不应重新设置存储库”的内容。)

P.P.P.S:谁能告诉我如何为此添加赏金?无论我处于编辑模式还是查看模式,我都没有在此屏幕上的任何地方看到该选项。

最佳答案

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

您的玩具 repo 测试用例中有一个 merge - 更糟糕的是,它 merge 了冲突。并且您在 merge 中重新定位。无 -p (这并不完全适用于 -i ), merge 被忽略。这意味着当 rebase 尝试挑选下一个提交时,您在冲突解决中所做的任何事情都不存在,因此它的补丁可能不适用。 (我相信这显示为 merge 冲突,因为 git cherry-pick 可以通过在原始提交、当前提交和共同祖先之间进行三向 merge 来应用补丁。)

不幸的是,正如我们在评论中指出的,-i-p (保留 merge )相处得不是很好。我知道编辑/改写有效,而重新排序则无效。但是,我相信它适用于南瓜。这没有记录,但它适用于我在下面描述的测试用例。如果您的情况更加复杂,您可能会在做自己想做的事情时遇到很多麻烦,尽管它仍然是可能的。 (故事的寓意:在 merge 之前用 rebase -i 清理。)

因此,假设我们有一个非常简单的案例,我们想将 A、B 和 C 压缩在一起:

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

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

如果有冲突,事情就会变得有点棘手。它会很好地压缩,但是当它尝试重新创建 merge 时,冲突将再次发生。您必须再次解析它们,将它们添加到索引中,然后使用 git rebase --continue继续前进。 (当然,您可以通过从原始 merge 提交中 check out 版本来再次解决它们。)

如果您碰巧有 rerere 在您的 repo 中启用(rerere.enabled 设置为 true),这会更容易 - git 将能够重用最初发生冲突时记录的解决方案,您所要做的就是检查它以确保它有效对,将文件添加到索引中,然后继续。 (您甚至可以更进一步,打开 rerere.autoupdate ,它会为您添加它们,因此 merge 甚至不会失败)。但是,我猜您从未启用过 rerere,因此您将不得不自己解决冲突。*

* 或者,您可以尝试使用 rerere-train.sh 来自 git-contrib 的脚本,它试图“从现有的 merge 提交中优先 [the] rerere 数据库” - 基本上,它检查所有 merge 提交,尝试 merge 它们,如果 merge 失败,它会获取结果并显示它们至 git-rerere .这可能很耗时,而且我从未真正使用过它,但它可能非常有帮助。

关于git - 当我所做的只是压缩提交时,为什么 git-rebase 会给我 merge 冲突?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/3133449/

有关git - 当我所做的只是压缩提交时,为什么 git-rebase 会给我 merge 冲突?的更多相关文章

  1. ruby - 为什么我可以在 Ruby 中使用 Object#send 访问私有(private)/ protected 方法? - 2

    类classAprivatedeffooputs:fooendpublicdefbarputs:barendprivatedefzimputs:zimendprotecteddefdibputs:dibendendA的实例a=A.new测试a.foorescueputs:faila.barrescueputs:faila.zimrescueputs:faila.dibrescueputs:faila.gazrescueputs:fail测试输出failbarfailfailfail.发送测试[:foo,:bar,:zim,:dib,:gaz].each{|m|a.send(m)resc

  2. ruby-on-rails - Rails - 子类化模型的设计模式是什么? - 2

    我有一个模型:classItem项目有一个属性“商店”基于存储的值,我希望Item对象对特定方法具有不同的行为。Rails中是否有针对此的通用设计模式?如果方法中没有大的if-else语句,这是如何干净利落地完成的? 最佳答案 通常通过Single-TableInheritance. 关于ruby-on-rails-Rails-子类化模型的设计模式是什么?,我们在StackOverflow上找到一个类似的问题: https://stackoverflow.co

  3. ruby - 什么是填充的 Base64 编码字符串以及如何在 ruby​​ 中生成它们? - 2

    我正在使用的第三方API的文档状态:"[O]urAPIonlyacceptspaddedBase64encodedstrings."什么是“填充的Base64编码字符串”以及如何在Ruby中生成它们。下面的代码是我第一次尝试创建转换为Base64的JSON格式数据。xa=Base64.encode64(a.to_json) 最佳答案 他们说的padding其实就是Base64本身的一部分。它是末尾的“=”和“==”。Base64将3个字节的数据包编码为4个编码字符。所以如果你的输入数据有长度n和n%3=1=>"=="末尾用于填充n%

  4. ruby - 解析 RDFa、微数据等的最佳方式是什么,使用统一的模式/词汇(例如 schema.org)存储和显示信息 - 2

    我主要使用Ruby来执行此操作,但到目前为止我的攻击计划如下:使用gemsrdf、rdf-rdfa和rdf-microdata或mida来解析给定任何URI的数据。我认为最好映射到像schema.org这样的统一模式,例如使用这个yaml文件,它试图描述数据词汇表和opengraph到schema.org之间的转换:#SchemaXtoschema.orgconversion#data-vocabularyDV:name:namestreet-address:streetAddressregion:addressRegionlocality:addressLocalityphoto:i

  5. ruby - 为什么 4.1%2 使用 Ruby 返回 0.0999999999999996?但是 4.2%2==0.2 - 2

    为什么4.1%2返回0.0999999999999996?但是4.2%2==0.2。 最佳答案 参见此处:WhatEveryProgrammerShouldKnowAboutFloating-PointArithmetic实数是无限的。计算机使用的位数有限(今天是32位、64位)。因此计算机进行的浮点运算不能代表所有的实数。0.1是这些数字之一。请注意,这不是与Ruby相关的问题,而是与所有编程语言相关的问题,因为它来自计算机表示实数的方式。 关于ruby-为什么4.1%2使用Ruby返

  6. ruby - ruby 中的 TOPLEVEL_BINDING 是什么? - 2

    它不等于主线程的binding,这个toplevel作用域是什么?此作用域与主线程中的binding有何不同?>ruby-e'putsTOPLEVEL_BINDING===binding'false 最佳答案 事实是,TOPLEVEL_BINDING始终引用Binding的预定义全局实例,而Kernel#binding创建的新实例>Binding每次封装当前执行上下文。在顶层,它们都包含相同的绑定(bind),但它们不是同一个对象,您无法使用==或===测试它们的绑定(bind)相等性。putsTOPLEVEL_BINDINGput

  7. ruby - Infinity 和 NaN 的类型是什么? - 2

    我可以得到Infinity和NaNn=9.0/0#=>Infinityn.class#=>Floatm=0/0.0#=>NaNm.class#=>Float但是当我想直接访问Infinity或NaN时:Infinity#=>uninitializedconstantInfinity(NameError)NaN#=>uninitializedconstantNaN(NameError)什么是Infinity和NaN?它们是对象、关键字还是其他东西? 最佳答案 您看到打印为Infinity和NaN的只是Float类的两个特殊实例的字符串

  8. ruby-on-rails - 如果 Object::try 被发送到一个 nil 对象,为什么它会起作用? - 2

    如果您尝试在Ruby中的nil对象上调用方法,则会出现NoMethodError异常并显示消息:"undefinedmethod‘...’fornil:NilClass"然而,有一个tryRails中的方法,如果它被发送到一个nil对象,它只返回nil:require'rubygems'require'active_support/all'nil.try(:nonexisting_method)#noNoMethodErrorexceptionanymore那么try如何在内部工作以防止该异常? 最佳答案 像Ruby中的所有其他对象

  9. ruby - 为什么 SecureRandom.uuid 创建一个唯一的字符串? - 2

    关闭。这个问题需要detailsorclarity.它目前不接受答案。想改进这个问题吗?通过editingthispost添加细节并澄清问题.关闭8年前。Improvethisquestion为什么SecureRandom.uuid创建一个唯一的字符串?SecureRandom.uuid#=>"35cb4e30-54e1-49f9-b5ce-4134799eb2c0"SecureRandom.uuid方法创建的字符串从不重复?

  10. ruby-on-rails - 在默认方法参数中使用 .reverse_merge 或 .merge - 2

    两者都可以defsetup(options={})options.reverse_merge:size=>25,:velocity=>10end和defsetup(options={}){:size=>25,:velocity=>10}.merge(options)end在方法的参数中分配默认值。问题是:哪个更好?您更愿意使用哪一个?在性能、代码可读性或其他方面有什么不同吗?编辑:我无意中添加了bang(!)...并不是要询问nobang方法与bang方法之间的区别 最佳答案 我倾向于使用reverse_merge方法:option

随机推荐