Welcome to MSDN Blogs Sign in | Join | Help

How is a policy installed?  Where is it placed?

Each client where the policy will be used will need to install and 'register' the policy.  During the evaluation (or definition), the application needs to know where to find the policy and needs to know which policies are available on the client.

The list of policies is needed so that the admin (dev lead) can choose which policies to install; the user also needs to be able to 'lookup' a policy which the server is aware of.

The policy locations are located in the registry:

HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\VisualStudio\8.0\TeamFoundation\SourceControl\Checkin Policies

or

HKEY_CURRENT_USER\SOFTWARE\Microsoft\VisualStudio\8.0\TeamFoundation\SourceControl\Checkin Policies

The list of policies is a concatenation of the HKCU and HKLM defined policies.

The value name of this entry is the name of the assembly without the extension.  The value data is the full local path name (with extension).

If a client does not have this policy installed, then the InstallationInstructions will be displayed to the user.  These instructions could then display a link which could install and update the registry appropriately.

 

There have been questions about the IPolicyDefinition interface around the following parameters:

-          Type

-          TypeDefinition

-          Definition

The main question I get is ‘What is the difference between TypeDefinition and
Definition and what should they be?’

Some developers will make these the same; that will work for some policies.  For others, making them unique will provide more information to your audience.

There is a brief description of each:

-          Type – this string is the type of the policy; intended to be a short name.

-          TypeDescription – this string is a more verbose description; this is intended to be a longer description.

-          Description – This is the description of a particular instance of the policy which is being created.

The Type and TypeDescription are fairly straight forward, so I will dig deeper into the Description value.

Let me first start by saying that you may create more than one instance of the same policy Type for a single team project.  At first, this may not be clear – the reason for allowing this; for example, it would not make too much sense to add two policies which require workitems to be associated for a checkin.  However, there are circumstances where this does make sense.  Take the sample tabs policy I blogged about in the past.  It is possible that you want to configure this policy numerous times; one instance could check for tabs in Visual Basic files (.vb, .vbproj, …); another could be added for C# files (.cs, .csproj).

Taking the above example, we would have two instances of this policy serialized on the server – one for VB and one for C#.  When displaying these in the Source Control properties, we will need to differentiate the two – this is where the Description is used.  The Description’s could then be:

-          “Check for tabs in files:  .vb, .vbproj”

-          “Check for tabs in files:  .cs, .csproj”

Another example may help clarify a need for this; take the tabs policy and update it so that it did not, by default, search for tabs, but instead, allowed the person configuring the policy to look for specific patterns in the code (expected patterns to find and patterns which should not exist in the code).  Now, this policy could be setup to look for tabs and another instance of this policy could look to ensure the appropriate copyright comment is in each file.

First off, my apologies in taking so long to post the next step in creating the policy.

 

Down below the framework for policy creation is listed; this template could be used whenever creating a new policy.  Let start coding some more:

 

 

Let us update the constructor:

public NoTabsPolicy()

{

    interestedExtensions = new SortedList();

}

 

* This SortedList contains the files extensions we are interested in; certainly we do not want to run this on every file type VS supports.

 

 

The IPolicyDefinition is sufficient for now; we will add a UI to the Edit method later which will allow the user to customize this policy.

 

 

Now to the IPolicyEvaluation…

 

We need to update the Initialize method to keep a hold of the IPendingCheckin and to add an event handler.

 

public void Initialize(IPendingCheckin pendingCheckin)

{

    pendingCheckinsDialog = pendingCheckin;

 

    pendingCheckin.PendingChanges.CheckedPendingChangesChanged += new EventHandler(pendingCheckin_CheckedPendingChangesChanged);

}

 

This event will notify our policy when items (files) are selected/unselected from the pending checkins toolwindow (or checkin dialog); when files are selected/unselected, we will re-run the policy to only search for tabs in those specific files (and the files matching the extensions we are interested in).

 

 

Update the Dispose method:

 

public void Dispose()

{

    pendingCheckinsDialog.PendingChanges.CheckedPendingChangesChanged -= new EventHandler(pendingCheckin_CheckedPendingChangesChanged);

 

    policyDisposed = true;

    PolicyStateChanged = null;

 

    for (int i = 0; i < fileWatchers.Count; i++)

    {

        fileWatchers[i] = null;

    }

    fileWatchers = null;

}

 

Notice that we remove the event handler – we do not want to receive events when we are disposed and not cleaned up.  Also, notice the cleanup of the fileWatchers elements – for each file checked in the pending checkins (and matching the extensions we are interested in) a FileWatcher is created; this file watcher notifies us if the file changes.  If the file does change, we need to check to see if file contains tabs again.  Once complete, we can tell VS of the policy failures or clear existing failures.  This is useful, so the user can make changes to files and see the policy failures disappear as the problems are addressed.

 

 

Next, lets make the evaluate methods actually do something useful:

public PolicyFailure[] Evaluate()

{

    if (policyDisposed)

    {

        throw new ObjectDisposedException(Strings.policyType,   Strings.policyDisposedMessage);

    }

 

    ArrayList changes = new ArrayList();

    PendingChange[] checkedFiles = pendingCheckinsDialog.PendingChanges.CheckedPendingChanges;

 

    foreach (PendingChange change in checkedFiles)

    {

        if (InterestedFileExtension(change.LocalItem))

        {

            if ((change.ChangeType == ChangeType.Edit) || (change.ChangeType == ChangeType.Add))

            {

                if (DoesFileContainTabs(change.LocalItem))

                {

                    PolicyFailure failure = new PolicyFailure(change.ChangeType + "  :  " + change.LocalItem, this);

                    changes.Add(failure);

                 }

 

                 RegisterForFileEvent(change.LocalItem);

            }

        }

    }

 

    return (PolicyFailure[])changes.ToArray(typeof(PolicyFailure));

}   

 

First off, throw exception is we are disposed; we should never be disposed and a method call on the policy.  Next, we need to create a list of failures which we will return; I broke out the next few lines to make it easier to read; the FileContainedInAffectedPortfolioProject method verifies that the file in question is in a team project (formally, known as portfolio project) which has this policy installed.  It is possible to have two files checked out of the same solution where each file resides in a different team project (and those team projects may have different policies installed).

 

Next the InterestedFileExtension verifies that the file we are looking at contains a file extension we are interested in.

 

We are only interested in Edit and Add (basically, ignoring renames, deletes, branches, …).

 

Finally, lets see if the file does contains a tab.  If it does, create a new policy failure and add it to the ArrayList of policy failures.  The first value in the PolicyFailure constructor is the message the user will see in VS.  Also, whether the file contains tabs or not, we want to place a FileWatcher on it to see if it is updated later.

 

Return the array back to VS.

 

 

Code up the helper methods listed above:

private bool InterestedFileExtension(string filePathAndName)

{

    string ext = Path.GetExtension(filePathAndName);

 

    if (interestedExtensions.ContainsKey(ext))

    {

        return true;

    }

 

    return false;

}

 

private bool DoesFileContainTabs(string filePathAndName)

{

    try

    {

        StreamReader sr = new StreamReader(filePathAndName, true);

        string fileContents = sr.ReadToEnd();

        bool result = fileContents.Contains(Strings.tab);

        sr.Close();

 

        return result;

    }

    catch (Exception)

    {

        return false;

    }

}

 

 

Add the SortedList to class (in the serialize portion).

 

SortedList interestedExtensions;

 

 

Now, let us finish up with implementation of the eventing methods.

 

private void RegisterForFileEvent(string filePathName)

{

    if (policyDisposed)

    {

        return;

    }

 

    FileSystemWatcher watcher = new FileSystemWatcher(Path.GetDirectoryName(filePathName), Path.GetFileName(filePathName));

    watcher.IncludeSubdirectories = false;

    watcher.Changed += new FileSystemEventHandler(OnSourcesChanged);

    watcher.Renamed += new RenamedEventHandler(OnSourceRenamed);

    watcher.NotifyFilter = NotifyFilters.FileName | NotifyFilters.LastWrite;

    watcher.EnableRaisingEvents = true;

 

    if (fileWatchers.ContainsKey(filePathName))

    {

        return;

    }

 

    fileWatchers.Add(filePathName, watcher);

}

 

* You may be wondering why the Changed and Renamed are both added above; the reason is that Change is needed if the file is modified outside of VS; the renamed is needed for file which change in VS (VS actually writes file X to Y then does a rename on the save).

 

Handle the renamed event…

 

private void OnSourcesRenamed(object sender, RenamedEventArgs e)

{

    OnSourcesChanged();

}

 

Handle the Changed event…

 

private void OnSourcesChanged(object sender, FileSystemEventArgs e)

{

    OnSourcesChanged();

}

 

At this point, we know a file has been modified/saved, so lets just evaluate it again and send event back to VS.

 

private void OnSourcesChanged()

{

    PolicyFailure[] failures = Evaluate();

 

    OnPolicyStateChanged(failures);

}

 

Do the actual eventing back to VS.

 

protected virtual void OnPolicyStateChanged(PolicyFailure[] failures)

{

    PolicyStateChangedHandler handler = PolicyStateChanged;

    if (handler != null)

    {

        handler(this, new PolicyStateChangedEventArgs(failures, this));

    }

}

 

Here, we know that the file selection has changed in VS, so let re-evaluate the file again.

 

private void pendingCheckin_CheckedPendingChangesChanged(Object sender, EventArgs e)

{

    OnPolicyStateChanged(Evaluate());

}

 

 

Finally, update the [NonSerialized] section:

 

[NonSerialized]

private bool policyDisposed = false;

private IPendingCheckin pendingCheckinsDialog = null;

private Hashtable fileList = new Hashtable(StringComparer.InvariantCultureIgnoreCase);

private Hashtable fileWatchers = new Hashtable(StringComparer.InvariantCultureIgnoreCase);

 

public event PolicyStateChangedHandler PolicyStateChanged;

First Step – Create/Compile a policy

 

Basic setup:

We will need to create a ClassLibrary (I used a C# library for this example).  Once the project is created, we need to add a few references:  System.Windows.Forms and Microsoft.VisualStudio.Hatteras.Client.dll (name will be changing shortly).  Now that our references are setup, make sure your class implements the correct interfaces:

 

public class NoTabsPolicy : IPolicyDefinition, IPolicyEvaluation

 

One more quick class definition step to take; this class must be Serializable – the serialized object is stored on the server, so each time the client needs to use this policy, the policy is de-serialized.  The only exception to this is when it is being configured or installed; the object is constructed and when the user saves the policy the serialized object is sent to server.  Make class serializable:

 

[Serializable]

public class NoTabsPolicy : IPolicyDefinition, IPolicyEvaluation

 

For this example, I am going to store my strings in an internal class – these should be pulled out and placed in a resource file (that is skipped here to focus on policy creation).  Here are the strings defined:

 

public class Strings

{

       public static string policyType = "No Tabs Policy";

       public static string policyDescription = "This policy ensure that there are no tabs embedded into the source control of particular file types.";

       public static string policyInstallationInstructions = "Please see http://instructions";

       public static string policyTypeDescription = "Type Description";

       public static string policyDisposedMessage = "Policy Object has been disposed.";

       public static string policyHelp = "Help for policy";

       public static string activateMessage = "Tabs found in: {0}";

}

 

For the base skeleton, we need no code in the constructor.

 

IPolicyDefinition implementation:

public String Type

{

       get

       {

             return Strings.policyType;

       }

}

 

public String Description

{

       get

       {

             return Strings.policyDescription;

       }

}

 

public String InstallationInstructions

{

       get

       {

              return Strings.policyInstallationInstructions;

       }

}

 

public String TypeDescription

{

       get

       {

              return Strings.policyTypeDescription;

       }

}

 

public bool Edit(System.Windows.Forms.IWin32Window parent, System.IServiceProvider serviceProvider)

{

       return true;

}

 

 

Now we can implement the IPolicyEvaluation:

public void DisplayHelp(Microsoft.VisualStudio.Hatteras.Client.PolicyFailure failure)

{

       MessageBox.Show(Strings.policyHelp);

}

 

public void Initialize(IPendingCheckin pendingCheckin)

{

}

 

public void Dispose()

{

       policyDisposed = true;

}

 

public PolicyFailure[] Evaluate()

{

       if (policyDisposed)

       {

             throw new ObjectDisposedException(Strings.policyType, Strings.policyDisposedMessage);

       }

 

       return null;

}

 

public void Activate(PolicyFailure failure)

{

       return;

}

 

Finally, we need some non-serialized data.

[NonSerialized]

private bool policyDisposed = false;

 

public event PolicyStateChangedHandler PolicyStateChanged;

 

The PolicyStateChanged event is part of the IPolicyEvaluation interface; just defining it here allows policy to compile (with a warning, but we will take care of that later).

 

Now we have a policy; the only problem is that the policy will never fail due to the return null in Evaluate().  The evaluate method is called when the user attempts to checkin source code.  The Activate() method is called when a user double-clicks on a failed policy in the toolwindow – we will add more information here as this policy matures.

 

Stay tuned… next we will add more functionality to this skeleton policy.

Ok – now more details on policies.  NOTE:  The interfaces and methods listed below are NOT in concrete and may change slightly prior to shipping.

 

A policy can be configured to run each time a checkin is issued – either through the command line checkin or through Visual Studio.  To implement a policy, the developer must implement 2 interfaces:  IPolicyDefinition and IPolicyEvaluation.

 

There are two locations where policies can be accessed:  the first is during the configuration/installation of the policy.  To install the policy, you must first be in the admin group for a particular portfolio project.  Once in that group, you select to add a policy to this portfolio project (you may have any number of policies installed/configured for a portfolio project).  Once the policy is added to the portfolio project, you are able to configure the policy; since every policy is different, you (the implementer of the interfaces) are responsible for displaying a dialog for the user to configure; of course, if there is no configuration available for your policy, you can just ‘return’ from that method.  Once the configuration is complete, the policy is serialized and is sent to the server (Yukon database).  This functionality is coded when the IPolicyDefinition interface is implemented.

 

The second time the policy is accessed is during the actual checkin.  When checking in a file in the portfolio project where the policy is configured, the policy will be evaluated.  If the policy has a failure (or numerous failures), the policy will need to return the list of failures to be displayed in the policy toolwindow.  Also, you may subscribe to events to notify your policy when files are checked out (or checked/unchecked in Visual Studio Source Control toolwindow).  You may also notify Visual Studio when a policy fails without Visual Studio asking.  This functionality is coded when the IPolicyEvaluation interface is implemented.

 

To help explain how policies work, we will create a policy over the next couple postings.  The policy which we will create will verify that there are no tabs in specific files; here are the requirements for this policy:

-          Allow the administrator the ability to configure which file extensions should be scanned for tabs.

-          Using the configuration above, verify the policy fails for each file containing tabs.

-          If the user selects/un-selected files from the pending checkins toolwindow, verify those files are scanned for failures dynamically.

-          Once a file is being ‘watched’ for tabs, automatically add a failure to Visual Studio for that file if file is saved and contains tabs.

-          Once a file is being ‘watched’ for tabs, automatically remove failure from Visual Studio for that file if file is saved and no longer contains tabs.

 

To create this policy, we will do it in steps:

  1. Create framework with little functionality – basically, implement all necessary interfaces to allow policy to compile.
  2. Add to above policy which will now scan for tabs each time the checkin button is pressed – notifying user of failures (files which contain tabs) or continue with checkin if files do not contain tabs.  Hardcode which extension to verify tabs.
  3. Add to above policy to allow user to configure which extension to use.
  4. Add to above policy to receive events from Visual Studio when files are selected/un-selected from pending checkin toolwindow.  Also, watch these files add post failures (and remove failures) to Visual Studio depending on if tabs were added or removed from the file.

Earlier, I referred to the term 'Checkin Experience' - I would like to expand on that a bit to make sure everyone is aware of what it is.

The 'Checkin Experience' consists of:

  • Selecting which files to include in a checkin (changeset)
  • Selecting which work items (bugs, tasks, ...) to associate with a checkin
  • Selecting which work items to resolve/close with a checkin
  • Filling in required and optional checkin notes
  • Ensure the checkin policies are met

Selecting which files to include:  This allows you to select all or a subset of files to include in a checkin (changeset).

Selecting which work items to associate with a checkin:  This allows you to associate a work item with a checkin; once associated, you can view changeset details to see which work items were associated with a particular checkin.

Select which work items to resolve/close:  This allows you to select which work items should be moved to the “next“ state.  For example, if a work item is in an active state, selecting it would move it to a resolved state; similarly, if a work item is in a resolved state, choosing it would close it.  [Note that the example transitions are samples only - work items are configurable, so they transition based on current state and the checkin action against it].

Filling in required and optional checkin notes:  By now, hopefully everyone has the concept of a portfolio project; for each portfolio project, checkin notes can be configured.  These checkin notes can be either optional or required for a checkin.  Some sample of checkin notes would be: “Build instructions“, “Code reviewer“, “Documentation Changes“, ...  One of the many uses of release notes is to query all release notes prior to shipping a product (more specifically new releases/patches); the readme.txt or other documentation can then be created from these notes.

Ensure Checkin Policies are met:  These are great!  There is an API to write policies against.  All the policies that have been created will then be run prior to a checkin.  This allows groups to ensure standards across a development team.  There are many possibilities for policies including:

  1. Ensure no tabs are in source code.
  2. Make sure a copyright notice is at the top of each source file.
  3. Make sure coding guidelines are followed.
  4. Ensure code coverage percentages are met on unit tests prior to check.

Next posting I will go into policies much more...

 

 
Page view tracker