自定义 MSBuild 任务以撤销 TFS 中的签出二进制文件
使用 TFS API 为构建过程撤销签出。
引言
本文提供了一种使 Team System 构建过程能够撤销 TFS 中已检出文件的方法。它要求构建服务帐户具有“解锁其他用户更改”的权限。如果您不同意这一点,请忽略本文。
背景
我们有一个非常大的代码库,其中包含许多共享的二进制程序集,并且我们喜欢持续集成。此代码库中有许多解决方案依赖于按特定顺序构建的共享二进制文件。此外,当运行生成这些共享二进制文件的解决方案时,构建过程需要将这些二进制文件检入 TFS,以便后续构建可以使用最新的二进制文件。这使我们的夜间构建能够拥有所有最新最好的二进制文件,并生成一个可靠的构建,供我们的 QA 团队开始他们的一天。当然,这很简单,除非有人检出一个或多个这些二进制文件,并且构建过程因此无法检入这些文件。这意味着系统的构建不会包含最新的更改!因此,需要通过构建服务帐户来撤销另一个用户的检出,这就产生了自定义的 MSBuild 任务。
本文将重点介绍撤销检出。我将在另一篇文章中介绍从 TFS 检出、复制构建输出以及检入新文件的过程。
使用代码
创建自定义任务相对简单,只需创建一个 C# 类并添加一些引用
同样,添加适当的 using 语句
using System;
using System.Collections;
using System.Collections.Generic;
using Microsoft.Build.Utilities;
using Microsoft.Build.Framework;
using Microsoft.TeamFoundation.Client;
using Microsoft.TeamFoundation.VersionControl.Client;
using System.IO;
自定义任务的目标是从 MSBuild 扩展 Task
对象。因此,将覆盖 Execute
方法。
namespace Epsilon.BuildTask
{
/// <summary>
/// UndoCheckoutTask - a task for undoing binary files that have not been checked in.
/// </summary>
public class UndoCheckoutTask : Task
{
public override bool Execute()
{
try
{
}
catch (Exception ex)
{
Log.LogError("Exception caught: " + ex.Message.ToString() + ex.StackTrace.ToString());
return false;
}
}
}
}
将从调用的 MSBuild .proj 文件中传入的参数之一是二进制文件要检入的服务器路径位置(例如,$/Project/Folder/MyBinaries)。我们已经预先确定此路径中的二进制文件存在挂起的更改集,因此撤销检出是安全的。我们还需要提供 TFS 服务器的 URI。这也会从调用的项目中传入。
/// <summary>
/// Input parameter - the server path of the binaries being checked.
/// </summary>
private string _binaryPath;
public string binaryPath
{
set { _binaryPath = value; }
}
/// <summary>
/// Input parameter - The TFS URI
/// </summary>
///
private string _collectionURI;
public string collectionURI
{
set { _collectionURI = value; }
}
/// <summary>
/// return the CheckinID to the caller
/// </summary>
如果需要,我们可以从该过程返回一个检入 ID。请注意 [Output]
修饰。
/// <summary>
/// return the CheckinID to the caller
/// </summary>
private int _checkinID;
[Output]
public int CheckinID
{
get { return _checkinID; }
}
现在进入正题。这里的想法是找到我们传入的服务器路径的任何挂起集,然后迭代这些挂起集的挂起更改。是的,可能存在多个挂起集,因为可能不止一个用户检出了二进制文件。找到挂起更改后,可以为我们从二进制文件路径派生的 itemSpecs
调用 workspace.Undo
。这是好东西
try
{
_checkinID = 0;
// Set up the scenario - get the server info, get the collection, connect to version control.
Uri serverUri = new Uri(_collectionURI);
TfsTeamProjectCollection collection = new TfsTeamProjectCollection(serverUri);
VersionControlServer versionControl = (VersionControlServer)collection.GetService(typeof(VersionControlServer));
// Construct the ItemSpec of the folder we want to check - passed in as _binaryPath.
// RecursionType.Full is REQUIRED for this to work!
ItemSpec[] itemSpecs = new ItemSpec[1];
itemSpecs[0] = new ItemSpec(@_binaryPath, RecursionType.Full);
// Get the list of Pending SETS for the ItemSpec - just the folder we're interested in...
// Workspacename and username arguments are set null to return all.
PendingSet[] pendingSet = versionControl.QueryPendingSets(itemSpecs, null, null, includeDownloadInfo: true);
Log.LogMessage("Number of pending sets: " + pendingSet.Length.ToString());
// Iterate through the pendingSets (only one)
foreach (PendingSet ps in pendingSet)
{
// Output to the log - the pending set's information
Log.LogMessage("====PS====>" + ps.Computer + "\t" + ps.Name +
"\t" + ps.OwnerName + "\t" + ps.PendingChanges + "\t" + ps.Type);
// More output to the log: The actual pending changes that will be checked in.
PendingChange[] pendingChanges = ps.PendingChanges;
foreach (PendingChange pc in pendingChanges)
{
Log.LogMessage("====PC====>" + pc.ChangeTypeName + "\t" +
pc.FileName + "\t" + pc.ServerItem + "\t" + pc.Version);
}
// Use the Pending Set info to query the workspaces desired.
Workspace[] wsList = versionControl.QueryWorkspaces(ps.Name, ps.OwnerName, null);
if (wsList.Length != 0)
{
Workspace ws = wsList[0];
// Undo the pending changes!
_checkinID = ws.Undo(itemSpecs);
Log.LogMessage("Check-in ID = " + _checkinID.ToString());
}
}
return true;
}
catch (Exception ex)
{
Log.LogError("Exception caught: " + ex.Message.ToString() + ex.StackTrace.ToString());
return false;
}
}
这一切中非常重要的一部分是确保正确指定了 ws.Undo(itemSpecs)
。这就是将撤销检入过程限制为指定的二进制文件路径的原因。
您可以选择在所有这些操作中执行有用的操作,例如发送电子邮件消息或将某些信息返回到调用的项目文件。天空才是极限。
如何使用该任务
为了从您的 MSBuild .proj 文件中使用此任务,您需要使此项目的 .dll 可供构建服务器使用。我们已经使用了 MSBuild 扩展,因此我们只是将 .dll 放在与 MSBuild 项目相同的目录中。这是 UndoCheckOut.proj 文件
<?xml version="1.0" encoding="utf-8"?>
<Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003" ToolsVersion="4.0" DefaultTargets="UndoCheckOut">
<UsingTask AssemblyFile="$(ExtensionTasksPath)Epsilon.BuildTask.dll" TaskName="Epsilon.BuildTask.UndoCheckoutTask"/>
<PropertyGroup>
<TPath>$(MSBuildExtensionsPath)\ExtensionPack\4.0\MSBuild.ExtensionPack.tasks</TPath>
<TPath Condition="Exists('$(MSBuildExtensionsPath)\ExtensionPack\4.0\
MSBuild.ExtensionPack.tasks')">$(MSBuildExtensionsPath)\
ExtensionPack\4.0\MSBuild.ExtensionPack.tasks</TPath>
</PropertyGroup>
<Import Project="$(MSBuildExtensionsPath)\MSBuildCommunityTasks\MSBuild.Community.Tasks.Targets"/>
<Import Project="$(MSBuildExtensionsPath)\Microsoft\VisualStudio\TeamBuild\Microsoft.TeamFoundation.Build.targets" />
<Import Project="$(TPath)"/>
<Target Name="UndoCheckOut">
<!-- Call a custom task to perform the checkin undo -->
<Epsilon.BuildTask.UndoCheckoutTask collectionURI="$(MyURI)" binaryPath="$(MyBinaryPath)">
<Output TaskParameter="CheckinID" PropertyName="ID"/>
</Epsilon.BuildTask.UndoCheckoutTask>
<Message Text="CheckinID = $(ID)" />
</Target>
</Project>
<UsingTask
指令提供了 .dll 的路径,并且可以在 <Target>
中找到任务的调用。请注意如何使用和使用输出参数。
兴趣点
正如本文开头提到的,构建服务帐户需要能够撤消其他帐户的更改。由于这些二进制文件都位于各自的文件夹中,因此授予构建服务帐户该权限没有风险。如果您不授予该权限,它仍然会运行,但显然不会撤消检出。
还要查看上面代码中的内联注释以获取一些小提示。其中有一些重要的信息。
历史
- 原文:2013 年 2 月 4 日。