我正在寻找一种在我的应用程序的两个版本之间自动生成变更日志(实际上是一个工作项列表)的方法。我有两个版本的应用程序,v1和v2,每个版本都由TFS 2010 (LABEL1和LABEL2)中的标签标识,这是我在构建应用程序设置之前手动创建的。我有一个分支系统,这意味着我有一个修复了大多数bug的主干,还有一个补丁主要使用来自主干的合并应用的分支(但也有一些修复只在分支上,与主干无关)。我的应用程序的两个版本(v1和v2)是来自分支的版本。
我希望TFS 2010能够返回修复的错误列表。在这两个标签之间,类型为"已关闭并已验证的Bug"的工作项列表。
我尝试使用TFS 2010的web UI或使用Visual Studio来实现这一点,但我没有找到任何方法。
然后我尝试使用以下命令行向tf.exe询问历史记录:
tf history /server:http://server_url/collection_name "$/project_path" /version:LLABEL1~LLABEL2 /recursive /noprompt /format:brief
,其中LABEL1是与应用程序v1的源代码相关联的标签,LABEL2是与应用程序v2的源代码相关联的标签。它实际上在两个方面失败了:-命令行只返回变更集列表,而不是关联的已关闭工作项列表-更改集列表只包含我在分支本身上应用的更改集,不包含我也应用和主干然后合并到分支的更改集。设置或不设置"/slotmode"参数不会改变任何东西。
我试着写一段c#代码来检索工作项列表(不是变更集列表):
var tfs = TfsTeamProjectCollectionFactory.GetTeamProjectCollection(new Uri("http://server_url/collection_name"));
VersionControlServer controlServer = tfs.GetService<VersionControlServer>();
VersionControlServer vcs = tfs.GetService<VersionControlServer>();
VersionSpec sFrom = VersionSpec.ParseSingleSpec("LLABEL1", null);
VersionSpec sTo = VersionSpec.ParseSingleSpec("LLABEL2", null);
var changesets = vcs.QueryHistory(
"$/project_path",
sTo,
0,
RecursionType.Full,
null,
sFrom,
sTo,
int.MaxValue,
true,
false); // Slotmode to false
Dictionary<int, WorkItem> dico = new Dictionary<int, WorkItem>();
foreach (Changeset set in changesets)
{
foreach (WorkItem zz in set.WorkItems)
{
if (!dico.ContainsKey(zz.Id))
{
dico.Add(zz.Id, zz);
}
}
}
foreach (KeyValuePair<int, WorkItem> pair in dico.OrderBy(z => z.Key))
{
Console.WriteLine(string.Format("ID: {0}, Title: {1}", pair.Key, pair.Value.Title));
}
这实际上是有效的,我得到了两个标签之间的工作项列表,这实际上是我想要的。但是只有与在分支本身上提交的变更集相关的工作项才会被考虑进去:在主干上解决然后合并到分支的"Bug"类型的工作项不会出现。Slotmode不改变任何东西
然后我最终尝试用变更集定义的versionspec替换由标签定义的versionspec:
VersionSpec sFrom = VersionSpec.ParseSingleSpec("C5083", null);
VersionSpec sTo = VersionSpec.ParseSingleSpec("C5276", null);
和我的代码最终工作。
所以我的问题是:我怎么能得到相同的结果与标签,这是我用来识别一个版本的TFS对象?如果这是不可能的,我应该如何确定一个版本在TFS 2010?Thx .
顺便说一句,我在stackoverflow上发现了一些问题,但是没有一个人给我带标签的答案。例如:问题示例
我想http://tfschangelog.codeplex.com/可能可以在这里帮助你。
TFS ChangeLog应用程序允许用户从TFS自动生成发布说明。用户必须提供关于他们的项目、分支和变更集范围的信息,然后TFS ChangeLog应用程序将从给定范围内的每个变更集和所有相关的工作项中提取信息。也就是说,它将从开始的变更集移动到结束的变更集,并在XML文件中提取关于每个变更集的数据以及相关的工作项。
用户可以使用他们自己的转换逻辑,包括过滤器、排序、样式、输出格式等来生成发布说明报告。
我想在这里添加的另一件事将与TFS中的标签有关。标签基本上是与变更集分配/关联的。目前,TFS ChangeLog应用程序不支持标签来定义起始点和结束点,但它支持更改集,可以作为一个变通的解决方案。
希望这是有用的。
一般来说,在任何SCM中定义时间点的绝对方法显然是签入id。
使用标签来抽象这一点,在TFS中不是最优的,正如这里所讨论的&在这里。更好的方法是使用构建,特别是在现代CI环境中。
为了检索给定构建中包含的最大更改集,您必须这样做:
using System;
using System.Collections.Generic;
using Microsoft.TeamFoundation.Build.Client;
using Microsoft.TeamFoundation.Client;
namespace GetChangesetsFromBuild
{
class Program
{
static void Main()
{
TfsTeamProjectCollection tpc = TfsTeamProjectCollectionFactory.GetTeamProjectCollection(new Uri("http://TFSServer:8080/Name"));
IBuildServer bs = (IBuildServer)tpc.GetService(typeof(IBuildServer));
IBuildDetail build = bs.GetAllBuildDetails(new Uri("vstfs:///..."));
List<IChangesetSummary> associatedChangesets = InformationNodeConverters.GetAssociatedChangesets(build);
int idMax = associatedChangesets[0].ChangesetId;
}
}
}
上述方法的一个难点是检索您感兴趣的构建的BuildUri。为了获得这些信息,你可以这样做:
IBuildDetail[] builds = bs.QueryBuilds("TeamPorjectName", "yourBuildDefinitionName")
,然后检索对您重要的Uri。
如果您最终坚持使用标签,这也是一个很好的工具:除了Uri
,每个build[]
也有一个LabelName
。
我也遇到过和你一样的情况。我还希望包括来自合并变更集的工作项。我只包括已完成的工作项。同样,如果相同的Work Item被链接到多个变更集,则只报告最后一个变更集。我在CI设置中使用它;并为每个构建创建一个变更日志。然后可以将List<ChangeInfo>
导出为XML/HTML/txt文件。下面是我的解决方案:
namespace TFSChangelog
{
public class TFSChangelogGenerator
{
private const string workItemDoneText = "Done";
/// <summary>
/// This class describes a change by:
/// Changeset details
/// and
/// WorkItem details
/// </summary>
public class ChangeInfo
{
#region Changeset details
public DateTime ChangesetCreationDate { get; set; }
public int ChangesetId { get; set; }
#endregion
#region WorkItem details
public string WorkItemTitle { get; set; }
public int WorkItemId { get; set; }
#endregion
}
public static List<ChangeInfo> GetChangeinfo(string tfsServer, string serverPath, string from, string to)
{
// Connect to server
var tfs = TfsTeamProjectCollectionFactory.GetTeamProjectCollection(new Uri(tfsServer));
tfs.Connect(ConnectOptions.None);
var vcs = tfs.GetService<VersionControlServer>();
// Create versionspec's
VersionSpec versionFrom = null;
if (!string.IsNullOrEmpty(from))
versionFrom = VersionSpec.ParseSingleSpec(from, null);
VersionSpec versionTo = VersionSpec.Latest;
if (!string.IsNullOrEmpty(to))
versionTo = VersionSpec.ParseSingleSpec(to, null);
// Internally used dictionary
var changes = new Dictionary<int, ChangeInfo>();
// Find Changesets that are checked into the branch
var directChangesets = vcs.QueryHistory(
serverPath,
VersionSpec.Latest,
0,
RecursionType.Full,
null,
versionFrom,
versionTo,
Int32.MaxValue,
true,
false
).Cast<Changeset>();
foreach (var changeset in directChangesets)
{
foreach (var workItem in changeset.WorkItems.Where(workItem => workItem.State == workItemDoneText))
{
if (changes.ContainsKey(workItem.Id))
{
if (changeset.ChangesetId < changes[workItem.Id].ChangesetId) continue;
}
changes[workItem.Id] = new ChangeInfo { ChangesetId = changeset.ChangesetId, ChangesetCreationDate = changeset.CreationDate, WorkItemId = workItem.Id, WorkItemTitle = workItem.Title };
}
}
// Find Changesets that are merged into the branch
var items = vcs.GetItems(serverPath, RecursionType.Full);
foreach (var item in items.Items)
{
var changesetMergeDetails = vcs.QueryMergesWithDetails(
null,
null,
0,
item.ServerItem,
VersionSpec.Latest,
0,
versionFrom,
versionTo,
RecursionType.Full
);
foreach (var merge in changesetMergeDetails.Changesets)
{
foreach (var workItem in merge.WorkItems.Where(workItem => workItem.State == workItemDoneText))
{
if (changes.ContainsKey(workItem.Id))
{
if (merge.ChangesetId < changes[workItem.Id].ChangesetId) continue;
}
changes[workItem.Id] = new ChangeInfo { ChangesetId = merge.ChangesetId, ChangesetCreationDate = merge.CreationDate, WorkItemId = workItem.Id, WorkItemTitle = workItem.Title };
}
}
}
// Return a list sorted by ChangesetId
return (from entry in changes orderby entry.Value.ChangesetId descending select entry.Value).ToList();
}
}
}
这个问题让我更接近于解决我遇到的一个类似的问题。
标签版本使用LabelVersionSpec
类型而不是VersionSpec
。
替换:
VersionSpec sFrom = VersionSpec.ParseSingleSpec("LLABEL1", null);
VersionSpec sTo = VersionSpec.ParseSingleSpec("LLABEL2", null);
:
LabelVersionSpec sFrom = new LabelVersionSpec("LLABEL1");
LabelVersionSpec sTo = new LabelVersionSpec("LLABEL2");