Question

I have a task to relocate work items from one TFS project to another within one TFS collection. All history and dates have to be identical to the original item. I read a lot information and have done a lot of work. I have almost completed the task but found one crucial issue. After saving of a new item which have been copied from original one I lost my attachments in revisions. Thus I can relocate item only once, after next attempt attachments will be lost.

There is my approach to copy items:

First of all I read that article about changing creation date and changed date: TFS API Change WorkItem CreatedDate And ChangedDate To Historic Dates.

Then I had next code:

var type = project.WorkItemTypes[itemToCopy.Type.Name];
var newWorkItem = new WorkItem(type);
List<Revision> revisions = item.Revisions.Cast<Revision>().OrderBy(r => r.Index).ToList();
var firstRevision = revisions.First();
revisions.Remove(firstRevision);
SetFields(newWorkItem, firstRevision.Fields, includeAreas);
result = Save(newWorkItem);
if (result != string.Empty)
    throw new Exception(result);
newWorkItem = tfsManager.GetWorkItemStore().GetWorkItem(newWorkItem.Id);
var changed = firstRevision.Fields.Cast<Field>().First(f => f.ReferenceName == "System.ChangedDate").Value;
var created = firstRevision.Fields.Cast<Field>().First(f => f.ReferenceName == "System.CreatedDate").Value;

var changedDate = (DateTime)changed;
var createdDate = (DateTime)created;

newWorkItem.Fields["System.CreatedDate"].Value = createdDate;
newWorkItem.Fields["System.ChangedDate"].Value = changedDate.AddSeconds(1);

result = Save(newWorkItem);
if (result != string.Empty)
    throw new Exception(result);

var attachments = firstRevision.Attachments.Cast<Attachment>().ToList();

var attachMap = new Dictionary<int, Attachment>();

if (attachments.Count > 0)
    AddAttachments(newWorkItem, firstRevision.Attachments.Cast<Attachment>().ToList(), attachMap);
else
{
    result = Save(newWorkItem);
    if (result != string.Empty)
        throw new Exception(result);
}

ApplyRevisions(newWorkItem, revisions, attachMap, includeAreas);

return newWorkItem;

The method "SetFields" copies all editable fields from the original work item to a new work item.

The method "Save" simply saves the work item and collect all info about mistakes during save process.

The method "ApplyRevisions" simple enumerate all revisions and copy fields and attachments. It looks like:

    private void ApplyRevisions(WorkItem toItem, 
    List<Revision> revisions, Dictionary<int,
    Attachment> attachMap,
    bool includeAreas)
{
    foreach (var revision in revisions.OrderBy(r => r.Index))
    {
        SetFields(toItem, revision.Fields, includeAreas);
        AddAttachments(toItem, revision.Attachments.Cast<Attachment>().ToList(), attachMap);
        AddChangesetLinks(toItem, revision.Links.Cast<Link>().ToList());

        var result = Save(toItem);
        if (result != string.Empty)
        {
            SetFields(toItem, revision.Fields, includeAreas);
            result = Save(toItem);
            if (result != string.Empty)
            {
                throw new Exception(result);
            }
        }
    }
}

And the main part is how I copy attachments:

private void AddAttachments(WorkItem item, IList<Attachment> attaches, Dictionary<int, Attachment> attachMap)
{    
    var guid = Guid.NewGuid();
    var currentAttaches = item.Attachments.Cast<Attachment>().ToList();
    var files = new List<string>();
    try
    {
        item.Open();
        foreach (var attach in attaches)
        {
            if (attachMap.ContainsKey(attach.Id))
            {
                var id = attachMap[attach.Id].Id;
                if (currentAttaches.Any(a => a.Id == id))
                    continue;
            }
            var bytes = tfsManager.TfsWebClient.DownloadData(attach.Uri);
            var tempFile = CreateFileName(guid, attach.Name);
            files.Add(tempFile);
            if (bytes != null && bytes.Length > 0)
                File.WriteAllBytes(tempFile, bytes);
            else
            {
                File.Create(tempFile).Dispose();
            }

            var attachInfo = new AttachmentInfo(tempFile);
            attachInfo.FieldId = 50;
            attachInfo.CreationDate = attach.CreationTimeUtc;
            //attachInfo.LastWriteDate = attach.LastWriteTimeUtc;
            attachInfo.Comment = attach.Comment;
            //attachInfo.AddedDate = attach.AttachedTimeUtc;
            var newAttach = Attachment.MakeAttachment(item, attachInfo);            
            item.Attachments.Add(newAttach);
            if (!attachMap.ContainsKey(attach.Id))
                attachMap[attach.Id] = newAttach;
        }
        foreach (var attach in currentAttaches)
        {
            var id = attachMap.First(pair => pair.Value.Id == attach.Id).Key;
            if (attaches.All(a => a.Id != id))
            {
                item.Attachments.Remove(attach);
            }
        }
        var result = Save(item);
        if (result != string.Empty)
            throw new Exception(result);
    }
    finally
    {
        if (files.Count > 0)
        {
            foreach (var file in files)
            {
                File.Delete(file);
            }
            RemoveTempFolder(guid);
        }
    }
}

After copying item and retrieving it from TFS property "Attachments" in property "Revisions" is empty. I guess, that the problem is in different dates... But do not know how to solve it... Sorry for enormous amount of code. It is my first question here.

P.S. I have digged deeper in the question and found out that the problem in AuthorizedAddedDate of AttachmentInfo. There is a comparison of this date and ChangeDate of the particular revision during of process of filling Attachments. See code below:

DateTime asof = (DateTime) changedDate;
foreach (LinkInfo linkInfo in this.m_linksData)
{
    if (linkInfo.AuthorizedAddedDate <= asof && asof < linkInfo.AuthorizedRemovedDate)
        yield return linkInfo;
}

ChangedDate we can change and I change it during of processing of revisions. Unfortunately, I do not know how to change AuthorizedAddedDate of AttachmentInfo... Even if I change it, the value is the same after next load of the work item... It seems that this value is prohibited for changing(

Was it helpful?

Solution

Yep, it seems that I have found the answer. At least this solution helped me. Sorry, that I have not posted it immediately. The process of adding attachment looks like that:

var attachInfo = new AttachmentInfo(tempFile);
attachInfo.AddedDate = attach.AddedDate;
attachInfo.CreationDate = attach.CreationDate;
attachInfo.Comment = string.Format(@"{0} (from attachId={1})", attach.Comment, attach.Id);                        
attachInfo.RemovedDate = attach.RemovedDate;
attachInfo.FieldId = 50;
itemTo.LinkData.AddLinkInfo(attachInfo, itemTo);

Where "tempFile" is a file which we want to attach (in my case it has been copied from another WorkItem) and "attach" is a container for information about AttachmentInfo. "itemTo" is a target WorkItem where we want to add attachment. If you need more detail or you want me to clarify any moments, please, let me know.

P.S. It is important to remember about time zones when setting AddedDate, CreationDate and RemovedDate.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top