VSTS

Subscriber Policy TFS check-in

Posted on Updated on


In order to filter about your check-in operations, for example for branching organised arround Featuring, constraint is to ensure that we don’t check-in on main branch, because it’s later have to stay clean, you haven’t check’in source on Main branch, all changes have to contain merge action , after quality test process on features.

So this post will speak about create custom check-in policy, we have two solutions :

  • First is to create subscriber, deployed on server, centralized for all TFS instances
  • Second is to create VSIX, deployed if needed by an customer

We develop into this post custom subscriber, permitting to filter on changes, ensuring that we have unless merge action.

Remark : Constraint is due to news TFS APIs 2017.

Below list of steps that you have to follow in order to accomplish this goal

1.Create library class

2. Add reference to following assemblies :

  • Microsoft.TeamFoundation.Common
  • Microsoft.TeamFoundation.Framework.Server
  • Microsoft.TeamFoundation.Server.Core
  • Microsoft.TeamFoundation.VersionControl.Server

3.Create class named MergeNotificationSubscriber implementing ISubscriber interface

Below specific implementing of members :

public string Name
{
get
{
throw new NotImplementedException();
}
}

public SubscriberPriority Priority
{
get
{
throw new NotImplementedException();
}
}
public EventNotificationStatus ProcessEvent(IVssRequestContext requestContext, NotificationType notificationType, object notificationEventArgs, out int statusCode, out string statusMessage, out ExceptionPropertyCollection properties)
{
throw new NotImplementedException();
}

public Type[] SubscribedTypes()
{
throw new NotImplementedException();
}

For Name property, you can define your message of your strategy

public string Name
{
get
{
return "Require Merge operations on Integration and Production branch";
}
}

For your priority, you can set to high

public SubscriberPriority Priority
{
get
{
return SubscriberPriority.High;
}
}

For type of operations, we specify check-in notifications

/// Subscribeds the types.
///
/// permit to define subscribed tyeps public Type[] SubscribedTypes()
{
return new Type[1] { typeof(CheckinNotification) };
}

Our logic will be placed into ProcessEvent method, note that we have condition on DecisionPoint, signify that we catch request before check-in action (Similar to http module, we pass every time into code on server forfor each request).

 

/// Processes the event.
///The request context. 
///Type of the notification. 
///The notification event arguments. 
///The status code. 
///The status message. 
///The properties. 
/// permit to define status public EventNotificationStatus ProcessEvent(IVssRequestContext requestContext, NotificationType notificationType, object notificationEventArgs, out int statusCode, out string statusMessage, out ExceptionPropertyCollection properties) 

{ 

const string IntegrationPatternTeamProject = "$/..."; 
const string ProductionPatternTeamProject = "$/..."; 
const string TemplateMessageWindowConstant = "Check-in was rejected due to a type action on item, have to be equal to merge"; 
const string CollectionNameConstant = "..."; 
statusCode = 0; 
properties = new ExceptionPropertyCollection(); 
statusMessage = String.Empty; 

//Ensure that policy is applied just on target collection 
if(requestContext.ServiceHost.Name != CollectionNameConstant) 
{ 
return EventNotificationStatus.ActionPermitted; 
} 

if (notificationType == NotificationType.DecisionPoint) 
{ 
if (notificationEventArgs is CheckinNotification) 
{ 
CheckinNotification checkinNotification = notificationEventArgs as CheckinNotification; 
var teamFoundationVersionControlService = requestContext.GetService(); 
var submittedItems = checkinNotification.GetSubmittedItems(requestContext); 

if (!submittedItems.All(x => x.Contains(IntegrationPatternTeamProject) || x.Contains(ProductionPatternTeamProject))) 
{ 
return EventNotificationStatus.ActionPermitted; 
} 

using (var teamFoundationDataReader = teamFoundationVersionControlService.QueryPendingChangesForWorkspace(requestContext, checkinNotification.WorkspaceName, checkinNotification.WorkspaceOwner.UniqueName, checkinNotification.GetSubmittedItems(requestContext).Select(i => new ItemSpec(i, RecursionType.None)).ToArray(), false, 100, null, true)) 
{ 
if (teamFoundationDataReader != null) 
{ 
var nonMergedItem = teamFoundationDataReader.CurrentEnumerable().FirstOrDefault(c => c.MergeSources == null || c.MergeSources.Count == 0); 

if (nonMergedItem != null) 
{ 
TeamFoundationApplicationCore.Log(TemplateMessageWindowConstant, 2010, EventLogEntryType.Information); 
statusMessage = TemplateMessageWindowConstant; 
return EventNotificationStatus.ActionDenied; 
} 
}
} 
} 
} 

return EventNotificationStatus.ActionApproved; 
}

GC.Collect