色々なコミット修正方法

はじめに
---------

Gitには、複数のコミット修正方法があります。

ここではその使い分けについて説明しています。


HEADコミットを修正
-------------------

* コマンド
  `git commit --amend`

* 目的
  * HEADコミットのコミットメッセージを修正したり、ファイルの登録漏れを追加したい場合。

* 特徴
  * 履歴は残らない。

* 実行例

HEADコミットのコミットメッセージを修正

```bash
$ touch testfile2

$ git add testfile2

$ git commit -m "add teste4re" <-メッセージを間違えた
[master f95faeb] add teste4re
 0 files changed, 0 insertions(+), 0 deletions(-)
 create mode 100644 testfile2

$ git log --oneline
f95faeb add teste4re <-メッセージを間違えたコミット
2fa37ff add test1

$ git commit --amend

viの編集画面が表示される
add teste4re　<-これを正しいメッセージ"add testfile2"に修正する

# Please enter the commit message for your changes. Lines starting
# with '#' will be ignored, and an empty message aborts the commit.
# On branch master
# Changes to be committed:
#   (use "git reset HEAD^1 <file>..." to unstage)
#
#       new file:   testfile2
#
:wp

[master 262c490] add testfile2
 0 files changed, 0 insertions(+), 0 deletions(-)
 create mode 100644 testfile2

$ git log --oneline
262c490 add testfile2 <-メッセージが修正されている
2fa37ff add test1
```

ファイルの登録漏れがあったため、HEADコミットにファイルを追加したい場合

```bash
$ touch testfile3

$ touch testfile4

$ git add testfile3 <-testfile4をaddし忘れた

$ git commit -m "add testfile3 & testfile4"
[master d75d6bb] add testfile3 & testfile4
 0 files changed, 0 insertions(+), 0 deletions(-)
 create mode 100644 testfile3

$ git status
# On branch master
# Untracked files:
#   (use "git add <file>..." to include in what will be committed)
#
#       testfile4　<-testfile4がaddされていない
nothing added to commit but untracked files present (use "git add" to track)

$ git add testfile4

$ git commit --amend

viの編集画面が表示されるが、特に問題なければメッセージを変更せずに、:wqする
add testfile3 & testfile4

# Please enter the commit message for your changes. Lines starting
# with '#' will be ignored, and an empty message aborts the commit.
# On branch master
# Changes to be committed:
#   (use "git reset HEAD^1 <file>..." to unstage)
#
#       new file:   testfile3
#       new file:   testfile4
#
:wq

master 8999e57] add testfile3 & testfile4
 0 files changed, 0 insertions(+), 0 deletions(-)
 create mode 100644 testfile3
 create mode 100644 testfile4

$ git log --oneline <-新しいコミットは作成されていない
8999e57 add testfile3 & testfile4
262c490 add testfile2
2fa37ff add test1

$ git status
# On branch master
nothing to commit (working directory clean)
```


指定したコミットにリセットする(そのコミットまで巻き戻す)
------------------------------------------------------------

* コマンド
  `git reset --hard [コミット]`

* 目的
  * 指定したコミットにリセットする(現在～指定したコミットの間のコミットを取り消す)

* 特徴
  * 作業ツリーやインデックス情報も**指定したコミットの状態**にする(そのコミットの状態まで巻き戻す)
  * 履歴は残らない。
  * コミット単位でのみ巻き戻し可能。ファイル単位の操作はできない。

* 注意
  * 過去のコミットを指定することで、そのコミットまで巻き戻すことができるが、その途中経過のコミットは
    消えてしまう。なので、直近のコミットを取り消す場合以外は、なるべく`git revert`を使った
    方がよい。

  * `git reset`は指定したコミットの状態にするのに対し、`git revert`は指定したコミットをピンポイントで取り消す。
    つまり、`git reset`の場合は"現在～指定したコミットの間のコミット"を取り消すことになり、指定したコミットが
    取り消されるわけではない。

* 実行例

現在のコミット(HEAD)を取り消し、1つ前の状態(HEAD^)まで巻き戻したい場合

```bash
$ git add testfile5

$ git commit -m "add testfile5"
[master 6e1dd4b] add testfile5
 0 files changed, 0 insertions(+), 0 deletions(-)
 create mode 100644 testfile5

$ ls
test1  testfile2  testfile3  testfile4  testfile5

$ touch testfile6

$ git add testfile6

$ git commit -m "add testfile6"
[master ae67e63] add testfile6
 0 files changed, 0 insertions(+), 0 deletions(-)
 create mode 100644 testfile6

$ ls
test1  testfile2  testfile3  testfile4  testfile5  testfile6

$ git log
commit ae67e63bf95df640341098e32efdc1338d5891ca
Author: spam ham
Date:   Fri Jun 13 07:05:18 2014 +0900

    add testfile6

commit 6e1dd4b9855ee4454863aee24930a5c05ee2e862
Author: spam ham
Date:   Fri Jun 13 07:04:48 2014 +0900

    add testfile5

$ git reset --hard HEAD^ <-これは"git reset --hard 6e1dd4"のようにコミットを指定しても良い
HEAD is now at 6e1dd4b add testfile5

$ ls <-testfile6が消えている
test1  testfile2  testfile3  testfile4  testfile5

$ git log <-コミットae67e6が消えている
commit 6e1dd4b9855ee4454863aee24930a5c05ee2e862
Author: spam ham
Date:   Fri Jun 13 07:04:48 2014 +0900

    add testfile5
```

マージをやり直したい時

```bash
$ git checkout -b new
Switched to a new branch 'new'

$ git branch -l
  master
* new

$ touch new_file1

$ git add new_file1

$ git commit -m "add new_file1"
[new cf904aa] add new_file1
 0 files changed, 0 insertions(+), 0 deletions(-)
 create mode 100644 new_file1

$ git branch -l
* master
  new

$ ls
master_file1

$ git merge --no-ff new
Merge made by recursive.
 0 files changed, 0 insertions(+), 0 deletions(-)
 create mode 100644 new_file1

$ ls
master_file1  new_file1

$ git log --graph
*   commit 945cb8efb71cdc41bad7cda657754bbfa6a5ff0f
|\  Merge: 1566a75 cf904aa
| | Author: spam ham
| | Date:   Fri Jun 13 07:18:10 2014 +0900
| |
| |     Merge branch 'new'
| |
| * commit cf904aaea51881fd56c0d2524074306ce192a6b8
|/  Author: spam ham
|   Date:   Fri Jun 13 07:17:20 2014 +0900
|
|       add new_file1
|
* commit 1566a75df4f3e81fb115ccbe4e9897efa0094703
  Author: spam ham
  Date:   Fri Jun 13 07:16:02 2014 +0900

      add master_file1

$ git reset --hard HEAD^
HEAD is now at 1566a75 add master_file1

$ ls <-newブランチからマージしたファイルが消えている
master_file1

$ git log --graph <-ログからもマージした履歴が消えている
* commit 1566a75df4f3e81fb115ccbe4e9897efa0094703
  Author: spam ham
  Date:   Fri Jun 13 07:16:02 2014 +0900

      add master_file1
```

間違って実行した`git reset --hard`を取り消したい
--------------------------------------------------

* コマンド
  * `git reflog`でリポジトリの操作履歴を確認
  * `git reset --hard`で間違いを修正する

* 目的
  * 間違って`git reset --hard`を実行した場合に、その操作を取り消し、コミットを復活させる

* 特徴
  * `git reflog`コマンドによって、過去の操作履歴を確認し、その履歴情報をもとに、`git reset --hard`で
　  削除されたコミットを復活します。
    `git reflog`で閲覧できる履歴情報はデフォルト設定で**30日間**保持されます。

* 実行例

ここでは、`git reset --hard HEAD^`として、最新のコミット(4ac1275)を取り消し、1つ前のコミット(0394e66)に
リセットしようとしたが、誤って`git reset --hard HEAD^^`として、2つ前のコミット(24ed0b8)にリセットして
しまった場合を想定しています。

```bash
$ git log --oneline
4ac1275 add testfile4　: 現在(HEAD)
0394e66 add testfile3  : 1つ前のコミット(HEAD^)
24ed0b8 add testfile2  : 2つ前のコミット(HEAD^^)
81e4af0 add testfile1  : 3つ前のコミット(HEAD^^^)

$ ls
testfile1  testfile2  testfile3  testfile4

$ git reset --hard HEAD^^ <-誤った操作
HEAD is now at 24ed0b8 add testfile2

$ git log --oneline
24ed0b8 add testfile2
81e4af0 add testfile1

$ ls
testfile1  testfile2

[shyamahira@www 20140616]$ git reflog
24ed0b8 HEAD@{0}: HEAD^^: updating HEAD
4ac1275 HEAD@{1}: commit: add testfile4
0394e66 HEAD@{2}: commit: add testfile3 <-これを復活させたい
24ed0b8 HEAD@{3}: commit: add testfile2
81e4af0 HEAD@{4}: commit (initial): add testfile1

$ git reset --hard HEAD@{2}
HEAD is now at 0394e66 add testfile3

$ git log --oneline
0394e66 add testfile3
24ed0b8 add testfile2
81e4af0 add testfile1

$ ls
testfile1  testfile2  testfile3
```

  
`git add`でインデックスに追加したファイルをインデックスから外す
-----------------------------------------------------------------

* コマンド
  `git reset [filename]`

* 目的
  `git add`でインデックスに追加したファイルをインデックスから外す

* 実行例

```bash
$ touch testfile2

$ git add testfile2

$ git status
# On branch master
# Changes to be committed:
#   (use "git reset HEAD <file>..." to unstage)
#
#       new file:   testfile2
#

$ git reset testfile2

$ git status <-インデックスから外れていることが確認できる。
# On branch master
# Untracked files:
#   (use "git add <file>..." to include in what will be committed)
#
#       testfile2
nothing added to commit but untracked files present (use "git add" to track)
```

過去のコミットの取り消し
-------------------------

* コマンド
  `git revert`

* 目的
  * 過去のコミットを取り消したい場合に利用

* 特徴
  * 取り消した履歴を明示的に残す

* `git reset`をすべきか? `git revert`をすべきか?

  * 取り消したいコミットが直前のものではない場合、なるべく`git revert`をした方がよい。
    なぜなら、`git reset`の場合、指定したコミットまで巻き戻ることになる。
    つまり、`git reset --hard HEAD^^`とした場合、現在(HEAD)からHEAD^^までが取り消されるため
    途中経過のHEAD^も失うことになる。
    `git revert`の場合は、指定したコミットのみを取り消すため、そのようなことにならない。

  * 取り消したい**コミット**がリモートリポジトリに公開済みの場合は、リモートリポジトリバージョンとの
    整合性がとれなくなるため、`git revert`をした方がよい。

* 注意
  * `git reset`は指定したコミットの状態にするのに対し、`git revert`は指定したコミットをピンポイントで取り消す。
    つまり、`git reset`の場合は"現在～指定したコミットの間のコミット"を取り消すことになり、指定したコミットが
    取り消されるわけではない。

* 実行例

```bash
$ git log --oneline
0394e66 add testfile3
24ed0b8 add testfile2
81e4af0 add testfile1

$ ls
testfile1  testfile2  testfile3

$ git revert --no-edit 24ed0b8 <--HEAD^を取り消す
Finished one revert.
[master 1c6720d] Revert "add testfile2"
 0 files changed, 0 insertions(+), 0 deletions(-)
 delete mode 100644 testfile2

$ git log --oneline
1c6720d Revert "add testfile2"
0394e66 add testfile3
24ed0b8 add testfile2
81e4af0 add testfile1

$ ls <- HEAD^でコミットしたファイルのみが消えており、HEADでのコミットファイルは消えていない
testfile1  testfile3
```


ファイルを過去のある時点に戻す
-------------------------------

* コマンド
  `git checkout [commit] [filename]`

* 目的
  * `git rm`で誤ってファイルを削除してしまった場合や、特定のファイルのみを過去の状態に戻したい時に使用

* 特徴
  * `git reset`コマンドの場合、`--mixed`、`--hard`、`--soft`のオプションを使用すると 引数には、
    コミットしか指定できません。そのため、特定のファイルのみを過去のある時点に戻す場合は
    `git checkout`を利用します。

* 実行例

`git rm`でファイルを削除してしまった場合

```bash
$ ls
file1  file2  file3

$ git rm file2 <-誤ってファイルを消してしまった
rm 'file2'

$ ls
file1  file3

$ git checkout HEAD -- file2

$ ls <-file2が復活している
file1  file2  file3
```

特定のファイルを過去の状態に戻したい時

```bash
$ vi testfile1
add

$ git add testfile1

$ git commit -m "add testfile1"
[master (root-commit) 4c9d6bb] add testfile1
 1 files changed, 1 insertions(+), 0 deletions(-)
 create mode 100644 testfile1

$ vi testfile1
edit1 <-testfile1に追記

$ git commit -am "edit1 testfile1"
[master 70161ba] edit1 testfile1
 1 files changed, 1 insertions(+), 0 deletions(-)

$ vi testfile1
edit2 <-testfile1に追記

$ git commit -am "edit2 testfile1"
[master f83f195] edit2 testfile1
 1 files changed, 1 insertions(+), 0 deletions(-)

$ vi testfile1
edit3 <-testfile1に追記

$ git commit -am "edit3 testfile1"
[master 2a335fe] edit3 testfile1
 1 files changed, 1 insertions(+), 0 deletions(-)

$ git log --oneline
2a335fe edit3 testfile1
f83f195 edit2 testfile1
70161ba edit1 testfile1
4c9d6bb add testfile1

$ cat testfile1
add
edit1
edit2
edit3

$ git checkout f83f195 -- testfile1 <-testfile1を過去の状態に戻す

$ cat testfile1
add
edit1
edit2
```