A common scenario when you use TFS and Team Build, is that you want to update your build number during that process. There is the AssemblyInfoTask to accomplish that, but the downside of this approach is that it will create an extra changeset on every build. When you use a braching strategy, then it makes the merge process harder because there are these polluting ***NO_CI*** changesets.
Fortunately there is the possibility to discard these changeset from the merge candidate list. The application that is shown below let you remove those changesets automatically.
using System;
using System.Diagnostics;
using System.IO;
using Microsoft.TeamFoundation.Client;
using Microsoft.TeamFoundation.VersionControl.Client;
namespace ConsoleApplication1
{
internal class Program
{
private static void Main(string[] args)
{
if (args.Length != 3)
{
Console.WriteLine(AppUsage());
return;
}
const string tf = @"c:\Program Files\Microsoft Visual Studio 9.0\Common7\IDE\tf.exe";
string tfsServer = args[0];
string sourcePath = args[1];
string targetPath = args[2];
// Get a reference to our Team Foundation Server.
var tfs = new TeamFoundationServer(tfsServer);
// Get a local path for the Tfs server. This is required by the TF command.
string localPath = GetFirstLocalFolder(tfs);
// Iterate through all merge candidates
foreach (MergeCandidate candidate in GetMergeCandidates(tfs, sourcePath, targetPath))
{
// Skip all changesets that are not commented with ***NO_CI***
if (candidate.Changeset.Comment != "***NO_CI***")
continue;
// Discard the ***NO_CI changeset from the merge candidates
Console.WriteLine(Execute(tf, localPath,
string.Format("merge /discard /recursive /version:C{0} \"{1}\" \"{2}\"",
candidate.Changeset.ChangesetId, sourcePath, targetPath)));
Console.WriteLine(Execute(tf, localPath,
string.Format(
"checkin /recursive /noprompt /comment:\"***NO_CI***\" /override:\"Discard the ***NO_CI*** changeset from the merge candidates.\" \"{2}\"",
candidate.Changeset.ChangesetId, sourcePath, targetPath)));
}
}
/// <summary>
/// Displays how the app should be used.
/// </summary>
private static string AppUsage()
{
return "Start the application with 3 arguments: " + Environment.NewLine +
@"- The url of the Tfs Server (eg http:\\MyServer:8080" + Environment.NewLine +
@"- The path of the source branch (eg $/MyTeamProject/MySourceBranch)" + Environment.NewLine +
@"- The path of the target branch (eg $/MyTeamProject/MyTargetBranch)";
}
/// <summary>
/// Returns the list of all merge candidates.
/// </summary>
private static MergeCandidate[] GetMergeCandidates(TeamFoundationServer tfs, string sourcePath,
string targetPath)
{
var versionControl = (VersionControlServer) tfs.GetService(typeof (VersionControlServer));
return versionControl.GetMergeCandidates(sourcePath, targetPath,
RecursionType.Full);
}
/// <summary>
/// Returns the local path of the first workspace.
/// </summary>
private static string GetFirstLocalFolder(TeamFoundationServer tfs)
{
var versionControl = (VersionControlServer) tfs.GetService(typeof (VersionControlServer));
Workspace[] workspaces = versionControl.QueryWorkspaces(null, null, Environment.MachineName);
return (workspaces.Length > 0 && workspaces[0].Folders.Length > 0)
? workspaces[0].Folders[0].LocalItem
: null;
}
/// <summary>
/// Runs the TF command
/// </summary>
private static string Execute(string fileName, string localPath, string arguments)
{
var pi = new ProcessStartInfo
{
FileName = fileName,
Arguments = arguments,
UseShellExecute = false,
ErrorDialog = false,
CreateNoWindow = true,
RedirectStandardOutput = true,
WorkingDirectory = localPath
};
Process p = Process.Start(pi);
// Read the redirected output.
using (StreamReader sr = p.StandardOutput)
{
return sr.ReadToEnd();
}
}
}
}