アリスとボブになりきってgitをちゃんと理解したい!



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


gitの解説には素晴らしいページがある。

こんなに親切に説明されているのに、読んでいるだけではgitの仕組みが見えてこない...。(そうです。自分の理解が悪いのです。)ちゃんと理解したいので、チュートリアルに出てくるアリスとボブになりきって、実際に作業してみることにする。以下は淡々としたその作業記録と自分の理解のイメージ。

環境

  • MacBook OSX 10.5.4
  • Xcode3.0以上インストール済

インストール

gitのページから最新版をダンロードしてみた。現在のバージョンは1.6.0.1のようだ。

cd ~/Downloads
curl http://kernel.org/pub/software/scm/git/git-1.6.0.1.tar.gz > git-1.6.0.1.tar.gz
tar zxvf git-1.6.0.1.tar.gz
cd git-1.6.0.1
./configure
make
sudo make install

名前と連絡先

  • まず最初に、自分がどこの誰だかを設定しておく。
  • この設定はcommitする時に履歴として記録される。
    • 名前は、アリス
    • 連絡先は、alice@example.com
alice$ git config --global user.name "alice"
alice$ git config --global user.email alice@example.com

プロジェクトの開始

  • アリスは、とあるプロジェクトを開始した。
alice$ cd
alice$ mkdir project
alice$ cd project
alice/project$ git init
Initialized empty Git repository in /Users/alice/project/.git/
  • これで、このprojectフォルダ以下はgitで管理されることとなった。

ファイルの追加

  • アリスは、WhatIsGitクラスを作成して保存した。
# About git
Class WhatIsGit
  def show
    puts 'Git is difficult...'
  end
  
  def about
    puts 'http://ja.wikipedia.org/wiki/Git'
  end
end
  • ファイルはwhat_is_git.rbで保存されている。
alice/project$ ls
what_is_git.rb
  • 「git add」で、gitに引数に指定したファイルのスナップショット(その時点の内容?)を取得してもらう。
  • スナップショットのイメージが今イチ理解できない場合...自分はMacOSX 10.5 LeopardTime Machineに入った時のアニメーションを見て一発で理解できた。
  • 「git add」は次に「git commit」するまで、同じシートにスナップショットを書き込んでいく。
    • git addを繰り返せば、スナップショットに取得された同じファイルは上書きされる。
    • git addした時点のファイルの内容がスナップショットとして取得される。
    • だから一度git addしたファイルも、その後変更されたら、もう一度git addしない限りスナップショットには反映されない。
alice/project$ git add what_is_git.rb
# または
alice/project$ git add .
      • 「git add .」は、gitに現在のディレクトリ以下のすべてのファイルのスナップショットを取得するように依頼する。(「.」重要。)
  • 「git commit」で、その時点のスナップショットのシートにメッセージを添付して、この場合はmasterキャビネットに保存する。
    • viが起動して、そのコミットのメッセージの入力を求められる。
    • 「#」で始まるコメント行はこのコミットの情報を教えてくれているようだ。(「---文---」は自分で追加した日本語訳)
      • メッセージは複数行の入力可能で、「#」で始まるコメント行はメッセージにはならない。
      • 最初はデフォルトではmasterというブランチ(gitが管理するディレクトリ?)に追加される。
      • その下に新規追加、変更、削除されるファイル名が表示されるようだ。
alice/project$ git commit

  1 first commit
  2 # Please enter the commit message for your changes.---変更に関するメッセージを入力してください---
  3 # (Comment lines starting with '#' will not be included)---「#」で始まるコメント行はメッセージには含まれません。---
  4 # On branch master---ここはmasterブランチです。---
  5 #
  6 # Initial commit
  7 #
  8 # Changes to be committed:
  9 #   (use "git rm --cached ..." to unstage)
 10 #
 11 #       new file: what_is_git.rb
 12 #
~
~
  • viの1行目にfirst commitと入力して保存するとコミットは完了した。
".git/COMMIT_EDITMSG" 12L, 269C written
Created initial commit 119674b: first commit
 1 files changed, 11 insertions(+), 0 deletions(-)
 create mode 100644 what_is_git.rb
  • ちなみに-mオプションでメッセージを指定してコミットすれば、viは起動せず、即完了する。
alice/project$ git commit -m 'first commit'
  • 分かりきったシンプルな変更ならこっちの方が簡単。
  • でも、いろいろな変更が絡んでいると、何がコミットされるか確認したくもなる。その場合は、-mオプションは無しの方が良さそう。

ファイルの変更

  • アリスは考え直した。本当はgitは簡単なんじゃないかと...。そしてコードを以下のように修正した。
# About git
Class WhatIsGit
  def show
    puts 'Git is easy!'
  end
  
  def about
    puts 'http://ja.wikipedia.org/wiki/Git'
  end
end
  • 変更したらgitにスナップショットを取得してもらい、それをmasterキャビネットに保存する。
  • だから以下のコマンドは、ペアで使うことになる。
alice/project$ git add .
alice/project$ git commit
  • もしgit addしないでgit commitだけでは、以下のように注意される。(スナップショットがないのでキャビネットに保存できない)
alice/project$ git commit
# On branch master
# Changed but not updated:
#   (use "git add ..." to update what will be committed)
#
#	modified:   what_is_git.rb
#
no changes added to commit (use "git add" and/or "git commit -a")
  • 注意の最後には、とても素敵なことが書いてある。
  • なんと!-aオプションを付けてコミットすれば、この場合git addは不要になる。
alice/project$ git commit -a
  • しかし、「git commit -a」は「git add .」+「git commit」と同等ではない。
    • 「git commit -a」は現在までに履歴管理しているファイルのみ、変更または削除を検知してコミットしてくれる。
    • 新規追加したファイルは、勝手にコミットしてくれない。注意が必要!
    • コミットを中止した場合、キャビネットに保存する予定のスナップショットも残らない。
      • スナップショットのシートは透明で、過去のスナップショットは、その時点のスナップショットに記録されなくても透けて見える、と理解している。
  • git commit -aを実行したら、viで以下のように表示された。
alice/project$ git commit -a

  1 
  2 # Please enter the commit message for your changes.
  3 # (Comment lines starting with '#' will not be included)
  4 # On branch master
  5 # Changes to be committed:
  6 #   (use "git reset HEAD ..." to unstage)
  7 #
  8 #       modified:   what_is_git.rb
  9 #
~
~
  • ここでアリスは不安になる。変更した文字列のスペルが間違ってないかしら?そこで、このコミットを中止することにした。
  • コミットはメッセージが空欄だと中止された。だから、viならescキーを押してから:q!returnキーで終了すればOK。
fatal: no commit message?  aborting commit.
  • これで、このコミットは中止された。
  • その後、アリスはwhat_is_git.rbを開いて、スペルには問題がなかったことを確認した。(ひと安心)
  • しかし、ここでさらに考え直して、以下のように修正した。
# About git
Class WhatIsGit
  def show
    puts 'Git is easy, if you understand the basis'
  end
  
  def about
    puts 'http://ja.wikipedia.org/wiki/Git'
  end
end
  • アリスは修正を繰り返して混乱してきたので、変更箇所を確認したくなった。
  • git add .したスナップショットと、前回コミットのスナップショットを比較してみた。
 alice/project$ git add .
 alice/project$ git diff --cached
 diff --git a/what_is_git.rb b/what_is_git.rb
 index 5864c55..2288d7f 100644
 --- a/what_is_git.rb
 +++ b/what_is_git.rb
 @@ -1,11 +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://ja.wikipedia.org/wiki/Git'
    end
  end
 -    
 \ No newline at end of file
  • ちなみにgit diffのオプション指定で以下のような違いがあるようだ。
alice/project$ git diff --cached # 前回コミットのスナップショットと、git add .したスナップショットを比較
alice/project$ git diff          # git add .したスナップショットと、現在のファイルディレクトリを比較
alice/project$ git diff HEAD     # 前回コミットのスナップショットと、現在のファイルディレクトリを比較
      • HEADは、コミットした最新のスナップショットを指し示すタグのようだ。
  • さらに、git statusでgit commitした時にviに表示される情報も確認できるようだ。(ファイルの変更がどのような状態かを表示してくれるようだ)
$ git status
# On branch master
# Changes to be committed:
#   (use "git reset HEAD ..." to unstage)
#
#	modified:   what_is_git.rb
#
  • これを確認して、アリスは安心してコミットした。
alice/project$ git commit -m 'show message changed'
Created commit d249d65: show message changed
 1 files changed, 1 insertions(+), 2 deletions(-)
 create mode 100644 test.rb

履歴の確認

  • アリスは「git log」で今までのコミットの履歴を一覧できる。
alice/project$ git log
commit d249d65e7d0d626e4b695d594237a7b3d73532fa
Author: alice 
Date:   Fri Sep 5 15:25:51 2008 +0900

    show message changed

commit 119674b5ddbac4a220df0851257a457a7623612e
Author: alice 
Date:   Fri Sep 5 08:40:25 2008 +0900

    first commit

いろいろな取り消し

上記のshow message changedをコミットする過程で、以下のような失敗をしてしまい修正したいという状況。

コミット前の場合
  • 実験でtest.rbを作ったが、それを削除するのを忘れて「git add .」してしまった。まだ、コミットはしていない。
    • test.rbをスナップショットから取り除きたい。
alice/project$ ls
test.rb        what_is_git.rb
alice/project$ git add .
alice/project$ git status
# On branch master
# Changes to be committed:
#   (use "git reset HEAD ..." to unstage)
#
#	new file:   test.rb
#	modified:   what_is_git.rb
#
すべてのファイルをスナップショットから取り除く方法
  • 上記コメント欄にも、その方法が"git reset HEAD ..."と書いてある。
alice/project$ git reset HEAD
what_is_git.rb: locally modified
  • その後、不要なファイルを削除して、改めて「git add .」する。
alice/project$ ls
test.rb		what_is_git.rb
alice/project$ rm test.rb
alice/project$ git add .
alice/project$ git status
# On branch master
# Changes to be committed:
#   (use "git reset HEAD ..." to unstage)
#
#	modified:   what_is_git.rb
#
test.rbだけ、スナップショットから取り除く方法
alice/project$ git reset HEAD :test.rb
  • その後、不要なファイルを削除する。
alice/project$ ls
test.rb		what_is_git.rb
alice/project$ rm test.rb
alice/project$ git status
# On branch master
# Changes to be committed:
#   (use "git reset HEAD ..." to unstage)
#
#	modified:   what_is_git.rb
#
コミットしてしまった直後、まだ公開されていない場合
  • 実験でtest.rbを作ったが、それを削除するのを忘れて「git add .」そして「git commit -m 'test'」まで完了してしまった。
    • test.rbをコミットから取り除きたい。
    • コミットのメッセージも変更したい。
alice/project$ ls
test.rb        what_is_git.rb
alice/project$ git add .
alice/project$ git commit -m 'test'
Created commit fb237cf: test
 1 files changed, 1 insertions(+), 1 deletions(-)
 create mode 100644 test.rb
alice/project $ git log
commit fb237cf09be3461d54be20298c027c8ccd1802b3
Author: alice 
Date:   Fri Sep 5 18:19:24 2008 +0900

    test

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

    first commit
そのコミットを無かったことにする方法
alice/project$ git reset --hard ORIG_HEAD
alice/project$ git log
commit cb83d4fae763b66f647f217cb3e0373ea065fe94
Author: alice 
Date:   Fri Sep 5 17:37:22 2008 +0900

    first commit
  • 「git reset --hard ORIG_HEAD」を実行すると、ファイルディレクトリの状態まで一つ前のコミットの状態に戻ってしまうようだ。
  • 追加したファイルはファイルディレクトリからも削除され、変更したファイルは一つ前のコミットに戻ってしまった。注意が必要!
$ ls
what_is_git.rb
# About git
Class WhatIsGit
  def show
    puts 'Git is difficult...'
  end
  
  def about
    puts 'http://ja.wikipedia.org/wiki/Git'
  end
end
直前のコミットにスナップショットを上書きする方法
  • 一旦git add .してコミットしてしまったファイルは、git rmで取り除く。
alice/project$ git rm test.rb
rm 'test.rb'
alice/project$ ls
what_is_git.rb
alice/project$ git add .
  • コミットする時に--amendオプションを指定して、testと表示されているコミットのメッセージもshow message changedに修正する。
alice/project$ git commit --amend

  1 show message changed
  2 # Please enter the commit message for your changes.
  3 # (Comment lines starting with '#' will not be included)
  4 # On branch master
  5 # Changes to be committed:
  6 #   (use "git reset HEAD^1 ..." to unstage)
  7 #   
  8 #       new file:   test.rb
  9 #       modified:   what_is_git.rb
 10 #       
 11 # Changed but not updated:
 12 #   (use "git add/rm ..." to update what will be committed)
 13 #   
 14 #       deleted:    test.rb
 15 #       
~
~
".git/COMMIT_EDITMSG" 15L, 400C written
Created commit 8a17866: show message changed
 1 files changed, 1 insertions(+), 1 deletions(-)
 create mode 100644 test.rb
  • 履歴を確認すると、直前のコミットのメッセージtestは、show message changedに変更されたようだ。(新たに履歴が追加されていないので)
alice/project$ git log
commit 920e5c44da8fa75c3ece7e0fed3c1f66d05c6778
Author: alice 
Date:   Fri Sep 5 20:21:47 2008 +0900

    show message changed

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

    first commit
  • first commitと、show message changedの差分を確認してみる。
  • git logで確認できるcommit コードの最初の数桁をコピーする。(他のコミットと重複しない最低の長さでOKだと思う)
  • 上記コードをドット二つ「..」で繋いで、git diffの引数に指定すると、その間の差分を確認できる。
 alice/project$ git diff cb83d4fae763b6..920e5c44da8fa75c3e
 diff --git a/what_is_git.rb b/what_is_git.rb
 index b126408..2288d7f 100644
 --- a/what_is_git.rb
 +++ b/what_is_git.rb
 @@ -1,7 +1,7 @@
  # About git
  Class WhatIsGit
    def show
 -    puts 'Git is difficult...'
 +    puts 'Git is easy, if you understand the basis'
    end
    
    def about
  • 上記コミットの差分には、test.rbは表示されていない。
  • 内部的にはgit addがgit rmで上書きされ、何も無かったことになっているようだ。
コミットして既に公開されている場合
  • コミット後、誰かがそれを元に作業をしている可能性がある場合は、そのコミット自体を取り消すのではなく、変更を打ち消す新たなコミットを実行するのが望ましいようだ。
  • そうしないと、既に作業している誰かは、元になるコミットが変化してしまい混乱することになる。
  • 現在のアリスの状況は、上記までの作業が完了して、現状は正しいコミット状態なのだが、実験として以下の方法で打ち消してみた。
直前のコミットの変更を打ち消す、新たなコミットを実行する方法
  • 「git revert HEAD」を実行すると、直前のコミットの変更を打ち消すファイルの修正とスナップショットの取り込みを、gitが自動で処理してくれる。
    • コミットのメッセージも、どのコミットの修正であるかを明記してくれている。
    • もちろん、git revertに頼らず、自分で変更を打ち消す修正をしてファイルを保存し、その後git addでも良いはず。
alice/project$ git revert HEAD
Finished one revert.

  1 Revert "show message changed"
  2 
  3 This reverts commit 920e5c44da8fa75c3ece7e0fed3c1f66d05c6778.
  4 
  5 # Please enter the commit message for your changes. Lines starting
  6 # with '#' will be ignored, and an empty message aborts the commit.
  7 # On branch master
  8 # Changes to be committed:
  9 #   (use "git reset HEAD ..." to unstage)
 10 #
 11 #       modified:   what_is_git.rb
 12 #
~
~
Created commit 127e51f: Revert "show message changed"
 1 files changed, 1 insertions(+), 1 deletions(-)
  • git revertが完了後、履歴を確認すると、Revert "show message changed"が新たにコミットされている。
alice/project$ git log
commit 127e51f5d9ea2f0c744918bfcce88a0774c98fd2
Author: alice 
Date:   Sat Sep 6 06:49:17 2008 +0900

    Revert "show message changed"
    
    This reverts commit 920e5c44da8fa75c3ece7e0fed3c1f66d05c6778.

commit 920e5c44da8fa75c3ece7e0fed3c1f66d05c6778
Author: alice 
Date:   Fri Sep 5 20:21:47 2008 +0900

    show message changed

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

    first commit
  • what_is_git.rbを確認すると、first commitの状態に戻っている。
# About git
Class WhatIsGit
  def show
    puts 'Git is difficult...'
  end
  
  def about
    puts 'http://ja.wikipedia.org/wiki/Git'
  end
end


本当は、アリスとボブのコミットのやり取りまで試したかったが、本日はここで力尽きたので、また後日...。

  • これじゃ、アリスが登場した意味がない...。
  • 自分の場合、git addとgit commitが、スナップショット(MacBookのTime Machineを想像)をどのように操作するのかイメージすると、gitのいろんなことが理解し易くなる気がした。