How can a Gated check-in be triggered programmatically? - tfs

I am getting errors using the Workspace.Checkin command in TFS because the files I am trying to check in are part of a Gated build definition. There are lots of people asking how to override a gated check-in using the TFS API. But a very different question is this: what if I actually want to perform the gated check-in, programmatically? How can I trigger this in TFS API?
What I want to do is:
Perform a normal check-in operation of a set of pending changes
Receive information about the gated build that was launched
Either subscribe to a completion event for that build, or poll the build until it is complete.
Continue if the build was successful, and changes were committed (get the resulting changeset if possible)
Stop if the build was not successful, and report the error.
I could not find anything out there to answer this question; perhaps it is such an edge case that I'm the only one crazy enough to want to do this. That said, my need for this is legitimate. The only thing I can figure is that I would need to go through the lower-level functions of the gated check-in process:
1) Walk through the build definitions to see if the path of any of the files coincide with any of the paths in any of the gated builds.
2) If an intersection exists, create a shelveset of the pending changes
3) Queue a new build of the gated definition using the shelveset
4) Query the build definition and refresh the information until the build status is completed
5) If the build was successful, unshelve the pending changes, and then check-in using an override.
However, note that even if I do these steps, the resulting build won't look like a gated build at all. Policy override notifications will be sent, and the only way of letting someone know that a build was actually performed would be to include such information in the policy override comments. Is there a more direct way to do this that would make it look like any other gated build?

Prompted by the post by Scordo I was able to figure out how to do this. Here is the code:
//Initialize connection to server
TfsTeamProjectCollection tpc = new TfsTeamProjectCollection(new Uri(ServerAddress));
try
{
tpc.EnsureAuthenticated();
}
catch (Exception e)
{
System.Environment.Exit(-1);
}
VersionControlServer vcServer = tpc.GetService<VersionControlServer>();
IBuildServer buildServer = tpc.GetService<IBuildServer>();
//.
//.
//Program logic goes here...
//.
//.
//Get the workspace and the respective changes
Workspace workspace = vcServer.TryGetWorkspace(LocalProjectPath);
PendingChange[] changes = workspace.GetPendingChanges();
//Perform the check-in
bool checkedIn = false;
//Either wait for the gated check-in or continue asynchronously...
bool waitForGatedCheckin = true;
//Attempt a normal check-in
try
{
//First, see what policy failures exist, and override them if necessary
CheckinEvaluationResult result = workspace.EvaluateCheckin(CheckinEvaluationOptions.All,
changes, changes, "Test", null, null);
//Perform the check-in, with overrides if necessary, including Work Item policies
//or include additional code to comply with those policies
PolicyOverrideInfo override = null;
if (result.PolicyFailures != null)
{
override = new PolicyOverrideInfo("override reason", result.PolicyFailures);
}
workspace.CheckIn(changes, comment, null, null, override);
checkedIn = true;
}
catch (GatedCheckinException gatedException)
{
//This exception tells us that a gated check-in is required.
//First, we get the list of build definitions affected by the check-in
ICollection<KeyValuePair<string, Uri>> buildDefs = gatedException.AffectedBuildDefinitions;
if (buildDefs.Count == 1)
{
//If only one affected build definition exists, then we have everything we need to proceed
IEnumerator<KeyValuePair<string, Uri>> buildEnum = buildDefs.GetEnumerator();
buildEnum.MoveNext();
KeyValuePair<string, Uri> buildDef = buildEnum.Current;
String gatedBuildDefName = buildDef.Key;
Uri gatedBuildDefUri = buildDef.Value;
string shelvesetSpecName = gatedException.ShelvesetName;
string[] shelvesetTokens = shelvesetSpecName.Split(new char[] { ';' });
//Create a build request for the gated check-in build
IBuildRequest buildRequest = buildServer.CreateBuildRequest(gatedBuildDefUri);
buildRequest.ShelvesetName = shelvesetTokens[0]; //Specify the name of the existing shelveset
buildRequest.Reason = BuildReason.CheckInShelveset; //Check-in the shelveset if successful
buildRequest.GatedCheckInTicket = gatedException.CheckInTicket; //Associate the check-in
//Queue the build request
IQueuedBuild queuedBuild = buildServer.QueueBuild(buildRequest);
//Wait for the build to complete, or continue asynchronously
if (waitForGatedCheckin)
{
while (!queuedBuild.Build.BuildFinished)
{
//Get the latest status of the build, and pause to yield CPU
queuedBuild.Refresh(QueryOptions.Process);
System.Threading.Thread.Sleep(1000)
}
if (queuedBuild.Build.Status == BuildStatus.Succeeded)
{
checkedIn = true;
}
}
}
else
{
//Determine a method for specifying the appropriate build definition
//if multiple build definitions are affected
}
}
catch (CheckinException checkinException)
{
//Handle other checkin exceptions such as those occurring with locked files,
//permissions issues, etc.
}
if (checkedIn)
{
//Additional logic here, if the check-in was successful
}

I never did it before but this should help you a bit:
When you checkin changes using the workspace and this checkin is protected by a gated build, a GatedCheckinException is thrown. This Exceptions should have all the necessary information to create a shelveset and to trigger the correct build(s) with that shelveset. For Example the AffectedBuildDefinitions property should contain the builddefinitions which are gated build defnitions.
Hope that helps.

Related

Cancelling as SUCCESS build when changeset is only certain file(s)?

Following an old question being asked in HERE, I was wondering... Is it possible to retrieve changeset without being inside a stage?
I would like that condition to be triggered whenever only a certain file is modified. Once that file has been modified, we must skip the build and mark it as success so we don't waste resources.
pipeline {
agent { ... }
if () {
currentBuild.result = 'SUCCESS'
return
}
stages {
...
}

How to send an email to requester only if the build is triggered manually?

I would like to configure a project in Jenkins to send emails to the recipients group for regular scheduled builds, but only to the requester in case the build is triggered manually. Is this possible?
You should be able to accomplish this by using the "Script - After Build" trigger of the Editable Email Notification post-build action. You can run a groovy script, with the last line evaluating to a Boolean, which determines whether or not to send the email. The screenshot below shows the trigger section that checks to see if the build was initiated by a user (manually).
This script will only tell you whether or not the IMMEDIATE cause of the build was a manual user action, though. Depending on how your build pipeline is setup, one of your upstream jobs may have been initiated manually, so I'm not sure if you want an email sent in that case. If you do, you will have to iterate through all the build causes and look for a manual cause.
def boolean wasStartedManually(causes) {
boolean manuallyStarted = false
causes.each { cause ->
if (cause.class == hudson.model.Cause$UserIdCause) {
manuallyStarted = true
}
if (!manuallyStarted) {
if (cause.class == hudson.model.Cause$UpstreamCause) {
manuallyStarted = wasStartedManually(cause.upstreamCauses)
}
}
}
return manuallyStarted
}
wasStartedManually(build.getCauses())
You will need to add 2 post-build Email actions to your job, one for when the job is triggered manually, and then another if the job was not submitted manually. For the latter, you would run the same script, but just negate the results.

TFS Build (2013) - Build Stop Handling

I have custom codeactivities in TFS Build. One of them is a background thread that TFS Build does not know about.
I want to find out if there is a way for this thread to check if a "stop build" has been requested. (i.e. check on the current build status WITHOUT needing a CodeActivityContext)
(NB: I can't use the AsyncCodeActivity (and its cancel mechanism) because this still blocks subsequent tasks)
I am currently using a heartbeat system and relying on a timeout of the heatbeat from the TFS Build flow loop but this is not fool proof.
IBuildDetail.BuildFinished exists but there is the catch 22 of if the build is finished, how do you get iBuildDetail?
Because code activities are "stateless", then using a previous "CodeActivityContext" to get iBuildDetail does not work, i.e. the context no longer exists.
I can get to a code path of _buildServer.GetBuild(_buildUri)
but can't find out how to establish your current builduri (not to be confused with build definition, server, agent or number)
Thanks
Good news, I found a solution
I was caught up in the BuildUri term, it turns out at the bottom of iBuildDetails, is "Uri"
This turns out to be the BuildUri
Thus code like this works ...
static private Uri _buildUri;
static private IBuildServer _buildServer;
...
protected override void Execute(CodeActivityContext context)
{
// Obtain the runtime value of the Text input argument
string ScriptPath = context.GetValue(this.InScriptPath);
_thisContext = context;
IBuildDetail buildDetail = _thisContext.GetExtension<IBuildDetail>();
_buildUri = buildDetail.Uri; // This is the complete string
_buildServer = buildDetail.BuildServer;
......
In background tthread
try
{
IBuildDetail buildDetail = _buildServer.GetBuild(_buildUri); // this does not work as it is not the BuildUri
if (buildDetail != null)
{
if (buildDetail.Status == BuildStatus.Stopped)
{
TerminateProcess();
}
}
}
catch (Exception Ex)
{
TerminateProcess();
}

manually associate changesets in tfs build

Microsoft TFS build by default assigns all changesets after last successful build into "Associated Changesets". Is there a way to do it manually?
What I would like to achieve is to search for last build that has "Build Quality" set to Released.
So Each build would have associated all changesets after latest "Released" build.
Is it possible?
If you are using TFS 2012, the build workflow has an activity "Associate Changesets and Work Items" of type "Microsoft.TeamFoundation.Build.Workflow.Activities.AssociateChangesetsandWorkItems" which gets all the changesets and associate it with the build.
If you want to change that, you need to replace this activity with your custom workflow steps. I will leave the "finding the changesets" part to you but the following activity will allow you to associate the changesets with your build
namespace ContractLibrary
{
using Microsoft.TeamFoundation.Build.Workflow.Activities;
using Microsoft.TeamFoundation.Build.Workflow.Tracking;
using System;
using System.Activities;
public class WriteAssociatedChangesets : CodeActivity
{
protected override void Execute(CodeActivityContext context)
{
if (context == null)
{
return;
}
this.WriteBuildInformation(context);
}
private void WriteBuildInformation(CodeActivityContext context)
{
var buildInformation = new WriteBuildInformation<AssociatedChangeset>()
{
Value = new AssociatedChangeset() { ChangesetId = 17997 /*Your ChangsetsID*/ }
};
context.Track(buildInformation);
}
}
}
The post is really useful in finding out how to write to build information.

TFS2010 Custom Build Activity : to Merge branches

I'm working on customizing our build activity. I'd like to have your help for an issue.
Following is our version control hierarchy.
Main
|- Dev
|- QA
we are working on Dev branch and while taking the build we need to merge Dev branch to Main then to QA.
Main is the root branch as you might know.
In our build template, I've added two custom activities to merge one from Dev to Main and another one to merge from Main to QA. Following is the code for the custom activity.
protected override string Execute(CodeActivityContext context)
{
string lstrStatus = string.Empty;
string lstrSourceBranchPath = context.GetValue(this.SourceBranchPath);
string lstrTargetBranchPath = context.GetValue(this.TargetBranchPath);
// Obtain the runtime value of the input arguments
Workspace workspace = context.GetValue(this.Workspace);
GetStatus status = workspace.Merge(lstrSourceBranchPath,
lstrTargetBranchPath,
null,
null,
LockLevel.None,
RecursionType.Full,
MergeOptions.None);
// resolve the conflicts, if any
if (status.NumConflicts > 0)
{
Conflict[] conflicts = workspace.QueryConflicts(new string[]
{ lstrTargetBranchPath }, true);
foreach (Conflict conflict in conflicts)
{
conflict.Resolution = Resolution.AcceptTheirs;
workspace.ResolveConflict(conflict);
}
}
// checkin the changes
PendingChange[] pendingChanges = workspace.GetPendingChanges();
if (pendingChanges != null && pendingChanges.Length > 0)
{
workspace.CheckIn(pendingChanges, "Merged by MERGE BRANCHES activity");
}
return lstrStatus;
}
Problem is, merging happens perfectly in the server. But, it's not getting reflected in the local folder. I tried to add SyncWorkspace activity after each Merge custom activity. Still not working.
My guess was that a SyncWorkspace should be the only thing to do.
You could try doing a RevertWorkspace before that.
EDIT
After you now stated that even this wouldn't work, I would generate a bug against MS at least to get an official answer.
In the meanwhile you can try with the following method, which I absolutely see as an overkill: Once you have checked in, redo all the steps within sequence Initialize Workspace.
If even that doesn't work I'd consider two different builds, one that does your merge & one that does the actual build. You can then organize a scheme where your first build, once it's done, triggers the second one. Here is a good resource for that.

Resources