web-dev-qa-db-ja.com

どのような場合に「git pull」は有害な可能性がありますか?

私はgit pullが有害であると主張し、誰かがそれを使用するたびに怒る同僚を持っています。

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

401
Richard Hansen

概要

デフォルトでは、git pullはマージコミットを作成し、コード履歴にノイズと複雑さを追加します。さらに、pullを使用すると、変更が受信した変更によってどのように影響を受ける可能性があるかについて考えるのが簡単になります。

git pullコマンドは早送りマージのみを実行する限り安全です。 git pullが早送りマージのみを行うように設定されていて、早送りマージが不可能な場合、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のマージ部分はマージコミットを作成します。

マージコミットについて本質的に悪いことは何もありませんが、それらは危険になる可能性があるため、注意して扱う必要があります。

  • マージコミットは本質的に調べるのが難しいです。マージが何をしているのかを理解するには、すべての親に対する違いを理解する必要があります。従来の差分はこの多次元情報をうまく伝えません。対照的に、一連の通常のコミットは見直しが簡単です。
  • マージコンフリクトの解決は慎重を要するものであり、マージコミットは見直すのが難しいため、ミスは長期間にわたって検出されないことがよくあります。
  • マージは通常のコミットの効果を静かに優先することができます。コードはもはや増分コミットの合計ではなく、実際に何が変更されたのかについての誤解につながります。
  • マージコミットはいくつかの継続的統合スキームを混乱させるかもしれません(例えば、2番目の親が進行中の不完全な作業を指すという仮定の下で最初の親パスのみを自動構築する)。

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

Gitの目的は、展開されたとおりに履歴を正確に記録するのではなく、コードベースの進化を共有および消費することを容易にすることです。 (同意しない場合は、rebaseコマンドと作成された理由を検討してください。)git pullによって作成されたマージコミットは、他の人に有用なセマンティクスを伝えません。あなたの変更と共に。他の人にとって意味がなく、危険になる可能性があるのに、なぜそれらのマージをコミットするのでしょうか。

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

リベースされたコミットの再紹介

誰かがブランチをリベースして強制的にプッシュしたとします。これは通常起こるべきではありませんが、時には必要です(たとえば、誤ってコミットされプッシュされた50GiBのログファイルを削除するなど)。 git pullによって行われたマージは上流のブランチの新しいバージョンをあなたのローカルリポジトリにまだ存在する古いバージョンにマージします。あなたが結果をプッシュするならば、ピッチフォークとトーチはあなたの方法を来始めます。

本当の問題は強制的な更新であると主張する人もいるかもしれません。はい、可能な限り強制プッシュを避けることをお勧めしますが、避けられないこともあります。開発者は強制的な更新に対処する準備をしなければなりません。なぜならそれらは時々起こるからです。これは、通常のgit pullを介して古いコミットを盲目的にマージしないことを意味します。

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

git pullが完了するまで、作業ディレクトリまたはインデックスがどのように見えるかを予測する方法はありません。他の操作を実行する前にマージ競合が発生する可能性があります。作業ディレクトリに50GiBのログファイルが誤ってプッシュされたために作業ディレクトリに名前が変更されたりする可能性があります。

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に指示することによってこれらの2つのステップを単一のステップに減らすためのいくつかの設定オプションを持っていることは十分に一般的です(branch.<branch>.rebasebranch.autosetuprebase、およびpull.rebaseオプションを参照)。

残念ながら、保存したくないプッシュされたマージコミット(例えば、プッシュされた機能ブランチをmasterにマージするコミット)がある場合、rebase-pull(git pullを含むbranch.<branch>.rebasetrueに設定)もマージプル(デフォルトのgit pullの振る舞い)とそれに続くリベースがうまくいきます。これは、git rebaseオプションが指定されていない場合、--preserve-mergesがマージを排除する(DAGを線形化する)ためです。 rebase-pull操作はマージを保持するように設定することはできません、そしてgit rebase -p @{u}が後に続くマージ - プルはマージ - プルによって引き起こされたマージを排除しません。 更新:Git v1.8.5にgit pull --rebase=preservegit config pull.rebase preserveが追加されました。これらは、上流のコミットを取得した後にgit pullgit rebase --preserve-mergesをさせます。 (ヘッドアップをしてくれた funkaster に感謝!)

削除された枝を片付ける

git pullは、リモートリポジトリから削除されたブランチに対応するリモートトラッキングブランチをPruneしません。たとえば、誰かがリモートリポジトリからブランチ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エイリアスと同じ動作をします。

  • あなたのローカルブランチがアップストリームブランチで設定されていない場合、エラーメッセージはもう少しわかりにくいです。
  • 文書化されていない機能(fetchに渡される-p引数)に依存しており、Gitの将来のバージョンでは変更される可能性があります。

Git 2.0以降を実行している場合

Git 2.0以降では、デフォルトで早送りマージのみを行うようにgit pullを設定できます。

git config --global pull.ff only

これはgit pullgit pull --ff-onlyのように動作させますが、それでも上流のコミットをすべて取得したり古いOrigin/*ブランチを一掃したりするわけではないので、私はまだgit upを好みます。

541
Richard Hansen

私の答えは、HackerNewsの arose という議論から引き出されました。

私は、Betteridgeの見出しの法則を使用して質問に答えるだけの誘惑に駆られます。なぜgit pullは有害と見なされるのですか?そうではありません。

  • 非線形性は本質的に悪くはありません。それらが実際の履歴を表す場合、それらは問題ありません。
  • コミットの偶発的な再導入rebasedアップストリームは、誤ってアップストリームの履歴を書き換えた結果です。履歴が複数のリポジトリに沿って複製されている場合、履歴を書き換えることはできません。
  • 作業ディレクトリの変更は期待される結果です。議論の余地のある有用性、つまりhg/monotone/darcs/other_dvcs_predating_gitの動作に直面しているが、本質的に悪いことではない。
  • マージには他の人の作業をレビューするために一時停止する必要があり、これもgit pullで予想される動作です。マージしたくない場合は、git fetchを使用する必要があります。繰り返しますが、これは以前の人気のあるdvcsと比較してgitの特異性ですが、予想される動作であり、本質的に悪いことではありません。
  • リモートブランチに対してリベースするのを難しくするのは良いことです。絶対に必要でない限り、履歴を書き換えないでください。私の人生では、この(偽の)線形歴史の追求を理解することはできません
  • 枝をきれいにしないことは良いことです。各レポは何を保持したいかを知っています。 Gitには、マスターとスレーブの関係という概念はありません。
194

Gitを正しく使っていても害はありません。ユースケースを考えると、それがあなたに悪影響を及ぼしていることがわかりますが、共有履歴を変更しないことで問題を回避できます。

26
Hunt Burdick

受け入れられた答えは主張する

Rebase-pull操作はマージを保持するように設定できません

しかし、 Git 1.8.5 の時点では、その答えを投稿して、あなたがすることができます

git pull --rebase=preserve

または

git config --global pull.rebase preserve

または

git config branch.<name>.rebase preserve

のドキュメント は言う

preserve,が 'git rebase'にも--preserve-mergesを渡すと、ローカルでコミットされたマージコミットは 'git pull'を実行してもフラット化されません。

この前の議論はより詳細な情報と図を持っています: git pull --rebase --preserve-merges 。また、git pull --rebase=preservegit pull --rebase --preserve-mergesと同じではない理由も説明されています。

この他の前の議論では、リベースのpreserve-mergeマージ版が実際に何をしているのか、そして通常のリベースよりもはるかに複雑であることを説明しています。 する(そしてなぜ?)

17
Marc Liyanage

古いgitリポジトリgit upにアクセスすると、提案されているエイリアスが異なります。 https://github.com/aanand/git-up

git config --global alias.up 'pull --rebase --autostash'

これは私に最適です。

0
Nathan Redblur