Search code examples
c#branchtfs-workitemchangelog

TFS2010: Retrieve all changesets associated with a branch (full recursion)


This follows my previous question about TFS 2010 and the possibility to create a changelog.

I was previously using labels to identify a version of the program, but as labels are not fixed points in time, now I'm using branches.

Here's how the branch hierarchy looks like:

branch hierarchy

As you can see, there are two different applications that are branches of the trunk: APP_A (application A) and APP_B (application B). Both are almost identical, but there are some functional differences.

Here is the process to create a new version of the application (say version 1.3):

  1. The Main trunk is modified (new functionalities are added, bug fixes...)
  2. From the modified Main trunk, a new branch is created: Main trunk 1.3
  3. APP_A branch might be modified, so unique functionalities of APP_A will work with modification of v1.3
  4. APP_B branch might be modified, so unique functionalities of APP_B will work with modification of v1.3
  5. Main trunk 1.3 is merged to APP_A and APP_B, so both APP_A and APP_B applications receive the modifications of the Main trunk
  6. From the modified APP_A branch, a new branch is created: APP_A_1.3
  7. From the modified APP_B branch, a new branch is created: APP_B_1.3

My goal is to be able to produce a changelog between APP_A_1.3 and APP_A_1.2.

By changelog I mean a list of WorkItems. Each changeset that is checked-in is associated with one or more WorkItem (for instance a Bug item). I would like to be able to get the list of all workitems that were linked to a changeset that has impacted APP_A_1.3: those changesets might come from the Main trunk (step 1 above), the APP_A branch (step 3 above) or even the APP_A_1.3 branch itself (if hotfixes are checked-in after the branch has been created).

To get this list of workitems, I tried to get the list of all changesets that are "linked" to APP_A_1.2 ("linked" = the code that was checked-in in the changeset is now on the branch APP_A_1.2) and the list of all changesets that are "linked" to APP_A_1.3.

Then, I'll be able to know which changesets are "linked" to APP_A_1.3 and not "linked" to APP_A_1.2. From this subset of changesets, I'll get all associated WorkItems and thus my changelog.

Here's my problem: how could I get the list of ALL changesets that are "linked" with a specified branch? I'm using the TFS 2010 API for C# code.

The input of my program (that would retrieve all changesets for a specified branch) would be the name of the branch (say APP_A_1.2), and the output would be the list of following changesets:

  • changesets applied on APP_A_1.2 branch itself
  • changesets applied on APP_A branch before APP_A_1.2 was created
  • changesets applied on Main trunk 1.2 branch before it has been merged to APP_A
  • changesets applied on Main trunk branch before Main trunk 1.2 was created

I've wrote the following pieces of code to get all those changesets:

// Gets the list of all changesets ID from APP_A_1.2 branch
var branch1ChangeSets = myVersionControlServer.QueryHistory(
    "$/PATH/APP_A_1.2/",
    VersionSpec.Latest,
    0,
    RecursionType.Full,
    null,
    null,
    null,
    int.MaxValue,
    false,
    false).OfType<Changeset>().Select(z => z.ChangesetId).ToList();

Even if RecursionType.Full is specified, the above code only returns changesets that were checked-in on the APP_A_1.2 branch itself. This is identical to the "History" command on Source Code Explorer view in Visual Studio.

Then I tried the following piece of code:

// Gets the list of all changesets ID from APP_A_1.2 branch
var branch1MergedChangeSets = myVersionControlServer.QueryMerges(
    null,
    null,
    "$/PATH/APP_A_1.2/",
    VersionSpec.Latest,
    null,
    null,
    RecursionType.Full).Select(z => z.SourceVersion).ToList();

This returns changesets that were checked-in on APP_A_1.2 branch + those that were cheked-in on APP_A branch before APP_A_1.2 was created. Much better, but not sufficient. I can't find a way to make the recursion work with branches that are "above" APP_A (Main trunk in my case)...

Anyone has an idea?

Also, any better ideas to get the changelog between two branches are welcome... Thx.


Solution

  • I finally came up with a simple solution. I'm not totally happy with it as it actually looks like a brute-force algorithm, but at least it works.

    What I do is:

    1) Get the list of every changeset that is applied on the very root of my TFS branches (i.e. the "parent path" of Main Trunk):

    var allChangesets = vcs.QueryHistory(
        "MySourcePath",
        VersionSpec.Latest,
        0,
        RecursionType.Full,
        null,
        firstPossibleChangeset,
        VersionSpec.Latest,
        int.MaxValue,
        true,
        false).OfType<Changeset>().ToList();
    

    2) For each retrieved changeset, I call TrackMerges to see if the changeset impacts in some way my branches. TrackMerges is able to tell me if a specified changeset is applied on the branches I specify as parameter of the function (it'll return the target changeset ID on these branches). If a changeset is applied on the destination branch (in my case APP_A_1.3) and not in the source branch (APP_A_1.2), then it means it's definitely something new on my APP_A_1.3 branch.

    List<int> newChangesets = new List<int>();
    foreach (var z in allChangesets.Where(y => y.ChangesetId > firstPossibleChangesetId))
    {
        var zz = vcs.TrackMerges(
            new int[] { z.ChangesetId },
            new ItemIdentifier("THE TRUNK PATH"),   // The root of all branches
            new ItemIdentifier[] { new ItemIdentifier(fromBranchPath), new ItemIdentifier(toBranchPath) },
            null);
    
        var targetInFromBranch = zz.Where(t => t.TargetItem.Item == fromBranchPath).FirstOrDefault();
        var targetInToBranch = zz.Where(t => t.TargetItem.Item == toBranchPath).FirstOrDefault();
    
        if (targetInToBranch != null && targetInFromBranch == null)
        {
            // Then the changeset is only applied on the ToBranch
            newChangesets.Add(z.ChangesetId);
        }
    }
    

    3) Now it's very simple to get my changelog (the list of workitems) from the list of "new changesets":

    // Now, gets associated work items!
    Dictionary<int, WorkItem> dico = new Dictionary<int, WorkItem>();
    foreach (int changesetId in newChangesets)
    {
        foreach (WorkItem zz in vcs.GetChangeset(changesetId).WorkItems)
        {
            this.AddWorkItemToDicRecursive(wis, dico, zz);
        }
    }
    
    private void AddWorkItemToDicRecursive(WorkItemStore wis, Dictionary<int, WorkItem> dico, WorkItem workItem)
    {
        if (!dico.ContainsKey(workItem.Id))
        {
            dico.Add(workItem.Id, workItem);
    
            foreach (WorkItemLink associatedItem in workItem.WorkItemLinks)
            {
                this.AddWorkItemToDicRecursive(wis, dico, wis.GetWorkItem(associatedItem.TargetId));
            }
        }
    }
    

    I don't think it's the best possible approach, but it works fine and remains simple. Also, I didn't have to hardcode anything (branch names/hierarchy) so it's not too bad IMO. Hope it'll help someone.