共用方式為


Enable Visualization and Changeset Tracking for Branches Through the UI and Programmatically

You have just upgraded to TFS 2010 and you are salivating to take advantage of the awesome new branch visualization tools that you have seen over at channel 9 or many other places and you are wondering what exactly you need to do to take advantage of these features.  Of course, there is some setup you need to do but hopefully this blog post can help walk you through it.  If you would like a little more background before you dive in then check out this introductory post by Matt Mitrik.

First of all, how do I know if visualization and changeset tracking is already enabled for a given branch?

Great question!  If you have just upgraded your existing TFS 2008 server then you likely don’t have any these features enabled for any of your branches yet because we don’t try to detect them on upgrade.  To make this example a little more concrete, let’s assume you have two branches where you are developing prototypes under the path $/MyProduct/features/Feature1/prototypes.  We can determine if these branches have visualizations enabled by navigating to that folder in Source Control Explorer:

pic1

In Source Control Explorer and a few other places the branch visualization status of a folder is represented by its icon.  Even though the Jupiter and Saturn folders above were both created by the branch command, only Saturn has visualizations enabled and this is can be determined by looking at their respective icons. 

Cool, so how do I enable visualizations for the Jupiter branch?

Start by right-clicking on the Jupiter folder, selecting Branching and Merging and then clicking on Convert to Branch….  This will bring up the following dialog:

pic2

At this point you can enter a description for the work that is being done in this branch before converting it.  You should also notice the check box at the bottom of the dialog that reads “Recursively perform this conversion on all folders previously branched from this folder”.  As you might have guessed, this option will recursively convert all of the folders that are child branches of this folder when the conversion is done.  If for some reason, you want to cherry-pick which branches you enable visualizations on then unchecking this box is a good idea.  Now, if we click convert, we will see that the icon for the Jupiter folder now indicates that it has visualizations enabled:

pic3

But when I tried to do that, I got an error reading “TF203028: You cannot create a branch at $/x because a branch already exists at $/x/y”.

Obviously, in a branch hierarchy, branches can have child branches and parent branches.  However, what a branch cannot have is a folder beneath it in the source control repository that is also a branch.  Thus, in our example, if Jupiter would have had a child folder that had already been converted to a branch we would have received this error.  In TFS 2010, when you perform a branch from the UI, the option to “convert source and target folders to branches” is enabled by default if neither of these folder have a parent folder that is a branch.  Thus, if you haven’t enabled visualizations for your existing branches then it is possible to accidentally create these spurious branches.  If we still want to convert Jupiter to be a branch we will need to convert the offending child folder which is marked as a branch back to a normal folder.  This can be done by selecting the offending child folder then clicking File > Source Control > Branching and Merging > Convert To Folder.  You will receive a prompt to confirm the action you are performing and after confirming it then you should be able to convert the desired parent folder to a branch.

Once you have done that to all of your branches then you can visualize your branch hierarchies by right-clicking on a node and selecting Branching and Merging > View Hierarchy.  By changing the “Show:” drop down menu to have the value “All hierarchies” I was able to generate this diagram:

pic4 

Unfortunately, its at this point that I notice that the Feature3 branch has the incorrect parent.  While it was originally branched from the Product1 branch we subsequently did a baseless merge with the Product2 branch because we changed our minds about where Feature3 was actually going to live.  Fortunately, there is a way to fix this!  By selecting the Feature3 branch in this view and then clicking File > Source Control > Branching and Merging > Reparent… you will be presented by a dialog that allows you to choose the branches correct parent.  Note, this dialog will only show branches that this branch has a merge relationship with.  Upon completing the reparenting, my diagram now correctly shows Feature3 with the correct parent (that being Product2):

pic5

Doing All of This Programmatically

Now let’s say that you do want to cherry-pick the branches that you want to convert and you have too many branches to convert by hand.  Fortunately, we can combat this problem with a command line tool that uses some of the APIs new in TFS 2010.  I will walk you through those APIs here.

In order to use the code from the examples in this post you will need the following include statements:

 using System;
using System.Collections.Generic;
using System.Linq;
using Microsoft.TeamFoundation.Client;
using Microsoft.TeamFoundation.VersionControl.Client;

First, let’s talk about the input to this command-line tool because we will need to know the list of branches that you want convert.  For my example I am going to use an xml document formatted in the following way:

 <BranchSpecs>
    <BranchSpec path="$/MyProduct/Main" 
                owner="taylaf" description="The main branch" />
    <BranchSpec path="$/MyProduct/Product1" 
                owner="taylaf" description="The product1 branch" />
    <BranchSpec path="$/MyProduct/Product2" 
                owner="taylaf" description="The product2 branch" />
    <BranchSpec path="$/MyProduct/features/Feature1" 
                owner="taylaf" description="The feature1 branch" />
    <BranchSpec path="$/MyProduct/features/Feature2" 
                owner="taylaf" description="The feature2 branch" />
    <BranchSpec path="$/MyProduct/features/Feature3" overrideParentPath="$/MyProduct/Product2" 
                owner="taylaf" description="The feature3 branch" />
    <BranchSpec path="$/MyProduct/features/prototypes/Jupiter" 
                owner="taylaf" description="The jupiter branch" />
    <BranchSpec path="$/MyProduct/features/prototypes/Saturn" 
                owner="taylaf" description="The saturn branch" />
</BranchSpecs>

You will notice that the $/MyProduct/features/Feature3 is the only branch that has the overrideParentPath attribute set.  This is due to the fact that we want to override what the parent branch is set to when we create the branch objects.  You should know the story as to why we want to do this override if you have read the whole blog post thus far.  If not, you may want to read the section in between the two branch hierarchy screen shots above.  Using some xml parsing magic (which I will leave up to the user to figure out :)) I am going to populate an instance of the following class for each of the BranchSpec entries:

 public class BranchSpec
{
    public String Path { get; set; }
    public String OverrideParentPath { get; set; }
    public String Owner { get; set; }
    public String Description { get; set; }
}

Using this class I will produce the list of BranchSpecs that show up in the rest of the example.  Feel free to use whichever input method works best for you.

Now that we have the data we need to do the processing, let’s start with the basics.  We will need a connection to the server that we are going to be converting the branches on as well as a link to the version control client OM entry point:

 TfsTeamProjectCollection tpc = new TfsTeamProjectCollection(new Uri("https://server:8080/tfs/MyTPC"));
VersionControlServer sourceControl = tpc.GetService<VersionControlServer>();

Before we write the loop that will actually create the branch objects, let’s write some of the meatier parts that will actually be responsible for the branch object creation.  One of the tasks we are going are going to want to perform is to determine the parent branch of the branch we want to convert so that we can set that piece of the metadata on the branch object.  To do this we will write a function to determine the branch parent that uses the GetBranchHistory API.  Here is the code with some explanation in the comments on how to do this:

 private String DetermineParentBranchPath(
    VersionControlServer sourceControl, 
    BranchSpec branchSpec)
{
    // Create an ItemSpec for the branch path that we want to determine
    // the parent for.  Make sure to set the RecursionType parameter to
    // None because we only want branch history for the root item.
    ItemSpec itemSpec = new ItemSpec(branchSpec.Path, RecursionType.None);

    // Call GetBranchHistory() on the ItemSpec that we constructed.  We
    // will set the VersionSpec to latest because we know the branch exists
    // at latest.
    BranchHistoryTreeItem[][] results = sourceControl.GetBranchHistory(
        new ItemSpec[] { itemSpec }, VersionSpec.Latest);

    // The results returned from the GetBranchHistory() call are a bit complicated.
    // First of all, we know that the results specific to our query are in the 
    // first entry of the 2D array.  This is because the contract with the method
    // is that it will return an array of BranchHistoryTreeItem for each path that
    // is queried and the entries in the array will be sorted in the order of the
    // input paths.  Let's grab the first element so we can operate on it.
    BranchHistoryTreeItem[] thisBranchResults = results[0];

    // Now that we have our results we need to process them in order to find the
    // branch that we originally queried on.  In order to do that we will want
    // to look at each BranchHistoryTreeItem and each of its children.  Let's 
    // start by iterating over the results.
    foreach (BranchHistoryTreeItem treeItem in thisBranchResults)
    {
        // Now, let's invoke a recursive function on the treeItem to iterate
        // over all of its children to determine if we have found the requested
        // item.
        BranchRelative requestedItem = FindRequestedItem(treeItem);

        // If we found the requested item see if it has an item it was branched from.
        if (requestedItem != null)
        {
            return (requestedItem.BranchFromItem == null) ? null : requestedItem.BranchFromItem.ServerItem;
        }
    }

    return null;
}
 private BranchRelative FindRequestedItem(
    BranchHistoryTreeItem treeItem)
{
    // If the current item is the requested item then return the
    // BranchRelative for the item.
    if (treeItem.Relative.IsRequestedItem)
    {
        return treeItem.Relative;
    }

    // Otherwise, iterate over all of the children for this 
    // branch item and call this method on the children.
    foreach (BranchHistoryTreeItem child in treeItem.Children)
    {
        BranchRelative requestedItem = FindRequestedItem(child);
        if (requestedItem != null)
        {
            return requestedItem;
        }
    }

    return null;
}

With the above two methods we can programmatically determine the parent of the current branch.  Of course, it is possible that the branch is a root branch and it does not have a parent.  In that case, null will be returned by DetermineParentBranchPath().  Now, onto making sure the branch exists as a branch object.  In terms of the object-model a branch that has visualizations enabled is referred to as a BranchObject.  For the method that makes sure the branch object exists we will make sure to check to see if it exists first and will update it with the appropriate information if it does exist.  The reason for this will be explained later.  Again, here is the code to do this with explanations in the comments:

 private void EnsureBranchObjectExists(
    VersionControlServer sourceControl,
    String path,
    String parentPath,
    String owner,
    String description)
{
    // Create an ItemIdentifier for the path that we will be querying branch objects on.
    ItemIdentifier branchItemIdentifier = new ItemIdentifier(path);

    // Call QueryBranchObjects to determine if a branch object already exists for the
    // given path.  Again, be sure to pass None for the recursion type because we only want to
    // look for the branch object on the item itself.
    BranchObject[] branchObjects = sourceControl.QueryBranchObjects(branchItemIdentifier, RecursionType.None);

    BranchObject branchObject = branchObjects.FirstOrDefault();
    if (branchObject != null)
    {
        // The branch object exists.  Let's make sure it has the correct properties.
        SetBranchProperties(branchObject.Properties, parentPath, owner, description);

        // Now make sure the properties are persisted to the server
        sourceControl.UpdateBranchObject(branchObject.Properties);
    }
    else
    {
        // We need to create the branch object.
        BranchProperties properties = new BranchProperties(branchItemIdentifier);

        // Update its properties to the passed in values.
        SetBranchProperties(properties, parentPath, owner, description);

        // Save the newly created branch object to the server
        sourceControl.CreateBranchObject(properties);
    }
}

private void SetBranchProperties(
    BranchProperties branchProperties,
    String parentPath, 
    String owner, 
    String description)
{
    // We don't want to overwrite the properties if the incoming values are null
    // so make sure to check the values of them before setting.
    if (parentPath != null)
    {
        branchProperties.ParentBranch = new ItemIdentifier(parentPath);
    }
    if (owner != null)
    {
        branchProperties.Owner = owner;
    }
    if (description != null)
    {
        branchProperties.Description = description;
    }
}

At this point we should be able to write a fairly simple outer loop that controls the calls to these methods.  This loop is going to consist of determining the proper parent branch, making sure the parent branch exists as a branch object and finally making sure the current branch exists as a branch object.  The reason we need to make sure the parent branch exists as a branch object is because a branch object cannot have a parent that is not also a branch object.  Here we go:

 foreach (BranchSpec branchSpec in branchSpecs)
{
    String parentPath = branchSpec.OverrideParentPath;
    if (String.IsNullOrEmpty(parentPath))
    {
        parentPath = DetermineParentBranchPath(sourceControl, branchSpec);
    }

    if (!String.IsNullOrEmpty(parentPath))
    {
        EnsureBranchObjectExists(sourceControl, parentPath, null, null, null); 
    }
    EnsureBranchObjectExists(sourceControl, branchSpec.Path, parentPath, branchSpec.Owner, branchSpec.Description);
}

Note, that by passing nulls for the metadata for the parent branch object we will never overwrite valid metadata for that branch object because of the way we have written our SetBranchProperties method. 

And that should be all that it takes!  Once you run the code you should have all of your branches converted to branch objects within TFS.  Have fun with all of the cool visualizations!