11

我正在寻找一种在我的应用程序的两个版本之间自动生成变更日志(实际上是工作项列表)的方法。我有两个版本的应用程序,v1 和 v2,每个版本都由 TFS 2010 中的标签(LABEL1 和 LABEL2)标识,这是我在构建应用程序设置之前手动创建的。我有一个分支系统,这意味着我有一个主干,大部分错误都已修复,还有一个分支主要使用主干合并应用补丁(但也有一些仅在分支上与主干无关的修复) . 我的应用程序的两个版本(v1 和 v2)是来自分支的版本。

我希望 TFS 2010 能够返回这两个标签之间已修复的错误列表(即类型 = 已关闭和验证的错误的工作项列表)。

我尝试使用 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 不会改变任何东西。

然后我终于尝试用由变更集定义的 VersionSpecs 替换由标签定义的 VersionSpecs:

VersionSpec sFrom = VersionSpec.ParseSingleSpec("C5083", null);
VersionSpec sTo = VersionSpec.ParseSingleSpec("C5276", null);

我的代码终于可以工作了。

所以我的问题是:如何使用标签获得相同的结果,标签是我用来识别版本的 TFS 对象?如果不可能,我应该如何识别 TFS 2010 中的版本?谢谢。

顺便说一句,我在 stackoverflow 上发现了一些问题,但没有一个给我带标签的答案。例如: 问题示例

4

4 回答 4

2

我认为http://tfschangelog.codeplex.com/可以在这里为您提供帮助。

TFS ChangeLog 应用程序允许用户从 TFS 自动生成发行说明。用户必须提供有关他们的项目、分支和变更集范围的信息,然后 TFS ChangeLog 应用程序将从给定范围内的每个变更集中提取信息,以及与此类变更集相关的所有工作项。即,它将从开始变更集到结束变更集,并将提取有关每个变更集的数据以及 XML 文件中的相关工作项。

然后,用户可以使用自己的转换逻辑,包括过滤、排序、样式、输出格式等来生成发布说明报告。

我想在这里添加的另一件事与 TFS 中的标签有关。标签基本上是与变更集分配/关联的。目前,TFS ChangeLog 应用程序不支持使用标签来定义起点和终点,但它确实支持可用作变通解决方案的变更集。

希望这是有用的。

于 2012-05-21T22:47:06.610 回答
1

一般来说,在任何 SCM 中定义时间点的绝对方法显然是 checkin-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.

于 2011-12-08T13:03:07.313 回答
1

我一直和你一样的情况。我还希望包含合并变更集中的工作项。我只包括已完成的工作项。此外,如果同一个工作项链接到多个变更集,则仅报告最后一个变更集。我在 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();
    }
  }
}
于 2011-12-18T10:30:27.177 回答
0

这个问题让我更接近解决我遇到的类似问题。

使用类型LabelVersionSpec而不是VersionSpec标签版本。

代替:

VersionSpec sFrom = VersionSpec.ParseSingleSpec("LLABEL1", null);
VersionSpec sTo = VersionSpec.ParseSingleSpec("LLABEL2", null);

和:

LabelVersionSpec sFrom = new LabelVersionSpec("LLABEL1");
LabelVersionSpec sTo = new LabelVersionSpec("LLABEL2");
于 2012-05-30T21:43:14.100 回答