CmdRunner:Plastic SCM API
簡介
當我們建置 Plastic SCM 時,我們新增使用者、客戶和內部時間軸所要求的功能,因此有一些是我們沒有時間和資源能夠處理的功能。還好有社群,我們能夠為它開發更多外掛程式和功能。
擴充 Plastic SCM 的方式之一就是使用 CmdRunner,這是在命令列上所建置的自動化層。它利用 C# 建置於 .NET 之上,而且在 Github 儲存庫上公開推出,因此您可以進行分支處理並貢獻內容。
Plastic SCM 的命令列具有 --machinereadable 和 --format 等參數,能讓我們自訂此工具的輸出,以便外部工具能輕鬆進行剖析。Eclipse、IntelliJ、TeamCity、Hudson/Jenkins、Crucible、Bamboo 等工具的外掛程式完全是以命令列用戶端為基礎,因此有許多擴充可能性。
在我們開始之前:這些範例需要在您機器上安裝有效 Plastic SCM 用戶端。您可以在
這裡取得免費授權 (最多可供 5 名開發者使用)。
Hello World
Code: Samples/HelloWorld.cs
展示包裝函式層的最佳方式是展示它的功用。以下是一個小範例:
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 下載儲存庫開始使用。有兩種方式可取得程式碼:
- 將最後的變更下載為 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 或更新版本。
下載套件後,您可以尋找具有兩個專案的 Visual Studio 2010 解決方案:
- CmdRunner,其中包含程式庫。
- 範例,其中包含本文件說明的所有範例。
所有範例都有各自的主要方法,因此您只需移至 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);
此程式碼會在儲存庫上執行基本查詢。此命令的原輸出應如下所示:
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
剖析輸出,並將該輸出轉換為字串清單。最後,針對每個元素產生新的分支物件。
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);
}
}
此物件現在有名稱和伺服器的資訊,以及可協助我們在後續範例中比較分支的相等覆寫。我們可以使用可從 cm find 取得的所有參數來擴充此資訊。
處理不同的伺服器:複寫器
Code: Samples/Replicator.cs
根據先前的範例以及分支物件,我們現在會將已建立的變更從一個儲存庫複寫至另一個儲存庫。為了複寫分支,我們需要它的完整規格和目的地儲存庫。這可透過下列程式碼進行:
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)
新增通知:通知器
Code: Samples/Notifier.cs
按照先前的範例,我們現在將追蹤分支上的變更,如此便可以將其從一個伺服器複寫至另一個伺服器。針對此案例,此程式碼將模擬一個儲存庫中的變更,如此我們就會在另一個儲存庫中收到通知。
我們首先需瞭解的是否有新的分支,然後據以尋找兩個伺服器上的分支,並新增只位於來源上的分支。
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 成功的原因之一在於簡單易用,且不太需要使用者互動,因為大多數工作都在背景自動完成。
我們需要做的第一件事是設定快顯視窗,並為設定變數取得伺服器資訊。如果我們不變更設定程式碼,IsSimulation
變數將設定為 true,並且將在暫時工作區中自動新增和變更檔案,我們只需輕鬆坐著靜觀其如何進行。
public CMbox()
{
ConfigureMenu();
string server = Configuration.ServerName;
mSampleRep = SampleHelper.GenerateEmptyRepository(server);
if (Configuration.IsSimulation)
CMboxHelper.StartSimulation();
}
有了此設定後,我們可以每隔 10 秒呼叫名為 CommitUpdates
的函數。此函數會執行的第一個動作是,使用命令 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 相關層面,只會說明三個特定按鈕的行為,亦即簽入、重新整理,以及從變更集檢視重新整理。
變更集清單的 [重新整理] 按鈕
此按鈕會重新載入變更集清單,而且也會建立該變更集的內容清單。為此,我們要從 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 Shell
所有顯示的範例都會對 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);
查看上一個布林值?這表示我們是使用 Shell 來載入命令,而且只會有一個執行中的 cm.exe。如果沒有執行中的 Shell,則會自動啟動新 Shell。這會等同於下列序列:
cm shell
repository list
workspace delete .
協助程式和設定
所有程式碼皆已準備就緒,只要在 Configuration.cs
中指定 Plastic SCM 本機伺服器即可執行。為了讓事情簡單化,所有範例也會為此作業建立新儲存庫,讓您可以在安全的環境中試用程式碼。
SampleHelper
包含下列用途的程式碼:
- 建立空白儲存庫。
- 建立具有分支的儲存庫。
- 在儲存庫內新增並簽入項目。
- 在工作區內新增並修改項目。
- 剖析輸出,使其變成字串陣列清單。
- 擷取目前的暫時工作區。
擴充
此程式碼是根據 MIT 授權所發佈的開放原始碼,因此您可以取得、修改和自訂該程式碼,以符合您的需求。我們也非常感謝您所貢獻的內容,因此隨時歡迎來複製並索取您自己貢獻的內容。
上次更新
2019 年 3 月 22 日
我們已使用新的對應項目來取代 cm mkrep 等棄用儲存庫系統管理命令的參考資料。