Git を使った開発をやっていると、手動でブランチ管理をするのが面倒になることがあります。
筆者が困っていたのは以下の2点についてでした。
- develop ブランチを毎日最新にしたいが、毎日同じようなコマンドを実行するのが面倒。
- 古いブランチがローカルに溜まっていて、いちいち削除するのが面倒。
今までは手動でこれらの管理をしていたのですが、そろそろ自動化しようと思います。
なお、私の開発環境では WSL 環境(Ubuntu)を使っています。
幸いにも shell スクリプトが使えますので、これを使って自動化をしましょう。
この記事で学べること
shell スクリプトの書き方のうち、以下について学べます。
- 処理を共通化する書き方 : function
- ループ処理の書き方 : for ... in
- 2つ以上のコマンドを連携させるやり方 : パイプ(|)、 xargs
ディレクトリ構成
開発で使っているディレクトリの構成は以下のようなものです。
/home/username/project/api
/home/username/project/front
api, front の2つの Git リポジトリが存在しています。
それぞれのリポジトリについて、不要なブランチを削除する shell を作成してみましょう。
Step1. develop ブランチを最新化する
試しに develop ブランチを更新する shell を考えます。
#!/bin/sh
cd ~/project/api
# api の develop を最新にする。
git checkout develop
git pull
cd ~/project/front
# front の develop を最新にする。
git checkout develop
git pull
develop ブランチを checkout
して pull
するだけの簡単な処理です。
これでも動作するのですが、同じ処理をコピー&ペーストしているのが気になります。
function を作って共通化しましょう。
Step.1 で完成したスクリプト
#!/bin/sh
function refresh_repository() {
local TARGET_DIR="$1"
cd "TARGET_DIR"
# develop を最新にする。
git checkout develop
git pull
}
refresh_repository ~/project/api
refresh_repository ~/project/front
refresh_repository
という function を作りました。第1引数 $1
でディレクトリを受け取っています。
develop ブランチを最新化する処理としては、ひとまずこれで良いでしょう。
Step2. 不要なブランチを削除する
続いて、不要なブランチを削除する処理を追加してみます。
先ほど作った refresh_repository
に機能を追加しましょう。
function refresh_repository() {
local TARGET_DIR="$1"
cd "TARGET_DIR"
# develop を最新にする。
git checkout develop
git pull
# リモートで削除されたブランチを削除する。
git remote prune origin
}
まず git pull
コマンドに --prune
オプションを追加します。
これはローカルに残ったリモート追跡ブランチを削除するオプションです。
これと git remote prune
を組み合わせることで、リモートで削除されたブランチを一括削除できるようになります。
しかし、リモートでブランチが削除されないケースについては対処できていません。
develop にマージ済みのブランチのうち、 feature/... で始まるものは削除したいです。
この条件を満たすブランチを列挙するには、以下のコマンドが使えます。
$ git branch --list --merged="develop" "feature/*"
> feature/1000
> feature/1001
> ...
git branch -d
でこれらのブランチを削除しましょう。
先ほど列挙したブランチを別のコマンドに渡すために xargs
を使います。
git branch --list --merged="develop" "feature/*" | xargs git branch -d
# xargs により、以下のようなコマンドが順番に実行される。
# git branch -d feature/1000
# git branch -d feature/1001
xargs
は、標準入力で受け取った値をコマンドに渡すコマンドです。
git branch --list
で取得したブランチを git branch -d
で削除できるようになりました。
これで問題ないように見えますが、1つだけちょっとした問題があります。
削除するべきブランチがないときに、以下のエラーが発生するのです。
fatal: branch name required
これは git branch -d
をブランチ名の指定なしで実行しているために起こります。
これを防ぐためには xargs
に -r
オプションを指定します。
git branch --list ... | xargs git branch -d
↓
git branch --list ... | xargs -r git branch -d
-r
オプションを指定すると、標準出力が空の時にコマンドが実行されません。
これでエラーメッセージが出なくなります。
では完成したコマンドを refresh_repository
に追加しましょう。
Step. 2 で完成したスクリプト
#!/bin/sh
function refresh_repository() {
local TARGET_DIR="$1"
cd "TARGET_DIR"
# develop を最新にする。
git checkout develop
git pull --prune
# リモートで削除されたブランチを削除する。
git remote prune origin
# develop ブランチにマージ済みの feature ブランチを削除する。
git branch --list --merged="develop" "feature/*" | xargs -r git branch -d
}
refresh_repository ~/project/api
refresh_repository ~/project/front
Step3. ブラッシュアップする
せっかくなので、他にも気になる点を修正してみます。
とりあえず気になるところといえば、ディレクトリの指定を個別で書いているところです。
refresh_repository ~/project/api
refresh_repository ~/project/front
project 配下のディレクトリをループで処理すると、きれいに書けそうです。
#!/bin/sh
BASE_DIR=~/project
function refresh_repository() {
# ... (省略)
}
for project in $(ls $BASE_DIR) ; do
refresh_repository "$BASE_DIR/$project"
done
ls $BASE_DIR
コマンドで処理対象のディレクトリを探し、ループするよう修正しました。
さらに refresh_repository
本体についても改良の余地があります。
develop ブランチを変数にして、 master や他の名前のブランチも処理できるようにしましょう。
最終的に完成したスクリプト
#!/bin/sh
BASE_DIR=~/project
BASE_BRANCH=develop
function refresh_repository() {
local TARGET_DIR="$1"
cd "TARGET_DIR"
# BASE_BRANCH がローカルに存在しないかも知れないので、先に fetch しておく。
git fetch
# ブランチを最新にする。
git checkout "$BASE_BRANCH"
git pull --prune
# リモートで削除されたブランチを削除する。
git remote prune origin
# BASE_BRANCH ブランチにマージ済みの feature ブランチを削除する。
# ただし BASE_BRANCH そのものは削除の対象外にする。(--no-contains)
git branch --list \
--merged="$BASE_BRANCH" \
--no-contains="$BASE_BRANCH" \
"feature/*" \
| xargs -r git branch -d
}
最後のコマンドは長くなりすぎたので、 \
を使って途中に改行を入れました。
まとめ
shell を使って git のブランチ管理を楽にするスクリプトを作成しました。
また、 shell について以下の書き方をご紹介しました。
- function で処理を共通化する。
- ls で取得した結果を for ... in でループ処理をする。
- xargs とパイプ「|」を使い、あるコマンドの実行結果を元に、別のコマンドを繰り返し実行する。
最初の shell を作るのは少々手間ですが、一度作ってしまえばその後の作業がぐっと楽になります。
もしかしたら shell に苦手意識を持っている方もいるかも知れませんが、この機会に是非触れてみてください。