Xcode + Subversion でソースコードの版管理を行う
SPECIAL
Subversion でソースコードを版管理する
Xcode にも Microsoft Visual Studio と同じように、ソースコードの版管理を行う機能が備わっています。
ソースコードの版管理ができれば、編集前と編集後とを都度比較したり、必要に応じて前の状態に戻したりなど、コードを回収して行く上でとても便利で安心です。
Xcode の場合には Subversion という版管理のソフトウェアを利用して、ソースコードを管理することができるようになっているようでしたので、今回はそれをやってみようと思います。
環境としては Mac OS X 10.6.5 上の Xcode 3.2.4 で Subversion を利用するといった形になります。
Subversion のインストール
Mac OS X 10.6 Snow Leopard には、標準で Subversion がインストールされているという話を耳にしました。
ただ、今回はそれを知らずにインストールから行ってみたので、それについても触れておこうと思います。もし "svn" コマンドを利用できない場合には、ここの手順でインストールを行うと良いと思います。
Subversion をインストールするに当たり、今回は Mac Ports を利用しようと思います。
そのため、あらかじめ Mac Ports を利用できるように Mac OS X の環境を整えておく必要があります。Mac Ports を利用できるようにする方法については EZ-NET: Mac Ports を利用できるようにする でも記してありますので、そちらも参考にしてみてください。
それでは、Mac Ports を利用して、次のようにして Subversion のインストールを行います。
sudo port -v install subversion
Mac OS X のターミナルで上記のコマンドを実行すると Subversion のインストールが始まります。
途中でパスワードを聞かれた場合は、Mac OS X でシステム設定を調整するときのパスワードを入力すれば大丈夫です。
リポジトリを作成する
Subversion のインストールが完了したら、続いて、版管理のためのデータを格納する "リポジトリ" を作成します。
ここでは、ターミナルから次のようにして "test.svn" という名前のリポジトリを "/svn" ディレクトリーに作成してみたいと思います。
mkdir /svn
svnadmin create /svn/test.svn --fs-type fsfs
ちなみにここで指定した "--fs-type fsfs" というオプションは、リポジトリを FSFS (Native filesystem) で作成するという意味になるそうです。ほかの選択肢としては BDB (Berkeley database) があるそうです。
ともあれこれで、リポジトリを作成することができました。
今作成したリポジトリは "file:////svn/test.svn" という名前でアクセスすることができますので、後はここに版管理を行いたいフォルダーを追加して行くことになります。
登録作業は、ターミナルから "svn import 取り込まれるディレクトリ 取り込むリポジトリ" という形で行うこともできるようですが、今回は後で Xcode の "リポジトリ" ツールを使用して取り込もうと思います。
Xcode にリポジトリを登録する
リポジトリを登録する
Subversion にリポジトリを作成したら、それを Xcode に登録します。
Xcode を起動したら、アップルメニューの "SCM" から "SCM リポジトリを構成" を選択します。すると "Xcode 環境設定" 画面が表示されるので、そこの "リポジトリ" を選択します。
そうしたら、左下の [ + ] ボタンを押すと、登録するリポジトリ名を入力するダイアログが表示されるので、ここで何か任意の名前を入力します。
そして URL の欄に、先ほど作成したリポジトリを "file:////svn/test.svn" という形で入力して、次の枠へ移動すると、スキームやパスといった情報が自動的に入力されました。
これで 【OK】 ボタンを押すと、Xcode にリポジトリが登録されました。
リポジトリを構成する
リポジトリはまだ作ったばかりで空のため、まずはそこに、これまでに作成したプロジェクトを取り込んで行くことにします。
アップルメニューから再び "SCM" を選択すると、今度は "リポジトリ" という項目が選択できるようになっているので、それを選択すると、リポジトリに格納されている内容を確認できる画面が表示されました。
そうしたら、必要に応じて "読み込む" を選んで Xcode プロジェクトが格納されているフォルダーをリポジトリーに取り込んで行きます。
独自の共通ライブラリなどを別の階層に分けて開発しているような場合は、"ディレクトリを作成" を選んで階層が実際とずれないように工夫しつつ、取り込みを行っていくと、後になって階層が分からなくなることがなくて良いかもしれません。
また、後でもお話しますが、Xcode が生成する "build" フォルダーを階層に含むフォルダーを取り込んだ場合は、それを削除しておかないと、内容を変更してリポジトリにコミットする時点で 155005 エラーが発生してしまうようなので注意が必要です。
プロジェクトのリポジトリを構成する
このようにしてリポジトリの準備が整ったら、次にプロジェクトごとのリポジトリ設定を行います。
Xcode で目的のプロジェクトを開いたら、アップルメニューの "SCM" から "このプロジェクトの SCM を構成" を選択します。
すると、プロジェクトの情報ダイアログが表示されるので、そこから "ルートと SCM を構成..." ボタンを選択して、"<プロジェクト・ファイル・ディレクトリ>" のリポジトリとして、先ほど Xcode に登録した "test.svn" を一覧の中から選択します。
また、独自ライブラリ等で、階層が複数に分かれている場合には、続けて [ + ] ボタンを押して、版管理が必要なフォルダーを選択して、そのリポジトリとして、そのファイル群を取り込んでおいたリポジトリを選択すれば良いようでした。
これで、このプロジェクトにリポジトリが関連付けられた感じです。
こうすることで、アップルメニューの "SCM" から、現時点と保存された状態との間の変更内容などを、その都度確認したりすることができるようになりました。変更をコミットすることで、コメントを残しつつ、意識的なタイミングで、変更をリポジトリに反映させることも可能です。
ただ、プロジェクトの設定で、リポジトリの階層内に build ディレクトリが生成される設定になっている場合、プロジェクト全体のコミットを実行してみると、SCM エラーとして、次のようなエラーメッセージが表示されてしまうようでした。
"エラー : 155005 (Working copy not locked; this is probably a bug, prease report) 説明 : Commit failed (details follow): ....
この後に続いて、エラーが発生したディレクトリーが表示されていたのですけど、見てみるとやはり "build" ディレクトリーの中に生成された Mac アプリケーションが問題となっているようです。
Xcode がビルドやクリーンアップの際にこの中のフォルダーを変更してしまうせいなのか、それとも Mac のアプリケーションは内部では特殊なフォルダーとして扱われるためそれをコミットしようとしておかしくなっているせいなのか、そんな事情でうまくコミットできなくなっているらしいです。
プロジェクト全体のコミットで 155005 エラーを回避する
Xcode の "SCM" メニューから "プロジェクト全体をコミット" をした際に 155005 エラーが発生するのを回避するためには、Xcode が出力するビルドファイルや中間ファイルを、リポジトリが管理するよりも外のフォルダーへ出力するようにする必要があるようです。
そのためには、プロジェクトから "プロジェクト設定の編集" を選択して、"一般" 設定から "ビルドプロダクトの保存場所" を変更します。
ここで "カスタムの保存場所" を選択して、リポジトリよりも外側のフォルダーに変更します。また "中間ビルドファイルの保存場所" については "ビルドプロダクトの保存場所" を選択しておきます。
そして、これまで利用していた、リポジトリ階層内の "build" ディレクトリーはまるごと削除しておきます。ローカルのフォルダー内もそうですが、リポジトリ内の build フォルダーについても削除しておきます。
この状態で、プロジェクト全体のコミットを行うことができるようになりました。
この状態でも 155005 エラーでコミットできない場合には、SCM からリポジトリを開いて、プロジェクトのすべての内容を再度 "チェックアウト" すれば、ローカルの Subversion 情報ファイルが更新されて、上手くコミットできるようになるようでした。
Xcode で版管理を行ってみる
Xcode 上で Subversion による版管理ができるようになったので、基本的な事項について、見て行きたいと思います。
ファイルの編集
まず、基本的な編集の流れですが、ソースファイルの変更を行う場合には、特に何もせずに編集を開始すれば良いようでした。
編集が完了して、それをリポジトリに登録したい場合には、そのファイルを右クリックして "変更をコミット" を選択すれば良いようです。逆に、変更したけれどそれを元に戻したいという場合には "変更を破棄" を選択すれば良いようです。
なお、これらの操作は、変更されたファイルについてのみ行うことができるようで、変更されていないファイルで右クリックしても、メニューの中にこれらの候補は表示されないようでした。
プロジェクトへのファイル追加
新しいファイルをプロジェクトに作成する場合は、まずは通常通りにファイルを新規作成した上で、それを右クリックして "リポジトリに追加" を実行する必要があるようです。
リポジトリに追加した段階では、まだローカル上にファイルが存在するだけの状態で、それをコミットすることで初めて、その内容がリポジトリへ反映されるようになるようでした。
プロジェクト内のファイル削除
プロジェクト内のファイルの削除については、Xcode 内からファイルを削除しようとすると、まず Xcode が削除しても良いかを尋ねてきます。それに OK した上で、続いてリポジトリから取り除くかを尋ねられるので、これにも OK することで、リポジトリから削除を行うことができました。
ただし、この段階ではまだリポジトリ内にファイルが存在しているようでした。リポジトリ内からもファイルを削除するためには、"SCM の結果" としてリストアップされているものの中から、該当するファイルについて "変更をコミット" する必要があるようでした。
編集内容の確認
ファイルを編集すると、現在の内容とリポジトリ内での内容とを比較することができるようになっています。
編集したファイルで右クリックをして "比較" を選択すると、どこが編集されているのかを確認することができます。ただし、編集していない場合には、右クリックをしても "比較" といった版管理に関するメニューが表示されないようでした。
編集していないファイルについても、そのファイルを選択した状態で、アップルメニューの "SCM" から "比較" を選択して、その中の "リビジョン" を選択すれば、リポジトリに記録されている任意の版(リビジョン)との変更点を確認することができました。
記録されているリビジョンを確認する
いつにどのリビジョンが記録されたかどうかについては、ターミナルから次のようなコマンドで確認することができました。
svn log file:////svn/test.svn
調べたいリポジトリのディレクトリへ移動していれば、"svn log" だけでも確認することができるようでしたが、ときおり何らかの理由で少し古い情報が取得できてしまう場合があるようでした。そのような時は "svn up" として作業コピーを更新すれば新しい情報を確認することができるようになるようでした。
ちなみに、どのリビジョンでどのファイルをどう処理したかといった情報も併せて確認したい場合には、次のようにします。
svn log --verbose file:////svn/test.svn
SCM の "更新" と "アップデート" について
SCM のメニューの中で "更新" と "アップデート" という二つのメニューが存在しています。
パソコンにおいて、日本語で "更新" といえば "アップデート" の事を言うような気がするので、それぞれの違いを言葉だけでは判断できなかったので調べてみたところ、どうやら次のような操作を行うための機能となっているようでした。
更新
まず "更新" についてですが、こちらはローカルコピーの SCM 状態を確認するためらしいとの情報がありました。
コマンドラインでいうところの "svn status" になるそうなので、つまり、ローカル上の内容と現在のリポジトリの内容とを照らし合わせて、ローカル上のファイルが現在どのような状態にあるかを確認することになるようです。
ただ、もしかするとこれは "svn up" になるのではないかとも思います。
"svn up" は、リポジトリの更新内容をローカルの作業コピーに取り込むという動作ですが、これを実行することで、ローカルファイルからも最新のリビジョン情報を取得することができるようになりました。
ただ、プロジェクト全体の更新を実行すると、そのような動作が得られることがあるようでしたが、得られない時もあり、まだ何とも言えないところではありますが、もし "svn status" なのだとしたら実行しても表向きは何も起こらないのは不思議ですし、"SCM の情報を見る" との違いがわからないところもあって、この操作はどちらかといえば "svn up" と同じと考えた方が自然なように思います。
ちなみに、もしこれがネットの情報にあった通り "svn status" と同じであったとした場合、その "状況" に表示される記号とその意味は、細かい意味が分からないものも多いのですが、とりあえず、次のようになるようでした。
変更されていません。 | |
A | リポジトリへの追加予告がされた状態です。 |
D | リポジトリからの削除予告がされた状態です。 |
M | 修正された状態です。 |
R | 別の内容に置き換えられた状態です。 |
C | ファイルの内容が、リポジトリの更新内容と衝突している状態です。 |
X | アイテムが外部定義されている状態です。 |
I | 管理対象から除外された状態です。 |
? | 管理対象下にない状態です。 |
! | ファイルが存在しない状態です。 |
~ | 管理されているものとは別の形式に置き換えられた状態です。ファイルだったはずのものが、ディレクトリになっているなどの場合が該当するようです。 |
L | ロックされた状態です。 |
O | 他のユーザーや作業コピーによってロックされた状態です。 |
* | リポジトリにもっと新しいバージョンが存在する状態です。 |
U | 更新されたファイルです。 |
G | マージ(結合)されたファイルです。 |
アップデート
"アップデート" の方はというと、ローカル上の内容を、リポジトリ内の最新状態にアップデートするためのものだそうです。
コマンドラインでいう "svn update" に相当する機能だそうで、つまり、複数人数で開発を行っている場合に、ローカルコピーを最新の状態に更新するために使用するもののようでした。
ちなみに Xcode では、目的のファイルを選択した状態で、アップルメニューの "SCM" から "SCM の情報をみる" を選択すると、そのファイルについて保持しているリビジョン一覧を確認することができるようになっていました。
ここで、目的のリビジョンを選択して "アップデート" ボタンを押すと、ローカルファイルの内容をそのリビジョン時点での内容に差し替えてくれるようになっていました。ただ、この操作を行うとき、差し替えても良いかといった確認は表示されないようなので、間違ってアップデートボタンを押してしまわないように注意したいところです。
衝突(コリジョン)が検出された場合について
ところで、プロジェクト全体のリビジョンを特定の段階にまで戻すにはどうしたら良いのだろうと思い、試行錯誤をしていたところ、変更の衝突が検出されてしまい、なかなかうまくそれを実施することができませんでした。
そこで、全体を特定のリビジョンに戻すための作業の仕方と、衝突が発生した場合の基本的な対処の仕方について、調べておこうと思います。
特定のリビジョンへ戻す
コマンドラインだと、"cd 対象ディレクトリー" で対象ディレクトリーへ移動してから、次のようなコマンドで、全体を特定のリビジョンへ元に戻す処理を行うことができるようでした。
cd 対象ディレクトリー
svn update --revision 22
上記の例では、対象ディレクトリー内のリビジョンを 22 に戻すという意味合いです。
ただ、これを実行してみたところ、Xcode のプロジェクトファイルで変更の衝突(コンフリクト)が検出されてしまいました。
具体的には、"プロジェクト名.xcodeproj/USERNAME.perspectivev3" と "プロジェクト名.xcodeproj/USERNAME.pbxuser" の 2 つのファイルです。
理由としては、おそらく Xcode が都度これらのプロジェクトファイルを更新するために、リポジトリ内の編集前と編集後との状態の他に、ローカル側のファイルも別の内容を持つため、リビジョン適用時にそれらが衝突し、元のリビジョンから作成された新しいリビジョンへのパッチが当てられなくなるのではないかと思います。
衝突が検出された際の基本操作
"svn update" コマンドで衝突が検出されると、その衝突についてどういった対応を取るかを選択する画面が現れます。
ちなみに、svn update で衝突が発生したときに採れる選択肢としては、次のものがあるようでした。
e | edit: change merged file in an editor |
---|---|
df | diff-full: show all changes made to merged file |
r | resolved: accept merged version of file |
dc | display-conflict: show all conflicts (ignoring merged version) |
mc | mine-conflict: accept my version for all conflicts (same) |
tc | theirs-conflict: accept their version for all conflicts (same) |
mf | mine-full: accept my version of entire file (even non-conflicts) |
tf | theirs-full: accept their version of entire file (same) |
p | postpone: make the conflict to be resolved later |
l | launch: launch external tool to resolve conflict |
これらのメニューから、衝突内容を "dc" や "df" で確認したり、"e" で手動で編集したり、"r" でマージされたファイルを採用したり、自動的に衝突を回避したり ("mc", "tc", "mf", "tf") することができるようになっていました。
また "p" を選択して、後で手動で操作することも可能なようです。
これらの選択肢の動作については、実際に試してみた結果を、以下のどこかで触れてみたいと思います。
衝突への対応を後回し (postpone) した場合
ここで、衝突の処理をとりあえず後回し ("p" = postpone) にしてみたところ、プロジェクトを開こうとすると "Xcode ユーザファイルを解析できないため開くことができません。" というエラーが表示され、プロジェクトが開けなくなってしまいました。
これはどうやら、Xcode のプロジェクトファイルが、衝突の対応を保留した際に、衝突についての情報がマージされて記録されてしまったために、本来のプロジェクトファイルの書式とは違うファイルが出来上がってしまったためだと思います。
そこで再度 "svn update 対象ディレクトリ" を実行して最新状態に戻そうとしたのですが、そうすると "Summary of conflicts: Skipped paths: 2" と表示されてしまい、元に戻してくれませんでした。
このとき、どのファイルがスキップされているかについては "Skipped 'ファイル名'" という形で画面に表示されていました。
衝突が発生してしまった場合にその手続きを保留すると、どうやらその情報が作業フォルダー内に格納されていて、何らかの方法で衝突を回避するまでは、それらのファイルにリポジトリの情報を反映させることができなくなってしまうようです。
衝突が検出されると、"当該ファイル名.mine" として元のファイルの内容が、また、更新前と更新後のそれぞれのリビジョンファイルが "当該ファイル名.r旧リビジョン番号" と "filename.r新リビジョン番号" として、それぞれが作業フォルダー内に保存されます。
また、本来のファイルには、衝突の処理の際に "dc" で確認できる、衝突情報を含んだマージされた内容が保存されてます。
これらのファイルの内容を頼りに何らかの方法で衝突を回避するまでは、リポジトリの内容を適用できないように、Subversion が制御しているようでした。
衝突を回避する
衝突が発生した場合に "p" でひとまず処理を延期したら、そのファイルについての対応は手作業で行う必要があります。
その方法としては、衝突が検出された際に作成された "当該ファイル.mine"(ローカルコピー)や "当該ファイル.rリビジョン番号"(該当リビジョンの内容)ファイルや、"該当ファイル" 名そのものに保存されたマージ情報を手掛かりに、適切な内容のファイルを "該当ファイル" として用意する必要があるようです。
ローカルでの編集内容を破棄し、リビジョンの内容に置き換える場合
ローカルの編集内容を破棄してリビジョンの内容に置き換えたい場合には、"svn" コマンドを使用して、対応することができるようでした。
"svn" コマンドを使用して対応する場合、リポジトリに格納されている内容に置き換えるには、次のようにします。
svn revert 当該ファイル名
これで、ローカルに加えていた修正内容が破棄されて、リポジトリ内の内容に置き換わってくれるようでした。
もし、どのリビジョンに置き換わっているかが不安な場合は、続けて次のコマンドを実行することで、もう一度、目的のリビジョンを取得してみても良いかもしれません。
svn update --revision 22 当該ファイル名
コマンドを使用して衝突の対応を行う
手作業での編集や上書きだけでなく、次のコマンドを実行することで、衝突した内容を調整することもできるようになっていました。
svn resolve --accept=COMMAND 当該ファイル名
上記の "COMMAND" のところには、次のようなものを指定することができるようです。
具体的な情報が得られなかったので、実際の動作を試行錯誤で試しながらやってみると、どうやら次のような用途で、それぞれを使い分ける感じになるようでした。
working | ローカルコピーを採用します。ただし postpone を選択して手作業で対応している場合には、ローカルコピーにはマージされた内容が記録されているため、それが採用されるようでした。 |
---|---|
mine-conflict | 衝突した部分については、ローカルコピーの内容を採用するようです。 |
theirs-conflict | 衝突した部分については、リビジョンの内容を採用するようです。 |
mine-full | ローカルコピーの内容を採用するようです。リビジョンの内容はすべて無視されるようでした。 |
theirs-full | リビジョンの内容を採用するようです。ローカルコピーの内容はすべて無視されるようでした。 |
"svn update" コマンドを実行して衝突が検出された場合にも、これらの選択肢から選ぶようになっているようなので、これらのどれかで対応できる状況ならば、"p" で対応を先送りすることなく、衝突を解消することができそうです。
例えば今回のように、以前のバージョンに戻したい場合には "tf" (theirs-full) を指定することで、リビジョンの内容に強制的に戻すこともできるものと思います。
ただし、あくまでも試行錯誤で試してみた感じでの動作なので、実際に行う際には注意して実施してみてください。