Team System

Team Foundation Server Version Control

Brian A. Randell

Code download available at:  VSTS 2007_01.exe(199 KB)

Contents

Digging into Team Foundation Server
Connecting to the Server
Documents under Version Control
Fit and Finish
Conclusion

I didn't know it at the time, but in February 2004 this column was conceived. Around that time, I was at the Microsoft campus in Redmond for a software design review covering an upcoming product that was code-named "Burton." In every review session, I raised my hand and asked the same question: "Will there be extensibility points?" Over the two days, I consistently got an answer that made me smile: "Yes, Brian, you can customize it." Burton, it turns out, became Visual Studio® Team System, and customizing it is what this column is all about.

In the first of what I expect to be many columns, I'll poke around and show you how to extend and enhance Visual Studio Team System on both the client and server. Along the way, I'll explain how you can build an add-in for Microsoft® Word that allows you to check Word documents in and out of the Team Foundation Server version control repository. Before we start, though, head over to msdn.microsoft.com/vstudio/extend and get yourself a copy of the Visual Studio 2005 SDK. The SDK contains documentation and samples for customizing and extending the entire Visual Studio product line. In order to build this add-in, you'll need a copy of Visual Studio 2005 Professional (or any of the role-based SKUs including Team Suite), the Visual Studio 2005 Tools for Office Second Edition Beta, and the Visual Studio 2005 Team Explorer. Since this column is about extending Team System, I won't be covering the details related to hooking the code into the Word infrastructure, but all the code will be provided for you to see.

Digging into Team Foundation Server

Before starting, you need to understand the core interaction between Team Foundation Server and my client-side add-in shown in Figure 1-or any other client for that matter. If you're going to work with Team Foundation Server, the first thing you need to do is connect to a valid server. To do this, you must know the server name, the protocol to use (HTTP or HTTPS), and the port. To perform any version control operations, you need a workspace with a working folder. A workspace represents the client-side copy of the artifacts that are under version control on the Team Foundation Server, and it serves as an isolated area to perform your work. Each user can have multiple workspaces on a client machine. Each workspace, in turn, supports multiple mappings between a local file path, known as a working folder, and a path in the version control repository. The first time you use the source code control features inside Visual Studio 2005 in conjunction with a Team Foundation Server, the Team Explorer tools create a default workspace using the name of your machine. So the add-in needs to perform all of these operations in order to be able to get documents in and out of the Team Foundation Server version control repository.

Figure 1 Checking a Word Doc into Team Foundation Server Version Control

Figure 1** Checking a Word Doc into Team Foundation Server Version Control **(Click the image for a larger view)

Microsoft factored the APIs for programming Team System across numerous assemblies. The core set of APIs the add-in uses to interact with Team Foundation Server are contained in three assemblies installed as part of a Team Explorer installation. They are Microsoft.TeamFoundation.Client.dll, Microsoft.TeamFoundation.VersionControl.Client.dll, and Microsoft.TeamFoundation.VersionControl.Common.dll. The Team Explorer setup application installs the assemblies in the Global Assembly Cache (GAC). However, the setup does not register them to show up in the Visual Studio 2005 Add References dialog. You'll need to either modify your Windows registry so that the Add References dialog displays the assemblies (see support.microsoft.com/kb/306149), or manually browse to the assemblies. You'll find browsable copies of the assemblies at %Program Files%\Microsoft Visual Studio 8\Common7\IDE\PrivateAssemblies\.

Connecting to the Server

To get started, you'll want to create a class library, which needs references to the aforementioned assemblies. In the library you'll create a class called tfsvcUtil that will contain a set of shared members (static in Visual C#®) to perform the heavy lifting with the Team Foundation Server APIs. As noted, before the add-in can do any work, it needs to connect to a valid Team Foundation Server. When using Team Explorer to connect to a box running Team Foundation Server, Team Explorer attempts to connect using your current credentials. If the credentials are invalid, Team Explorer displays a dialog asking for a valid user name and password. This is how the add-in should behave as well.

To do any work with Team Foundation Server, you must first acquire an instance of the root object, TeamFoundationServer. The APIs allow you to connect to your server in a couple of ways, depending on your need to reuse the same connection. In the case of the add-in, you'll be using a single reference for the lifetime of the add-in so you'll want to use the TeamFoundationServerFactory.GetServer method, specifically the overload that accepts both a server name and a reference to an object that implements ICredentialsProvider. Because you'll want to be able to provide alternate credentials if authentication fails, you should create an instance of UICredentialsProvider before calling GetServer. Once you call GetServer, you'll want to authenticate against the server by calling the Authenticate method on the TeamFoundationServer instance returned from GetServer. Assuming you are connected, you'll need to acquire a reference to the Team Foundation Server Version Control service.

As mentioned earlier, Microsoft designed Team Foundation Server to be extensible. You can see this in the way the services have been factored on the server. Team Foundation Server provides a set of core services around version control, work item tracking, and team builds. Each core service has its own set of classes that you use when you want to interact with a particular feature. The top-level object you use to work with the Version Control service is VersionControlServer class. You retrieve an instance of this type by calling the GetService method on a valid TeamFoundationServer reference. Figure 2 lists the code necessary to do everything just discussed. There you'll also find some basic error handling and some additional class level variables that you'll use later.

Figure 2 Connect and Authenticate

' Team Foundation Server Version Control Utilities
Public Class tfsvcUtil
  Private Shared serverValidated As Boolean = False
  Private Shared m_tfs As TeamFoundationServer = Nothing
  Private Shared m_tfsVCS As VersionControlServer = Nothing
  Private Shared m_userWorkspace As Workspace = Nothing
  Private Shared m_serverName As String = Nothing

  Private Const MSG_UNEXPECTED As String = _
    "Unexpected exception in tfsUtil.{0}."

  Public Shared ReadOnly Property IsServerReady() As Boolean
    Get
      Return serverValidated
    End Get
  End Property

  Public Shared Sub Connect(ByVal fqServerName As String, _
      ByVal showLoginUI As Boolean)
    Try
      m_serverName = fqServerName
      If showLoginUI Then
        Dim icp As ICredentialsProvider = New UICredentialsProvider
        m_tfs = TeamFoundationServerFactory.GetServer( _
          m_serverName, icp)
      Else
        m_tfs = TeamFoundationServerFactory.GetServer(m_serverName)
      End If

      m_tfs.Authenticate()

      m_tfsVCS = CType(m_tfs.GetService( _
        GetType(VersionControlServer)), VersionControlServer)

      serverValidated = True
    Catch ex As Exception
      Throw New tfsUtilException( _
        String.Format(MSG_UNEXPECTED, "Connect"), ex, m_serverName)
    End Try
  End Sub

  Public Shared Sub Connect(ByVal serverName As String, _
    ByVal protocol As String, ByVal port As Integer, _
    ByVal showLoginUI As Boolean)

    m_serverName = String.Format("{0}://{1}:{2}", _ 
      protocol, serverName, port)

    tfsvcUtil.Connect(m_serverName, showLoginUI)
  End Sub
End Class

If you examine the code in Figure 2, you'll note there are two versions of the Connect method. One requires that the Team Foundation Server name be in a fully qualified format that includes the protocol, server name, and port. The other version accepts the server connection data as individual items. Both versions expose a parameter that specifies whether the Team Foundation Server should show the login UI in cases where there is an authentication failure. This allows you to reuse the code in situations where you want it to fail if the provided credentials are not satisfactory.

Once you can connect to the server, the next decision is to figure out where you want to put your documents. In order to put documents under version control, a Team Foundation Server needs to have at least one Team Project created with version control enabled. Currently, Microsoft does not provide an API to create a Team Project, so this is something you must do manually before attempting to put documents under version control. Your team project's root trunk will be named $/%TeamProjectName%. From there you can define any number of folders and subfolders. The VersionControlServer object has a GetTeamProject method that you can use to access the ServerItem property to get the correct string representing the root. You should be aware of one thing. If you create a Team Project without defining the source control folder, a valid option when using the New Team Project wizard, you will receive a Microsoft.TeamFoundation.VersionControl.Client.VersionControlException exception when you call GetTeamProject. The string returned from the ServerItem property provides the base to build your folder structure for your documents.

Connecting to the server is only one part of the equation. In order to move documents in and out of the server, the client machine needs some configuration also. As mentioned earlier, Team Foundation Server Version Control uses the concept of a workspace to manage the client portion of version control. If you want to check a file in, check it out, or even check if a file is under version control, you need a workspace. As noted, the first time you use the Team Explorer and work with files under version control, it will create a default workspace for you using your machine name. You manage your workspaces from within Visual Studio 2005 via File | Source Control | Workspaces. However, the add-in can't count on the workspace having been created, so you'll need to add some code to create one if necessary.

Before you create a workspace, you'll want to check to see if one already exists and whether the location of the document you want to add to version control is already mapped to the active Team Project. You start with the Workstation class and use the Workstation object to obtain the WorkspaceInfo instance for the document you wish to add to source code control.

' Get workstation object
Dim userWorkstation As Workstation = Workstation.Current

' We need the path of the of the document:
Dim docPath As String = Environment.GetFolderPath( _ 
  Environment.SpecialFolder.MyDocuments) & "\Test.doc"
Dim docWSInfo As WorkspaceInfo = _
  userWorkstation.GetLocalWorkspaceInfo(docPath)

If the document's path is mapped to a server path, then the call to GetLocalWorkspaceInfo will return a hydrated instance; otherwise, it will be Nothing. You need to understand a key issue with workspaces and folder mappings. When you check to see if a mapping exists, GetLocalWorkspaceInfo isn't looking for an exact match; partial matches are accepted. If, for example, you have a path of C:\Docs\Specs\MySpec.doc and a folder mapping exists for C:\Docs\Specs\, a WorkspaceInfo instance will be returned as expected. However, if a mapping exists for C:\Docs, the API returns a valid instance even if a mapping does not exist for C:\Docs\Specs\. In general, this implicit association is what you want. Team Foundation Server will map recursively file system subfolders of an existing mapping to subfolders in the version control repository. If you don't have a mapping, create a one by using the CreateMapping method of the Workspace object. Getting a Workspace when you have a valid WorkspaceInfo object is simply a matter of calling GetWorkspace on the VersionControlServer instance.

Documents under Version Control

At this point, the core functions of the add-in-checking in and checking out Word documents-are ready to run. Assuming you can log in to a machine running Team Foundation Server, access a valid Team Project and a local Workspace, you can attempt to put a file under version control. You'll find the relevant methods you need to do your work as members of the Workspace class. For the add-in, you'll need to be able call PendAdd, PendEdit, and CheckIn.

PendAdd is the method to use the first time you place a document under version control. If you examine the API's documentation, you'll see there are six overloads. You have the flexibility to queue a single item or a set of items at once. The method has versions that support recursion as well as the ability to adjust the lock level. For the add-in, you'll be checking in one document at a time, so the first overload that accepts a file name is sufficient. When you call PendAdd, it returns an integer representing the number of items queued for check in. To perform the actual checking, you need to query the Workspace for a list of pending changes using the GetPendingChanges method. Microsoft overloaded this method with nine versions. For the add-in, you'll use the version that accepts a string as input. Because you're passing in a single file path, PendAdd should return 1. When you call GetPendingChanges, it should return a PendingChange array with a single element. You need to be careful with using other versions of GetPendingChanges. The reason is that you could attempt to check in files that are not ready, for example code you're editing in Visual Studio 2005.

Once you have the array of pending changes, you can pass the PendingChange array instance and any check-in comments to the CheckIn method to perform the actual check-in operation. If the method is successful, Team Foundation Server returns an integer representing the Changeset number for the check-in. A changeset is a set of committed modifications to one or more items that are stored in the Team Foundation Server version control repository. It can also contain work item data, check-in notes, and policy override information. As part of the check-in process, the Team Foundation Server client API will turn the read-only bit on for the document. Figure 3 lists two methods from the tfsvcUtil class that perform the initial PendAdd and then the CheckIn. As you examine the code, you should see the extra code necessary to get the Workspace. In addition, there's a helper method GetDefaultWorkspace that will get or create the default Workspace if necessary. This code assumes you've successfully executed the Connect method.

Figure 3 Add Document to Version Control

Public Shared Function AddDocumentToVCR( _
  ByVal docPath As String, _
  ByVal serverPath As String) As Boolean

  If serverValidated Then
    ' We need the Workstation
    Dim userWorkstation As Workstation = Workstation.Current
    ' We need the Workspace
    Dim docWSInfo As WorkspaceInfo = _
      userWorkstation.GetLocalWorkspaceInfo(docPath)

    If docWSInfo Is Nothing Then
      ' there's no existing mapping
      ' so let's get the default workspace
      m_userWorkspace = GetDefaultWorkspace(userWorkstation)
    Else
      m_userWorkspace = m_tfsVCS.GetWorkspace(docWSInfo)
    End If

      ' Now the question is does the workspace 
      ' we're working with have a mapping
      Dim docWorkingFolder As New WorkingFolder(serverPath, _
        IO.Path.GetDirectoryName(docPath))
      ' It appears that CreateMapping is happy to let us try and 
      ' create a mapping even if an entry already exists
      ' This eliminates the need to search
      m_userWorkspace.CreateMapping(docWorkingFolder)

    Return m_userWorkspace.PendAdd(docPath) = 1
  Else
    Throw New tfsUtilException(MSG_SERVER_NOT_VALIDATED)
  End If
End Function

Public Shared Function CheckInDocument(ByVal docPath As String, _
  ByVal comment As String) As Integer

  If serverValidated Then
    If m_userWorkspace Is Nothing Then
      Dim userWorkstation As Workstation = Workstation.Current
      Dim docWSInfo As WorkspaceInfo = _
        userWorkstation.GetLocalWorkspaceInfo(docPath)
      m_userWorkspace = m_tfsVCS.GetWorkspace(docWSInfo)
    End If

    Dim pc As PendingChange() = m_userWorkspace.GetPendingChanges(docPath)

    If pc IsNot Nothing AndAlso pc.Length > 0 Then
      Return m_userWorkspace.CheckIn(pc, comment)
    Else
      Return -1
    End If
  Else
    Throw New tfsUtilException(MSG_SERVER_NOT_VALIDATED)
  End If
End Function
Private Shared Function GetDefaultWorkspace( _
    ByVal userWorkstation As Workstation) As Workspace

  ' Get the users default's workspace
  Dim defaultWorkspaceInfo As WorkspaceInfo = _
    userWorkstation.GetLocalWorkspaceInfo(m_tfsVCS, _
      Environment.MachineName, m_tfs.AuthenticatedUserName)

  Dim defaultWorkspace As Workspace = Nothing
  If defaultWorkspaceInfo Is Nothing Then
    ' Need to create it
    defaultWorkspace = m_tfsVCS.CreateWorkspace( _
      Environment.MachineName, m_tfs.AuthenticatedUserName, _
      "Auto-generated by tfsUtil")
  Else
    ' Get other 
    defaultWorkspace = m_tfsVCS.GetWorkspace(defaultWorkspaceInfo)
  End If

  Return defaultWorkspace

End Function

You check out a file for edit by calling PendEdit on the Workspace object. Microsoft overloaded this method as well, providing five versions. As before, the version you'll use is the one that accepts a single file name. You'll find the code for this operation in the download that accompanies this column at msdn.microsoft.com/msdnmag/code07.aspx. It's almost an exact copy of PendAdd.

Fit and Finish

The core code necessary for the add-in to work with Team Foundation Server is nearly ready, but there are a few more things left to do:

  • oProvide the Team Foundation Server with information such as server name, protocol, and port.
  • oSpecify the Team Project and server path.
  • oSpecify the document and comments as part of a check-in operation.
  • oBuild a Word 2003 application add-in to hook all of the forms and code into a command bar, and handle the Word document related events.

These items require a GUI if the code presented earlier is going to work from an add-in hosted in Word 2003. Unfortunately, even though Microsoft wrote the Team Explorer functions and UI for version control in managed code, the actual dialog boxes used for specifying Team Foundation Server information or adding a document to version control are not exposed. In the code download, you'll find three Windows Forms that satisfy the aforementioned requirements.

The first form you need is frmAddTFS. If you look at Figure 4, you'll see it mimics the Microsoft dialog. It lets you specify the sever name, protocol, and port. The form contains only a small amount of code. You could extend it to look up machines running Team Foundation Server via Active Directory®, check for SSL and more.

Figure 4 The Add Team Foundation Server Form

Figure 4** The Add Team Foundation Server Form **

The second form, frmAddtoSCC (displayed in Figure 5), is more complicated in design and implementation. To mimic the Microsoft version, I added some additional helper methods to the tfsvcUtil class: GetAllProjects and GetFolders. GetAllProjects does what it says in one line of code. GetFolders uses the GetItems method of the VersionControlServer object to get a single level of folders for the provided path. As you expand a node, the form calls this method to populate the next level of folders, allowing you to specify where you want your document stored in the repository. There's a bit of extra code you can poke around in that loads the tree view control, handles support for adding sub folders, and so forth.

Figure 5 The Add Document to Version Control Form

Figure 5** The Add Document to Version Control Form **(Click the image for a larger view)

The frmCheckIn (the dialog you saw in Figure 1) shows you what document you're checking in and provides a textbox for comments. If you've used the Team Explorer version, you'll note that this is much simpler. In a future column, I'll enhance this form to support work items and check-in notes.

Finally, a few notes related to the Word add-in. Although I'm not covering all the gory details of the add-in, I want to point out a couple of things. First, you'll find three additional helper methods in the downloadable version of tfsvcUtil: Disconnect, GetDocumentVCStatus, and IsDocumentUnderVersionControl. Disconnect is a cleanup method so you can change machines running Team Foundation Server. GetDocumentVCStatus is used by the add-in to get the version control status for the active document to keep the commandbar buttons in sync. IsDocumentUnderVersionControl checks to see if the document exists in version control, if you've mapped the folder that contains the document into a workspace.

Second, the Team Foundation Server client API wants an exclusive lock on the document to perform a check-in. In order to make this work, the add-in must close the document, perform the check-in, and then reopen the document.

Conclusion

Visual Studio Team System provides extensibility options for almost every area of the product, and the Version Control service is a shining example. Using the rich APIs, we created a simple add-in that lets you put Word documents into the Team Foundation Server version control repository where they can be easily shared and managed.

Send your questions and comments for Brian to  mmvsts@microsoft.com.

Brian A. Randell is a senior consultant with MCW Technologies LLC. Brian spends his time speaking, teaching, and writing about Microsoft technologies. He is the author of Pluralsight's Applied Team System course and is a Microsoft MVP. Contact Brian via his blog at mcw­tech.com/cs/blogs/brianr.