LoginSignup
92
90

More than 3 years have passed since last update.

git checkout理解してなかった

Last updated at Posted at 2018-04-14

TL;DR

git checkout <branch>には2つの意味があるよ。

  1. HEADを<branch>ブランチに移動するだけ。<branch>ローカルブランチが存在しているときにこの振る舞いをする。

  2. git checkout -b <branch> origin/<branch>のショートカットとして。<branch>ローカルブランチが存在せず、かつorigin/<branch>というremote-tracking branchが存在するときに、この振る舞いをする。この時、<branch>は、tracking branch、origin/<branch>はupstream branchとなる。

準備

Definition

Pro git 2nd Editionから:

  • 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 です2tracking branchupstream branchは対の関係ね。3

で、ちょっとややこしいんですが、local branchの意味を(local branch全体の集合 - remote-tracking branch全体の集合)と捉えてたりもします。4
ここでは厳密に区別したいので、広義のローカルブランチ、狭義のローカルブランチと名付けましょう。それで、単にlocal branchというときには、狭義のローカルブランチの意味で使うことにし、広義のローカルブランチを単にbranchということにします。

本題

Sample

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ブランチを作成したい。

checkout
# 現時点で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-readmetracking 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とかの意味がわかったってことなので、fetchmergeとかもすぐ理解できる(と思われる)。

まとめ

  • git checkout <branch>には2つの異なる意味があるよ

補足

tracking branchupstream branchについて、言葉の定義だけ述べて存在意義を確認していなかったので、改めて補足。

一つは、git pullするときに、tracking branchupstream 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ってわけ)7

# 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

  1. コミットから見るとHEADはポインタのポインタってやつになります。 

  2. 厳密には、(local branch全体の集合) $\supset$ (remote-tracking branch全体の集合) $\supset$ (upstream branch全体の集合)です。 

  3. tracking branchupstream branchについては、補足の節に書きました。 

  4. 例えば、https://git-scm.com/docs/git-branch#git-branch--a ではローカルブランチを本記事の「狭義のローカルブランチ」の意味で用いています。 

  5. Pro git 2nd Editionのp.87が詳しい。 

  6. "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 pullgit fetch; git merge FETCH_HEADのショートカット8なんだけど、2つのコマンドを順に実行すれば、Already up to date.となる。 

  7. pull.defaultオプションはpullの仕様から存在しないです。 

  8. https://git-scm.com/docs/git-pull#_description に記載されている 

92
90
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
92
90