First, a disclaimer: some of the things I describe in this post involve directly changing data in TFS’s databases. This isn’t usually recommended. Also, I was using TFS 2005. I’m not sure how much of this works on other versions.

A couple of weeks ago, while a coworker and I were deploying some new work item types, we created a field as an Integer that we realized needed to be a String. Changing the field’s type in the work item type definition results in the error “TF26038: Field type for My.Custom.Field does not match the existing type. It was Integer, but now is String.”

A quick look on google made it look like there wasn’t a good way to change the field type. I tried updating the Fields table in the database. This looked like it worked, but the actual fields on the work item type didn’t get updated, so we couldn’t only insert integer data. Non-integers failed with a strange error; for me, it said that the TFS server was offline. So the quick-fix in the database was obviously a bad fix.

So now the question was, how do we preserve the existing data in a new field?

Step 1 was to create a new field. The first attempt used the same friendly name, but a different RefName. Team Foundation objected: “Error: Field name ‘Custom Field’ is used by the field ‘My.Custom.Field’, so it cannot be used by the field ‘My.Custom.Field2’. So now, I’m thinking about trying to change the type of the column in the database. Theoretically, SQL Server can do this. If I’d had some planned downtime, I would probably have gone this route. I wasn’t sure that changing the database column type would be the last time I had a problem, so I wanted to use TF’s tools to do most of the work.

Also, around this time, I (re)discovered the witfields tool.

Here’s what I did.

  1. Created a new field, My.Custom.Field.Temp, as a String, and added it to the work item type with witimport.
  2. Looked in the Fields database table for the FldId of the old and new fields. For example, the old field had FldId 10138 and the new one was 10146.
  3. In each of WorkItemsAre, WorkItemsWere, and WorkItemsLatest, I copied everything from Fld10138 to Fld10146.
  4. I used witimport to remove the old My.Custom.Field field from the work item type.
  5. I used witfields to delete the old field from the database.
  6. I updated the work item type with a new field, My.Custom.Field.
  7. I repeated steps 2 and 3 to get the data from the new field to the fixed old field.
  8. I used witimport to remove the temp field from the work item.
  9. I used witfields to remove the temp field from the database.

It looks like this worked, and each step was pretty quick, and I wasn’t too worried about being interrupted between steps. That said, I make no claim that this will work for you. If you decide to try this, definitely read up on the TFS commands you’re using, and quadruple-check the SQL updates you run.

>>> git tfs clone http://team:8080/ $/sandbox sample_for_git_tfs
Initialized empty Git repository in d:/Projects/sample_for_git_tfs/.git/
C4949 = 84cfc504fd85a826ede8d852e3a5e75fc8952bd2
C69840 = fb10351a88a948d07b3eb6ef176b7e3bb1999ecb
C69841 = 764abfc9995945cafce2e23a1d2735aec8d288bd
C69842 = bb87001af0e93de25e13dab4602db2f84f4b1932
C69843 = 664705c06790fbd1da066833e9e81da24c88ea61
C86163 = 99d300275e87adedae65cd3c9a57c9f55c76eb94
C86164 = 2d10de0deb9fe96e22b14eb2e65e2f95ef55c1d6
C86165 = da9da4b79b2d9e16aa17f6a12317bfe3e29687de
C86167 = b3d7f12b3cf39b546cb19ddc37518be1080b86d8
C86170 = 5df88af2597f219a7b4ccf979ff90ff1cc7ed05b
C86209 = 46d814b83a5a75508e1584a8850d8bcb3c9d5a7d
>>> cd sample_for_git_tfs
>>> ls -R
.:
ANewDirectory

./ANewDirectory:
SomeDir

./ANewDirectory/SomeDir:
readme.txt sayhi.bat

>>> echo another line >> ANewDirectory/SomeDir/readme.txt
>>> git commit -a -m "Contributed using git-tfs."
[master a72b561] Contributed using git-tfs.
1 files changed, 1 insertions(+), 0 deletions(-)
>>> git tfs shelve GIT_TFS_SAMPLE
edit ANewDirectory/SomeDir/readme.txt
>>> mkdir ../sample_workspace
>>> cd ../sample_workspace
>>> tf workspace -new -noprompt sample_workspace
>>> tf unshelve -noprompt GIT_TFS_SAMPLE
sandbox\ANewDirectory\SomeDir:
Unshelving edit: readme.txt
>>> tf diff -noprompt
edit: d:\Projects\sample_workspace\sandbox\ANewDirectory\SomeDir\readme.txt
File: sandbox\ANewDirectory\SomeDir\readme.txt
===================================================================
--- sandbox\ANewDirectory\SomeDir\readme.txt;C69843 (server) 1/28/2010 1:02 PM
+++ sandbox\ANewDirectory\SomeDir\readme.txt (local) 1/28/2010 1:02 PM
@@ -4,3 +4,4 @@
----
This line was added for the second checkin.
This line was added after the parent directory was nested inside a new top-level-directory.
+another line
===================================================================

I’m pretty impressed with Hudson. It’s very easy to administer, and has had a plugin available for everything I’ve wanted to do so far. Despite being from outside of the Windows world, it’s pretty easy to get VS stuff running in it.

However, one thing that would make it better is the ability to watch several source control sources, similar to CCNET’s Multi Source Control plugin. I’m not up for writing an entire plugin. (The one time I tried to do that, I was overwhelmed by the difficulty of creating a plugin from scratch for hudson as compared to CCNET.)

So I patched the Hudson TFS plugin to support multiple paths. So now, in addition to being able to use a simple source control path (e.g. $/myproject/some/directory) that’s mapped to the job’s workspace, you can map several TFS paths into hudson’s workspace (e.g. $/path1 : path1 ; $/path2/a/b/c/d : c\d). You can get this from the dev.java.net issue, github, or download a snapshot build I created.

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!

Today Team Foundation Server was offline in a mysterious way. The fix was pretty non-intuitive, so I’m writing it up.

First, we had done an upgrade of SQL Server over the weekend, and things (eventually) worked. Some users reported problems with some TFS queries, though, so we applied some updates that were supposed to fix it. Unfortunately, bigger problems ensued.

The errors generated when someone tried to access source control were

TF53002: Unable to obtain registration data for application VersionControl.
TF53002: Unable to obtain registration data for application VersionControl.
TF30040: The database is not correctly configured.

and then a warning and two info messages

TF53005: Unable to retrieve the Team Foundation Server installed UI culture.
TF10181: The Team Foundation source control server started at ...
TF10182: The Team Foundation source control server stopped at ...

… all right at once. This happened right after some updates had been installed.

I scrolled down in the error messages and noticed that the first three all had the message “TF30063: You are not authorized to access http://TFS:8080” so I assumed there was some basic auth[nz] thing that wasn’t working.

Google to the rescue! I looked for pairs of the error codes, and came across a post on MSDN’s forums about TFS being unusable after installing Windows 2003 SP1. Following the chain of “This other post solved my problem” links, I found KB 926642. It didn’t look related at all. Problems accessing windows shares via cnames?

Thinking about it a little more, it started to sound plausible. Our TFS server name is not the same as the machine name, we use a CNAME. The KB article had some other symptoms: “Event ID 537 is logged in the Security event log” – check; “you experience authentication issues when you try to access a server locally by using … its CNAME alias” – check (tried going to \\realmachine\c$ – works, \\tfs\c$ – tells me I’m not authorized).

I set the DisableLoopbackCheck flag (didn’t need DisableStrictNameCheckin) and restarted IIS (didn’t need to restart the entire machine), and things appear to work.