Changing to Slot Mode in TFS 2010 Version Control

In TFS 2010, one of the more significant changes that we made to our version control platform was how we identify items. Previously, version control operated in what we called “item mode” and in TFS 2010 it operates in “slot mode”. To better understand the motivation and impact of these changes, and what “item mode” and “slot mode” actually mean, I’ve decided to provide some background and detail into the changes we’ve made.

Item Mode Explained

In 2008, items are identified by an integer ItemID. Each new item added to the system received a unique integer ID that is used to refer to that item. Additionally, each item has a path, which end users of the system typically use to refer to items that they store on the server. Using this combination of ItemIDs and paths to refer to items worked in many situations, and was a design choice that enabled the rename feature (i.e. renaming an item changed its path, but not its ID). In many cases, we’ve referred to this as operating in “item mode”, that is, users act on items in the system. Items are added, edited, and renamed, and they are the same item even with a new name and modified content. The most evident place this is seen is in History, where history on, say, foo.cs will show changes made to the old name, bar.cs, because it is actually the same item.

The Switch to Slot Mode

While using ItemID and path worked in many cases, there are several situations that allow items to share a path but have differing ItemIDs. Over time, these scenarios began to complicate our code, and there were even scenarios that we couldn’t solve with this approach. This led us to change the design so that the ItemID and the path for an item are synonymous – that is, an ItemID is unique to a path. Practically, this means that when an item is renamed, it effectively becomes a new item (because it has a new path).

Unlike item mode, this concept of slot mode means that users operate on a namespace slot (i.e. $/Proj/foo.cs), not an item that exists at some path. Again, it is easy to think about this from the perspective of History. In the slot mode world, asking for history on foo.cs will only show you what has happened to files at that path. In the previous example, you wouldn’t see edits from the item when it was named bar.cs.

There were at least two primary motivating factors behind our decision to make these changes: improving the robustness of Merge, and improving the performance of the system. In some cases, merge can actually block a user trying to check-in changes, and they are forced to perform a second merge to get all of the changes. While these cases aren’t extremely common, they are severe enough that we decided we needed to change the system to make it more robust. Merge is one aspect of version control that is inherently complex, and the implementation of rename using ItemIDs further complicated the problem. Using the path as the identifier allowed us to significantly reduce complexity of the system, leading to a system that is easier to maintain, and one that performs better than the previous implementation.

Where will I see changes when using the product?

While the primary motivation was to improve merge, there are a bunch of places in the system where behaviors are changing. Here is a list of the most significant of those changes (§ indicates a change coming in Beta 2):

Merge

Merges will always be performed in slot mode, or on the path. That is, the content of an item at a path in the source branch will be merged over to an item with the same relative path in the target branch. In cases where items are renamed in the source branch, the history for the item in the target branch will indicate where the content was merged from.

Rename

Now implemented as a branch + delete behind the scenes (new name is branched from the old, then the old name is deleted). This allows traceability in History, but allows us to get around the problems with merging renames (see Add, Rename, Add scenario). We also added a new “source rename” bit to identify items that were deleted because they were renamed.

History

History will default to slot mode, that is, renames won’t be followed automatically (§). Because renames are branches behind the scenes, the history of an item before a rename will be followed exactly like a branch (§). For items that have been renamed “out” to another name, the history will have an entry that shows the “source rename” change type.

Changeset Details

Items that were renamed will show the “source rename” change type on the old name (§). The new name will continue to show as a “rename”.

Source Control Explorer

The option to show deleted items will show the old names of files and folders in addition to items that were simply deleted (§).

A Scenario: Add, Rename, Add

Since this is all pretty complex stuff, I think scenarios are the best way to show how the system is changing. This “Add, Rename, Add” scenario, as we like to call it, is one example that a 2008 server would be blocked on that a 2010 server now handles with ease. Here’s what it looks like:

Add a file to $/Proj/Main/File1.cs

Branch $/Proj/Main to $/Proj/Dev

Rename the file on the Dev branch to $/Proj/Dev/File2.cs

Add a second file to $/Proj/Dev/File1.cs (Note that this file overlaps the old namespace slot of File2.cs)

Merge $/Proj/Dev to $/Proj/Main

At this point, most users would expect the system to be able to correctly merge the two files back up to the Main branch, resulting in two files, File1.cs and File2.cs, that have contents that match the corresponding files in the Dev branch. However, the problem is that in 2008, all file renames were treated as conflicts when merging, so the user would have needed to accept the merge of the rename before the new file could be added in its place. Since the system can’t handle that, users were forced into merging over the rename first, and then merging over the add. Needless to say, this is less than ideal, and it’s a simple case – imagine if we had conflicting edits on the Main branch!

With the new changes we’ve made, the rename of the file on the Dev branch causes a new item, File2.cs, to be branched over to Main, and the content from the new File1.cs goes into the File1.cs on the target branch. The result is that the merge is pended successfully, no conflicts are created, and the merged can be checked in as one changeset. After the check in to Main, viewing the History for File2.cs on Main will show a change type of “merge, branch”, with the merged change coming from File2.cs on Dev (where the file was renamed from File1.cs).

The History on File1.cs on Main will show a change type of “merge, edit”, to reflect the change in the content that was merged from the Dev branch. In this screenshot, notice that changeset 26 has a change type of “add, source rename”. This is because in this example, I renamed File1.cs to File2.cs and added a new file named File1.cs in the same changeset.

Forward Compatibility for 2008 Clients

All of these platform changes in TFS 2010 have had a fairly large impact on the version control client. While we always aim to preserve forward and backwards compatibility, these changes were substantial enough that the old clients accessing 2010 servers no longer provide an optimal experience. John Nierenberg has already blogged about some of the compatibility issues on the WIT team blog, and in the future we’ll post future updates about our plans to address these issues.

For showing old names of deleted items. This seems confusing. I would think the last name that was used for the when it was deleted should be enough. Is there a scenario where it isn’t? Maybe it should be an extra option to show old names?

When you undelete an old name do you get the new file undeleted or the one at the point in time where it was that name undeleted?

(I guess I need to get a TFS2010 beta server installed to try some of these out.)

However, there is no way for me to easily link the new item to the old. Something like item.oldid would be nice.

I find it a very scary propositon to try and determine through name parsing which item in the changeset "might" have been the previous name/path/id.

My current scenario is that I need to write a code sync tool from TFS to another SC system until TFS is adopted officially. I have to know which item was renamed, so that I perform the proper action in the other system.

For example, if a very large changeset is submitted, containing any renames of files in a single folder, TFS is basically giving me data like this:

$/MAIN/folder1/oldfilename1.cs (changed, is the old name)

$/MAIN/folder1/oldfilename2.s (changed, is the old name)

$/MAIN/folder1/oldfilename1a.cs (changed, is the new name)

$/MAIN/folder1/oldfilename2a.cs (changed, is the new name)

Yes, I can tell that a rename occured. But how do I know which item is the NEW one that the OLD one was renamed to? Is there something to link the 2 that I can get from the API?

Looks like you have thrown away the baby with the bathwater. Complicated merges are an intrinsic problem of software developement, and can be worked through using enough man- and brainpower. Fundamental inability to see history through renames is a good enough reason to use another SCM.

You are showing all nice and new history UI without mentioning that it does not work the same way for contents of folders! Yes, it does work for folders themselves — but that is not exactly what I need. I need a combination of /itemmode and /recursive, which DOES NOT WORK ANYMORE/

I used 2008 in the past and I loved the ease of working with history. I insisted on using TFS at the new place. Well, now I'm enjoying your innovation: I can't see history for a release after I branch it off Main.