代码之家  ›  专栏  ›  技术社区  ›  Paul Michaels

在TFS中移动工作项

  •  4
  • Paul Michaels  · 技术社区  · 14 年前

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

    我找到这个了 article

    2 回复  |  直到 14 年前
        1
  •  1
  •   Robaticus    14 年前

    不可能移动,只能复制。我们这样做的方式,是我们做的副本,链接原件,然后关闭原件作为过时。您也可以创建副本并销毁原件,但您将丢失所有历史记录。

        2
  •  1
  •   Robaticus    12 年前

    Lars Wilhelmsen写了一个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>();
        }
    }
    }