私の同僚の中には、それがgit pull
有害だと主張し、誰かがそれを使うたびに腹を立てる人がいます。
このgit pull
コマンドは、ローカル リポジトリを更新するための標準的な方法のようです。 を使用するとgit pull
問題が発生しますか? どのような問題が発生しますか? Git リポジトリを更新するより良い方法はありますか?
ベストアンサー1
まとめ
デフォルトでは、git pull
マージコミットが作成され、コード履歴にノイズと複雑さが追加されます。さらに、pull
受信した変更によって自分の変更がどのように影響を受けるかを考えずに済むようになります。
このgit pull
コマンドは、高速マージのみを実行する限り安全です。git pull
高速マージのみを実行するように設定されていて、高速マージが不可能な場合、Git はエラーで終了します。これにより、受信コミットを調査し、それがローカルコミットにどのような影響を与えるかを検討し、最善のアクション (マージ、リベース、リセットなど) を決定する機会が得られます。
Git 2.0 以降では、以下を実行できます。
git config --global pull.ff only
デフォルトの動作を早送りのみに変更します。Git バージョン 1.6.6 から 1.9.x の場合は、次のように入力する習慣を身に付ける必要があります。
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
高速フォワード マージのみを実行するように設定する必要があります。
リベースアウトコミットの再導入
誰かがブランチをリベースして強制的にプッシュしたとします。これは通常起こるべきではありませんが、必要な場合もあります (たとえば、誤ってコミットされプッシュされた 50GiB のログ ファイルを削除する場合など)。 によって実行されるマージでは、git pull
アップストリーム ブランチの新しいバージョンが、ローカル リポジトリにまだ存在する古いバージョンにマージされます。その結果をプッシュすると、フォークとトーチがあなたに向けられ始めます。
本当の問題は強制更新であると主張する人もいるかもしれません。確かに、強制プッシュは可能な限り避けることが一般的に推奨されますが、避けられないこともあります。強制更新は時々発生するため、開発者は強制更新に対処する準備をする必要があります。つまり、通常の . を介して古いコミットを盲目的にマージしないということですgit pull
。
作業ディレクトリの予期せぬ変更
完了するまで、作業ディレクトリまたはインデックスがどのようになるか予測する方法はありませんgit pull
。他の作業を行う前に解決しなければならないマージ競合が発生する可能性があり、誰かが誤ってプッシュしたために作業ディレクトリに 50 GiB のログ ファイルが導入される可能性があり、作業中のディレクトリの名前が変更される可能性もあります。
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 には、 でマージの代わりにリベースを実行するように指示することで、この 2 つの手順を 1 つの手順に減らす構成オプションがいくつgit pull
かあります ( branch.<branch>.rebase
、branch.autosetuprebase
、およびpull.rebase
オプションを参照)。
残念ながら、プッシュされていないマージコミットを保持したい場合(プッシュされた機能ブランチを にマージするコミットなどmaster
)、rebase-pull(をgit pull
にbranch.<branch>.rebase
設定true
)も、merge-pull(デフォルトのgit pull
動作)の後に rebase を実行しても機能しません。これは、 がオプションgit rebase
なしでマージを排除する(DAG を線形化する)ためです--preserve-merges
。rebase-pull 操作はマージを保持するように構成することはできず、merge-pull の後に を実行しても、git rebase -p @{u}
merge-pull によって発生したマージは排除されません。更新: Git v1.8.5 でgit pull --rebase=preserve
と が追加されましたgit 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
。
- ローカルブランチがアップストリームブランチで構成されていない場合、エラーメッセージは少しわかりにくくなります。
- これは、Git の将来のバージョンで変更される可能性のある、文書化されていない機能 (
-p
に渡される引数) に依存しています。fetch
Git 2.0以降を実行している場合
git pull
Git 2.0 以降では、デフォルトで fast-forward マージのみを実行するように設定できます。
git config --global pull.ff only
これにより、git pull
は のように動作しますgit pull --ff-only
が、それでもすべてのアップストリームコミットを取得したり、古いブランチをクリーンアップしたりするわけではないorigin/*
ので、私は を好みますgit up
。