サルでもわかるGit入門(入門編&発展編)をざっくりと呼んだメモ

サルでもわかるGit入門の入門編と発展編を読んだ時のメモです。

リポジトリとインデックスとワークツリー

Gitには3つの階層がある。

ワークツリー

作業場。変更中のファイルがある場所。

インデックス

git add されたファイルが置かれる場所。 git commitでコミットされたファイルはリポジトリに移動する。

リポジトリ

コミットされたファイルが保存されている場所。

ブランチ

統合ブランチとトピックブランチ

ブランチは、統合ブランチとトピックブランチに分かれる。
統合ブランチは、いつでもリリースできる状態のブランチで通常、masterブランチを用いる。masterブランチはCIツールによる自動ビルドやテストを定期的に行うことで常に明らかなバグが無いようにしておくことが大切。
トピックブランチは、機能追加やバグの修正時に作る一時的なブランチのこと。作業が終われば統合ブランチにマージして、トピックブランチは削除する。

stash

コミットしていない変更内容や追加したファイルがインデックスやワークツリーに残ったままで、他のブランチへのチェックアウトを行うと、その変更内容はもとのブランチから移動先にブランチに対して移動する。
ただし、移動先のブランチで同じファイルが既に何らかの変更が行われている場合はチェックアウトに失敗する。
このような場合には変更内容を一度コミットするか、stashを使って一時的に変更内容を退避させてからチェックアウトする必要がある。

具体的なコマンドは以下の通り。

# ワークツリーの変更を保存する。stashに保存された変更分は取り消される。
$git stash save
# 以下のコマンドで保存しているstashの一覧を確認できる。
$ git stash list
stash@{0}: WIP on localize: 67f7992 localized
# 以下のコマンドでそのstash内の変更内容を確認できる。
$ git stash show stash@{0}
 sample1.txt | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)
# 以下のコマンドでstashを適用できる
$ git stash apply stash@{0}
Auto-merging sample1.txt
CONFLICT (content): Merge conflict in sample1.txt 
# stashを削除するにはapplyをdropに変えればよい。
# applyとdropを同時にしたい場合は、popとする。

ブランチの統合

ブランチの統合にはmergeを使う方法と、rebaseを使う方法がある。

merge

mergeを使うと2つのブランチを1つのブランチに統合することができる。通常、マージ先のブランチは統合(master)ブランチでマージ元がトピックブランチとなる。
具体的なコマンドは以下の通り。

$ git merge <ブランチ名>

もし、マージ先にも変更がある場合は、コミットメッセージを入力する画面がvimで表示される。コミットしない場合は :q でvimを終了する。
マージが完了したら、以下のコマンドで用済みのトピックブランチを削除する。

$ git branch -d <ブランチ名>

rebase

rebaseを使うと、統合元ブランチの履歴が統合先ブランチ(通常master)ブランチの後ろに付け替えられれる。統合元のブランチは削除され、統合先ブランチに一本化される。
うーん。難しいので具体的な手順は省略ー。

リモートリポジトリ

pull

リモートリポジトリの履歴を取得することが出来る。リモートリポジトリが変更されていて、ローカルリポジトリが変更されていない場合は、fast-forwardマージが行われる。
ローカルリポジトリも変更されている場合は、競合の有無を確認し、競合が無い場合は自動的にマージコミットされる。競合がある場合は、やっぱり手動解決&コミットが必要。

fetch

pullを実行すると、リモートリポジトリの内容が自動的にマージされてしまう。しかし、単にリモートリポジトリの内容を確認したいだけのときはマージしたくない場合もある。そんなときに、fetchを使う。
もしmergeしたくなれば、

$ git merge origin/master

でマージする。 なので、pull = fetch + mergeであるとも言える。

push

リモートリポジトリにローカルリポジトリの変更を反映したい場合は、pushを実行する。ただし、競合が発生する場合は拒否されるので、事前にリモートリポジトリをpullして競合を解決しておく必要がある。

タグ

# コミットに注釈なしタグを付ける。今回はappleというタグを付ける。
$ git tag apple
# 付けたタグを確認する
$ git tag
apple
# 今度は注釈ありのタグを付ける。タグ名は banana。
$ git tag -am "サルでも分かるGit" banana
# タグの一覧を注釈付きで確認する
$ git tag -n
apple           first commit
banana          サルでも分かるGit
# タグを削除する
$ git tag -d <タグ名>

コミットの書き換え

commit --amend を使って直前のコミットを編集する

まずは、ログを確認する。

$ git log
commit 326fc9f70d022afdd31b0072dbbae003783d77ed
Author: yourname <yourname@yourmail.com>
Date:   Mon Jul 16 23:17:56 2012 +0900

    addの説明を追加

次に、ファイルを編集する。ここでは、sample.txtにcommitに関する説明を追記する。編集が終わったら commit --amend コマンドで直前のコミットを修正する

$ git commit --amend
[master 0f911e9] addとcommitの説明を追加
 Date: Mon Jul 16 23:17:56 2012 +0900
 1 file changed, 3 insertions(+), 1 deletion(-)

再度ログを確認すると・・・。

$ git log
commit 0f911e9ebc8b373eeb62b56b999c02280440f737
Author: yourname <yourname@yourmail.com>
Date:   Mon Jul 16 23:17:56 2012 +0900

    addとcommitの説明を追加

無事、直前のコミットが編集されているのでOK。

revert を使って直前のコミットを打ち消す

まずはログを確認。

$ git log
commit 0d4a808c26908cd5fe4b6294a00150342d1a58be
Author: yourname <yourname@yourmail.com>
Date:   Mon Jul 16 23:19:26 2012 +0900

    pullの説明を追加

commit 9a54fd4dd22dbe22dd966581bc78e83f16cee1d7
Author: yourname <yourname@yourmail.com>
Date:   Mon Jul 16 23:19:01 2012 +0900

    commitの説明を追加

git revert コマンドで直前のコミットを取り消す。

$ git revert HEAD
[master d47bb1d] Revert "pullの説明を追加"
 1 files changed, 1 insertions(+), 2 deletions(-)

ログを確認してみる。

$ git log
commit 7bcf5e3b6fc47e875ec226ce2b13a53df73cf626
Author: yourname <yourname@yourmail.com>
Date:   Wed Jul 18 15:46:28 2012 +0900

    Revert "pullの説明を追加"

    This reverts commit 0d4a808c26908cd5fe4b6294a00150342d1a58be.

commit 0d4a808c26908cd5fe4b6294a00150342d1a58be
Author: yourname <yourname@yourmail.com>
Date:   Mon Jul 16 23:19:26 2012 +0900

    pullの説明を追加

直前のコミットを打ち消すコミットが追加されているのが分かる。つまり、直前のコミットとその一つ前のコミットの差分を自動的に求め、その差分を打ち消すようなコミットがされているということ。

reset を使ってコミットを削除する

revertと違ってこっちは物理的にコミットを削除するコマンド?(正直あんまよくわかってません。。)

$ git reset --hard HEAD~
HEAD is now at 9a54fd4 commitの説明を追加

これで直前の履歴が削除される。
ただし、GitHubのパブリックリポジトリなど既に公開されているものに対してこれをやるのは絶対に駄目!

chrry-pick で別ブランチのコミットを取り込む

masterブランチとトピックブランチがある時、トピックブランチの1つ前のコミットをmasterブランチに取り込みたい場合などに使う。

$ git branch
* issue1
  master
# issue1の履歴を確認
$ git log
commit 08084a5c58e9ca3672292c3883c44e623f817b72
Author: yourname <yourname@yourmail.com>
Date:   Mon Jul 16 23:22:17 2012 +0900

    pullの説明を追加

commit 99daed25b45fcae2ce9d707a3434951cf69f253a
Author: yourname <yourname@yourmail.com>
Date:   Mon Jul 16 23:21:57 2012 +0900

    commitの説明を追加

この issue1 の 99daed25.. のコミットをmasterブランチに取り込む。

# まずmasterブランチをチェックアウト
$ git checkout master
Switched to branch 'master'
# あとはトピックブランチのコミットをmasterブランチの先頭に持ってくる
$ git cherry-pick 99daed2
error: could not apply 99daed2... commit
hint: after resolving the conflicts, mark the corrected paths
hint: with 'git add <paths>' or 'git rm <paths>'
hint: and commit the result with 'git commit'

競合が発生しているので、ファイルを開いて競合箇所を修正してからコミットする。

rebase -i でコミットをまとめる

後からコミットをまとめることも出来る。例えば以下のような履歴があった時

$ git log --oneline
0d4a808 pullの説明を追加
9a54fd4 commitの説明を追加
326fc9f addの説明を追加
48eec1d first commit

直前2つのコミットをrebase -iを使ってまとめる。

$ git rebase -i HEAD~~

rebase -iコマンドを実行すると、以下のようなvimが開く。

pick 9a54fd4 commitの説明を追加
pick 0d4a808 pullの説明を追加

# Rebase 326fc9f..0d4a808 onto 326fc9f (2 command(s))
#
# Commands:
# p, pick = use commit
# r, reword = use commit, but edit the commit message
# e, edit = use commit, but stop for amending
# s, squash = use commit, but meld into previous commit
# f, fixup = like "squash", but discard this commit's log message
# x, exec = run command (the rest of the line) using shell
# d, drop = remove commit
#
# These lines can be re-ordered; they are executed from top to bottom.
#
# If you remove a line here THAT COMMIT WILL BE LOST.
#
# However, if you remove everything, the rebase will be aborted.
#
# Note that empty commits are commented out

2行目の pick を squash に変更して保存・終了する(squash は押しつぶすという意味らしい)。すると今度はまとめたコミットのメッセージを入力するためのエディタが開くので適切なメッセージを入力して保存・終了する。今回は、「pullとcommitを追加」とする。
再び履歴を確認してみると・・。

$ git log --oneline
866d3e3 pullとcommitの説明を追加
326fc9f addの説明を追加
48eec1d first commit

直前2つのコミットが1つのコミットになっているのでOK。

rebase -i でコミットを修正する

rebase -iを使ってコミットを修正することも出来る・・。
こんな履歴があった時

$ git log --oneline
0d4a808 pullの説明を追加
9a54fd4 commitの説明を追加
326fc9f addの説明を追加
48eec1d first commit

2つ前の 9a54fd4 コミットを修正するとする。

$ git rebase -i HEAD~~

するとvimが開く。

pick 9a54fd4 commitの説明を追加
pick 0d4a808 pullの説明を追加

# Rebase 326fc9f..0d4a808 onto d286baa
#
# Commands:
#  p, pick = use commit
#  r, reword = use commit, but edit the commit message
#  e, edit = use commit, but stop for amending
#  s, squash = use commit, but meld into previous commit
#  f, fixup = like "squash", but discard this commit's log message
#  x, exec = run command (the rest of the line) using shell
#
# If you remove a line here THAT COMMIT WILL BE LOST.
# However, if you remove everything, the rebase will be aborted.
#

編集対象(ここでは0d4a808)のpickを edit に変更して保存・終了する。
すると次のような出力が表示され、修正するコミットがチェックアウトされた状態になる。

Stopped at d286baa... commitの説明を追加
You can amend the commit now, with

        git commit --amend

Once you are satisfied with your changes, run

        git rebase --continue

修正対象のファイルを開いて適当に変更する。
終わったら、commit --amendで保存する。

$ git add sample.txt
$ git commit --amend

最後に、次のコマンドを実行して作業が終了した事を知らせる必要がある。

$ git rebase --continue

merge --squash でトピックブランチのコミットをまとめてマージする

$ git branch
* issue1
  master
$ git log --oneline
08084a5 pullの説明を追加
99daed2 commitの説明を追加
48eec1d first commit
$ git checkout master
Switched to branch 'master'
$ git merge --squash issue1
Auto-merging sample.txt
CONFLICT (content): Merge conflict in sample.txt
Squash commit -- not updating HEAD
Automatic merge failed; fix conflicts and then commit the result.

競合が発生したので、sample.txtを開いて競合箇所を修正してからコミットする。

$ git add -A
$ git commit
[master a6a3be4] merge issue1
 1 file changed, 3 insertions(+), 1 deletion(-)