January 2013

Volume 28 Number 01

ALM Rangers - Version Control in the TFS Client Object Model

By Jeff Bramwell | January 2013

This article is a follow-up to “Using the Team Foundation Server Client Object Model,” written by members of the Visual Studio ALM Rangers in the August 2012 issue (msdn.microsoft.com/magazine/jj553516). So far, we’ve introduced the Team Foundation Server (TFS) client object model, and now I’ll introduce the version control APIs.

To recap, the ALM Rangers are a group of experts who promote collaboration among the Visual Studio product group, Microsoft Services and the Microsoft Most Valuable Professional (MVP) community by addressing missing functionality, removing adoption blockers and publishing best practices and guidance based on real-world experiences.

If someone were to ask you to explain what TFS is, chances are you’d mention version control within the first few sentences. Although version control does play an important role within TFS, you can see in Figure 1 that there’s much more to TFS. As with many features within TFS, the version control subsystem is accessible via the TFS object model. This accessibility provides you with an extensibility model that you can leverage within your own custom tools and processes.

Team Foundation Server Features
Figure 1 Team Foundation Server Features

Assemblies and Namespaces

Before you can access the functionality provided within the TFS object model, you must first understand the required assemblies and namespaces. You’ll recall the first article used the namespace Microsoft.TeamFoundation.Client. This namespace contains the classes and methods necessary for connecting to a TFS configuration server, and it’s located within the identically named assembly. This namespace is central to all TFS object model-related development.

When working with version control, we must also utilize the namespace Microsoft.TeamFoundation.VersionControl.Client. This namespace contains the classes necessary for interacting with the TFS version control system. Utilizing the APIs within this namespace allows you to access files and folders, pending changes, merges, branches, and so on. The VersionControlServer class within this namespace is the main class that provides access to the TFS version control repository.

A Simple Example to Start

The VersionControlServer class exposes many properties, methods and events for interacting with version control within TFS. I’ll start with a simple example: retrieving the latest changeset ID.

The three basic steps required to interact with most of the APIs exposed by the TFS object model are:

  1. Connect to a TFS configuration server.
  2. Obtain a reference to the TFS service you plan to utilize.
  3. Make use of the various properties, methods and events provided by the service.

Taking a slightly different approach to connecting to TFS, as opposed to the examples presented in the August article, I’m going to connect to TFS using the TeamProjectPicker class. The TeamProjectPicker class displays a standard dialog for connecting to TFS servers. This class is not only useful for full-featured applications but is also very handy for simple utilities where you might need to switch among multiple instances of TFS.

Create a new instance of TeamProjectPicker and display it using the ShowDialog method:

private TfsTeamProjectCollection _tpc;
using (var picker = new TeamProjectPicker(TeamProjectPickerMode.NoProject, false))
{
  if (picker.ShowDialog()== DialogResult.OK)
  {
    _tpc = picker.SelectedTeamProjectCollection;
  }
}

This code will display a dialog similar to that shown in Figure 2.

The TeamProjectPicker Dialog
Figure 2 The TeamProjectPicker Dialog

Clicking Connect will return an instance of TfsTeamProject­Collection representing the selected Team Project Collection (TPC). If you prefer to use a more programmatic approach (that is, no user interaction) to connect to TFS, refer back to the August article for further examples.

Once you’ve obtained a reference to a TfsTeamProjectCollection, it can be used to obtain an instance of the VersionControlServer service:

var vcs = _tpc.GetService<VersionControlServer>();

Once you have a reference to the service you can make use of the methods exposed by the service:

var latestId = vcs.GetLatestChangesetId();

This is a simple example, but it does demonstrate the basic steps for interacting with the version control system in TFS. However, few applications are this simple.

“Getting Latest”

A common scenario related to version control is obtaining the latest source code from the repository—that is, “getting latest.” While working within Visual Studio, you typically get the latest source code by right-clicking a file or folder within the Source Control Explorer (SCE) and selecting Get Latest Version. For this to work properly, you must also have a mapped workspace selected. When downloading the latest source code from the server, the selected workspace determines where it will be stored.

Follow these steps to programmatically obtain the latest source code:

  1. Connect to a TFS configuration server.
  2. Obtain a reference to the version control service.
  3. Utilize an existing workspace or create a new, temporary workspace.
  4. Map the workspace to a local folder.
  5. Download the desired files from the workspace.

Building on the previous example, add the code shown in Figure 3.

Figure 3 Get Latest from Version Control

// Create a temporary workspace
var workspace = vcs.CreateWorkspace(Guid.NewGuid().ToString(),
  _tpc.AuthorizedIdentity.UniqueName,
  "Temporary workspace for file retrieval");
// For this workspace, map a server folder to a local folder
workspace.Map("$/Demo/TFS_VC_API", @"C:\Dev\Test");
// Create an ItemSpec to determine which files and folders are retrieved
// Retrieve everything under the server folder
var fileRequest = new GetRequest(
  new ItemSpec("$/Demo/TFS_VC_API", RecursionType.Full),
  VersionSpec.Latest);
// Get latest
var results = workspace.Get(fileRequest, GetOptions.GetAll | GetOptions.Overwrite);
If a workspace already exists and you’d like to use it, replace lines 1-4 in Figure 3 with the following:
// Get a reference to an existing workspace,
// in this case, "DEMO_Workspace"
var workspace = vcs.GetWorkspace("DEMO_Workspace",
  _tpc.AuthorizedIdentity.UniqueName);

Note that if you don’t know the name of the workspace or don’t want to specify it, you can call GetWorkspace (see preceding code sample), passing in only a local path. This will return the workspace mapped to the local path.

You don’t need to map the workspace programmatically, so you can also remove lines 5 and 6.

Identifying Files and Folders for Download

As you might expect, several of the APIs provided by TFS allow you to query the version control server for specific items as well as specific versions of items. In the previous example, when I created a new instance of GetRequest, I had to provide an instance of ItemSpec. An ItemSpec, short for item specification, describes a set of files or folders. These items can exist on your local machine or in the version control server. In this specific example, I’m building an ItemSpec to return all files within the server folder “$/Demo/TFS_VC_API.”

The second parameter in the ItemSpec constructor used here specifies RecursionType, which can be None, Full or OneLevel. This value determines how many levels deep an API should consider when querying items. Specifying a RecursionType of OneLevel will query or return items from only the topmost level (relative to the ItemSpec). A value of Full will query or return items from the top-most level as well as all levels below (again, relative to the ItemSpec).

Whereas an ItemSpec determines which items to consider based on name and location when querying the version control system, a VersionSpec, short for version specification, provides the ability to limit item sets based on version. Version­Spec is an abstract class so it can’t be instantiated directly. TFS provides several implementations of Version­Spec that you can make use of when querying the version control system. Figure 4 lists the various implementations of VersionSpec provided out of the box with TFS 2012.

Figure 4 VersionSpec Types

VersionSpec Description
ChangesetVersionSpec Specifies a version based on a changeset number.
DateVersionSpec Specifies a version based on a date/time stamp.
LabelVersionSpec Specifies a version based on a label.
LatestVersionSpec Represents the latest valid version in the repository.
WorkspaceVersionSpec Specifies a version based on a workspace name/owner.

Going back to the previous example of creating a GetRequest, I specified VersionSpec.Latest as my version specification. VersionSpec.Latest is simply a reference to a singleton instance of LatestVersionSpec provided just for convenience. To retrieve code based on a specific label, for example, create an instance of LabelVersionSpec:

var fileRequest = new GetRequest(
  new ItemSpec("$/Demo/TFS_VC_API", RecursionType.Full),
  new LabelVersionSpec("MyLabel"));

Checking out Code

Now that you know how to identify and retrieve specific items from the version control server, let’s look at how you can check out source code. In TFS terms, to check out an item is to pend an edit on that item. To pend an edit on items within a particular workspace, you call the Workspace.PendEdit method. The PendEdit method has nine overloads, all of which require a path or an array of paths as well as a few other optional parameters. One of the optional parameters is RecursionType, which works exactly as previously described for ItemSpec.

For example, to check out all C# (.cs) files, make this call:

// This example assumes we have obtained a reference
// to an existing workspace by following the previous examples
var results = workspace.PendEdit("$/Demo/TFS_VC_API/*.cs", RecursionType.Full);

In this example, I’m requesting that TFS pend edits on all C# files (via the *.cs wildcard) beneath the server folder “$/Demo/TFS_VC_API.” Because I’m specifying a RecursionType of Full, I’ll check out C# files in all folders beneath the specified path. The specific method signature used in this example will also download the checked-out files to the local path as mapped by the specified workspace. You can use one of the overloaded versions of this method that accepts an argument of type PendChangesOptions and specify PendChangesOption.Silent to suppress the downloading of files when pending edits. The value returned in results contains a count of items downloaded because of the call to PendEdit.

Edits aren’t the only action that you can pend within the version control system. There are also methods for pending:

  • Adds via PendAdd
  • Branches via PendBranch
  • Deletes via PendDelete
  • Properties via PendPropertyName
  • Renames via PendRename
  • Undeletes via PendUndelete

For example, the following code pends a new branch, named Dev, from the folder Main:

// This example assumes we have obtained a reference
// to an existing workspace by following the previous examples
var results = workspace.PendBranch("$/Demo/TFS_VC_API/Main",   "$/Demo/TFS_VC_API/Dev", VersionSpec.Latest);

We’ll cover branching and merging using the APIs in more detail in a future article.

Checking in Changes

Once you’ve made changes to one or more of the checked-out files, you can check them back in via the Workspace.CheckIn method. Before you call the CheckIn method, however, you must first obtain a list of pending changes for the workspace by calling Workspace.GetPendingChanges. If you don’t specify any parameters for the GetPendingChanges method, you’ll get back all pending changes for the workspace. Otherwise, you can make use of one of the other 11 overloads and filter the list of pending changes returned by the call to TFS.

The following example will check in all pending changes for the workspace:

// This example assumes we have obtained a reference
// to an existing workspace by following the previous examples
var pendingChanges = workspace.GetPendingChanges();
var results = workspace.CheckIn(pendingChanges, "My check in.");

In the first line of code, I’m getting a list of all pending changes for the workspace. In the second line, I check everything back in to the version control server specifying a comment to be associated with the changeset. The value returned in results contains a count of the items checked in. If there’s one or more pending changes and  results comes back as zero, then no differences were found in the pending items between the server and the client.

Pending edits aren’t the only changes checked in to the version control system. You also check in:

  • Additions
  • Branches
  • Deletions/Undeletes
  • Properties
  • Renames

You can also undo pending changes by calling the Work­space.Undo method. As with the CheckIn method, you must also specify which pending changes you want to undo. The following example will undo all pending changes for the workspace:

var pendingChanges = workspace.GetPendingChanges();
var results = workspace.Undo(pendingChanges);

Retrieving History

A common task within Team Explorer is viewing the history of one or more files or folders. You might find the need to do this programmatically as well. As you might expect, there’s an API for querying history. In fact, the method I’m going to discuss is available from the VersionControlServer instance (as represented by the variable vcs in the previous examples). The method is VersionControlServer.QueryHistory, and it has eight overloads. This method provides the capability of querying the version control server in many different ways, depending on the types and values of the parameters passed into the method call.

Figure 5 shows what the history view for the file Form1.cs might look like within the SCE.

History for Form1.cs
Figure 5 History for Form1.cs

You can replicate this functionality programmatically using the code shown in Figure 6.

Figure 6 Retrieve Item History

var vcs = _tpc.GetService<VersionControlServer>();
var results = vcs.QueryHistory(
"$/Demo/TFS_VC_API/Form1.cs", // The item (file) to query history for
VersionSpec.Latest,           // We want to query the latest version
0,                            // We're not interested in the Deletion ID
RecursionType.Full,           // Recurse all folders
null,                         // Specify null to query for all users
new ChangesetVersionSpec(1),  // Starting version is the 1st changeset        
                              // in TFS
VersionSpec.Latest,           // Ending version is the latest version
                              // in TFS
int.MaxValue,                 // Maximum number of entries to return
true,                            // Include changes
false);               // Slot mode
if (results != null)
{
  foreach (var changeset in (IEnumerable<Changeset>)results)
  {
    if (changeset.Changes.Length > 0)
    {
      foreach (var change in changeset.Changes)
      {
        ResultsTextBox.Text +=
          string.Format("  {0}\t{1}\t{2}\t{3}\t{4}\t{5}\r\n",
          change.Item.ChangesetId,
          change.ChangeType,
          changeset.CommitterDisplayName,
          change.Item.CheckinDate,
          change.Item.ServerItem,
          changeset.Comment);
      }
    }
  }
}

Pay special attention to the argument on line 13 in Figure 6. I’m specifying a value of true for the parameter includeChanges. If you specify false, then specific changes for the version history won’t be included in the returned results and the Figure 6 example won’t display the details. You can still display basic changeset history without returning the changes, but some detail will be unavailable.

Running the Figure 6example produces the results shown in Figure 7.

History from API
Figure 7 History from API

There are many other variations of calling the QueryHistory API. For ideas on how you might make use of this method, simply play around with the history features in the SCE. You can also query much more than history. For example, there are other query-related methods provided by the VersionControlServer, such as:

  • QueryBranchObjectOwnership
  • QueryBranchObjects
  • QueryLabels
  • QueryMergeRelationships
  • QueryMerges
  • QueryMergesExtended
  • QueryMergesWithDetails
  • QueryPendingSets
  • QueryRootBranchObjects
  • QueryShelvedChanges
  • QueryShelvesets
  • QueryWorkspaces

There’s a lot of information available from the version control server, and the various query methods provide you with a window into that information.

Next Steps

The Rangers have only begun to touch on the version control features exposed by the TFS object model. The features covered in this article can be useful and powerful, but myriad features are exposed by the TFS version control server that we haven’t covered. Some examples of these features include branching and merging, shelvesets, labels and version control events. With this series of articles, we hope to expose many of these APIs and provide you with the knowledge you need to make better use of the TFS object model. Stay tuned for more.


Jeff Bramwell is director of enterprise architecture at Farm Credit Services of America. He has more than 20 years of software development experience and always strives to follow the mantra of start simple and work your way up from there. His blog is at devmatter.blogspot.com. You can follow him on Twitter at twitter.com/jbramwell.

Thanks to the following technical experts for reviewing this article: Brian Blackman, Mike Fourie and Willy-Peter Schaub