186

我们有一个包含 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 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

这是第一次壁球提交后的第一个选择。运行会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

如果我然后运行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)和一个版本认为不应该存在的文件(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:我们确实知道在 rebase 之后我们将不得不删除现有存储库的所有克隆。(按照“发布后您不应重新设置存储库”的思路。)

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

4

7 回答 7

118

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

主要:

# 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"
于 2017-06-27T20:29:15.370 回答
82

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

您的玩具回购测试用例中有一个合并 - 更糟糕的是,它有一个冲突合并。而且您正在跨合并重新设置基础。没有-p(不完全适用-i),合并将被忽略。这意味着当 rebase 尝试挑选下一个提交时,您在解决冲突中所做的任何事情都不存在,因此它的补丁可能不适用。(我相信这显示为合并冲突,因为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在您的 repo 中启用(设置为 true),这将更容易 - gitrerere.enabled能够重新使用您最初遇到冲突时记录的re解决方案,您所要做的就是检查它为确保它正常工作,将文件添加到索引中,然后继续。(您甚至可以更进一步,打开,它会为您添加它们,因此合并甚至不会失败)。但是,我猜您从未启用 rerere,因此您将不得不自己解决冲突。 *rerere.autoupdate

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

于 2010-06-29T18:09:22.050 回答
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”

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

于 2015-09-06T06:40:34.937 回答
3

如果您想从一长串提交中准确地创建一个提交,其中一些是合并提交,最简单的方法是将您的分支重置到第一次提交之前的点,同时保留所有更改,然后重新提交它们:

git reset $(git merge-base origin/master @)
git add .
git commit

替换origin/master为您从中分支的分支的名称。

add .是必要的,因为新添加的文件在重置后显示为未跟踪。

于 2021-02-16T23:01:46.510 回答
2

在@ 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
于 2020-02-27T10:04:24.480 回答
0

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

请参阅提交 db2b3b820e2b28da268cc88adff076b396392dfe(2013 年 7 月,git 1.8.4+),

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

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

签字人:Arnaud Fontaine

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

于 2013-07-12T05:56:50.793 回答
0

我遇到了一个更简单但类似的问题,我有 1) 解决了本地分支上的合并冲突,2) 继续工作添加更多的小提交,3) 想要变基并遇到合并冲突。

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

希望对某人有所帮助!

于 2015-11-13T17:03:48.133 回答