TFS2010 Custom Build Activity : to Merge branches - tfs

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.

Related

Performance issue while iterating through repo's commits?

I want to display just the first 100 commits of a repository. I used the linux-repo to test this:
const int maxSize = 100;
Stopwatch sw = new Stopwatch();
Console.WriteLine( "Getting Commits in own Thread" );
sw.Start();
using( Repository repo = new Repository( path_to_linux_repo ) )
{
ICommitLog commits = repo.Commits.QueryBy( new CommitFilter { Since = "HEAD" } );
int index = 0;
foreach( Commit commit in commits )
{
if( index++ > maxSize ) break;
}
}
sw.Stop();
Console.WriteLine( "Took {0}ms for {1} entries", sw.ElapsedMilliseconds, maxSize );
This simple loop takes over 9000ms on my machine. Its FAR faster when using a repo with less commits, but why is it so slow in repos with a lot of commits?
Another question: is it possible to just retrieve a given number of commits e.g. to page through
all commits?
I can reproduce here. It's definitely far too long to take. It looks as though libgit2 is enqueueing the full graph before returning, which would be a bug with the given settings. Would you mind opening an issue?
As for retrieving a number of commits, the iteration is pull-based, so you will only grab as many out of the repository as you ask for. the commit log implements IEnumerable so you can use whatever LINQ methods you like (or do it manually as in this example).
UPDATE:
The bug was quite embarrassing, but there's a PR to fix it in libgit2 which will make its way into libgit2sharp releases in due course. With the fix, this test now takes ~80ms. Thanks for bringing it up.
UPDATE 2:
The fix is now available in the vNext branch of LibGit2Sharp.

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.

Resolving merge conflicts in the TFS API

I am trying to resolve merge conflicts with the TFS API and am struggling. My issue is as follows:
Merge from trunk -> branch
Conflict occurs during merge (same line of a file edited in both)
At this point I simply want to supply my own text for what the merged file should look like and then mark it as resolved.
I can't work out how to do this. See code below:
// merge was performed and there are conflicts to resolve
Conflict[] conflicts = ws.QueryConflicts(new string[] {"$/ProjectName/solution/"}, true);
Console.WriteLine(" - Conflicts: {0}", conflicts.Count());
if (conflicts.Any())
{
Console.WriteLine(" - Resolving conflicts");
// There are conflicts to deal with
foreach (Conflict conflict in conflicts)
{
// Attempt to perform merge internally
ws.MergeContent(conflict, false);
if (conflict.ContentMergeSummary.TotalConflicting > 0)
{
// Conflict was not resolved
// Manually resolve
// PROBLEM IS HERE <--------------
var allLines = File.ReadAllLines(conflict.TargetLocalItem).ToList();
allLines.Add("This is the MANUAL merge result");
File.WriteAllLines(conflict.TargetLocalItem, allLines.ToArray());
}
//conflict was resolved
conflict.Resolution = Resolution.AcceptMerge;
ws.ResolveConflict(conflict);
}
}
Checkin(ws, "Merge comment");
Console.WriteLine("Done");
As you can see I am trying to manually edit the target file, but this does not work. I can't seem to work out what file I should be editing on the conflict object to manually perform the merge.
One commenter has asked me to elaborate on the issues with the code above:
At the point of the comment "PROBLEM IS HERE", the target file still has a read only flag on it, so I cannot edit the file.
if I remove the readonly flag with some code and continue with the code, the resulting file does not contain my new line, but contains a diff style text with all the comments in. I can elaborate on this if it doesn't make sense.
If I pend an edit on the TargetLocalItem, this does nothing. No edit is pended.
This tells me that editing the target file in the local workspace is not the correct action.
Essentially I am trying to mimic what an external tool does, but my program will be the external tool and provide the merged text.
Try this
string tmpOutFile = Path.GetTempFileName();
conflict.MergedFileName = tmpOutFile;
// do stuff with tmpOutFile
conflict.Resolution = Resolution.AcceptMerge;
ws.ResolveConflict(conflict);

Programmatically delete a TFS branch

I want to programmatically delete a branch in TFS that was create automatically.
There is an existing method "ICommonStructureService.DeleteBranches" that should do the work.
My problem is that the method requires a parameter "string[] nodeUris" that specifies the branch to delete using a "vstfs://... " URI and I just don't know how to get that for my branch.
What I need is something like:
var projectCollection = TfsTeamProjectCollectionFactory.GetTeamProjectCollection(new Uri <myCollectionUrl>));
var cssService = projectCollection.GetService<ICommonStructureService3>();
var project = cssService.GetProjectFromName(<myProjectName>);
But how can I get the Branch Uri from there?
Meanwhile I found a solution. For deleting the branches I am using
versionControl.Destroy(new ItemSpec(myBranchPath, RecursionType.Full), VersionSpec.Latest, null, DestroyFlags.KeepHistory);
This does exactly what I needed.
versionControl is of type VersionControlServer and must be initialized using the Team Collection
Deleting a branch in version control is like deleting any other version control item. You will need to pend a delete with Workspace.PendDelete on the Item.
The method you reference is wholly unrelated to version control, it's part of the TFS common structure service, which controls the "areas and iterations" that TFS work items can be assigned to.
In short, there's no way to perform any sort of version control operations against the common structure service. You delete a branch by creating a Workspace against a VersionControlServer, pending a delete and then checking in your pending changes.
I agree to Edward Thomson about using Destroy command. So I followed on advice from him and came up with following,
public void DeleteBranch(string path)
{
var vcs = GetVersionControlServer();
var itemSpec = new ItemSpec(path, RecursionType.Full);
var itemSpecs = new[] {itemSpec};
var workSpace = GetOrCreateWorkSpace(vcs);
try
{
workSpace.Map(path, #"c:\Temp\tfs");
var request = new GetRequest(itemSpec, VersionSpec.Latest);
workSpace.Get(request, GetOptions.GetAll | GetOptions.Overwrite);
workSpace.PendDelete(path, RecursionType.Full);
var pendingchanges = workSpace.GetPendingChanges(itemSpecs);
workSpace.CheckIn(pendingchanges, "Deleting The Branch");
}
finally
{
if (workSpace != null)
{
workSpace.Delete();
}
}
}
If there is a neat way to do the same than I am looking forward to it. This is bit slow as it does too many things,
Creates Temp Workspace
Gets All changes to that
Performs Delete to whole change set
checks it in
Cleans up the workspace

TFS2010 - Wrong changeset appearing at SourceGetVersion

I am currently setting up a Team Foundation Server 2010 and I found a very strange behavior when performing a build:
The situation explained:
We have 2 Branches
Development
Main
All developers check in code into the Development branch only. Once per day, the build manager merges some changesets over to the Main branch. On the Development brach, a continuous build at each check in is running. On the Main branch, once per day (in the night) a build is triggered.
Now suppose that the changesets 1-100 are being merged into the Main brach at 5pm, giving changeset 101 as the merge operation. Some developers check in changesets 102-106 after 5 o'clock into the Development branch. Now at 11pm the daily build is automatically triggered and runs on the Main branch. The last changeset of the Main branch is changeset 101. However, the Build details shows changeset 106:
I could imagine that this behavior is intended, because if you check out changeset 106 on the Main branch, you will in fact get the content of changeset 101. But it would be much more readable if this Build summary showed the correct number.
Question 1: Is there a way of manipulating the ouput of the SourceGetVersion information? Maybe through the Build Process Template?
The second scenario, where the TFS behaves strange is even worse:
When queuing a new build, there is the option of entering the "Get Version" Parameter, as shown in the following picture:
If I now click on "queue", the build is triggered and AGAIN the build detail outputs the changeset 106 although I specifically set it to get changeset 76.
Question 2: Is this a bug? Is there a hotfix or something to fix this? Or is there any option flag that has to be set?
I hope someone knows more about this. I don't really believe that this is a bug, because it is such a vital functionality that other people must have encountered it before.
Thanks for any help!!
Christian
EDIT 1
The folder structure of the Team Project is:
$ProjectName
BuildProcessTemplates
Documentation
SourceCode
Development <-- this is a branch
3rdParty
Source
Main <-- this is a branch
3rdParty
Source
The build only pulls the Main branch and everything below it.
EDIT 2
Here is a picture of the Workspace tab in the build definition:
Finally I found out what is going on:
Basically The changeset that can be seen in my picture 1 is always the latest changeset of the entire Team Project Collection. It is the property "SourceGetVersion" on the object "BuildDetails" of type "IBuildDetails".
I think this is a bug which can be worked around:
If you change the BuildDetails.SourceGetVersion (which is a string) to some other value, then the build summary will show the updated string. Furthermore, it is then saved correctly to the collection database.
What I have done in order to add the correct changeset number is I have created a custom build activity that takes the branch which should be build as input parameter. It outputs the correct changeset. The activity finds out the correct changeset by connecting to the TFS and downloading the History. Then it looks at all the items in the history and outputs the largest changeset number. Here is the code of that activity:
using System.Activities;
using System.Collections;
using System.Net;
using Microsoft.TeamFoundation.Client;
using Microsoft.TeamFoundation.VersionControl.Client;
using Microsoft.TeamFoundation.Build.Client;
namespace SourceGetVersionActivity
{
[BuildActivity(HostEnvironmentOption.All)]
public sealed class SourceGetVersionActivity : CodeActivity<string>
{
// Define an activity input argument of type string
public InArgument<string> Branch { get; set; }
// If your activity returns a value, derive from CodeActivity<TResult>
// and return the value from the Execute method.
protected override string Execute(CodeActivityContext context)
{
// Obtain the runtime value of the Text input argument
string branch = context.GetValue(this.Branch);
ICredentials account = new NetworkCredential("Useranme", "password", "domain");
// connect / authenticate with tfs
TeamFoundationServer tfs = new TeamFoundationServer("http://tfs:8080/tfs/CollectionName", account);
tfs.Authenticate();
// get the version control service
VersionControlServer versionControl = (VersionControlServer)tfs.GetService(typeof(VersionControlServer));
IEnumerable changesets = versionControl.QueryHistory(branch, VersionSpec.Latest, 0, RecursionType.Full,
null, null, null, int.MaxValue, false, false, false, false);
int maxVersion = 0;
foreach (Changeset c in changesets)
{
if (c.ChangesetId > maxVersion)
{
maxVersion = c.ChangesetId;
}
}
return string.Concat('C', maxVersion.ToString());
}
}
}
I call this activity as soon as possible (after the GetBuild activity).
Basically in the BuildProcessTemplate I have added an Argument (string) "Branch" which needs to be filled with a string that points to the top folder that is being build. The custom activity takes that as input and outputs a string which is the correct changeset id. The BuildDetail.SourceGetVersion property will then be overriden by the correct changeset id.
I find it really strange that no-one else seems to have encountered this problem. I could not find any person on the internet with the same problem. Anyway, I hope this answer helps someone else in the future as well.
EDIT - Writing the above code directly in Workflow Foundation:
To get the correct changeset using more compact code and avoiding custom activites, it is also possible to use Workflow Foundation directly. Below is the "code" (doing exactly what is done in above C# code):
(1) The GetTeamProjectCollection activity gets the current collection. I am saving it inside the TeamProjectCollection variable (see bottom of the picture). Important: The variable needs to be defined inside this sequence, if you define it in outer scope, an error will occur: "Unable to serialize type 'Microsoft.TeamFoundation.Client.TfsTeamProjectCollection'. Verify that the type is public and either has a default constructor or an instance descriptor."
(2) Foreach "changeset" in "TeamProjectCollection.GetService(Of VersionControlServer).QueryHistory(Branch, VersionSpec.Latest, 0, RecursionType.Full, Nothing, Nothing, Nothing, Integer.MaxValue, False, False, False).Cast(Of Changeset)()"
The TypeArgument of the Foreach loop is "Microsoft.TeamFoundation.VersionControl.Client.Changeset".
This expression gets the version control object from the collection, calls it "QueryHistory" method which returns an IEnumerable with all changesets.
(3) So we are iterating over all changesets and looking at the ChangesetId. Then saving the maximum ChangesetId to the variable "maxId".
(4) At the end, BuildDetails.SourceGetVersion = "C" + maxId.ToString(). The "C" indicates, that the version is a changeset.
I hope someone finds this piece of "Code" useful!
Christian

Resources