426

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

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

4

5 に答える 5

565

概要

デフォルトで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は、この多次元情報をうまく伝達しません。対照的に、一連の通常のコミットは簡単に確認できます。
  • マージの競合解決には注意が必要です。また、マージコミットの確認が難しいため、間違いが長期間検出されないことがよくあります。
  • マージは、通常のコミットの効果に静かに取って代わることができます。コードはもはや増分コミットの合計ではなく、実際に何が変更されたかについて誤解を招きます。
  • マージコミットは、一部の継続的インテグレーションスキームを混乱させる可能性があります(たとえば、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には、マージの代わりにリベースを実行するようにgit pull指示することで、これら2つのステップを1つのステップに減らすためのいくつかの構成オプションがあることは十分に一般的です( 、、、およびオプションを参照)。git pullbranch.<branch>.rebasebranch.autosetuprebasepull.rebase

残念ながら、保持したいプッシュされていないマージコミットがある場合(たとえば、プッシュされた機能ブランチをにマージするコミットmaster)、rebase-pull(git pullbranch.<branch>.rebase設定されているtrue)もmerge-pull(デフォルトのgit pull動作)の後にリベースは機能します。これは、オプションgit rebaseなしでマージを排除する(DAGを線形化する)ためです。--preserve-mergesrebase-pull操作は、マージを保持するように構成できません。また、merge-pullの後に、merge-pullgit rebase -p @{u}によって引き起こされたマージを削除することはできません。 更新: Gitv1.8.5が追加されましgit pull --rebase=preservegit config pull.rebase preserve。これらは、アップストリームコミットをフェッチした後git pullに実行します。git rebase --preserve-merges(ヘッズアップをしてくれたfunkasterに感謝します!)

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

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

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

Git2.0以降を実行している場合

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

git config --global pull.ff only

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

于 2013-03-09T22:23:10.533 に答える
198

私の答えは、HackerNewsで起こった議論から引き出されたものです。

Betteridge Law of Headlinesを使用して質問に答えたくなります:なぜgit pull有害であると見なされるのですか?そうではありません。

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

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

于 2014-03-12T15:43:46.817 に答える
18

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

リベースプル操作は、マージを保持するように構成できません

しかし、その回答よりも後の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て、ローカルでコミットされたマージコミットが「gitpull」を実行してもフラット化されないようにする場合。

この前の説明には、より詳細な情報と図があります:git pull--rebase--preserve-merges。また、なぜgit pull --rebase=preserveがと同じではないのかを説明しgit pull --rebase --preserve-mergesます。これは正しいことをしません。

この他の以前の議論では、rebaseのpreserve-mergesバリアントが実際に何をするのか、そしてそれが通常のrebaseよりもはるかに複雑である方法を説明しています:gitの「rebase--preserve-merges」は正確に何をしますか(そしてその理由は?)

于 2014-03-12T16:38:35.023 に答える
-1

古いgitリポジトリに移動すると、エイリアスが異なります。 https://github.com/aanand/git-up

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

これは私にとって完璧に機能します。

于 2019-08-13T21:56:58.690 に答える