git入門 ~仕組みを理解する~ 第6回 gitのbranch(ブランチ)はcommit-hashの別名(ポインタ)

2020年3月14日git-master

gitを学ぶ際に、よく理解しないままbranchを作成し利用していると、branchをファイルやフォルダの塊が別の環境にコピーされたようなイメージを持たれる方がいますが、これはSVNなどを経験している方に多いです。

第1回から第5回まで順番に読んでいただけた方にはbranch(ブランチ)の説明はすごく簡単に終わってしまいます。

タイトルの通りですが、branchはcommit-hashの別名(ポインタ)です。

その意味を説明していきます。

commitに命名されたcommit-hashでは管理が難しい

repositoryに配置されたcommitは名前をhash値(commit-hash)で命名しています。

第5回 実際にcommitツリーを作成しよう」ではcommit-hashでcommitを操作していました。

以下は、git logコマンドで出力したcommitツリーです。

/c/Git/git-test ((ec63e48...))
$ git log --all --graph
* commit ec63e48359151e7473415a5fec3a4f00aee6cc94 (HEAD)
| Author: snow <snow@abc.com>
| Date:   Thu Mar 19 23:26:13 2020 +0900
|
|     file-B update
|
* commit 10dd8d43f84488986c858a6b001c48b9d5358f90
| Author: snow <snow@abc.com>
| Date:   Thu Mar 19 23:09:13 2020 +0900
|
|     file-A delete
|
* commit 5802a30e2843c3a1c894b2223a5a3c2d9f4aded9
| Author: snow <snow@abc.com>
| Date:   Thu Mar 19 22:35:34 2020 +0900
|
|     dir-A file-D add
|
| * commit b64a4ea0401eefa42c3f3e530123c6edeb6fe80e (master)
|/  Author: snow <snow@abc.com>
|   Date:   Thu Mar 19 21:55:12 2020 +0900
|
|       file-C add
|
* commit 3e8a749b6a72756998f920a0c6786902458c3341
| Author: snow <snow@abc.com>
| Date:   Thu Mar 19 21:29:43 2020 +0900
|
|     file-B add
|
* commit d7d2877dc111bbf6e1a7c966d991e867245291c6
  Author: snow <snow@abc.com>
  Date:   Wed Mar 18 21:13:29 2020 +0900

ただ、commit-hashは「3e8a749b6a72756998f920a0c6786902458c3341」のように40文字のランダムな文字列ですので、人が管理するには不便です。

また、新しいcommitを作成する都度commit-hashを覚えておくことは現実的ではありません。

commit-hashには別名(ポインタ)を付けて管理する

実はcommitのcommit-hashには別名(ポインタ)を付けて管理することができます。

commit-hashの別名(ポインタ)には色々とあるのですが、代表的なもの(よく使うもの)には、次のようなものがあります。

  • HEAD
  • branch(ブランチ)
  • remote-tracking-branch(リモート追跡ブランチ)
  • tag(タグ)

今回はHEADとbranch(ブランチ)について学習しましょう。

では、まずHEADから。

HEAD

HEADは「作業中のcommit」のcommit-hashへのポインタです。

作業中のcommitとは

「git checkout」コマンドで展開したcommit

「git commit」コマンドで作成したcommit
などです

 

第5回で作成したcommitで確認してみましょう。

/c/Git/git-test ((ec63e48...))
$ git log --all --graph
* commit ec63e48359151e7473415a5fec3a4f00aee6cc94 (HEAD)
| Author: snow <snow@abc.com>
| Date:   Thu Mar 19 23:26:13 2020 +0900
|
|     file-B update
|
* commit 10dd8d43f84488986c858a6b001c48b9d5358f90
| Author: snow <snow@abc.com>
| Date:   Thu Mar 19 23:09:13 2020 +0900
|
|     file-A delete
|

一番最後に作成したcommitに(HEAD)と表示されていますね。

この状態のHEADはcommit-hash(ec63e48359151e7473415a5fec3a4f00aee6cc94)を指しています。

 

では、「ec63e48359151e7473415a5fec3a4f00aee6cc94」と「HEAD」が同じcommitを指していることを、前回も利用した「git cat-file」コマンドで確認してみましょう。

 

まずは、commit-hashを指定して、確認してみましょう。

/c/Git/git-test ((ec63e48...))
$ git cat-file -p ec63e48359151e7473415a5fec3a4f00aee6cc94
tree 06c5b9b94366d4f65175b207ee7d297b3f30ff42
parent 10dd8d43f84488986c858a6b001c48b9d5358f90
author snow <snow@abc.com> 1584627973 +0900
committer snow <snow@abc.com> 1584627973 +0900

file-B update

 

では次にHEADを指定して、確認してみましょう。

/c/Git/git-test ((ec63e48...))
$ git cat-file -p HEAD
tree 06c5b9b94366d4f65175b207ee7d297b3f30ff42
parent 10dd8d43f84488986c858a6b001c48b9d5358f90
author snow <snow@abc.com> 1584627973 +0900
committer snow <snow@abc.com> 1584627973 +0900

file-B update

同じ結果が表示されましたね。

これで、HEADとcommit-hash(ec63e48359151e7473415a5fec3a4f00aee6cc94)は同じcommitを指していることがわかりました。

 

gitコマンド以外でも確認してみましょう。

現在のHEADがどのcommit-hashを指しているかは「.git」フォルダのHEADファイルでも確認することができます。

/c/Git/git-test ((ec63e48...))
$ cd .git

/c/Git/git-test/.git (GIT_DIR!)
$ ls
COMMIT_EDITMSG  config  description  HEAD  hooks/  index  info/  logs/  objects/  refs/

/c/Git/git-test/.git (GIT_DIR!)
$ cat HEAD
ec63e48359151e7473415a5fec3a4f00aee6cc94

HEADファイルの中身が「ec63e48359151e7473415a5fec3a4f00aee6cc94」となっていまね。

gitではこのHEADファイルに現在操作中のcommitがわかるように管理されているのです。

興味のある方は、「git checkout」コマンドでHEADが指すcommit-hashが更新されることを確認してみましょう。

branch(ブランチ)

branchもHEADと同様にcommit-hashの別名なのですが、少し役割が異なります。

HEADは現在操作中のcommitのcommit-hashを指していますが、branch(ブランチ)はcommitツリーが分岐するタイミングで分岐した枝のcommitの変化を追跡してくれるポインタのような役割です。

文章ではわかりづらいので図にしました。

背景色が緑のcommitの変化を追跡するcommitを追跡するポインタのようなものがbranchです。

 

branch(ブランチ)の作り方の説明の前に、masterブランチについて説明します。

 

materブランチ

masterブランチはrepository作成時に自動で作成されるブランチです。

以下は、第5回で作成したcommitの1つですが、commit-hashの右に(master)とありますね。

この「master」がブランチの名前です。

| * commit b64a4ea0401eefa42c3f3e530123c6edeb6fe80e (master)
|/  Author: snow <snow@abc.com>
|   Date:   Thu Mar 19 21:55:12 2020 +0900
|
|       file-C add

では、HEADの場合と同様に「b64a4ea0401eefa42c3f3e530123c6edeb6fe80e」の別名が「master」となっていることを、前回も利用した「git cat-file」コマンドで確認してみましょう。

 

まずは、commit-hashを指定して、確認してみましょう。

/c/Git/git-test ((ec63e48...))
$ git cat-file -p b64a4ea0401eefa42c3f3e530123c6edeb6fe80e
tree f91906459447b856dff968c24bcf859a261baa63
parent 3e8a749b6a72756998f920a0c6786902458c3341
author snow <snow@abc.com> 1584622512 +0900
committer snow <snow@abc.com> 1584622512 +0900

file-C add

 

では次にmasterを指定して、確認してみましょう。

/c/Git/git-test ((ec63e48...))
$ git cat-file -p master
tree f91906459447b856dff968c24bcf859a261baa63
parent 3e8a749b6a72756998f920a0c6786902458c3341
author snow <snow@abc.com> 1584622512 +0900
committer snow <snow@abc.com> 1584622512 +0900

file-C add

commitの中身は全く同じですね。

branchもHEADど同様にcommit-hashを指していることがわかりました。

 

gitコマンド以外でも確認してみましょう。

現在のmasterがどのcommit-hashを指しているかは「.git」フォルダのmasterファイルでも確認することができます。

/c/Git/git-test ((ec63e48...))
$ cd .git/refs/heads

/c/Git/git-test/.git/refs/heads (GIT_DIR!)
$ ls
master

/c/Git/git-test/.git/refs/heads (GIT_DIR!)
$ cat master
b64a4ea0401eefa42c3f3e530123c6edeb6fe80e

HEADファイルの中身が「b64a4ea0401eefa42c3f3e530123c6edeb6fe80e」となっていまね。

gitではこのmasterファイルにmasterブランチが追跡しているcommitがわかるように管理されているのです。

 

では、実際にbranchを作成して動きを確認してみましょう。

branch(ブランチ)を作成してみよう

新しくrepositoryを作成したほうがわかりやすいので、別のrepositoryで確認していきます。

作成するcommitツリーのイメージは先ほど例に挙げたこれにしましょう。

では、gitコマンドで作成していきましょう

前準備として1つ目のcommitを作成するまで

まず、repositoryを作成します。

/c/Git
$ mkdir git-test2

/c/Git
$ cd git-test2

/c/Git/git-test2
$ git init
Initialized empty Git repository in C:/Git/git-test2/.git/

/c/Git/git-test2 (master)

gitのコンソール画面でプロンプトに(master)と表示されましたね。

これは、「現在操作中のブランチはmasterですよ」ということです。

ただし、まだ1つもcommitを作成していないので、「git log」コマンドを実行すると以下のような結果になります。

/c/Git/git-test2 (master)
$ git log
fatal: your current branch 'master' does not have any commits yet

 

1つcommitを作成します。

/c/Git/git-test2 (master)
$ echo 'aaa' > file-A

/c/Git/git-test2 (master)
$ git add .

/c/Git/git-test2 (master)
$ git commit -m 'file-A add'
[master (root-commit) a1b1b72] file-A add
 1 file changed, 1 insertion(+)
 create mode 100644 file-A

 

git logコマンドで確認してみましょう

/c/Git/git-test2 (master)
$ git log
commit a1b1b72c7b558450c36556014eaa45ad505a1656 (HEAD -> master)
Author: snow <snow@abc.com>
Date:   Fri Mar 20 14:34:58 2020 +0900

    file-A add

作成したcommit(a1b1b72c7b558450c36556014eaa45ad505a1656)に「HEAD」と「master」が表示されていますね。

 

続いて、HEADファイルの中身を確認すると、

/c/Git/git-test2 (master)
$ cd .git

/c/Git/git-test2/.git (GIT_DIR!)
$ ls
COMMIT_EDITMSG  config  description  HEAD  hooks/  index  info/  logs/  objects/  refs/

/c/Git/git-test2/.git (GIT_DIR!)
$ cat HEAD
ref: refs/heads/master

commit-hash(a1b1b72c7b558450c36556014eaa45ad505a1656)ではなく、「refs/heads/master」となっています。

これは、HEADがmasterブランチを指しているということです。

画面に表示された形式も「HEAD -> master」となっているので視覚的にもわかりやすいですね。

そしてmasterブランチはcommit-hash(a1b1b72c7b558450c36556014eaa45ad505a1656)を指しているはずです。

「refs/heads/master」を確認すると

/c/Git/git-test2/.git (GIT_DIR!)
$ cat refs/heads/master
a1b1b72c7b558450c36556014eaa45ad505a1656

予想通り「a1b1b72c7b558450c36556014eaa45ad505a1656」でしたね。

参照する関係でいうとこういう感じです。

HEAD -> master -> a1b1b72c7b558450c36556014eaa45ad505a1656

branchを作成する

brachを作成するgitコマンドは「git branch」です。

branch名を「branch-A」として作成してみます。

/c/Git/git-test2 (master)
$ git branch branch-A

branchは正常に作成されると特にメッセージは表示されません。

branchの一覧を表示するgitコマンドで確認してみましょう。

/c/Git/git-test2 (master)
$ git branch
  branch-A
* master

branch-Aとmasterが表示されました。

そして、masterの左に「*」とありますが、これは現在操作中のbranchに表示されます。

 

git logコマンドでも確認してみましょう。

/c/Git/git-test2 (master)
$ git log
commit a1b1b72c7b558450c36556014eaa45ad505a1656 (HEAD -> master, branch-A)
Author: snow <snow@abc.com>
Date:   Fri Mar 20 14:34:58 2020 +0900

    file-A add

branch-Aとmasterが同じcommit-hashを指していますね。

 

では、操作するcommitを切り替えてみましょう。

gitコマンドは「git checkout」です。

commitをrepositoryから取得する際に利用したコマンドですね。

/c/Git/git-test2 (master)
$ git checkout branch-A
Switched to branch 'branch-A'

/c/Git/git-test2 (branch-A)
$ git branch
* branch-A
  master

プロンプトに(branch-A)と表示されました。

また、git branchコマンドで確認した場合も「*」がbranch-Aに表示されています。

commitが1つの状態でmasterとbranch-Aが同じcommitを指しているので、プロンプトの表示が変更された程度に感じますが、HEADが指すbranchがmasterからbranch-Aに変更されています。

/c/Git/git-test2 (branch-A)
$ cat .git/HEAD
ref: refs/heads/branch-A

.gitフォルダのHEADファイルはbranch-Aを指していますね。

利用したgitコマンド

git checkout [ブランチ名]

ポイント

git checkoutはworking-directoryとstageing-areaにファイルを展開するだけでなく、HEADが指すbranchを切り替えるという操作も行っています。

作成したbranchでcommitを作成する

branch-Aをcheckoutした状態でもう1つcommitを作成てみましょう。

/c/Git/git-test2 (branch-A)
$ echo 'bbb' > file-B

/c/Git/git-test2 (branch-A)
$ git add .

/c/Git/git-test2 (branch-A)
$ git commit -m 'file-B add'
[branch-A 30b11bb] file-B add
 1 file changed, 1 insertion(+)
 create mode 100644 file-B

 

続いて、git logでcommitの状態を確認しましょう。

/c/Git/git-test2 (branch-A)
$ git log --all --graph
* commit 30b11bb11c1dd94416ed8c031c7be7bca9206eab (HEAD -> branch-A)
| Author: snow <snow@abc.com>
| Date:   Fri Mar 20 16:12:28 2020 +0900
|
|     file-B add
|
* commit a1b1b72c7b558450c36556014eaa45ad505a1656 (master)
  Author: snow <snow@abc.com>
  Date:   Fri Mar 20 14:34:58 2020 +0900

      file-A add

HEADがbranch-Aを指しています。

また、HEADとbranch-Aが作成したcommit(30b11bb11c1dd94416ed8c031c7be7bca9206eab)を指していることからも更新したcommitを追跡してくれていることがわかりましたね。

masterブランチをcheckoutする

1つ目のcommitを取得してみたいと思います。

第5回ではbranchを使用しないで操作しましたので、commitのcommit-hashを指定していましたが、masterを指定してgit checkoutコマンドを実行してみましょう。

git logコマンドでcommitの状態を確認します。

/c/Git/git-test2 (branch-A)
$ git log --all --graph
* commit 30b11bb11c1dd94416ed8c031c7be7bca9206eab (HEAD -> branch-A)
| Author: snow <snow@abc.com>
| Date:   Fri Mar 20 16:12:28 2020 +0900
|
|     file-B add
|
* commit a1b1b72c7b558450c36556014eaa45ad505a1656 (master)
  Author: snow <snow@abc.com>
  Date:   Fri Mar 20 14:34:58 2020 +0900

      file-A add

 

それでは、git checkoutコマンドを実行しましょう。

/c/Git/git-test2 (branch-A)
$ git checkout master
Switched to branch 'master'

/c/Git/git-test2 (master)
$ ls
file-A

/c/Git/git-test2 (master)
$ git ls-files
file-A

/c/Git/git-test2 (master)
$ git branch
  branch-A
* master

/c/Git/git-test2 (master)
$ git log --all --graph
* commit 30b11bb11c1dd94416ed8c031c7be7bca9206eab (branch-A)
| Author: snow <snows@abc.com>
| Date:   Fri Mar 20 16:12:28 2020 +0900
|
|     file-B add
|
* commit a1b1b72c7b558450c36556014eaa45ad505a1656 (HEAD -> master)
  Author: snow <snow@abc.com>
  Date:   Fri Mar 20 14:34:58 2020 +0900

      file-A add

git checkout後にworking-directoryとstaging-areaの内容が1つ目のcommitに置き換わりました。

git branchコマンド操作中のbranchがmasterに変更されました。

git logコマンドでHEADが指すbranchがmasterに切り替わりました。

まとめ

今回はbranchとHEADがcommit-hashを示すポインタの役割をしていることを学習しました。

このことが理解できていないと、Gitの運用フローなどを考える場合にbranchをどう作成するかなどを正しく考えることも難しくなってきますので、是非理解できるまで学習してみてください。

私もgitについて今ひとつ理解できていないと感じることあったのですが、その一つがbranchでした。

理解できるまでは、branchを削除すると管理対象のファイルまで削除されてしまうと思っていたこともあったのですが、実際にはbranchはただのcommit-hashのポインタですのでファイルが削除されることなんてないんですよね。

次回は、共同開発を想定して2つのgit環境でcommitの共有を行ってみたいと思います。

git-master

Posted by snow