git push解説

以下は社内向け勉強会のLT枠で話した内容をベースにして編集増補したものである。増補分があるので、この内容を5分で話したわけではない。

## git pushコマンドの概要

git pushはローカルリポジトリのオブジェクトをリモートリポジトリに送信して更新を行うコマンドである。

オブジェクトはコミット、[ツリー](https://qiita.com/yunano/items/f3133ea64efed762df2f#tree-ish)、ブロブ（[ツリー](https://qiita.com/yunano/items/f3133ea64efed762df2f#tree-ish)に含まれるファイル）、タグの補足情報（git tagの-aや-sや-uのオプション付きで作ったタグに付けられる）の総称である。

英単語上の逆コマンドであるgit pullは内部的にはgit fetchの後git merge（あるいはgit rebase）を行うコマンドだが、git pushは2つに分けられることはない。

共通のコミットを持たない複数のリポジトリから、1つのリポジトリに向けてpushすることも可能であり、この時に送信先ブランチが異なっていれば送信先リポジトリでは共通の先祖を持たない複数のコミット履歴が共存することになる。

## git pushコマンドの基本の形

git pushコマンドは`git push [オプション] [<リポジトリ名> [<refspec>...]]`という形を取る。

### リモートリポジトリ設定

git pushコマンドで指定するリポジトリ名はgit remoteコマンドで設定されるリモートリポジトリに付けた名称である。

すなわち、`git remote add <リポジトリ名> <URL>`で指定したリポジトリ名とURLでリモートリポジトリ設定が新規に行われ、`git remote set-url <リポジトリ名> <URL>`で指定したURLでリポジトリ名のリモートリポジトリ設定が更新される。

`git remote -v`で設定されたリポジトリ名とURLの一覧表示が行われる（-vがないとリポジトリ名だけでURLは表示されない）。

ちなみに`git remote set-url --add <リポジトリ名> <URL>`を実行した場合、そのリポジトリに対して追加でもう1つのURLが設定される。そうした場合、一度のgit pushコマンドで複数箇所のリモートリポジトリにpushを行うことができる。git fetch/git pullは1箇所からのみの取得となる。どこから取得されるかも`git remote -v`の出力を見れば確認できる。

なお、git cloneを行った場合、originというリポジトリ名で自動的にクローン元URLが設定されている（-oあるいは--originオプションでリポジトリ名は変更可能）。

### refspec

refspecは以下を繋げたものであり、複数指定が可能である。

* オプションとして、pushが強制的に行われるかどうかを示す「+」
* ローカル側の送信元。[commit-ish](https://qiita.com/yunano/items/f3133ea64efed762df2f#commit-ish)で指定する
* 「:」
* 送信先となるリモートリポジトリの[リファレンス](https://qiita.com/yunano/items/f3133ea64efed762df2f#リファレンス)

強制的とは、通常git pushではローカル側コミットがリモート側コミットの履歴上の子孫である場合にしか成功しないが、そのチェックを無効にしてpushを成功させ、リモート側のコミット履歴をローカル側と同じになるように書き換えてしまうことである。git push自体にも-f（あるいは--force）という強制的pushを行うオプションがあるが、それとの違いはrefspecは複数指定可能であることから個々の送信先ごとに「+」を付けて強制するか、付けないで強制しないかを指定できることである。-fではすべての送信先が強制になる。

ローカル側の送信元は省略することが可能だが、省略した場合送信先に指定したリモート側のリファレンス、実質的にはブランチあるいはタグが削除されるという強力な効果が発生する。`git push origin :branch`という「リモートブランチを削除するにはブランチ名の前にコロンを付ける」とWeb検索すると良く見掛けるイディオムである。なお、-d（あるいは--delete）オプションを使って`git push -d <リポジトリ名> <ブランチ名>`とすることでも同じ効果が発生する。-fと同様-dでは個別に削除するかどうかを選ぶことはできずにすべての指定した送信先が削除される。

送信先を省略する時はその前の「:」とともに省略する。この場合、送信元と同じ名前のブランチあるいはタグが送信先に指定されたものとして扱う。つまり、

* ブランチだったなら同名のブランチに送信される
* タグだったなら、
  * そのタグが送信先に存在しない場合は、送信先に送信元と同じオブジェクトを参照するタグが追加される
  * タグが送信先に存在して強制的pushならタグが参照するオブジェクトが送信元のそれに更新される
  * タグが送信先に存在して強制的pushでなければエラー
* ブランチでもタグでもない場合、あるいは同じ名前のブランチもタグもある場合はエラー

となる。

なお、リファレンスを指定する際には送信元でも送信先でも「refs/」で始まる文字列で指定するのが省略しない形であるが、「refs/」で始まらない文字列を指定した場合はブランチかタグかに可能な限り適切に解釈してくれるものである。

「refs/」で始まる文字列で指定する場合、リファレンスはリポジトリのルートディレクトリからの相対で.git/refsディレクトリの中にファイルとして置かれている（ここから後はディレクトリのパスから指定に使わない.gitは省略する）。「refs/heads」の中にあるファイルがそのままブランチ名であり、「refs/tags」の中にあるファイルがそのままタグ名であるので、指定したいファイルを「refs/」で始まるファイルパスで指定すれば良い。ただし、「refs/remotes」にあるのはリモート追跡ブランチと呼ばれるもので、この中にあるものを送信先に指定するとリモート側にリモート追跡ブランチができるので大抵は期待通りにはならないだろう。[リモート追跡ブランチ](#リモート追跡ブランチ)については独立した項目で説明する。また、「refs/」で始まる形式で指定した場合はディレクトリとサブディレクトリにあるファイルすべてを表す「\*」を使用することができる（例：「refs/heads/\*」）。

### 引数なく「git push」だけを実行した時の動作

git pushに-u（あるいは--set-upstream）オプションを付けて実行した場合、pushに成功したすべての送信元ブランチについて、それぞれ送信先のリポジトリ名とブランチ名がupstreamとして記録される。なお、送信元がブランチでなければ記録されない。

upstreamの記録は`git branch -u <リポジトリ名>/<送信先ブランチ名> [<送信元ブランチ名>]`でも行うことができる。-uは--set-upstream-toとも書くことができる（git pushでは--set-upstreamだったが微妙にオプション名が異なる）。送信元ブランチ名を省略した場合はHEADが参照するブランチ（ブランチを参照していない、つまり[detached HEAD状態](https://qiita.com/yunano/items/f3133ea64efed762df2f#detached-head)だとしっかりエラーとなる）、つまり現在作業中のブランチとなる（以降、「HEADが参照するブランチ」を「現在作業中のブランチ」と表現する）。また、`git branch -vv`を実行すると記録されている内容が一覧で確認できる。

git cloneした場合にはクローンされたブランチについてupstream記録済みとなっている。

記録されたupstreamは、リポジトリ名とrefspecを省略した時のgit pushやgit pull等のデフォルト値として使用されることがある。`git push`においては、git configで設定可能なpush.defaultの設定値（デフォルトはsimple）によって挙動が変化する。

* nothing: 何もせず常にエラー
* simple: 現在作業中のブランチにupstreamが記録されており、かつ記録された送信先ブランチ名が同名の時のみ送信する。それ以外はエラー
* upstream: 現在作業中のブランチにupstreamが記録されている場合、記録されている先に送信する。記録されていなければエラー
* current: upstreamの記録に関わらず現在作業中のブランチと同名のブランチに送信する
* matching: upstreamの記録に関わらず送信元とリモート追跡ブランチに同名で存在するすべてのブランチを同名の送信先に送信する

currentにおいてはリポジトリ名はupstreamに記録されている値、matchingにおいては現在作業中のブランチのupstreamに記録されている値が使用される。記録されていないならリポジトリ名省略時のデフォルト値であるoriginとなる。

ちなみに、リポジトリ名は指定してrefspecは省略する、ということもできるわけだが、その場合上記のうち挙動が変わるものもある。simpleはcurrentと同じく「upstreamの記録に関わらず現在作業中のブランチと同名のブランチに送信する」となり、upstreamは「記録されたupstreamと同じリポジトリ名が指定されている場合は記録されている先に送信し、違うリポジトリ名が指定されている場合はエラー」となる。

#### リモート追跡ブランチ

リモート追跡ブランチとは、リモート側ブランチをローカル側にコピーしたブランチである。言い換えるとリモート側ブランチの参照先コミットが書かれた[リファレンス](https://qiita.com/yunano/items/f3133ea64efed762df2f#リファレンス)である。この参照先コミットはgit fetchやgit pushの正常な完了により更新される。

ちなみに-u（--upstream-to）等によってupstreamとして記録されるのは厳密にはリモート側のブランチではなくリモート追跡ブランチであり、[リモート追跡ブランチからブランチが作られる](https://qiita.com/yunano/items/f3133ea64efed762df2f#オプションなしで新しいブランチを作ってそのブランチへ切り替えが行われる場合)時には、デフォルトでそのリモート追跡ブランチがupstreamとして記録される。

## git pushの便利なオプション

ここまで挙がっていないオプションの中で良く使われるのを見掛けるものを紹介する。

* --force-with-lease

-f（--force）と似ているが、pushする前にリモート追跡ブランチとリモート側ブランチを比較して、リモート側ブランチに新しいコミットがない場合のみ強制的pushを行う。つまり誰かが先にリモート側ブランチに新たなコミットを追加している場合にpushを行えないようにしているわけである。

* --all

ローカルのすべてのブランチをpushする。同時にrefspecも指定することはできずエラーになる。リポジトリ名は省略した場合、upstreamに記録されている値が使用される。記録されていないならリポジトリ名省略時のデフォルト値であるoriginとなる。

* --tags

ローカルのすべてのタグをpushする。refspecを指定した時は指定したものも同時にpushされる。refspecを省略すると同時にpushされるものは何もない。リポジトリ名は省略した場合、現在作業中のブランチのupstreamに記録されている値が使用される。記録されていないならリポジトリ名省略時のデフォルト値であるoriginとなる。--allと--tagsは同時に使用することはできない。

* --prune

ローカルにないブランチをリモート側でも削除する。リポジトリ名やrefspecを省略した場合の挙動はpush.defaultの設定値、あるいは他のオプションが指定されているならそのオプションの挙動に依存する。通常は--allや--tagsとともに、あるいはrefspecの送信元と送信先との両方（送信先は同じなら省略しても良いが）を「refs/」から始める形式で「\*」を使って指定する。これらの指定ですべてのブランチやタグが対象になると言ってもすべて削除されるわけではなく、削除されるのはあくまでローカルに存在しないものであり、存在するものは通常通りpushされる。なお、refspecで送信元にローカルにない単一のブランチを指定して削除しようとしても存在しないものを指定しているのでエラーになるだけである。