Git Advent Calendar 2019 1日目です。
2日目は@100さんの【VRChat】継続的なクロスプラットフォーム対応です。
TL;DR
[alias]
#以下の3種は全て同じコマンドが実行される
# 単純な置き換え
st = status -uno
# 先頭に!を付けると、その後のコマンドを外部コマンドとして("git "を付けずに)シェルスクリプトで実行
stex = !git status -uno
# 関数を使うと引数を任意の場所に展開できる
stfunc = !"f(){ git stauts -uno \"${@}\"; };f"
#はじめに
Gitも気付いたら2.24.0
になっていて(2019/12/01現在)、git switch
とか新しいコマンドが生えててどんどん使いやすくなっている訳ですが、それでも我々は記憶量とタイピング量を減らしたい。人類の脳みそはそんなに強くない。
という訳でGitというよりシェルスクリプトの初級編、エイリアスについて。CUIをちょっと楽にしような。
Gitのエイリアスとは
Git
のコマンドの「別名」を付ける機能のこと。
タイピング量を減らしたり、覚えやすいコマンド名に置き換えるために利用される。git エイリアス名
と打つと設定したコマンドが実行される。つまりシェルに任意のコマンドを一行で展開する機能として使えるので、複数のコマンドを繋げたり、それ単体で簡単なシェルスクリプトを組む芸当も可能。
設定ファイルの場所
エイリアスの設定を行うにはgit config --global alias.エイリアス名 エイリアス先のコマンド
を打つ、などとエイリアスの公式ドキュメントには書かれている。が、後述する特殊文字のエスケープに加えて「git config
コマンドを解釈するときのエスケープ」が必要になるため非常に面倒くさい。それよりはconfig
ファイルを弄ってしまった方が楽だと思う。
config
ファイルの場所は参考URLを参照のこと。
参考:gitconfig の基本を理解する
以下ではユーザー単位の~/.gitconfig
に記述するものとする。
.gitconfig
などの設定ファイルに[alias]
と書くことで、それ以降~次の[---]
までがエイリアスの設定として認識される。
基本的には1行 = 1コマンドである。複数行に記述したいときは行末に\
を置く。
#書き方まとめ
単純な置き換え
[alias]
st = status -uno
最も単純な方法。エイリアスに登録した文字列が置き換わる。
この場合、git st
と打つとgit status -uno
が実行される。従って、Gitのコマンド以外は指定できない。
オプションを付与したい場合は後ろに追記する。この場合、例えばgit st -s
と打てばgit status -uno -s
が実行される。
外部コマンド
[alias]
stex = !git status -uno
エイリアスの冒頭に!
を付与することで、git ~
が丸ごと置き換えられる。この置き換え先はシェルコマンドとして解釈される。従ってGit以外のコマンドを利用することが可能。
ちなみにこの方法で「エイリアスのエイリアス」を無限に作成できる。
[alias]
#特に意味はない
super_status = !git st
extra_status = !git super_status
ultra_status = !git extra_status
また、適切な制御記号を入れることで複数のコマンドを繋げることが可能。
[alias]
#git fetchが完了したらログを直近20個分出して、最後にstatusを確認
now = !git fetch; git graph -20 --date-order; git st
以下はエイリアスというよりシェルスクリプトの勉強内容だが、コマンド間の制御記号と効果についてはこんな感じ。
記号 | 効果 |
---|---|
; | 順番に実行 |
&& | 前のコマンドが成功したら実行 |
|| | 前のコマンドが失敗したら実行 |
| | 前のコマンドの標準出力を次のコマンドの標準入力にする |
& | 前のコマンドをバックグラウンドで実行(つまり待たない) |
一番最後の&
はコマンドの途中に入れるとエイリアス(の実行シェル)が終了しても終了しないので、基本的には外部のプログラム(エディタなど)を起動したい時などに利用する。
関数にする
[alias]
stfunc = !"f(){ git stauts -uno \"${@}\"; };f"
シェルスクリプトはf(){ コマンド }
で関数を定義し、f
でその関数を実行できる。従って"f(){ コマンド };f"
で関数を定義し、即座に実行できる。両端の""
が無いとsyntax errorとなる。
このとき、変数${1}
~${n}
に個別の引数が、${@}
に全ての引数が、${#}
に引数の数が、それ以外にもいろいろ入る。
参考:初心者向けシェルスクリプトの基本コマンドの紹介 #特別な変数
見やすさのために改行したい場合は以下の様に、行末に\
を書く。これはシェルに渡される時点で一行に戻されて実行される。
[alias]
stfunc = !"f(){ \
git stauts -uno \"${@}\";\
};f"
なお引数を使うための方法として!sh -c \"git status\"
のような記述方法もあるが、「プロセスが多重起動すること」「Gitのエスケープとshのエスケープが両方必要になって記述が面倒くさくなること」から投げ捨てた方が良い、とのこと。
参考:関数の使用方法 UNIX & Linux コマンドおよびシェルスクリプトリファレンス
参考:位置パラメーターの一括展開 $* $@ "$*" "$@" の違いを知れ!!
#登録時の注意点
大文字/小文字について
git status
などの本来のGit
のコマンドは大文字/小文字を区別しなければならない。しかし、エイリアスでst = status
と設定した場合は大文字/小文字を無視する。つまりgit ST
でもgit St
でもgit sT
でも全てgit st
と同じ動作をする。逆に言うと大文字/小文字で別のエイリアスを登録することはできない。
ついでにGitのコマンドと同名かつ大文字/小文字違い、つまりSTATUS = status
などとしても、fatal: cannot handle STATUS.exe as a builtin
として怒られる。名前探索の最初の段階でgit-~
と一致するか否かを見ている模様。
外部コマンドが動作するディレクトリ
単純な置き換えのときはカレントディレクトリから移動することは無い。しかし、!
によって外部コマンド呼び出しにするとそのときのgitが動いている(.git
フォルダがある)ディレクトリに移動してから動作する。
[alias]
pwd = !pwd
試しにこういうエイリアスを作ってpwd
とgit pwd
を見比べてみる(Gitのリポジトリ位置は/c/source/alias_test/RemoteRepo
とする)。
usern@me MINGW64 /c/source/alias_test/RemoteRepo (master)
$ cd Child/
usern@me MINGW64 /c/source/alias_test/RemoteRepo/Child (master)
$ pwd
/c/source/alias_test/RemoteRepo/Child
usern@me MINGW64 /c/source/alias_test/RemoteRepo/Child (master)
$ git pwd
/c/source/alias_test/RemoteRepo
カレントディレクトリを/Child
に移動してからpwd
を記述すると当然(Gitのレポジトリ)/Child
が表示されるが、git pwd
と打つと(Gitのリポジトリ)
に移動していることが分かる。これは公式ドキュメントに書かれている。
Note that shell commands will be executed from the top-level directory of a repository, which may not necessarily be the current directory. GIT_PREFIX is set as returned by running git rev-parse --show-prefix from the original current directory.
注:それらの(!
を付与された)シェルコマンドはリポジトリのトップレベルディレクトリで実行されます。これはカレントディレクトリと一致するとは限りません。このとき、カレントディレクトリでgit rev-parse --show-prefix
を実行した結果がGIT_PREFIX
にセットされます。
このため、エイリアスでGit内のファイルを参照したい場合などはリポジトリのディレクトリからの相対位置だけ考えれば良い。
一方でgit ls-files
のようなカレントディレクトリの位置によって結果が変わるコマンドを使いたい場合、この挙動は困るかもしれない。公式ドキュメントによれば変数GIT_PREFIX
に相対パスの値が入るよ、とのことなので、下記のようにcd ${GIT_PREFIX}
を先に書いてあげれば良いようだ。
[alias]
# git ls-filesを行うだけのエイリアス。これ自体に意味は無い
ls-files-alias = !cd ${GIT_PREFIX} && git ls-files;
参考:git aliasの外部コマンド呼び出しはgit管理下のルートディレクトリから呼ばれる
エスケープの仕様(未完成)
特に関数を使う場合、いくつかの特殊文字のエスケープが必要となる。関数を"f(){ コマンド };f"
で記載する都合上、""
に囲まれた範囲のどの文字をシェルに展開すればよいのかを明示しなければならない。
確認できる範囲では、"\
は少なくとも\
でエスケープする必要がある。例外として、if
直後の[...]
ブロック中は(これ自体が単体のコマンドなので)エスケープしなくても動く……たぶん。
どの文字がどの状況でエスケープ対象になるのか、ご存知の方がいらっしゃれば編集リクエスト頂きたく。
#晒す
手元に設定しているエイリアス一覧はこんな感じ。
下の方にあるやたら長いエイリアスは以下の記事参照。
特定時点の特定ファイルをエディタで開くgit-alias
【git】checkoutせずに特定ブランチをpullするエイリアス
[alias]
# git alias : 現在登録してあるエイリアス一覧を表示する.
# 長いエイリアスが多いので前方42文字で切っている
alias = !git config --get-regexp '^alias\\.' | sed 's/alias\\.\\([^ ]*\\) \\(.*\\)/\\1\\\t => \\2/' | cut -c 1-42 | sort
# git st : トラッキングしているファイルのステータスのみ表示.
st = status -uno
# git graph : logを良い感じにグラフで表示.
# 全てのブランチを表示したいので --remotes と --branches を追加している
graph = log --graph --date=format:'%m/%d %H:%M' --format=format:'%C(yellow)%h %C(cyan)%ad %C(green)%an%Creset%x09%s %C(red)%d%Creset' --remotes --branches
# git now : 現在欲しい情報(直近20個のログとステージング)を更新してから表示.
# 便利だが、同名のフリーコマンドがあるらしいので別名にした方が良さそう.
now = !git fetch; git graph -20 --date-order; git st
# git F5 : git now が他のコマンドと被っているので別名を模索していた最中の産物
# なんとなく「F5キーって更新っぽいよな」というだけ。
F5 = !git now
# git cm : commitの省略形。そんだけ。
cm = commit
cmm = commit -m
cmam = commit -a -m
# mergetool系
# 大本の設定とかはこちらを参考にした → https://qiita.com/kobake@github/items/fb317b4fdacad718a4b2
# difftoolをそのまま起動するとシェルが待機状態になってしまうので、関数形式にして末尾に`&`を付与し並列実行させている.
windiff = "!f(){ git difftool -y -d -t WinMerge /"${@}/" & };f"
winmerge = mergetool -y -t WinMerge
# git winshow <コミットハッシュ等> : 一つ前のコミットと比較してWinMergeで表示する.
winshow = "!f(){ git difftool -y -d -t WinMerge ${1}~..${1} & };f"
# git-flow系
# ぶっちゃけコマンドを毎回忘れるから設定しておくだけ
fi = flow init -d
ff = flow feature
fr = flow release
fh = flow hotfix
#情報取得系
# git where <コミットハッシュ> そのコミットが属しているブランチを表示.
where = branch --contains
# git here : 現在のリポジトリのパスを取得. pwdコマンドに近いが表記がWindowsオリジナルに近くなる.
here = rev-parse --show-toplevel
#git showコマンドだと--onelineオプションが効かない気がするので、等価な働きをするlog -1を使う
show2 = log -1
# git show-file <コミットハッシュorブランチ名> <ファイルパス>
# 詳細はこちら(https://qiita.com/YamEiR/items/7002ec1869717b7da9ce)
# 特定の時点(コミット/ブランチ等)の特定ファイルをエディタで開く.
# 作業ディレクトリは(Windowsの場合)AppData/Local/Temp/git-show/ であり、1週間以上経過すると削除される.
# git config core.editorに適当なエディタを設定していないと動作しないため注意.
show-file = "!f(){ \
if [ $# -eq 2 ];then \
local repo=$(git rev-parse --short ${1});\
if [ "${?}" -ne 0 ]; then \
return 1;\
fi;\
git ls-files ${2} > /dev/null || return 1 ; \
local f_path=${2};\
local f_name=${f_path##*/};\
local f_row_name=${f_name%.*};\
local f_ext=${f_name##*.};\
mkdir -p /tmp/git-show;\
git show ${repo}:${f_path} > /tmp/git-show/${f_row_name}-${repo}.${f_ext};\
if [ $? -eq 0 ];then \
$(git config core.editor) /tmp/git-show/${f_row_name}-${repo}.${f_ext} &\
fi;\
else \
echo \"syntax error : git show-file <repo-name> <file-path>\";\
fi;\
find /tmp/git-show/* -mtime +7 -exec rm {} + ;\
};f"
# git pull-o ブランチ名
# 詳細はこちら(https://qiita.com/YamEiR/items/630fcdd3c4c1984fdc2a)
# 任意のブランチがfast-forward可能な場合にpullする
pull-o = "!f(){ \
local branch_name=${1};\
local current_branch=$(git symbolic-ref --short HEAD);\
local branch_remote=$(git config branch.${branch_name}.remote);\
local branch_path=$(git config branch.${branch_name}.merge);\
if [ ${branch_name} = ${current_branch} ];then \
git pull ${branch_remote} ${branch_name} --ff-only;\
return ${?};\
else \
git fetch ${branch_remote} && \
git fetch . refs/remotes/${branch_remote}/${branch_name}:${branch_path};\
return ${?};\
fi;\
};f"