この記事について
Livesense(その2) Advent Calendar7日目の記事です。
6日目はI/OスケジューラがMySQLにどの程度影響するのか確認してみると、Simplenote でブックマークレットで GitHub Flavored Markdown (GFM)でした。
はじめに
Git便利ですよね。
触り始めた当初は、git status
、git add
、git commit
、git log
くらいしか使ってませんでした。
いろんなサブコマンドに興味を持ち始めたのは、git rebase
を知ってからだと思います。
CVSやSubversionからGitに移った私としては、git rebase -i
により気軽にコミットの順番を入れ替えたり、メッセージを変更できることに衝撃を受けた記憶があります。
今回は、あまり知られてないけど(個人調べ)、知っておくと何かと役立つサブコマンド(+オプション)をご紹介します。
便利なGitサブコマンド4選
git reflog
git log -S/-G
git alias
git name-rev
git reflog
Gitのいわゆる操作履歴を時系列に表示してくれるコマンドです。
操作とはcommitはもちろん、他ブランチのcheckout、rebase、mergeなどを含みます。
とても便利ですが、rebaseやblame、stashなどに比べてあまり広く知られていない気がします。
通常作業時に使うことはほとんどないですが、ミスをしてしまったときに役立ちます。
たとえば、developブランチで開発していて、いくつかコミットしているとします。
極端な例ですが、git reset --hard orgin/develop
を間違えて叩いてしまい、青ざめてしまったり。
git log
で確認しても、無情にもコミットが消えています。
またイチから作るのも面倒臭い、なんとか復旧はできないものでしょうか。
こんなときにgit reflog
です。
さっそく叩いてみましょう。
$ git reflog --date=local -n 10
b51fc6a HEAD@{Mon Dec 7 08:29:10 2015}: reset: moving to origin/develop
787dedf HEAD@{Mon Dec 7 08:28:52 2015}: commit: 機能2
b51fc6a HEAD@{Mon Dec 7 08:27:45 2015}: commit: 機能1
5da05c3 HEAD@{Mon Dec 7 08:20:58 2015}: checkout: moving from master to develop
おお、見覚えのあるコミットログが。
戻したい時点のハッシュ値を確認し、resetしてみましょう。
$ git reset --hard 787dedf
これで元どおりです。
つまり履歴上表示されなくてもオブジェクト自体は残っているということですね。
.gitディクトリを覗くと、実際のオブジェクトを確認できます。
# .git/objects/(ハッシュ値の先頭2桁)/(ハッシュ値残り)
$ ls .git/objects/78/7dedf4532fddf57acdde17a615c9c395ed63ed
blob、tree、objectsなどGitの内部的な仕組みはGitの.git/objectsの中身を追ってみるがとてもわかりやすいです。
このコマンドを知ってから、rebaseやmergeなどのGit操作が大胆に行えるようになった気がします。
なにかあれば、任意の時点まで戻せるという安心感からでしょうか。
便利なgit reflog
ですが、デフォルトの保持期間は90日間のようです。git gc
によって削除されます。
この挙動を変えたい場合は、git config
でgc.reflogexpireをneverなどに設定することができるようです(未検証)
The optional configuration variable gc.reflogExpire can be set to indicate how long historical entries within each branch’s reflog should remain available in this repository. The setting is expressed as a length of time, for example 90 days or 3 months. It defaults to 90 days.
補足ですが、コミット自体ではなく、一部のファイルだけ戻したいケースもあると思います。
そのときはgit show --stat 787dedf
でファイルを調べて、
git checkout 787dedf -- hoge1.rb hoge2.rb
とするだけです。
git log
現在のコード(ワークツリー)を対象に検索する場合、git grep
が使えます。
では過去のコミットの差分に対して検索する場合はどうすればいいでしょうか。
例えば、hogehogeというメソッドがあるコミットで消されており、それによりメソッドが見つからずエラーが出てしまったとします。
まずは不具合を引き起こしてしまったコミットを特定したいですよね。
そんなときはgit log -S --pickaxe-regex
やgit log -G
が使えます。
-S --pickaxe-regex
-S単体では文字列検索、--pickaxe-regexをつけると正規表現による検索になります。
さっそく使ってみましょう。
$ git log -S hogehoge -p
commit 93de1b1021c974b9dd5ceee7166698af902cf34c
Author: hoge <hoge@gmail.com>
Date: Sun Dec 6 14:06:07 2015 +0100
--- a/hoge.rb
+++ b/hoge.rb
@@ -37,9 +37,7 @@ EOT
- def hogehoge
差分の中から検索できてますね。93de1b1021c97で不具合が紛れ込んでしまったようです。
ノイズが多い場合は-diff-filterや、--sinceと--after指定で期間を絞ってもいいかもしれません。
-G
-Gは-Sとほぼ同様にコミットの差分を検索してくれます。
違いはgit log --help
に記載されています。
+ return !regexec(regexp, two->ptr, 1, ®match, 0);
...
- hit = !regexec(regexp, mf2.ptr, 1, ®match, 0);
While git log -G"regexec(regexp" will show this commit, git log -S"regexec(regexp" --pickaxe-regex will not (because the number of occurrences of that string did not change)
-Gは指定した正規表現が差分の中に含まれていたら対象として表示しますが、-Sはそれだけではなく、出現回数の差分も見て判断しています。上記の場合はregexec(regexpという文字列が1つのコミットの中に+と-があり、差し引きゼロのため表示されません。
具体的な例としては、リファクタリングでメソッドを移動したときなどは-Sだと表示されません。
hogehogeというメソッドが消えたコミットを特定したいという今回の目的に対しては、ノイズが減るため-Sの方が適していそうです。
git alias
すいません、現状このようなコマンドはありません。
ただ、Gitにはエイリアスを定義する機能があり、下記のようにgitconfigファイルに設定すると他のサブコマンドと同様に実行することができます。
[alias]
alias = confg --get-regexp \"alias.*\"
br = branch
bra = branch -a -vvv
co = checkout
ls = ls-files
cm commit -m
tree log -n 15 --graph --pretty='format:%C(yellow)%h%Creset %C(green)%cd%Creset %s %C(cyan)(%an)%Creset %Cred%d%Creset'
log1 log -n 1
log3 log -n 3
ss stash save
sp stash pop
sl stash list
ref = reflog --date=local -n 10
これで、git aliasと叩いた結果はこのようになります。たくさん定義していて、忘れてしまってもこれで一目瞭然ですね。
$ git alias
alias.st status
alias.br branch
‥
$ git co HEAD
$ git ref -n 30 #このようにエイリアスで指定したオプションを上書きもできる
git name-rev
これも影の薄いコマンドですが、役立つ場面が結構あります。
ハッシュ値を渡すとhuman-readableな形式で教えてくれます。
$ git name-rev 1ee87e4
1ee87e4 master~4^2
1ee87e4はmasterブランチから数えて、4つ前のコミットの2つ目の親を表しています。
そのコミットが現状どのような状態か、さっと確認するときに便利ですね。
補足ですが、あるコミットがどのブランチに属しているかはbranchコマンドでわかります。
$ git branch --contains 1ee87e4
* master
さいごに
以上、他のエンジニアと話してて、あまり知られてないと感じたコマンドを紹介しました。
Gitもバージョンがあがるにつれ、ますます便利になってきています。
今回の記事を書くときに知りましたが、git rebase --autostash
により、git pull
する際にいちいちgit stash
する必要がなくなりました。
2.5では同一Gitリポジトリ内に作業ツリーを追加して作成することのできるgit worktree
が追加され、2.7も絶賛開発中のようです。
これからも、Gitの動向に目が離せそうにありませんね!