こんにちは。Gitはコマンド操作とクライアントアプリ操作を使い分けるのが好きな福田です。
突然ですが問題です。 このGitのよくあるコマンドラインを見てください。
git push origin main
このmain
って、どのmain
だと思いますか?
...答えは、ローカルリポジトリーのmain
です。
え、当たり前ですかね...?
なんと私は10年以上にわたって、これがorigin
のmain
のことだと勘違いしながら使い続けてきました。
え、結果的に同じことじゃん、って? まあそうなんですが、この勘違いが見事にpush誤爆を生みまして、真相を知った私はまあ大きな衝撃を受けたわけです。
この記事では、私の勘違いポイントと、その勘違いが招いたpush誤爆を紹介します。
私の勘違いポイント
git push remote branch-A
というGitコマンドを考えてみます。 私はこのコマンドが、
- ローカルリポジトリーの今のブランチを、
remote
のbranch-A
にpushする
というものだと勘違いしたまま、10年以上使っていました。
正しくは、
- ローカルリポジトリーの
branch-A
を、remote
のbranch-A
にpushする
です。
このコマンドで指定するbranch-A
は、push元のブランチ名を指します。
私はこれをpush先だと勘違いしていたわけです。
いつも「元」と「先」が同じブランチ名だったので、ずっと気付かなかったわけですね。。
さて、push先のブランチ名はどのように決まるのでしょうか? 実は冒頭のコマンドは省略形であり、自動的にpush元と同じになります(大抵は1)。 そして、省略しない場合は次のような形になります。
git push remote branch-A:branch-B
これがフルの形で、
- ローカルリポジトリーの
branch-A
を、remote
のbranch-B
にpushする
ということになります。
この最後の:branch-B
の部分が省略可能なわけです。
冒頭のコマンドは、次の省略形だったわけです。
git push remote branch-A:branch-A
勘違いが招いたpush誤爆
その時私は、GitHubのとあるリポジトリーに寄せられた、とあるpull requestのレビューをしていました。 最後に少し修正が必要になったので、私が代わりに修正をpushしようと思ったのが発端です。
こういう場合、いつもの私は、pull request元のブランチをローカルリポジトリーにfetchしてブランチを切替え、修正を足してpushします。
しかし、その時のpull requestは、forkリポジトリーのmain
ブランチからリクエストされていました。
ローカルリポジトリーの既存のmain
ブランチと被ってしまうので、仕方なく私は、適当に名前を付けたブランチを作成しました。
作成したブランチ名を仮にtmp-feature
とすると、やりたかったのは次の図のような操作です。
さて、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。。
正しくは、次のように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の引数には気をつけましょう!なるべくリモート追跡ブランチを活用しましょう!
クリアコードは、様々な自由ソフトウェア(オープンソースソフトウェア)の開発・メンテナンスを行い、日々得られた知見を公開しています。 興味があったらぜひ他の記事もご覧ください!
また、エンタープライズ向けのサービスも幅広くやっておりますので、詳しくはサービス一覧をご覧いただき、お問い合わせフォームよりお気軽にご相談ください。