ククログ

株式会社クリアコード > ククログ > git push origin mainの意味をずっと勘違いしていた話

git push origin mainの意味をずっと勘違いしていた話

こんにちは。Gitはコマンド操作とクライアントアプリ操作を使い分けるのが好きな福田です。

突然ですが問題です。 このGitのよくあるコマンドラインを見てください。

  • git push origin main

このmainって、どのmainだと思いますか?

...答えは、ローカルリポジトリーのmainです。

え、当たり前ですかね...? なんと私は10年以上にわたって、これがoriginmainのことだと勘違いしながら使い続けてきました。

え、結果的に同じことじゃん、って? まあそうなんですが、この勘違いが見事にpush誤爆を生みまして、真相を知った私はまあ大きな衝撃を受けたわけです。

この記事では、私の勘違いポイントと、その勘違いが招いたpush誤爆を紹介します。

私の勘違いポイント

git push remote branch-A

というGitコマンドを考えてみます。 私はこのコマンドが、

  • ローカルリポジトリーの今のブランチを、remotebranch-Aにpushする

というものだと勘違いしたまま、10年以上使っていました。

正しくは、

  • ローカルリポジトリーのbranch-Aを、remotebranch-Aにpushする

です。

このコマンドで指定するbranch-Aは、pushのブランチ名を指します。 私はこれをpushだと勘違いしていたわけです。 いつも「元」と「先」が同じブランチ名だったので、ずっと気付かなかったわけですね。。

さて、pushのブランチ名はどのように決まるのでしょうか? 実は冒頭のコマンドは省略形であり、自動的にpushと同じになります(大抵は1)。 そして、省略しない場合は次のような形になります。

git push remote branch-A:branch-B

これがフルの形で、

  • ローカルリポジトリーのbranch-Aを、remotebranch-Bにpushする

ということになります。

この最後の:branch-Bの部分が省略可能なわけです。 冒頭のコマンドは、次の省略形だったわけです。

git push remote branch-A:branch-A

勘違いが招いたpush誤爆

その時私は、GitHubのとあるリポジトリーに寄せられた、とあるpull requestのレビューをしていました。 最後に少し修正が必要になったので、私が代わりに修正をpushしようと思ったのが発端です。

こういう場合、いつもの私は、pull request元のブランチをローカルリポジトリーにfetchしてブランチを切替え、修正を足してpushします。

usual action

しかし、その時のpull requestは、forkリポジトリーのmainブランチからリクエストされていました。 ローカルリポジトリーの既存のmainブランチと被ってしまうので、仕方なく私は、適当に名前を付けたブランチを作成しました。

作成したブランチ名を仮にtmp-featureとすると、やりたかったのは次の図のような操作です。

expected action

さて、tmp-featureに修正を足し、いよいよpushしようとした時に、私の勘違いが10年越しに火を吹きます。 私はpush先のブランチ名を書くものだと勘違いしていたので、次のようにpushしたのです。

git push --force fork main
  • forkはpull request元のforkリポジトリーに名付けたリモート名とします
  • なぜ--forceを付けたのか当時の記憶が定かではないです
    • --forceを付けていなければ、コマンドが失敗して事故は防げていたでしょう
    • もしかしたらrebaseなどをしていたのかもしれませんし、一度失敗して深く考えずに付けたのかもしれません...

さて、どうなるでしょう?

そうです、ローカルリポジトリーのmainを、forkリポジトリーのmainへpushすることになります。 結果、pull requestは差分なしの状態となって、自動クローズされてしまいました。 なんということでしょう2。。

actual action

正しくは、次のようにpushする必要があったわけですね。

git push fork tmp-feature:main

おすすめのやり方

ここまで私の勘違いと実際にやってしまった誤爆について紹介しました。

ふりかえると、そもそもpushの引数でpush元やpush先を考えるのではなく、事前にリモート追跡ブランチを設定しておいて、pushの引数を省略するのが便利でおすすめです。

forkリポジトリー毎fetchして修正をpushする例:

git remote add fork {forkリポジトリーのURL}
git fetch --all --prune --tags --force -j$(nproc)
git switch -c tmp-feature fork/main
# 修正実施
git add {修正したやつ}
git commit {}
git push

※ このようにforkリポジトリーを丸ごとfetchしなくても、ブランチだけfetchすることもできるようです。 詳しくは次のドキュメントをご覧ください。

補足: GitHubのpush権限について

GitHubにおいて他の人のリポジトリーにpushするには、その権限をもらう必要があります。

しかしpull requestの場合は、pull requestの作成者が、pull request元のブランチにpushする権限を、アップストリームのリポジトリーのメンテナーに与えることができます。

このため、今回紹介したようにpull requestにメンテナーがコミットを追加する、ということができる場合があります。

詳しくは次のドキュメントをご覧ください。

まとめ

今回は、私が10年以上にわたり勘違いしていたGitコマンドについて紹介しました。 pushの引数には気をつけましょう!なるべくリモート追跡ブランチを活用しましょう!

クリアコードは、様々な自由ソフトウェア(オープンソースソフトウェア)の開発・メンテナンスを行い、日々得られた知見を公開しています。 興味があったらぜひ他の記事もご覧ください!

また、エンタープライズ向けのサービスも幅広くやっておりますので、詳しくはサービス一覧をご覧いただき、お問い合わせフォームよりお気軽にご相談ください。

  1. push元としてはHEADなど単なるブランチ名以外の形式も使えます。そのような場合のpush先の省略の仕組みは私には分かりません。ドキュメントではsuch a push will update a ref that <src> normally updates without any <refspec> on the command line.と説明されています。

  2. 結局、ブランチを修正してpull requestを開き直していただくことになりました。。