「git pull」が有害なのはどのような場合ですか?


409

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

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



8
またはgit pull --rebase、この戦略を新しいブランチのデフォルトとして設定することもできます git config branch.autosetuprebase
knoopx

4
knoopxはそれを正しく、ローカルをリモート--rebasegit pull同期するフラグを追加して、更新されたローカルの上にローカルの変更を再生します。次に、プッシュすると、リモートの最後に新しいコミットを追加するだけです。ものすごく単純。
Heath Lilley、2014年

4
@BenMcCormickに感謝します。私はすでにそれをしました、しかし質問の妥当性に関する議論は質問の下のこれらのコメントで行われているようです。そして、あなたの個人的な意見を事実として提示するためのプラットフォームを作成するために質問をすることは、SOのQ&A構造が本当に目的とするものではないと思います。
mcv 14年

4
@RichardHansen、それはポイントシステムをだます方法のように思えます。特に、あなたの答えには、そのような劇的な違いや短い時間ギャップがあります。Q&Aのモデルを使用すると、以前の知識を使用して、質問をして自分たちで答えることができます。その時点で、ブログ投稿を書くことを検討する必要があります。それは何倍も適切だからです。Q&Aは特に他の人々の知識を求めます。ブログ投稿はあなた自身のものを展示します。
ジョシュブラウン

回答:


546

概要

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

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

  • マージコミットは本質的に調べるのが困難です。マージが何をしているかを理解するには、すべての親との違いを理解する必要があります。従来のdiffはこの多次元情報をうまく伝えていません。対照的に、一連の通常のコミットは簡単に確認できます。
  • マージコミットの解決は難しいので、マージの競合解決はトリッキーであり、多くの場合、ミスは長期間検出されません。
  • Mergeは通常のコミットの影響を静かに置き換えることができます。コードはインクリメンタルコミットの合計ではなくなり、実際に何が変更されたかについて誤解が生じます。
  • マージコミットは、一部の継続的な統合スキームを混乱させる可能性があります(たとえば、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の使用パターンは、aを実行git pullして最新の変更を取り込み、その後にa git rebase @{u}git pull導入して、導入されたマージコミットを削除することです。これは、Gitは伝えることで、単一のステップにこれらの2つのステップを減らすために、いくつかの設定オプションを持っていることを共通十分だgit pull(参照の代わりにマージのリベースを実行するためにbranch.<branch>.rebasebranch.autosetuprebase、およびpull.rebaseオプション)。

残念ながら、保持したいプッシュされていないマージコミットがある場合(たとえば、プッシュされた機能ブランチをにマージするコミット)、リベースプルmastergit pullbranch.<branch>.rebase設定されているtrue)またはマージプル(デフォルトのgit pull動作)の後にリベースが機能します。これはgit rebase--preserve-mergesオプションなしでマージを排除する(DAGを線形化する)ためです。rebase-pullオペレーションは、マージを保持するように構成することはできません。マージプルとそれに続くgit rebase -p @{u}は、マージプルによって引き起こされたマージを排除しません。 アップデート: 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 2.0以降でgit pullは、デフォルトで早送りマージのみを実行するように設定できます。

git config --global pull.ff only

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


6
@brianz:git remote update -pと同等git fetch --all -pです。昔はオプションがなかっgit remote update -pたので、私はタイピングの習慣にfetchあり-pます。大手に関しては!、説明の参照alias.*ではgit help config。「エイリアス展開の前に感嘆符が付いている場合は、シェルコマンドとして扱われます。」
Richard Hansen、

13
Git 2.0は、pull.ffエイリアスなしで同じことを実現するように見える構成を追加します。
ダニートーマス

51
理由のいくつかは、「他の人がクレイジーなことをするときにプルが問題を引き起こす可能性がある」のように聞こえます。いいえ、問題を引き起こすのは、アップストリームのリポジトリからコミットをリベースするようなクレイジーなものです。IMOリベースは、まだプッシュされていないコミットでローカルで行う場合にのみ安全です。たとえば、プッシュする前にプルする場合、ローカルコミットをリベースすると、履歴を線形に保つことができます(ただし、線形履歴はそれほど大きな問題ではありません)。それでも、git up興味深い代替案のように聞こえます。
mcv

16
あなたのポイントのほとんどはあなたが何か間違ったことをしているためです:あなたはあなた自身の作業ブランチでコードをレビューしようとしています。それは良い考えではありません。新しいブランチを作成し、--rebase = preserveをプルして、そのブランチを投げます(または、必要に応じてマージします)。
ファンカスター2014年

5
ここでの@funkasterのポイントは、特に「他の人のコミットメントをレビューする難しさ」に関して、非常に理にかなっています。これは、ほとんどのGitユーザーが使用するレビューフローではありません。これは、どこでもお勧めしたことがないものであり、見出しの下に記載されている不要な追加作業のすべての原因git pullです。
Ben Regenspan、2014年

195

私の答えは、HackerNews で生じた議論から引き出されました:

ヘッドリッジのヘッドリッジの法則を使用して質問に答えるだけに誘惑されます。 git pullます。そうではありません。

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

13
同意する。本質的に有害なものは何もありませんgit pull。ただし、厳密に必要以上に履歴を書き換えたいなど、いくつかの有害な慣行と競合する可能性があります。しかし、gitは柔軟なので、別の方法で使用したい場合は、必ずそうしてください。しかし、それはあなた(まあ、@ Richard Hansen)がgitで異常なことをしたいからであり、git pull有害だからではありません。
mcv

28
これ以上同意できませんでした。人々は有害であるgit rebaseと主張し、検討していgit pullますか?本当に?
Victor Moroz 2014年

10
道徳を軸にして誰かがグラフを作成し、gitコマンドを良い、悪い、またはその中間に分類するのを見るのはいいことです。このグラフは開発者によって異なりますが、1つはgitを使用することについて多くのことを示しています。
マイケルト2014年

5
オプションgit pullなしの私の問題--rebaseは、それが作成するマージの方向です。差分を見ると、そのマージのすべての変更は、変更を行った人ではなく、プルした人​​に属しています。マージが2つの別々のブランチ(A-> B)に予約されているワークフローが好きなので、マージコミットは導入された内容を明確にし、リベースは同じブランチで最新の状態になるように予約されています(リモートA->ローカルA) )
Craig Kochis 14年

4
それで、誰かが誰かの数秒前に引っ張りをしたのか、それともその逆かを知ることで、あなたは何を得ることができますか?これは単なるノイズであり、本当に関連する履歴を難読化しているだけだと思います。これは歴史の価値をさらに低下させます。良い歴史は、a)きれいで、b)実際に重要な歴史があるべきです。
David Ongaro 2014年

26

Gitを正しく使用していても、害はないと考えられています。ユースケースを考えると、それがどのように悪影響を与えるかはわかりますが、共有履歴を変更しないことで問題を回避できます。


10
これについて詳しく述べると、全員が自分のブランチで作業している場合(私の意見ではgitを使用する適切な方法です)、git pull問題は発生しません。gitでの分岐は安価です。
AlexQueue 2014年

18

受け入れられた回答の主張

rebase-pull操作は、マージを保持するように構成できません

しかし、Git 1.8.5以降では、その回答の日付が後日、

git pull --rebase=preserve

または

git config --global pull.rebase preserve

または

git config branch.<name>.rebase preserve

ドキュメントは言います

ローカルでコミットされたマージコミットが「git pull」を実行してもフラット化されないように、「git rebase」にpreserve,も渡す場合--preserve-merges

この前のディスカッションには、より詳細な情報と図があります:git pull --rebase --preserve-merges。またgit pull --rebase=preserve、がと同じではない理由も説明しますがgit pull --rebase --preserve-merges、これは正しく動作しません。

この他の以前のディスカッションでは、rebaseのpreserve-mergesバリアントが実際に行うこと、および通常のrebaseよりもはるかに複雑な方法について説明しています。


弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.