I started working on a new project that uses Visual Studio 2005 Team Foundation Server for managing the entire project lifecycle (source control, bug tracking, reporting etc.). This is quite new to me and I tried to acquaint myself with the console commands to carry out the various source control operations. While doing so, I found that the command to view a change set is tf changeset #num. This command pops up a dialog with the list of files that got changed in the given change set. The dialog is shown below…
Unfortunately, if we would like to view the changes done in the change set, we’ll have to click on each file and wait for a second or two for the diff to show up. This, you would agree, is a definite productivity killer and so I wanted to solve the problem.
A little googling revealed the VS TFS extensibility APIs which can be used to write our custom client tools that talk to the team foundation server back-end. I used the APIs to capture the list of files in the change set, download the latest as well as the previous versions of each file and bulk-open them in windiff. The main piece of code is shown below…
// get the changeset for which we want to show the bulk-diff
TFVC.Changeset currentChangeset = VCS.GetChangeset(ChangesetId);
// extract all the item ids that correspond to the files that got changed in the change set
int[] itemIds = Array.ConvertAll<Change,int>(currentChangeset.Changes, delegate(Change c) { return c.Item.ItemId; });
// bulk retreive all the changed items
Item[] items = VCS.GetItems(itemIds, currentChangeset.ChangesetId);
// for each changed item, get the previous version of that item
Item[] olderItems = new Item[items.Length];
for (int i = 0; i < items.Length; ++i)
{
Change currentChange = currentChangeset.Changes[i];
Item currentItem = items[i];
// get the previous version only if it is an edit or merge
if (currentChange.ChangeType == ChangeType.Edit || currentChange.ChangeType == ChangeType.Merge)
{
// query history of the item to get the previous changeset and hence the previous version of the item
foreach (TFVC.Changeset previousChangeset in VCS.QueryHistory(currentItem.ServerItem, new ChangesetVersionSpec(currentItem.ChangesetId),
currentItem.DeletionId, RecursionType.None, null, null, new ChangesetVersionSpec(currentItem.ChangesetId - 1), 1, true, false, false))
{
if (previousChangeset.Changes != null && previousChangeset.Changes.Length > 0)
{
Change previousChange = previousChangeset.Changes[0];
olderItems[i] = VCS.GetItem(previousChange.Item.ItemId, previousChange.Item.ChangesetId);
break;
}
}
}
}
// TempFilesManager is an abstraction to manage temporary files and automatically delete them during the call to Dispose()
using (TempFilesManager tempFiles = new TempFilesManager())
{
// download the latest/older version of the files and also create the description file for windiff
string descriptionFile = tempFiles.GetNewTempFile();
using (StreamWriter sw = new StreamWriter(descriptionFile))
{
const string FormatForSingleFile = "\"{0}\"";
const string FormatForTwoFiles = "\"{0}\" \"{1}\"";
for(int i = 0; i < items.Length; ++i)
{
if (items[i].ItemType == ItemType.File)
{
string olderVersion = null;
if (olderItems[i] != null)
{
olderVersion = tempFiles.GetNewTempFile();
olderItems[i].DownloadFile(olderVersion);
}
string currentVersion = tempFiles.GetNewTempFile();
items[i].DownloadFile(currentVersion);
if (!String.IsNullOrEmpty(olderVersion))
{
sw.WriteLine(FormatForTwoFiles, olderVersion, currentVersion);
}
else
{
sw.WriteLine(FormatForSingleFile, currentVersion);
}
}
}
}
// launch windiff
LaunchWindiff(descriptionFile);
}
The fully working project is attached to this post. Hope you find it useful.