Git で開発物を管理することはよくありますが、ある程度規模が大きい開発プロジェクトでは複数の Git リポジトリに分散することがあります。そのような場合に、複数のリポジトリを同時にコマンド実行できると、作業の所要時間を大きく減らせることがあります。
この記事では、複数の git ローカルリポジトリを同時に操作する方法を、基本形とバリエーションに分けて紹介します。
前提条件
- bash
基本形
最も基本的な形は次の通りです。
# 例: 各リポジトリで git status を実行
ls -1 | xargs -I {} bash -c 'echo {}:; git --no-pager -C {} status'
次のように、各リポジトリの git status
の結果を得られます。
$ ls -1 | xargs -I {} bash -c 'echo {}; git --no-pager -C {} status'
repo1:
On branch main
Your branch is up to date with 'origin/main'.
nothing to commit, working tree clean
repo2:
On branch main
Your branch is up to date with 'origin/main'.
nothing to commit, working tree clean
リポジトリ一覧取得のカスタマイズ
ただし、前半の ls -1
は、次のことを前提にして対象のリポジトリパスをリストアップしています。
- カレントディレクトリに対象のリポジトリがそろっていること
- カレントディレクトリにそれ以外のファイル・ディレクトリが無いこと
これらの条件を満たさない場合は、以下の例のように前半をカスタマイズしてください。
cat list.txt | xargs -I {} bash -c 'echo {}; git --no-pager -C {} status'
# .git ディレクトリがあれば、そこを git リポジトリとして使用する
find . -type d -name .git | sed 's:.git$::' | xargs -I {} bash -c 'echo {}; git --no-pager -C {} status'
基本原理
ここでは、次の2つの仕様を活用しています。
- パイプ
|
とxargs -I <mark> <command>...
で、前のコマンドの標準出力を1行ずつ取り出して指定のコマンドの指定の位置に挿入して実行することができる (-I
を使う場合、1行あたり1回 が実行され、複数個所に<mark>
がある場合、それらの<mark>
には同じ文字列が挿入される) -
git
コマンドにオプション引数-C <path>
を指定することで、カレントディレクトリ以外のパスのリポジトリを操作できる
xargs -I <mark>
によって、その前のコマンドで用意したリポジトリパス一覧を git
コマンドに割り振っています。割り振られたパスは git
の -C
オプションとして使用しているので、そのパスのリポジトリに対して処理が実行されます。
なお、ここで xargs
に渡すコマンドとして直接 git
を指定せず bash -c ...
を指定しているのは、xargs
で割り振った各リポジトリパス毎に複数のコマンドを実行するためです。上の例では git
の前に echo
を実行するようにすることでどのリポジトリに関する出力かがわかるようにしています。
バリエーション
次に、各シチュエーションに応じて基本形を修正したバリエーションを紹介します。
ローカルリポジトリの配置に応じて、前半部分は リポジトリ一覧取得のカスタマイズ を見ながら書き換えてください。
バリエーションでは、--no-pager
が不要な場合に適宜省略しています。自分でアレンジする際は、適宜 --no-pager
を git
のオプションとして足してください。要不要を判断できない場合は毎回つけて構いません。
標準出力を捨てる (git checkout <branch> --
)
ls -1 | xargs -I {} bash -c 'echo {} 1>&2; git -C {} checkout <branch> -- > /dev/null || echo error: {}' 2> tmp.txt
基本形のままでもよいのですが、次の方針のもと改良しています。
- リポジトリが多いと一部のみ失敗したときにエラーメッセージが埋もれてしまうので、成功した場合は何も表示されないでほしい
- ⇒ git の標準出力は捨て、標準エラー出力 (成功時も何か出ることがある) はファイルへ出力
- エラー時は見落とさない方法で知らせてほしい
- ⇒
git ...
の終了コードが0以外の場合は「error: <リポジトリパス>」と標準出力する
- ⇒
- エラー時は要因を調べられるようにしたい
- ⇒ git の標準エラー出力はファイル出力しておき、また、リポジトリ名も同じファイルに出力しておく
このコードでは標準エラー出力のリダイレクト先を tmp.txt
としていますが、状況に合わせて適切に変更してください。基本的には、上書き保存されること、消し忘れるといつまでも残ること、自分以外の人が見ると意味不明で判断に困るかもしれないことを念頭に置いて決めてください。
実行例
$ ls -1 | xargs -I {} bash -c 'echo {} 1>&2; git -C {} checkout main -- > /dev/null || echo error: {}' 2> tmp.txt
error: repo2
$ cat tmp.txt
repo1
repo2
fatal: invalid reference: main
main ブランチへの切り替えを試みました。repo2 のみブランチ main が存在しないことによるエラーが起きていることがわかります。
この方法は、成功時の出力に興味がない場合に使用できます。状況にもよりますが、次のコマンドが該当します。
git checkout ...
git switch <branch>
git reset ...
git fetch
git pull
標準出力の各行の先頭にリポジトリ名を付ける (git branch --show-current
, git grep <pattern>
)
ls -1 | xargs -I {} bash -c 'git -C {} branch --show-current | sed s@^@{}:\\x20@'
ls -1 | xargs -I {} bash -c 'git -C {} grep <pattern> | sed s@^@{}/@'
基本形のままでもよいのですが、次の方針のもと改良しています。
- 見やすさのため、リポジトリ1つあたり、出力を1行にしたい
- 行の先頭にリポジトリ名を付けたい。区切りはひとまず
:
にする 1 -
git grep
は行の先頭がファイルパスになっているので、リポジトリ名を連結してカレントディレクトリからの相対パスを完成させる
実行例
$ ls -1 | xargs -I {} bash -c 'git -C {} branch --show-current | sed s@^@{}:\\x20@'
repo1: main
repo2: master
$ ls -1 | xargs -I {} bash -c 'git -C {} grep SSL | sed s@^@{}/@'
repo1/src/main/com/example/HttpClient.java: private static final String SSL_PROTOCOL = "TLS1.2";
repo1/src/main/com/example/HttpClient2.java: private static final String SSL_PROTOCOL = "TLS1.2";
repo2/example/client.py:SSL_PROTOCOL = "TLS1.2"
標準出力をExcelでインポートする際にリポジトリ名を別の列にしたい場合、sed コマンドを sed s@^@{}:@
(コロン :
の後ろにスペース \\x20
を付けない) にすると、テキストウィザードでの読み込みで列を分割できるようになります。
この方法は、標準出力1行が1レコードになるようなコマンド全般で活用できます。例えば、以下のコマンドが該当します。
git branch
git grep
git diff --name-only
git diff --shortstat
git status --short
git ls-files
git show --no-patch --format='%H %d'
-
半角スペースはsedコマンド中で
\\x20
で表しています ↩