Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles / productivity / SharePoint / SharePoint2010

Merging the History of Two Documents in SharePoint 2010

0.00/5 (No votes)
15 May 2011CPOL2 min read 11.4K  
Using the SharePoint API to control the dates and merge the histories of two documents into a third one

The problem I needed to solve was this: Two users were independently creating the same document. Once this was discovered, one of them was chosen to be the master document. But the users didn’t want to lose the other document, even though it was not chosen as the master. The users wanted to merge this document into the history of the master document. Technically, it is not possible to alter the history of any document in SharePoint. So I decided we would create a new third document, which was generated based on the two documents to be merged. By using the SharePoint API, I could control the dates and merge the histories of the two documents into a third one. Once that was done, the original documents could be deleted.

The basic algorithm works like this:

  1. Choose two documents, one to be the master, one to merge into the master
  2. Fetch the history of both items into a List<>
  3. Sort the list by date
  4. Loop through the list and write the historical files to a new list item, creating a new history
  5. Write the current version of the merge document to the new list item
  6. Write the current version of the master document to the new list item, including the properties

I did try to keep the properties to all the previous versions, but there was some resulting weirdness that went away when that part was removed. Since the metadata wasn’t a strict requirement, but a nice-to-have, I didn’t investigate that any further. YMMV. Here is the code for the algorithm:

C#
/// <summary>
/// Merges the history of two SPListItems into a single SPListItem
/// </summary>
/// <param name="siteUrl">The URL of the SharePoint site</param>
/// <param name="listName">The Name of a list
//     containing the documents to be merged</param>
/// <param name="keepDocName">The name of the document
//     which is the "Master"</param>
/// <param name="mergeDocName">The name of the document
///    which is merging it's history into the master.</param>
/// <param name="newName">The name of a third document
//     which is the destination of the other two</param>

protected void MergeDocuments(string siteUrl, string listName, 
          string keepDocName, string mergeDocName, string newName)
{
     //Attach to SharePoint, get the list and documents
     var site = new SPSite(siteUrl);
     var web = site.OpenWeb();
     var list = web.Lists[listName];
     var keepUrl = string.Format("{0}/{1}", 
         list.RootFolder.ServerRelativeUrl, keepDocName);
     var discardUrl = string.Format("{0}/{1}", 
         list.RootFolder.ServerRelativeUrl, mergeDocName);
     var keep = web.GetListItem(keepUrl);

     var merge = web.GetListItem(discardUrl);
     //Push the versions from each document into a list that can be sorted by date
     var allVersions = keep.Versions.Cast<SPListItemVersion>().ToList();
     allVersions.AddRange(merge.Versions.Cast<SPListItemVersion>());
     allVersions.Sort(new SortVersionsByDate());
     //SortVersionsByDate is a simple IComparer for the created date 

    //Create the upload url for the new document being merged
    var newUrl = string.Format("{0}/{1}", 
        list.RootFolder.ServerRelativeUrl, newName);
    //Write each old version as the new file to create a merged history
    foreach (var version in allVersions)
    {
        if (version.IsCurrentVersion)
            continue;
            //Skip the current versions of each,
            //we need for them to be the last two docs
            
        var oldFile = version.ListItem.File.Versions.GetVersionFromID(version.VersionId);
        var oldestFile = list.RootFolder.Files.Add(newUrl, 
            oldFile.OpenBinaryStream(), null, version.CreatedBy.User,
            version.CreatedBy.User, version.Created, version.Created,
            oldFile.CheckInComment, true);
        //Even though the dates were set in the call above, it doesn't work
        // when setting them in the past, so call this fixes the issue
        UpdateListItem(oldestFile.Item, version.Created,
                       version.Created);
        list.Update();
     }
     //Add the last version of the merged document
     WriteFileToSharePoint(list, merge, newUrl, false);
     //Add the Final version of the document
     WriteFileToSharePoint(list, keep, newUrl, true);
} 

/// <summary>
/// Helper function to set the created and modified dates on a list item.
/// The reason for this function is to allow the dates to be set in the past.
/// </summary>
/// <param name="item">a SPListItem</param>
/// <param name="created">Date created</param>
/// <param name="modified">Date modified</param>

protected void UpdateListItem(SPListItem item, DateTime created, DateTime modified)
{
    //Update the modification/creation dates.
    item[SPBuiltInFieldId.Modified] = modified.ToLocalTime();
    item[SPBuiltInFieldId.Created] = created.ToLocalTime();
    item.UpdateOverwriteVersion();
    //Keep the changes to the date/time from making a new version of the item
}

/// <summary>
/// Upload a file to a list based on an existing SPListItem
/// </summary>
/// <param name="list">The target list for the upload</param>
/// <param name="doc">The existing SPListItem</param>
/// <param name="newUrl">The url to upload the file to</param>
/// <param name="final"></param>
protected void WriteFileToSharePoint(SPList list, SPListItem doc, string newUrl, bool final)
{
    Hashtable props = null;
    if (final)
        props = doc.Properties;
    var lastMergedFile = list.RootFolder.Files.Add(newUrl, 
        doc.File.OpenBinaryStream(), props, doc.File.Author,
        doc.File.Author, doc.File.TimeCreated, doc.File.TimeCreated,
        doc.File.CheckInComment, true);
    UpdateListItem(lastMergedFile.Item, doc.File.TimeCreated,
        doc.File.TimeLastModified);
    list.Update();
}

I deployed this as a PowerShell cmdlet, which is explained here and here. Essentially, that route was chosen because the merging was to be performed on request by an administrator. It would be easy enough to wire this up to a ribbon command in the UI though.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)