I\'m looking for a way to automatically produce a changelog (actually a list of workitems) between two releases of my application. I have two versions of my application, v1
In general, the absolute method of defining points in time in any SCM is clearly the checkin-id.
Using labels to abstract this, is in TFS not the optimum as discussed here & here. A better approach is to use builds instead, especially in a modern CI environment.
In order to retrieve the max changeset that was contained in a given build you 'd have to do something like this:
using System;
using System.Collections.Generic;
using Microsoft.TeamFoundation.Build.Client;
using Microsoft.TeamFoundation.Client;
namespace GetChangesetsFromBuild
{
class Program
{
static void Main()
{
TfsTeamProjectCollection tpc = TfsTeamProjectCollectionFactory.GetTeamProjectCollection(new Uri("http://TFSServer:8080/Name"));
IBuildServer bs = (IBuildServer)tpc.GetService(typeof(IBuildServer));
IBuildDetail build = bs.GetAllBuildDetails(new Uri("vstfs:///..."));
List<IChangesetSummary> associatedChangesets = InformationNodeConverters.GetAssociatedChangesets(build);
int idMax = associatedChangesets[0].ChangesetId;
}
}
}
A difficulty with the above is to retrieve the BuildUri of the builds you are interested in. In order to get this information you could do something like this:
IBuildDetail[] builds = bs.QueryBuilds("TeamPorjectName", "yourBuildDefinitionName")
and then retrieve the Uri's that are important to you.
This is also a good vehicle if you eventually insist on using labels: Besides Uri
, each build[]
has also a LabelName
.
This question got me closer to solving a similar problem I was having.
Use the type LabelVersionSpec
instead of VersionSpec
for label versions.
Replace:
VersionSpec sFrom = VersionSpec.ParseSingleSpec("LLABEL1", null);
VersionSpec sTo = VersionSpec.ParseSingleSpec("LLABEL2", null);
with:
LabelVersionSpec sFrom = new LabelVersionSpec("LLABEL1");
LabelVersionSpec sTo = new LabelVersionSpec("LLABEL2");
I think http://tfschangelog.codeplex.com/ can possibly help you here.
TFS ChangeLog applicatoin allows users to automatically generate release notes from TFS. Users will have to provide information on thier project, branch and changeset range and then TFS ChangeLog application will extract information from each changeset in a given range and all the associated workitems to such changesets. i.e. it will travel from starting changeset upto ending changeset and will extract data about each changeset along with associated workitems in an XML file.
Users can then use their own transformation logic including filter, sorting, styling, output formatting, etc. to generate Release Notes Report.
Another thing I would like to add here will be related to Labels in TFS. Labels are basically assigned / associated with changesets. Currently, TFS ChangeLog application does not support Labels to define starting and ending point but it does support changeset which can be used as a workaround solution.
Hope this is useful.
I have been in the same situation as you. I also want Work Items from merged changesets included. I only include Work Items that are Done. Also if the same Work Item is linked to multiple changesets, only the last changeset is reported. I use this in a CI setup; and create a changelog for each build. The List<ChangeInfo>
can then be exported to a XML/HTML/TXT-file. Here is my solution:
namespace TFSChangelog
{
public class TFSChangelogGenerator
{
private const string workItemDoneText = "Done";
/// <summary>
/// This class describes a change by:
/// Changeset details
/// and
/// WorkItem details
/// </summary>
public class ChangeInfo
{
#region Changeset details
public DateTime ChangesetCreationDate { get; set; }
public int ChangesetId { get; set; }
#endregion
#region WorkItem details
public string WorkItemTitle { get; set; }
public int WorkItemId { get; set; }
#endregion
}
public static List<ChangeInfo> GetChangeinfo(string tfsServer, string serverPath, string from, string to)
{
// Connect to server
var tfs = TfsTeamProjectCollectionFactory.GetTeamProjectCollection(new Uri(tfsServer));
tfs.Connect(ConnectOptions.None);
var vcs = tfs.GetService<VersionControlServer>();
// Create versionspec's
VersionSpec versionFrom = null;
if (!string.IsNullOrEmpty(from))
versionFrom = VersionSpec.ParseSingleSpec(from, null);
VersionSpec versionTo = VersionSpec.Latest;
if (!string.IsNullOrEmpty(to))
versionTo = VersionSpec.ParseSingleSpec(to, null);
// Internally used dictionary
var changes = new Dictionary<int, ChangeInfo>();
// Find Changesets that are checked into the branch
var directChangesets = vcs.QueryHistory(
serverPath,
VersionSpec.Latest,
0,
RecursionType.Full,
null,
versionFrom,
versionTo,
Int32.MaxValue,
true,
false
).Cast<Changeset>();
foreach (var changeset in directChangesets)
{
foreach (var workItem in changeset.WorkItems.Where(workItem => workItem.State == workItemDoneText))
{
if (changes.ContainsKey(workItem.Id))
{
if (changeset.ChangesetId < changes[workItem.Id].ChangesetId) continue;
}
changes[workItem.Id] = new ChangeInfo { ChangesetId = changeset.ChangesetId, ChangesetCreationDate = changeset.CreationDate, WorkItemId = workItem.Id, WorkItemTitle = workItem.Title };
}
}
// Find Changesets that are merged into the branch
var items = vcs.GetItems(serverPath, RecursionType.Full);
foreach (var item in items.Items)
{
var changesetMergeDetails = vcs.QueryMergesWithDetails(
null,
null,
0,
item.ServerItem,
VersionSpec.Latest,
0,
versionFrom,
versionTo,
RecursionType.Full
);
foreach (var merge in changesetMergeDetails.Changesets)
{
foreach (var workItem in merge.WorkItems.Where(workItem => workItem.State == workItemDoneText))
{
if (changes.ContainsKey(workItem.Id))
{
if (merge.ChangesetId < changes[workItem.Id].ChangesetId) continue;
}
changes[workItem.Id] = new ChangeInfo { ChangesetId = merge.ChangesetId, ChangesetCreationDate = merge.CreationDate, WorkItemId = workItem.Id, WorkItemTitle = workItem.Title };
}
}
}
// Return a list sorted by ChangesetId
return (from entry in changes orderby entry.Value.ChangesetId descending select entry.Value).ToList();
}
}
}