gitメモ図解編 (その1 )

これだけ知っていれば、どうにか使えそうなコマンドについて、コマンド操作の図解とそのメモ。

基本操作

> git init

git initでローカルディレクトリにgitリポジトリを作成する。
リポジトリの内部構造は大まかにHEAD位置を追跡しているリポジトリと、
ステージ(またはインデックス)と呼ばれるコミット対象を特定する一時領域に分かれる。

> git add [file]
> git add -u [file]

git addでワーキングディレクトリで編集したファイルを指定することにより、次のコミット対象になる。
ワーキングディレクトリ上のファイルがステージにコピーされる為、万が一誤ってローカルファイルを削除しても、
ステージから復帰させることも出来る。-uオプションで既にリポジトリで管理されているファイルのみを対象とすることもできる。

> git commit -m [message]
> git commit -am [message]
> git commit --amend

git commitコマンドでステージ上のファイルをリポジトリへコミットする。
必ずコミットメッセージを記述する必要がある。-amを使用するとgit add -uを省略することができる。
--amendオプションは直前のコミットに現在の修正を追加し、コミット後に微修正が発生したり、コミット漏れしたときに使用する。

チェックアウト機能 (git checkout)

> git checkout [file]
> git checkout HEAD [file]
> git checkout HEAD~ [file]

一般的には、ブランチの切替で使用するが、カレントブランチに対して使用する場合、
上図のようにワーキングディレクトリの内容を上書きする機能になる。

ファイル名だけ指示した場合、ステージの内容でワーキングの内容が上書きされる。
HEADを指示すると、HEADの内容でステージおよびワーキングの内容が上書きされる。
また、==HEAD~==のように過去のコミット内容をチェックアウトすることもできる。

上図のようにファイルを削除した状態で、git checkoutを実行すると、削除したファイルを復元することができる。
また、削除をコミットすると、リポジトリのファイルも削除される。

リセット機能 (git reset)

> git reset --soft HEAD~
> git reset [--mixed] HEAD~
> git reset --hard HEAD~

チェックアウトとよく似た振る舞いをするケースもあるが、基本的にはHEADの位置を移動する機能である。
--softはHEADのみ、--mixedはHEADおよびステージ、--hardはHEAD、ステージ、ワークの内容を全て
指定したHEAD位置の内容で書き換える。またオプション無しと--mixedが同じ振る舞いになる。

> git reset HEAD [file]

また、HEADの位置を移動しない場合でも、上図のようにgit addを取り消すこともできる。

ブランチ作業とマージ (git merge)

> git checkout [branch name]
> git checkout -b [branch name]

-bオプションを付けてgit checkoutを実行すると、
ブランチを作成すると同時に作成したブランチにHEADを移動する。
オプション無しの場合は、既に作成されているブランチへ移動する。

> git merge [branch name]

一般的にgit管理下でのソースの修正は、トピックブランチを作成し、
そこで修正・追加作業を行った後、masterブランチへその修正を反映する流れが一般的である。
(ある程度の定石パターンとしてgit-flowgithub-flowがある)

この修正を反映するのが、git mergeで行う。masterから分岐して、その修正をまたmasterに
反映する場合、単純にmasterの位置をブランチの位置に移動するだけのマージをFast forward
と呼ばれる。master側にも修正が入っており、修正箇所がバッティングするようなケースでは、コンフリクトが発生する。
その場合は、gitが自動的にマージできないので、手動によるマージを行う必要がある。

a1
<<<<<<< HEAD:aaa.txt
a2
=======
a2'
a3
>>>>>>> branch1:aaa.txt

ほかにも1行ずつ確認しながらどちらの修正を採用するかGUIで行うマージツールもある。

> git mergetool

リモートリポジトリ操作

> git push [remote] [branch]

gitは分散リポジトリである為、複数のローカルの場所で修正した内容をリモートの管理リポジトリへ反映したり、
ほかの場所で修正された内容をローカルに取り込む機能がある。

git pushコマンドでリモートリポジトリへ内容をアップロードすることが出来る。
もちろんリポジトリを破壊しないようにpushするタイミングで
リモートリポジトリの最新状態を取り込んで整合性が合っている必要があり、そうでない場合は
エラーになってpushできない。

> git fetch [remote]

リモートリポジトリの内容を取り込むには、git fetchを利用する。上図のように
ローカルのリポジトリの内容だけをリモートリポジトリと同期を取るような処理になる。
fetchを実行すると、ローカルリポジトリにリモートの最新を指すリモートブランチやリモートHEAD
が作成される。(これを移動することはできない)
これとgit mergeコマンドで、ローカルブランチの内容とマージすることにより、リモートの最新と
ローカルの修正がマージされる。
これらの一連の操作を自動化するgit pullコマンドがあるが、fetchとmergeの動きをよく理解して使わないと
混乱するので注意する。

履歴を書き換える (git rebase)

> git checkout branch1
> git rebase master

過去のコミットの内容を履歴を書き換える機能になる。
上図の場合は、前述したmerge機能とよく似た結果になり、トピックブランチの修正内容をそのまま
masterブランチ上に履歴を移動する結果になる。もちろん移動時にコンフリクトすることは普通にあり、
その場合は、merge実行時のコンフリクト対処と同じ手順を踏む。

rebaseコマンドには、-iオプションをつけるとインタラクティブに細かく履歴操作することができる。
上記オプションで実行すると、エディタで操作範囲のコミットが列挙され、各コミットごとに以下のスイッチオプション
を指定して履歴操作できる。

git rebase -i 操作内容
p, pick そのまま何もしない
r, reword コミットメッセージの修正
e, edit コミットの内容を修正・分割する
s, squash 1つ前のコミットにまとめる(メッセージの修正あり)
f, fixup 1つ前のコミットにまとめる(メッセージの修正なし)
x, exec コミットの間に入れると、指定したコマンドが実行される(コミットの履歴操作ではない)
d, drop コミットを破棄する

コミットを分割する ( git rebase -i )

上図のように、=="a2-a3"コミットを"a2""a3"==に分ける手順をメモしておく。
(これ以外にももっと賢い方法があるとは思う)

まず、分割対象のコミットまでを範囲としてrebaseを実行し、分割対象をeditとする。

> git rebase -i HEAD~2
------------------------------
edit  388afab  a2-a3
pick  0d9cc4e  a4
------------------------------

edit対象のところまで履歴がまき戻って停止する。この内容を一時退避&修正したいので、git reset
さらに1つ履歴を戻る。

> git reset HEAD~

ワーキングの内容をgit stashで一時退避する。
(ここで退避するのは、復元する為に何度か復元する為である)
退避すると、ワーキングの内容もHEAD~と同じになる。

> git stash

ワーキングの内容を退避した内容で復元して、コミットを分割する内容になるように修正したら、
分割後の最初のコミットを実行する。

> git stash apply stash@{0}
> vi xxx
---
a1
a2
---
> git commit -am "a2"

再度、退避した内容を適用して分割2つめのコミットを実行する。
また、退避した内容は不要なので、git stash clearで全て除去する。

> git stash apply stash@{0}
> git commit -am "a3"
> git stash clear

リベースを進めるため、--continueオプションを指定してリベースを完了させる。
進めたときにコンフリクトが発生したら、適宜修正して--continueオプションで完了するまで
繰り返す。

> git rebase --continue