TL;DR
git checkout <branch>
には2つの意味があるよ。
-
HEADを
<branch>
ブランチに移動するだけ。<branch>
ローカルブランチが存在しているときにこの振る舞いをする。 -
git checkout -b <branch> origin/<branch>
のショートカットとして。<branch>
ローカルブランチが存在せず、かつorigin/<branch>
というremote-tracking branch
が存在するときに、この振る舞いをする。この時、<branch>
は、tracking branch、origin/<branch>
はupstream branchとなる。
準備
Definition
-
branch .. simply a lightweight movable pointer to one of commits. (p.60)
-
HEAD .. The pointer to the local branch you’re currently on. (p.61)1
-
remote branch .. The pointer in your remote repositories (for instance, repository on GitHub), including branches, tags. (p.79)
-
remote-tracking branch .. Reference to the state of remote branch. Remote-tracking branch takes the form
<remote>/<branch>
. It's local reference that you can’t move.(p.80) (it's also called track remote branch) -
tracking branch .. The local branch that is checking out from a remote-tracking branch.(p.86-87)
-
upstream branch .. The remote-tracking branch that the tracking branch tracks.(p.87)
この本にはlocal branch
の定義が明示されていませんでしたが、remote branch
との対義語と捉えるのが自然でしょう。つまり、The pointer in your local repositories
とします。
包含関係的には、local branch
$\supset$ remote-tracking branch
$\supset$ upstream branch
です2。tracking branch
とupstream branch
は対の関係ね。3
で、ちょっとややこしいんですが、local branch
の意味を(local branch
全体の集合 - remote-tracking branch
全体の集合)と捉えてたりもします。4
ここでは厳密に区別したいので、広義のローカルブランチ、狭義のローカルブランチと名付けましょう。それで、単にlocal branch
というときには、狭義のローカルブランチの意味で使うことにし、広義のローカルブランチを単にbranch
ということにします。
本題
Sample
- リモートレポジトリ https://github.com/knknkn1162/git_test にmasterとfix-readmeリモートブランチがある状態。( graph: https://github.com/knknkn1162/git_test/network )
Version
% git --version
git version 2.16.2
git cloneと状態の確認
# git fetch .. 最新のリモートリポジトリの情報を取得してremote-tracking branchに反映する(update)
% git clone git@github.com:knknkn1162/git_test.git # .. リモートレポジトリをローカルのディレクトリ内に複製して remote-tracking branchを作成する。(create)
Cloning into 'git_test'...
remote: Counting objects: 11, done.
remote: Compressing objects: 100% (6/6), done.
remote: Total 11 (delta 0), reused 11 (delta 0), pack-reused 0
Receiving objects: 100% (11/11), done
# 確認
% git branch -vv
# `master`と`origin/master`は`tracking branch`と`upstream branch`の関係。
* master dcfa179 [origin/master] add sample.txt
% git remote show origin
* remote origin
Fetch URL: git@github.com:knknkn1162/git_test.git
Push URL: git@github.com:knknkn1162/git_test.git
HEAD branch: master
Remote branches:
fix-readme tracked
master tracked
Local branch configured for 'git pull':
master merges with remote master
Local ref configured for 'git push':
master pushes to master (up to date)
# List both remote-tracking branches and local branches with `-a` option. (See https://git-scm.com/docs/git-branch#git-branch--a)
% git branch -a
# `local branch`は`master`, `remote-tracking branch`は`origin/fix-readme`と`origin/master`。
# `remotes/`がprefixで付いているのは、論理的には、`origin/fix-readme`という名のlocal branchも存在しうるので、
# `local branch`と`remote-tracking branch`を峻別するため。
* master
remotes/origin/HEAD -> origin/master # 現在のリモート追跡ブランチのHEADは`origin/master`リモート追跡ブランチ
remotes/origin/fix-readme
remotes/origin/master
% git remote show origin
* remote origin
Fetch URL: git@github.com:knknkn1162/git_test.git
Push URL: git@github.com:knknkn1162/git_test.git
HEAD branch: master
Remote branches:
fix-readme tracked # origin/fix-readme というremote-tracking branchがローカルにある
master tracked # origin/master というremote-tracking branchがローカルにある
Local branch configured for 'git pull':
master merges with remote master
Local ref configured for 'git push':
master pushes to master (up to date)
# 各ブランチの位置関係はこんな感じになっています。
% git log --graph --all
* commit 31458c1bec7bb6dcc420ffe6ca9a296a106737a7 (origin/fix-readme)
| Author: knknkn <knknkn1162@gmail.com>
| Date: Sat Apr 14 11:27:26 2018 +0900
|
| insert item in readme
|
| * commit dcfa17936dc786159288eb4668d4e49499d0d378 (HEAD -> master, origin/master, origin/HEAD)
| | Author: knknkn <knknkn1162@gmail.com>
| | Date: Sat Apr 14 09:52:09 2018 +0900
| |
| | add sample.txt
| |
| * commit 731dbcfb14fa0c7f24a9efc0d469bceb885eccbe
|/ Author: knknkn <knknkn1162@gmail.com>
| Date: Sat Apr 14 09:51:45 2018 +0900
|
| fix readme
|
* commit 0cf6aa39477d9a09b2656b1f1ab86b91c17e1485
| Author: knknkn <knknkn1162@gmail.com>
| Date: Sat Apr 14 09:49:01 2018 +0900
|
| add README.md
|
* commit fbd0bed4c2e6f3d4dad87049f7bc0ffd61b63a26
Author: knknkn <knknkn1162@gmail.com>
Date: Fri Apr 13 10:14:47 2018 +0900
first commit
NOTE)
論理的には、
origin/fix-readme
という名のlocal branchも存在しうる
っていうのは、つまりこういうことです。
# ブランチ名を(わざと)`origin/aaaaa`で作成してみる
% git branch origin/aaaaa
# 問題なく`origin/aaaaa`ローカルブランチを作成できる
% git branch -vv
* master dcfa179 [origin/master] add sample.txt
origin/aaaaa dcfa179 add sample.txt
% git branch -a
* master
origin/aaaaa #<= `origin/`がついてるけど、これは、remote-tracking branchでない!
remotes/origin/HEAD -> origin/master
remotes/origin/fix-readme
remotes/origin/master
checkout
このあと、remote-tracking branch
(origin/fix-readme)を元に、fix-readme
ブランチを作成したい。
# 現時点でfix-readmeローカルブランチは存在しないことに留意する
# HEAD の移動を伴う
% git checkout fix-readme # same as `git checkout -b fix-readme origin/fix-readme` or `git checkout --track origin/fix-readme`
Branch 'fix-readme' set up to track remote branch 'fix-readme' from 'origin'.
Switched to a new branch 'fix-readme'
# ローカルブランチfix-readme を、追跡ブランチである origin/fix-readmeからつくる
% git branch fix-readme origin/fix-readme
Branch 'fix-readme' set up to track remote branch 'fix-readme' from 'origin'.
# この後、`git checkout fix-readme`とすれば、上のコマンドと同義になる
なんとなく使ってた。
ここで、注意すべきは、git checkout local-branch
には2つの用法があるということだ!
一つは、HEADの移動のみを伴うコマンドとして。もう一つは、git checkout -b fix-readme origin/fix-readme
のエイリアスとして。
前者は、すでにブランチが存在しているときにHEADを移動させるために用いるコマンド。(コチラがcheckout本来の用法)
後者の場合は、git branch fix-readme origin/fix-readme
+ git checkout fix-readme
(HEADを移動させるだけ)のショートカットの意味で使われる。5
記法は全く同じだが、動作がぜんぜん違う。
以下、理解していないと、間違えやすいところ。
- もし、
-b
オプションを付けてしまって、git checkout -b fix-readme
とすると、全く違ったブランチが切られてしまう:
# fix-readme作ったので、一旦消す
% git checkout master
% git branch --delete fix-readme
Deleted branch fix-readme (was 0cf6aa3).
% git checkout -b fix-readme # masterからローカルブランチを切ることに注意する。
# 一個前の例はリモート追跡ブランチ origin/fix-readmeからローカルブランチを作成している
Switched to a new branch 'fix-readme'
% git branch -vv
* fix-readme dcfa179 add sample.txt # <= masterブランチから切られたブランチなので、fix-readmeブランチはmasterブランチと現時点で同じ
master dcfa179 [origin/master] add sample.txt
# fix-readme ローカルブランチと origin/fix-readme リモート追跡ブランチが離れていることに注意する
% git log --graph
* commit 31458c1bec7bb6dcc420ffe6ca9a296a106737a7 (origin/fix-readme)
| Author: knknkn <knknkn1162@gmail.com>
| Date: Sat Apr 14 11:27:26 2018 +0900
|
| insert item in readme
|
| * commit dcfa17936dc786159288eb4668d4e49499d0d378 (HEAD -> fix-readme, origin/master, origin/HEAD, master)
| | Author: knknkn <knknkn1162@gmail.com>
| | Date: Sat Apr 14 09:52:09 2018 +0900
| |
| | add sample.txt
| |
| * commit 731dbcfb14fa0c7f24a9efc0d469bceb885eccbe
|/ Author: knknkn <knknkn1162@gmail.com>
| Date: Sat Apr 14 09:51:45 2018 +0900
|
| fix readme
|
* commit 0cf6aa39477d9a09b2656b1f1ab86b91c17e1485
| Author: knknkn <knknkn1162@gmail.com>
| Date: Sat Apr 14 09:49:01 2018 +0900
|
| add README.md
|
* commit fbd0bed4c2e6f3d4dad87049f7bc0ffd61b63a26
Author: knknkn <knknkn1162@gmail.com>
Date: Fri Apr 13 10:14:47 2018 +0900
first commit
- もし、
fix-readme
ブランチがすでに存在していたら、git checkout fix-readme
としても、HEAD
が切り替わるだけでfix-readme
がtracking branch
に変化するわけではない:
# fix-readme作ったので、一旦消す
% git checkout master
% git branch --delete fix-readme
Deleted branch fix-readme (was 0cf6aa3).
% git branch fix-readme
MOB18006:git_test % git branch -vv (git)-[master]
fix-readme dcfa179 add sample.txt # fix-readmeは、tracking branchでなく、ただのbranch
* master dcfa179 [origin/master] add sample.txt # 対してmasterはtracking branch
# HEADがmasterにあることの確認
% git log --graph --all --oneline
* 31458c1 (origin/fix-readme) insert item in readme
| * dcfa179 (HEAD -> master, origin/master, origin/HEAD, fix-readme) add sample.txt
| * 731dbcf fix readme
|/
* 0cf6aa3 add README.md
* fbd0bed first commit
% git checkout fix-readme
Switched to branch 'fix-readme'
# HEADがfix-readmeに移動したことの確認
% git log --graph --all --oneline
* 31458c1 (origin/fix-readme) insert item in readme
| * dcfa179 (HEAD -> fix-readme, origin/master, origin/HEAD, master) add sample.txt #<= HEADが移動しただけ
| * 731dbcf fix readme
|/
* 0cf6aa3 add README.md
* fbd0bed first commit
% git branch -vv
* fix-readme dcfa179 add sample.txt # fix-readmeは、tracking branchでなく、ただのbranch
master dcfa179 [origin/master] add sample.txt
どちらの用法でgit checkout fix-readme
が使われているのかを把握しておかないと、各ブランチの位置関係が想定とは全く異なってしまう。
mergeとかrebaseとか難しいなぁ、って思ってたんだけど、そもそも、ちゃんとcheckout
理解してなかった。
逆に言うと、checkout
理解できたってことは、remote-tracking branch
とかlocal branch
とかtracking branch
とかupstream branch
とかの意味がわかったってことなので、fetch
やmerge
とかもすぐ理解できる(と思われる)。
まとめ
-
git checkout <branch>
には2つの異なる意味があるよ
補足
tracking branch
とupstream branch
について、言葉の定義だけ述べて存在意義を確認していなかったので、改めて補足。
一つは、git pull
するときに、tracking branch
とupstream branch
が定められている必要がある。6
これを確かめるために、fix-readme
がただのローカルブランチの場合(つまり、git checkout -b fix-readme
と-b
オプションをつけてチェックアウトする)、pullが失敗することを確認しよう。
% git branch
* master
*
# fix-readmeがtracking branchにならないように、新規に`fix-readme`ブランチをmasterブランチから作成する
% git checkout -b fix-readme
Switched to a new branch 'fix-readme'
# 現状の確認
% git branch -vv
* fix-readme dcfa179 add sample.txt
master dcfa179 [origin/master] add sample.txt
% git log --graph --all
* commit 31458c1bec7bb6dcc420ffe6ca9a296a106737a7 (origin/fix-readme)
| Author: knknkn <knknkn1162@gmail.com>
| Date: Sat Apr 14 11:27:26 2018 +0900
|
| insert item in readme
|
| * commit dcfa17936dc786159288eb4668d4e49499d0d378 (HEAD -> fix-readme, origin/master, origin/HEAD, master)
| | Author: knknkn <knknkn1162@gmail.com>
| | Date: Sat Apr 14 09:52:09 2018 +0900
| |
| | add sample.txt
| |
| * commit 731dbcfb14fa0c7f24a9efc0d469bceb885eccbe
|/ Author: knknkn <knknkn1162@gmail.com>
| Date: Sat Apr 14 09:51:45 2018 +0900
|
| fix readme
|
* commit 0cf6aa39477d9a09b2656b1f1ab86b91c17e1485
| Author: knknkn <knknkn1162@gmail.com>
| Date: Sat Apr 14 09:49:01 2018 +0900
|
| add README.md
|
* commit fbd0bed4c2e6f3d4dad87049f7bc0ffd61b63a26
Author: knknkn <knknkn1162@gmail.com>
Date: Fri Apr 13 10:14:47 2018 +0900
first commit
% git pull
There is no tracking information for the current branch. # <= fix-readmeはtracking branchでないため、pullできない
Please specify which branch you want to merge with.
See git-pull(1) for details.
git pull <remote> <branch>
If you wish to set tracking information for this branch you can do so with:
git branch --set-upstream-to=origin/<branch> fix-readme
対して、fix-readme
がtracking branchとなっている場合。
% git branch --delete fix-readme
Deleted branch fix-readme (was dcfa179).
% git checkout fix-readme
Branch 'fix-readme' set up to track remote branch 'fix-readme' from 'origin'.
Switched to a new branch 'fix-readme'
% git branch -vv
# fix-readmeとorigin/fix-readmeがtracking branchとupstream branchの関係で対応している
* fix-readme 31458c1 [origin/fix-readme] insert item in readme
master dcfa179 [origin/master] add sample.txt
% git pull
Already up to date. # 今回は、更新されたorigin/fix-readmeとfix-readmeが同じなので。
Note) ちなみに、pushに関しても同様のことが起こる。詳しくは、https://git-scm.com/docs/git-config#git-config-pushdefault を読んでください。(要するに、fix-readmeをtracking branchに、origin/fix-readmeをupstream branchにしなきゃpushできないってオプションがpush.default = simple
ってわけ)8
# git clone git@github.com:knknkn1162/git_test.git
% git checkout -b fix-readme
Switched to a new branch 'fix-readme'
% git branch -vv
* fix-readme dcfa179 add sample.txt # fix-readmeはただのlocal-branchであって、tracking-branchではない。
master dcfa179 [origin/master] add sample.txt
% git push
fatal: The current branch fix-readme has no upstream branch.
To push the current branch and set the remote as upstream, use
git push --set-upstream origin fix-readme
# remote branch(origin)を指定しても同じ。
% git push origin
fatal: The current branch fix-readme has no upstream branch.
To push the current branch and set the remote as upstream, use
git push --set-upstream origin fix-readme
# ちなみに、この話は`git push`のローカルブランチの引数がない場合の挙動のことを言っている。
# `git push origin fix-readme`と明示的に書けば、下記のようにrejectされる:
# `fix-readme`は`origin/fix-readme`の子孫でないので、pushがrejectされる
% git log --graph --all
* commit 31458c1bec7bb6dcc420ffe6ca9a296a106737a7 (origin/fix-readme)
| Author: knknkn <knknkn1162@gmail.com>
| Date: Sat Apr 14 11:27:26 2018 +0900
|
| insert item in readme
|
| * commit dcfa17936dc786159288eb4668d4e49499d0d378 (HEAD -> fix-readme, origin/master, origin/HEAD, master)
| | Author: knknkn <knknkn1162@gmail.com>
| | Date: Sat Apr 14 09:52:09 2018 +0900
| |
| | add sample.txt
| |
| * commit 731dbcfb14fa0c7f24a9efc0d469bceb885eccbe
|/ Author: knknkn <knknkn1162@gmail.com>
| Date: Sat Apr 14 09:51:45 2018 +0900
|
| fix readme
|
* commit 0cf6aa39477d9a09b2656b1f1ab86b91c17e1485
| Author: knknkn <knknkn1162@gmail.com>
| Date: Sat Apr 14 09:49:01 2018 +0900
|
| add README.md
|
* commit fbd0bed4c2e6f3d4dad87049f7bc0ffd61b63a26
Author: knknkn <knknkn1162@gmail.com>
Date: Fri Apr 13 10:14:47 2018 +0900
first commit
% git push origin fix-readme
To github.com:knknkn1162/git_test.git
! [rejected] fix-readme -> fix-readme (non-fast-forward)
error: failed to push some refs to 'git@github.com:knknkn1162/git_test.git'
hint: Updates were rejected because the tip of your current branch is behind
hint: its remote counterpart. Integrate the remote changes (e.g.
hint: 'git pull ...') before pushing again.
hint: See the 'Note about fast-forwards' in 'git push --help' for details
-
コミットから見るとHEADはポインタのポインタってやつになります。 ↩
-
厳密には、(
local branch
全体の集合) $\supset$ (remote-tracking branch
全体の集合) $\supset$ (upstream branch
全体の集合)です。 ↩ -
tracking branch
とupstream branch
については、補足の節に書きました。 ↩ -
例えば、https://git-scm.com/docs/git-branch#git-branch--a ではローカルブランチを本記事の「狭義のローカルブランチ」の意味で用いています。 ↩
-
Pro git 2nd Editionのp.87が詳しい。 ↩
-
"If you’re on a tracking branch and type git pull, Git automatically knows which server to fetch from and which branch to merge in."と,Pro git 2nd Editionのp.87に書いてある。ちなみに、
git pull
はgit fetch; git merge FETCH_HEAD
のショートカット7なんだけど、2つのコマンドを順に実行すれば、Already up to date.
となる。 ↩ -
pull.defaultオプションは
pull
の仕様から存在しないです。 ↩