2018/08/27にペパボ社内で行われたシェル大活用講座の発表資料です
開発効率をあげるgitテクニック〜シェル芸もあるよ〜
View Slide
高谷雄貴 @buty4649シニアエンジニア技術部 プラットフォームグループ
git
● 普段からgitを使っている○ GitHub / GH:E○ GitHub Workflow● gitコマンドを便利にして生産性↑↑今日のテーマ
● gitってなに???って話はしません○ ググるか誰かに聞いてください● bashを使うことを前提で書いている部分があります。○ zsh/fish/bsh/ksh/csh/tcsh/ash/dash な人は適宜読み替えてくださいおことわり
目次1. 減らす2. 便利にする
PRを作る場合のオペレーション例普段どんなオペレーションをしますか?$ git clone [email protected]/foo/bar$ cd bar$ git checkout -b new-branch$ git status$ git add foobar$ git commit$ git push origin new-branch1.減らす
PRを作る場合のオペレーション例普段どんなオペレーションをしますか?$ git clone [email protected]/foo/bar$ cd bar$ git checkout -b new-branch$ git status$ git add foobar$ git commit$ git push origin new-branch打鍵数が多い!84文字!(下線部のみ)1.減らす
減らす
alias g=git
1.減らす● シェルのエイリアス機能○ コマンドに別名を割り当てる● g で gitコマンドが実行されるalias g=git
1. 減らす● 引数はそのまま処理される○ g status は git status として実行される● 引数付きのコマンドもエイリアスにできる○ alias gs=’git status’ とするとgsでgit statusが実行される● エイリアスを消したい場合は unalias コマンドを使う○ unalias g○ 一時的に無効にしたい場合はコマンド名の頭に \ をつける● エイリアスはカレントシェルのみに反映される○ 永続化したい場合は ~/.bash_profile へエイリアス機能の補足
84文字 → 74文字!!エイリアスによって削減$ g clone [email protected]/foo/bar$ cd bar$ g checkout -b new-branch$ g status$ g add foobar$ g commit$ g push origin new-branch1.減らす
まだ減らす
git config --global alias.st status
1.減らす● “git” のエイリアス機能○ サブコマンドをにエイリアスをつけられる● よく使うサブコマンドを短い文字にエリアスする○ st → status, a → add など● 設定された内容は ~/.gitconfig に保存される○ ~ は $HOME と同じ意味○ ファイルに設定を書いても良い(即時反映される)git config --global alias.st status$ cat ~/.gitconfig[alias]st = status
1.減らすgitサブコマンドのエイリアス例$ cat ~/.gitconfig[alias]a = addcl = clonecm = commitco = checkoutcob = checkout -bp = pushst = status
84文字 → 50文字!!エイリアスによって削減$ g cl [email protected]/foo/bar$ cd bar$ g cob new-branch$ g st$ g a foobar$ g cm$ g p origin new-branch1.減らす
1.減らす● よく使うコマンドを短くすると効果的● 引数を含めよく使うコマンドもエイリアスにする○ サブコマンド+オプションにするとよいと思う○ 例: git checkout -b → git cob● シェルのエイリアスとの使い分け○ シェルのエイリアスにした方がサブコマンドのスペースが減らせる○ 例: alias gcob=’git checkout -b’○ シェルの補完が効かないしコマンド名がかぶる可能性○ 好みの問題かもエイリアス作成のコツ(私のやり方)
1. 減らす私の設定例[alias]br = branchcm = commit -vcma = commit -v --amendcman = commit -v --amend --no-editco = checkoutcob = checkout -bd = diffdc = diff --cachedg = grep詳しくは https://github.com/buty4649/dotfiles/blob/master/cookbooks/configfiles/files/.gitconfig
もっと減らす
もっと減らす$ g cl [email protected]/foo/bar$ cd bar$ g cob new-branch$ g st$ g a foobar$ g cm$ g p origin new-branch1.減らすめんどくさい!
1.減らす● いちいちファイル名を書くのが手間○ git add . でもいいけど、一部だけaddしたい場合不便● 極力ファイル名を打ちたくない!○ 浅い階層ならいいが深い階層だとその分タイプ数が増えるファイル名を書くのがめんどくさい!どうしたらファイル名を打たずにgit addできるか?
1.減らす● git addのあとにaddしたいファイル名を羅列する○ git add …● このコマンドを生成できればよい● 変更がかかったファイルはgit statusで取れる● つまり↑をいい感じにしてgit addするスクリプトを書けばよい!どうしたらファイル名を打たずにgit addできるか?
1.減らす● 生のgit statusの結果はパースしづらいのでオプションをつけるスクリプトを作る$ git status --shortMM file1ワークツリーの状態インデックスの状態M: 変更された A:追加された D:削除されたU:更新されたがマージされていない(コンフリクトなど)※ 詳しくはgit status --helpを参照
1.減らす● 出力結果をフィルタする必要がある○ Dなファイルをgit addに渡すとエラーになる○ addしたいファイルのみを選択したい■ コマンドラインセレクターを使うと便利!■ 例えば peco (詳しくはこのあとのudzuraさんの発表で)● スクリプトをどこに書くか?○ gitのエイリアスはスクリプトが書ける○ !を先頭に書くとスクリプトとして解釈される○ 例: v = !vim → git v でvimが起動するスクリプトを作る
1.減らす完成したコマンド[alias]a = !"git status --short | awk '!/^[ADRM] /' | peco| awk '{print $NF}' | xargs -r git add"git status --short |awk '!/^[ADRM] /' |peco |awk '{print $NF}' |xargs -r git add先頭がADRMを除外ファイルを選択ファイル名のみ切り出しgit addを組み立て
1.減らす解説(awk)$ git status --shortD file1M file2$ git status --short | awk ‘!/[ADRM] /’M file2$ git status --short | awk ‘!/[ADRM] /’ |> awk ‘{print $NF}’file2
1.減らす解説(xargs)● 標準入力をコマンドの引数に追加し実行する○ 例: echo b | xargs echo a → echo a b が実行される● forやwhileでも同じことができる○ 例: date | xargs echo → date | while read LINE;do echo $LINE;done○ サブシェルを作らない分xargsのほうが早い● -r オプションをつけると入力が空の場合実行しない○ macOSのxargsにはないかも…● 通常は末尾に引数を追加するが-Iで任意の場所に追加できる○ -Iに続いて置換する文字列を書く○ 2文字以上でも使える○ 例: date | xargs -I{} echo {} hogehoge● これ以外にもすごく便利なのだけどそれを書くには十分な余白がry
1.減らすこれでpushはgit gpushでいけるようになるgit push origin new-branchも短くする[alias]gpush = !"git rev-parse --abbrev-ref HEAD | xargsgit push origin"$ git rev-parse --abbrev-ref HEADnew-branch
1.減らす● git push origin HEAD でいける○ この資料を作っているときに知った。。● つまり、↑をエイリアスすればよいgit push origin new-branchも短くする[alias]gpush = push orogin HEAD
1.減らす私の設定例# ブランチを選択してcheckoutc = !"git branch | awk '!/^\\*/' | peco | xargs -r gitcheckout"# リモートブランチを選択してcheckoutcobr = !"git branch -r | grep -vE '/(HEAD|master$)' | sed-e 's,origin/,,g' | peco | xargs -r -I{} git checkout -b{} origin/{}"# 変更されたファイルを選択してcheckoutcof = !"git status --short | peco | awk '{print $2}' |xargs -r git checkout --"※ \ はエスケープする必要がある
84文字 → 29文字!! 65%効率アップ!!!!最終結果$ g cl [email protected]/foo/bar$ cd bar$ g cob new-branch$ g st$ g a$ g cm$ g gpush1.減らす
● 減らすではタイプ数を減らすことに注力した● 次はタイプ数削減以外の方法で効率化する● 便利なツールや機能を使ってより作業効率をあげる2.便利にする
2.便利にするgit-completion.bash & git-prompt.sh● gitをインストールするとついてくる便利スクリプト● bashの補完機能を提供○ (brew) /usr/local/etc/bash_completion.d/git-completion.bash○ sourceで読み込む● プロンプトにカレントブランチを表示○ (brew) /usr/local/etc/bash_completion.d/git-prompt.sh○ PS1環境変数に __git_ps1関数を追加する$ git branch20180827-pepabo-college~/path/to/hoge ( 20180827-pepabo-college )$ echo $PS1\w$(__git_ps1 " (\[\e[0;31m\]%s\[\e[00m\])")\n\$
2.便利にするghq● ローカルリポジトリの管理ツール○ https://github.com/motemen/ghq● 指定のディレクトリ配下にgit cloneする○ デフォルトは ~/.ghq○ GOPATHと合わせておくと便利■ git config --global ghq.root ~/src● 使い方○ リモートリポジトリのクローン■ ghq get ○ ローカルリポジトリのフルパスを一覧で表示■ ghq list -p
2.便利にするghq● ghq list と pecoを組み合わせて移動できる○ cd $(ghq list -p | peco)● これをコマンドにする○ 外部コマンドではcdできないので関数で定義する○ 例: gcd() { cd $(ghq list -p | peco); }
2.便利にするtig● TUIなgitクライアント○ https://github.com/jonas/tig● 詳しくはこのあとのjune29の発表で!!!● 私の使い方○ git ll にエイリアスしている○ fixup / squash できるようにしている$ cat ~/.gitconfig[tig "bind"]diff = F ?!git commit --fixup %(commit)diff = S ?!git commit --squash %(commit)main = F ?!git commit --fixup %(commit)main = S ?!git commit --squash %(commit)
2.便利にするhub● Github社が作ったgitコマンドラッパー○ https://github.com/github/hub● gitにエイリアスして使う○ eval $(hub alias -s)● gitに機能を追加する○ issueやPRの作成ができる○ 詳しくは https://hub.github.com/hub.1.html● ローカルリポジトリでhub browseすると便利○ GH:Eなリポジトリを見るときは設定が必要○ git config --global hub.host
2.便利にするgit hookを使う● 特定のアクションの実行前/後にスクリプトを実行する○ 詳しくは https://git-scm.com/docs/githooks● hookスクリプトは .git/hooks 配下にある○ 例えばcommit前にhookするなら■ .git/hooks/pre-commit■ chmod +x するのを忘れない(重要)
2.便利にするhook利用例(1): プッシュ前にlintを行う● プッシュした後にCIのlintチェックで落ちると悲しい…● git pushする前にlintすれば回避できる!$ cat .git/hooks/pre-push#!/bin/bashrake lint● しかし、プッシュに時間がかかるようになるというデメリット
2.便利にするhook利用例(2): master pushを防ぐ● master pushやりがち● Github側で設定変更するという手段もある○ すべてのリポジトリに設定するのは手間● hookを使って防ぐ
2.便利にするhook利用例(2): master pushを防ぐhttps://github.com/buty4649/dotfiles/blob/master/cookbooks/configfiles/files/.git_template/hooks/pre-push$ cat .git/hooks/pre-push#!/bin/bashCURRENT_BRANCH="$(git rev-parse --abbrev-ref HEAD)"if [ "$CURRENT_BRANCH" = "master" ];thenif [ -z "$ALLOW_GIT_MASTER_PUSH" -a \-z "$(git config --get git.allow-master-push)" ];thenecho "WARN: It's the master branch !!"echo 'If you want git push, please set either.'echo '* $ALLOW_GIT_MASTER_PUSH=1'echo '* git config --local git.allow-master-push 1'exit 1fifi
2.便利にするhook利用例(2): master pushを防ぐ$ git branchmaster$ git pushWARN: It's the master branch !!If you want git push, please set either.* $ALLOW_GIT_MASTER_PUSH=1* git config --local git.allow-master-push 1error: failed to push some refs to 'foobar'
2.便利にするhook利用例(2): master pushを防ぐ● master pushしなくなって悲しみが減った● ただ、master pushでもいいときに回避するのが手間○ プロンプトをだしてyを入力したらpushでもいいかも● hookにスクリプトを直接書くと変更するときに面倒○ hookからはコマンドを呼び出すだけにしたほうが良いかも● git init / git cloneの度にhookを配置するのが手間○ git-templateを使うと便利○ https://git-template.readthedocs.io/en/latest/● すでにあるリポジトリに設置したい○ ghq list -p | xargs -L1 -I{} cp -pv pre-push "{}/.git/hooks"○ (自信なし)
まとめ
まとめ● gitコマンドのタイプ数を減らすと効率があがる● シェルスクリプトを駆使してサブコマンドを作ると便利だし楽しい● 便利なツールを使うとより生産性があがる