186

私たちの 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」オプションなしでこれを行いました (つまり、デフォルトの戦略を使用します。ドキュメントを正しく理解している場合は再帰的ですが、どの再帰戦略が使用されているかわかりません)。動作しません。また、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

これは単なる選択の結果であるため、「両方が変更されました」というビットは私には奇妙に聞こえます。また、「競合」を見ると、[タブ] 文字で始まるバージョンと 4 つのスペースで始まるバージョンの 1 つの行に要約されることも注目に値します。これは、構成ファイルの設定方法に問題があるように聞こえましたが、そのようなものは何もありません。(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: リベース後に既存のリポジトリのすべてのクローンを吹き飛ばさなければならないことはわかっています。(「公開後にリポジトリをリベースしてはならない」という行に沿って。)

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

4

7 に答える 7

82

よし、答えを出せる自信はある。多分それを編集しなければならないでしょうが、私はあなたの問題が何であるかを知っていると信じています.

あなたのおもちゃレポのテストケースにはマージがあります - さらに悪いことに、競合を伴うマージがあります。そして、マージ全体でリベースしています。なし-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.enabledtrue に設定)、これはずっと簡単になります。gitは、最初に競合があったときに記録されたリソース ソリューションを再利用できるようになり、それを検査するだけ済みます。正しく機能することを確認するには、ファイルをインデックスに追加して続行します。(さらに一歩進んで をオンにすると、それらが追加されるので、マージは失敗しません)。ただし、rerere を有効にしたことがないので、自分で競合を解決する必要があると思います。rerere.autoupdate

rerere-train.sh* または、 git-contrib のスクリプトを試すこともできます。これは、「既存のマージ コミットからデータベースを再起動」しようとします。基本的に、すべてのマージ コミットをチェックアウトし、それらをマージしようとし、マージが失敗した場合は、結果を取得して に表示します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 に答える
0

-Xインタラクティブなリベースで使用された場合、戦略オプションは無視されることに注意してください。

commit db2b3b820e2b28da268cc88adff076b396392dfe (2013 年 7 月、git 1.8.4+)を参照してください。

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

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

署名者: Arnaud Fontaine

その手段-Xと戦略は、インタラクティブなリベースとプレーンなリベースで機能するようになり、最初のスクリプトがより適切に機能するようになりました。

于 2013-07-12T05:56:50.793 に答える