アリスとボブのコラボレーション、gitをちゃんと理解したい!



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


前回からの続き。ひたすらアリスとボブの操作の記録。

ボブがアリスのプロジェクトを手伝う

  • アリスは自分のプロジェクトが全く進んでいないことに気付いて、愕然とした...。そこで同僚のボブにも手伝ってもらうことにした。
  • 「ボブ、お願い!」アリスはボブに頼んでおきながら、今までの作業に相当疲れたので、すぐに休憩に出てしまった。
  • アリスとボブは同じマシン上にホームディレクトリを持っている。ボブは早速以下の操作をした。(ボブは優しい。)
  • まずはgitに自分の名前とメールアドレスを設定
bob$ git config --global user.name "bob"
bob$ git config --global user.email bob@example.com
  • アリスに教えてもらったパスを指定して、アリスのプロジェクトをコピーした。
bob$ git clone /Users/alice/project myrepo
Initialized empty Git repository in /Users/Guest/myrepo/.git/
bob$ cd myrepo
bob/myrepo$ git log
commit 9e0c7cdc0de938ede09432849a952e8b1e4c6f9d
Author: alice 
Date:   Mon Sep 8 11:20:59 2008 +0900

    Revert "show message changed"
    
    This reverts commit ea192b7aa11fc5ebae988d1f063705a629d8acff.

commit ea192b7aa11fc5ebae988d1f063705a629d8acff
Author: alice 
Date:   Fri Sep 5 18:19:24 2008 +0900

    show message changed

commit cb83d4fae763b66f647f217cb3e0373ea065fe94
Author: alice 
Date:   Fri Sep 5 17:37:22 2008 +0900

    first commit
  • この作業でボブは、アリスのプロジェクトをボブのホーム直下のmyrepoフォルダにコピーしたことになる。
  • /Users/alice/projectと/Users/bob/myrepoは、どちらも同等なgit管理されたフォルダになる。
    • git logで確認すると、今までアリスが行った修正もちゃんと残っている。
    • git config -lで確認すると、その設定に若干の違いはある。
  • ボブは早速what_is_git.rbを以下のように修正した。(aboutメソッドが出力するURLをウィキペディア英語版にした。)
# About git
Class WhatIsGit
  def show
    puts 'Git is difficult...'
  end
  
  def about
    puts 'http://en.wikipedia.org/wiki/Git'
  end
end
  • 変更を確認し、ファイルに保存後、ボブはコミットした。
bob/myrepo$ git commit -a

  1 about URL changed
  2
  3 # Please enter the commit message for your changes. Lines starting
  4 # with '#' will be ignored, and an empty message aborts the commit.
  5 # On branch master
  6 # Changes to be committed:
  7 #   (use "git reset HEAD ..." to unstage)
  8 #
  9 #       modified:   what_is_git.rb
 10 #
~
~
".git/COMMIT_EDITMSG" 10 lines, 281 characters written
Created commit 941e293: about URL changed
 1 files changed, 1 insertions(+), 1 deletions(-)
  • ボブはコミットの履歴を確認した。
bob/myrepo$ git log
commit 941e2939cb5645f5f352d414b9bdb86108c006af
Author: bob 
Date:   Mon Sep 8 12:15:19 2008 +0900

    about URL changed

commit 9e0c7cdc0de938ede09432849a952e8b1e4c6f9d
Author: alice 
Date:   Mon Sep 8 11:20:59 2008 +0900

    Revert "show message changed"
    
    This reverts commit ea192b7aa11fc5ebae988d1f063705a629d8acff.

commit ea192b7aa11fc5ebae988d1f063705a629d8acff
Author: alice 
Date:   Fri Sep 5 18:19:24 2008 +0900

    show message changed

commit cb83d4fae763b66f647f217cb3e0373ea065fe94
Author: alice 
Date:   Fri Sep 5 17:37:22 2008 +0900

    first commit
bebe-MacBook:myrepo Guest$ 

アリスがボブの変更をプロジェクトに取り込む

  • アリスが休憩から戻ってくると、ボブから例のプロジェクトを修正したと伝えられた。相変わらずボブは仕事が早い。頼りになる。
  • 早速アリスはボブに教えてもらったパスを指定して、プロジェクトの変更を取り込んだ。
alice/project$ git pull /Users/Guest/myrepo master
remote: Counting objects: 5, done.
remote: Compressing objects: 100% (2/2), done.
remote: Total 3 (delta 1), reused 0 (delta 0)
Unpacking objects: 100% (3/3), done.
From /Users/bob/myrepo
 * branch            master     -> FETCH_HEAD
Updating 9e0c7cd..941e293
Fast forward
 what_is_git.rb |    2 +-
 1 files changed, 1 insertions(+), 1 deletions(-)
  • この操作でボブの変更は、アリスのプロジェクトに取り込まれたようだ。
  • アリスはコミットの履歴を確認してみた。ボブがabout URL changedをコミットしてくれたようだ。
alice/project$ git log
commit 941e2939cb5645f5f352d414b9bdb86108c006af
Author: bob 
Date:   Mon Sep 8 12:15:19 2008 +0900

    about URL changed

commit 9e0c7cdc0de938ede09432849a952e8b1e4c6f9d
Author: alice 
Date:   Mon Sep 8 11:20:59 2008 +0900

    Revert "show message changed"
    
    This reverts commit ea192b7aa11fc5ebae988d1f063705a629d8acff.

commit ea192b7aa11fc5ebae988d1f063705a629d8acff
Author: alice 
Date:   Fri Sep 5 18:19:24 2008 +0900

    show message changed

commit cb83d4fae763b66f647f217cb3e0373ea065fe94
Author: alice 
Date:   Fri Sep 5 17:37:22 2008 +0900

    first commit
  • アリスはさらに変更点を確認してみた。「なるほど、aboutの出力をウィキペディア英語版のURLにしてくれたのね。さすがボブ。」
alice/project$ git diff 9e0c7cdc0de938e..941e2939cb5645f5
diff --git a/what_is_git.rb b/what_is_git.rb
index b126408..12f415a 100644
 --- a/what_is_git.rb
 +++ b/what_is_git.rb
 @@ -5,6 +5,6 @@ Class WhatIsGit
    end
   
    def about
 -    puts 'http://ja.wikipedia.org/wiki/Git'
 +    puts 'http://en.wikipedia.org/wiki/Git'
    end
  end
  • たまたま、アリスの後ろを通りかかったボブは、コミットの詳細を見るならもっと便利なコマンドがあることを教えてくれた。
alice/project$ git show 941e2939cb5645f5
# または...
alice/project$ git show HEAD
 commit 941e2939cb5645f5f352d414b9bdb86108c006af
 Author: bob 
 Date:   Mon Sep 8 12:15:19 2008 +0900

     about URL changed

 diff --git a/what_is_git.rb b/what_is_git.rb
 index b126408..12f415a 100644
 --- a/what_is_git.rb
 +++ b/what_is_git.rb
 @@ -5,6 +5,6 @@ Class WhatIsGit
    end
   
    def about
 -    puts 'http://ja.wikipedia.org/wiki/Git'
 +    puts 'http://en.wikipedia.org/wiki/Git'
    end
  end
  • git show HEADで最新のコミットの詳細を表示してくれる。(diffの部分は直前のコミットとの差分)
  • ちなみに、以下のような便利な引数指定ができる。
alice/project$ git show HEAD^   # HEAD の1つ前を表示
alice/project$ git show HEAD^^  # HEAD の2つ前を表示
alice/project$ git show HEAD^^^ # HEAD の3つ前を表示
...
alice/project$ git show HEAD~1  # HEAD の1つ前を表示
alice/project$ git show HEAD~2  # HEAD の2つ前を表示
alice/project$ git show HEAD~3  # HEAD の3つ前を表示
...

アリスとボブが同時に作業する

アリスの修正
  • ボブの素早い仕事を見て、アリスも俄然やる気になった。早速、前回の無駄な打ち消しを修正した。
# About git
Class WhatIsGit
  def show
    puts 'Git is easy, if you understand the basis'
  end
  
  def about
    puts 'http://en.wikipedia.org/wiki/Git'
  end
end
  • 変更を確認し、ファイルに保存後、アリスはコミットした。
alice/project$ git commit -a

  1 show message changed2
  2 
  3 # Please enter the commit message for your changes. Lines starting
  4 # with '#' will be ignored, and an empty message aborts the commit.
  5 # On branch master
  6 # Changes to be committed:
  7 #   (use "git reset HEAD ..." to unstage)
  8 #       
  9 #       modified:   what_is_git.rb
 10 #
~
~
".git/COMMIT_EDITMSG" 10L, 285C written
Created commit 4117bcb: show message changed2
 1 files changed, 1 insertions(+), 1 deletions(-)
  • アリスはコミットの履歴を確認した。
alice/project$ git log
commit 3ce0713a5cf938397f6fe4695b11857d43b63314
Author: alice 
Date:   Mon Sep 8 14:50:53 2008 +0900

    show message changed2

commit 941e2939cb5645f5f352d414b9bdb86108c006af
Author: bob 
Date:   Mon Sep 8 12:15:19 2008 +0900

    about URL changed

commit 9e0c7cdc0de938ede09432849a952e8b1e4c6f9d
Author: alice 
Date:   Mon Sep 8 11:20:59 2008 +0900

    Revert "show message changed"
    
    This reverts commit ea192b7aa11fc5ebae988d1f063705a629d8acff.

commit ea192b7aa11fc5ebae988d1f063705a629d8acff
Author: alice 
Date:   Fri Sep 5 18:19:24 2008 +0900

    show message changed

commit cb83d4fae763b66f647f217cb3e0373ea065fe94
Author: alice 
Date:   Fri Sep 5 17:37:22 2008 +0900

    first commit
gitが自動的に取り込んでくれる場合
  • アリスは無事コミットが完了してホッとしたのも束の間、ボブから「さらに修正が入ったのでプロジェクトに取り込んで欲しい」と伝えられた。
  • アリスはすぐにgit pullしようと考えたが、それに不安を覚えた。
    • ボブは今、私(アリス)が行った修正を知っているのだろうか?(たぶん知らない)取り込む前にボブの修正を確認したい。
  • このような状況で、アリスは以下の手順でボブの変更をプロジェクトに取り込む前に確認することができる。
  • コマンドを簡潔に表現するために、アリスはボブの作業ディレクトリにエイリアス名を付けた。(bob = /Users/bob/myrepo)
alice/project$ git remote add bob /Users/bob/myrepo
  • git fetchによって、ボブの変更を、アリスのプロジェクトのキャッシュ(一時的な保存領域)に取り込む。
alice/project$ git fetch bob
remote: Counting objects: 5, done.
remote: Compressing objects: 100% (2/2), done.
remote: Total 3 (delta 0), remote: reused 0 (delta 0)
Unpacking objects: 100% (3/3), done.
From /Users/Guest/myrepo
 * [new branch]      master     -> bob/master
  • その後アリスは、git logを-pオプション付きで実行して、ボブの変更箇所を確認できた。
    • -pオプションを指定することで、コミットの履歴を変更箇所(直前のコミットとのdiff)も含めて確認できる。
alice/project$ git log -p master..bob/master
 commit 12508a3561d41f3393b93c598026ad5057735f97
 Author: bob 
 Date:   Mon Sep 8 14:55:35 2008 +0900

     about URL changed2

 diff --git a/what_is_git.rb b/what_is_git.rb
 index 12f415a..f5760c9 100644
 --- a/what_is_git.rb
 +++ b/what_is_git.rb
 @@ -4,7 +4,7 @@ Class WhatIsGit
      puts 'Git is difficult...'
    end
   
 -  def about
 -    puts 'http://en.wikipedia.org/wiki/Git'
 +  def about(lang = 'en')
 +    puts 'http://#{lang}.wikipedia.org/wiki/Git'
    end
  end
  • ボブはaboutメソッドに引数を設定して、多言語対応にしてくれていたのだった。
  • しかも、引数が無い場合はウィキペディア英語版のURLが出力されるデフォルト。素晴らしい、ボブ!
  • アリスは安心して、ボブの修正を取り込んだ。
alice/project$ git merge bob/master
Auto-merged what_is_git.rb
Merge made by recursive.
 what_is_git.rb |    4 ++--
 1 files changed, 2 insertions(+), 2 deletions(-)
  • アリスの修正とボブの修正は、別々の箇所でコードが対立しないので、そのような場合gitは自動的に取り込んでくれる。
  • これでアリスのプロジェクトに、ボブの修正は反映された。
二つの意見が対立する場合
  • アリスがgitに四苦八苦しているうちに、ボブは更なる修正をしていた。
  • アリスは再び修正を取り込むようにボブから言われた。
  • アリスは今回エイリアス名は使わずに、ボブの作業ディレクトリのパスを指定して、git fetchしてみた。
    • ボブの変更は、アリスのプロジェクトのFETCH_HEADというキャッシュに取り込まれたようだ。
alice/project$ git fetch /Users/bob/myrepo
remote: Counting objects: 5, done.
remote: Compressing objects: 100% (2/2), done.
remote: Total 3 (delta 1), reused 0 (delta 0)
Unpacking objects: 100% (3/3), done.
From /Users/Guest/myrepo
 * branch            HEAD       -> FETCH_HEAD
  • git diffで、今のアリスとボブの変更箇所を確認
alice/project$ git diff HEAD..FETCH_HEAD
 diff --git a/what_is_git.rb b/what_is_git.rb
 index 6c645b8..652973c 100644
 --- a/what_is_git.rb
 +++ b/what_is_git.rb
 @@ -1,7 +1,7 @@
  # About git
  Class WhatIsGit
    def show
 -    puts 'Git is easy, if you understand the basis'
 +    puts 'Git is easy!'
    end
   
    def about(lang = 'en')
  • git mergeで取り込んでみると...コンフリクトしていて自動的にマージできないと警告された。なぜだろう?
    1. ボブは'Git is difficult...'時点のアリスのプロジェクトをコピーしている。
    2. その後、アリスは'Git is easy, if you understand the basis'に修正した。
    3. 上記を知らずに、ボブも'Git is easy!'に修正した。
  • よって、アリス'Git is easy, if you understand the basis' vs ボブ'Git is easy!'という、二つの意見が対立していることになる。
alice/project$ git merge FETCH_HEAD
Auto-merged what_is_git.rb
CONFLICT (content): Merge conflict in what_is_git.rb
Automatic merge failed; fix conflicts and then commit the result.
  • この時点でgit diffを実行してみると...以下のように表示された。
alice/project$ git diff
 diff --cc what_is_git.rb
 index 6c645b8,652973c..0000000
 --- a/what_is_git.rb
 +++ b/what_is_git.rb
 @@@ -1,7 -1,7 +1,11 @@@
   # About git
   Class WhatIsGit
     def show
 ++<<<<<<< HEAD:what_is_git.rb
  +    puts 'Git is easy, if you understand the basis'
 ++=======
 +     puts 'Git is easy!'
 ++>>>>>>> FETCH_HEAD:what_is_git.rb
     end
    
     def about(lang = 'en')
  • アリスが、ファイルwhat_is_git.rbを確認してみると...何だか変なコードになっている。大変!
# About git
Class WhatIsGit
  def show
<<<<<<< HEAD:what_is_git.rb
    puts 'Git is easy, if you understand the basis'
=======
    puts 'Git is easy!'
>>>>>>> FETCH_HEAD:what_is_git.rb
  end
  
  def about(lang = 'en')
    puts 'http://#{lang}.wikipedia.org/wiki/Git'
  end
end
  • この状態を解消するためには、自分で「<<<<<<< HEAD:what_is_git.rb」と「>>>>>>> FETCH_HEAD:what_is_git.rb」ブロック内のコードを修正する必要がある。
    • 「=======」を挟んで上下が対立しているコードらしい。
    • どちらかを残しても良いし、全く別のコードに変更してしまっても良いようだ。
  • アリスはボブに状況を説明して、今回は自分のコードを優先させることで了解してもらった。
    • ただし、ピリオド「.」が抜けていることに気付いたので、最後に追加しておいた。
# About git
Class WhatIsGit
  def show
    puts 'Git is easy, if you understand the basis.'
  end
  
  def about(lang = 'en')
    puts 'http://#{lang}.wikipedia.org/wiki/Git'
  end
end
  • 修正したらgit addする。
    • git add .より、地道にgit add ファイル名を指定した方が良いかもしれない。(修正漏れ防止のため)
    • git addすることで、対立しているコードの差分は修正されたものと見なされ、git diffから除外されるようだ。
alice/project$ git add what_is_git.rb
  • その後、git commitを実行すると、コミットのメッセージにはコンフリクトの情報も最初から含まれて表示された。
alice/project$ git commit

  1 Merge /Users/bob/myrepo
  2 
  3 Conflicts:
  4         what_is_git.rb
  5 #
  6 # It looks like you may be committing a MERGE.
  7 # If this is not correct, please remove the file
  8 #       .git/MERGE_HEAD
  9 # and try again.
 10 #
 11 
 12 # Please enter the commit message for your changes. Lines starting
 13 # with '#' will be ignored, and an empty message aborts the commit.
 14 # On branch master
 15 # Changes to be committed:
 16 #   (use "git reset HEAD ..." to unstage)
 17 #
 18 #       modified:   what_is_git.rb
 19 #
~
~
".git/COMMIT_EDITMSG" 19L, 450C written
Created commit 52cfe0d: Merge /Users/bob/myrepo
  • このまま保存して、コミットは完了した。
  • アリスがコミットの履歴を確認すると、以下のように表示された。
    • ボブのコミットgit is easy!と、アリスのコミットMerge /Users/bob/myrepoの二つの履歴が追加されていた。
alice/project$ git log
commit 52cfe0de979425dbf1dcef3d081e06ea40a68965
Merge: 616c5cb... fb7c82f...
Author: alice 
Date:   Mon Sep 8 17:44:48 2008 +0900

    Merge /Users/bob/myrepo
    
    Conflicts:
        what_is_git.rb

commit fb7c82fda542762554792fc9b198428a3e6ae5b3
Author: bob 
Date:   Mon Sep 8 16:11:45 2008 +0900

    git is easy!

commit 616c5cbd40587a31cd20dd8ed9b30d282a77a970
Merge: 3ce0713... 12508a3...
Author: alice 
Date:   Mon Sep 8 15:10:43 2008 +0900

    Merge commit 'bob/master'

commit 12508a3561d41f3393b93c598026ad5057735f97
Author: bob 
Date:   Mon Sep 8 14:55:35 2008 +0900

    about URL changed2

commit 3ce0713a5cf938397f6fe4695b11857d43b63314
Author: alice 
Date:   Mon Sep 8 14:50:53 2008 +0900

    show message changed2

commit 941e2939cb5645f5f352d414b9bdb86108c006af
Author: bob 
Date:   Mon Sep 8 12:15:19 2008 +0900

    about URL changed

...(中略)...
  • 直前のコミットをgit show HEADを確認してみる
    • ボブのコミットと、アリスのコミットをマージした時の様子が読み取れる。
alice/project$ git show HEAD
 commit 52cfe0de979425dbf1dcef3d081e06ea40a68965
 Merge: 616c5cb... fb7c82f...
 Author: alice 
 Date:   Mon Sep 8 17:44:48 2008 +0900

     Merge /Users/bob/myrepo
    
     Conflicts:
         what_is_git.rb

 diff --cc what_is_git.rb
 index 6c645b8,652973c..103a58e
 --- a/what_is_git.rb
 +++ b/what_is_git.rb
 @@@ -1,7 -1,7 +1,7 @@@
   # About git
   Class WhatIsGit
     def show
 -     puts 'Git is easy, if you understand the basis'
  -    puts 'Git is easy!'
 ++    puts 'Git is easy, if you understand the basis.'
     end
    
     def about(lang = 'en')
  • さらに一つ前のコミットを確認してみると、コミットMerge commit 'bob/master'が表示された。
    • このコミットはgit logで表示される順番では、最新の3番目だ。
    • このことから、マージされたコミットMerge /Users/bob/myrepoとgit is easy!は、同じタイミングの一回のコミットとして扱われているようだ。(履歴の表示は分かれているが)
alice/project$ git show HEAD^
 commit 616c5cbd40587a31cd20dd8ed9b30d282a77a970
 Merge: 3ce0713... 12508a3...
 Author: alice 
 Date:   Mon Sep 8 15:10:43 2008 +0900

     Merge commit 'bob/master'

 diff --cc what_is_git.rb
 index 453239c,f5760c9..6c645b8
 --- a/what_is_git.rb
 +++ b/what_is_git.rb
 @@@ -1,10 -1,10 +1,10 @@@
   # About git
   Class WhatIsGit
     def show
  -    puts 'Git is difficult...'
  +    puts 'Git is easy, if you understand the basis'
     end
    
 -   def about
 -     puts 'http://en.wikipedia.org/wiki/Git'
 +   def about(lang = 'en')
 +     puts 'http://#{lang}.wikipedia.org/wiki/Git'
     end

アリスの最新の状態をボブに取り込んでもらう

  • 最後にアリスは、プロジェクトの最新状態をボブにも更新しておいて欲しいと伝えた。
  • ボブは最小の手順で素早く更新した。
bob/myrepo$ git pull
remote: Counting objects: 15, done.
remote: Compressing objects: 100% (6/6), done.
remote: Total 9 (delta 3), reused 0 (delta 0)
Unpacking objects: 100% (9/9), done.
From /Users/bebe/alice/project/
   9e0c7cd..52cfe0d  master     -> origin/master
Updating fb7c82f..52cfe0d
Fast forward
 what_is_git.rb |    2 +-
 1 files changed, 1 insertions(+), 1 deletions(-)
  • 何故、ボブはgit pullのみでOKなのか?
    • ボブはアリスのgitをコピーしており、gitはその時アリスのパスを記憶していたのだ。賢い!
  • git config -lを実行すると以下のように表示された。
bob/myrepo$ git config -l
user.name=bob
user.email=bob@example.com
core.repositoryformatversion=0
core.filemode=true
core.bare=false
core.logallrefupdates=true
core.ignorecase=true
remote.origin.url=/Users/alice/project/.git
remote.origin.fetch=+refs/heads/*:refs/remotes/origin/*
branch.master.remote=origin
branch.master.merge=refs/heads/master
  • remote.origin.url=/Users/alice/project/.gitがアリスのプロジェクトのパスなのかもしれない。
  • git pull完了後、ボブのファイルwhat_is_git.rbも以下の状態に更新された。
# About git
Class WhatIsGit
  def show
    puts 'Git is easy, if you understand the basis.'
  end
  
  def about(lang = 'en')
    puts 'http://#{lang}.wikipedia.org/wiki/Git'
  end
end

所感

合っているかどうか不安だが、アリスとボブの操作を実際にやってみて、今はgitの仕組みを以下のように考えている。(git pushやブランチについてはまた後日。また理解が変わるかもしれない。)

  • git add .とgit commitで自分のホームディレクトリに履歴を保存する。
  • gitリポジトリは、git cloneで任意の場所にコピーすることが出来る。
  • gitリポジトリ同士を同期させるためには、git pull、git fetch、git mergeを利用する。
    • git fetchで外部のgitリポジトリをキャッシュに取り込む。
    • git mergeで合算されてコードが修正される。
      • 変更箇所が重複していなければ、自動的に取り込まれる。
      • コードが対立していれば、対立の状況がファイルに保存される。
    • git pullはgit fetch + git merge。(git commit -aのような感覚だ)
  • git remote add エイリアス名 リポジトリのパスで、外部リポジトリにエイリアス名を設定することが出来る。

Time Machineのスナップショットの集合がコピーされ、変更されたスナップショットの集合を再びキャッシュに取り込み、それを混ぜ込む様子を想像している。