Git の効率を爆上げするスクリプト集
ここ数日間開発効率そこあげするためにツール周りを見直している毎日です。
peco はすごいですね。めちゃくちゃ便利です。こんなに可愛らしい名前なのに。もりもり食べて吐き出してくれます。
と言うわけで peco を使った、何番煎じか分かりませんが、よく使う Git 操作を効率化するスクリプトです。
モバイルアプリエンジニアでも最低限この辺はよく使うので、シュッと使って生産効率爆上げしていきましょう!
macOS + zsh でしか動作は確認していません...
なんかめっちゃインデントずれてる....見にくくてすみません.....
git diff
git diff
の利用頻度が圧倒的に多い自分なのでまずは。
基本的には status を ls した結果を peco に食べさせて diff を表示する感じです。
入力するコマンドは少なければ少ないほど時間短縮になる、それが利用頻度が高ければ高いほど、と思います。
なので先頭1文字で動くように alias をはります。
ここで思ったのは、peco で選ぶのが手っ取り早いけどパス指定して取る事もあるよな、と言うことです。
でもそのために diff_peco
と diff_path
みたいなのを複数作って alias 両方はるのはファイル内の可読性も悪くなるし覚える事増えて効率悪くなるじゃん...
と、言うことで、オプション的な感じで切り替えれたらいいのではと思い getopts
を使う事にしました。
-f <path>
でパスのファイルの、 -s
で peco の出力の diff が出力されます。
-s した後は add なり checkout なりしたいと思うのでクリップボードにパスをコピーします。
alias d='_git_diff'
function _git_diff() {
local selected_file="."
while getopts ":sf:" opt; do
case $opt in
f) selected_file="$OPTARG"; break ;;
s)
selected_file="$(git ls-files -m | peco)"
if [ -z $selected_file ]; then
echo "No selected for diff."
return 1
fi
echo "$selected_file" | pbcopy
break
;;
?)
echo "Unknown option selected."
return 1
;;
esac
done
git diff $selected_file
}
以降、基本的に peco と getopts を使ったこんな感じのスクリプトが続きます。
git add
add だけ -p
をよく使うのでファイル選択に加えてそれもコマンドに反映させる様にしています。
それと 勝手に全ファイル add されると困るので -a
を付けた時だけにしています。
alias a='_git_add'
function _git_add() {
local selected_file=""
local options=()
while getopts ":spaf:" opt; do
case $opt in
f) selected_file="$OPTARG" ; break ;;
a) selected_file="." ; break ;;
s)
selected_file="$(git status --porcelain -s | peco)"
if [ -z $selected_file ]; then
echo "No selected for add."
return 1
fi
break
;;
p) options+=("-p") ;;
?)
echo "Unknown option selected."
return 1
;;
esac
done
git add $options[@] ${selected_file##* }
}
git restore
add した差分をやっぱり元に戻したい時に使います。
alias r='_git_restore'
function _git_restore() {
local selected_file=""
while getopts ":saf:" opt; do
case $opt in
f) selected_file="$OPTARG"; break ;;
a) selected_file="."; break ;;
s)
selected_file="$(git diff --name-only --diff-filter=M --staged | peco)"
if [ -z $selected_file ]; then
echo "No selected for restore."
return 1
fi
break
;;
?)
echo "Unknown option selected."
return 1
;;
esac
done
git restore --staged $selected_file
}
git checkout file
diff 削除はかなり危険なのでほんとにやるか最終チェックを入れています。
ブランチ操作の checkout とは棲み分けたかったため名前を変えています。
alias dd='_git_discard_diff'
function _git_discard_diff() {
local selected_file=""
while getopts ":sf:" opt; do
case $opt in
f) selected_file="$OPTARG"; break ;;
s) selected_file="$(git ls-files -m | peco)"; break ;;
?)
echo "Unknown option selected."
return 1
;;
esac
done
if [ -z $selected_file ]; then
echo "No selected for restore."
return 1
fi
echo -n "Really discard $selected_file changes? "; read answer
case $answer in
[yY] | [yY]es | YES )
git checkout $selected_file
;;
* )
echo "No actions."
return 1;;
esac
}
git checkout
-l
でローカルブランチのリストを、-r
でリモートブランチにリストをそれぞれ peco に食べさせています。
-s <branch>
で指定したブランチにチェックアウトします。
-h
で head からのコミットハッシュのヒストリーを peco に食べさせて、選んだコミットにチェックアウトします。
-b <branch name>
で git checkout -b
をします。
alias ch='_git_checkout'
function _git_checkout() {
local selected_branch=""
local options=()
while getopts ":lrhsb:s:" opt; do
case $opt in
s) selected_branch=$OPTARG; break ;;
h) selected_branch="$(git log --oneline | peco | cut -d ' ' -f 1)"; break ;;
l) selected_branch="$(git branch | peco)"; break ;;
r) selected_branch="$(git branch -r --sort=-authordate | perl -pe 's#origin/###' | peco)"; break ;;
b)
selected_branch=$OPTARG
options+=("-b")
break
;;
?)
echo "Unknown option selected."
return 1
;;
esac
done
if [ -z $selected_branch ]; then
echo "No branch selected."
return 1
fi
git checkout $options[@] ${selected_branch##* }
}
git branch
git branch
する時は、だいたいそのブランチ名をコピペして merge したり rebase したりしたいと思います。
なので今回は branch のリストアップは peco がやってくれるので標準出力はせず、クリップボードにコピーします。
alias b='_git_branch'
function _git_branch() {
local selected_branch=""
while getopts ":lr" opt; do
case $opt in
l) selected_branch="$(git branch | peco)"; break ;;
r) selected_branch="$(git branch -r --sort=-authordate | perl -pe 's#origin/###' | peco)"; break ;;
?)
echo "Unknown option selected.";
return 1
;;
esac
done
if [ -z $selected_branch ]; then
echo "No branch selected."
return 1
fi
echo ${selected_branch##* } | pbcopy
}
git push
current branch を origin にプッシュします。
alias po='_git_push_current_branch'
function _git_push_current_branch() {
local checkouted="$(g b --show-current)"
echo -n "Really push to origin/${checkouted}? "; read answer
case $answer in
[yY] | [yY]es | YES )
git push origin $(g b --show-current);;
* )
echo "No actions."
return 1;;
esac
}
git cherry-pick
cherry-pick したい時はだいたい別のブランチのログからハッシュ値をコピペしてくるんですが、その辺もっと上手くできたらいいですよね。
alias chp='_git_cherry_pick'
function _git_cherry_pick() {
while getopts ":s:r:" opt; do
case $opt in
r)
local start="$(git log --oneline $OPTARG | peco | cut -d ' ' -f 1)"
local end="$(git log --oneline $OPTARG | peco | cut -d ' ' -f 1)"
git cherry-pick $start..$end
;;
s)
local commit="$(git log --oneline $OPTARG | peco | cut -d ' ' -f 1)"
git cherry-pick $commit
;;
?)
echo "Unknown option selected."
return 1
;;
esac
done
}
git bisect
ここからここまでの間でなんか悪さしてるんだよな〜〜、って事がよくあるので毎回 tig からコミットハッシュ調べてコピペして bisect してたんですが、それもその時選べると効率 UP ですよね。
-s
でコミットハッシュのヒストリーから始点と終点を選んで start します。
alias bs='_git_bisect'
function _git_bisect() {
while getopts ":sbgr" opt; do
case $opt in
b) git bisect bad ;;
g) git bisect good ;;
r) git bisect reset ;;
s)
local bad="$(git log --oneline | peco | cut -d ' ' -f 1)"
local good="$(git log --oneline | peco | cut -d ' ' -f 1)"
git bisect start $bad $good
;;
?)
echo "Unknown option selected."
return 1
;;
esac
done
}
git rebase -i
あ、この差分3つ前のコミットに含めるやつだった :innocent:
みたいな事がままあります()
そういう時もコミットハッシュ引っ張ってこれたら楽ですよね。
alias rbi='_git_rebase_interactive'
function _git_rebase_interactive() {
local start="$(git log --oneline $OPTARG | peco | cut -d ' ' -f 1)"
git rebase -i $start
}
develop ブランチを更新
git flow に則っているところでは develop ブランチを運用しているのではないでしょうか。
develop は日々更新されていくのでローカルでも常に最新の状態にしておきたいですね。
なので develop の fetch & rebase をします。
最後は元いたブランチに戻ります。
本来はちゃんと fetch してアップデートの内容確認して rebase するのが正しいやり方なので、完全に更新内容が分かってる時に使いましょう。
個人的に git pull
は嫌いです...
function update_develop() {
local was_there="$(git branch --show-current)"
git fetch && git checkout develop && git rebase origin/develop
git checkout $was_there
}
番外編
git alias
ご存知の方もたくさんいると思いますが、 git も alias がつけれるので init や status など peco らなくても問題なさそうなものはそちらに登録しておくとさらに効率あがります。
[alias]
s = status
r = reset
c = commit -m
md = merge develop
みたいにしておくと git s
で status が確認できますし、git md
で develop ブランチをマージできます。
さらに zsh の alias に alias g='git'
とかしておくと g s
ですみます!
これを alias s='g s'
とかしておくともう1文字で status が確認できます!
もはやなんなんだ...
tig からコミットハッシュをコピー
↑ でやってきたコミットハッシュの取得はコミットメッセージしか表示されないので、差分を見ながら「あぁ、これこれ」って感じでハッシュを取ってきたい場合は tig を見ながらやれると嬉しいですね。
bind generic y @sh -c "echo %(commit) | pbcopy"
こうすると、tig を開いてる時に y
を入力するとカーソルが当たってるコミットのハッシュ値がクリップボードにコピペされます。
これを git cherry-pick
とかすると幸せになれるかも。
最後に
タイトルのわりにはいろんな方の dotfiles に書いてある様なスクリプトでした!!
自分の dotfiles にも載せています。
peco、いいですね。
僕が愛して止まない NEW GAME!
と言うおじさんの擬人化漫画にも同じ名前が出てくるので愛着があります。
オプション多用してるので、はたして効率が良くなっているのかは意見が分かれそうですが、慣れれば爆速で Git 操作ができる様になるので機能実装に注力できる様になります。
参考
Discussion