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 で始まる行の直前までをパッチ内容として取り出す、という操作が必要。

使用した正規表現は "^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 結果を取り出した、というカラクリ。