Migrating a large SVN repository to Mercurial on Windows

22. March 2012 14:06

I couldn't find any good guide for doing this, so I figured I'll write my own guide based on my experiences from converting a huge SVN repo. There are a few differences between Windows and other platforms which can take you by surprise if you're not prepared.

What worked best for me was to split up the migration into three steps:

  1. Creating a local copy of the SVN repository (will speed up conversion)
  2. Converting to Mercurial - locally
  3. Pushing the new Mercurial repository to it's destination server.

Creating a local copy of the SVN repository

First, create an empty local repository that will store the mirrored repository locally:

svnadmin create svnrepofolder

Next, you'll need to enable revision properties to be changed:

echo exit 0 > svnrepofolder\hooks\pre-revprop-change.bat

The actual conversion is done using the svnsync command, as following:

svnsync init file:///c:/path/to/svnrepofolder http://svn.server/path/to/reponame
svnsync sync file:///c:/path/to/svnrepofolder http://svn.server/path/to/reponame

You can specify usename and password using --source-username and --source-password options (type svnsync help init for more info).

Converting to Mercurial

Make sure you have an empty local Mercurial repository - usually done by cloning a newly created master repository.

hg clone http://mercurial.server/reponame hgrepofolder

Before converting, you need to enable the convert extension in Mercurial. This is done by adding/editing the following lines in Mercurial.ini (which resides in C:\Users\username\ in Windows Vista or later):

[extensions]
convert = 

If you have TortoiseHg installed, this can also be done in Global settings -> Extensions by checking "convert".

Next you're off to do the actual conversion. There are many settings you may tweak, but I'd like to keep it simple here:

hg convert file:///c:/path/to/svnrepofolder hgrepofolder

This command will convert the trunk and all branches and tags found in the \trunk, \branches and \tags folders - appropriately. If your naming scheme is different, you can configure alternate folder paths.

Pushing the new repository to its destination server

After the conversion, the new folder is empty, except a hidden .hg folder. That is because you need to update to a specific branch/trunk. Don't let that worry you - things work differently in Mercurial than in SVN.

To push the repository to its destination server, simply use:

hg push http://mercurial.server/reponame


References:

 

Excluding properties from being serialized in ASP.NET MVC JsonResult

7. October 2009 18:19

Lately I have been working a lot on a large AJAX-application based on ASP.NET MVC. Every AJAX request goes to a controller action that returns a model class serialized to JSON.

A typical controller action like that could be:

public JsonResult ShowUser(int id)
{
User user = dataLayer.GetUserByID(id);
return Json(user);
}

In the example above, all the public properties of the User class will be serialized into the JSON object. When working with large model classes (in this case the User class), you often want to have some special properties you can use for keeping track the object state, for example IsEmpty or IsValid. These are not strictly part of the model data but are useful when manipulating the objects in the C# code. They are often not necessary in the Javascript that will render the objects.

I just found out that it is possible to exclude such properties from being serialized by ASP.NET MVC - you just have to add a [ScriptIgnore] attribute on them.

Consider the following (simplified) case:

public class User
{
public int Id { get; set; }
public string Name { get; set; }
[ScriptIgnore]
public bool IsComplete
{
get { return Id > 0 && !string.IsNullOrEmpty(Name); }
}
}

In this case, only the Id and the Name properties will be serialized, thus the resulting JSON object would look like this:

{ Id: 3, Name: 'Test User' }

Without the [ScriptIgnore] attribute, the serialization process would include both the computation and transport overhead of having to deal with the IsComplete field, which in many cases is just an unnecessary performance hit. When dealing with large websites, every CPU cycle and every byte counts.

location.hash javascript bug in Firefox 3.0

2. October 2009 13:02

After struggling for awhile trying to figure out a problem with resetting the URL hash (also called the URL fragment) in Javascript, I discovered a bug in Firefox 3.0.

When setting location.hash to en empty string in Firefox 3.0, it will cause a page reload. To solve, use location.hash = '#' instead. This will also work in all other major browsers. The issue appears only in Firefox version 3.0.* - it seems to have been fixed in 3.5.

This might be a useful point to remember if you are writing a lot of cross-browser Javascript.

Robust pattern for hvordan man lukker WCF-klienter

19. May 2009 10:39

Lad os se det i øjnene: systemintegration med web-services er nok den letteste og mest populære måde at integrere systemer på. Hvis man i dag laver web-services på .NET-platformen, bør man bruge Microsoft’s WCF-teknologi. WCF er et slagkraftigt stykke framework, men med magten følger der også et ansvar. Der findes en del faldgruber som jeg ofte har set andre falde i – og selvfølgelig også selv er faldet i.

En af de mest almindelige faldgruber er hvordan man lukker en WCF klient ordentligt. Sådan kunne en typisk (fejlagtig)interaktion med en web service foregå:

using (var wcfClient = new SimpleServiceClient())
{
string response = wcfClient.SampleMethod("test");
Console.WriteLine(response);
wcfClient.Close();
}

Problemet her ligger i at man glemmer at tage højde for er at der kan opstå exceptions i Close(). Og gæt hvad – implementationen af Dispose() på ClientBase kalder Close() – altid! (kan ses med Reflector). Det kan til tider medføre uventede og mærkelige exceptions. For eksempel, hvis der opstår en fejl ved kaldet til SampleMethod vil wcfClient disposes, og Close() kaldet. Men så finder Close() ud af at klienten er i Faulted-tilstanden, og kaster en ny og uønsket exception (“The service is in the Faulted state” el.lign.) Denne MSDN artikel beskriver det samme problem bare på teknisk engelsk.

Løsningen er, at når der opstår fejl skal klienten lukkes med Abort() i stedet for med Close(). Abort() sørger for at klienten afbrydes med det samme – så der ikke kan opstå nye exceptions. Jeg har ofte glemt at bruge den, så derfor, har jeg lavet denne lille klasse som hjælper mig med det. Jeg kalder den ServiceOperationScope:

/// <summary>
/// Encapsulates a web-service client instance and makes sure it gets properly closed.
/// </summary>
public class ServiceOperationScope<T> : IDisposable
where T:class
{
private readonly T m_Instance;

public ServiceOperationScope(T instance)
{
m_Instance = instance;

var commObj = m_Instance as ICommunicationObject;
if (commObj != null)
commObj.Faulted += ServiceOperationFaulted;
}

public T Instance
{
get { return m_Instance; }
}

private void ServiceOperationFaulted(object sender, EventArgs e)
{
((ICommunicationObject)m_Instance).Abort();
}

protected virtual void Dispose(bool isDisposing)
{
var commObj = m_Instance as ICommunicationObject;
if (commObj != null && (commObj.State != CommunicationState.Faulted &&
commObj.State != CommunicationState.Closed))
commObj.Close();

var d = m_Instance as IDisposable;
if (d != null)
d.Dispose();
}

public void Dispose()
{
Dispose(true);
}
}

Og sådan bruger man den:

try
{
using (var scope = new ServiceOperationScope<ISimpleService>(new SimpleServiceClient()))
{
string response = scope.Instance.SampleMethod("test");
Console.WriteLine(response);
}
}
catch (TimeoutException te)
{
// handle timeout error
}
catch (FaultException<SimpleServiceFault> simpleServiceFault)
{
// handle custom web service fault
}
catch (FaultException)
{
// handle generic web service fault
}
catch (CommunicationException)
{
// handle communication error
}

Rækkefølgen af de forskellige catch-linjer følger Microsofts anbefalinger.

Håber nogen kan bruge det til noget. ServiceOperationScope er i hvert fald blevet en fast del af min værktøjskasse.

Sequential GUID i SQL Server og i .NET

13. May 2009 13:11

Jeg havde aldrig gættet at min første blog post ville omhandle noget der har med SQL Server at gøre, men – der kan man bare se…

I det sidste er jeg blevet mere varm på idéen med at bruge GUIDs som PK i SQL Server. Den største ulempe er nok at ens index begynder at performe dårligt når man bruger almindelige, tilfældige GUIDs (NEWID eller Guid.NewGuid ).

Som en løsning på problemet kom Microsoft op med NEWSEQUENTIALID i SQL Server 2005. Denne funktion (som kun kan bruges ifm. en kolonnes DEFAULT værdi) returnerer altid den næste GUID i rækkefølge, uanset hvilken tabel Guid’en skal bruges i.

Det er jo genialt! Men hvad hvis man gerne vil populere ID-kolonnen selv, og ikke gider hente den fra databasen efter at rækken er blevet tilføjet? Artiklen her dokumenterer at NEWSEQUENTIALID bruger en Windows API metode ved navn UuidCreateSequential - og den kan vi bruge fra C#!

Efter at have googlet lidt rundt lavede jeg først denne metode:

public static class SequentialGuid
{
[DllImport("rpcrt4.dll", SetLastError = true)]
private static extern int UuidCreateSequential(out Guid guid);

public static Guid NewGuid()
{
Guid newGuid;
int returnVal = UuidCreateSequential(out newGuid);

if (returnVal != 0)
throw new Exception("UuidCreateSequential failed with exit code " + returnVal);

return newGuid;
}
}

Da jeg kørte en test der sammenlignede de GUIDs som min metode genererer og dem som SQL Server genererer fandt jeg ud af at de er formateret lidt forskelligt. Det var da irriterende:

SQL Server:
E4DF655E-A03F-DE11-9DE7-001FF3ABBCAF
E5DF655E-A03F-DE11-9DE7-001FF3ABBCAF

SequentialGuid.NewGuid():
5E665517-3FA0-11DE-9DE7-001FF3ABBCAF
5E665518-3FA0-11DE-9DE7-001FF3ABBCAF

Jeg har fremhævet de bytes der “tæller opad”. Der sker åbenbart det, at SQL Server konverterer de tre første byte-gruppers endianness for bedre at kunne sortere. Derfor lavede jeg en ny metode som returnerer GUIDs i SQL Server formatet:

public static Guid NewSqlServerFormattedGuid()
{
var bytes = NewGuid().ToByteArray();

CopyBigEndianBytes(BitConverter.ToInt32(bytes, 0), 4, bytes, 0);
CopyBigEndianBytes(BitConverter.ToInt16(bytes, 4), 2, bytes, 4);
CopyBigEndianBytes(BitConverter.ToInt16(bytes, 6), 2, bytes, 6);

return new Guid(bytes);
}

// Tak til Jon Skeet http://www.yoda.arachsys.com/csharp/miscutil
private static void CopyBigEndianBytes(long value, int bytes, byte[] buffer, int index)
{
int endOffset = index + bytes - 1;
for (int i = 0; i < bytes; i++)
{
buffer[endOffset - i] = unchecked((byte)(value & 0xff));
value = value >> 8;
}
}

Jeg fik inspiration fra artiklen her, som fremlægger en lidt anden løsning, men som jeg synes ikke var så elegant.

Der er også andre sjove finurligheder med Sequential GUIDs – bl.a. at man kan se computerens MAC adresse i de sidste 6 bytes. Så hvis du laver systemer til PET er det måske bedre at bruge noget andet.