當我們建置 Plastic SCM 時,我們新增使用者、客戶和內部時間軸所要求的功能,因此有一些是我們沒有時間和資源能夠處理的功能。還好有社群,我們能夠為它開發更多外掛程式和功能。
擴充 Plastic SCM 的方式之一就是使用 CmdRunner,這是在命令列上所建置的自動化層。它利用 C# 建置於 .NET 之上,而且在 Github 儲存庫上公開推出,因此您可以進行分支處理並貢獻內容。
Plastic SCM 的命令列具有 --machinereadable 和 --format 等參數,能讓我們自訂此工具的輸出,以便外部工具能輕鬆進行剖析。Eclipse、IntelliJ、TeamCity、Hudson/Jenkins、Crucible、Bamboo 等工具的外掛程式完全是以命令列用戶端為基礎,因此有許多擴充可能性。
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 下載儲存庫開始使用。有兩種方式可取得程式碼:
若要使用您自己的命令擴充儲存庫,您可為儲存庫建立分支,這會提供您自己的儲存庫以進行同步處理,之後如要回饋,您可以執行提取要求,而您的變更將會在經過審查後合併回上層儲存庫。
下載套件後,您可以尋找具有兩個專案的 Visual Studio 2010 解決方案:
所有範例都有各自的主要方法,因此您只需移至 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
命令的原始輸出如下所示:
如果已在目標儲存庫上建立新分支,將會在最後顯示該名稱。完成命令後,會像上一個範例一樣剖析結果,建立具有每項作業結果的新 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 是背景執行緒。
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 儲存庫,或連線至現有儲存庫以使用儲存的遊戲。
Code: Samples/MiniGUI/MiniGUI.cs
Plastic SCM 的每一個主要功能,均可在命令列上使用。這能讓我們建立外掛程式,而且可用來建立完全自訂的使用者介面。在此範例中,我們建立了一個小工具,其中具有暫止的變更檢視和變更集清單。並且能讓我們執行簽入作業 (包括註解),以及瞭解哪些檔案已在各個認可上變更。此介面如下所示:
透過此索引標籤項目可簽入檔案,並新增註解。
此索引標籤項目會顯示變更集,以及已在各個變更集上所做的不同變更。
在我們模擬一般使用行為時,主控台將會輸出自動完成的變更。
我們不會著重於 GUI 相關層面,只會說明三個特定按鈕的行為,亦即簽入、重新整理,以及從變更集檢視重新整理。
所有顯示的範例都會對 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 授權所發佈的開放原始碼,因此您可以取得、修改和自訂該程式碼,以符合您的需求。我們也非常感謝您所貢獻的內容,因此隨時歡迎來複製並索取您自己貢獻的內容。