コミットを押しつぶしているだけなのに、git-rebaseでマージの競合が発生するのはなぜですか?


146

Gitリポジトリには400以上のコミットがあり、最初の数十は試行錯誤を繰り返しました。これらのコミットをクリーンアップして、多くを1つのコミットにまとめます。当然、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」オプションなしでこれを実行しました(つまり、ドキュメントを正しく理解している場合は再帰的ですが、どの再帰的戦略が使用されているのかわからないデフォルトの戦略を使用しています)。 t動作します。また、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、さらに数回のコミットを行った後に、非常によく似たメッセージが表示されます。その後、再度実行すると、数十回のコミット後に非常によく似たメッセージが表示されます。もう一度やり直すと、今度は約100回のコミットが行われ、次のメッセージが表示されます。

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

これは単なるピックの結果だったので、「両方変更された」ビットは奇妙に聞こえます。また、「競合」を見ると、1つのバージョンが[タブ]文字で始まり、もう1つのバージョンが4つのスペースがある1行に要約されることにも注意してください。これは、構成ファイルの設定方法に問題があるように思えますが、そのようなものはありません。(core.ignorecaseがtrueに設定されていることに注意しましたが、明らかにgit-cloneがそれを自動的に実行しました。元のソースがWindowsマシン上にあることを考慮しても、完全に驚くことはありません。)

file_X.cppを手動で修正すると、すぐに失敗し、別の競合が発生します。今回は、1つのバージョンが存在すると考えられるファイル(CMakeLists.txt)と、1つのバージョンが存在すると考えないファイルとの間で競合します。このファイルが必要だと言ってこの競合を修正すると(私はそうします)、数回コミットすると、(同じファイルで)別の競合が発生し、かなり重要な変更が加えられます。それはまだ紛争を乗り越える道のほんの約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

「最終的に試行されたアクション」の意味がわかりませんが、これ、ピックとスカッシュの混合のリストであり、最後の400個ほどがすべてピックです。そのリストにはマージはありませんが、リベース自体は独自のマージを実行しています。私が読んだことによると、 "rebase -p"はインタラクティブモードではお勧めできません(もちろん、この場合、インタラクティブモードのすべてではありません)。kernel.org/pub/software/scm/git/docs/git-rebase.htmlから:「これは内部的に--interactive機構を使用しますが、それを--interactiveオプションと明示的に組み合わせることは一般に良い考えではありません」
ベン2010年

「最終的に試行されたアクション」とは、返されたピック/スカッシュのリストを意味します。rebase --interactiveこれらは、gitが試行するアクションのリストのようなものです。これを、競合を引き起こしている単一のスカッシュに減らし、ヘルパースクリプトの余分な複雑さをすべて回避できることを望んでいました。他の不足している情報は、競合が発生したときです-gitがスカッシュを形成するためにパッチを適用するとき、またはスカッシュを通過して次のパッチを適用しようとするとき?(そして、あなたのGIT_EDITOR kludgeで何も悪いことが起こらないと確信していますか?単純なテストケースに対する別の投票です。)
Cascabel

役立つコメントをありがとう。回答を反映するように質問を更新しました。
Ben Hocking

2
おもちゃリポジトリのケースは、スクリプトよりもはるかに理解しやすく、スクリプトがそれとは何の関係もないことを示しています。競合解決でマージを含むブランチをリベースしようとしているという事実(わずかに邪悪なマージ) )。
Cascabel

回答:


69

大丈夫、私は答えを捨てるのに十分自信があります。多分それを編集する必要がありますが、私はあなたの問題が何であるかを知っていると思います。

おもちゃのレポテストケースにはマージがあります。さらに悪いことに、競合とのマージがあります。そして、マージ全体でリベースしています。なければ-p(これは完全に動作しません-i)、マージは無視されます。これは、リベースが次のコミットをチェリーピックしようとするときに、競合解決で行ったことがそこないため、そのパッチが適用されない可能性があることを意味します。(git cherry-pick元のコミット、現在のコミット、共通の祖先の間で3者間マージを行うことでパッチを適用できるため、これはマージの競合として示されていると思います。)

残念ながら、コメントで述べたように、-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.enabledGitができるようになります- trueに設定)、これは簡単な方法になります、再使用、再コード付きのREあなたはもともと競合を持っていたときからのソリューションを、そしてあなたが持っているすべては、ISはそれを点検行うには正しく機能することを確認し、ファイルをインデックスに追加して続行します。(さらに1歩進んでをオンにするとrerere.autoupdate、それらが追加され、マージが失敗することもありません)。ただし、rerereを有効にしたことがないので、競合の解決を自分で行う必要があると思います。*

*または、rerere-train.sh「既存のマージコミットからデータベースを再起動する」ように試みるgit-contribからスクリプトを試すこともできます-基本的に、すべてのマージコミットをチェックアウトしてマージしようとし、マージが失敗した場合、結果を取得して表示しますgit-rerere。これは時間がかかる可能性があり、実際に使用したことはありませんが、非常に役立つ場合があります。


PS私のコメントを振り返ってみると、もっと早くこれを捕らえるべきだったことがわかります。マージを含むブランチをリベースするかどうか尋ねたところ、インタラクティブリベースリストにマージがないと言っていましたが、これは同じではありません- -pオプションを指定しなかったため、そこにはマージがありませんでした。
Cascabel 2010年

私は間違いなくこれをやってみます。これを投稿してから気づいたこともあるのですが、単純にgit commit -a -m"Some message"and とタイプするだけでgit rebase --continue続行されます。これは-pオプションなしでも機能しますが、オプションを使用するとさらにうまく機能し-pます(並べ替えを行っていないため、問題ないようです-p)。とにかく、投稿しておきます。
Ben Hocking

テスト例を設定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

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 2017

2
これは素晴らしい提案でした!複数のコミットをすばやく統合するために非常にうまく機能しました。
philidem

3
これははるかに安全なソリューションです。また、マージに--squashを追加しました。存在:git merge --squash original_messy_branch
jezpez 2018

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に存在するファイルのみをチェックアウトします。
2015年

1

手作業による介入を最小限に抑える上記の@ hlidkaの素晴らしい答えに基づいて、スカッシュするブランチにないマスターでの新しいコミットを保持するバージョンを追加したいと思いました。

私はこれらの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、戦略はインタラクティブなリベースとプレーンリベースで機能するようになり、初期スクリプトはより適切に機能するようになります。


0

私は、1)ローカルブランチでのマージの競合を解決し、2)さらに多くの小さなコミットを追加して作業を続けた、3)マージの競合をリベースしてヒットしたいと思った、より単純で類似した問題に遭遇していました。

私にとって、git rebase -p -i master働いた。これにより、元の競合解決のコミットが維持され、他のメンバーを押しつぶすことができました。

それが誰かを助けることを願っています!


-pを使用してリベースを試みたときに手動で解決する必要があるいくつかの競合がまだありました。明らかに競合が少なく、以前に競合していたすべてのコードファイルではなく、プロジェクトファイルに限定されていました。どうやら「マージの競合解決またはコミットをマージするための手動の修正は保持されません。」- stackoverflow.com/a/35714301/727345
JonoB
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.