すでに Git はインストール済みとする。
まず、システム上のすべてのリポジトリに適用される設定のデフォルト値を定義する。
$ git config --global user.name "Kazuyoshi TOMITA"
$ git config --global user.email "*****@gmail.com"
$ git config --global color.ui "auto"
$ git config --global core.excludesfile "/home/fortune/.gitignore"
core.excludesfile
で指定したファイルには、すべてのリポジトリで無視すべきファイルのパターンを記述する。Mac OS なら .DS_STORE
を記述するだろう。
設定情報は、ホームディレクトリの .gitconfig
ファイルに記録される。設定情報を確認するには、このファイルを直接見る他に
$ git config --global --list
とする。ここでの設定値はグローバルだが、リポジトリごとにローカルな設定をすることもできる。
リポジトリを作成したいプロジェクト myproject
があるなら、
$ mkdir myproject
$ cd myproject
$ git init
$ ls -la
drwxr-xr-x 3 kazu admin 96 Nov 8 10:13 .
drwxr-xr-x 5 kazu admin 160 Nov 8 10:10 ..
drwxr-xr-x 9 kazu admin 288 Nov 8 10:13 .git
.git
ディレクトリがリポジトリとなり、リポジトリのあらゆるメタデータが格納される。myproject
は空でもいいし、すでにプロジェクトのファイルがあってもよい。
リポジトリにローカルな設定は、.git/config
ファイルに記述されている。git init
でリポジトリを作成した時点でいくつかの値がデフォルトで設定されており、
$ git config --local --list
で確認できる。このリポジトリが会社の仕事で使うリポジトリで、user.email
のグローバルな設定値が個人用アドレスになっているのなら、ローカルに会社用アドレスで設定したいだろう。その場合はこうする。
$ git config --local user.email "tomita@***.jp
このリポジトリでのみ無視したいファイルは、myproject/.gitignore
ファイルに記述する。また、自分の環境でのみ生成されてしまうような無視すべきファイルは、.git/info/exclude
ファイルに記述する。
作業ツリーとはリポジトリの現在の見た目のこと。ここでは myproject
ディレクトリをルートとするツリーのこと。この作業ツリーを編集、つまりファイルの作成、更新等をしていく。
ステージングエリアとは、作業ツリーにおける変更の差分をバッファする場所のこと。ステージングエリアの内容をリポジトリへとコミットする。一つ一つのコミットは連なっていき、コミットを追跡することができる。
$ git add newfile modifiedfile # 新規作成されたファイル newfile と、前にステージングあるいはコミット後に更新したファイル modifiedfile の更新分をステージング
$ git add . # カレントディレクトリ以下の全更新分(新規作成ファイルも含む)をステージング
somefile を更新し、それをステージングし、その後でさらに somefile を更新してステージングすると、前のステージング内容は上書きされる。
$ git commit -m "Create an empty index.html" # ステージングエリアの内容をコミット
$ git commit -m "コミットログ" -a # 作業ツリーの変更分をステージングし、同時にコミット。ただし、新規作成ファイルは除く。
$ git commit -m "コミットログ" somefile_1 sub_dir/somefile_2 # 指定したファイルをステージングし、同時にコミット
-m
オプションで指定するログは、大文字で開始し、最後のピリオドは省略し、50文字以内にするのが慣習になっている。-m
を指定しなければ、エディタが起動するので、そこでログメッセージを書く。1行目の後に空行を入れ、それ以降の行は72文字以内にするのが慣習である。こうすることで、git log
コマンドでログが見やすくなる。
$ git status
ステージングエリアに何が追加されたか? 作業ツリー内のどのファイルが更新されたか? 作業ツリー内に新規作成されたファイルがあるか? を確認できる。なお、Git はディレクトリは追跡しないので、空のディレクトリは無視される。
$ git diff # ステージングエリアに対する作業ツリーの差分
$ git diff --cached # 現在のブランチの HEAD(直近のコミット)に対するステージングエリアの差分
$ git diff HEAD # 現在のブランチの HEAD に対する作業ツリーの差分
$ git diff HEAD^ # 現在のブランチの HEAD の1つ前のコミットと作業ツリーとの差分
$ git log # HEAD から順に各コミットのハッシュ値、作成者、日時、ログメッセージを表示する
$ git log --oneline # 各コミットごとに、そのハッシュ値とログメッセージの最初の行を含む1行を表示する
$ git log -n3 # HEAD から3つのコミットだけ表示する
$ git log -n3 --oneline
Commit 済みの somefile というファイルがあるとする。これを削除してコミットするにはこうする。
$ rm somefile
$ git add somefile # または git rm somefile
$ git commit -m "Remove somefile"
もしくはこうする。
$ git rm somefile # rm somefile と git add somefile を一括して実行する。
$ git commit -m "Remove somefile"
コミット済みの somefile_1 を somefile_2 へ移動する。まずは面倒くさい方法から。git status
で確認しながらすすめる。
$ mv somefile_1 somefile2
$ git status
On branch master
Changes not staged for commit:
(use "git add/rm <file>..." to update what will be committed)
(use "git checkout -- <file>..." to discard changes in working directory)
deleted: somefile_1
Untracked files:
(use "git add <file>..." to include in what will be committed)
somefile_2
no changes added to commit (use "git add" and/or "git commit -a")
$ git add somefile_1 somefile_2
$ git status
On branch master
Changes to be committed:
(use "git reset HEAD <file>..." to unstage)
renamed: somefile_1 -> somefile_2
$ git commit -m "Rename somefile_1 to somefile_2"
git mv
コマンドが上の操作のショートカットだ。
$ git mv somefile_1 somefile_2
$ git status
On branch master
Changes to be committed:
(use "git reset HEAD <file>..." to unstage)
renamed: somefile_1 -> somefile_2
$ git commit -m "Rename somefile_1 to somefile_2"
ブランチとは、コミットが連なったシーケンスの末端(直近のコミット)を指すポインタのこと。
左図は最初のコミットA をつくった後の状態の master
ブランチ。右図は、コミット A の後に B, C をコミットした後の master
ブランチ。
$ git branch
* master
このようにリポジトリ内のブランチを表示できる。カレントブランチには * マークがつく。
リポジトリが下図のような状態だとする。
コミット C の後で些細な間違いやコミットログのみ修正したいようなとき、新たなコミットを追加するのではなく、最新のコミット C を修正して置き換えたい。そのようなとき、--amend
オプションを使う。
$ git commit --amend -m "New comment" # 新しいログをつけて直近のコミットを修正する
$ git commit --amend --no-edit # ログメッセージを変更せずに直近のコミットを修正する
$ git commit --amend -c HEAD # 直近のログメッセージが入力されているエディタを開く
$ git commit --amend -c HEAD^ # 直近の1つ前のコミットのログメッセージを使ってエディタを開く
上のいずれかのコマンドを実行後、リポジトリは次の状態になる。
コミットのハッシュ値は変化する。
ブランチを新たに作成するには、git branch
コマンドを使う。今、master
ブランチにいるとして
$ git branch alternate master # カレントブランチが作成元なので master は省略できる。
とすると、次の図のように alternate
ブランチができる。
いま、master
ブランチにいるので、作業ツリーも当然 master
ブランチに対応している。これを別のブランチ alternate
に変更するには、alternate
をチェックアウトする。
$ git checkout alternate
チェックアウトすると、作業ツリーは alternate
に切り替わる。ブランチの作成とチェックアウトを同時に実行するには、今 master
ブランチにいるとして
$ git checkout -b alternate master # カレントブランチが作成元なので master は省略できる。
とする。
作業ツリー内のファイルを誤って削除してしまったり、ファイルへの変更を取り消したい場合、git checkout
コマンドを利用できる。たとえば、作業ツリーにある index.html
ファイルを間違って削除してしまったり、間違った変更をしてしまい元に戻せなくなったとする。こういう場合、次のようにする。
$ git checkout -- index.html
こうすると、作業ツリーの index.html
がステージングエリアでのバージョンの index.html
に戻すことにより復旧できる。しかし、index.html
を削除し、それをステージングしてしまった後だとうまくいかない。
$ git rm index.html
$ git checkout -- index.html # うまくいかない
error: pathspec 'index.html' did not match any file(s) known to git
失敗する理由は、git rm
コマンドにより、ファイルの削除と同時にその変更がステージングされてしまっているからだ。この場合、ステージングでのバージョンに戻そうとしてもうまくいかなくなる。こういうときは、git restore --staged
コマンドを使って index.html
ファイルの削除のステージングを解除してから git checkout
すればよい。
$ git status # index.html の削除がステージングされていることを確認
On branch master
Changes to be committed:
(use "git restore --staged <file>..." to unstage)
deleted: index.html
$ git restore --staged index.html
$ git status # 削除のステージングが解除されたことを確認。
On branch master
Changes not staged for commit:
(use "git add/rm <file>..." to update what will be committed)
(use "git restore <file>..." to discard changes in working directory)
deleted: index.html
no changes added to commit (use "git add" and/or "git commit -a")
$ git checkout -- index.html
これで index.html
は元に戻る。
コミット C を取り消して B に戻したいとする。これには git revert
コマンドを使う。
$ git revert --no-edit HEAD # --no-edit により、Revert したことを示すログメッセージを自動でつけてくれる。
こうすると次の状態になる。
コミット B' は B の C に対する差分をコミットしたもの。その結果 B' と B は内容が同じになる。
複数のコミットを巻き戻す場合、その都度 git revert
すると、その度に新たなコミットが追加されてしまうので、-n
オプションを使ってステージング(と作業ツリーに)だけ実行し、一つにまとめたほうがよい。最初の図で B と C の両方を取り消す場合には次のようにする。
$ git revert -n HEAD
$ git revert -n HEAD^
$ git commit --no-edit
この結果、リポジトリは次の状態になる。
ここでは、まず、B の C に対する差分をステージングし、次に A の B に対する差分をステージングし、最後にコミットし A' を作っている。その結果、A と A' の内容は等しくなる。
変更を巻き戻すのではなく、コミットそのものを消してしまいたいときは git reset
コマンドを使う。たとえば、下のようなリポジトリに置いて、コミット B と C を取り消したいとする。
この場合、次のようにする。
$ git reset HEAD^^ # 直近のコミットの2つ前のコミットまでリセットする。
リポジトリは下の状態になる。
インデックスは reset 後のコミットが表すツリー構造と一致するように変更される。作業ツリーはそのままだ。
$ git reset --soft HEAD^^
とすると、インデックスも作業ツリーも変更されない。インデックスも作業ツリーも reset 後のコミットに合致するように変更するなら、
$ git reset --hard HEAD^^
とする。こうすると、コミット A をコミットした直後の状態と同じになる。
リポジトリの状態が
だとして、いま、alternate
ブランチにいるとする。このとき、D, E コミットをしたとすると、次の図のようになる。
ここで、master
ブランチへ alternate
ブランチの変更をマージするには、master
をチェックアウトして git merge
コマンドを使う。
$ git checkout master
$ git merge alternate
実行後の状態は次の図のようになる。
ここでは master
のブランチポインターが前方へ移動しただけなので、Fast-forward マージと呼ぶ。
共同作業をする場合、協力者は元のリポジトリの複製を作る必要がある。このために git clone
を使う。現実的ではないが、
同じホストにオリジナルのリポジトリがあるとして、それの作業ツリーのルートが Cats だとしよう。別のディレクトリ Alice
で次のようにする。
$ git clone ../Cats .
こうすると、カレントディレクトリに Cats リポジトリの複製がつくられ、master ブランチが checkout された状態になる。
git clone
で作成されたリポジトリには、リモートリポジトリへのポインタとして、元のリポジトリの場所が remote.origin.url
変数に
設定されている。これは、git config --list --local
で見ることができる。
remote.origin.url=/Users/kazu/Documents/samples/Git_intro/Alice/../Cats
リモートリポジトリの名前として origin がデフォルトで使われる。
クローンとして作成したリポジトリの作業ツリーでファイルを変更し、それをコミットしたとする。そのとき、git config --local
コマンドで
user.name
, user.email
を別の値に設定しておく。この後、元のリポジトリでこの変更を受け入れるには、
$ cd ../Cats
$ git remote add remote-alice ../Alice # リポジトリのローカル設定に remote.remote-alice.url=../Alice がセットされ、remote-alice がリモートリポジトリの名前になる。
$ git pull remote-alice master # remote-alice という名前のリモートリポジトリの master ブランチの変更を取得する。
とする。
共同作業をするには、複数の協力者がもつローカルなリポジトリからリモートリポジトリとして共有されるリポジトリを 1つだけ作成する。このリポジトリでは変更作業がおこなわれることはないため、作業ツリーがない。これを Bare リポジトリ という。
Bare リポジトリは次のように作成する。リポジトリ名は、Shared.git とする。Bare リポジトリのディレクトリは、.git で 終わる名前にするのが慣習になっている。
$ mkdir Shared.git
$ cd Shared.git
$ git init --bare
次は、自分自身のリポジトリの内容で共有リポジトリの内容を初期化する。このために、自分のリポジトリに戻って、 共有リポジトリへと Push する。
$ cd ../Cats
$ git remote add origin ../Shared.git # origin をリモートリポジトリ ../Shared.git への短縮名として使えるように設定
$ git push origin master # origin リモートリポジトリへ master ブランチをプッシュする
git push
コマンドには、リモートリポジトリ名と、対象となるブランチ名を指定する必要がある。ただし、--set-upstream
オプションを
つければ、次からは指定する必要がなくなる。
これ以降は、共有リポジトリを git clone
すればいい。
自分のローカルリポジトリで作業し、変更内容を git commit
していく。そして、リモートリポジトリと同期するために
git push
、git pull
していく。git pull
とは、リモートリポジトリの内容を取得して、それを自分のブランチにマージすることだ。
git pull
する前に自分の作業ツリーの内容は Commit してクリーンな状態にしておく方がよい。ダーティな状態で Pull しても
同じファイルを変更していなければ問題ないが、そうでない場合、警告が出て、中断するので、
git diff origin -- 問題のファイル名
として変更内容を確認し、git stash
して自分の変更を退避してから
git pull
する。その後、git stash pop
して退避内容をポップすると、変更がマージされる。その上で、git commit
し、git push
すればよい。
よりよい方法は、master
ブランチでは作業をせず、特定機能の追加用、またはデバッグ用のブランチを作って、そこで作業する。そこでの作業が完了したら、
master
ブランチへとマージし(マージの前に git pull
で master
ブランチを最新の状態にする)、リモートリポジトリへとプッシュする。
Fast-forward では済まない場合もある。リポジトリの状態が下図のようになっており、master
ブランチにいるとする。
ここで、alternate
ブランチをマージするために
$ git merge alternate --no-edit
とすると、リポジトリは次のような状態になる。
新しいコミットは H であり、git merge
したときに --no-edit
オプションをつけたので、Merge branch 'alternate' というログメッセージが自動で
つけられている。このとき、master
ブランチで git log
コマンドを実行すると、D も E も履歴に表示される。もし、A, B, C, F, G, D, E という時間順で
コミットされたのなら、git log
は新しい順に表示されるので、H, E, D, G, F, C, B, A の順で表示される。ただし、コミット H の前のコミットは、G であり、
HEAD^
はコミット G を指している。
コミット D, E を履歴として残したくないなら圧縮コミットを使う。
$ git merge --squash alternate
これを実行すると D, E をマージした変更分がステージングエリアに追加されて終了する。後は、コミットすれば上の図のような状態になるが、git log
しても D, E は表示されなくなる。
git merge
コマンドで競合が発生すると、競合を起こしたファイルに競合箇所が指摘されてマージが停止する。たとえば、さっきの例で master
も
alternate
ブランチでも同じ index.html
の同じ箇所を編集してしまっていたため、自動マージに失敗したとする。この場合、こうなる。
$ git merge alternate --no-edit
Auto-merging index.html
CONFLICT (content): Merge conflict in index.html
Automatic merge failed; fix conflicts and then commit the result.
競合を起こした index.html
には、競合を起こしている箇所に特殊な書式が設定されている。これをエディタで編集して
手動で競合を解消する。その上で
$ git add index.html # 競合が解消したことを git に伝える
$ git commit -m "Resolved conflict"
とすれば、自動マージが成功した場合と同じブランチの状態になる。もしくは、git merge --abort
か git reset --hard HEAD
とすれば
マージを実行する前の状態にもどる。
$ git branch -d alternate # alternate ブランチがマージされていないと失敗する
$ git branch -D alternate # alternate ブランチを強制的に削除する
$ git branch -m alternate new # new ブランチがすでにあると失敗する
$ git branch -M alternate new # new ブランチがすでにあるとそれを上書きしてしまう!
git diff
コマンドは、バージョン間の差分を取るのにも使える。比較対象は同じブランチでなくていい。
$ git diff 18f822e # コミット名 18f822e に対する作業ツリーの差分を表示。両者は同じブランチでなくていい。
$ git diff 1.0 HEAD # タグ 1.0 に対する直近のコミットの差分を表示
$ git diff --stat 1.0 # タグ 1.0 に対する作業ツリーの差分の統計情報を表示
ファイルの行ごとに、コミット名、作成者、コミット日時を確認するには git blame
コマンドを使う。
$ git blame hello.html
$ git blame -L 12,13 hello.html # 12行目と13行目に対してのみ表示する。
$ git blame -L 12,+2 hello.html # 上と同じ
$ git blame -L 12,-2 hello.html # 11行目と12行目に対してのみ表示する。
$ git blame -L 12,13 18f822e^^ -- hello.html # 18f822e の2つ前のコミットにおける hello.html の12行目と13行目に対してのみ表示する。