@Shazwazza

Shannon Deminick's blog all about web development

Importing SVN to Mercurial with complex SVN repository

November 2, 2010 21:14
This post was imported from FARMCode.org which has been discontinued. These posts now exist here as an archive. They may contain broken links and images.
Here @ TheFARM, we’ve been moving across to Mercurial (on BitBucket) for our code repositories. In many cases our SVN repositories are structured ‘normally’:
  • trunk
  • tags
  • branches

Using the ‘hg convert’ command line, when your SVN repository is structured this way will import your trunk into the Mercurial ‘default’ branch and your branches/tags into named branches. This also imports all history and revisions. From there, you can merge as you wish to structure your Mercurial repository the way that you want.

However, in some cases we have more complicated repositories. An example of this is a structure like the following:

  • trunk
    • DotNet
    • Flash
  • tags
  • branches
    • v1.1-DotNet
    • v1.2-Flash

In the above structure, we’ve actually branched the trunk/DotNet & trunk/Flash folders separately into their own branches. Unfortunately, Mercurial doesn’t operate this way so it doesn’t really understand creating branches from folders. There’s a couple different ways that you can get this from SVN into Mercurial whilst maintaining all of your history…

One way is to run ‘hg convert’ on the entire repository. You’ll end up with 3 branches in Mercurial: default, v1.1-DotNet & v1.2-Flash. The problem is that if you try to merge the named branches into default, you’ll end up with a mess since the branches don’t have the same folder structure as default. To overcome this, you can restructure each named branch to follow the same folder structure as default. To do this, we us the ‘rename’ method on Tortoise Hg. So for instance, if we had this folder structure inside of v1.1-DotNet:

  • BuildFiles
  • MyProject.Web
  • MyProject.Config

So that we can merge this with default we need to restructure this into:

  • DotNet
    • BuildFiles
    • MyProject.Web
    • MyProject.Config

So we just need to right click each folder seperately, and select the rename option from the Tortoise Hg sub menu:

image

Then we prefix the folder name with the new folder location which will the ‘move’ the file:

image

Now that the named branch v1.1-DotNet is in the same folder structure as default, we can perform a merge.

The other way to import a complicated SVN structure to mercurial is to convert individual branches to mercurial repositories one by one. The first thing you’ll need to do is run an ‘hg convert’ on the Trunk of your SVN repository. This will create your new ‘master’ mercurial repository for which will push the other individual mercurial repositories in to. Next, run an ‘hg convert’ on each of your SVN branches. For example: hg convert svn://my.svn.server.local/MyProject/Branches/v1.1-DotNet.

Once you have individual repositories for your branches, we can force push these into your ‘master’ repository. To do a merge of these branches, the above procedure will still need to be followed to ensure your branches have the same folder structure as default. HOWEVER, because we’ve forced pushed changesets into Mercurial, it has no idea how these branches relate to each other (in fact, it gives you warnings about this when you force push). When you try to do a merge, you’ll end up getting conflict warnings for every file that exists in both locations since Mercurial doesn’t know which one is newer/older. This can be a huge pain in the arse, especially if you have tons of files. If we assume that the branch files are the most up to date and we just want to replace the files in default, then there’s a fairly obscure way to do that. In the merge dialog, you’ll need to select the option “internal : other” from the list of Merge tools:

image

This tells Mercurial that for any conflict you want to use the ‘other’ revision (which is your branch revision since you should have default checked out to do the merge).

We’ve had success with both of these options for converting SVN to Mercurial and maintaining our history.

Backing up Document Types

June 11, 2010 20:20
This post was imported from FARMCode.org which has been discontinued. These posts now exist here as an archive. They may contain broken links and images.
Something I’ve heard a number of people say is that they want a way in which they can store the DocumentType in their source control system.

This is obviously a bit of a problem since they are actually stored in the database, not on the file system. Hmmm…

Then yesterday I was talking to Tatham Oddie about it and how you could go about CI with Umbraco. Then after bouncing a few ideas of Shannon we had a great idea, that when you say a DocumentType it would just dump it to the file system. You can then check this file into your source control system and you have a backup.

Sounds pretty simple, and in fact, Umbraco has all the stuff you’d need for this, it’s just a matter of doing it. So while waiting for a rather large project to check out of source control I decided to just write it.

Please note, the following code is not tested, it’s just a POC, when I get some time I do plan on actually testing it :P

How do go about it

It’s actually quite simple, you just need to tie into the Umbraco event model for a DocumentType and use the built in XML export feature.

I’ve also done the code so you can either dump to a single file or to multiple files (depending which is easiest in your solution.

It doesn’t check the files out for you, so if you’re using something like TFS you’ll have a problem, but I have put in handlers for read-only files.

Also, there’s no error checking, like I said, this is POC code :P.

Code baby!

using System.Linq;
using System.IO;
using System.Web;
using System.Xml.Linq;
using umbraco;
using umbraco.BusinessLogic;
using umbraco.cms.businesslogic.web;

namespace AaronPowell.Umbraco
{
    public class DocumentTypeSerializer : ApplicationBase
    {
        public DocumentTypeSerializer()
        {
            DocumentType.AfterSave += new DocumentType.SaveEventHandler(DocumentType_AfterSave);
            DocumentType.AfterDelete += new DocumentType.DeleteEventHandler(DocumentType_AfterDelete);
        }

        void DocumentType_AfterDelete(DocumentType sender, umbraco.cms.businesslogic.DeleteEventArgs e)
        {
            DumpDocumentTypes(false);
        }

        void DocumentType_AfterSave(DocumentType sender, umbraco.cms.businesslogic.SaveEventArgs e)
        {
            DumpDocumentTypes(false);
        }

        private static void DumpDocumentTypes(bool useSingleFile)
        {
            var allDocTypes = DocumentType.GetAllAsList();
			var storageFolder = GlobalSettings.StorageDirectory + "/";
			System.Xml.XmlDocument xmlDoc = new System.Xml.XmlDocument();
			
			if(useSingleFile)
			{
				var xdoc = new XDocument(new XElement("DocumentTypes"));

				foreach (var dt in allDocTypes)
					xdoc.Root.Add(XElement.Parse(dt.ToXml(xmlDoc).InnerXml));

				var file = storageFolder + "DocumentTypes.config";
				var fileOnFileSystem = new FileInfo(HttpContext.Current.Server.MapPath(file));
				if (fileOnFileSystem.Exists)
				{
					if (fileOnFileSystem.Attributes == FileAttributes.ReadOnly)
						fileOnFileSystem.Attributes &= ~FileAttributes.ReadOnly;
						
					fileOnFileSystem.Delete();
				}

				xdoc.Save(fileOnFileSystem.FullName);
			}
			else
			{
				storageFolder += "DocumentTypes/";
				if(!Directory.Exists(storageFolder))
				{
					Directory.Create(storageFolder);
				}
				else
				{
					var di = new DirectoryInfo(storageFolder);
					var files = di.GetFiles();
					foreach(var file in files) 
					{
						if (file.Exists)
						{
							if (file.Attributes == FileAttributes.ReadOnly)
								file.Attributes &= ~FileAttributes.ReadOnly;
								
							file.Delete();
						}
					}
				}				
				
				foreach(var dt in allDocTypes) 
				{
					var xdoc = XDocument.Parse(dt.ToXml(xmlDoc).ToString());
					
					var file = storageFolder + dt.Alias + ".config";
					
					var fileOnFileSystem = new FileInfo(HttpContext.Current.Server.MapPath(file));
					if (fileOnFileSystem.Exists)
					{
						if (fileOnFileSystem.Attributes == FileAttributes.ReadOnly)
							fileOnFileSystem.Attributes &= ~FileAttributes.ReadOnly;
							
						fileOnFileSystem.Delete();
					}

					xdoc.Save(fileOnFileSystem.FullName);
				}
			}
        }
    }
}

I'll look at cleaning this up and testing it soon and releasing it as an actual Umbraco package, but in the mean time feel free to have a play around with it.