TFS 2010: как создать журнал изменений (т. Е. Список рабочих элементов) между двумя выпусками приложения с помощью меток?

Я ищу способ автоматически создавать журнал изменений (фактически список рабочих элементов) между двумя выпусками моего приложения. У меня есть две версии моего приложения, v1 и v2, каждая из которых обозначена меткой в ​​TFS 2010 (LABEL1 и LABEL2), которую я создал вручную перед созданием настроек моего приложения. У меня есть система ветвления, что означает, что у меня есть ствол, в котором исправлено большинство ошибок, и ветка, в которой исправления применяются в основном с использованием слияний из ствола (но есть также некоторые исправления в ветке, которые не касаются ствола) . Две версии моего приложения (v1 и v2) - это версии из ветки.

Я бы хотел, чтобы TFS 2010 могла возвращать список исправленных ошибок (т. Е. Список рабочих элементов с type = Bug, которые закрыты и проверены) между этими двумя ярлыками.

Я пытался добиться этого с помощью веб-интерфейса TFS 2010 или 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));
}

Это действительно работает, я получаю список рабочих элементов между двумя моими ярлыками, что на самом деле то, что я хотел. Но учитываются только рабочие элементы, связанные с наборами изменений, которые были зафиксированы в самой ветке: рабочие элементы типа «Ошибка», которые были решены в стволе, а затем объединены с ветвью, не отображаются. Slotmode ничего не меняет.

Затем я, наконец, попытался заменить VersionSpecs, которые были определены меткой, на VersionSpecs, которые определены наборами изменений:

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

И мой код наконец-то заработал.

Итак, мой вопрос: как я могу получить тот же результат с метками, которые являются объектами TFS, которые я использую для идентификации версии? Если это невозможно, как мне определить версию в TFS 2010? Спасибо.

Кстати, я нашел несколько вопросов по stackoverflow, но ни один из них не дал мне ответов с метками. Например: Вопрос пример


person ken2k    schedule 08.12.2011    source источник


Ответы (4)


Я думаю, что http://tfschangelog.codeplex.com/ может вам здесь помочь.

Приложение TFS ChangeLog позволяет пользователям автоматически создавать заметки о выпуске из TFS. Пользователи должны будут предоставить информацию о своем проекте, ветке и диапазоне набора изменений, а затем приложение TFS ChangeLog будет извлекать информацию из каждого набора изменений в заданном диапазоне и всех связанных рабочих элементов для таких наборов изменений. то есть он будет перемещаться от начального набора изменений до конечного набора изменений и будет извлекать данные о каждом наборе изменений вместе со связанными рабочими элементами в XML-файле.

Затем пользователи могут использовать свою собственную логику преобразования, включая фильтр, сортировку, стили, форматирование вывода и т. Д., Для создания отчета с примечаниями к выпуску.

Еще одна вещь, которую я хотел бы добавить, будет связана с метками в TFS. Ярлыки в основном присваиваются / связаны с наборами изменений. В настоящее время приложение TFS ChangeLog не поддерживает метки для определения начальной и конечной точки, но поддерживает набор изменений, который можно использовать в качестве обходного решения.

Надеюсь, это будет полезно.

person Dharmesh Shah    schedule 21.05.2012

В общем, абсолютным методом определения моментов времени в любом 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.

person pantelif    schedule 08.12.2011
comment
Я полностью согласен, но, к сожалению, я не могу создавать свои приложения с помощью TFS 2010 (я использую проекты vdproj, которые не поддерживаются TFS). Поэтому я не могу определить версию своего приложения с помощью сборки. Я действительно мог бы использовать идентификатор набора изменений, но для этого потребовалось бы вести отдельный список, в котором сохраняются соответствия между версиями моего приложения и связанными идентификаторами набора изменений (я имею в виду, что нет никакого способа узнать, что набор изменений 4597 на самом деле был последней версией для файлов, которые были использованы для компиляции версии 1.2 моего приложения). Должен ли я настаивать на использовании меток, поскольку в настоящее время я использую их как отдельные моменты времени (я не редактирую метки)? - person ken2k; 08.12.2011
comment
Вы можете оставить ярлыки, если абсолютно уверены, что они не будут редактироваться. Лично я бы не стал на это рассчитывать. Могу я спросить, как вы создаете свое приложение? - person pantelif; 08.12.2011
comment
Наш процесс CI создает наше приложение (и запускает тесты и т. Д.) Каждый раз, когда мы регистрируемся (закрытая регистрация) и каждую ночь. Но то, что создано TFS, - это наше приложение само (т.е. служба Windows, приложение ASP.Net ...), а не установщики (MSI), которые мы отправляем нашему клиенту, когда мы выпускаем новый версия. Для создания файлов MSI, которые мы отправляем нашему клиенту, мы вручную используем файл * .bat, который вызывает devenv.exe для компиляции проектов vdproj. Но это полностью вне нашего процесса CI. - person ken2k; 08.12.2011
comment
По поводу этикеток согласен; Я особо на это не рассчитываю, но пока я не нашел ничего другого, чтобы правильно идентифицировать версию. Возможно, лучший подход, чем использование меток, - создать новую ветку для каждой версии, а затем заблокировать созданную ветку (только для чтения). Затем, чтобы получить журнал изменений между двумя версиями, я, вероятно, мог бы получить последний идентификатор набора изменений для предыдущей и новой ветвей, а затем запросить у TFS историю между этими наборами изменений? - person ken2k; 08.12.2011

Я был в той же ситуации, что и ты. Я также хочу, чтобы были включены Рабочие элементы из объединенных наборов изменений. Я включаю только те рабочие элементы, которые Готово. Также, если один и тот же Рабочий элемент связан с несколькими наборами изменений, сообщается только последний набор изменений. Я использую это в настройке 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();
    }
  }
}
person Morten Frederiksen    schedule 18.12.2011
comment
Спасибо! К сожалению, он не работает при попытке указать from и to с помощью меток (например, LLabel1): он генерирует исключение VersionControlException на QueryMergesWithDetails. Я также заметил, что цикл foreach (var item в items.Items) довольно медленный? - person ken2k; 19.12.2011
comment
Привет. Я не использую ярлыки. Но попробуйте использовать LabelVersionSpec msdn.microsoft.com/en-us/library/bb171081 .aspx. Класс является производным от VersionSpec. И вы правы, поиск всех объединенных изменений может быть медленным. Я выполняю это на сервере TFS, и это не быстро, но нормально. Информация важна для нас, поэтому штраф по времени - это нормально. - person Morten Frederiksen; 19.12.2011
comment
Фактически ParseSingleSpec возвращает экземпляр LabelVersionSpec (этот класс наследуется от VersionSpec). Но по неизвестной причине QueryMergesWithDetails выдает исключение при передаче LabelVersionSpec в качестве входных параметров. - person ken2k; 19.12.2011
comment
Есть ли у вас пробелы в названии ярлыка? connect.microsoft.com/VisualStudio/feedback/details/289048/ - person Morten Frederiksen; 19.12.2011
comment
Дополнительная информация: social.msdn.microsoft .com / Forums / is / tfsversioncontrol / thread /. - person Morten Frederiksen; 19.12.2011

Этот вопрос приблизил меня к решению похожей проблемы, с которой я столкнулся.

Для версий этикеток используйте тип LabelVersionSpec вместо VersionSpec.

Заменять:

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

с участием:

LabelVersionSpec sFrom = new LabelVersionSpec("LLABEL1");
LabelVersionSpec sTo = new LabelVersionSpec("LLABEL2");
person CodePoet    schedule 30.05.2012
comment
Я думаю, что оба кода эквивалентны. ParseSingleSpec возвращает объект VersionSpec, но на самом деле VersionSpec является абстрактным классом. Итак, ParseSingleSpec возвращает либо ChangesetVersionSpec, DateVersionSpec, _7 _... и т. Д. экземпляр (который наследуется от VersionSpec) в зависимости от анализируемой строки (LabelVersionSpec в приведенном выше случае). - person ken2k; 31.05.2012