Sunday, November 11, 2012

TFS: Who Is a Father of My Branch?

When you create a branch you hope it will be easy to find out what changeset it was created from. That's probably true for most of source control systems but TFS (at least but TFS 2010). If you are lucky like me and have to use TFS, you need to leverage some TFS-Fu to achieve this goal.

GUI Way

First of all, I need to mention that it's possible to get this information from standard Team Explorer UI - you just need to look in the right place.
  1. Right-click the root folder of the branch your want to find parent changeset for and select View History from a popup menu
  2. In the History window scroll down to the very first changeset, right-click it and select Track Changeset option
  3. In Tracking Changeset window select only your branch and the parent branch. It's also a good idea to set up a path filter by the root folder of your branch, otherwise it's very likely you'll get TF247006 error (too many items to track). Then click Visualize button
As a result of the visualization you'll see something like this:
If you hover your mouse over the parent branch (trunk on the picture) you'll see a list of associated changesets. The list displays all the changesets "included" into the first changeset of your child branch (Dev on the picture). Now you just need to find the latest (i.e. the changeset with max ID), it's 83887 on the picture.
Note: Since "Create Branch" operation in TFS is always done for root folder of your branch (by definition) it's safe to set up a path filter as described in step #3 - you'll never exclude the parent changeset by that filter.

API Way

If you have access to an SQL Server instance which TFS uses, you can make use of this thread: http://social.msdn.microsoft.com/Forums/en-US/tfsversioncontrol/thread/a010da85-39f7-4810-99fc-c33db4800c8f
In brief, they suggest to use the following SQL query:
 SELECT MAX(SourceVersionTo)  
 FROM tbl_MergeHistory WITH(NOLOCK)  
 WHERE TargetVersionFrom = <first_changeset_in_branch>  
But it seems like a hack to me. The correct way should be to use TFS API, and the following piece of code does it:
 public static int GetStartChangesetForBranch( string path )  
 {  
   // Get TFS version control service.  
   TfsTeamProjectCollection tpc = new TfsTeamProjectCollection(  
     new Uri( "<Your TFS URL>" ) );  
   var vcServer = tpc.GetService<VersionControlServer>();  
   

   // Get the first changeset within a branch.  
   int firstChangeset = vcServer.QueryHistory( path,  
     LatestVersionSpec.Latest, 0, RecursionType.None, null,  
     null, null, 1, false, false, false, true ).Cast<Changeset>().First().ChangesetId;  
   

   // Calculate and return a parent changeset for the branch.  
   return vcServer.QueryMerges( null, null,  
     new ItemSpec( path, RecursionType.Full ),  
     LatestVersionSpec.Latest,  
     null,  
     new ChangesetVersionSpec( firstChangeset ) ).Max( x => x.SourceVersion );  
 }  

Making the Life Easier

Well, now we have a piece of C# code which does the task we need. There are two options to turn it into a tool: create a console application or create a powershell snap-in. I'll stick to console application since it's a standard command-line interface Visual Studio uses (at least Visual Studio 2010).
To behave similarly to tf.exe we'll need to add support of getting TFS URL and branch information from current workspace. This can be achieved via Workstation class:
 var workspaceInfo = Workstation.Current.GetLocalWorkspaceInfo( serverOrLocalPathToBranch );  
 var tpc = new TfsTeamProjectCollection( workspaceInfo.ServerUri );  
 var vcServer = tpc.GetService<VersionControlServer>();  
The sample application can be downloaded here: tf-getparentcs.zip
You can copy it to any path visible from Visual Studio 2010 Command Prompt so you can use this tool the same way you use tf.exe or tfpt.exe.
Supported syntax is:
  • tf-getparentcs
    If no parameters are provided, the tool will try to figure out the branch from the current directory (yes, you need to call the tool from a TFS mapped folder in this case)
  • tf-getparentcs <server_path>
    You can provide a root path to a branch or a path to any item within a branch - it will work the same way