1. ホーム
  2. ギット

[解決済み】`git pull`が有害なのはどのような場合ですか?

2022-05-30 23:51:24

質問

私の同僚に、次のように主張する人がいます。 git pull は有害だと主張し、誰かがそれを使うたびに腹を立てている同僚がいます。

git pull コマンドは、ローカルリポジトリを更新する標準的な方法のようです。 このコマンドで git pull を使用すると問題が発生しますか?どのような問題が発生するのでしょうか?git リポジトリを更新するためのより良い方法はありますか?

どのように解決するのですか?

概要

デフォルトでは git pull はマージコミットを作成し、コード履歴にノイズと複雑さを加えます。 さらに pull を使うと、自分の変更が入ってくる変更にどのように影響されるかを考えなくなりがちです。

git pull コマンドは、早送りのマージを行うだけであれば安全です。 もし git pull が fast-forward マージのみを行うように設定されている場合、そして fast-forward マージが不可能な場合、Git はエラーで終了します。 これは、入ってきたコミットを研究し、それがローカルのコミットにどのような影響を与えるかを考え、最善の行動(マージ、リベース、リセットなど)を決定する機会を与えてくれます。

Git 2.0 以降では、実行することができます。

git config --global pull.ff only

を追加すると、デフォルトの挙動が早送りだけになります。 1.6.6 から 1.9.x までの Git のバージョンでは、タイプする習慣を身につける必要があります。

git pull --ff-only

しかし、すべてのバージョンのGitにおいて、私は git up のようなエイリアスを設定することをおすすめします。

git config --global alias.up '!git remote update -p; git merge --ff-only @{u}'

を使用し git up の代わりに git pull . 私はこのエイリアスの方が git pull --ff-only なぜなら

  • すべての(古いバージョンでない)Gitで動作します。
  • すべての上流ブランチを取得します (現在作業しているブランチだけではありません)。
  • は古い origin/* ブランチを削除します。

の問題 git pull

git pull は、適切に使われるのであれば、悪いものではありません。 最近のGitのいくつかの変更によって、より簡単に git pull を適切に使うことができるようになりました。しかし残念ながら、デフォルトの挙動であるプレーンな git pull にはいくつかの問題があります。

  • 履歴に不必要な非線形性を導入してしまう
  • 上流で意図的にリベースされたコミットを誤って再導入してしまいがちです。
  • 作業ディレクトリが予測不可能な方法で変更されます。
  • を使うと、他の人の作業をレビューするために自分の作業を一時停止するのが煩わしいです。 git pull
  • リモートブランチに正しくリベースすることが難しくなります。
  • リモートで削除されたブランチをクリーンアップできない

これらの問題は、以下でより詳細に説明します。

非線形の歴史

デフォルトでは git pull コマンドを実行するのと同じです。 git fetch の後に git merge @{u} . ローカルリポジトリにプッシュされていないコミットがある場合、マージ部分である git pull はマージコミットを作成します。

マージコミットについて本質的に悪いことは何もありませんが、危険なこともあるので、敬意をもって扱われるべきです。

  • マージコミットは本質的に調べるのが難しいものです。 マージが何をしているかを理解するには、すべての親に対する違いを理解する必要があります。 従来の diff では、この多次元的な情報をうまく伝えることができません。 対照的に、一連の通常のコミットは簡単にレビューできます。
  • マージの競合の解決は厄介で、マージコミットはレビューしにくいため、間違いが長い間発見されないことがよくあります。
  • マージは通常のコミットの効果に静かに取って代わります。 コードはもはやインクリメンタルなコミットの合計ではなく、実際に何が変更されたのかについて誤解を招きます。
  • マージ コミットは、いくつかの継続的インテグレーション スキームを混乱させるかもしれません (たとえば、2 番目の親が不完全な進行中の作業を指すという想定で、最初の親のパスだけを自動ビルドするなど)。

もちろん、マージには時間と場所があります。しかし、マージがいつ使われるべきで、いつ使われるべきでないかを理解することは、リポジトリの有用性を向上させることができます。

Gitの目的は、コードベースの進化を簡単に共有・消費できるようにすることであり、歴史が展開したとおりに正確に記録することではないことに注意してください。 (もしあなたが反対なら rebase コマンドとそれがなぜ作られたのかを考えてみてください)。 で作成されたマージコミットは git pull によって作成されたマージコミットは、他の人に有用な意味を伝えません。それは、あなたが変更を終える前に、他の誰かがたまたまリポジトリにプッシュしたというだけのことだからです。 他の人にとって意味がなく、危険であるなら、なぜそのようなマージコミットがあるのでしょうか?

を設定することは可能です。 git pull をマージする代わりにリベースするように設定することもできますが、これにも問題があります(後述します)。 代わりに git pull は fast-forward マージのみを行うように構成されるべきです。

リベースアウトコミットの再導入

誰かがブランチをリベースして、それを強制的にプッシュしたとします。 これは通常起こるべきではありませんが、時には必要な場合もあります (たとえば、誤ってコミットしてプッシュしてしまった 50GiB のログファイルを削除する場合など)。 で行われたマージは git pull によって行われるマージは、上流ブランチの新バージョンをローカルリポジトリにまだ存在する古いバージョンにマージします。 この結果をプッシュすると、熊手や松明があなたのところにやってくるでしょう。

本当の問題は強制アップデートだと主張する人もいるかもしれません。 たしかに、一般的には可能な限り強制プッシュを避けることが望ましいのですが、時には避けられないこともあります。 開発者は、強制アップデートが時々起こるので、それに対処する準備をしておかなければなりません。 これは、古いコミットをやみくもにマージするのではなく、通常の git pull .

サプライズ作業ディレクトリの変更

作業ディレクトリやインデックスがどのようなものになるかを予測する方法はありません。 git pull が実行されるまで、作業ディレクトリやインデックスがどのようになるかを予測する方法はありません。 他の何かをする前に解決しなければならないマージの衝突があるかもしれませんし、誰かが誤って押してしまったために作業ディレクトリに 50GB のログファイルが導入されるかもしれませんし、作業中のディレクトリの名前を変更するかもしれません、などなどです。

git remote update -p (または git fetch --all -p ) を使うと、マージやリベースを決める前に他の人のコミットを見ることができ、行動を起こす前に計画を立てることができます。

他の人のコミットをレビューするのが難しい

あなたが何か変更をしている最中に、他の人がプッシュしたコミットをレビューしてほしいと言ったとしましょう。 git pull のマージ (またはリベース) 操作は作業ディレクトリとインデックスを変更するので、作業ディレクトリとインデックスがクリーンでなければならないことを意味します。

を使うことができます。 git stash で、次に git pull といった具合に、様々な方法でレビューすることができますが、レビューが終わった後はどうするのでしょうか? 元の場所に戻るには、で作成したマージを元に戻さなければなりません。 git pull で作成したマージを元に戻し、スタッシュを適用する必要があります。

git remote update -p (または git fetch --all -p ) は作業ディレクトリやインデックスを変更しないので、ステージ済みおよび未ステージの変更がある場合でもいつでも安全に実行できます。 自分の作業を中断して他の人のコミットをレビューすることも可能で、作業中のコミットを保存したり終了させたりする心配がありません。 git pull ではそのような柔軟性はありません。

リモートブランチへのリベース

Git のよくある使い方は git pull を実行して最新の変更を取り込み、その後に git rebase @{u} というマージコミットを排除するために git pull が導入したマージコミットを削除します。 Git には、この二つのステップを一度に済ませるための設定オプションがあります。 git pull に、マージの代わりにリベースを行うように指示します ( branch.<branch>.rebase , branch.autosetuprebase そして pull.rebase のオプションがあります)。

残念ながら、保存したい未押しのマージコミットがある場合 (たとえば、押された機能ブランチをマージするコミットを master にマージするコミット) がある場合、リベースプル ( git pullbranch.<branch>.rebase に設定します。 true ) またはマージプル (デフォルトの git pull の動作) の後にリベースを実行してもうまくいきません。 これは git rebase はマージを排除する(DAGを線形化する)からです。 --preserve-merges オプションがない場合です。 rebase-pull 操作はマージを保持するように設定することはできず、 merge-pull に続いて git rebase -p @{u} は、マージプルによるマージを解消しません。 更新してください。 Git v1.8.5で追加された git pull --rebase=preservegit config pull.rebase preserve . これらにより git pull を実行します。 git rebase --preserve-merges を、上流のコミットを取得した後に実行します。 (おかげさまで ファンキャスター に感謝します!)

削除されたブランチのクリーンアップ

git pull は、リモートリポジトリから削除されたブランチに対応するリモート追跡ブランチを刈り込みません。 たとえば、誰かがブランチ foo をリモートリポジトリから削除した場合、まだ origin/foo .

これは、ユーザーがまだアクティブであると思い込んで、誤って削除したブランチを復活させることにつながります。

より良い代替案 使用する git up の代わりに git pull

の代わりに git pull の代わりに、次のようなものを作成し、使用することをお勧めします。 git up のエイリアスを作成することをお勧めします。

git config --global alias.up '!git remote update -p; git merge --ff-only @{u}'

このエイリアスは、すべての上流ブランチから最新のコミットをすべてダウンロードし(デッドブランチを刈り込み)、ローカルブランチを上流ブランチの最新コミットに高速転送しようとします。 成功した場合は、ローカルでのコミットがなかったことになり、マージマージマージが発生する危険性はありません。 ローカル (プッシュされていない) コミットがある場合は早送りに失敗するので、行動を起こす前に上流側のコミットを確認する機会が与えられます。

これはまだ作業ディレクトリを予測不可能な方法で変更しますが、ローカルに変更がない場合に限ります。 とは異なり git pull , git up は決してマージの衝突を修正することを期待するプロンプトにあなたを落としません。

もうひとつの選択肢 git pull --ff-only --all -p

以下は、上記の代替案です。 git up のエイリアスです。

git config --global alias.up 'pull --ff-only --all -p'

このバージョンの git up は、以前の git up というエイリアスを除きます。

  • ローカルブランチが上流ブランチと一緒に設定されていない場合、エラーメッセージはもう少し不可解なものになります。
  • ドキュメント化されていない機能に依存している ( -p 引数に渡される fetch に渡される引数) は、Git の将来のバージョンで変更される可能性があります。

Git 2.0 以降をお使いの場合

Git 2.0 以降のバージョンでは、以下のように設定することができます。 git pull を設定して、デフォルトでは fast-forward マージのみを行うようにすることができます。

git config --global pull.ff only

これにより git pull のように動作します。 git pull --ff-only のように動作しますが、それでも上流のコミットをすべて取得したり、古い origin/* ブランチを削除できないので、私はまだ git up .