Here in Spartez we are currently working hard on developing TFS4JIRA – our great app integrating TFS and JIRA. It is an indispensable tool especially (but not only!) for teams that use JIRA as their issue tracker and store their repository in TFS. They usually want to include JIRA issue key in every check-in message so that this check-in will be linked and shown in JIRA issue view (like on the image below).
The problem appeared that there was no mechanism to ensure that developer included JIRA issue key in his check-in comment. If there is no such key, then obviously such a check-in is not shown in JIRA. We decided to create an open source TFS check-in policy plugin that will enforce developers to include at least one valid JIRA issue key. It's already available on theVisual Studio Gallery.
How custom TFS check-in policy works internally
Custom check-in policy is a plugin to Visual Studio installed by each developer separately but its configuration is stored globally in TFS Server. To see what it means, let's first take a look at the diagram below:
Let's assume the following:
- Team Lead installed and configured custom policy in his Visual Studio.
- Developer A installed custom policy plugin in his Visual Studio.
- Developer B didn't install custom policy plugin in his Visual Studio.
In this case, Team Lead and Developer A will have custom policy installed and configured properly (the settings are stored in TFS server) but Developer B won't use this custom policy at all (so he'll successfully check-in even if his code doesn't meet the policy requirements). That's why installing custom policy plugin should be a part of onboarding process for each developer.
Creating custom check-in policy
A good place to start developing custom check-in policy is this tutorial (remember to install Visual Studio SDK before you dive in). It explains the basics well, but during the development of our custom check-in policy, we had to solve some interesting problems that weren't covered there:
1. Custom check-in policy created for one version of Visual Studio will not work with other versions. That's because the version of assembly Microsoft.TeamFoundation.VersionControl.Client referenced from your plugin has to match the version of target Visual Studio. That's why we've created separate packages for VS 2010, 2012, and 2013 that reference the Core project with business logic. Taking into account the fact that for each VS you need a separate VSIX package, the structure of our plugin looks as follows:
Plugin projects are just very thin wrappers for Core. The code looks exactly the same for VS 2010, 2012, and 2013 so we took advantage from Add as Link functionality in Visual Studio to have only one copy of source files.
2. Initially, we configured assembly name for projects 'Plugin for VS 2010', 'Plugin for VS 2012' and 'Plugin for VS 2013' in the following way:
- 'Plugin for VS 2010' produced an assembly called CheckinPolicyVS2010.dll;
- 'Plugin for VS 2012' produced an assembly called CheckinPolicyVS2012.dll;
- 'Plugin for VS 2013' produced an assembly called CheckinPolicyVS2013.dll.
Now let's assume that the Team Leader has installed and configured our custom check-in policy using Visual Studio 2010. The problem is that TFS stores assembly name in which custom check-in policy is defined (in this case, CheckinPolicyVS2010.dll) in central TFS server. So the custom policy will work for team members using VS 2010 but not for those using VS 2012 and VS 2013 (because they don't have CheckinPolicyVS2010.dll). The solution is to set the same assembly name for all three mentioned projects.
3. Our custom policy connects with JIRA so we need to store somewhere connection credentials. Of course, central TFS server is not a good place for such data so we decided to use Data Protection API (DPAPI) to encrypt them and store locally for each developer. We used the great EasySec nuget package to work in DPAPI. It gives you a very convinient API to encrypt and decrypt data:
var encryptor = new EasySec.Encryption.DPAPIEncryptor(); var stringToEncrypt = "stringToEncrypt"; var encryptedString = encryptor.Encrypt(stringToEncrypt); var decryptedString = encryptor.Decrypt(encryptedString);
You can find the source code for our plugin on Bitbucket. Feel free to comment and contribute.