Remove a Branching Relationship in TFS 2010

2020-03-01 10:18发布

问题:

I have a TFS 2010 Team Project that I've just taken over. The branching hierarchy is Dev is a child of Test, Test is a child of Main e.g.

Main

----Test

--------Dev

However at some point in the past someone has done a baseless merge between Dev and Main. this is causing a great deal of confusion as Developers are now accidentally merging code directly from Dev to Main.

This is causing pain with unforeseen merge conflicts when code follows the correct process and is merged from Test to Main, as well as potentially creating a scenario where untested code is promoted to the Main branch.

Is there any way to remove the branching relationship between Dev and Main without deleting the branch? I would like to keep the Dev branch and it's relationship with Test. Just remove the relationship with Main.

I've tried to convert the branch to a folder to remove the relationships but this doesn't work. I can reparent the branch but there doesn't seem to be any obvious way to remove the relationship completely. I could convert the branch to a folder, then delete it and rebranch from Test but this will mean making sure that the codebase is synchronised which may be difficult to achieve.

回答1:

TFS doesn't necessarily build merge candidates based on branch objects for backwards compatibility (branch objects were new in TFS 2010) and to support baseless merges (once you perform a baseless merge between two paths, those will be stored with a merge relationship for future merges.)

I'd suggest using a custom check-in policy here that enforces that merges go from Dev -> Test or Test -> Dev and don't skip the intermediate level. This also allows you to keep Dev as a branch object, so you'll still get all the nice features like branch visualization.

I can provide a very rough, very pseudo-codey example of what I had in mind. (It's rough both because I'm lazy and because I spend my time in the Java SDK and not the .NET SDK and I realize that most people want a .NET check-in policy. But mostly because I'm lazy.)

/*
 * Create a dictionary of merge targets to allowable merge sources.
 * For an item in this list, the allowable merge sources are a whitelist.
 */
private readonly Dictionary<String, List<String>> restrictedMergeTargets =
    new Dictionary<String, List<String>>();

public static MergeWhitelistPolicy
{
    /* Only allowed merges to $/Main from $/Test. */
    List<String> mainWhitelist = new List<String>();
    mainWhitelist.add("$/Test");
    allowedMerges.put("$/Main", mainWhitelist);

    /* Only allow merges to $/Test from $/Dev. */
    List<String> testWhitelist = new List<String>();
    testWhitelist.add("$/Dev");
    allowedMerges.put("$/Test", testWhitelist);
}

public PolicyFailure[] evaluate(PolicyContext context)
{
    PendingChange[] pendingChanges = GetPendingCheckin().GetCheckedPendingChanges();

    foreach(PendingChange change : pendingChanges)
    {
        if(! change.IsMerge())
        {
            continue;
        }

        foreach(KeyValuePair<String, List<String>> restrictedTarget : restrictedMergeTargets)
        {
            if(VersionControlPath.IsChild(restrictedTarget.GetKey(), change.GetServerItem())
            {
                /* Whitelisted merge path - investigate. */
                foreach(String allowedSource : restrictedTarget.GetValue())
                {
                    foreach(MergeSource mergeSource : change.GetMergeSources())
                    {
                        if(! VersionControlPath.IsChild(allowedSource, mergeSource.GetServerItem()))
                        {
                            return new PolicyFailure("Merge from " +
                                mergeSource.GetServerItem() + " to " +
                                change.GetServerItem() + " is disallowed.");
                        }
                    }
                }
            }
        }
    }

    return null;
}

There are several issues with this, of course. You certainly wouldn't want to hardcode the list of acceptable merge relationships into the policy - you could externalize it into a configuration file, or you could query the merge relationships on the server and cache them if you had particular rules like only direct descendants can merge. (Caching this is important as check-in policy evaluation runs frequently and is expected to be fast. It may even run on the UI thread occasionally (though I doubt it), so your mileage may vary.)

Also, my path testing code is pretty sloppy, mostly just to save some space in my comment. (And also, the aforementioned laziness on my part.)

I hope this is a good start.



回答2:

Excellent question! I don't have the direct answer but I have suggestion to reduce risk of future occurrences:

Severely restrict who has rights to merge to parent branches, especially to the Main branch. I designate one Dev Lead or Sr. Dev as a branch owner who is responsible for RI merges to that branch. (Admins can also cover without needing extra privileges.)

This doesn't help you once the baseless merge creates a new relationship, but it should reduce the risk of unintended recurrence in the future. There might also be a valid case where jumping your Test branch would be necessary, but I can't think of any good reason for doing so.