`git pull` が有害となるケースはどのような場合でしょうか? 質問する

`git pull` が有害となるケースはどのような場合でしょうか? 質問する

私の同僚の中には、それが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 fetchgit 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>.rebasebranch.autosetuprebase、およびpull.rebaseオプションを参照)。

残念ながら、プッシュされていないマージコミットを保持したい場合(プッシュされた機能ブランチを にマージするコミットなどmaster)、rebase-pull(をgit pullbranch.<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 pullgit 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 pullGit 2.0 以降では、デフォルトで fast-forward マージのみを実行するように設定できます。

git config --global pull.ff only

これにより、git pullは のように動作しますgit pull --ff-onlyが、それでもすべてのアップストリームコミットを取得したり、古いブランチをクリーンアップしたりするわけではないorigin/*ので、私は を好みますgit up

おすすめ記事