Tig で Git を自由自在に操作するための .tigrc 設定例

本記事は [Git Advent Calendar 2016](http://qiita.com/advent-calendar/2016/git) の12日目です。執筆者は [sfus](http://qiita.com/sfus) です。

# はじめに

[Tig](https://jonas.github.io/tig/) は軽量でグラフィカルな ncurses ベースのテキストユーザインタフェース Git クライアントです。ターミナル上で Git GUI クライアントのような表示・操作を行うことができる便利なツールです。

本記事では既に Tig を利用している方向けに、より便利になる設定をご紹介します。

（Tig に詳しくない方は次の記事が詳しいと思います）

[tigでgitをもっと便利に！ addやcommitも \- Qiita](http://qiita.com/suino/items/b0dae7e00bd7165f79ea)

# カスタマイズ

`tig` を起動して `h` を押せばキーバインド一覧が表示されますが、デフォルトのキーバインドは最低限度のものしか設定されていません。

ユーザは `~/.tigrc` に設定を書くことで Tig の表示やキーバインド設定をカスタマイズすることができます。

.tigrc の書き方については次の記事が詳しいです。

[やけに丁寧なtigのキーバインド設定ガイド \- Qiita](http://qiita.com/yoshikazusawa/items/3eaa6db78fa348d38bfe)


より詳細な内容については本家のマニュアル [tigrc\(5\)](http://jonas.nitro.dk/tig/tigrc.5.html) を参照してください。

また、デフォルトの設定は [github 上](https://github.com/jonas/tig/blob/master/tigrc)で確認することができます。


## 外観設定

まず外観設定の設定例です。

### 各ビュー設定

```.tigrc
# main view の左端にコミットIDを表示する
set main-view = id date author:email-user commit-title:graph=yes,refs=yes

# blame view の行数表示をON、intervalを1に
set blame-view  = date:default author:email-user id:yes,color line-number:yes,interval=1 text

# Pager系の行数表示をON、intervalを1に (default: line-number:no,interval=5)
set pager-view  = line-number:yes,interval=1 text
set stage-view  = line-number:yes,interval=1 text
set log-view    = line-number:yes,interval=1 text
set blob-view   = line-number:yes,interval=1 text
set diff-view   = line-number:yes,interval=1 text:yes,commit-title-overflow=no
```

`line-number:yes` で画面の左端に行数を表示することができます（行数表示は対象ソースの行数ではなくそのビューにおける行数なので注意してください）。interval のデフォルトは 5 なので、1, 5, 10, 15, ... のような表示になっているのですが、これはちょっと見づらいので 1, 2, 3, 4, ... と表示されるように `interval=1` としています。

 各行に行数表示がないとスクロールバーのない Tig では全体での位置を見失いがちなのでお勧めです。（一応メニューバー見たら行数や位置の%はわかりますが） 


ちなみに行数表示だけを設定するには次のように書きます（上記でまとめて設定していれば記述不要です）

```.tigrc
# 行数表示をON
set blob-view-line-number = yes
set blame-view-line-number = yes
```

### その他の設定

```.tigrc
# # 画面を垂直方向に分割する
# set vertical-split = yes
#
# 横分割に強制する
set vertical-split = no
#
# # デフォルト値
# set vertical-split = auto

# utf-8 文字で画面描画する (~でトグル)
set line-graphics = utf-8

# タブサイズ
set tab-size = 4

# # 空白無視
# set ignore-space = all

# 空白無視 (status-view では表示するが diff-view では無視する) (W でトグル)
set ignore-space = at-eol

# 検索時に大文字小文字を無視する (default: false)
set ignore-case = true

# 水平分割したウィンドウの下画面サイズを % で指定（行数指定も可）
set split-view-height = 80%

# マージ diff を表示する
set diff-options = -m --first-parent

# マウスを有効にする
set mouse = true

# '+行数' の形で Editor に行番号を引数として渡すか
set editor-line-number = yes

# リフレッシュするタイミング
set refresh-mode = auto
#set refresh-mode = after-command
#set refresh-mode = periodic
#set refresh-interval = 1
```

`line-graphics` は端末が多バイト文字を表示できるなら `utf-8` が一番見やすいと思います。

マージ diff の設定は[こちら](http://qiita.com/YasuOza/items/7be136f03867d20b8332)を参考にしました。

### diff-highlight 利用設定 (追記)

[Tig 2.2.1](https://github.com/jonas/tig/releases/tag/tig-2.2.1) で diff-highlight が diff-view で使えるようになりました。tig 2.2.1 以降かつ diff-highlight を入れている場合、次の設定を入れることで単語単位での差分ハイライトが有効になります。

```.tigrc
# 差分表示に diff-highlight を使う (要 tig 2.2.1 以降. diff-highlight が $PATH にあること)
set diff-highlight = true
```


### カラー設定

```.tigrc
# カラー設定
# diff-view で境界が見やすくなるように変更
color  diff-header       default magenta
color  diff-chunk        black   white
```

ダークカラー端末の場合、個人的に見やすいと思って上記の設定を追加しています。


## キーバインド設定

続いてキーバインド設定の例を説明します。前提として、筆者は Emacs ユーザなので Emacs のキーバインドになぞらえて設定している箇所がありますので、違和感を感じるようだったら別のキーバインドにしてみてください。

キーバインドを設定するには `bind ビュー名` につづいて `キー` と `コマンド` を書いていきます。全ビューを対象にしたい場合は `generic` を指定します。

### 移動系

#### 最上/最下部移動

```.tigrc
# g をファイル先頭に移動に変更、 view-grep を再割当て
bind generic g      move-first-line
bind generic E      view-grep

# G でファイル末尾に移動 (default: :toggle commit-title-graph)
bind generic G      move-last-line
bind main    G      move-last-line

# Alt-g で :toggle commit-title-graph
bind main    <Esc>g :toggle commit-title-graph
```

`g` や `G` は vim っぽいキーバインドにしています。もとのコマンドは `Alt-g` に退避させています。

#### 段落移動

```.tigrc
# n / p を上下移動に割り当て (default: find-next / view-pager)
bind generic n      move-down
bind generic p      move-up

# n / p で単位ごとの移動
bind diff    n      :/^@@
bind diff    p      :?^@@
bind diff    <Esc>n :/^diff --(git|cc)
bind diff    <Esc>p :?^diff --(git|cc)
bind stage   n      :/^@@
bind stage   p      :?^@@
bind stage   <Esc>n :/^diff --(git|cc)
bind stage   <Esc>p :?^diff --(git|cc)
bind pager   n      :/^@@
bind pager   p      :?^@@
bind pager   <Esc>n :/^diff --(git|cc)
bind pager   <Esc>p :?^diff --(git|cc)
bind log     n      :/^commit
bind log     p      :?^commit

```

`n`/`p` で hunk やコミット単位ごとに、`Alt-n`/`Alt-p`でファイルごとにジャンプするようにしています。通常の `j`/`k` と組み合わせることでより高速なページ内移動ができます。（上下移動は `J`/`K` は使わずに `Ctrl-n`/`Ctrl-p` を使っています）

#### その他

```.tigrc
# 元の n / p を別に割り当て
bind generic V      view-pager
bind generic <Ctrl-s> find-next # 要 stty stop undef
bind generic <Ctrl-r> find-prev

# Ctrl-v, Alt-v でページ単位移動 (ターミナルに食われるので Ctrl-v は2度押しが必要)
bind generic <Ctrl-v> move-page-down
bind generic <Esc>v move-page-up

# Ctrl-f, Ctrl-b で左右スクロール
bind generic <Ctrl-f> scroll-right
bind generic <Ctrl-b> scroll-left

# Ctrl-g でリフレッシュ
bind generic <Ctrl-g> refresh
```

その他 Emacs 系移動キーバインドを割り当てています。 `Ctrl-s` は通常端末のストップに割り当てられていると思うので、 .bashrc とかに `stty stop undef` を設定しておく必要があります。


### 各種 Git コマンド割り当て

外部コマンドを指定するには外部コマンドの前に `!`, `?`, `@` のいずれかを付けます。`!` は通常の外部コマンドの即時実行、`?` は実行前にコマンド内容の確認が出る指定、`@` はバックグラウンド実行して出力を捨てる指定になります。

#### fetch / pull / push

```.tigrc
# F で fetch (default: :toggle file-name / :toggle commit-title-refs)
bind generic F      ?git fetch %(remote)
bind main    F      ?git fetch %(remote)

# Alt-f で :toggle file-name / :toggle commit-title-refs
bind generic <Esc>f :toggle file-name
bind main    <Esc>f :toggle commit-title-refs

# U で pull
bind generic U      ?git pull %(remote)

# Alt-u で該当ブランチを更新
bind main    <Esc>u ?sh -c "git checkout %(branch) && git pull %(remote) --ff-only && git checkout -"
bind diff    <Esc>u ?sh -c "git checkout %(branch) && git pull %(remote) --ff-only && git checkout -"
bind refs    <Esc>u ?sh -c "git checkout %(branch) && git pull %(remote) --ff-only && git checkout -"

# P で remote への push
bind generic P      ?git push -u %(remote) %(repo:head)
```

`U` を `git pull`、`P` を `git push` にしています。

push 対象のブランチを指定するのに `%(branch)` ではなく `%(repo:head)` を指定していますが、これは [Tig 2.2](https://github.com/jonas/tig/releases/tag/tig-2.2) からの機能で、それまでは次のように `git rev-parse --abbrev-ref HEAD` を指定していました。

`` bind generic P      ?sh -c "git push -u %(remote) `git rev-parse --abbrev-ref HEAD`" ``

これは `%(branch)` を指定すると、カレント行のブランチの upstream がない場合に空になってしまうので、一番最初にローカルブランチを push するときにそのブランチ名がセットできない、という問題があったためです。

`git rev-parse --abbrev-ref HEAD` はカレントブランチのブランチ名を取るコマンドなので、これを指定することでその問題を解決していました。（がもう不要になりました）

#### commit

```.tigrc
# C で commit (refs での C は checkout, main での C は cherry-pick だが上書きする)
bind generic C      !git commit
bind refs    C      !git commit
bind main    C      !git commit

# Alt-c で commit --allow-empty
bind generic <Esc>c !git commit --allow-empty

# + で commit --amend
bind generic +      !git commit --amend --allow-empty
```

`C` はデフォルトで設定されている数少ない外部コマンドですが、各ビューで動作が違うのが好きではないので自分はどのビューでも commit になるようにしています。`--allow-empty` は差分が空のコミットを許可する設定です。

`git --ammend` はコミットに追加する、というイメージで `+` に割り当てています。（オフィシャルドキュメントの設定例でも `+` になっています）


#### reflog


```.tigrc
# L で reflog
bind generic L      !sh -c "git reflog --pretty=raw | tig --pretty=raw"

# _ で指定 reflog 番号にリセット
bind generic _      ?git reset --hard HEAD@{"%(prompt Enter HEAD@{} number: )"}
```

`tig` コマンドは、git の出力をパイプで渡してあげることで view モードとして動きます。上記設定により、 `L` で reflog の出力を tig で見ることができます。

そのまま戻したい `HEAD@{X}`の番号 X を確認したら、そのまま `_` でその番号 X を指定することで該当 reflog に戻れるようにしています。(残念ながら view モードなのでカレント行の番号を自動でセットするのは難しそう)

ちなみに Emacs での undo は `C-x _` がデフォルトのキーバインドなので(普通は `C-/` 使うと思いますが) `_` に割り当てています。

なお、 `!sh -c "git...` の部分を `@sh -c "git...` とすれば reflog の view-mode を抜けたときに確認の Enter を押さなくて良くなるのですが、 tig を終了させたときに tig の表示が残ってしまうので諦めて ! にしています。


#### reset --soft

```.tigrc
# ^ で reset --soft
bind generic ^      ?git reset --soft HEAD^
```

作業の途中で別ブランチを操作する時、stash save する事もあれば、いったん仮コミットしておく事もありますが、仮コミットした際は別ブランチ作業を終えて戻ってくるときに `^` で直前のコミットをステージング状態に戻せるようにしています。

HEAD^ に戻るので `^` を割り当てています。


#### diff

```.tigrc
# . で HEAD との diff
bind main    .      !sh -c "git diff %(commit) | tig"
bind diff    .      !sh -c "git diff %(commit) | tig"
bind refs    .      !sh -c "git diff %(branch) | tig"

# Alt-. で指定 commit ID との diff
bind main    <Esc>. ?sh -c "git diff %(commit)..%(prompt Enter commit ID: ) | tig"
bind diff    <Esc>. ?sh -c "git diff %(commit)..%(prompt Enter commit ID: ) | tig"
bind refs    <Esc>. ?sh -c "git diff %(branch)..%(prompt Enter branch name: ) | tig"
```

`.` で指定コミットから HEAD までの diff を見れるようにしています。`git diff` で `コミットID..コミットID` と指定するところから `.` を割り当てています。

出力は reflog のときと同様に `tig` にパイプで渡しています。

`Alt-.` の方で渡す commit ID は後ほど書いている commit ID コピーコマンドでコピーしておきます。


#### 親コミットへの移動

```.tigrc
# ~ で親コミットに移動 (default: :toggle line-graphics)
bind generic ~      :goto %(commit)^

# Alt-~ で表示のトグル (default: ~)
bind generic <Esc>~ :toggle line-graphics
```

上記 diff の設定では `%(commit)^` ではなく `%(commit)` を対象としているので、そのコミットIDでの diff は含まれません。それを含めるためには一つ親のコミットで `.` を実行する必要がありますが、マージが入り組んでくると親を探すのが大変になることがあります。そんな時、[Tig 2.2](https://github.com/jonas/tig/releases/tag/tig-2.2) で `:goto` コマンドが実装されたので、上記設定により一つ親に飛ぶことができるようになりました。一つ親なので `~` を割り当てています。


#### stash

```.tigrc
# S で stash save
bind generic S      ?git stash save "%(prompt Enter stash comment: )"

# Y で stash pop
bind diff    Y      ?git stash pop %(stash)
bind stash   Y      ?git stash pop %(stash)

# Alt-y で stash apply
bind diff    <Esc>y ?git stash apply %(stash)
bind stash   <Esc>y ?git stash apply %(stash)
```

デフォルトでは stash-view での `P` が `git pop` に割り当てられていますが、`git push` と混同したくないのと、`y` -> (stash-view) -> `Y` -> (y/n) `y` と連打で `stash pop` できるのが便利なので `Y` にしています。

#### git reset --hard

```.tigrc
# H で reset --hard
bind main    H      ?git reset --hard %(commit)
bind diff    H      ?git reset --hard %(commit)
bind refs    H      ?git reset --hard %(branch)

# H で reset --hard HEAD (status-view)
bind status  H      ?git reset --hard HEAD
```

`H` でそのコミットに HEAD を移動させます。--hard なので `H` に割り当てています。status-view では作業中の変更をリセットします。

#### 削除系

```.tigrc
# D でブランチを削除 (マージ済みブランチのみ. 強制削除(branch -D) は `!`) (refs-view)
bind refs    D      ?git branch -d %(branch)

# D でトラッキングされていないファイルを削除 (status-view)
bind status  D      ?git clean -d -f -- %(file)

# D で stash を削除 (`!` と同じ) (stash-view)
bind stash   D      ?git stash drop %(stash)

# D でファイルを削除 (tree-view)
bind tree    D      ?git rm %(file)

# x でトラッキングされていないファイルをすべて削除
bind main    x      ?git clean -d -f
bind status  x      ?git clean -d -f

# x でマージ済みローカルブランチをすべて削除 (master/develop 除く)
bind refs    x      ?sh -c "git branch --merged | grep -vE '^\\*|master$|develop$' | xargs -I % git branch -d %"
```

git で消す操作がいつも混乱するので `D` でうまいこと消してくれるように割り当てています。

`x` では不要ファイルを一気に消してしまう処理を割り当てています。マージ済みのローカルブランチを削除するコマンドは[こちら](http://qiita.com/kyanny/items/10a57a4f1d2806e3a3b8)を参考にしています。

ちなみに remote のマージ済みブランチは `git fetch --prune` で消すか、 `~/.gitconfig` に以下の設定を書くことで `git fetch` で消えてくれます。

```.gitconfig
[fetch]
	prune = true
```


#### commit ID コピー

```.tigrc
# ` で commit ID をクリップボードにコピー (Mac用)
bind generic `      @bash -c "echo -n '%(commit)' | pbcopy"

# ` でファイルパスをクリップボードにコピー (Mac用)
bind stage   `      @bash -c "echo -n '%(file)' | pbcopy"
bind status  `      @bash -c "echo -n '%(file)' | pbcopy"
bind tree    `      @bash -c "echo -n '%(file)' | pbcopy"
bind blob    `      @bash -c "echo -n '%(file)' | pbcopy"
```

Mac用なのでその他プラットフォームの方は適当に読み替えをお願いします。この設定は[こちら](http://qiita.com/yuyuchu3333/items/1f6a0c121ad42c24dfd9)を参考にしています。

`` ` `` の連想はこれといって無いですが、強いて言えば shell のコマンド置換で使うバッククオートに見立てて `` ` `` にしています。

#### revert

```.tigrc
# ! で revert
bind main    !      ?git revert %(commit)
```

Tig では `!` が削除・取り消し系なので revert も main-view での `!` に割り当てています。


#### cherry-pick

```.tigrc
# K で cherry-pick (default: 'C')
bind main    K      ?git cherry-pick %(commit)
bind diff    K      ?git cherry-pick %(commit)

# Alt-k で cherry-pick -n (--no-commit)
bind main    <Esc>k ?git cherry-pick -n %(commit)
bind diff    <Esc>k ?git cherry-pick -n %(commit)
```

デフォルトでは main-view `C` ですが、上記の通り commit に割り当てているので、 cherry-pic'K' で `K` にしています。--no-commit はその名の通り commit せずに cherry-pick を当てていくオプションです。

#### checkout


```.tigrc
## 以下、Shift付きでブランチ指定、Alt付きでコミットID指定のコマンド

# = で checkout
bind main    =      ?git checkout %(branch)
bind refs    =      ?git checkout %(branch)
bind main    <Esc>= ?git checkout %(commit)

# - で直前のブランチに戻る (checkout -)
bind generic -      ?git checkout -
```

`=` はそのブランチにする、という意味から割り当てています。

#### tag/branch

```.tigrc
# T で tag
bind main    T ?git tag "%(prompt Enter tag name: )" %(commit)
bind refs    T ?git tag "%(prompt Enter tag name: )" %(branch)

# B でブランチを作成してそのブランチに移動 (checkout -b)
bind main    B      ?git checkout -b "%(prompt Enter branch name: )" %(branch)
bind refs    B      ?git checkout -b "%(prompt Enter branch name: )" %(branch)
bind main    <Esc>b ?git checkout -b "%(prompt Enter branch name: )" %(commit)
```

`git checkout -b` はお好みで `git branch` に。（作ったブランチに移動するかどうかの差）

#### merge/rebase

```.tigrc
# M で merge
bind main    M      ?git merge %(branch)
bind diff    M      ?git merge %(branch)
bind refs    M      ?git merge %(branch)
bind main    <Esc>m ?git merge %(commit)
bind diff    <Esc>m ?git merge %(commit)

# R で rebase
bind main    R      ?git rebase %(branch)
bind diff    R      ?git rebase %(branch)
bind refs    R      ?git rebase %(branch)
bind main    <Esc>r ?git rebase %(commit)
bind diff    <Esc>r ?git rebase %(commit)

# I で rebase -i
bind main    I      ?git rebase -i %(branch)
bind diff    I      ?git rebase -i %(branch)
bind refs    I      ?git rebase -i %(branch)
bind main    <Esc>i ?git rebase -i %(commit)
bind diff    <Esc>i ?git rebase -i %(commit)
```

よく使うコマンドなのでキーバインドは連想しやすいものにしています。

ちなみに Emacs を使う場合、 magit を入れておくと `rebase -i` バッファを非常に快適に編集することができるのでお勧めです。筆者の `~/.gitconfig` は以下のようにしています。

```.gitconfig
[core]
	editor = "/usr/local/bin/emacsclient -nw -a \"\""
```


#### GitHub 連携

GitHub のコマンドラインツール [hub](https://github.com/github/hub) をインストールしておくことで色々便利な操作ができます。

```.tigrc
# ; で GitHub の該当コミットを開く ※要 hub インストール
bind main    ;      @hub browse -- commit/%(commit)
bind blame   ;      @hub browse -- commit/%(commit)
bind diff    ;      @hub browse -- commit/%(commit)

# ; で GitHub の該当ページを開く
bind tree    ;      @hub browse -- blob/%(branch)/%(file)
bind blob    ;      @hub browse -- blob/%(branch)/%(file)
bind grep    ;      @hub browse -- blob/%(branch)/%(file)

# Alt-; で GitHub の指定ブランチの該当ページを開く
bind tree    <Esc>; @hub browse -- blob/"%(prompt Enter branch name: )"/%(file)
bind blob    <Esc>; @hub browse -- blob/"%(prompt Enter branch name: )"/%(file)
bind grep    <Esc>; @hub browse -- blob/"%(prompt Enter branch name: )"/%(file)

# w で GitHub の該当ブランチの Pull Request 作成ページを開く ※要 hub インストール
bind main    w      @hub browse -- compare/%(branch)?expand=1
bind diff    w      @hub browse -- compare/%(branch)?expand=1
bind refs    w      @hub browse -- compare/%(branch)?expand=1

```

`;` についても特に元ネタはありません… 外部コマンドということでセパレータの `;` にしたつもりです。


ちなみに GitHub Enterprise の場合、`~/.gitconfig` に以下の様な設定を追加してカスタマイズします。

```.gitconfig
[hub]
    protocol = http
    host = git.example.com
```

# まとめ

Tig は慣れると非常に高速な Git 操作ができる上、ポータビリティ性も高く、非常に柔軟なカスタマイズができるので、コマンドライン派も GUI 派もぜひ使ってみて欲しいツールだと思っています。

本設定はあくまで筆者の手に馴染む設定（そして日々調整が続いている設定）なので、ぜひ皆さんも自分自身の手に馴染む設定を見つけていってください。

最後までご覧いただきありがとうございました。


# 参考リンク

## 本家

- [Introduction · Tig \- Text\-mode interface for Git](https://jonas.github.io/tig/)

- [tigrc\(5\)](http://jonas.nitro.dk/tig/tigrc.5.html)

- [tig/tigrc at master · jonas/tig](https://github.com/jonas/tig/blob/master/tigrc)

## その他

- [tigでgitをもっと便利に！ addやcommitも \- Qiita](http://qiita.com/suino/items/b0dae7e00bd7165f79ea)

- [やけに丁寧なtigのキーバインド設定ガイド \- Qiita](http://qiita.com/yoshikazusawa/items/3eaa6db78fa348d38bfe)

- [tigでmerge diffを表示する \- Qiita](http://qiita.com/YasuOza/items/7be136f03867d20b8332)

- [Git でマージ済みのブランチを一括削除する \- Qiita](http://qiita.com/kyanny/items/10a57a4f1d2806e3a3b8)

- [github/hub: hub helps you win at git\.](https://github.com/github/hub)