Update VSO history and add attachments - azure-devops

How do I add history records and attachments to a workitem in VSO 2015?
So far I have succeeded in logging in, running a query and retrieve the workitems from the query result.
But the workitem objects doesn't have properties for history or attatchments:
WorkItemTrackingHttpClient witClient = new WorkItemTrackingHttpClient(new Uri(collectionUri), new VssAadCredential("first.last#blablabla.com", "secret"));
List<QueryHierarchyItem> items = witClient.GetQueriesAsync(teamProjectName, QueryExpand.All,2).Result;
QueryHierarchyItem myQueriesFolder = items.FirstOrDefault(qhi => qhi.Name.Equals("Shared Queries"));
if (myQueriesFolder != null)
{
string queryName = "All Workitems";
QueryHierarchyItem newBugsQuery = null;
if (myQueriesFolder.Children != null)
{
newBugsQuery = myQueriesFolder.Children.FirstOrDefault(qhi => qhi.Name.Equals(queryName));
}
WorkItemQueryResult result = witClient.QueryByIdAsync(newBugsQuery.Id).Result;
if (result.WorkItemRelations.Count() > 0)
{
int skip = 0;
const int batchSize = 100;
IEnumerable<WorkItemLink> workItemRefs;
do
{
workItemRefs = result.WorkItemRelations.Skip(skip).Take(batchSize);
if (workItemRefs.Count() > 0)
{
// get details for each work item in the batch
List<WorkItem> workItems = witClient.GetWorkItemsAsync(workItemRefs.Select(wir => wir.Target.Id)).Result;
foreach (WorkItem workItem in workItems)
{
// write work item to console
if ((string)workItem.Fields["System.WorkItemType"] == "Requirement")
{
Console.WriteLine("{0} {1}", workItem.Id, workItem.Fields["System.Title"]);
//workItem doesn't have properties for history or attachmens...
WorkItemHistory history = (WorkItemHistory) workItem.Fields["System.History"];
}
}
}
skip += batchSize;
}
while (workItemRefs.Count() == batchSize);
}
}

In order to retrieve the history you need to loop through the revisions of a work item (WorkItem.Revisions) and output the history field for each revision.
You can't use WorkItem.Revision and instead should use WorkItem.Fields["System.History"].value to retrieve it.
Attachments are in WorkItem.Attachments for both read and write.

Using namespace:
Microsoft.TeamFoundation.Client
Microsoft.TeamFoundation.WorkItemTracking
Refer to following code for details:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Net;
using Microsoft.TeamFoundation.Client;
using Microsoft.TeamFoundation.WorkItemTracking.Client;
namespace TFSAPI
{
class Program
{
static void Main(string[] args)
{
string project = "https://xxxxxxxx.visualstudio.com/defaultcollection";
NetworkCredential nc = new NetworkCredential("username","pwd");
BasicAuthCredential cred = new BasicAuthCredential(nc);
TfsClientCredentials tc = new TfsClientCredentials(cred);
tc.AllowInteractive = false;
TfsTeamProjectCollection tpc = new TfsTeamProjectCollection(new Uri(project), tc);
WorkItemStore workItemStore = tpc.GetService<WorkItemStore>();
Project teamProject = workItemStore.Projects["Sonar"];
WorkItemType workItemType = teamProject.WorkItemTypes["User Story"];
int workitemid = 2;
WorkItem wi = workItemStore.GetWorkItem(workitemid);
//Update history
wi.Fields["History"].Value = "You comments";
//Add attachment
wi.Attachments.Add(new Attachment("E:\\a\\1.txt"));
wi.Save();
}
}
}

Related

Is it possible to rollback a changeset using Microsoft.TeamFoundation.SourceControl.WebApi?

Following code try to rollback a changeset using Microsoft.TeamFoundation.SourceControl.WebApi. But "The specified change type Rollback is not supported." is raised when calling CreateChangesetAsync. Is this a limitation of Microsoft.TeamFoundation.SourceControl.WebApi? Or I did something wrong?
using System;
using System.Collections.Generic;
using Microsoft.TeamFoundation.SourceControl.WebApi;
using Microsoft.VisualStudio.Services.Client;
using Microsoft.VisualStudio.Services.Common;
using Microsoft.VisualStudio.Services.WebApi;
namespace RestRollBackInTFS
{
class Program
{
static void Main(string[] args)
{
const String collectionUri = #"https://myTfs.visualstudio.com/";
VssConnection connection = new VssConnection(new Uri(collectionUri),
new VssClientCredentials()
);
TfvcHttpClient tfsClient = connection.GetClient<TfvcHttpClient>();
int changeSetIdToRollBack = 11651;
var changeSet = tfsClient.GetChangesetAsync(changeSetIdToRollBack, maxChangeCount: 10, includeDetails: true).Result;
foreach (var c in changeSet.Changes)
{
c.ChangeType = VersionControlChangeType.Rollback;
}
TfvcChangeset newC = new TfvcChangeset();
newC.Changes = new List<TfvcChange>(changeSet.Changes);
var ret = tfsClient.CreateChangesetAsync(newC).Result;
}
}
}

Hangfire , EF context used in the Task is not updated, the query inside the task always gives the old values

The context used inside the Hangfire task , always gives the old database values, it seems like the context is not updating inside the task. How to get the updated info context data inside hangfire task.
calling the task
BLL.Extraction NewExtractionCls = new BLL.Extraction();
var jobId = BackgroundJob.Enqueue(() => NewExtractionCls.SearchEngineExtraction("SearchURL", "1","1", null));
This is implementation
[Authorize]
public void SearchEngineExtraction(string SearchURL, int PageLimit, int SearchEngineID, PerformContext context)
{
WebClient wc = new WebClient();
#region Main Table - SearchEngineTbl
var NewExtraction = db.SearchEngineTbls.Where(x => x.SearchEngineID == SearchEngineID).FirstOrDefault();
var JobID = context.BackgroundJob.Id;
NewExtraction.JobID = Convert.ToInt32(JobID);
NewExtraction.SeachEngineURL = SearchURL;
NewExtraction.Status = "Processing";
db.SaveChanges();
var LinkCollectionRefined = ExtractLinkFromThisPage(i, SearchURL, wc).Distinct().ToList();//.Skip(10);
foreach (var Link in LinkCollectionRefined)
{
using (Entities dbRefreshed = new Entities())
{
// I get the same old value here, even if I update the table manually, when I rerun, everything is fine.
var CurrentStatusOfExtraction = db.SearchEngineTbls.Where(x => x.SearchEngineID == NewExtraction.SearchEngineID).FirstOrDefault();
if (CurrentStatusOfExtraction.IsActive == false)
{
return;
}
}
}
#endregion
}

Dynamics CRM plugin -Pre Operation Update

I created a plug-in that calculates the pricing of the quote based on custom fields whenever update done on the quote entity.I executed the plugin on pre-opertaion update of the quote entity.I tried to debbug my plugin, it retrives null values.For example the value of the field "edm_CashAmount" contains 5000 but the code retrieved null. Kindly advise on the below. Thanks in advance.
namespace CRMPlugins.Plugins
{
using System;
using System.ServiceModel;
using Microsoft.Xrm.Sdk;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Microsoft.Crm.Sdk.Messages;
public class PrequoteUpdate : Plugin
{
public PrequoteUpdate()
: base(typeof(PrequoteUpdate))
{
base.RegisteredEvents.Add(new Tuple<int, string, string, Action<LocalPluginContext>>(20, "Update", "quote", new Action<LocalPluginContext>(ExecutePrequoteUpdate)));
}
protected void ExecutePrequoteUpdate(LocalPluginContext localContext)
{
if (localContext == null)
{
throw new ArgumentNullException("localContext");
}
Entity entity = null;
if (localContext.PluginExecutionContext.InputParameters.Contains("Target") && localContext.PluginExecutionContext.InputParameters["Target"] is Entity)
{
entity = (Entity)localContext.PluginExecutionContext.InputParameters["Target"];
}
else
{
return;
}
decimal instotal = 0;
Quote quote = entity.ToEntity<Quote>();
using (GeneratedEntities orgContext = new GeneratedEntities(localContext.OrganizationService))
{
var installements = (from b in orgContext.edm_installementSet
where b.GetAttributeValue<Guid>("edm_quote") == quote.QuoteId
select b);
foreach (var c in installements)
{
if (c.edm_Amount != null)
{
instotal += (decimal)c.new_Decimal;
}
}
}
decimal cash = 0;
if (quote.edm_CashAmount != null)
{
cash = Convert.ToDecimal(quote.GetAttributeValue<Money>("edm_cashamount").Value);
}
decimal check = 0;
if (quote.edm_CheckAmount != null)
{
check = Convert.ToDecimal(quote.GetAttributeValue<Money>("edm_checkamount").Value);
}
decimal credit = 0;
if (quote.edm_CreditCardAmount != null)
{
credit = Convert.ToDecimal(quote.GetAttributeValue<Money>("edm_creditcardamount").Value);
}
decimal gift = 0;
if (quote.new_GiftCard != null)
{
gift = Convert.ToDecimal(quote.GetAttributeValue<Money>("new_giftcard").Value);
}
decimal total = 0;
if (quote.TotalLineItemAmount != null)
{
total = Convert.ToDecimal(quote.GetAttributeValue<Money>("totallineitemamount").Value);
}
decimal currentbalane = 0;
if (quote.edm_CurrentBalane != null)
{
currentbalane = Convert.ToDecimal(quote.GetAttributeValue<Money>("edm_currentbalane").Value);
}
decimal DiscAmount = 0;
if (quote.DiscountAmount != null)
{
DiscAmount = Convert.ToDecimal(quote.GetAttributeValue<Money>("discountamount").Value);
}
decimal totalafterdiscount = total - DiscAmount;
decimal tax = (totalafterdiscount * 10) / 100;
decimal totalwithvat = totalafterdiscount + tax;
decimal paidamount = cash + check + credit + gift + instotal;
decimal remainingamount = totalwithvat - paidamount;
quote["edm_cashamount_1"] = new Money(tax);
quote["edm_totaltax"] = new Money(tax);
quote["edm_paidamount"] = new Money(paidamount);
quote["edm_remainingamount"] = new Money(remainingamount);
quote["edm_total"] = new Money(totalwithvat);
quote["edm_totalinstallements"] = new Money(instotal);
quote["edm_checkamount_1"] = new Money(totalwithvat);
}
}
}
for update plugins, if the value has not been changed then it will not appear in the target entity. Therefore you should create a pre entity image an include these values, or you can create a post entity image to see the state of the entity after the changes will be applied

How to find top 10 files changed most frequently in a period from TFS 2015 Version Control?

My team uses TFS 2015 as an ALM and Version Control system,I want to analyse which files change most frequently.
I found TFS didn't have this functionality out of the box, but TFS2015 has a REST API to query Changesets for files, like this below:
http://{instance}/tfs/DefaultCollection/_apis/tfvc/changesets?searchCriteria.itemPath={filePath}&api-version=1.0
There are thousands of files in my Project Repository, querying it one by one is not a good idea, are there any better solution to this question?
I don't think there's a defacto out of the box solution for your question, I've tried two separate approaches to solve your question, I initially focused on the REST API but later switched to the SOAP API to see what features are supported in it.
In all options below the following api should suffice:
Install the client API link
#NuGet
Install-Package Microsoft.TeamFoundationServer.ExtendedClient -Version 14.89.0 or later
In all options the following extension method is required ref
public static class StringExtensions
{
public static bool ContainsAny(this string source, List<string> lookFor)
{
if (!string.IsNullOrEmpty(source) && lookFor.Count > 0)
{
return lookFor.Any(source.Contains);
}
return false;
}
}
OPTION 1: SOAP API
With the SOAP API one is not explicitly required to limit the number of query results using the maxCount parameter as described in this excerpt of QueryHistory method's IntelliSense documentation:
maxCount: This parameter allows the caller to limit the number of
results returned. QueryHistory pages results back from the server on
demand, so limiting your own consumption of the returned IEnumerable
is almost as effective (from a performance perspective) as providing a
fixed value here. The most common value to provide for this parameter
is Int32.MaxValue.
Based on the maxCount documentation I made a decision to extract statistics for each of the products in my source control system since it may be of great value to see how much code flux there is for each system in the codebase independent of each other instead of limiting to 10 files across the entire codebase which could contain hundreds of systems.
C# REST and SOAP (ExtendedClient) api reference
Install the SOAP API Client link
#NuGet
Install-Package Microsoft.TeamFoundationServer.ExtendedClient -Version 14.95.2
limiting criteria are: Only scan specific paths in source
control since some systems in source control are older and possibly only there for historic purposes.
only certain file extensions included e.g. .cs, .js
certain filenames excluded e.g. AssemblyInfo.cs.
items extracted for each path: 10
from date: 120 days ago
to date: today
exclude specific paths e.g. folders containing release branches or
archived branches
using Microsoft.TeamFoundation.Client;
using Microsoft.TeamFoundation.VersionControl.Client;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
public void GetTopChangedFilesSoapApi()
{
var tfsUrl = "https://<SERVERNAME>/tfs/<COLLECTION>";
var domain = "<DOMAIN>";
var password = "<PASSWORD>";
var userName = "<USERNAME>";
//Only interested in specific systems so will scan only these
var directoriesToScan = new List<string> {
"$/projectdir/subdir/subdir/subdirA/systemnameA",
"$/projectdir/subdir/subdir/subdirB/systemnameB",
"$/projectdir/subdir/subdir/subdirC/systemnameC",
"$/projectdir/subdir/subdir/subdirD/systemnameD"
};
var maxResultsPerPath = 10;
var fromDate = DateTime.Now.AddDays(-120);
var toDate = DateTime.Now;
var fileExtensionToInclude = new List<string> { ".cs", ".js" };
var extensionExclusions = new List<string> { ".csproj", ".json", ".css" };
var fileExclusions = new List<string> { "AssemblyInfo.cs", "jquery-1.12.3.min.js", "config.js" };
var pathExclusions = new List<string> {
"/subdirToForceExclude1/",
"/subdirToForceExclude2/",
"/subdirToForceExclude3/",
};
using (var collection = new TfsTeamProjectCollection(new Uri(tfsUrl),
new NetworkCredential(userName: userName, password: password, domain: domain)))
{
collection.EnsureAuthenticated();
var tfvc = collection.GetService(typeof(VersionControlServer)) as VersionControlServer;
foreach (var rootDirectory in directoriesToScan)
{
//Get changesets
//Note: maxcount set to maxvalue since impact to server is minimized by linq query below
var changeSets = tfvc.QueryHistory(path: rootDirectory, version: VersionSpec.Latest,
deletionId: 0, recursion: RecursionType.Full, user: null,
versionFrom: new DateVersionSpec(fromDate), versionTo: new DateVersionSpec(toDate),
maxCount: int.MaxValue, includeChanges: true,
includeDownloadInfo: false, slotMode: true)
as IEnumerable<Changeset>;
//Filter changes contained in changesets
var changes = changeSets.SelectMany(a => a.Changes)
.Where(a => a.ChangeType != ChangeType.Lock || a.ChangeType != ChangeType.Delete || a.ChangeType != ChangeType.Property)
.Where(e => !e.Item.ServerItem.ContainsAny(pathExclusions))
.Where(e => !e.Item.ServerItem.Substring(e.Item.ServerItem.LastIndexOf('/') + 1).ContainsAny(fileExclusions))
.Where(e => !e.Item.ServerItem.Substring(e.Item.ServerItem.LastIndexOf('.')).ContainsAny(extensionExclusions))
.Where(e => e.Item.ServerItem.Substring(e.Item.ServerItem.LastIndexOf('.')).ContainsAny(fileExtensionToInclude))
.GroupBy(g => g.Item.ServerItem)
.Select(d => new { File=d.Key, Count=d.Count()})
.OrderByDescending(o => o.Count)
.Take(maxResultsPerPath);
//Write top items for each path to the console
Console.WriteLine(rootDirectory); Console.WriteLine("->");
foreach (var change in changes)
{
Console.WriteLine("ChangeCount: {0} : File: {1}", change.Count, change.File);
}
Console.WriteLine(Environment.NewLine);
}
}
}
OPTION 2A: REST API
(!! problem identified by OP led to finding a critical defect in v.xxx-14.95.4 of api) - OPTION 2B is the workaround
defect discovered in v.xxx to 14.95.4 of api: The TfvcChangesetSearchCriteria type contains an ItemPath property
which is supposed to limit the search to a specified directory. The
default value of this property is $/, unfortunately when used
GetChangesetsAsync will always use the root path of the tfvc source repository irrespective of the value set.
That said, this will still be a reasonable approach if the defect were to be fixed.
One way to limit the impact to your scm system would be to specify limiting criteria for the query/s using the TfvcChangesetSearchCriteria Type parameter of the GetChangesetsAsync member in the TfvcHttpClient type.
You do not particularly need to check each file in your scm system/project individually, checking the changesets for the specified period may be enough. Not all of the limiting values I used below are properties of the TfvcChangesetSearchCriteria type though so I've written a short example to show how I would do it i.e.
you can specify the maximum number of changesets to consider initially and the specific project you want to look at.
Note: The TheTfvcChangesetSearchCriteria type contains some additional properties that you may want to consider using.
In the example below I've used the REST API in a C# client and getting results from tfvc.
If your intention is to use a different client language and invoke the REST services directly e.g. JavaScript; the logic below should still give you some pointers.
//targeted framework for example: 4.5.2
using Microsoft.TeamFoundation.SourceControl.WebApi;
using Microsoft.VisualStudio.Services.Client;
using Microsoft.VisualStudio.Services.Common;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Threading.Tasks;
public async Task GetTopChangedFilesUsingRestApi()
{
var tfsUrl = "https://<SERVERNAME>/tfs/<COLLECTION>";
var domain = "<DOMAIN>";
var password = "<PASSWORD>";
var userName = "<USERNAME>";
//Criteria used to limit results
var directoriesToScan = new List<string> {
"$/projectdir/subdir/subdir/subdirA/systemnameA",
"$/projectdir/subdir/subdir/subdirB/systemnameB",
"$/projectdir/subdir/subdir/subdirC/systemnameC",
"$/projectdir/subdir/subdir/subdirD/systemnameD"
};
var maxResultsPerPath = 10;
var fromDate = DateTime.Now.AddDays(-120);
var toDate = DateTime.Now;
var fileExtensionToInclude = new List<string> { ".cs", ".js" };
var folderPathsToInclude = new List<string> { "/subdirToForceInclude/" };
var extensionExclusions = new List<string> { ".csproj", ".json", ".css" };
var fileExclusions = new List<string> { "AssemblyInfo.cs", "jquery-1.12.3.min.js", "config.js" };
var pathExclusions = new List<string> {
"/subdirToForceExclude1/",
"/subdirToForceExclude2/",
"/subdirToForceExclude3/",
};
//Establish connection
VssConnection connection = new VssConnection(new Uri(tfsUrl),
new VssCredentials(new Microsoft.VisualStudio.Services.Common.WindowsCredential(new NetworkCredential(userName, password, domain))));
//Get tfvc client
var tfvcClient = await connection.GetClientAsync<TfvcHttpClient>();
foreach (var rootDirectory in directoriesToScan)
{
//Set up date-range criteria for query
var criteria = new TfvcChangesetSearchCriteria();
criteria.FromDate = fromDate.ToShortDateString();
criteria.ToDate = toDate.ToShortDateString();
criteria.ItemPath = rootDirectory;
//get change sets
var changeSets = await tfvcClient.GetChangesetsAsync(
maxChangeCount: int.MaxValue,
includeDetails: false,
includeWorkItems: false,
searchCriteria: criteria);
if (changeSets.Any())
{
var sample = new List<TfvcChange>();
Parallel.ForEach(changeSets, changeSet =>
{
sample.AddRange(tfvcClient.GetChangesetChangesAsync(changeSet.ChangesetId).Result);
});
//Filter changes contained in changesets
var changes = sample.Where(a => a.ChangeType != VersionControlChangeType.Lock || a.ChangeType != VersionControlChangeType.Delete || a.ChangeType != VersionControlChangeType.Property)
.Where(e => e.Item.Path.ContainsAny(folderPathsToInclude))
.Where(e => !e.Item.Path.ContainsAny(pathExclusions))
.Where(e => !e.Item.Path.Substring(e.Item.Path.LastIndexOf('/') + 1).ContainsAny(fileExclusions))
.Where(e => !e.Item.Path.Substring(e.Item.Path.LastIndexOf('.')).ContainsAny(extensionExclusions))
.Where(e => e.Item.Path.Substring(e.Item.Path.LastIndexOf('.')).ContainsAny(fileExtensionToInclude))
.GroupBy(g => g.Item.Path)
.Select(d => new { File = d.Key, Count = d.Count() })
.OrderByDescending(o => o.Count)
.Take(maxResultsPerPath);
//Write top items for each path to the console
Console.WriteLine(rootDirectory); Console.WriteLine("->");
foreach (var change in changes)
{
Console.WriteLine("ChangeCount: {0} : File: {1}", change.Count, change.File);
}
Console.WriteLine(Environment.NewLine);
}
}
}
OPTION 2B
Note: This solution is very similar to OPTION 2A with the exception of a workaround implemented to fix a limitation in the REST client API library at time of writing.
Brief summary - instead of invoking client api library to get changesets this example uses a web request direct to the REST API to fetch changesets, thus additional types were needed to be defined to handle the response from the service.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Threading.Tasks;
using Microsoft.TeamFoundation.SourceControl.WebApi;
using Microsoft.VisualStudio.Services.Client;
using Microsoft.VisualStudio.Services.Common;
using System.Text;
using System.IO;
using Newtonsoft.Json;
public async Task GetTopChangedFilesUsingDirectWebRestApiSO()
{
var tfsUrl = "https://<SERVERNAME>/tfs/<COLLECTION>";
var domain = "<DOMAIN>";
var password = "<PASSWORD>";
var userName = "<USERNAME>";
var changesetsUrl = "{0}/_apis/tfvc/changesets?searchCriteria.itemPath={1}&searchCriteria.fromDate={2}&searchCriteria.toDate={3}&$top={4}&api-version=1.0";
//Criteria used to limit results
var directoriesToScan = new List<string> {
"$/projectdir/subdir/subdir/subdirA/systemnameA",
"$/projectdir/subdir/subdir/subdirB/systemnameB",
"$/projectdir/subdir/subdir/subdirC/systemnameC",
"$/projectdir/subdir/subdir/subdirD/systemnameD"
};
var maxResultsPerPath = 10;
var fromDate = DateTime.Now.AddDays(-120);
var toDate = DateTime.Now;
var fileExtensionToInclude = new List<string> { ".cs", ".js" };
var folderPathsToInclude = new List<string> { "/subdirToForceInclude/" };
var extensionExclusions = new List<string> { ".csproj", ".json", ".css" };
var fileExclusions = new List<string> { "AssemblyInfo.cs", "jquery-1.12.3.min.js", "config.js" };
var pathExclusions = new List<string> {
"/subdirToForceExclude1/",
"/subdirToForceExclude2/",
"/subdirToForceExclude3/",
};
//Get tfvc client
//Establish connection
VssConnection connection = new VssConnection(new Uri(tfsUrl),
new VssCredentials(new Microsoft.VisualStudio.Services.Common.WindowsCredential(new NetworkCredential(userName, password, domain))));
//Get tfvc client
var tfvcClient = await connection.GetClientAsync<TfvcHttpClient>();
foreach (var rootDirectory in directoriesToScan)
{
var changeSets = Invoke<GetChangeSetsResponse>("GET", string.Format(changesetsUrl, tfsUrl, rootDirectory,fromDate,toDate,maxResultsPerPath), userName, password, domain).value;
if (changeSets.Any())
{
//Get changes
var sample = new List<TfvcChange>();
foreach (var changeSet in changeSets)
{
sample.AddRange(tfvcClient.GetChangesetChangesAsync(changeSet.changesetId).Result);
}
//Filter changes
var changes = sample.Where(a => a.ChangeType != VersionControlChangeType.Lock || a.ChangeType != VersionControlChangeType.Delete || a.ChangeType != VersionControlChangeType.Property)
.Where(e => e.Item.Path.ContainsAny(folderPathsToInclude))
.Where(e => !e.Item.Path.ContainsAny(pathExclusions))
.Where(e => !e.Item.Path.Substring(e.Item.Path.LastIndexOf('/') + 1).ContainsAny(fileExclusions))
.Where(e => !e.Item.Path.Substring(e.Item.Path.LastIndexOf('.')).ContainsAny(extensionExclusions))
.Where(e => e.Item.Path.Substring(e.Item.Path.LastIndexOf('.')).ContainsAny(fileExtensionToInclude))
.GroupBy(g => g.Item.Path)
.Select(d => new { File = d.Key, Count = d.Count() })
.OrderByDescending(o => o.Count)
.Take(maxResultsPerPath);
//Write top items for each path to the console
Console.WriteLine(rootDirectory); Console.WriteLine("->");
foreach (var change in changes)
{
Console.WriteLine("ChangeCount: {0} : File: {1}", change.Count, change.File);
}
Console.WriteLine(Environment.NewLine);
}
}
}
private T Invoke<T>(string method, string url, string userName, string password, string domain)
{
var request = WebRequest.Create(url);
var httpRequest = request as HttpWebRequest;
if (httpRequest != null) httpRequest.UserAgent = "versionhistoryApp";
request.ContentType = "application/json";
request.Method = method;
request.Credentials = new NetworkCredential(userName, password, domain); //ntlm 401 challenge support
request.Headers[HttpRequestHeader.Authorization] = "Basic " + Convert.ToBase64String(Encoding.UTF8.GetBytes(domain+"\\"+userName + ":" + password)); //basic auth support if enabled on tfs instance
try
{
using (var response = request.GetResponse())
using (var responseStream = response.GetResponseStream())
using (var reader = new StreamReader(responseStream))
{
string s = reader.ReadToEnd();
return Deserialize<T>(s);
}
}
catch (WebException ex)
{
if (ex.Response == null)
throw;
using (var responseStream = ex.Response.GetResponseStream())
{
string message;
try
{
message = new StreamReader(responseStream).ReadToEnd();
}
catch
{
throw ex;
}
throw new Exception(message, ex);
}
}
}
public class GetChangeSetsResponse
{
public IEnumerable<Changeset> value { get; set; }
public class Changeset
{
public int changesetId { get; set; }
public string url { get; set; }
public DateTime createdDate { get; set; }
public string comment { get; set; }
}
}
public static T Deserialize<T>(string json)
{
T data = JsonConvert.DeserializeObject<T>(json);
return data;
}
}
Additional References:
C# REST and SOAP (ExtendedClient) api reference
REST API: tfvc Changesets
TfvcChangesetSearchCriteria type #MSDN

List changed files in TFS but ordered by number of changes applied

Is there a way of listing all the files that have changed on a project, but ordered by the number of changes made to each file?
I want to do a code review but only from a selection of the most active files.
You may try to use Excel as a TFS reporting tool like in this blog post:
http://www.woodwardweb.com/vsts/getting_started.html
ps. I found that link in this question.
I searched different ways and finally I found that best way is using TFS API
here is the code :
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using Microsoft.TeamFoundation.Client;
using Microsoft.TeamFoundation.VersionControl.Client;
namespace VControl
{
class Program
{
class SourceElement
{
public string filename;
public int numberOfModification;
}
static void Main(string[] args)
{
TfsTeamProjectCollection projectCollection = new
TfsTeamProjectCollection(new Uri("http://server:8080/tfs/ProjectCollection/"),
new System.Net.NetworkCredential("username", "password"));
projectCollection.EnsureAuthenticated();
Workspace workspace = null;
Boolean createdWorkspace = false;
String newFolder = String.Empty;
VersionControlServer versionControl = projectCollection.GetService<VersionControlServer>();
var teamProjects = new List<TeamProject>(versionControl.GetAllTeamProjects(false));
String workspaceName = String.Format("{0}-{1}", Environment.MachineName, "TestWorkspace");
try
{
workspace = versionControl.GetWorkspace(workspaceName, versionControl.AuthorizedUser);
}
catch (WorkspaceNotFoundException)
{
workspace = versionControl.CreateWorkspace(workspaceName, versionControl.AuthorizedUser);
createdWorkspace = true;
}
var serverFolder = String.Format("$/{0}", teamProjects[0].Name) + "/solutionFolder/";
var localFolder = Path.Combine(Path.GetTempPath(), "localFolder") + "/solutionFolder/";
var workingFolder = new WorkingFolder(serverFolder, localFolder);
// Create a workspace mapping.
workspace.CreateMapping(workingFolder);
if (!workspace.HasReadPermission)
{
throw new SecurityException(
String.Format("{0} does not have read permission for {1}",
versionControl.AuthorizedUser, serverFolder));
}
// Get the files from the repository.
workspace.Get();
string[] directories = Directory.GetDirectories(workspace.Folders[0].LocalItem);
FileStream outputFile = new FileStream("result.txt", FileMode.Create);
StreamWriter writer = new StreamWriter(outputFile);
List<SourceElement> fileLiset = new List<SourceElement>();
foreach (string dir in directories)
{
foreach (string file in Directory.GetFiles(dir))
{
string filenamae = System.IO.Path.GetFileName(file);
Item source = versionControl.GetItem(file);
System.Collections.IEnumerable history = versionControl.QueryHistory(file,
VersionSpec.Latest, 0, RecursionType.Full, null, null, null, 300, true, true, false, false);
int numberOfModification = 0;
foreach (var item in history)
numberOfModification++;
SourceElement fileElement = new SourceElement();
fileElement.filename = filenamae;
fileElement.numberOfModification = numberOfModification;
fileLiset.Add(fileElement);
}
}
var sortedList = fileLiset.OrderBy(x=> x.numberOfModification);
// Loop through keys.
foreach (var key in sortedList)
{
writer.WriteLine("{0}: {1}", key.filename, key.numberOfModification);
}
writer.Close();
}
}
}