manually associate changesets in tfs build - tfs

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.

Related

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();
}

How can a Gated check-in be triggered programmatically?

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.

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.

How is the Change.MergeSources Field Populated in TFS?

We'd like to generate build notes with the following format:
1) Associated ChangeSets:
2) - ChangeSet 45241, by Joe: "Patching fix for foobar"
3) 'Foo.cs' integrated from dev v. 22 to qa v. 7
4) 'Bar.cs' integrated from dev v. 9 to qa v. 3
So far, we have a custom build step that accomplishes 1) and 2). It looks at the information produced by the 'AssociatedChangesetsAndWorkItems' TFS Build Activity. Here is the code:
protected override bool Execute(CodeActivityContext context)
{
StreamWriter sw = new StreamWriter(Path.Combine(BuildNotesPath.Get(context),"build-notes.txt"));
sw.WriteLine("Associated ChangSets:");
foreach (Changeset changeset in BuildAssociatedChangesets.Get(context))
{
sw.WriteLine(string.Format("ChangeSet {0}, by {1}: {2}", changeset.ChangesetId, changeset.Committer, changeset.Comment));
foreach (Change change in changeset.Changes)
{
foreach (MergeSource source in change.MergeSources)
sw.WriteLine(string.Format("\'t{0}': integrated from dev v. {1} to qa v. {2}", source.ServerItem, source.VersionFrom, source.VersionTo));
}
}
sw.Flush();
sw.Dispose();
return true;
}
The problem we're having is that the 'MergeSources' field is always an empty list. What do we have to do to get that field populated?
Take a look at MergeWorkItemsEventHandler.MergeWorkItems and .GetMergeHistory along with the Extensions.PendingMerges method in the TFS 2010 Merge Work Items Event Handler for an example of getting merge sources.
Jakob Enh deserves the credit for this answer. :)

How can I copy a TFS 2010 Build Definition?

Is there any way to copy a build definition? I work in a mainline source control methodology which utilizes many different branches that live for very short periods (ie. a few days to a week). I'd really like to copy a build template and just change the solution to build. Is there any way to do this?
You can download the new TFS 2010 power tools. It has the option to clone a build definition.
See http://msmvps.com/blogs/molausson/archive/2010/10/21/clone-a-build-definition.aspx for an example
Note: Be aware that the Clone only works when you did NOT pop out the Build window.
You can write an add-in to do it. Here's the code to copy an existing build definition:
static IBuildDefinition CloneBuildDefinition(IBuildDefinition buildDefinition)
{
var buildDefinitionClone = buildDefinition.BuildServer.CreateBuildDefinition(
buildDefinition.TeamProject);
buildDefinitionClone.BuildController = buildDefinition.BuildController;
buildDefinitionClone.ContinuousIntegrationType = buildDefinition.ContinuousIntegrationType;
buildDefinitionClone.ContinuousIntegrationQuietPeriod = buildDefinition.ContinuousIntegrationQuietPeriod;
buildDefinitionClone.DefaultDropLocation = buildDefinition.DefaultDropLocation;
buildDefinitionClone.Description = buildDefinition.Description;
buildDefinitionClone.Enabled = buildDefinition.Enabled;
buildDefinitionClone.Name = String.Format("Copy of {0}", buildDefinition.Name);
buildDefinitionClone.Process = buildDefinition.Process;
buildDefinitionClone.ProcessParameters = buildDefinition.ProcessParameters;
foreach (var schedule in buildDefinition.Schedules)
{
var newSchedule = buildDefinitionClone.AddSchedule();
newSchedule.DaysToBuild = schedule.DaysToBuild;
newSchedule.StartTime = schedule.StartTime;
newSchedule.TimeZone = schedule.TimeZone;
}
foreach (var mapping in buildDefinition.Workspace.Mappings)
{
buildDefinitionClone.Workspace.AddMapping(
mapping.ServerItem, mapping.LocalItem, mapping.MappingType, mapping.Depth);
}
buildDefinitionClone.RetentionPolicyList.Clear();
foreach (var policy in buildDefinition.RetentionPolicyList)
{
buildDefinitionClone.AddRetentionPolicy(
policy.BuildReason, policy.BuildStatus, policy.NumberToKeep, policy.DeleteOptions);
}
return buildDefinitionClone;
}
The following tool (VS Addin) will satisfy your requirement>
Community TFS Build Manager
http://visualstudiogallery.msdn.microsoft.com/16bafc63-0f20-4cc3-8b67-4e25d150102c
I just had a need to copy build definitions, and found Jim's answer above to be helpful. However, being new to the TFS API, I needed help connecting to the server and getting the existing build definition through code. These two links helped fill in the gaps:
http://msdn.microsoft.com/en-us/library/bb286958.aspx
http://geekswithblogs.net/jakob/archive/2010/04/26/creating-a-build-definition-using-the-tfs-2010-api.aspx
You can right click the build definition and select 'clone build definition' to copy the definition file. You can then edit it from there.
Here is the soltion if you want to move the Build definition from one Team Project to other Team project.
public void MoveBuild(string fromTeamProject, string toTeamProject, string buildName, string newBuildName)
{
var _server = TfsTeamProjectCollectionFactory.GetTeamProjectCollection(new
Uri("http://Mytfs:8080/defaultcollection"));
IBuildServer _buildServer = _server.GetService<IBuildServer>();
var buildDetails = _buildServer.QueryBuildDefinitions(fromTeamProject);
foreach (var fromBuild in buildDetails)
{
if (fromBuild.Name != buildName) continue;
var newBuildDefinition = _buildServer.CreateBuildDefinition(toTeamProject);
newBuildDefinition.Name = !string.IsNullOrEmpty(newBuildName) ? newBuildName : fromBuild.Name;
newBuildDefinition.BuildController = fromBuild.BuildController;
// This finds the template to use
foreach (var mapping in fromBuild.Workspace.Mappings)
{
newBuildDefinition.Workspace.AddMapping(
mapping.ServerItem, mapping.LocalItem, mapping.MappingType, mapping.Depth);
}
newBuildDefinition.DefaultDropLocation = fromBuild.DefaultDropLocation;
newBuildDefinition.Description = fromBuild.Description;
// buildDefinition.Workspace.AddMapping(build.Workspace.);
newBuildDefinition.Process = _buildServer.QueryProcessTemplates(fromBuild)[2];
newBuildDefinition.ProcessParameters = fromBuild.ProcessParameters;
newBuildDefinition.Enabled = false;
newBuildDefinition.Save();
}//end of for each loop
}
From your message it is not clear which template is your build definition using (default, upgrade or lab management). If I understand correctly you would like to easily set up a build definition which builds the same solution but from a different branch.
One thing that you could try instead of copying the definition is to edit it. When the branch dies, rename the build definition (might help with reporting), change the workspace mapping of the build and you should be done.
Thanks, Ladislau

Resources