2010-06-30 131 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>(); 
    } 
} 
}