添加链接
link管理
链接快照平台
  • 输入网页链接,自动生成快照
  • 标签化管理网页链接
我们在开发 Plastic SCM 时,按照用户、客户和我们内部时间表的要求添加相应的功能,因此有些功能我们没有时间和资源去完成。幸运的是,感谢社区,我们已经能够开发更多插件和功能。 扩展 Plastic SCM 的方法之一是使用 CmdRunner ,这是构建在命令行之上的自动化层。它使用 C# 构建在 .NET 上,并且可在我们的 Github 存储库中公开获得,因此您可以进行分叉和参与。 Plastic SCM 的命令行具有 --machinereadable --format 等参数,可以让我们自定义工具的输出,方便外部工具进行解析。适用于 Eclipse、IntelliJ、TeamCity、Hudson/Jenkins、Crucible、Bamboo 等的插件完全基于命令行客户端,因此有很多扩展可能性。 在我们开始之前: 这些示例需要在您的机器上安装一个可运行的 Plastic SCM 客户端。您可以在 此处 获得免费许可证(最多可供 5 名开发者使用)。 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 中可用,您可以直接调用它(如资源管理器);否则,您可以使用可执行文件的完整路径调用它。 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 方法解析该输出并转换为字符串列表。最后,我们为每个元素生成一个新的 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); 此对象现在有名称和服务器的信息,以及一个相等覆盖可以帮助我们比较下一个示例中的分支。我们可以使用从 cm find 获得的所有参数来扩展此信息。 Code: Samples/Replicator.cs

根据我们前面的示例并使用我们的 Branch 对象,我们现在要将创建的更改从一个存储库复制到另一个存储库。为了复制分支,我们需要该分支的完整规格以及目标存储库。这一过程可通过以下代码完成: 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); 这段代码将调用 replicate 命令,然后解析结果并转换为一个名为 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; 一旦我们拥有所有变更集,我们便可以比较它们,如果它们不匹配,则必须复制分支。整个这一过程会以提示框的形式显示给用户: 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 添加和删除标记为已添加和已删除的项。 通知中包含已签入的更改数量以及每个更改的简短描述。 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 存储库,或连接到现有存储库以使用已保存的游戏。 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 内部完成的许多事项。 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 包含以下方面的代码:
  • 创建空的存储库。
  • 创建具有分支的存储库。
  • 添加和签入存储库内的项。
  • 添加和修改工作区内的项。
  • 解析输出,使其成为字符串数组列表。
  • 检索当前的临时工作区。
  •