Git初心者が絶対に覚えておくべきコマンド

Gitの使い方を覚えるにあたって、まず知っておきたいのは――git-cloneだのgit-commitだのは当然として――「操作をミスったときにどのように回復するか」である。それを実現するのは、次の3つのコマンドだ。

  1. git-commit --amend
  2. git-reset
  3. git-reflog

git-commit --amend

あるファイルをコミットしたとしよう。

$ (edit...)
$ git commit -am 'メッセージ生成処理を実装したよ。'

しかし、しばらくして彼は気づいた。

def create_massage(param)
  ...

typoしてる!massageじゃない、messageだ!マッサージを作ってどうする!

慌てるな。まずは直してステージに上げるんだ*1

def create_message(param)
 ...
$ git add .

そして…。

$ git commit --amend

…これで、typo修正は直前のコミット時に行われたことになり、恥ずかしい歴史は消える*2

つまり、git-commit --amendは、直前のコミットを作り直す操作である。このコマンドを知っていれば、ミスしてもすぐに回復できるわけだ。コミットログを修正する目的でもよく使う。

git-reset

これに安心した彼は、意気揚々と開発に没頭する。

しかし、またやってしまった。

$ git commit -am '文字化けしないようにしたよ。'

コミット先のブランチを間違えてしまった!本当はfix_garbleブランチにコミットしたかったのに、practiceブランチでやってしまった!

慌てるな。git-resetするんだ。

$ git reset HEAD^

これでコミット前の状態に戻る。もちろん、変更内容は消えたりしない。

$ git status
# On branch practice
# Changed but not updated:
#   (use "git add <file>..." to update what will be committed)
#   (use "git checkout -- <file>..." to discard changes in working directory)
#
#       modified:   view.rb
#
no changes added to commit (use "git add" and/or "git commit -a")

あとは本来のブランチに移動してコミットすればいい。臆するな、変更内容はブランチを移動しても付いてくる。

$ git checkout fix_garble
M       view.rb
Switched to branch 'fix_garble'
$ git status
# On branch fix_garble
# Changed but not updated:
#   (use "git add <file>..." to update what will be committed)
#   (use "git checkout -- <file>..." to discard changes in working directory)
#
#       modified:   view.rb
#
no changes added to commit (use "git add" and/or "git commit -a")
$ git commit -am '文字化けしないようにしたよ。'

git-resetは、HEADの位置を変更するコマンドである。対象コミットがわかりさえすれば、そこをHEADとすることができる。HEADより未来のコミットは履歴に表れないので、存在しなくなった = 削除したも同然である。つまり、HEADを過去のコミットに戻せば、その時点より先のコミットを取り除いたことになる。

git-reflog

二度目の窮地も無事脱した。もう安心だ、これ以上変なミスはしないだろう…。

しかし、二度あることは三度あるのだ。

git-resetを覚えた彼は、後になって不要だと気づいたコミットをサクッと取り除く。しかも、変更内容が完全に不要だとわかっているから、気持ちよく--hardを使うぞ*3

$ git reset --hard HEAD^^^^

しまった!HEAD^^^までリセットしたかっただけなのに、一つ多い!必要なコミットを取り除いてしまった!あのコミット分の変更に半日は費やしたというのに…。

慌てるな。git-reflogを使うんだ。

$ git reflog
ef1f900 HEAD@{0}: head^^^^: updating HEAD
6ebb02d HEAD@{1}: commit: 修正漏れがあった。
99a7983 HEAD@{2}: commit: バグフィックスした。
f63f70b HEAD@{3}: commit: すばらしい機能を追加した。
68da592 HEAD@{4}: commit: 機能を拡張した。
ef1f900 HEAD@{5}: commit: 基本的な機能を実装した。
...

これはHEADがどのように移り変わってきたのかを示すログだ。HEAD@{0}が現在のHEAD、そして見よ、HEAD@{1}こそがgit-reset前に参照していたコミットだ。ここめがけてgit-resetし直すのだ。

$ git reset --hard HEAD@{1}

これで全て元通りだ。今度は慎重に…。

$ git reset --hard HEAD^^^

これでよし。*4

git-reflogは、HEADがこれまでどのコミットを指してきたかというログを表示するコマンドである。git-resetしてコミットを取り除いても、それは履歴中のHEADから辿れる範囲から外れたというだけで、ちゃんと残っている。そいつをgit-reflogで探し出して、git-resetで直接跳べば元通りになる。

ちなみに同様の、より詳しい情報をgit-log -gで得ることができる。

まとめ

git-reflogを使えば、自分がこれまでHEADとして参照してきたコミット、つまり自分の過去の操作を一覧できる。これを利用して操作ミス直前の時点(コミット)を特定できれば、git-resetでその時点に――それがたとえgit-logに出てこない時点であっても――戻ることができる。つまり、この二つを覚えておけば大抵の操作ミスから回復できる。回復操作自体が誤りだった、という場合でも、同様の手法でその回復操作の直前のコミットを特定して、その時点に戻れる。どうしたってやり直せるのだ。

git-commit --amendはこれらのちょっと便利なショートカットにすぎないが、頻繁に使うので挙げておいた。

Gitはそう簡単にデータを消失しないように設計されているそうなのだが、たとえ消えてなくても、残っているデータを参照する手段を知らないのでは意味がない。ということで紹介してみた。私もgit-reflogを覚えてからとてつもなく安心感を高めたクチである。これらの操作を知っていれば、よくわからないコマンドをとりあえず叩いて動作を確認してみる、といったこともより気軽に行えるというものだ。

識者の方々から見れば「git-cherry-pickでいいんじゃね?」等のツッコミもあろうかと思うが、最低限覚えていれば、という意図なのでご容赦願いたい。

他には、git-rebase -iを覚えておくと幸せかもしれない。HEADの作り直しにはgit-commit --amendを使うが、HEAD^以前を作り直したいばあいはgit-rebase -iが必要になる。

注意事項

ここで紹介した操作は、ローカルリポジトリでのみ実施すること。公開して他者と共用しているリポジトリに対してこうした履歴の操作を行うと、いろいろ面倒なことになる。git-pushするまえに、本当に公開していいのか入念に確認する必要がある。

入門Git入門Git

秀和システム 2009-09-19
売り上げランキング : 1796

Amazonで詳しく見る
by G-Tools

*1:省略してるが、もちろんテストもしよう。

*2:正しくは消えるのではなく参照しなくなるだけである。そのため、後述のgit-reflogとgit-resetを使えばまた参照できる。が、とりあえず見えなくなる。

*3:普通にgit-resetしてコミットを廃棄した場合はそのコミットでの変更内容が作業コピーに書き戻されるが、--hardを付けると変更内容も含めてバッサリ取り除かれ、全て無かったことになる。

*4:もちろん直接 git reset --hard HEAD@{4} してもいいのだが、「元に戻せる」という話をしたいので。