アリスがチャレンジなコードを書く時、git branchをちゃんと理解したい!



アリスとボブのGitシリーズが本になりました!
  アリスとボブのGit入門レッスン


アリスは迷っていた。現状のshowメソッドは固定されたメッセージしか出力しないが、理想的にはユーザーの条件によって変化させたいと。
しかし、その機能を実装するためには結構な大改修になってしまう。果たして今の自分の技術でちゃんと完了させることが出来るだろうか?この機能追加をやるべきか、このままにするか...。

  • アリスはこの修正が失敗に終わった時のことを考えて、ボブに連絡しておくことにした。「失敗したらごめんね。」と。(なんて無責任なアリス...。)
  • 連絡を受けたボブは、アリスの機能追加には大賛成。ボブ:「ただし、新しいブランチを追加して、そこで作業くれ。」と。アリス:「ブランチ???」
  • アリスはブランチを理解できていないが、とりあえず、ボブに説明された手順をそのままやってみることにした。アリス:「習うより、慣れろだわ」
alice/project$ git branch challenge #ブランチchallengeを作成
alice/project$ git branch           #ブランチの一覧を確認
   challenge
 * master
  • ブランチの一覧を確認すると、今作成した「challenge」と、「master」が確認できる。
  • ブランチ「master」はgitがデフォルトで作成するブランチ。最初はみんなmasterから始まる。
  • ブランチ名の先頭に「*」マークが付いているので、現在作業中のブランチは「master」ということになる。
alice/project$ git checkout challenge #ブランチを「challenge」に変更
Switched to branch "challenge"
alice/project$ git branch             #ブランチの一覧を確認
 * challenge
   master
  • ブランチの一覧を確認すると、ブランチは「challenge」に変更された。

challengeブランチでのアリスの修正

  • ブランチが切り替わったことを確認して、アリスはコードの修正を始めた。
# About git
Class WhatIsGit
  def show
    puts 'Do you understand the basis of git? [yes/no]'
    input = gets.chomp.downcase
    case input
    when 'yes', 'y'
      puts 'Git is easy.'
    else
      puts 'Git is difficult...'
    end
  end
  
  def about(lang = 'en')
    puts "http://#{lang}.wikipedia.org/wiki/Git"
  end
end
  • ひとまず完成したので、コミット。
alice/project$ git commit -a -m 'challenge commit 1'
Created commit 472085b: challenge commit 1
 1 files changed, 8 insertions(+), 1 deletions(-)
  • アリスがじっくり考え直すと、もっと簡潔に書くべきと感じたので、さらに修正。
# About git
Class WhatIsGit
  def show
    case input('Do you understand the basis of git? [yes/no]')
    when 'yes', 'y'
      puts 'Git is easy.'
    else
      puts 'Git is difficult...'
    end
  end
  
  def about(lang = 'en')
    puts "http://#{lang}.wikipedia.org/wiki/Git"
  end
  
  private
    def input(message)
      puts message
      gets.chomp.downcase
    end
end
  • これで良し、コミット。
alice/project$ git commit -a -m 'challenge commit 2'
Created commit 31ce330: challenge commit 2
 1 files changed, 7 insertions(+), 3 deletions(-)

ブランチを切り替えてみる

  • アリスは、challengeブランチでの作業が一段落したので、masterブランチに切り替えてみた。
alice/project$ git checkout master
Switched to branch "master"
  • すると、what_is_git.rbの内容が、以前の状態に戻ってしまった...。
  • アリスは一瞬焦ったが、もう一度challengeブランチに切り替えてみると、さっき書いたコードが復元された!
alice/project$ git checkout challenge
Switched to branch "challenge"
  • つまりgitに管理されたディレクトリにおいては、what_is_git.rbというファイルは、編集作業をするための一時的なコピーでしかないのだ。
  • gitがコミット時点のスナップショットを脈々と保存している.gitフォルダの履歴がすべてであり、
  • 今やその履歴は、masterと、そこから分岐するchallengeという二つの系統に分かれているのだ。
イメージ図
過去 <----------------------> 未来

           o--o <-- challengeブランチ
          /
...o--o--o <------- masterブランチ

oはコミット、またはマージを表す

ボブの修正

  • ちょうどその頃、ボブは重大なバグを見つけていた。ボブ:「クラス宣言のclassが大文字で始まっていたとは...」
bob/myrepo$ git diff
 diff --git a/what_is_git.rb b/what_is_git.rb
 index ac3f723..bec373b 100644
 --- a/what_is_git.rb
 +++ b/what_is_git.rb
 @@ -1,5 +1,5 @@
  # About git
 -Class WhatIsGit
 +class WhatIsGit
    def show
      puts 'Git is difficult, if you understand the basis.'
    end
bob/myrepo$ git commit -a -m 'class downcase'
Created commit 87f0ad7: class downcase
 1 files changed, 1 insertions(+), 1 deletions(-)
bob/myrepo$ git push shared master
Counting objects: 5, done.
Compressing objects: 100% (2/2), done.
Writing objects: 100% (3/3), 281 bytes, done.
Total 3 (delta 1), reused 0 (delta 0)
Unpacking objects: 100% (3/3), done.
To /Users/Shared/project.git
   16150ca..87f0ad7  master -> master
  • すぐさま修正して、コミット、プッシュ。

challengeブランチでのアリスの迷走

  • 一方、アリスはchallengeブランチのコードをテストしていた。
alice/project$ git checkout challenge
Switched to branch "challenge"
alice/project$ irb
 >> require 'what_is_git'
 SyntaxError: ./what_is_git.rb:21: syntax error, unexpected kEND, expecting $end
 	from /Library/Ruby/Site/1.8/rubygems/custom_require.rb:27:in `gem_original_require'
 	from /Library/Ruby/Site/1.8/rubygems/custom_require.rb:27:in `require'
 	from (irb):1
 >> 
  • しかし、requireからして実行できない...。やはり私(アリス)には無理だったのか?アリスは落胆していた。
  • その時、ボブから連絡があった。重大なバグを修正したと。これを取り込まないとすべてのテストがエラーになると。
  • アリスは早速、共有リポジトリからプルした。なんと、クラス定義の先頭が大文字になっていたとは。ダメだこりゃ。
alice/project$ git checkout challenge
Switched to branch "challenge"
alice/project$ git pull shared master
From /Users/Shared/project
 * branch            master     -> FETCH_HEAD
Auto-merged what_is_git.rb
Merge made by recursive.
 what_is_git.rb |    2 +-
 1 files changed, 1 insertions(+), 1 deletions(-)
alice/project$ git diff HEAD^
 diff --git a/what_is_git.rb b/what_is_git.rb
 index 8fe919a..559d6cb 100644
 --- a/what_is_git.rb
 +++ b/what_is_git.rb
 @@ -1,5 +1,5 @@
  # About git
 -Class WhatIsGit
 +class WhatIsGit
    def show
      case input('Do you understand the basis of git? [yes/no]')
      when 'yes', 'y'
  • すぐさま修正を取り込んで、irbでテストしてみた。
alice/project$ irb
    
=> true
=> #
Do you understand the basis of git? [yes/no] y Git is easy. => nil
Do you understand the basis of git? [yes/no] n Git is difficult... => nil
http://en.wikipedia.org/wiki/Git => nil
  • うまく動いているようだ!さすがボブ、ありがとう!
  • アリスはmasterブランチに戻って、challengeブランチをマージした。
alice/project$ git checkout master
Switched to branch "master"
alice/project$ git merge challenge
Updating 16150ca..2d04f6c
Fast forward
 what_is_git.rb |   15 +++++++++++++--
 1 files changed, 13 insertions(+), 2 deletions(-)
alice/project$ git show
 commit 2d04f6c21384ece8e0a14c5c18b179aa43b90f7f
 Merge: 1689263... 87f0ad7...
 Author: zarigani 
 Date:   Fri Sep 12 10:59:02 2008 +0900

     Merge branch 'master' of /Users/Shared/project into challenge

 diff --cc what_is_git.rb
 index 8fe919a,bec373b..559d6cb
 --- a/what_is_git.rb
 +++ b/what_is_git.rb
 @@@ -1,12 -1,7 +1,12 @@@
   # About git
 - Class WhatIsGit
 + class WhatIsGit
     def show
  -    puts 'Git is difficult, if you understand the basis.'
  +    case input('Do you understand the basis of git? [yes/no]')
  +    when 'yes', 'y'
  +      puts 'Git is easy.'
  +    else
  +      puts 'Git is difficult...'
  +    end
     end
    
     def about(lang = 'en')
  • 最後にアリスは、共有gitリポジトリにプッシュしておいた。良い仕事ができた。
alice/project$ git push shared master
Counting objects: 13, done.
Compressing objects: 100% (6/6), done.
Writing objects: 100% (9/9), 1.07 KiB, done.
Total 9 (delta 2), reused 0 (delta 0)
Unpacking objects: 100% (9/9), done.
To /Users/Shared/project.git
   d0688af..7c03a98  master -> master

所感

  • コミットを繰り返すと、その時点のファイルの状態を記録したスナップショットが順に連なって保存されていく。(Time Machineや連凧をイメージ)
  • スナップショットの連続した集合をブランチと呼んでいる。
  • ブランチは途中で分岐させることができるし、再び合流させることもできる。
  • ブランチが分岐すると、分岐先のブランチは新たなブランチとして区別される。
リモートブランチについて
  • ボブは元々アリスのプロジェクトをgit cloneでコピーしている。
  • すると、ボブのプロジェクトにはアリスのプロジェクトを追跡するためのキャッシュが準備される。
  • そのキャッシュにはつまり、アリスのプロジェクトのブランチが保存されることになるので、これをリモートブランチと呼んでいる。(ブランチの一種なのだ)
  • リモートブランチは、-rオプションを指定してgit branch -rで確認できる。
bob/myrepo$ git branch -r
  origin/master
  shared/master
  • origin/masterリモートブランチは、git cloneによってgitが自動的に用意してくれた。
  • shared/masterリモートブランチは、git remote add shared /Users/Shared/project.gitによって、パスにエイリアス名を設定した時に用意されるようだ。
  • git pullを使ってしまうとその存在は見えにくいが、git fetchとgit mergeでその存在を感じられる。
bob/myrepo$ git fetch shared        #共有gitリポジトリと同期させて、キャッシュに保存する
bob/myrepo$ git merge shared/master #キャッシュ内のshared/masterリモートブランチとマージする
どちらのブランチでgit pullするべきか?
  • 上記の例では、作業中のchallengeブランチで、git pull shared masterを実行してしまった。
イメージ図
過去 <----------------------> 未来


...1--2--3--6         <--- 共有gitリポジトリのmasterブランチ(6はボブの修正Class -> class)

           4--5--M6   <--- アリスのchallengeブランチ
          /       \
...1--2--3---------M7 <--- アリスのmasterブランチ


M6: アリスのchallengeブランチでgit pull shared master
M7: アリスのmasterブランチでgit merge challenge

数字は、コミットを表す
数字の先頭にMが付く場合はマージを表す
数字の順にコミットまたはマージされている
alice/project$ git log
commit d4d076320c1fe7a3f9bee447148aefe28530289a
Merge: 6912d3f... b48a40e...
Author: zarigani 
Date:   Fri Sep 12 15:29:44 2008 +0900

    Merge branch 'master' into challenge

commit 6912d3ff8f82d869240daa05db89655d271ded7c
Author: zarigani 
Date:   Fri Sep 12 15:26:11 2008 +0900

    challenge commit 2

commit cd12c7fff1e9320042612409a1833c38de79d0b9
Author: zarigani 
Date:   Fri Sep 12 15:25:42 2008 +0900

    challenge commit 1

commit b48a40ed3c38dfce1e4c33f733c85733cedac43d
Author: bob 
Date:   Fri Sep 12 15:21:31 2008 +0900

    class downcase

commit 16150ca79dc873b972edb6570bf750a99ddf80fe
Author: bob 
Date:   Wed Sep 10 16:36:59 2008 +0900

    changed to quotation marks
  • masterブランチでgit pull shared masterを実行する手順も考えられる。
  • 最終的な結果は同じ。ただし、マージの際に生成されるメッセージが若干異なる。
イメージ図
過去 <----------------------> 未来


...1--2--3--6         <--- 共有gitリポジトリのmasterブランチ(6はボブの修正Class -> class)


           4--5--M7   <--- アリスのchallengeブランチ
          /     / \
...1--2--3-----6---M8 <--- アリスのmasterブランチ


 6: アリスのmasterブランチでgit pull shared master(コミットが追加されただけなのでマージは発生しない)
M7: アリスのchallengeブランチでgit merge master
M8: アリスのmasterブランチでgit merge challenge

数字は、コミットを表す
数字の先頭にMが付く場合はマージを表す
数字の順にコミットまたはマージされている
alice/project$ git log
commit 7c03a98a2225ff55a00341b9a146263bc2a8131b
Merge: 4aac071... d0688af...
Author: zarigani 
Date:   Fri Sep 12 21:21:04 2008 +0900

    Merge branch 'master' of /Users/Shared/project into challenge

commit d0688af70c18996a57de04055c1011162f070427
Author: bob 
Date:   Fri Sep 12 21:07:13 2008 +0900

    class downcase

commit 4aac071139e4407b1ae731408ffe61138dfc9aaa
Author: zarigani 
Date:   Fri Sep 12 20:53:07 2008 +0900

    challenge commit 2

commit 872622077a0bb0af50462044a0143207e38c56c7
Author: zarigani 
Date:   Fri Sep 12 20:52:23 2008 +0900

    challenge commit 1

commit 16150ca79dc873b972edb6570bf750a99ddf80fe
Author: bob 
Date:   Wed Sep 10 16:36:59 2008 +0900

    changed to quotation marks
推奨されるのは、どちらの手順なのだろう?

もう一度、日本語訳されたマニュアルを読み直す

全4回、実際にアリスとボブになりきって、gitを操作して、それをひたすら記録してみた。 その後,以下のマニュアルを読むと、以前と違って知りたいことが頭にどんどん入ってくる!実際に操作してみることで、ある程度gitの理解が深まったのが良いのかもしれない。自分は身体を使わないと理解できないタイプなのかもしれない...。読んでるだけでは眠くなってしまって。