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:
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):
Main trunk
is modified (new functionalities are added, bug fixes...) Main trunk
, a new branch is created: Main trunk 1.3
APP_A
branch might be modified, so unique functionalities of APP_A
will work with modification of v1.3 APP_B
branch might be modified, so unique functionalities of APP_B
will work with modification of v1.3 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
APP_A
branch, a new branch is created: APP_A_1.3
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:
APP_A_1.2
branch itself APP_A
branch before APP_A_1.2
was created Main trunk 1.2
branch before it has been merged to APP_A
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.
first let me first ask one question. At the top of the post you write: "My goal is to be able to produce a changelog between APP_A_1.3 and APP_A_1.2."
but then when you write what changes specifically you are looking for you list: 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
This is not a valid list because it will give you all changes that contributed to APP_A_1.3, APP_A_1.2, 1.1 and so on to the beginning of the repository.
I'm not able to test my approach right now, but this is what I would do: - QueryHistory to get all changes checked in directly into branch 1.3 - use QueryMergesExtended to follow merges into this branch. QueryMergesExtended (http://msdn.microsoft.com/en-us/library/ff736485.aspx) was added in TFS 2010 specifically to be much more performant and robust than QueryMerges and QueryMergesWithDetails, in order to support branch visualization tools - afaik you don't need to specify option FollowRenames in QueryMergesExtended because you query merges on the root of the branch - when you get list of source changes (from APP_A) you need to check each changeset to see of it contains merge changes. If so, you need to query merges on app_a for those changesets. Do so recursively until you walk whole branch hierarchy.
On the side topic, you can look later at the QueryMergeRelationships (http://msdn.microsoft.com/en-us/library/microsoft.teamfoundation.versioncontrol.client.versioncontrolserver.querymergerelationships.aspx) which gives you branch object list introduced in tfs 2010 (this is what happens when in Source Control Explorer you select folder and click Convert to branch). However if you can discover your branch in different way (hardcode them) than it's not needed.
Hope this helps!
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 (ie 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.
Yeah, working on this problem myself too. I found a codeplex project that solves it, when you're diff-ing labels, anyhow.
See if this helps: http://tfslabeldiff.codeplex.com/SourceControl/changeset/view/7075#158224
I was pretty surprised how difficult this was to find, but the documentation for TFS is lacking at best. It seemed like it ought to be obvious!
链接地址: http://www.djcxy.com/p/31820.html