repo diff した結果を別のリポジトリーに適用する
Android のリポジトリークローンに手を加えてから repo diff すると、配下の各 git プロジェクトに加えた変更が unified diff 形式で表示される。
しかしこの逆操作、つまり diff を別のリポジトリーにパッチとして適用するしくみは repo コマンドに用意されていないみたい。
同僚とパッチを共有しても、これを適用するしくみがないんじゃ不便だってんで、「くわっ」となってつくってみた。 repo diff の結果を適用する python スクリプトを。 (python を使った理由は repo が python スクリプトで実装されているから。)
# repo-apply.py import re import subprocess import sys def git_apply(dir, patch): print "patching project " + dir # open subprocess which stdin connected to pipe to fed up patch cmd = ['git','apply','--directory',dir,'-'] proc = subprocess.Popen(cmd, stdin=subprocess.PIPE) proc.stdin.write(patch) proc.stdin.close() proc.wait() rexp = '^project\s+([^\n]+)\n' + '((?:(?!^project)[^\n]*\n)*)' map((lambda (d, p): git_apply(d, p)), re.findall(rexp, sys.stdin.read(), re.M)) print "DONE"
repo コマンドのように、リポジトリーの位置を探索する機能はつくりこんでいないので、クローンした repo のトップレベルディレクトリーで使う。こんな感じに。:
$ cat result-of-repo-diff.patch | python repo-apply.py
サンプルワークフロー
ワークフローを考えると A さんが作業した結果を次のように保存して…:
machine-A $ repo diff > work-of-A.patch
これを受け取った B さんが適用する。:
machine-B $ cat work-of-A.patch | python repo-apply.py
適用する前にブランチを切った方がいいかもだけど、いまのところそこまで repo/git に習熟していないのでまた今度。
ローカルで試験
diff とってから、加えた変更を破棄して先のスクリプトで diff 結果を適用するという方法で、動作を確認できる。(といっても万全でない自信はたっぷりあるので、変更の破棄や適用はご自身の判断で!)
$ repo diff > my.patch $ repo forall -c git checkout . $ cat my.patch | python repo-apply.py
正規表現について
repo diff の結果は次の構造の繰り返しになっている。:
project
というわけで project ではじまる行の
使用した正規表現は "^project\s+([^\n]+)\n((?:(?!^project)[^\n]*\n)*)"。
この前半 "^project\s+([^\n]+)\n" は、行頭が project で、空白文字類がいくつか続き、そこから改行文字直前までをグループとしてキャプチャー ('([^\n]+)' がキャプチャー) するという意味。
残りの "((?:(?!^project)[^\n]*\n)*)" が、「project で始まる行の直前までの複数行」をあらわしている。
(?:...) はグループとしてまとめるけれどキャプチャーしないという特殊表現で、 (?!...) は今の位置からはじまる文字列が正規表現 ... にマッチしなければマッチングを続行するという特殊表現。
"[^\n]*\n" が一行全体にマッチングする正規表現で、 "(?!^project)[^\n]*\n" とすることで行先頭が project でない行という意味になる。このグループ (名前付けは不要) の繰り返し "(?:(?!^project)[^\n]*\n)*" をグループとしてキャプチャー "((?:(?!^project)[^\n]*\n)*)" して diff 結果を取り出した、というカラクリ。