[解決済み] コミットをつぶしているだけなのに、なぜ git-rebase はマージの衝突を起こすのですか?
質問
私たちのGitリポジトリには400以上のコミットがあり、そのうち最初の数十回は多くの試行錯誤がありました。これらのコミットを整理して、多くのコミットをひとつのコミットにまとめたいと考えています。当然ながら、git-rebaseが最適な方法だと思われます。私の問題は、マージコンフリクトが発生してしまうことで、このコンフリクトを解決するのは簡単ではありません。私はただコミットを潰しているだけなので(削除や再配置をしているわけではありません)、なぜコンフリクトが発生するのかが理解できません。おそらく、これは私がgit-rebaseがどのようにsquashを行うかを完全に理解していないことを示しています。
以下は、私が使っているスクリプトの修正版です。
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
new message"の中身はご想像にお任せします。当初、"--strategy theirs"オプションなしで(つまり、デフォルトの戦略を使い、ドキュメントを正しく理解すれば再帰的ですが、どの再帰的戦略が使われるかはわかりません)やってみたところ、これもうまくいきませんでした。また、repo_squash_helper.sh のコメントアウトされたコードを使用して、sed スクリプトが動作する元のファイルを保存し、それに対して sed スクリプトを実行して、それが私の望むことを行っているかどうかを確認しました(それは行われました)。繰り返しになりますが、なぜそこに は だから、どちらの戦略を使うかはそれほど重要ではないように思います。何かアドバイスや洞察があれば助かりますが、ほとんど私はこのスクワッシュを動作させたいだけです。
Jefromi氏との議論から得た追加情報を更新しました。
私たちの巨大な "real" リポジトリで作業する前に、私はテストリポジトリで同様のスクリプトを使用しました。それは非常にシンプルなリポジトリで、テストはきれいに動作しました。
失敗した時に出るメッセージは
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
これはピックした結果なので、"both modified"の部分が変に聞こえますね。また、quot;conflict" を見ると、1つの行が[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
そして、元のエントリーをそのまま受け入れました。つまり、"rebase" は何も変えていないはずなのです。結局、以前記述したのと同じ結果になりました。
更新その3。
あるいは、strategyを省略して、最後のコマンドを置き換えると。
git rebase -i bd6a09a484b8230d0810e6689cf08a24f26f287a
私はもう "nothing to commit" リベースの問題は発生しませんが、他のコンフリクトはまだ残っています。
問題を再現したトイリポジトリで更新します。
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.: リベースの後、既存のリポジトリのクローンをすべて破棄しなければならないことは承知しています。("thou shalt not rebase a repository after it's published" にならって。)
P.P.P.S: 誰かこれに懸賞金をつける方法を教えてください。編集モードでも表示モードでも、この画面のどこにもオプションが表示されないのです。
解決方法
よし、答えを出す自信はある。もしかしたら編集しなければならないかもしれませんが、あなたの問題が何であるかは分かっていると思います。
あなたのトイリポのテストケースにはマージがあり、さらに悪いことに、コンフリクトを伴うマージがあります。そして、そのマージをまたいでリベースしています。それがなければ
-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.enabled
を true に設定)、これは非常に簡単です - git が
再
を使用します。
再
コード化
レ
の解決策は、もともとコンフリクトがあったときのもので、あとはそれが正しく動作しているかどうかを検査し、ファイルをインデックスに追加して、続行するだけでいいのです。(さらにもう一歩進んで
rerere.autoupdate
そうすれば、マージに失敗することもないでしょう。) しかし、あなたはrerereを有効にしていないようなので、競合の解決は自分でやらなければならないでしょう。
<サブ
* または
rerere-train.sh
スクリプトは git-contrib にあり、既存のマージコミットから "Prime [中略] rerere database" を試みます。基本的には、すべてのマージコミットをチェックしてマージを試み、マージに失敗したら結果を取得して
git-rerere
. これは時間がかかるかもしれないし、実際に使ったこともないのですが、とても便利かもしれません。
関連
-
[解決済み】Git, fatal: リモートエンドが予期せずハングアップしました。
-
git commits with an error: 更新が拒否されました。現在のブランチの先端が、このブランチより後ろにあるためです。
-
[解決済み] ssh-keygen' は内部コマンドまたは外部コマンドとして認識されません。
-
[解決済み] Git で直近のローカルコミットを取り消すには?
-
[解決済み] Git リポジトリでのマージの衝突を解決するには?
-
[解決済み] git rebase の取り消し
-
[解決済み] 複数のコミットを1つのスクワッシュされたコミットとして別のブランチにマージするにはどうすればよいですか?
-
[解決済み] Git でファイルがいつ削除されたかを調べる
-
[解決済み] 既にリベースを開始している場合、2つのコミットを1つにマージするにはどうすればよいですか?
-
[解決済み】Gitのワークフローとrebaseとmergeの質問
最新
-
nginxです。[emerg] 0.0.0.0:80 への bind() に失敗しました (98: アドレスは既に使用中です)
-
htmlページでギリシャ文字を使うには
-
ピュアhtml+cssでの要素読み込み効果
-
純粋なhtml + cssで五輪を実現するサンプルコード
-
ナビゲーションバー・ドロップダウンメニューのHTML+CSSサンプルコード
-
タイピング効果を実現するピュアhtml+css
-
htmlの選択ボックスのプレースホルダー作成に関する質問
-
html css3 伸縮しない 画像表示効果
-
トップナビゲーションバーメニュー作成用HTML+CSS
-
html+css 実装 サイバーパンク風ボタン
おすすめ
-
[解決済み】git revert <hash> not allowed due to a merge but no -m option was given.
-
[解決済み] git pull リモートブランチがリモートの参照先を見つけることができない
-
[解決済み】ERROR: Error cloning remote repo 'origin'.
-
[解決済み】git push >> fatal: 設定されたプッシュ先がありません。
-
[解決済み】マージが終了していません(MERGE_HEADは存在します)。
-
[解決済み] git stashを元に戻す
-
[解決済み] VSTS Git Fetch Failed with exit code: 128
-
[解決済み] git でディレクトリ階層が異なる 2 つのブランチをマージするには?
-
[解決済み] Git エラー : 'upstream' は git リポジトリでないようです。
-
[解決済み] すべてのgitコミットを1つにまとめるには?