[解決済み】`git pull`が有害なのはどのような場合ですか?
質問
私の同僚に、次のように主張する人がいます。
git pull
は有害だと主張し、誰かがそれを使うたびに腹を立てている同僚がいます。
は
git pull
コマンドは、ローカルリポジトリを更新する標準的な方法のようです。 このコマンドで
git pull
を使用すると問題が発生しますか?どのような問題が発生するのでしょうか?git リポジトリを更新するためのより良い方法はありますか?
どのように解決するのですか?
概要
デフォルトでは
git pull
はマージコミットを作成し、コード履歴にノイズと複雑さを加えます。 さらに
pull
を使うと、自分の変更が入ってくる変更にどのように影響されるかを考えなくなりがちです。
は
git pull
コマンドは、早送りのマージを行うだけであれば安全です。 もし
git pull
が fast-forward マージのみを行うように設定されている場合、そして fast-forward マージが不可能な場合、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
はマージコミットを作成します。
マージコミットについて本質的に悪いことは何もありませんが、危険なこともあるので、敬意をもって扱われるべきです。
- マージコミットは本質的に調べるのが難しいものです。 マージが何をしているかを理解するには、すべての親に対する違いを理解する必要があります。 従来の diff では、この多次元的な情報をうまく伝えることができません。 対照的に、一連の通常のコミットは簡単にレビューできます。
- マージの競合の解決は厄介で、マージコミットはレビューしにくいため、間違いが長い間発見されないことがよくあります。
- マージは通常のコミットの効果に静かに取って代わります。 コードはもはやインクリメンタルなコミットの合計ではなく、実際に何が変更されたのかについて誤解を招きます。
- マージ コミットは、いくつかの継続的インテグレーション スキームを混乱させるかもしれません (たとえば、2 番目の親が不完全な進行中の作業を指すという想定で、最初の親のパスだけを自動ビルドするなど)。
もちろん、マージには時間と場所があります。しかし、マージがいつ使われるべきで、いつ使われるべきでないかを理解することは、リポジトリの有用性を向上させることができます。
Gitの目的は、コードベースの進化を簡単に共有・消費できるようにすることであり、歴史が展開したとおりに正確に記録することではないことに注意してください。 (もしあなたが反対なら
rebase
コマンドとそれがなぜ作られたのかを考えてみてください)。 で作成されたマージコミットは
git pull
によって作成されたマージコミットは、他の人に有用な意味を伝えません。それは、あなたが変更を終える前に、他の誰かがたまたまリポジトリにプッシュしたというだけのことだからです。 他の人にとって意味がなく、危険であるなら、なぜそのようなマージコミットがあるのでしょうか?
を設定することは可能です。
git pull
をマージする代わりにリベースするように設定することもできますが、これにも問題があります(後述します)。 代わりに
git pull
は fast-forward マージのみを行うように構成されるべきです。
リベースアウトコミットの再導入
誰かがブランチをリベースして、それを強制的にプッシュしたとします。 これは通常起こるべきではありませんが、時には必要な場合もあります (たとえば、誤ってコミットしてプッシュしてしまった 50GiB のログファイルを削除する場合など)。 で行われたマージは
git pull
によって行われるマージは、上流ブランチの新バージョンをローカルリポジトリにまだ存在する古いバージョンにマージします。 この結果をプッシュすると、熊手や松明があなたのところにやってくるでしょう。
本当の問題は強制アップデートだと主張する人もいるかもしれません。 たしかに、一般的には可能な限り強制プッシュを避けることが望ましいのですが、時には避けられないこともあります。 開発者は、強制アップデートが時々起こるので、それに対処する準備をしておかなければなりません。 これは、古いコミットをやみくもにマージするのではなく、通常の
git pull
.
サプライズ作業ディレクトリの変更
作業ディレクトリやインデックスがどのようなものになるかを予測する方法はありません。
git pull
が実行されるまで、作業ディレクトリやインデックスがどのようになるかを予測する方法はありません。 他の何かをする前に解決しなければならないマージの衝突があるかもしれませんし、誰かが誤って押してしまったために作業ディレクトリに 50GB のログファイルが導入されるかもしれませんし、作業中のディレクトリの名前を変更するかもしれません、などなどです。
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
に、マージの代わりにリベースを行うように指示します (
branch.<branch>.rebase
,
branch.autosetuprebase
そして
pull.rebase
のオプションがあります)。
残念ながら、保存したい未押しのマージコミットがある場合 (たとえば、押された機能ブランチをマージするコミットを
master
にマージするコミット) がある場合、リベースプル (
git pull
と
branch.<branch>.rebase
に設定します。
true
) またはマージプル (デフォルトの
git pull
の動作) の後にリベースを実行してもうまくいきません。 これは
git rebase
はマージを排除する(DAGを線形化する)からです。
--preserve-merges
オプションがない場合です。 rebase-pull 操作はマージを保持するように設定することはできず、 merge-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
というエイリアスを除きます。
- ローカルブランチが上流ブランチと一緒に設定されていない場合、エラーメッセージはもう少し不可解なものになります。
-
ドキュメント化されていない機能に依存している (
-p
引数に渡されるfetch
に渡される引数) は、Git の将来のバージョンで変更される可能性があります。
Git 2.0 以降をお使いの場合
Git 2.0 以降のバージョンでは、以下のように設定することができます。
git pull
を設定して、デフォルトでは fast-forward マージのみを行うようにすることができます。
git config --global pull.ff only
これにより
git pull
のように動作します。
git pull --ff-only
のように動作しますが、それでも上流のコミットをすべて取得したり、古い
origin/*
ブランチを削除できないので、私はまだ
git up
.
関連
-
[解決済み] Git で直近のローカルコミットを取り消すには?
-
[解決済み] Gitブランチをローカルやリモートで削除するには?
-
[解決済み] git pull」と「git fetch」の違いは何ですか?
-
[解決済み] コミット前に 'git add' を取り消すにはどうすればよいですか?
-
[解決済み] リモートのGitブランチをチェックアウトするには?
-
[解決済み] 現在のGit作業ツリーからローカル(未追跡)ファイルを削除する方法
-
[解決済み] Git のリモートブランチを作成する方法を教えてください。
-
[解決済み] git export」(「svn export」のようなもの)を行うか?
-
[解決済み】"git pull" でローカルファイルを強制的に上書きするには?
-
[解決済み】ローカルのGitブランチの名前を変更するには?
最新
-
nginxです。[emerg] 0.0.0.0:80 への bind() に失敗しました (98: アドレスは既に使用中です)
-
htmlページでギリシャ文字を使うには
-
ピュアhtml+cssでの要素読み込み効果
-
純粋なhtml + cssで五輪を実現するサンプルコード
-
ナビゲーションバー・ドロップダウンメニューのHTML+CSSサンプルコード
-
タイピング効果を実現するピュアhtml+css
-
htmlの選択ボックスのプレースホルダー作成に関する質問
-
html css3 伸縮しない 画像表示効果
-
トップナビゲーションバーメニュー作成用HTML+CSS
-
html+css 実装 サイバーパンク風ボタン
おすすめ
-
[解決済み] git rebase fatal: 必要なリビジョンは1つです。
-
[解決済み] ERROR: リモートレポ 'origin' のクローン作成に失敗しました。
-
[解決済み] 複数のコミットをチェリーピックする方法
-
[解決済み] GitHubに空のブランチを作成する
-
[解決済み] git stashを元に戻す
-
[解決済み] GIT_DISCOVERY_ACROSS_FILESYSTEM が設定されていない。
-
[解決済み] SourceTree error:1407742E:SSLルーチン:SSL23_GET_SERVER_HELLO:tlsv1警告プロトコルバージョン
-
[解決済み] Gitのエラー「object file ... is empty」はどうすれば直せますか?
-
[解決済み】ブランチを頭出しで早送りする方法は?
-
[解決済み] なぜ「リモート追跡ブランチ 'origin/develop' を develop にマージする」のでしょうか?