2010-06-30 135 views
4

是否可以在TFS内将工作项目从一个项目移动到另一个项目?我看过一个复制选项,但没有移动。另外,如果可能的话,WI历史的含义是什么?在TFS中移动工作项目

从2008年我发现这个article似乎说不是,但我想知道自那以后是否有任何进展。

回答

1

这是不可能移动,只是复制。我们这样做的方式是,我们复印,链接原件,然后关闭原件作为废弃。您也可以创建副本和TF销毁原始文件,但您将失去所有历史记录。

如果你愿意,你可以变得非常花哨,并创建自己的“移动”工具,复制工作项目和所有历史,然后关闭(或销毁)旧工具。看起来像是过度杀伤你可能不需要经常这样做的事情。

+0

我错了,当我认为你不能通常删除现有的工作项目?任何被创建的东西都会永远留在TFS中,我现在知道的唯一选择是“销毁”工作项类型,而这又应该删除所有特定类型的工作项。 – Gorgsenegger 2012-06-13 12:34:27

+0

是的。您可以销毁该工作项目,但不能将其删除。这就是为什么你应该真的关闭“过时”并链接新的工作项目。我只会摧毁,如果它是一个明显的错误,并没有历史和没有签入/建立/等。分配给它。 – Robaticus 2012-06-13 12:44:22

0

拉尔斯·威廉森写了WorkItemMigrator - >http://larsw.codeplex.com/SourceControl/list/changesets

良好起点实用,你可以自定义您的需求。我们用它将100个左右的工作项目分解为一个新项目。这是我结束的程序。将查询修改为要迁移的项目的子集。

namespace WorkItemMigrator 
{ 
using System; 
using System.Collections.Generic; 
using System.IO; 
using System.Linq; 
using System.Net; 
using System.Text; 
using Microsoft.TeamFoundation.Client; 
using Microsoft.TeamFoundation.Framework.Common; 
using Microsoft.TeamFoundation.Server; 
using Microsoft.TeamFoundation.WorkItemTracking.Client; 

class Program 
{ 

    #region Members 
    private static readonly Dictionary<Uri, TfsTeamProjectCollection> Collections = new Dictionary<Uri, TfsTeamProjectCollection>(); 
    private static readonly Uri SourceCollectionUri = new Uri("http://your.domain.com:8080/tfs/DefaultCollection"); 
    private static readonly Uri TargetCollectionUri = new Uri("http://your.domain.com:8080/tfs/DefaultCollection"); 
    private const String Areas = "ProjectModelHierarchy"; 
    private const string Iterations = "ProjectLifecycle"; 
    private const string TargetProjectName = "TargetProject"; 
    private const string MicrosoftVstsCommonStackRankFieldName = "Microsoft.VSTS.Common.StackRank"; 
    private const string MicrosoftVstsCommonPriority = "Microsoft.VSTS.Common.Priority"; 
    private const string TargetWorkItemType = "User Story"; 
    private const string Wiql = "SELECT [System.Id], [System.State], [System.Title], [System.AssignedTo], [System.WorkItemType], [Microsoft.VSTS.Common.Priority], " + 
         "[System.IterationPath], [System.AreaPath], [System.History], [System.Description] " + 
         "FROM WorkItems WHERE [System.TeamProject] = 'SourceProject' AND " + 
         "[System.State] = 'Active' " + 
         "ORDER BY [System.Id]"; 

    private static WorkItemTypeCollection WorkItemTypes; 
    private static Dictionary<int, int> WorkItemIdMap = new Dictionary<int, int>(); 

    #endregion 
    static void Main() 
    { 
    var createAreasAndIterations = GetRunMode(); 

    var sourceWorkItemStore = GetSourceWorkItemStore(); 

    var sourceWorkItems = sourceWorkItemStore.Query(Wiql); 

    var targetWorkItemStore = GetTargetWorkItemStore(); 
    var targetProject = targetWorkItemStore.Projects[TargetProjectName]; 


    WorkItemTypes = targetProject.WorkItemTypes; 

    foreach (WorkItem sourceWorkItem in sourceWorkItems) 
    { 
     if (createAreasAndIterations) 
     { 
     Console.WriteLine(); 
     EnsureThatStructureExists(TargetProjectName, Areas, sourceWorkItem.AreaPath.Substring(sourceWorkItem.AreaPath.IndexOf("\\") + 1)); 
     EnsureThatStructureExists(TargetProjectName, Iterations, sourceWorkItem.IterationPath.Substring(sourceWorkItem.IterationPath.IndexOf("\\") + 1)); 
     } 
     else 
     { 
     MigrateWorkItem(sourceWorkItem); 
     } 
    } 

    if (!createAreasAndIterations) 
    { 
     var query = from WorkItem wi in sourceWorkItems where wi.Links.Count > 0 select wi; 
     foreach (WorkItem sourceWorkItem in query) 
     { 
     LinkRelatedItems(targetWorkItemStore, sourceWorkItem); 
     } 
    } 

    TextWriter tw = File.CreateText(@"C:\temp\TFS_MigratedItems.csv"); 
    tw.WriteLine("SourceId,TargetId"); 
    foreach (var entry in WorkItemIdMap) 
    { 
     tw.WriteLine(entry.Key + "," + entry.Value); 
    } 
    tw.Close(); 
    Console.WriteLine(); 
    Console.WriteLine("Done! Have a nice day."); 
    Console.ReadLine(); 
    } 

    private static bool GetRunMode() 
    { 
    bool createAreasAndIterations; 
    while (true) 
    { 
     Console.Write("Create [A]reas/Iterations or [M]igrate (Ctrl-C to quit)?: "); 
     var command = Console.ReadLine().ToUpper().Trim(); 
     if (command == "A") 
     { 
     createAreasAndIterations = true; 
     break; 
     } 
     if (command == "M") 
     { 
     createAreasAndIterations = false; 
     break; 
     } 
     Console.WriteLine("Unknown command " + command + " - try again."); 
    } 
    return createAreasAndIterations; 
    } 

    private static void MigrateWorkItem(WorkItem sourceWorkItem) 
    { 

    var targetWIT = WorkItemTypes[sourceWorkItem.Type.Name]; 
    var newWorkItem = targetWIT.NewWorkItem(); 
    //var newWorkItem = targetWorkItemType.NewWorkItem(); 

    // Description (Task)/Steps to reproduce (Bug) 

    if (sourceWorkItem.Type.Name != "Bug") 
    { 
     newWorkItem.Description = sourceWorkItem.Description; 
    } 
    else 
    { 
     newWorkItem.Fields["Microsoft.VSTS.TCM.ReproSteps"].Value = sourceWorkItem.Description; 
    } 

    // History 
    newWorkItem.History = sourceWorkItem.History; 
    // Title 
    newWorkItem.Title = sourceWorkItem.Title; 
    // Assigned To 
    newWorkItem.Fields[CoreField.AssignedTo].Value = sourceWorkItem.Fields[CoreField.AssignedTo].Value; 
    // Stack Rank - Priority 
    newWorkItem.Fields[MicrosoftVstsCommonPriority].Value = sourceWorkItem.Fields[MicrosoftVstsCommonPriority].Value; 
    // Area Path 
    newWorkItem.AreaPath = FormatPath(TargetProjectName, sourceWorkItem.AreaPath); 
    // Iteration Path 
    newWorkItem.IterationPath = FormatPath(TargetProjectName, sourceWorkItem.IterationPath); 
    // Activity 
    if (sourceWorkItem.Type.Name == "Task") 
    { 
     newWorkItem.Fields["Microsoft.VSTS.Common.Activity"].Value = sourceWorkItem.Fields["Microsoft.VSTS.Common.Discipline"].Value; 
    } 
    // State 
    //newWorkItem.State = sourceWorkItem.State; 
    // Reason 
    //newWorkItem.Reason = sourceWorkItem.Reason; 

    // build a usable rendition of prior revision history 
    RevisionCollection revisions = sourceWorkItem.Revisions; 
    var query = from Revision r in revisions orderby r.Fields["Changed Date"].Value descending select r; 
    StringBuilder sb = new StringBuilder(String.Format("Migrated from work item {0}<BR />\n", sourceWorkItem.Id)); 
    foreach (Revision revision in query) 
    { 
     String history = (String)revision.Fields["History"].Value; 
     if (!String.IsNullOrEmpty(history)) 
     { 
     foreach (Field f in revision.Fields) 
     { 
      if (!Object.Equals(f.Value, f.OriginalValue)) 
      { 
      if (f.Name == "History") 
      { 
       string notation = string.Empty; 
       if (revision.Fields["State"].OriginalValue != revision.Fields["State"].Value) 
       { 
       notation = String.Format("({0} to {1})", revision.Fields["State"].OriginalValue, revision.Fields["State"].Value); 
       } 
       //Console.WriteLine("<STRONG>{0} Edited {3} by {1}</STRONG><BR />\n{2}", revision.Fields["Changed Date"].Value.ToString(), revision.Fields["Changed By"].Value.ToString(), f.Value, notation); 
       sb.Append(String.Format("<STRONG>{0} Edited {3} by {1}</STRONG><BR />\n{2}<BR />\n", revision.Fields["Changed Date"].Value.ToString(), revision.Fields["Changed By"].Value.ToString(), f.Value, notation)); 
      } 
      } 
     } 
     //Console.WriteLine("Revision {0}: ", revision.Fields["Rev"].Value); 
     //Console.WriteLine(" ChangedDate: " + revision.Fields["ChangedDate"].Value); 
     //Console.WriteLine(" History: " + sb.ToString()); 
     } 
    } 
    newWorkItem.History = sb.ToString(); 

    // Attachments 
    for (var i = 0; i < sourceWorkItem.AttachedFileCount; i++) 
    { 
     CopyAttachment(sourceWorkItem.Attachments[i], newWorkItem); 
    } 

    // Validate before save 
    if (!newWorkItem.IsValid()) 
    { 
     var reasons = newWorkItem.Validate(); 
     Console.WriteLine(string.Format("Could not validate new work item (old id: {0}).", sourceWorkItem.Id)); 
     foreach (Field reason in reasons) 
     { 
     Console.WriteLine("Field: " + reason.Name + ", Status: " + reason.Status + ", Value: " + reason.Value); 
     } 
    } 
    else 
    { 
     Console.Write("[" + sourceWorkItem.Id + "] " + newWorkItem.Title); 
     try 
     { 
     newWorkItem.Save(SaveFlags.None); 
     Console.ForegroundColor = ConsoleColor.Cyan; 
     Console.WriteLine(string.Format(" [saved: {0}]", newWorkItem.Id)); 
     WorkItemIdMap.Add(sourceWorkItem.Id, newWorkItem.Id); 
     Console.ResetColor(); 
     } 
     catch (Exception ex) 
     { 
     Console.WriteLine(ex.Message); 
     throw; 
     } 
    } 
    } 

    private static void CopyAttachment(Attachment attachment, WorkItem newWorkItem) 
    { 
    using (var client = new WebClient()) 
    { 
     client.UseDefaultCredentials = true; 
     client.DownloadFile(attachment.Uri, attachment.Name); 
     var newAttachment = new Attachment(attachment.Name, attachment.Comment); 
     newWorkItem.Attachments.Add(newAttachment); 
    } 
    } 

    private static void LinkRelatedItems(WorkItemStore targetWorkItemStore, WorkItem sourceWorkItem) 
    { 
    int newId = WorkItemIdMap[sourceWorkItem.Id]; 
    WorkItem targetItem = targetWorkItemStore.GetWorkItem(newId); 
    foreach (Link l in sourceWorkItem.Links) 
    { 
     if (l is RelatedLink) 
     { 
     RelatedLink sl = l as RelatedLink; 
     switch (sl.ArtifactLinkType.Name) 
     { 
      case "Related Workitem": 
      { 
       if (WorkItemIdMap.ContainsKey(sl.RelatedWorkItemId)) 
       { 
       int RelatedWorkItemId = WorkItemIdMap[sl.RelatedWorkItemId]; 
       RelatedLink rl = new RelatedLink(sl.LinkTypeEnd, RelatedWorkItemId); 
       // !!!! 
       // this does not work - need to check the existing links to see if one exists already for the linked workitem. 
       // using contains expects the same object and that's not what I'm doing here!!!!!! 
       //if (!targetItem.Links.Contains(rl)) 
       // !!!! 
       var query = from RelatedLink qrl in targetItem.Links where qrl.RelatedWorkItemId == RelatedWorkItemId select qrl; 
       if (query.Count() == 0) 
       { 
        targetItem.Links.Add(rl); ; 
        // Validate before save 
        if (!targetItem.IsValid()) 
        { 
        var reasons = targetItem.Validate(); 
        Console.WriteLine(string.Format("Could not validate work item (old id: {0}) related link id {1}.", sourceWorkItem.Id, sl.RelatedWorkItemId)); 
        foreach (Field reason in reasons) 
        { 
         Console.WriteLine("Field: " + reason.Name + ", Status: " + reason.Status + ", Value: " + reason.Value); 
        } 
        } 
        else 
        { 
        try 
        { 
         targetItem.Save(SaveFlags.None); 
         Console.ForegroundColor = ConsoleColor.Cyan; 
         Console.WriteLine(string.Format(" [Updated: {0}]", targetItem.Id)); 
         Console.ResetColor(); 
        } 
        catch (Exception ex) 
        { 
         Console.WriteLine(ex.Message); 
         throw; 
        } 
        } 

       } 
       } 
       break; 
      } 
      default: 
      { break; } 
     } 

     } 
    } 
    } 

    private static void EnsureThatStructureExists(string projectName, string structureType, string structurePath) 
    { 
    var parts = structurePath.Split('\\'); 

    var css = GetCommonStructureService(); 
    var projectInfo = css.GetProjectFromName(projectName); 
    var parentNodeUri = GetCssStructure(GetCommonStructureService(), projectInfo.Uri, structureType).Uri; 
    var currentPath = FormatPath(projectName, structureType == Areas ? "Area" : "Iteration"); 
    foreach (var part in parts) 
    { 
     currentPath = FormatPath(currentPath, part); 
     Console.Write(currentPath); 

     try 
     { 
     var currentNode = css.GetNodeFromPath(currentPath); 
     parentNodeUri = currentNode.Uri; 
     Console.ForegroundColor = ConsoleColor.DarkGreen; 
     Console.WriteLine(" [found]"); 
     } 
     catch 
     { 
     parentNodeUri = css.CreateNode(part, parentNodeUri); 
     Console.ForegroundColor = ConsoleColor.Green; 
     Console.WriteLine(" [created]"); 
     } 
     Console.ResetColor(); 
    } 
    } 

    private static string FormatPath(string currentPath, string part) 
    { 
    part = part.Substring(part.IndexOf("\\") + 1); 
    currentPath = string.Format(@"{0}\{1}", currentPath, part); 
    return currentPath; 
    } 

    private static TfsTeamProjectCollection GetProjectCollection(Uri uri) 
    { 
    TfsTeamProjectCollection collection; 
    if (!Collections.TryGetValue(uri, out collection)) 
    { 
     collection = TfsTeamProjectCollectionFactory.GetTeamProjectCollection(uri); 
     collection.Connect(ConnectOptions.IncludeServices); 
     collection.Authenticate(); 
     Collections.Add(uri, collection); 
    } 
    return Collections[uri]; 
    } 

    private static WorkItemStore GetSourceWorkItemStore() 
    { 
    var collection = GetProjectCollection(SourceCollectionUri); 
    return collection.GetService<WorkItemStore>(); 
    } 

    private static WorkItemStore GetTargetWorkItemStore() 
    { 
    var collection = GetProjectCollection(TargetCollectionUri); 
    return collection.GetService<WorkItemStore>(); 
    } 

    public static NodeInfo GetCssStructure(ICommonStructureService css, String projectUri, String structureType) 
    { 
    return css.ListStructures(projectUri).FirstOrDefault(node => node.StructureType == structureType); 
    } 

    private static ICommonStructureService GetCommonStructureService() 
    { 
    var collection = GetProjectCollection(TargetCollectionUri); 
    return collection.GetService<ICommonStructureService>(); 
    } 
} 
}