CmdRunner:Plastic SCM API
概要
Plastic SCM をビルドする際にはユーザー、お客様、社内のタイムラインから要望のあった機能を追加するので、追加のための時間やリソースを割けない機能もあります。幸いなことに、コミュニティのおかげでより多くの Plastic SCM 向けプラグインと機能の開発が可能になっています。
Plastic SCM を拡張する方法の 1 つとして、CmdRunner の使用があります。これはコマンドラインをベースとした自動化レイヤーです。C# を使用して .NET 上に構築されており、Github リポジトリ上で公開されるので、フォークや共同作成を行うことができます。
Plastic SCM のコマンドラインには --machinereadable や --format などのパラメーターがあり、これを使用してツールの出力をカスタマイズして、外部ツールで解析しやすくすることができます。Eclipse、IntelliJ、TeamCity、Hudson / Jenkins、Crucible、Bamboo、その他のプラグインはすべてコマンドラインクライアントをベースとしているため、さまざまな拡張の可能性があります。
始める前に:これらのサンプルを使用するには、正常に動作する Plastic SCM クライアントがマシンにインストールされている必要があります。無料ライセンス(最大 5 人の開発者に対して有効)は
こちらで入手できます。
Hello world
Code: Samples/HelloWorld.cs
Plastic のラッパーレイヤーが何であるかを示す最もよい方法は、実際に何ができるかを示すことです。わかりやすい例がこちらです。
string cmVersion = CmdRunner.ExecuteCommandWithStringResult("cm version", Environment.CurrentDirectory);
Console.WriteLine(string.Format("The cm version is: {0}", cmVersion));
.NET 方式での起動プロセスに慣れている方であれば、そのすべてのロジックをシンプルな静的メソッドに抽出し、通常であれば標準出力から取得するであろう出力を復元していることを理解できると思います。ある特定のサーバーにあるすべてのリポジトリの一覧を表示するなど、もう少し本格的なことをやってみましょう。
string server = "remoteserver:8087";
string repoList = CmdRunner.ExecuteCommandWithStringResult(string.Format("cm repository list {0}", server), Environment.CurrentDirectory);
Console.WriteLine(string.Format("{0}", repoList));
この簡単なコードの出力は、次のようになります。
The cm version is: 5.4.16.633
1 default localhost:8084
4 cmdSamplesToyRepo localhost:8084
また、さまざまな引数をどのように扱っているか見たことはありますか?このセクションを締めくくる前のちょっとしたヒントとして、CmdRunner を使用して、difftool や semanticmerge などの他のプログラムを起動できます。PATH にある場合は、直接(エクスプローラーから)呼び出すことができます。ない場合は、実行ファイルのフルパスを使用して呼び出すことができます。
作業の開始
GitHub からリポジトリをダウンロードすることで開始できます。コードを取得する方法は 2 つあります。
- zip ファイルの最後の変更をダウンロードする。
- git-sync(詳細はこちら)を使用する。ローカルリポジトリをこちらと同期します。
$ cm repository create plastic-cmdrunner
$ cm sync plastic-cmdrunner@localhost:8087 git https://github.com/PlasticSCM/plastic-cmdrunner.git
独自のコマンドで拡張する必要がある場合は、リポジトリをフォークできます。これにより、同期元となる独自のリポジトリが作成されます。後で貢献を反映する必要がある場合には、プルリクエストを発行してください。これにより、自分が加えた変更がレビューされ、親リポジトリにマージバックされます。
重要:git-sync を行うには Plastic SCM 4.2 以上である必要があります。
パッケージをダウンロードすると、次の 2 つのプロジェクトがある Visual Studio 2010 ソリューションが見つかります。
- CmdRunner:ライブラリが含まれます。
- Samples:このドキュメントで説明しているすべてのサンプルが含まれます。
すべてのサンプルには独自の main メソッドがあるため、Visual Studio のプロジェクトのプロパティに移動して、実行するサンプルを選択するだけで済みます。
最初のアプローチ:ブランチのリスト表示
Code: Samples/ListBranches.cs
このサンプルでは、リポジトリで使用可能なすべてのブランチをリスト表示します。cm find の詳細については、こちらのガイドを確認してください。
string cmdResult = CmdRunner.ExecuteCommandWithStringResult(
string.Format("cm find branch on repositories '{0}' --format={{id}}#{{name}} --nototal", repository),
Environment.CurrentDirectory);
ArrayList results = SampleHelper.GetListResults(cmdResult, true);
List<Branch> branches = GetBranchListFromCmdResult(results);
foreach (Branch branch in branches)
Console.WriteLine(branch);
このコードは、リポジトリに対して基本的なクエリを実行します。このコマンドの raw の出力は次のようになります。
3#/main
12#/main/task0
13#/main/task1
14#/main/task2
15#/main/task3
16#/main/task4
17#/main/task5
18#/main/task6
19#/main/task7
20#/main/task8
21#/main/task9
その後、サンプルヘルパーを使用して、メソッド GetListResults
でその出力を解析し、文字列のリストに変換します。最後に、各要素に対して新しい Branch オブジェクトを生成します。
public class Branch
{
public string Id { get; set; }
public string Name { get; set; }
public Branch(string output)
{
string[] parsed = output.Split('#');
Id = parsed[0];
Name = parsed[1];
}
public override string ToString()
{
return Id + " - " + Name;
}
public override bool Equals(object obj)
{
if (!(obj is Branch))
return base.Equals(obj);
return ((Branch)obj).Name.Equals(Name);
}
}
このオブジェクトには、名前とサーバー、および、以降のサンプルでブランチを比較するのに役立つ equal のオーバーライドの情報が含まれています。この情報を、cm find から取得できるすべてのパラメーターで拡張できます。
複数のサーバーの処理:replicator
Code: Samples/Replicator.cs
前のサンプルを基に、Branch オブジェクトを使用して、1 つのリポジトリで作成された変更を別のリポジトリにレプリケートしていきましょう。ブランチをレプリケートするには、そのブランチの完全な指定と、同期先リポジトリが必要です。これは、次のコードを使用して実行できます。
private static ReplicationResult Replicate(Branch branch, string repository, string secondRepository)
{
Console.WriteLine("Replicating branch {0}", branch.Name);
string cmdResult = CmdRunner.ExecuteCommandWithStringResult(
string.Format("cm replicate \"br:{0}@{1}\" \"rep:{2}\"",
branch.Name, repository, secondRepository),
Environment.CurrentDirectory);
return new ReplicationResult(SampleHelper.GetListResults(cmdResult, true), branch);
}
レプリケートコマンドが呼び出され、結果が解析されて、Replication Result
というシンプルなクラスに変換されます。Replicate
コマンドの生の出力は次のようになります。
DataWritten
Items 1
Revs 0
ACLs 0
Changesets 0
Labels 0
Applied labels 0
Links 0
Applied links 0
Attributes 0
Applied attributes 0
Reviews 0
Review comments 0
branch /main/task001
ターゲットリポジトリで新規ブランチが作成されると、最後に名前が表示されます。コマンドが完了すると、前のサンプルのように結果が解析され、各操作の結果を含んだ新しい ReplicationResult
オブジェクトが作成されます。
class ReplicationResult
{
public long Items { get; set; }
public Branch Branch { get; set; }
public ReplicationResult(ArrayList cmdResult, Branch branch)
{
Branch = branch;
string buffer = (string)cmdResult[1];
Items = long.Parse(buffer.Substring(buffer.LastIndexOf(' ')));
}
}
レプリケーション結果には、レプリケートされた項目の数と、関連するブランチが含まれます。これは、前に表示された項目を使って拡張することもできます。
最後に、レプリケーション全体の簡単なレポートが生成されます。
private static void PrintReplicationResult(List<ReplicationResult> resultList)
{
Console.WriteLine("Branches replicated: {0}" , resultList.Count);
foreach (ReplicationResult item in resultList)
Console.WriteLine("- {0} ({1} item{2})",
item.Branch.Name, item.Items, item.Items == 1 ? "" : "s");
}
レプリケーションの出力は次のようになります。
Replicating branch /main
Replicating branch /main/task0
Replicating branch /main/task1
Replicating branch /main/task2
Replicating branch /main/task3
Replicating branch /main/task4
Replication complete
Branches replicated: 6
- /main (0 items)
- /main/task0 (3 items)
- /main/task1 (4 items)
- /main/task2 (2 items)
- /main/task3 (3 items)
- /main/task4 (2 items)
通知の追加:notifier
Code: Samples/Notifier.cs
前の例を基に、今度はブランチへの変更を追跡して、変更を 1 つのサーバーから別のサーバーへとレプリケートできるようにしましょう。このシナリオでは、コードで一方のリポジトリの変更をシミュレートすることにより、もう一方で通知を受け取れるようにします。
まず把握する必要があるのは、新しいブランチがあるかどうかということなので、両方のサーバーでブランチを検索し、ソースにしかないブランチを追加します。
List<Branch> GetBranchesToSync()
{
List<Branch> srcBranches = GetBranchesFromRepo(mSampleRep);
List<Branch> dstBranches = GetBranchesFromRepo(mSecondRep);
List<Branch> newBranches = new List<Branch>();
foreach (Branch item in srcBranches)
{
if (!dstBranches.Contains(item))
{
newBranches.Add(item);
continue;
}
if (HasChangesInBranch(item))
newBranches.Add(item);
}
return newBranches;
}
次のステップは、共通のブランチに変更があるかどうかを確認することです。このプロセスのための最も簡単な方法は、cm find をもう一度使用することです。
private bool HasChangesInBranch(Branch branch)
{
string srcResult = CmdRunner.ExecuteCommandWithStringResult(
string.Format("cm find changeset where branch = 'br:{0}' on repositories '{1}' --format={{id}} --nototal",
branch.Name, mSampleRep),
Environment.CurrentDirectory);
ArrayList srcResults = SampleHelper.GetListResults(srcResult, true);
string dstResult = CmdRunner.ExecuteCommandWithStringResult(
string.Format("cm find changeset where branch = 'br:{0}' on repositories '{1}' --format={{id}} --nototal",
branch.Name, mSecondRep),
Environment.CurrentDirectory);
ArrayList dstResults = SampleHelper.GetListResults(dstResult, true);
return srcResults.Count != dstResults.Count;
}
すべての変更セットが取得できたら、それらを比較し、一致しない場合はブランチをレプリケートする必要があります。このプロセスはすべて、バルーン内に表示されます。
バルーンをクリックすると、レプリカプロセスが起動します。もう一方のリポジトリでの作業をシミュレートするために、項目を継続的に追加してチェックインするバックグラウンドスレッドが存在するので、ユーザーは変更が表示されるのを待つだけです。このサンプルでは、スレッド番号が追加されています。画像のスレッド 9 がメインスレッドで、スレッド 10 がバックグラウンドスレッドです。
ローカルの変更の追跡:CMbox
Code: Samples/CMbox/CMbox.cs
このサンプルでは、ローカルファイルの変更を追跡し、それらを自動的にコミットすることで、Plastic SCM を Dropbox のようなトレイアプリに変換する方法について説明します。Dropbox が成功した理由の 1 つは、シンプルさとユーザーの対応の少なさです。つまり、多くの操作がバックグラウンドで自動的に実行されるという点です。
最初に行わなければならないことは、ポップアウトウィンドウを設定することと、設定変数用のサーバー情報を取得することです。設定コード(IsSimulation
)を変更しなかった場合、変数は true に設定され、ファイルが一時ワークスペースに自動的に追加されて変更されるので、ユーザーは操作が実行されるのを座って待つだけです。
public CMbox()
{
ConfigureMenu();
string server = Configuration.ServerName;
mSampleRep = SampleHelper.GenerateEmptyRepository(server);
if (Configuration.IsSimulation)
CMboxHelper.StartSimulation();
}
設定が完了したら、CommitUpdates
という関数を 10 秒ごとに呼び出します。この関数が最初に行うことは、コマンド cm status を使用して、ワークスペースの既存の変更を確認することです。
private List<Change> GetChanges()
{
List<Change> changes = new List<Change>();
string cmdResult = CmdRunner.ExecuteCommandWithStringResult(
string.Format("cm status --all --machinereadable"),
SampleHelper.GetWorkspace());
ArrayList results = SampleHelper.GetListResults(cmdResult, true);
for (int i = 1; i < results.Count; i++)
changes.Add(new Change((string)results[i]));
return changes;
}
cm status を machinereadable 修飾子とともに使用した場合、出力は次のようになります。
STATUS 4 cmdSample5d4ce30 localhost:8084
PR c:\Users\rbisbe\AppData\Local\Temp\c10d4d2e-1e14-43e4-af1d-a24df76176d5\sampleb165621 False NO_MERGES
PR c:\Users\rbisbe\AppData\Local\Temp\c10d4d2e-1e14-43e4-af1d-a24df76176d5\samplef3390c5 False NO_MERGES
生成された出力は、先にも示したように配列リストに変換され、各行は解析されて単一の Change
要素になります。
Code: /Samples/CMbox/Change.cs
class Change
{
public string Name { get; set; }
public string FullPath { get; set; }
public ChangeType ChangeType { get; set; }
public Change(string resultRow)
{
string[] separated = resultRow.Split(' ');
ChangeType = GetFromString(separated[0]);
FullPath = separated[1];
Name = Path.GetFileName(separated[1]);
}
private ChangeType GetFromString(string source)
{
switch (source.ToUpperInvariant())
{
case "AD+LD":
case "LD+CO":
return CmdRunnerExamples.ChangeType.Deleted;
case "PR":
case "AD":
return CmdRunnerExamples.ChangeType.Added;
case "CO":
return CmdRunnerExamples.ChangeType.Changed;
default:
return CmdRunnerExamples.ChangeType.None;
}
}
}
返される変更には、項目の名前(バルーンでの表示用)、項目のフルパス、および変更の種類が含まれます(結果の解析から復元されます)。
変更が復元された後、チェックインされていない変更がある場合はチェックイン操作が実行され、終了時にユーザーへの通知が表示されます。
private void CheckinUpdates(object sender, EventArgs e)
{
List<Change> mChanges = GetChanges();
if (mChanges.Count == 0)
return;
StringBuilder builder = new StringBuilder();
foreach (var item in mChanges)
builder.Append(string.Format("{0} {1}\n", item.ChangeType, item.Name));
foreach (var item in mChanges)
{
if (item.ChangeType == ChangeType.Added)
CmdRunner.ExecuteCommandWithStringResult(
string.Format("cm add {0}", item.Name),
SampleHelper.GetWorkspace());
if (item.ChangeType == ChangeType.Deleted)
CmdRunner.ExecuteCommandWithStringResult(
string.Format("cm rm {0}", item.Name),
SampleHelper.GetWorkspace());
}
CmdRunner.ExecuteCommandWithStringResult("cm ci ",
SampleHelper.GetWorkspace());
mTrayIcon.ShowBalloonTip(3, string.Format("{0} new changes saved", mChanges.Count),
string.Format("The following changes have been checked in.\n{0}",
builder.ToString()),
ToolTipIcon.Info);
}
このサンプルでは、cm add と cm rm を使用して、追加済みおよび削除済みとしてマークされた項目の追加と削除も行っています。
通知には、チェックインされた変更の数と、各変更の簡単な説明が含まれます。
ゲーム用のソース管理
Code: Samples/Game.cs
このサンプルでは、ソース管理をゲームセーバーとして使用し、レベルを変更するたびに、そのレベル情報を使用して新しい変更セットが作成されるようにします。これにより、以前のゲームステータスを常に復元できるようになります。
このゲームでは、ランダムファイルの新規変更セットを作成することで、ステータスが保存されます。変更セットのコメントには、ゲームのステータスが含まれます。
private void SaveGame()
{
string item = SampleHelper.AddRandomItem();
SampleHelper. CheckinItem(item, string.Format("{0}#{1}",
mColors, mScore));
}
これにより、ゲームのロード時には、すべての変更セットを検索し、コメントでフィルタリングして、そのコメントを解析するだけで済むようになります。各コメントは、SaveGame
という新しいクラス内で解析されます。これにより、スコアと色番号が格納されます。
private void ReloadSavedGames()
{
string cmdResult = CmdRunner.ExecuteCommandWithStringResult(
string.Format("cm find changeset on repositories '{0}' --format={{comment}} --nototal",
textBox1.Text),
Environment.CurrentDirectory);
ArrayList results = SampleHelper.GetListResults(cmdResult, true);
listBox1.Items.Clear();
results.RemoveAt(0);
foreach (string item in results)
listBox1.Items.Add(new SavedGame(item));
}
このコンテンツは後でゲームにロードされます、これにより、前回のプレイ終了時と同じステータスでプレイを続行できるようになります。
「ロード」ボタンと「新規作成」ボタンを使用すると、このゲーム用の新しい Plastic SCM リポジトリを作成したり、既存のリポジトリに接続して保存済みのゲームを使用することができます。
独自の PlasticSCM GUI の作成
Code: Samples/MiniGUI/MiniGUI.cs
Plastic SCM の主な機能はすべて、コマンドラインで実行できます。これにより独自のプラグインを作成できるようになっただけでなく、全カスタマイズ化したユーザーインターフェースを作成するために活用もできるようになったのではないでしょうか。このサンプルでは、保留中の変更ビューと変更セットの一覧がある小さなツールを作成しています。ツールではチェックイン操作(コメントを含む)を実行できるほか、各コミットで変更されたファイルを確認できます。このような見た目になります。
このタブ項目では、ファイルをチェックインしてコメントを追加できます。
このタブ項目には、変更セットと、各変更セットで加えられたさまざまな変更が表示されます。
ここでは通常の使用における動作についてシミュレーションを行っているため、コンソールには自動的に行われる変更が出力されるようになっています。
GUI 関連の側面には注視せず、3 つの具体的なボタンの動作、チェックイン、更新、変更セットビューからの更新にのみ注目していきます。
変更セットリストの「最新情報に更新」ボタン
このボタンを押すと、変更セットのリストが再ロードされ、その変更セットの内容のリストも作成されます。この機能では、cm からの次のコマンドが使用されています。
- find changeset(変更セットを特定の形式で取得するため)。
- log(指定された変更セットについて、追加、移動、変更、または削除された項目を取得できます)。
private void RefreshChangesetList(object sender, EventArgs e)
{
string cmdResult = CmdRunner.ExecuteCommandWithStringResult(
"cm find changeset --nototal --format=\"{changesetid}#{date}#{comment}\"",
SampleHelper.GetWorkspace());
ArrayList results = SampleHelper.GetListResults(cmdResult, true);
changesetList.Items.Clear();
foreach (string item in results)
{
Changeset cset = new Changeset(item);
cmdResult = CmdRunner.ExecuteCommandWithStringResult(
string.Format("cm log {0} --csFormat=\"{{items}}\" --itemFormat=\"{{path}}#{{fullstatus}}#{{newline}}\"",cset.Id),
SampleHelper.GetWorkspace());
results = SampleHelper.GetListResults(cmdResult, true);
foreach (string changedItem in results)
cset.Changes.Add(new Item(changedItem));
changesetList.Items.Add(cset);
}
}
このプロセスは、他のプロセスと非常によく似ています。コマンドを呼び出し、結果を解析して新しいオブジェクトを生成することで、GUI にデータを表示できるようになります。
Changeset
クラスは、ID、日付、コメント、および変更のリストのみを含んだ、シンプルなクラスです。このクラスには、単一の cm find 行の出力を解析するのに必要なコードも含まれています。
public class Changeset
{
public string Date { get; set; }
public string Id { get; set; }
public string Comment { get; set; }
public List<Item> Changes { get; set; }
public Changeset(string output)
{
string[] parsed = output.Split('#');
Id = parsed[0];
Date = parsed[1];
Comment = parsed[2];
Changes = new List<Item>();
}
public override string ToString()
{
return string.Format("{0}: cs:{1}", Date, Id);
}
}
Item
クラスには、ステータスと、ワークスペースへの相対パスが含まれます。
public class Item
{
public string Path { get; set; }
public string Status { get; set; }
public Item(string output)
{
if (string.IsNullOrEmpty(output))
{
Path = string.Empty;
Status = string.Empty;
return;
}
string[] parsed = output.Split('#');
Path = parsed[0].Replace(SampleHelper.GetWorkspace().ToLowerInvariant(),
"");
Status = parsed[1];
}
public override string ToString()
{
if (string.IsNullOrEmpty(Status))
return string.Empty;
return string.Format("{0} ({1})", Path, Status);
}
}
保留中の変更ビューの「最新情報に更新」ボタン
「最新情報に更新」ボタンを押すと、両方のリストがクリアされ、変更のリストが取得されて、変更済みのものから追加済みと削除済みがフィルタリングされます。この機能では、cm からの単一のコマンドが使用されています。cm status です。実行される操作は、CMBox サンプルで実行したのと同じ GetChanges
です。
private void Update(object sender, EventArgs e)
{
itemsToCommit.Items.Clear();
itemsNotAdded.Items.Clear();
List<Change> mChanges = GetChanges();
if (mChanges.Count == 0)
return;
foreach (var item in mChanges)
{
if ((item.ChangeType == ChangeType.Added)
|| (item.ChangeType == ChangeType.Deleted))
{
itemsNotAdded.Items.Add(item);
continue;
}
itemsToCommit.Items.Add(item);
}
}
追加済み項目と削除済み項目をリストに追加するには、このボタンをダブルクリックします。
保留中の変更ビューの「チェックイン」ボタン
「チェックイン」ボタンを使用すると、リストでは既に追加されている項目(追加済み項目と削除済み項目)が加えられ、現在のワークスペースステータスのチェックイン操作が実行されます。さらに、コメントテキストボックスがクリアされ、最後に行われた変更の形でリストが更新されます。
private void Checkin(object sender, EventArgs e)
{
foreach (Change item in itemsToCommit.Items)
{
if (item.ChangeType == ChangeType.Added)
CmdRunner.ExecuteCommandWithStringResult(
string.Format("cm add {0}", item.Name),
SampleHelper.GetWorkspace());
if (item.ChangeType == ChangeType.Deleted)
CmdRunner.ExecuteCommandWithStringResult(
string.Format("cm rm {0}", item.Name),
SampleHelper.GetWorkspace());
}
CmdRunner.ExecuteCommandWithStringResult(
string.Format("cm ci -c=\"{0}\"", textBox1.Text),
SampleHelper.GetWorkspace());
textBox1.Text = string.Empty;
Update(sender, e);
}
このようないくつかのコマンドを使用することで、独自の Plastic SCM クライアントを設定できますが、エラーの取り扱いやマージ、および内部で処理することになる多くの操作については、引き続き GUI で処理する必要があります。
CM シェルの使用
ここで示したすべてのサンプルは、cm.exe プロセスへの個別呼び出しを行うものですが、複数のコマンドを実行する必要がある場合は、同じ結果をより迅速に達成できる方法があります。
string repos = CmdRunner.ExecuteCommandWithStringResult("cm repository list",
Environment.CurrentDirectory, true);
string output;
string error;
CmdRunner.ExecuteCommandWithResult(
"cm workspace delete .",
Environment.CurrentDirectory, out output, out error, true);
最後の boolean 値に注目してください。これは、シェルを使用してコマンドをロードし、単一の cm.exe だけを実行することを意味します。実行中のシェルがない場合は、新しいシェルが自動的に起動されます。これは、次のシーケンスに相当するものです。
cm shell
repository list
workspace delete .
ヘルパーと設定
すべてのコードは Configuration.cs
に Plastic SCM のローカルサーバーを指定するだけで実行する準備が整うようになっています。便宜上、すべてのサンプルはそのサンプルを操作するための新規リポジトリも作成するため、セキュリティで保護された環境でコードを試すことができます。
SampleHelper
には、次のことを行うコードが含まれます。
- 空のリポジトリを作成する。
- ブランチ付きのリポジトリを作成する。
- リポジトリ内の項目の追加とチェックイン。
- ワークスペース内の項目の追加と変更。
- 出力を解析して文字列の配列リストになるようする。
- 現在の一時ワークスペースを取得する。
拡張
このコードはオープンソースであり、MIT ライセンスの下で公開されているため、必要に合わせて入手や変更、カスタマイズを行うことができます。また、皆様からのご協力にも大変感謝しております。クローンとプルリクエストの発行により、ご助力いただくことについても、いつでも大歓迎です。
最終更新
2019 年 3 月 22 日
非推奨となったリポジトリ管理コマンド(cm mkrep など)に関する説明を、新しい代替機能の説明に置き換えました。