概要
デフォルトで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>.rebase
、branch.autosetuprebase
、およびpull.rebase
オプション)。
残念ながら、保持したいプッシュされていないマージコミットがある場合(たとえば、プッシュされた機能ブランチをにマージするコミット)、リベースプルmaster
(git pull
にbranch.<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 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 2.0以降でgit pull
は、デフォルトで早送りマージのみを実行するように設定できます。
git config --global pull.ff only
これはのgit pull
ように動作しますgit pull --ff-only
が、それでも上流のコミットをすべてフェッチしたり、古いorigin/*
ブランチを削除したりしないので、私はまだ好みgit up
ます。