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.3APP_B
branch might be modified, so unique functionalities of APP_B
will work with modification of v1.3Main 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 itselfAPP_A
branch before APP_A_1.2
was createdMain trunk 1.2
branch before it has been merged to APP_A
Main trunk
branch before Main trunk 1.2
was createdI'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.
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.