ノード:Merging Repeatedly Into The Trunk, 次:, 前:Some Principles For Working With Branches, 上:Going Out On A Limb (How To Work With Branches And Survive)



Merging Repeatedly Into The Trunk

qsmith はトランクを jrandom と共有していて、それを不安定にさせたくな いのでしばらくブランチ上で開発をする必要があるとします。最初のステッ プはブランチの作成です。qsmith がまず通常の(ブランチでない)タグを作 成し、その後ブランチを作っていることに注意してください:

paste$ pwd
/home/qsmith/myproj
paste$ cvs tag Root-of-Exotic_Greetings
cvs tag: Tagging .
T README.txt
T foo.gif
T hello.c
cvs tag: Tagging a-subdir
T a-subdir/whatever.c
cvs tag: Tagging a-subdir/subsubdir
T a-subdir/subsubdir/fish.c
cvs tag: Tagging b-subdir
T b-subdir/random.c
paste$ cvs tag -b Exotic_Greetings-branch
cvs tag: Tagging .
T README.txt
T foo.gif
T hello.c
cvs tag: Tagging a-subdir
T a-subdir/whatever.c
cvs tag: Tagging a-subdir/subsubdir
T a-subdir/subsubdir/fish.c
cvs tag: Tagging b-subdir
T b-subdir/random.c
paste$

最初トランクにタグづけした点は、将来、ブランチを作成した時点のトラン クにアクセスするときに必要になるでしょう。そういう必要がでてきた場合 に、ブランチ自体を参照することなくトランクのスナップショットを参照す ることができます。ブランチタグでは、ブランチの根のあるトランクではな く、ブランチにアクセスしてしまうので使えません。ブランチが生えている その同じリビジョンに、通常のタグを作るしか方法はありません。(「ブラ ンチの原則4、ブランチポイントにはノン-ブランチタグを作成する」とでも 言うべきこのルールを忠実に守っている人も居ますが、多くのサイトではこ れが守られていませんし、まあかまわないようにも思えます。趣味の問題で すね) これ以降ではこのようなノン-ブランチタグをブランチポイント タグと呼びます。

ブランチポイントタグは Root-of- で始まり、ハイフンではなくア ンダスコアで単語を区切った実際のブランチ名を続ける、という命名規則に も注意してください。実際のブランチを作成する時には、そのタグ名の最後 は -branch とします。タグ名を見ればそのタグがブランチタグだと わかるようにするためです。(ブランチポイントタグ Root-of-Exotic_Greetings には -branch とは書いてありません、 これはブランチタグではありませんので。) もちろん、この命名規則を使う 必要は特にありませんが、何らかの規則を用いるべきだと思います。

ああ、ちょっとうるさく言いすぎましたね。小さなプロジェクトでは、誰が 何をしているかをみんながが知っていて、混乱が起こっても容易に回復でき るので、このような規則を使わねばならないわけではありません。ブランチ ポイントタグを使うとかタグに厳しい命名規則を課すかどうかというのは、 プロジェクトの複雑度やブランチのやりかたによります。(あとでいつでも 戻って古いタグを新しい規則に沿うように直すことができることを忘れない でください。古いタグのバージョンにアクセスして、新しいタグをつけ、古 いタグを削除すればよいのです)

さて、qsmith はブランチで作業を開始します:

paste$ cvs update -r Exotic_Greetings-branch
cvs update: Updating .
cvs update: Updating a-subdir
cvs update: Updating a-subdir/subsubdir
cvs update: Updating b-subdir
paste$

ファイルをいくつか変更し、それをブランチへコミットします:

paste$ emacs README.txt a-subdir/whatever.c b-subdir/random.c
...
paste$ cvs ci -m "print greeting backwards, etc"
cvs commit: Examining .
cvs commit: Examining a-subdir
cvs commit: Examining a-subdir/subsubdir
cvs commit: Examining b-subdir
Checking in README.txt;
/usr/local/newrepos/myproj/README.txt,v  <--  README.txt
new revision: 1.14.2.1; previous revision: 1.14
done
Checking in a-subdir/whatever.c;
/usr/local/newrepos/myproj/a-subdir/whatever.c,v  <--  whatever.c
new revision: 1.3.2.1; previous revision: 1.3
done
Checking in b-subdir/random.c;
/usr/local/newrepos/myproj/b-subdir/random.c,v  <--  random.c
new revision: 1.1.1.1.2.1; previous revision: 1.1.1.1
done
paste$
<<<<<<< j-chapter-6.texi =======

この間、jrandom はトランクでの作業を続行しています。qsmith が触った 3つのファイルのうち2つを変更しました。 >>>>>>> 1.4

<<<<<<< j-chapter-6.texi Meanwhile, jrandom is continuing to work on the trunk. She modifies two of the three files that qsmith touched. Just for kicks, we'll have her make changes that conflict with qsmith's work:

======= 一方、 jrandom はトランク上で作業を続行しています。qsmith が触った3 つのファイルのうち、2つを変更しました。試しに、qsmith とコンフリクト するような変更を施してみましょう:

>>>>>>> 1.4

floss$ emacs README.txt whatever.c
 ...
floss$ cvs ci -m "some very stable changes indeed"
cvs commit: Examining .
cvs commit: Examining a-subdir
cvs commit: Examining a-subdir/subsubdir
cvs commit: Examining b-subdir
Checking in README.txt;
/usr/local/newrepos/myproj/README.txt,v  <--  README.txt
new revision: 1.15; previous revision: 1.14
done
Checking in a-subdir/whatever.c;
/usr/local/newrepos/myproj/a-subdir/whatever.c,v  <--  whatever.c
new revision: 1.4; previous revision: 1.3
done
floss$

コンフリクトがあるようには見えません。当たり前ですね、ブランチとトラン クをマージしようとしたわけではありませんから。ではここで jrandom が マージを実行します:

floss$ cvs update -j Exotic_Greetings-branch
cvs update: Updating .
RCS file: /usr/local/newrepos/myproj/README.txt,v
retrieving revision 1.14
retrieving revision 1.14.2.1
Merging differences between 1.14 and 1.14.2.1 into README.txt
rcsmerge: warning: conflicts during merge
cvs update: Updating a-subdir
RCS file: /usr/local/newrepos/myproj/a-subdir/whatever.c,v
retrieving revision 1.3
retrieving revision 1.3.2.1
Merging differences between 1.3 and 1.3.2.1 into whatever.c
rcsmerge: warning: conflicts during merge
cvs update: Updating a-subdir/subsubdir
cvs update: Updating b-subdir
RCS file: /usr/local/newrepos/myproj/b-subdir/random.c,v
retrieving revision 1.1.1.1
retrieving revision 1.1.1.1.2.1
Merging differences between 1.1.1.1 and 1.1.1.1.2.1 into random.c
floss$ cvs update
cvs update: Updating .
C README.txt
cvs update: Updating a-subdir
C a-subdir/whatever.c
cvs update: Updating a-subdir/subsubdir
cvs update: Updating b-subdir
M b-subdir/random.c
floss$

ファイルが2つ、コンフリクトを起こしました。たいした問題ではありません、 jrandom はいつものように機転を利かせてコンフリクトを解消し、コミット した後、トランクにマージ成功のタグをつけます:

floss$ emacs README.txt a-subdir/whatever.c
 ...
floss$ cvs ci -m "merged from Exotic_Greetings-branch (conflicts resolved)"
cvs commit: Examining .
cvs commit: Examining a-subdir
cvs commit: Examining a-subdir/subsubdir
cvs commit: Examining b-subdir
Checking in README.txt;
/usr/local/newrepos/myproj/README.txt,v  <--  README.txt
new revision: 1.16; previous revision: 1.15
done
Checking in a-subdir/whatever.c;
/usr/local/newrepos/myproj/a-subdir/whatever.c,v  <--  whatever.c
new revision: 1.5; previous revision: 1.4
done
Checking in b-subdir/random.c;
/usr/local/newrepos/myproj/b-subdir/random.c,v  <--  random.c
new revision: 1.2; previous revision: 1.1
done
floss$ cvs tag merged-Exotic_Greetings
cvs tag: Tagging .
T README.txt
T foo.gif
T hello.c
cvs tag: Tagging a-subdir
T a-subdir/whatever.c
cvs tag: Tagging a-subdir/subsubdir
T a-subdir/subsubdir/fish.c
cvs tag: Tagging b-subdir
T b-subdir/random.c
floss$

qsmith は開発を続行するのにマージが終わるのを待つ必要はありません。 qsmith は jrandom がマージした後の一連の変更に対し、タグをつけておけ ばよいのです。(あとで jrandom はこのタグ名を知る必要があります; 一般 に、ブランチというものは開発者が頻繁かつ綿密に連絡を取り合って初めて 成り立つものなのです):

paste$ cvs tag Exotic_Greetings-1
cvs tag: Tagging .
T README.txt
T foo.gif
T hello.c
cvs tag: Tagging a-subdir
T a-subdir/whatever.c
cvs tag: Tagging a-subdir/subsubdir
T a-subdir/subsubdir/fish.c
cvs tag: Tagging b-subdir
T b-subdir/random.c
paste$ emacs a-subdir/whatever.c
 ...
paste$ cvs ci -m "print a randomly capitalized greeting"
cvs commit: Examining .
cvs commit: Examining a-subdir
cvs commit: Examining a-subdir/subsubdir
cvs commit: Examining b-subdir
Checking in a-subdir/whatever.c;
/usr/local/newrepos/myproj/a-subdir/whatever.c,v  <--  whatever.c
new revision: 1.3.2.2; previous revision: 1.3.2.1
done
paste$

もちろん、qsmith は変更が終わり次第タグをつけるべきです:

paste$ cvs -q tag Exotic_Greetings-2
T README.txt
T foo.gif
T hello.c
T a-subdir/whatever.c
T a-subdir/subsubdir/fish.c
T b-subdir/random.c
paste$

これをやっている間、qsmith が一連の編集で触ったのとは別のファイルを jrandom が変更したとします:

floss$ emacs README.txt
 ...
floss$ cvs ci -m "Mention new Exotic Greeting features" README.txt
Checking in README.txt;
/usr/local/newrepos/myproj/README.txt,v  <--  README.txt
new revision: 1.17; previous revision: 1.16
done
floss$

ここで qsmith は新たな変更をブランチにコミットし、jrandom は別のファ イルのコンフリクトしない変更をトランクにコミットします。jrandom がブ ランチをもう一度マージしようとしたときに何が起こるか見てみましょう:

floss$ cvs -q update -j Exotic_Greetings-branch
RCS file: /usr/local/newrepos/myproj/README.txt,v
retrieving revision 1.14
retrieving revision 1.14.2.1
Merging differences between 1.14 and 1.14.2.1 into README.txt
rcsmerge: warning: conflicts during merge
RCS file: /usr/local/newrepos/myproj/a-subdir/whatever.c,v
retrieving revision 1.3
retrieving revision 1.3.2.2
Merging differences between 1.3 and 1.3.2.2 into whatever.c
rcsmerge: warning: conflicts during merge
RCS file: /usr/local/newrepos/myproj/b-subdir/random.c,v
retrieving revision 1.1
retrieving revision 1.1.1.1.2.1
Merging differences between 1.1 and 1.1.1.1.2.1 into random.c
floss$ cvs -q update
C README.txt
C a-subdir/whatever.c
floss$

コンフリクトが起きてしまいました! これ、予測していましたか?

マージの意味に問題があるようです。先に An Overview of CVS で述 べた通り、作業コピー中で

floss$ cvs update -j BRANCH

を実行すると、CVS はブランチの根と先端の相違を作業コピーにマージする のです。今回の場合、それらの変更の大半は、jrandom が最初に行ったマー ジの時に既にトランクに組み込まれているので、この動作は問題になります。 CVS がその変更を再びマージしようとすると(その変更自身に上書きするよ うなことになります)、当然コンフリクトだと見なされるわけです。

jrandom が本当にやりたかったことは、ブランチのうち、一番最後に実行し たマージ時点と現在のブランチ先端の間の変更を作業コピーにマージする、 ということだったのです。これをするには update に -j フラグを2つ渡せ ばよいのです。An Overview of CVS を思い出して下さい。これをす るには、各フラグでどのリビジョンを指定すれば良いかを知っておかなけれ ばなりません。幸い、qsmith は最後のマージポイントにタグをつけておき ましたので(hurrah for planning ahead!)、これについて問題はありません。 まずは jrandom の作業コピーを元のきれいな状態に戻しましょう。そこか ら再マージするのです:

floss$ rm README.txt a-subdir/whatever.c
floss$ cvs -q update
cvs update: warning: README.txt was lost
U README.txt
cvs update: warning: a-subdir/whatever.c was lost
U a-subdir/whatever.c
floss$

さて、これでマージの準備ができました、今回は qsmith がつけてくれたタ グを使うことにしましょう:

floss$ cvs -q update -j Exotic_Greetings-1 -j Exotic_Greetings-branch
RCS file: /usr/local/newrepos/myproj/a-subdir/whatever.c,v
retrieving revision 1.3.2.1
retrieving revision 1.3.2.2
Merging differences between 1.3.2.1 and 1.3.2.2 into whatever.c
floss$ cvs -q update
M a-subdir/whatever.c
floss$

いいカンジです。qsmith の変更が whatever.c に組み込まれました; jrandom はコミットし、タグをつけます:

floss$ cvs -q ci -m "merged again from Exotic_Greetings (1)"
Checking in a-subdir/whatever.c;
/usr/local/newrepos/myproj/a-subdir/whatever.c,v  <--  whatever.c
new revision: 1.6; previous revision: 1.5
done
floss$ cvs -q tag merged-Exotic_Greetings-1
T README.txt
T foo.gif
T hello.c
T a-subdir/whatever.c
T a-subdir/subsubdir/fish.c
T b-subdir/random.c
floss$

qsmith がマージポイントにタグをつけるのを忘れても、望みがなくなって しまったわけではありません。 jrandom が qsmith の最初の変更を大体い つ頃コミットしたか覚えていれば、日付でフィルタリングしてみることがで きます:

floss$ cvs update -j Exotic_Greetings-branch:3pm -j Exotic_Greetings_branch

日付でフィルタリングするのは、最後の切り札にはなりますが、あまり理想 的な方法とは言えません。なぜなら、信頼に足る開発上の名称ではなく、人 の記憶に頼った方法だからです。qsmith の初回マージ分の変更が、1回のコ ミットではなく何度かに分けてコミットされていた場合、jrandom が間違っ て日付や時刻を指定すると、変更全てではなく一部分だけを取ってきてしま う可能性があります。

qsmith の変更のタグづけできる各ポイントが、1回のコミットでリポジトリ に送られなければならない理由はありません。この例でたまたまこうなって いるだけなのです。実際には、qsmith はタグとタグの間に何度かコミット するかもしれません。qsmith はブランチ上でひとりで作業したければ、そ うすることができます。タグというのはつまり、トランクへマージできると 思う点をブランチ上に連続的に記録していくことなのです。jrandom が常に -j フラグを2つ使ってマージし、qsmith のマージタグを正しい順序で注意深 く用いてそれぞれを1度だけ使う限り、トランクでダブルマージの問題が起 こることはないでしょう。コンフリクトは起こるかもしれませんが、それは 人の手で解決しなければならない、不可避なものなのでしょう。同じ領域に おいて、ブランチでもトランクでも変更があるような状況だというわけです。