When I started this project, I already had some limited experience with programming against the TFS object model since I had written a utility to migrate bugs from our old bug tracking system to TFS. That said, there were still some new areas to explore since my previous experience was all with the WorkItemStore portion of TFS and nothing with the VersionControlServer portion (I’ve linked to the TFS 2005 version of the documentation since we haven’t yet upgraded to TFS 2008).
To make life a little bit easier for myself I started out by cleaning up the code from my previous utility and refactoring it in a way that made it a bit more broadly useable.
The base of my two utility classes (one for work items and one for version control) is a very basic class that just takes care of providing access to a TeamFoundationServer object
using System; using System.Collections.Generic; using System.Text; using Microsoft.TeamFoundation.Client; namespace Tranxition.Tools.Tfs { /// <summary> /// The ServiceBase class serves as an abstract base class for the /// service-specific TFS utility classes. /// </summary> public abstract class ServiceBase { #region -- Constructor /// <summary> /// Standard constructor for the class, connects to the /// specified TFS server. /// </summary> /// <param name="serverUrl">The URL of the TFS server to connect to.</param> /// <exception cref="ArgumentNullException">Thrown if <paramref name="serverUrl"/> is <c>null</c>.</exception> protected ServiceBase( Uri serverUrl ) { if ( serverUrl == null ) { throw new ArgumentNullException( "serverUrl" ); } else { _server = TeamFoundationServerFactory.GetServer( serverUrl.ToString() ); } } #endregion #region -- Protected properties /// <summary> /// The actual TFS server we're connected to. /// </summary> protected TeamFoundationServer Server { get { return _server; } } private TeamFoundationServer _server; #endregion } }
As you can see, there’s nothing particularly fancy in the base class. The VersionControlService that wraps the basic features needed by TFS Spam is a slightly more interesting:
using System; using System.Collections.Generic; using System.Diagnostics.CodeAnalysis; using System.Text; using Microsoft.TeamFoundation.VersionControl.Client; using Microsoft.TeamFoundation.WorkItemTracking.Client; namespace Tranxition.Tools.Tfs { /// <summary> /// The VersionControlService class represents the TFS version /// control functionality and provides support for the major /// version control features needed by TFS Spam. /// </summary> public class VersionControlService : ServiceBase { #region -- Constructor /// <summary> /// Standard constructor for the class. Connects to the version /// control service on the specified TFS server. /// </summary> /// <param name="serverUrl">The URL of the TFS server to connect to.</param> public VersionControlService( Uri serverUrl ) : base( serverUrl ) { _versionControl = (VersionControlServer) Server.GetService( typeof( VersionControlServer ) ); } #endregion #region -- Public properties /// <summary> /// The version control service on the TFS server /// we're connected to. /// </summary> public VersionControlServer VersionControl { get { return _versionControl; } } private readonly VersionControlServer _versionControl; #endregion #region -- Public methods /// <summary> /// Retrieves the specified changeset with all information /// included. /// </summary> /// <param name="changesetId">The id of the changeset that should be retrieved.</param> /// <returns>The requested changeset with complete information.</returns> public Changeset GetChangeset( int changesetId ) { return _versionControl.GetChangeset( changesetId, true, true ); } /// <summary> /// Downloads the version of a file associated with the specified /// changeset it. /// </summary> /// <param name="itemId">The id of the file item to download.</param> /// <param name="changesetId">The changeset version of the file item that should be downloaded.</param> /// <param name="targetFile">The fully qualified path of the file the file item should be saved as locally.</param> public void DownloadFile( int itemId, int changesetId, string targetFile ) { Item item = _versionControl.GetItem( itemId, changesetId ); DownloadFile( item, targetFile ); } /// <summary> /// Downloads the specified file item to a local file. /// </summary> /// <param name="item">The file item that should be downloaded.</param> /// <param name="targetFile">The fully qualified path of the file the file item should be saved as locally.</param> public void DownloadFile( Item item, string targetFile ) { if ( item == null ) { throw new ArgumentNullException( "item" ); } else if ( string.IsNullOrEmpty( targetFile ) ) { throw new ArgumentNullException( "targetFile" ); } else { ChangesetVersionSpec itemVersion = new ChangesetVersionSpec( item.ChangesetId ); _versionControl.DownloadFile( item.ServerItem, item.DeletionId, itemVersion, targetFile ); } } /// <summary> /// Retrieves the project the specified changeset belongs to. /// </summary> /// <remarks> /// If the changeset contains items from multiple projects, only /// the project the first item in the changeset belongs to is /// returned. /// </remarks> /// <param name="changeset">The changeset whose project should be retrieved.</param> /// <returns>The TFS project the changeset belongs to.</returns> /// <exception cref="ArgumentNullException">Thrown if <paramref name="changeset"/> is <c>null</c>.</exception> public TeamProject GetTfsProject( Changeset changeset ) { if ( changeset == null ) { throw new ArgumentNullException( "changeset" ); } else { foreach ( Change change in changeset.Changes ) { if ( change.Item != null ) { return GetProject( change.Item ); } } return null; } } /// <summary> /// Retrieves the TFS project the specified item belongs to. /// </summary> /// <param name="item">The item whose project should be retrieved.</param> /// <returns>The TFS project the item belongs to.</returns> /// <exception cref="ArgumentNullException">Thrown if <paramref name="item"/> is <c>null</c>.</exception> public TeamProject GetProject( Item item ) { if ( item == null ) { throw new ArgumentNullException( "item" ); } else { return (TeamProject) _versionControl.GetTeamProjectForServerPath( item.ServerItem ); } } #endregion } }
The primary method used by TFS Spam is GetChangeset which (as the name implies) retrieves a Changeset with a specified id. The Changeset instance is the starting point for all of the operations performed by TFS Spam.
Starting with the changeset id is convenient since the id can (obviously) easily be extracted from the check-in notification provided by TFS, but this approach also makes it possible to provide a feature where a user could just provide a changeset id and then get the corresponding check-in e-mail delivered on an on-demand basis. This is something I have yet to actually implement, but something that I think might be handy down the road.
The next post will cover some more details about how we go about getting some interesting information out of the changeset.