Team System
Check-In Notes and Policies
Brian A. Randell
Code download available at: TeamSystem2008_Launch.exe(150 KB)
Contents
Check-In Notes
Check-In Policies
Check-In Conflicts
Back in the January 2007 installment of this column, I started a discussion on using the Team Foundation Server (TFS) version control APIs from a Microsoft® Word 2003 add-in. Here I am, more than a year later, with the fifth and final part dedicated to the version control APIs and the Word add-in. (I really didn't think it would take this many installments.)
You may recall from earlier columns that the add-in currently supports check-in, check-out, undo pending changes, and the ability to associate work items with a check-in. In my most recent column, I examined check-in notes and check-in policies. This month, I will show you how to add check-in notes and check-in policy support to the Word add-in. In addition, you'll learn about how to deal with check-in conflicts. And if you haven't read the rest of this series (listed in the "First Four Parts of This Series" sidebar), be sure to check them out.
Check-In Notes
Check-in notes are free-form text fields that you can associate with a check-in. You can mark check-in notes as mandatory and control the order in which they appear in the Visual Studio® check-in dialog. In order to mimic this behavior in the Word add-in, you should get the list of check-in notes for the active team project, sort the list, and display a label and textbox for each check-in note.
The VersionControlServer object exposes the GetCheckinNoteDefinitions method, which accepts an array of team project names and returns an array of CheckinNoteFieldDefinition objects. Each CheckinNoteFieldDefinition instance exposes properties for check-in note name, display order, whether it's required, and a server item string. GetCheckinNoteDefinitions returns the list of items in what appears to be a seemingly random order. In my tests, I found that notes are returned sorted by check-in note name and grouped by team project name no matter what order the team project references are passed into the method. Thus, assuming there are two team projects defined, one called MSF-Agile and one called MSF-CMMI with the default set of notes, the results would appear as shown in Figure 1. And if you created a new check-in note called Build Master and added it to the MSF-CMMI team project, it would appear first in the list.
Figure 1 Results from Calling GetCheckinNoteDefinitions
Check-In Note Name | Team Project |
---|---|
Code Reviewer | MSF-Agile |
Code Reviewer | MSF-CMMI |
Performance Reviewer | MSF-Agile |
Performance Reviewer | MSF-CMMI |
Security Reviewer | MSF-Agile |
Security Reviewer | MSF-CMMI |
The tfsVCUtil class, created for the Word add-in, has two new shared methods that you can use to retrieve check-in notes: GetAllCheckInNotesByTeamProjectName and GetAllCheckInNotesByItemLocalPath. Using either of these methods, you can get the unmodified list of CheckinNoteFieldDefinitions, if any, for the team project.
Supporting check-in notes on the check-in dialog requires that you add an extra toggle button under the existing work items button, an extra panel to act as a container, and a label and textbox for each check-in note. (The code download provides a simple implementation.) However, adding support for check-in notes to the check-in dialog is just one piece of the puzzle. You also need to add code to enforce required notes. Check-in policies and conflict detection require that you make additional changes to how the check-in dialog and supporting code in the tfsVCUtil class work.
Check-In Policies
First Four Parts of This Series
- Team Foundation Server Version Control (January 2007)
- Work Item Tracking (April 2007)
- Work Items and Undo Support (September 2007)
- Custom Check-in Policies (November 2007)
Just as with check-in notes, the first order of business for supporting check-in policies is figuring out what policies are enabled for the active team project. The second order of business is deciding how to process check-in policies. Because Microsoft designed check-in policies primarily for developers checking in code from Visual Studio, two of the three built-in policies that ship with TFS 2005 don't work correctly when used to a validate a check-in for a single Word document. Thus it's necessary that the add-in only process errors from check-in policies that make sense in the context of a Word document.
It turns out there is only one way to solve this problem. You have the add-in evaluate all enabled policies and ignore the errors generated by those policies that don't make sense. On initial inspection of how check-in policies work, it looks as if a possible alternative is to have the add-in run the evaluation process manually against those policies that should work. But it turns out this is not possible.
By default, check-in policies and check-in notes are not evaluated as part of the check-in process when using custom code. In order to mimic the behavior of Visual Studio, it's up to your application code to ensure that required check-in notes are provided and that there are no policy violations—or, if there are violations, that policy override information is provided.
To make life a bit easier, the Workspace class exposes an EvaluateCheckin method. You use this to check for conditions that might cause a check-in to fail. Here's the method prototype from the MSDN® documentation:
Public Function EvaluateCheckin ( _ options As CheckinEvaluationOptions, _ allChanges As PendingChange(), _ changes As PendingChange(), _ comment As String, _ checkinNote As CheckinNote, _ workItemChanges As WorkItemCheckinInfo() _ ) As CheckinEvaluationResult
The method first wants to know what you would like to evaluate. You provide this information by passing the correct enumeration value via the options parameters. You can have the method look for check-in conflicts, required check-in notes, policies violations, or all three.
The method then requires that you pass in an array of PendingChange objects for the second and third parameters. Why two sets? In Visual Studio, for example, you could have ten modified files in the current workspace, but the user can choose to check only three files for check-in. Thus, Visual Studio passes all ten files via the allChanges parameters and the three checked files via the changes parameter. The method provides this information for the enabled policy assemblies. In the case of the Word add-in, you pass the same array for both parameters.
For the fourth parameter, you pass in the comment text, if any, provided by the user doing the check-in. To pass check-in notes, you create an instance of the type CheckInNote, which accepts an array of CheckinNoteFieldValue instances, and pass this as the fifth parameter. Finally, you pass an array of WorkItemCheckinInfo instances for the last parameter. You can pass nothing for any of the last three parameters.
Once you've called EvaluateCheckin, you receive a CheckinEvaluationResult instance. This object exposes the results via three array properties: Conflicts, NoteFailures, and PolicyFailures. If there are no values for a particular result set, EvaluateCheckin returns a zero-length array. It is up to you to enumerate each array and process the results. Note that even if EvaluateCheckin returns failures for check-in notes or policies, this does not block a check-in. You must implement logic in your code to prevent a check-in when these rules aren't satisfied. However, if conflicts exist, TFS does block the check-in. To take advantage of this method for the Word add-in, expose EvaluateCheckin via the tfsVCUtil class using a shared method called PreValidateCheckIn:
Public Shared Function PreValidateCheckIn( _ ByVal docPath As String, _ ByVal options As CheckinEvaluationOptions, _ ByVal comments As String, ByVal notes As CheckinNote, _ ByVal workitems() As WorkItemCheckinInfo) _ As CheckinEvaluationResult Dim pc As PendingChange() = _ m_userWorkspace.GetPendingChanges(docPath) Return m_userWorkspace.EvaluateCheckin(options, _ pc, pc, comments, notes, workitems) End Function
As you can see from the code listing, PreValidateCheckIn mimics the signature of EvaluateCheckin with the additional parameter for the document to evaluate. Once you call this method from the check-in dialog, you enumerate the arrays for the items you requested to be validated, looking for issues and, if necessary, providing a visual indication to the user of what's wrong.
With regard to policy violations, you have to decide what policies make sense for your application. Naturally, the built-in policies offered by Microsoft, as well as many of those available in the wild, are biased to the check-in experience in Visual Studio. In the case of the Word add-in, only the work-item-required policy makes sense when examining the three built-in policies in the 2005 edition of Visual Studio Team System. Aside from the built-in policies, the Word add-in solution needs to worry only about policy errors raised by relevant assemblies. Using some out-of-band mechanism, such as name/value pairs stored in the add-in's configuration file, the code knows which policies should be respected. Then, at run time, the code examines the policy errors collection and ignores all errors not on the list. One way you can do this is to cast the IPolicyEvaluation reference returned from the PolicyFailure instance's Policy property to an instance of IPolicyDefinition. Then, IPolicyDefinition exposes a Type property that can be used to identify the policy source. Here's a simple example:
Dim cer As CheckinEvaluationResult = {Call to server elided} Dim i As IPolicyDefinition = Nothing For Each pf As PolicyFailure In cer.PolicyFailures i = DirectCast(pf.Policy, IPolicyDefinition) If i.Type = "Work Items" Then ' process this policy failure End If Next
There is a downside to this approach. As part of the Visual Studio 2005 Team Foundation Server Power Tools, Microsoft added four additional check-in policies (see msdn2.microsoft.com/vstudio/aa718351). One policy in particular, the custom path policy, uses a bit of indirection via delegation to run another policy, such as the work item policy, when the pending changes being provided match a particular regular expression. Teams use this policy to enforce stricter check-in rules on only part of their code repository while another section can be less restrictive. Thus, if this policy is used, the previously listed code sees the type of the custom path policy, not the actual policy executing—for example, the work items policy.
You might try to do what Visual Studio does, but that doesn't work. While you can discover the list of enabled policies by calling GetCheckinPolicies off of a valid TeamProject instance, there's no (reasonable) way to acquire a valid IPendingCheckin reference that is required as a parameter to IPolicyEvaluation's Initialize method. Thus, to deal with the custom path policy, you need to examine the Description property returned from IPolicyDefinition. The authors of the policy chose to store the Type value from the delegated policy here. Thus, the refactored code looks like this:
Dim cer As CheckinEvaluationResult = '{Call to server elided} Dim i As IPolicyDefinition = Nothing Dim processPolicy As Boolean = False For Each pf As PolicyFailure In cer.PolicyFailures i = DirectCast(pf.Policy, IPolicyDefinition) processPolicy = (i.Type = "Work Items") If Not processPolicy AndAlso _ i.Type = "Custom Path Policy" Then processPolicy = (i.Description = "Work Items") End If If processPolicy Then ' process this policy failure End If
This version does a basic job of handling both the standard Work Items policy and a delegated version using the Custom Path Policy. Your own implementation may be more complex and may require a more elaborate solution, but now you are equipped with the tools to solve the problem.
I've shown you how to get the list of check-in notes so you can present a UI. If you look at EvaluateCheckin and the wrapper method PreValidateCheckIn, you'll see that in order to validate check-in notes, you need to create and then pass an instance of CheckinNote. After you execute EvaluateCheckin, either directly or via the wrapper, you need to examine the NoteFailures collection. Each NoteFailure instance exposes a Definition property that represents the CheckinNoteFieldDefinitions for the check-in note. From there, you can easily access the Name property to know which check-in notes to highlight in your UI. The Message property is a generic message stating "A value must be specified for the check-in note." As mentioned earlier, neither policy violations nor missing check-in notes will block a check-in executed from code. It's up to you to ensure that things are, in fact, satisfactory before you call your check-in code.
Check-In Conflicts
Check-in conflicts, on the other hand, do block your check-in. You control the default check-out behavior for artifacts from the version control repository using the File Types dialog (shown in Figure 2), which is accessible via Team | Team Foundation Server Settings | Source Control File Types. Using this dialog, you control which files support file merging and thus simultaneous check out. For example, you can see in Figure 2 that Microsoft Office Files have file merging disabled. As a result, when you perform a check out using the Source Control explorer or the APIs, you get an exclusive check out. To adjust the multiple check-out and file merging settings for a set of file extensions, simply click the Edit button to open the Edit File Types dialog.
Figure 2** Configured File Types for Version Control **(Click the image for a larger view)
If file merging is enabled for a particular file extension and a conflict occurs at check-in, Visual Studio, by default, displays the Team Explorer Merge Tool. Microsoft made this a configurable option on a per developer basis. Within Visual Studio, you can go to Tools | Options, and then under the Source Control node, select the Visual Studio Team Foundation Server node. On the options page, you click Configure User Tools and then Add. You now have full control over how a particular file extension or set of extensions are handled (see Figure 3).
Figure 3** Configuring a New Merge Tool for a File Extension **(Click the image for a larger view)
Thus, the default behavior is for an exclusive check out when you use the Word add-in to check out a Word document. In theory, this eliminates the need to deal with conflicts in the add-in code. However, there's an issue with this default behavior that catches many new TFS users off guard. By default, if you request a checkout using TFS 2005 via Visual Studio or the APIs, the check out occurs against the current local file in your workspace—even if it's not the most current version of the file stored in TFS. The underlying code does not perform a get latest. So it is possible for you to check out a file via the Word add-in, make changes, and then have the check-in fail when you are done.
You can solve this problem in a number of ways. In fact, Microsoft has listened to customer feedback, and in TFS 2008 you can enable Get Latest on Check out on a per Team Project basis; you can also control this feature at your local installation. Microsoft also updated the PendEdit API to support this feature. However, for the add-in working against TFS 2005, you need to modify the existing check out code to manually perform a get latest before check out. To do this, you need to add a line of code that calls Workspace.Get with the correct parameters before the call to PendEdit. The code in Figure 4 does just that.
Figure 4 Code to Get Latest before Check Out
Public Shared Function CheckOutDocument( _ ByVal docPath As String) As Boolean If serverValidated Then If m_userWorkspace Is Nothing Then ' We need the Workstation Dim userWorkstation As Workstation = Workstation.Current ' We need the Workspace Dim docWSInfo As WorkspaceInfo = _ userWorkstation.GetLocalWorkspaceInfo(docPath) m_userWorkspace = m_tfsVCS.GetWorkspace(docWSInfo) End If Dim status As GetStatus = _ m_userWorkspace.Get(New String() {docPath}, _ VersionSpec.Latest, RecursionType.None, _ GetOptions.GetAll Or GetOptions.Overwrite) Dim statusCount As Integer = status.NumConflicts + _ status.NumFailures + status.NumOperations + _ status.NumWarnings If statusCount = 1 Then If m_userWorkspace.PendEdit(docPath) = 1 Then Return True Else Return False End If Else Throw New tfsUtilException( _ String.Format(MSG_UNEXPECTED_GETLATEST_CONFLICT, _ "Failures count: " & status.NumFailures)) End If Else Throw New tfsUtilException(MSG_SERVER_NOT_VALIDATED) End If End Function
The Get command returns an instance of a GetStatus object, which exposes a set of properties that provide statistics related to the number of conflicts, failures, operations, and warnings. You can use this data to determine if the Get command worked as expected.
With the add-in using this version of CheckOutDocument, you shouldn't run into any conflicts. But if you're still concerned about conflicts, just check the Conflicts array returned by EvaluateCheckin. In case there are conflicts, you need to provide an experience that lets the user pick the so-called "winner." If you want the winner to be the person attempting to perform the current check-in, you need to use the QueryConflicts method of the Workspace object. This returns an array of Conflict objects. You then set the Resolution property of the Conflict instance to Resolution.AcceptYours and pass the object to the Workspace.ResolveConflict method. Providing a richer experience than this will require serious thought and additional investment in both time and code.
Now, provided you have all five parts of this series on hand, you should have all the tools and information necessary to code up your own rich custom check-in experiences for your applications. Going forward, you'll need to review the updated APIs provided with Team Foundation Server 2008 and get ready for even more enhancements in the "Rosario" release coming sometime in the future.
A special thanks to Martin Woodward of Teamprise and all the folks on the Visual Studio Team System team who have helped with this and the previous columns in this series.
Send your questions and comments to mmvsts@microsoft.com.
Brian A. Randell is a Senior Consultant with MCW Technologies LLC. He spends his time speaking, teaching, and writing about Microsoft technologies. Brian is the author of Pluralsight's Applied Team System course and is a Microsoft MVP. You can contact Brian via his blog at mcwtech.com/cs/blogs/brianr.