web-dev-qa-db-ja.com

私がやっていることはコミットをつぶしているだけなのに、なぜgit-rebaseはマージの競合を引き起こすのですか?

400を超えるコミットを含むGitリポジトリがあり、そのうちの最初の数十は多くの試行錯誤でした。これらのコミットをクリーンアップして、多数のコミットを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」オプションを使用せずにこれを行いました(つまり、ドキュメントを正しく理解すれば再帰的であるが、どの再帰的戦略が使用されるかわからないデフォルト戦略を使用しました)。仕事。また、repo_squash_helper.shのコメント化されたコードを使用して、sedスクリプトが機能する元のファイルを保存し、sedスクリプトを実行して、目的の処理を実行していることを確認します(そうだった)。繰り返しになりますが、なぜwouldが競合するのかわからないので、どの戦略が使用されるかはそれほど重要ではないようです。アドバイスや洞察は役立ちますが、ほとんどの場合、このスカッシュを機能させたいだけです。

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つのバージョンが[tab]文字で始まり、もう1つのバージョンが4つのスペースで始まる1行に要約されることも注目に値します。これは、構成ファイルの設定方法に問題があるように聞こえますが、そのようなものは何もありません。 (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

追伸:はい、emacsをフォールバックエディターとして使用しているのを見たとき、あなたの一部がうんざりしています.

P.P.S .:リベース後に既存のリポジトリのすべてのクローンを吹き飛ばす必要があることはわかっています。 (「公開後、リポジトリをリベースしないでください」の行に沿って。)

P.P.P.S:これに賞金を追加する方法を教えてもらえますか?編集モードでも表示モードでも、この画面のどこにもオプションは表示されません。

120
Ben Hocking

わかった、答えを出すのに十分な自信がある。それを編集する必要があるかもしれませんが、私はあなたの問題が何であるかを知っていると信じています。

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

残念ながら、コメントで述べたように、-i-p(マージを保持)はあまりうまくいきません。編集/言い直しは機能しますが、並べ替えは機能しないことを知っています。しかし、私は信じるそれはスカッシュでうまく動作します。これは文書化されていませんが、以下で説明するテストケースでは機能しました。ケースがかなり複雑な場合、それでも可能ですが、あなたが望むことをするのに多くの問題があるかもしれません。 (物語の教訓:rebase -ibeforeマージでクリーンアップ。)

それでは、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はre最初に競合が発生したときからrecordedreソリューションを使用し、必要なすべてのdoを調べて、正しく機能することを確認し、ファイルをインデックスに追加して続行します。 (さらに一歩進んでrerere.autoupdateをオンにすると、それらが追加されるので、マージは失敗しません)。ただし、rerereを有効にしたことはないため、自分で競合解決を行う必要があります。*

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

62
Cascabel

新しいブランチの作成を気にしない場合、これは私が問題に対処した方法です:

マスターになる:

# 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"
50
hlidka

同様の要件を探していました。つまり、開発ブランチの中間コミットを破棄しましたが、この手順がうまくいったことがわかりました。
作業ブランチ上

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最新のコミットをブランチの開始コミットに接続しました!マージの競合の問題はありません!私の学習実践では、この段階でこの結論に達しました。目的により良いアプローチがありますか。

4
user28186

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

私のために、 git rebase -p -i master働いた。元の競合解決コミットを維持し、他の競合をトップに押しつぶすことができました。

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

0
abaldwinhunter

-Xおよび戦略オプションは、対話型リベースで使用されたときに無視されたことに注意してください。

commit db2b3b820e2b28da268cc88adff076b396392dfe (2013年7月、git 1.8.4+)、

対話型リベースのマージオプションを無視しないでください

マージ戦略とそのオプションはgit rebaseで指定できますが、-- interactiveを使用すると、それらは完全に無視されました。

サインオフ:Arnaud Fontaine

つまり、-Xとストラテジーは、インタラクティブなリベースとプレーンなリベースで機能するようになり、初期スクリプトがより適切に機能するようになりました。

0
VonC