削除済みリモートブランチを追跡しているローカルブランチを1発で全削除するコマンド
リモートブランチはマージ後GitHubが削除するか聞いてくれるのでその時点で削除するようにしている。
だが、そのリモートブランチを追跡しているローカルブランチの削除を忘れて溜まっていることがよくある。
$ git branch -v * develop fef1cdd Merge pull request #30 from abcdef/fix/bazbarfoo feature/foo e57408f [gone] Fix deploy feature/bar a9b73ad [gone] Upload image feature/baz b833113 [gone] Fix test feature/foobar 703e2ad [gone] Add foobar.c feature/foobaz 1587581 [gone] Fix foobaz.rb feature/barfoo 37265cb [gone] Add barfoo.php feature/barbaz 3c121ca [gone] Fix barbaz.py feature/bazfoo cb69767 [gone] Add bazfoo.go feature/bazbar bb8575f [gone] Fix bazbar.pl feature/foobarbaz 66d2807 [gone] Fix README.md fix/foobazbar 84a781e [gone] Add gitignore fix/bazfoobar cd36a6b [gone] Fix ci master 0a41ce8 Merge pull request #20 from abcdef/develop
[gone]
が付いているのがリモートブランチへの追跡が切れているローカルブランチ。
いつも git branch -d
の後ろに1つずつブランチ名をコピペして削除していた。
$ git branch -d feature/foo feature/bar feature/baz feature/foobar feature/foobaz feature/barfoo feature/barbaz feature/bazfoo feature/bazbar feature/foobarbaz fix/foobazbar fix/bazfoobar Deleted branch feature/foo (was e57408f). Deleted branch feature/bar (was a9b73ad). Deleted branch feature/baz (was b833113). Deleted branch feature/foobar (was 703e2ad). Deleted branch feature/foobaz (was 1587581). Deleted branch feature/barfoo (was 37265cb). Deleted branch feature/barbaz (was 3c121ca). Deleted branch feature/bazfoo (was cb69767). Deleted branch feature/bazbar (was bb8575f). Deleted branch feature/foobarbaz (was 66d2807). Deleted branch fix/foobazbar (was 84a781e). Deleted branch fix/bazfoobar (was cd36a6b).
面倒なので1発コマンド打つだけで全部消えるコマンドを組もうと思い調べた。
コマンド
git for-each-ref --format '%(if)%(upstream:track)%(then)%(refname:short)%(end)' | xargs git branch -d
解説
最初は git branch
コマンドを使って作ろうとしていた。
だが、先の記事にも書いた通り、 git branch
を使ってスクリプトを書かないほうがいいらしい。
なので git for-each-ref
を使って書いた。
これはGitレポジトリの各refの情報を条件に従って出力していくコマンド。
Git - git-for-each-ref Documentation
何点か解説を書いてみる。
ブランチ名
ブランチ名は %(refname:short)
で取得。
short
をつけないと refs/heads
がついてしまい、ブランチ名として認識されないので注意が必要。
# `refs/heads` がついてしまう。 $ git for-each-ref --format '%(refname)' refs/heads/develop refs/heads/feature/foo refs/heads/feature/bar refs/heads/feature/baz refs/heads/feature/foobar refs/heads/feature/foobaz refs/heads/feature/barfoo refs/heads/feature/barbaz refs/heads/feature/bazfoo refs/heads/feature/bazbar refs/heads/feature/foobarbaz refs/heads/fix/foobazbar refs/heads/fix/bazfoobar refs/heads/master refs/remotes/origin/HEAD refs/remotes/origin/develop refs/remotes/origin/feature/aaa refs/remotes/origin/feature/bbb refs/remotes/origin/ccc refs/remotes/origin/master refs/remotes/origin/try/ddd refs/remotes/origin/try/eee # shortをつければ正しいブランチ名が取得できる。 $ git for-each-ref --format '%(refname:short)' develop feature/foo feature/bar feature/baz feature/foobar feature/foobaz feature/barfoo feature/barbaz feature/bazfoo feature/bazbar feature/foobarbaz fix/foobazbar fix/bazfoobar master origin/HEAD origin/develop origin/feature/aaa origin/feature/bbb origin/ccc origin/master origin/try/ddd origin/try/eee
gone
削除対象は [gone]
が付いたブランチなのでこれを取得する。
upstream の項で以下のように説明されている。
:track
also prints "[gone]" whenever unknown upstream ref is encountered.
試すと
$ git for-each-ref --format '%(upstream:track)' [gone] [gone] [gone] [gone] [gone] [gone] [gone] [gone] [gone] [gone] [gone] [gone]
[gone]
のみでは分かりづらいと思うのでブランチ名をつけてみる。
$ git for-each-ref --format '%(upstream:track) %(refname:short)' develop [gone] feature/foo [gone] feature/bar [gone] feature/baz [gone] feature/foobar [gone] feature/foobaz [gone] feature/barfoo [gone] feature/barbaz [gone] feature/bazfoo [gone] feature/bazbar [gone] feature/foobarbaz [gone] fix/foobazbar [gone] fix/bazfoobar master origin/HEAD origin/develop origin/feature/aaa origin/feature/bbb origin/ccc origin/master origin/try/ddd origin/try/eee
リモートへの追跡が切れていない develop
, master
やリモートにのみ存在しているブランチには [gone]
がついておらず、リモートへの追跡が切れたローカルブランチにのみ [gone]
がついている事がわかる。
if
git for-each-ref
ではif文( %(if)…%(then)…%(end)
)が使える。
if文を使い、 [gone]
があるときだけブランチ名を表示するようにする。
$ git for-each-ref --format '%(if)%(upstream:track)%(then)%(refname:short)%(end)' feature/foo feature/bar feature/baz feature/foobar feature/foobaz feature/barfoo feature/barbaz feature/bazfoo feature/bazbar feature/foobarbaz fix/foobazbar fix/bazfoobar
xargs
取得できたブランチ名をxargsを使って git branch -d
に流す。
ちなみにxargsだけ実行すると git branch -d
にどんな値が渡っているのかわかる。
$ git for-each-ref --format '%(if)%(upstream:track)%(then)%(refname:short)%(end)' | xargs feature/foo feature/bar feature/baz feature/foobar feature/foobaz feature/barfoo feature/barbaz feature/bazfoo feature/bazbar feature/foobarbaz fix/foobazbar fix/bazfoobar
こんな感じ。
別解
サブコマンドとして実行してもよい。
git branch -d $(git for-each-ref --format '%(if)%(upstream:track)%(then)%(refname:short)%(end)')
$(git branch)でカレントディレクトリのファイル一覧も出てしまう時の対処法
とあるシェルでgitのブランチ一覧を取りたくて echo $(git branch)
と書いたのだが、なぜかファイル一覧も出てしまい戸惑った。
前提
下記のようなディレクトリがあって、それがgit管理されていたとする。
$ ls README.md main.go main_test.go
そして以下のようなブランチが存在する。
$ git branch develop feature/bar feature/baz feature/foo * master
事象
echo $(git branch)
を実行すると下記のようにブランチ一覧の間にファイル一覧が差し込まれてしまう。
$ echo $(git branch) develop feature/bar feature/baz feature/foo README.md main.go main_test.go master
原因
勘のいい人はもうお分かりかもしれませんが、 git branch
した時にmasterの前に *
がついていますよね。
それが echo
にワイルドカードとみなされてファイル一覧が出てしまいます。
$ echo * README.md main.go main_test.go
と同じになるわけです。
対処法
ダブルクオートで囲めばワイルドカードが展開されずに出力されます。
$ echo "$(git branch)" develop feature/bar feature/baz feature/foo * master
ブランチ一覧を1行で取得したい場合
tr
を使って *
をスペースに変換したリストをダブルクオート無しで echo
すれば取得可能です。
$ echo $(echo "$(git branch | tr '*' ' ')") develop feature/bar feature/baz feature/foo master
でも、そもそも git branch
を使ってスクリプトを書かないほうがいいらしい。
上記によれば、Gitはスクリプトで使用するために明示的に設計されたplumbingインターフェースを提供しているとのこと。
なので自分はplumbingインターフェースを使ってみようと思います。
参考
特定のディレクトリ内にあるjavascriptを全てMinifyする
例えば static/js
ディレクトリにjsが置いてあるとする。
$ tree . └── static └── js ├── bar.js ├── baz.js └── foo.js
そのディレクトリ内にあるjavascriptを全てMinifyしたい場合のbashスクリプト。
for f in $(ls static/js/*.js); do uglifyjs -cm -o "${f/.js/.min.js}" "${f}" done
Minifyには uglify-js
を使用する。
オプションの c
が単純なソース圧縮、 m
が変数名の圧縮(1文字にする)、 o
はアウトプットするファイルを指定。
これを minify.sh
というファイル名で作成し、実行する。
注意点としては ${f/.js/.min.js}
がbashの文字列置換を使用しているのでbashで実行すること。
$ bash minify.sh
すると全てのjavascriptがMinifyされる。
$ tree . ├── minify.sh └── static └── js ├── bar.js ├── bar.min.js ├── baz.js ├── baz.min.js ├── foo.js └── foo.min.js
ちなみにMinifyしたjavascriptをgit管理したくない場合は .gitignore
に *.min.js
を入れると無視してくれる。
もうひと工夫
シバンを入れて実行権限をつけるとスッキリして良い。
実行権限をつけるのでファイル名も minify
にすると綺麗かも。
#!/bin/bash for f in $(ls static/js/*.js); do uglifyjs -cm -o "${f/.js/.min.js}" "${f}" done
$ mv minify.sh minify $ chmod u+x minify $ ./minify $ tree . ├── minify └── static └── js ├── bar.js ├── bar.min.js ├── baz.js ├── baz.min.js ├── foo.js └── foo.min.js
参考
CircleCIのYAMLの仕様が変わった
CircleCIを使っている。
今週水曜(2018年10月10日)なぜか本番デプロイができなくなっていた。
調査すると設定を書いているYAMLの仕様が変更されたようで、キーの上書きができなくなっていた。
元々の仕様
デプロイにはFabricを使用している。
fab dev deploy
で開発環境、 fab prod deploy
で本番環境にデプロイされるように設定ファイルを書いている。
references: env_dev: &env_dev environment: ENV: dev env_prod: &env_prod environment: ENV: prod jobs: deploy_dev: &deploy docker: - image: circleci/python:3.6 environment: AWS_DEFAULT_REGION: ap-northeast-1 steps: - checkout - run: name: Install python package command: sudo pip install -r requirements.txt - run: name: Deploy command: fab $ENV deploy <<: *env_dev deploy: <<: *deploy <<: *env_prod
Anchor(&name)とAlias(*name)を使用してコンパクトに書いている。
CircleCIは各jobページのConfigurationタブで展開されたYAMLを確認することができる。
展開されたYAMLを確認すると下記のようになっている。
jobs: deploy_dev: environment: - ENV: dev steps: - checkout - run: name: Install python package command: sudo pip install -r requirements.txt - run: name: Deploy command: fab $ENV deploy docker: - image: circleci/python:3.6 environment: AWS_DEFAULT_REGION: ap-northeast-1 deploy: environment: - ENV: prod docker: - image: circleci/python:3.6 environment: AWS_DEFAULT_REGION: ap-northeast-1 steps: - checkout - run: name: Install python package command: sudo pip install -r requirements.txt - run: name: Deploy command: fab $ENV deploy
ちゃんと environment
に ENV
が上書きされて展開されているのがわかります。
仕様変更後
水曜日、本番デプロイのCIが失敗することで仕様変更を知る。
仕様変更後に展開されたYAMLを確認すると deploy
jobの ENV
が上書きされず、 dev
のままになってしまっていた。
jobs: ... deploy: ... environment: - ENV: dev
Twitterでつぶやかれている方も居た。
違うっぽい。circleciのYAMLパーザがおかしくなってないっすか
— こうげ (@koooge) October 10, 2018
修正
AnchorとAlias周りがおかしいのではないかという事はすぐわかったので、以下のように修正を行った。
deploy: - <<: *deploy - <<: *env_prod + <<: [*env_prod, *deploy]
元々は *deploy
を先に書き、そこに *env_prod
を上書きするという意図で書いていた。
しかし、上書きされないため書く順番を逆にし、 *env_prod
を先に書き、 *deploy
を後に書いた。
この時、Aliasの書き方もCircleCIで推奨されている []
を使用した書き方に変更した。
すると下記のように以前の想定通り展開されました。
jobs: ... deploy: environment: - ENV: prod docker: - image: circleci/python:3.6 environment: AWS_DEFAULT_REGION: ap-northeast-1 steps: - checkout - run: name: Install python package command: sudo pip install -r requirements.txt - run: name: Deploy command: fab $ENV deploy
そして無事デプロイされるようになった。
ブログ再始動
お久しぶりです。
178inabaです。
久しぶりにブログを書いています。
最近、技術書典で技術書を販売した方のブログを見ました。
このブログを見て
(自分もいつかは本が書けたら楽しいだろうな)
(沢山の人が来場していて交流できるし、報酬も得られる!)
などと思い、
でも本を出すにはまず、休止状態のブログを再始動して技術的な記事とか書くべきだろうと思いこのブログを書いています。
Kyash始めてみた
Kyashは同僚と食事に行った時に割り勘代金を渡すために入れてみたのですが便利です。
上の技術書典のブログを読んだときに、KyashのAndroidエンジニアのこにふぁーさんのブログを思い出しました。
このこにふぁーさんのブログには投げ銭の話が書いてあって、上の技術書典の話と 『技術情報の発信で報酬を得る』 という所で通ずるなと思いました。
なので真似してKyashのIDとQRコードを貼ってみます。
kyash_id: 178inaba
今後
今後は定期的に技術情報をブログに書いていけたらと思います。
よろしくです!
GitHub Gistにファイルをアップロードするコマンドをgolangで作った
Gistを作る時、毎回catでファイル内容を表示してコピーして、GistのWeb画面にペーストしてたんだけど、
これが地味にめんどくなってきたので、ファイルを渡すとGistを作ってくれるコマンドをgolangで作った。
使い方
target.go
をGistにアップロードする場合。
$ gistup target.go
- 複数ファイルをGistにアップロードする場合。
$ gistup target.go target_test.go target_linux.go
- 標準入力をGistにアップロードする場合。
$ stdin | gistup
アップロードが成功するとブラウザで作成されたGistのページを開きます。
(開けなかった場合はURLを表示。)
デフォルトでは最初にユーザ名とパスワードでログインを求められます。
(後述のオプションで匿名を選択すればログイン不要。)
また、デフォルトでは非公開のGistとして作成されます。
(こちらもオプションで公開のGistとして作成することも可能です。)
オプション
-a
- 匿名のGistを投稿します。
-d <description>
- Gistの説明を追加します。
-n <file_name>
- 標準入力からGistにアップロードする場合のファイル名を設定します。
- 指定しない場合はGistのデフォルト
gistfile1.txt
になります。
-p
- パブリック(公開)のGistを投稿します。
所感など
入力時のコンテキストキャンセル検知
ユーザ名、パスワード入力は下記のようにコンテキストのキャンセルを検知できるようにしている。
func readString(ctx context.Context, hint string, readFunc func(t *tty.TTY) (string, error), t *tty.TTY) (string, error) { fmt.Printf("%s: ", hint) ch := make(chan string) errCh := make(chan error) go func() { s, err := readFunc(t) if err != nil { errCh <- err } ch <- s }() var s string select { case <-ctx.Done(): return "", ctx.Err() case s = <-ch: case err := <-errCh: return "", err } return s, nil }
理由はgistupはInterruptシグナル(Ctrl+C
)を受けてコンテキストをキャンセルする仕組み(下記参照)になっているのだが、
sigCh := make(chan os.Signal, 1) signal.Notify(sigCh, os.Interrupt) ctx, cancel := context.WithCancel(context.Background()) go func() { <-sigCh cancel() }()
これだと、入力中にCtrl+C
を押しても終わらない。
なので、ゴルーチンで入力処理を実行して、コンテキストのキャンセルと、入力終了をselect
で同時に待つようにした。
結果、入力中にCtrl+C
を押すと終了するようにできた。
ユーザ名、パスワードの入力はTTY
を使用
gistupではユーザ名とパスワードの入力にTTY
を使用している。
理由は、最初、標準入力からユーザ名とパスワードを取得していたけど、
標準入力をGistにアップロードする箇所を実装してる時にユーザ名、パスワードの取得ができなくなる事に気づいた。
いろいろ調べたけど引き続き標準入力からユーザ名、パスワードを取得するのは難しそうで、調べて出てきたのはperlの質問でTTY
を使っているやつだった。
「goでTTY
って確かmattnさんが作ってたよな」と探したらあって、
ただ、パスワードを読むメソッドしか無かったのでReadString
メソッドをプルリクエストした。
その後、速攻でマージして頂けたのでユーザ名の入力にはReadString
を使用。
パイプで前のコマンドの結果貰うツールでStdinからユーザ名とかパスワードを入力しようとするとパイプの方を拾っちゃってうまくいかなかった。
— 178inaba (@i178inaba) 2017年7月26日
go-ttyのReadString(さっきPRした)を使ったらうまくいった。
皆さんもぜひ!https://t.co/PGlJcQEUJr
結果、ユーザ名、パスワードを入力しつつ、標準入力から取得した文字列をGistにアップロードすることができた。
まとめ
これでコピペせずにGistにアップロードできるようになった。
またなんか作ったら書きます。
あと、PR大歓迎です😆
Raspberry Piで使うSDカードをコマンドラインで作る
新しいRaspberry Piを買ったので起動SDを作るコマンドをメモしておきます。
使用するOSはRaspbianです。
# ダウンロード $ wget https://downloads.raspberrypi.org/raspbian_lite_latest -O raspbian.zip # zip解凍 $ unzip raspbian.zip # SDカードのマウント箇所を確認 $ diskutil list ... /dev/disk2 (internal, physical): #: TYPE NAME SIZE IDENTIFIER 0: FDisk_partition_scheme *2.0 GB disk2 ... # SDカードのマウントを解除 $ diskutil unmountDisk /dev/disk2 # SDカードにイメージを書き込む $ sudo dd bs=1m if=2017-03-02-raspbian-jessie-lite.img of=/dev/rdisk2
イメージを書き込むのが結構時間かかります。
マウント箇所(上記だとdisk2)はPCによって異なります。
イメージファイル名(上記だと2017-03-02-raspbian-jessie-lite.img)もRaspbianがアップデートされると変わります。
参考: