もっと怠惰に Git を使うシェルの設定でもっとラクをする、あるいは賢い st の話。そしておまけ。

[Git Advent Calendar / Jun.](http://qiita.com/advent-calendar/git) 22日目の記事です。21日目は[git最強のオプション filter-branch](http://qiita.com/items/f60c391b5dbf569a1d12)でした。

## gst には怠惰さが足りない

さて、12日目に[怠惰にgitを使うためのシェル活用術、またはgst一族](http://qiita.com/items/1f01aa09ccf148542f21)という記事がありました。これ、僕に言わせればまだ怠惰さが足りません。 `alias gst='git status'` などを設定すると便利という内容なのですが、 `gst` は長い。3文字です。g を取って `st` でいいじゃん。 `gb` も g を取って `b` でいいじゃん。

こう反論する向きもあるかもしれません。「自分は Mercurial や Subversion も使っていて `alias hst='hg status'; alias sst='svn status'` と設定している。区別と統一性のために g が必要なのだ。てゆーか短いエイリアスってヤじゃね」

でもやっぱり `gst` は長い。3文字です。カレントリディレクトリが Git リポジトリなのか Mercurial リポジトリなのかは明らかですから、人間様がプレフィクスをつけて `gst` やら `hst` やら使い分けなくても、コマンドの側が空気を読んでくれないものでしょうか。

たとえば、こんな `st` コマンドを想像してみてください。Git リポジトリの中で `st` と叩けば `git status` になり、 Mercurial リポジトリの中で `st` すれば `hg status` になる。 Subversion リポジトリの中での `st` は当然 `svn status` を実行する。コンピュータと言えどそれくらいの融通は利いてくれてもいいと思いませんか。

思いませんか、と自分の MacBook に語り掛けてみてもヨシきたお前さんの言うとおりにしてやらあと袖まくりしてくれるわけではないので、てやんでいべらぼうめとか言いながら自分で Zsh スクリプトを書きました。

https://github.com/uasi/zsh-context-sensitive-alias

この csa.zsh を使うと上述の賢い `st` が実現できます。

## 賢い st の使い方

賢い `st` はこんな具合に賢いです。

```

# Git リポジトリに cd すると st == git status になる
$ cd ~/git-repo
$ st
## master
?? hoge

# 引数も普通に渡せる
$ st --ignored
## master
!! .DS_Store
?? hoge

# Mercurial リポジトリに cd すると st == hg status になる
$ cd ~/hg-repo
$ st
? fuga

# リポジトリでないディレクトリでは st は未定義
$ cd ~/not-a-repo
$ st
zsh: command not found: st

```

.zshrc の設定はだいたいこんな感じです。

```zsh:.zshrc

# csa.zsh を読み込む
source csa.zsh

# 初期化する
# （csa_init 以降 alias コマンドが正しく動かなくなるので、
# これ以下の設定は .zshrc の終わりあたりに書くとよい）
csa_init

# エイリアスを定義する
# csalias <ctx> <alias> <cmd>
#     ctx  : コンテキストを表す任意の文字列
#     alias: エイリアス名
#     cmd  : エイリアスに割り当てるコマンド
csalias ctx_git st 'git status -sb'
csalias ctx_hg  st 'hg  status'
csalias ctx_svn st 'svn status'

# ディレクトリを移動したときコンテキストが設定されるようにする
# （chpwd という名前の関数は、 cd したとき必ず呼ばれる）
function chpwd() {
    # カレントディレクトリがどの種類のリポジトリか判別し、
    # それに合わせてコンテキストを選ぶ
    local -a ctx
    if [[ -n `git rev-parse --is-inside-work-tree 2> /dev/null` ]]; then
        ctx+=ctx_git
    elif [[ -n `hg root 2> /dev/null` ]]; then
        ctx+=ctx_hg
    elif [[ -d .svn ]]; then
        ctx+=ctx_svn
    fi
    
    # コンテキストを設定する
    # csa_set_context [<ctx> [<ctx> …]]
    #     ctx: コンテキストを表す任意の文字列
    csa_set_context $ctx
}

```

これで `st` の2文字さえ覚えればどのリポジトリでもステータスを表示できるようになりました。 `gst` と `hst` と `sst` の計9文字を使い分けていたのに比べると、覚えるべき文字数を78%も削減できたことになります。

僕は他にも以下のような設定をしています。1文字エイリアスこそ怠惰の極みです。

```zsh:.zshrc

csalias ctx_git b 'git branch'
csalias ctx_git c 'git commit'
csalias ctx_git d 'git diff'
csalias ctx_git l 'git log --decorate'
csalias ctx_git m 'git merge --no-ff'
# hg と svn の設定は略

```

## まだまだ怠惰さが足りない？
と、作るには作ってみましたが、他に活用できる場面を思いつきません。怠惰さが足りないのかもしれません。[以前書いた記事](http://qiita.com/items/54af83acdc232ee0f975)を焼き直してこの記事を仕立てる程度の怠惰さでは不足なのでしょうか。

僕よりも怠惰な皆さんはこのスクリプトのうまい活用法をきっと見つけてくれるものと信じています。あるいは怠惰道を極め、念じるだけで `st` できる手段を編み出してください。以上です。

## 追伸
ほとんど宣伝だし使い回しだし Git の話もしてないと書き上げてから気づいたので、適当に便利そうな小ネタをおまけしてお茶を濁すことにします。

### insteadOf
.gitconfig に

```ini:.gitconfig

[url "git@github.com:USERNAME/"]
	insteadof = gh:

```

こう書いておくと `gh:repo` で GitHub にある自分の `repo` リポジトリにアクセスできます。 `git clone gh:repo` とか `git push gh:repo master` とか。便利ですね。

### pushInsteadOf
GitHub にあるリポジトリには様々なプロトコルでアクセスできます：

- `https://github.com/USERNAME/repo.git`
- `git://github.com/USERNAME/repo.git`
- `git@github.com:USERNAME/repo.git`

上の2つはリードオンリーなため、 push するとエラーが表示されます。

```

$ git push origin master  # (origin == git://github.com/USERNAME/repo.git)
fatal: remote error: 
  You can't push to git://github.com/USERNAME/repo.git
  Use git@github.com:USERNAME/repo.git

```

コミット権限のないリポジトリをリードオンリーな URL から clone したけれど、後からやっぱりコミッタになった、というときは origin の URL を設定し直さなければならず不便ですね。

ところが pushInsteadOf を使うと push 先の URL を書き換えることができます。

```ini:.gitconfig

[url "git@github.com:"]
    pushinsteadof = "https://github.com/"
    pushinsteadof = "git://github.com/"

```

こう設定しておくだけで、 `git://github.com/...` への push が `git@github.com:...` への push に置き換わります。

```

$ git push origin master  # (origin == git://github.com/USERNAME/repo.git)
Everything up-to-date

```

便利ですね。