svn:externals代替計画
個人的なソースコードはsubversionに全部突っ込んでまして、それなりに便利に使ってたのですが、時代の流れと共にいろいろ不都合が生じてきました。具体的には1.7系列にしたらsvkが動かなくなったのでオフラインコミットができなくなったり(将来的に本家で実装する予定だそうですがとりあえずsvkが動かないのは「今」なのです)、githubを使うようになって二重管理めんどかったり(公開用gitリポジトリの方はgit svn fetchしてmerge --squashしてcommitしてpushしてるだけなので手間はたいしたことないのですが、HDD上に同じソースコードが2つあるってのは何かと面倒を生じるのです)、あとまあいつの間にかbitbucketがプライベートリポジトリ作り放題になってたり最近私はWindowsを全く使ってない等の諸々の事情が合わさってsubversionを捨ててgitに完全移行する障壁が無くなってきた感じです。
という日記はともかく、今回はsvn:externalsの真似の話です。
各種分散型vcsも外部リポジトリを取り込む機能は提供してますが、subversionが便利だったのは、svn:externalsプロパティひとつで各種ゴタゴタを良きに計らってくれる点でした。
具体的には↓な運用。
lib1/ source/ lib.ads app1/ extlib/ svn:externalsに../../lib/source lib1を設定
この状態で全体をチェックアウトすると↓になります。
lib1/ source/ lib.ads app1/ extlib/ lib1/ lib.ads
lib1/source/lib.adsを更新してupdateするとapp1/extlib/lib1/lib.adsに反映されますし、逆も可。svkを使ってますと全部オフラインでできます(できました)。push先の指定なんかも不要で操作ミスを誘発するような要素もなくて、単純明快です。で、lib1以下だけ、app1以下だけをチェックアウトしても上手く動きます。
これをgitで再現したいわけです。
操作が煩雑になるのはまあ仕方ない。リポジトリを細かく分けないといけないのも仕方ない。app1にlib1を取り込むのはsubmoduleでできます。後はpartial checkout。ここまで前置き。
やりたいこととしては、lib1のmasterはlib1に必要なもの全部入りで、それとは別に外部からlib1を利用するため用に、lib1のsourceのみブランチを用意しよう、と。
http://progit.org/book/ja/ch6-7.html で説明されている例では、ブランチをmasterのサブディレクトリとして取り込み、ブランチ側の更新をmasterでマージしていますが、この逆ができたらいいなと。
とりあえずmaster作ります。
$ mkdir subtree && cd subtree && git init # 実験用リポジトリ Initialized empty Git repository in ~/subtree/.git/ $ mkdir source $ edit source/lib.ads # ソースを追加(editはTextWranglerを起動するコマンド) $ edit manual.txt # ソースコード以外のものも追加しておく $ git add source/lib.ads $ git add manual.txt $ git status # On branch master # # Initial commit # # Changes to be committed: # (use "git rm --cached <file>..." to unstage) # # new file: manual.txt # new file: source/lib.ads # $ git commit -m "master initial commit" [master (root-commit) 52b8a83] master initial commit 2 files changed, 2 insertions(+), 0 deletions(-) create mode 100644 manual.txt create mode 100644 source/lib.ads
空ブランチを作ります。作り方はgithub:pagesから。
$ git symbolic-ref HEAD refs/heads/sourceonly # 新しいブランチの名前 $ rm .git/index $ git clean -fdx # (.gitディレクトリ以外の)ファイルを全部手動で消してもいい Removing manual.txt Removing source/ $ git status # On branch sourceonly # # Initial commit # nothing to commit (create/copy files and use "git add" to track) $ git branch master # sourceonlyブランチは出てこない。initial commitがまだ無いから?
この新しいブランチに、masterのsourceディレクトリをそのままコミットします。
$ git cat-file -p master # masterブランチが指しているコミットオブジェクトを調べる tree b9231259976ca289eba4c430902a912d9bb12af7 # このtreeが本体だな author yt <...> 1327636245 +0900 committer yt <...> 1327636245 +0900 master initial commit $ git cat-file -p b9231259976ca289eba4c430902a912d9bb12af7 # treeの中を調べる 100644 blob ba0c3d41594a6bf8c9c45b8ea750426abc4789c2 manual.txt 040000 tree 493e1a42635421242b367f4793bc1cbd92cbcbef source # 目的のディレクトリ発見 $ git read-tree 493e1a42635421242b367f4793bc1cbd92cbcbef # sourceディレクトリをそのままindexにする $ ls # ワーキングコピーには反映されてない $ git status # On branch sourceonly # # Initial commit # # Changes to be committed: # (use "git rm --cached <file>..." to unstage) # # new file: lib.ads # # Changes not staged for commit: # (use "git add/rm <file>..." to update what will be committed) # (use "git checkout -- <file>..." to discard changes in working directory) # # deleted: lib.ads # ワーキングコピーがないためなんか言われてますが無視します # $ git commit -m "initial source only" [sourceonly (root-commit) 84cb94a] initial source only 1 files changed, 1 insertions(+), 0 deletions(-) create mode 100644 lib.ads $ git branch master * sourceonly
めでたくsourceonlyブランチができました。
続けてマージのテスト。
$ git checkout master # masterに戻って Switched to branch 'master' $ edit source/lib.ads # 何がしかの変更を加えます $ git status # On branch master # Changes not staged for commit: # (use "git add <file>..." to update what will be committed) # (use "git checkout -- <file>..." to discard changes in working directory) # # modified: source/lib.ads # no changes added to commit (use "git add" and/or "git commit -a") $ git add source/lib.ads $ git commit -m "change in master" [master fbdd687] change in master 1 files changed, 3 insertions(+), 1 deletions(-) $ git checkout sourceonly # sourceonlyブランチに切り替え Switched to branch 'sourceonly' $ ls lib.ads $ git merge -s subtree master # サブツリーマージ Auto-merging lib.ads CONFLICT (add/add): Merge conflict in lib.ads Automatic merge failed; fix conflicts and then commit the result. $ git status # On branch sourceonly # Unmerged paths: # (use "git add/rm <file>..." as appropriate to mark resolution) # # both added: lib.ads # no changes added to commit (use "git add" and/or "git commit -a")
あれ、なんかコンフリクトしました……?なんででしょう?
$ git reset --hard # というわけでやり直し HEAD is now at 84cb94a initial source only $ git merge -s subtree -Xtheirs master # とにかくmasterを使え Auto-merging lib.ads Merge made by the 'subtree' strategy. lib.ads | 4 +++- 1 files changed, 3 insertions(+), 1 deletions(-) $ git diff master # git diffには-sオプションが無いぞ…… # 大量のログ省略 $ edit lib.ads # しょうがないので目視確認、ちゃんとmasterの内容になってそう $ git log commit 5f079753cadb727a813556de9b29f2a2851b0552 Merge: 84cb94a fbdd687 Author: yt <...> Date: Fri Jan 27 13:30:36 2012 +0900 Merge branch 'master' into sourceonly commit fbdd68745883a47099ad386a1a40d2514ad3dca2 Author: yt <...> Date: Fri Jan 27 13:20:02 2012 +0900 change in master commit 84cb94a7ea53233bc89374413980181694fd0b6c Author: yt <...> Date: Fri Jan 27 12:53:00 2012 +0900 initial source only commit 52b8a833a00339de319d8dbd2037fd41e521e34f Author: yt <...> Date: Fri Jan 27 12:50:45 2012 +0900 master initial commit
後はこのsouceonlyブランチを使う側でサブモジュールとして取り込めばおしまいです。
めでたしめでたし……煩雑ですよねー。
subversionのローカルコミット実装を熱烈希望します!!