- html - 出于某种原因,IE8 对我的 Sass 文件中继承的 html5 CSS 不友好?
- JMeter 在响应断言中使用 span 标签的问题
- html - 在 :hover and :active? 上具有不同效果的 CSS 动画
- html - 相对于居中的 html 内容固定的 CSS 重复背景?
我的团队使用 TFS 2015 作为 ALM 和版本控制系统,我想分析哪些文件更改最频繁。
我发现 TFS 没有开箱即用的这个功能,但是 TFS2015 有一个 REST API 来查询文件的变更集,如下所示:
http://{instance}/tfs/DefaultCollection/_apis/tfvc/changesets?searchCriteria.itemPath={filePath}&api-version=1.0
我的Project Repository有几千个文件,一个一个查询不是个好主意,请问有没有更好的解决办法?
最佳答案
我认为您的问题没有现成的解决方案,我尝试了两种不同的方法来解决您的问题,我最初专注于 REST API
但后来切换到SOAP API
以查看其中支持的功能。
在下面的所有选项中,以下 api 应该足够了:
Install the client API link @NuGet
Install-Package Microsoft.TeamFoundationServer.ExtendedClient -Version 14.89.0 or later
在所有选项中都需要以下扩展方法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;
}
}
选项 1:SOAP API
对于 SOAP API,没有明确要求使用 maxCount
参数限制查询结果的数量,如 QueryHistory
方法的 IntelliSense< 摘录中所述/strong> 文档:
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 returnedIEnumerable
isalmost as effective (from a performance perspective)
as providing a fixed value here. The most common value to provide for this parameter isInt32.MaxValue
.
根据 maxCount
文档,我决定为我的源代码控制系统中的每个产品提取统计信息,因为它可能很有值(value),可以了解每个系统有多少代码流量在彼此独立的代码库中,而不是在可能包含数百个系统的整个代码库中限制为 10 个文件。
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);
}
}
}
选项 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 anItemPath
property which is supposed to limit the search to a specified directory. The default value of this property is$/
, unfortunately when usedGetChangesetsAsync
will always use the root path of thetfvc
source repository irrespective of the value set.That said, this will still be a reasonable approach if the defect were to be fixed.
限制对 scm 系统影响的一种方法是使用 GetChangesetsAsync
成员的 TfvcChangesetSearchCriteria
类型参数为查询指定限制条件 TfvcHttpClient
类型。
您不需要单独检查 scm 系统/项目中的每个文件,检查指定时间段的变更集可能就足够了。并非我在下面使用的所有限制值都是 TfvcChangesetSearchCriteria
类型的属性,所以我写了一个简短的示例来展示我将如何做到这一点,即您可以指定最初要考虑的变更集的最大数量以及您要查看的特定项目。
注意:TheTfvcChangesetSearchCriteria
类型包含一些您可能要考虑使用的附加属性。
在下面的示例中,我在 C# 客户端中使用了 REST API 并从 tfvc 获取结果。
如果您打算使用不同的客户端语言并直接调用 REST 服务,例如JavaScript
;下面的逻辑应该仍然可以给你一些指示。
//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);
}
}
}
选项 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;
}
}
其他引用资料:
关于version-control - 如何从 TFS 2015 版本控制中找到一段时间内更改最频繁的前 10 个文件?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/36287349/
管理项目时,我可以在团队项目级别编辑权限。 Web 界面允许管理: 团队 TFS 组 但是,我找不到有关这些不同之处的任何指导。根据经验,我希望一个团队成为多个 TFS 组的一部分,但似乎一个团队可以
如何从 http://tfs.visualstudio.com 上托管的 TFS 迁移到本地版本的 TFS?我想用Visual Studio Team Foundation Server Expres
在 Team Foundation (TFS2017) 上,您可以连接到 TFS 实例的构建代理的最大数量是多少? 最佳答案 目前还没有任何官方文档声明 TFS 构建代理数量的限制。也没有得到任何相关
我试图了解 TFS 2008(和 2010,如果它不同)如何存储和传达变更集中一组更改的详细信息。 现在,当我提交一个 Subversion 托管项目时,客户端会将差异发送到服务器。因此,如果我添加了
基本上,我有 2 个构建定义,一个构建和部署一些数据库项目,另一个构建和部署网站。 我想做的是能够构建/部署数据库,如果没有任何错误,则构建和部署网络应用程序。 这个想法是,如果数据库部署由于某种原因
我们有一个 CI 构建服务器进行自动构建,并希望在程序集版本中包含当前的冲刺/迭代编号。是否有环境变量或简单的方法可以在构建过程中获取当前的冲刺编号? TFS On Permise 2015 更新 3
我正在探索在我们的下一个项目中使用 TFS 进行软件过程的可行性。一种需求是能够从 TFS 中的各个链接需求创建需求文档。我目前在 Agile 和 CMMI 中都有模型项目,其中有一组与父子关系相关的
我的公司有一个本地 TFS 2017 实例。我知道如何通过 Web 界面安装和更新扩展程序,但这很乏味。我想知道是否有更快的方法来使用 tfs-cli . 最佳答案 由于某种原因,我花了一段时间才弄清
关闭。这个问题不满足Stack Overflow guidelines .它目前不接受答案。 想改善这个问题吗?更新问题,使其成为 on-topic对于堆栈溢出。 3年前关闭。 Improve thi
似乎没有太多关于团队管理员的文档,但是如果我进入我的团队页面并在左侧添加一个用户作为管理员,这并没有让他们成为项目管理员,只是一个叫做团队管理员。有权限的团队管理员和项目管理员有什么区别? 最佳答案
我们计划从 Tfs 2012 升级到 Tfs 2013。谁能帮助我了解它们之间的流程模板差异?我们将所有三个流程模板用于不同的项目。 最佳答案 变化非常小,除了: 引入投资组合积压。 测试计划和测试套
我们已经成功构建了 TFS 2010 基础架构和第一个使用 Visual Studio 2010 的虚拟机。现在我有一个非常简单的问题。如何将解决方案从我们现有的 TFS 2008 转移到新的 201
我们公司正在使用 Team Foundation Server 2015。是否可以在每个拉取请求中自动添加一个或几个人作为审阅者?作为必需的默认审阅者? 最佳答案 是的,您可以很容易地将默认审阅者添加
通过管理员帐户导航到 TFS 2013 集合的项目页面返回此错误: http://server:8080/tfs/DMS/ProjectX Error The page you are looking
TFS 中等效的拉取请求是什么? 我特别询问跨团队协作,其中有一个团队负责产品,但其他团队可以处理自己的副本并提交“拉取请求”。 所以核心团队仍然拥有产品,但其他团队仍然可以为它做出贡献。 最佳答案
当尝试将项目的较新版本与较旧版本合并时,我得到“该项目已在目标分支中删除”,它给了我三个选项: Restore File And AutoMerge Keep Target Branch Versio
我必须将一个项目恢复到上周的版本。所以我按日期得到了一个特定的版本,但是现在如果我在文件中添加一个空格,它会 check out 并获得最新版本。 我去了工具 -> 选项 -> 源代码控制 -> VS
如何在没有 Visual Studio 的情况下将 VS 项目添加到源代码管理 (TFS)?我还获得了对 tfs 项目的 Web 访问权限,但我没有看到任何上传选项或创建子项目选项(我看到我的主项
我只是想知道是否有一种方法可以分析 TFS,以找出针对特定开发人员的代码覆盖率结果。 假设我想确定我团队中一位开发人员的代码覆盖率统计数据。 最佳答案 它不支持开箱即用。 不要认为它会在 future
我想使用尚未映射到 TFS 中的工作项的 .MPP MS Project 2007 计划,TFS 项目是空的,我的意思是根本没有工作项。我要同步 http://localhost:8080/tfs/这
我是一名优秀的程序员,十分优秀!