{filelink=2}
I have tried different methods to move work items from one Team Foundation Server to another. The easiest method is doing it with Excel, but the problem is that I also want to keep all the work item history which Excel cannot do.
I found that the Revision property of each work item contains an array of complete copies of the work item as it appears at each revision. So iterating over each revision in order allows us to recreate each version that was saved! By setting the system time to the time of the original revision we can recreate a work item including all its changes, provided that we execute the code on our server so that the date changes are reflected when the work items are saved. An added bonus of iterating over each revision and saving them is that all the state changes are legal so we don’t need to figure out how to get our work item to it’s final state if we have complicated state transition rules.
If we execute our updates as a different user than the one that made the changes originally then each history entry will be tagged as “Edited by XXX on behalf of YYY”. Unfortunately it appears that project reports are not updated with the historic data, all the new work items will be added to the reports the day that you import them.
The following code illustrated how I have copied almost all of the basic data from work items using revisions (unfortunately indentation was lost when I copy/pasted the code):
foreach (Revision r in wi.Revisions)
{
foreach(Field f in r.Fields)
{
if(f.ReferenceName == CoreFieldReferenceNames.ChangedDate)
{
System.Diagnostics.Debug.WriteLine(f.Name + " : "+ f.Value);
//Copy revision date
System.DateTime revDateTime = (System.DateTime)f.Value;
revSysTime.wDay = Convert.ToUInt16(revDateTime.Day);
revSysTime.wHour = Convert.ToUInt16(revDateTime.Hour);
revSysTime.wMilliseconds = Convert.ToUInt16(revDateTime.Millisecond)
revSysTime.wMinute = Convert.ToUInt16(revDateTime.Minute);
revSysTime.wMonth = Convert.ToUInt16(revDateTime.Month);
revSysTime.wSecond = Convert.ToUInt16(revDateTime.Second);
revSysTime.wYear = Convert.ToUInt16(revDateTime.Year);
}
if(f.ReferenceName != CoreFieldReferenceNames.History
&& f.ReferenceName != CoreFieldReferenceNames.WorkItemType
&& f.ReferenceName != CoreFieldReferenceNames.TeamProject
&& f.ReferenceName != CoreFieldReferenceNames.Id
&& f.ReferenceName != CoreFieldReferenceNames.IterationId
&& f.ReferenceName != CoreFieldReferenceNames.IterationPath
&& f.ReferenceName != CoreFieldReferenceNames.AreaId
&& f.ReferenceName != CoreFieldReferenceNames.AreaPath
&& f.ReferenceName != CoreFieldReferenceNames.CreatedBy
)
{
if(!f.IsComputed && f.IsValid)
{
destItem.Fields[f.Name].Value = f.Value;
}
}
}
System.Collections.ArrayList arrl = destItem.Validate();
if(arrl.Count > 0)
{
//Handle validation errors
}
else
{
GetSystemTime(ref currentSysTime);
SysTimeNeedsReset = true;
if(SetSystemTime(ref revSysTime) == 0)
{
throw new ApplicationException("SystemTime was not set!");
}
destItem.Save();
if(SetSystemTime(ref currentSysTime) == 0)
{
throw new ApplicationException("SystemTime was not reset!");
}
SysTimeNeedsReset = false;
}
}
You can download the code to the minimal WinForms app that I have created for copying work items. The code is provided as-is with no guarantees or support…
{filelink=2}
Things that are not handled at all are: user mappings if the source and destination server have different users, area and iterations, links and attachments. There may be more things that are missing or don’t work that haven’t affected my setup.
Gosh, I wish I would have had that inorfmaiton earlier!