I’m sure I’m not the first person to discover this, but it has come up twice in the past few days (once for the code review tool I maintain, and the other for a project-to-be-named later), so I thought I’d toss it out as interesting.

Let’s say you’re writing an app that needs to process TF changesets. You might pull some changesets with a history query …

changesets = VersionControl.QueryHistory("$/Project", VersionSpec.Latest, 0, RecursionType.Full, null, 1, VersionSpec.Latest, int.MaxValue, true, true, true);

… or you might grab the changeset directly …

changeset = VersionControl.GetChangeset(12345);

Regardless, you now have a shiny Changeset object that will tell you all about what happened at that point in time. So you start looking through the changes in the changeset, and you see some Adds, some Renames, some Deletes, some Rename | Edits, etc. So now you can present a list to the user with the things that got updated, one item per change in the changeset. Except…

I found that I need to pay attention to more than just the change type. For example, if you have a directory with a bunch of deleted files, and you move the directory, the changeset for the directory move will include a change with ChangeType.Rename for every file under that directory, including deleted files! The only indication you’ll get is that the change.Item will have a DeletionId value of something other than 0.

I saw this issue on the code review tool when a user asked why an apparent rename was giving a diff of two empty files. The answer was that the file had been deleted before the rename, so it didn’t really exist on either side of the rename. It was just dragged along. So, I should have checked DeletionId!

Also because of this behavior, the project-to-be-named-later ended up adding files to a directory that weren’t actually supposed to be there. Before I write the new version of files in the changeset, I check DeletionId… if it’s not 0, then I ignore the file.

So, if you’re consuming TF changesets, and you want to ignore files that were deleted in the past, be sure to check the change.Item.DeletionId property!