# Tuesday, January 29, 2013

TFS has some pretty powerful functionality out of the box one of them is the merge functionality. However I’ve always felt it missed what I thought was a rather nice feature to have, and that is the ability to see a complete list of PBI’s that would be affected by a merge from one branch to another. Now you could argue that you’ll get this anyway by just looking at your PBI’s. However if you find yourself cherry picking change sets  from one branch to the other e.g. you want a finished component to use in your own branch another team has just completed or you only want to release a certain PBI to live, its nice to know if that PBI turns up.

I must point out before continuing that this approach is far from infallible and relies on good housekeeping on the part of your developers i.e. they associate their checked in change sets with work items in TFS. You could also argue that TFS already gives you the PBI’s associated with a change set. It does but you have to go through a bit of pain to actually get to it, that is you open each change set and then find the work items associated with that change set and then the PBI associated with that work item.

The Code
I have put this together as a console app you will need to reference the following in your app in order for this to work. Be sure if you have both the TFS 2010 and TFS 2012 clients installed that you do not mix DLL versions!

  • Microsoft.TeamFoundation.Client
  • Microsoft.TeamFoundation.VersionControl.Client
  • Microsoft.TeamFoundation.WorkItemTracking.Client

Firstly we just do a bit of setup such as the TFS server URL and credentials, the from and to branches etc.

   1:  //The url of your tfs server
   2:  // Don't forget if you have more than one collect you will have to indicate that in the URL!
   3:  private const string TfsServer = "http://localhost:8080/tfs";
   5:  //Replace these with your tfs username and password
   6:  private const string TfsUserName = "tfsusername";
   7:  private const string TfsPassword = "tfspassword";
   9:  //This is the branch that you are merging from
  10:  private const string FromBranch = "$/TestSource/Dev";
  12:  //This is the branch you are merging to.
  13:  private const string ToBranch = "$/TestSource/Main";
  15:  //In my TFS the PBI is called a Product Backlog Item it may be called 
  16:  // something else depending on the template you use.
  17:  const string ProductBackLogItem = "Product Backlog Item";
  18:  const string SprintBacklogTask = "Sprint Backlog Task";
  20:  static readonly List<int> WorkItemCache = new System.Collections.Generic.List<int>();
  22:  static WorkItemStore workItemStore;
  24:  static TfsTeamProjectCollection tfsTeamProjectCollection;
  25:  static TfsTeamProjectCollection GetTeamProjectCollection()
  26:  {
  27:      var tfsTeamProjectCollection = new TfsTeamProjectCollection(
  28:         new Uri(TfsServer),
  29:     new System.Net.NetworkCredential(TfsUserName, TfsPassword));
  30:      tfsTeamProjectCollection.EnsureAuthenticated();
  32:      return tfsTeamProjectCollection;
  33:  }
  35:  static void Main(string[] args)
  36:  {
  37:      tfsTeamProjectCollection = GetTeamProjectCollection();
  39:      //First we get the version control server.
  40:      var versionControl = tfsTeamProjectCollection.GetService<VersionControlServer>();
  42:      workItemStore = new WorkItemStore(tfsTeamProjectCollection);
  44:      // Second we get a list of merge candidates between our two branches (very simple)
  45:      var mergeCandidates = 
  46:          versionControl.GetMergeCandidates(FromBranch, ToBranch, RecursionType.Full);
  48:      //Thirdly we get a list of workitems from our changesets (using some recursion)
  49:      var workItems = GetWorkItemsForChangesets(mergeCandidates);
  51:      //And last we output these all to the screen
  52:      foreach (var workItem in workItems)
  53:      {
  54:          Console.WriteLine(string.Format("{0} {1}", workItem.Id, workItem.Title));
  55:      }
  57:      Console.WriteLine("Complete");
  58:      Console.ReadLine();
  59:  }

We get our TFS collection first and then pull out our change sets from TFS using the VersonControlServer as you can see TFS gives us this functionality straight out of the box.

Next we go and get our workitems from TFS by iterating through our merge candidates and then workitems. If we find a PBI at this level we store them. If we find Tasks we then look inside them for more PBI’s

   1:  //In the example below I have deliberately not used LINQ statements so you can see what is happening more clearly.
   2:  // These lines of code can quite easily be condensed
   3:  static IEnumerable<WorkItem> GetWorkItemsForChangesets(IEnumerable<MergeCandidate> mergeCand)
   4:  {
   5:      var workItems = new List<WorkItem>();
   7:      foreach (var itemMerge in mergeCand)
   8:      {
   9:          var changeSet = itemMerge.Changeset;
  11:          var workItemsCollection = changeSet.WorkItems;
  13:          foreach (WorkItem item in workItemsCollection)
  14:          {
  15:              if (ProductBackLogItem == item.Type.Name)
  16:              {
  17:                  if (!WorkItemCache.Contains(item.Id))
  18:                  {
  19:                      WorkItemCache.Add(item.Id);
  20:                      workItems.Add(item);
  21:                  }
  22:              }
  24:              if (item.WorkItemLinks.Count > 0 && SprintBacklogTask == item.Type.Name)
  25:                  {
  26:                      WorkItemCache.Add(item.Id);
  27:                      var collectedWorkItems = GetProductBacklogItems(item);
  29:                      if (collectedWorkItems != null && collectedWorkItems.Count > 0)
  30:                      {
  31:                          workItems.AddRange(collectedWorkItems);
  32:                      }
  33:                  }
  36:          }
  37:      }
  39:      return workItems;
  40:  }

The code below will take a Task item and check its links for a parent item such as a PBI.

   1:  static List<WorkItem> GetProductBacklogItems(WorkItem workItem)
   2:  {
   3:      var workItems = new List<WorkItem>();
   5:      foreach (WorkItemLink workItemLinks in workItem.WorkItemLinks)
   6:      {
   7:          //We only want parent items so we look for "Implements"
   8:          if (workItemLinks.TargetId > 0 && (workItemLinks.LinkTypeEnd.Name == "Implements"))
   9:          {
  10:              var tempWorkItem = workItemStore.GetWorkItem(workItemLinks.TargetId);
  12:              if (ProductBackLogItem == tempWorkItem.Type.Name)
  13:              {
  14:                  if (!WorkItemCache.Contains(tempWorkItem.Id))
  15:                  {
  16:                      WorkItemCache.Add(tempWorkItem.Id);
  17:                      workItems.Add(tempWorkItem);
  18:                  }
  19:              }
  20:          }
  21:      }
  23:      return workItems;
  24:  }

That's all there is to it. I’m pretty sure the code can be refactored to work better. I would state to be careful when using the code above. Searching through workitems can take a lot of time so if you do end up using it try it out on a test TFS or use your code within a debugger so can see what its doing.

Tags: TFS

Tuesday, January 29, 2013 1:03:54 PM (GMT Standard Time, UTC+00:00)  #    Comments [0]

All comments require the approval of the site owner before being displayed.
Home page

Comment (Some html is allowed: a@href@title, strike) where the @ means "attribute." For example, you can use <a href="" title=""> or <blockquote cite="Scott">.  

Enter the code shown (prevents robots):

Live Comment Preview