Show Blogger Panel Hide Blogger Panel
Alex Yakunin

October 7, 2009

Migrating to Mercurial

So yesterday I have finally figured out how to migrate our repository to Mercurial. I'm happy it was possible to do this just with a single .bat file.

Why standard hg convert didn't help? That's because:
  • The source repository was already migrated from another SVN repository earlier - actually, we extracted a part of more general one. So the path to root folder of new repository was changing two times.
  • After v4.0 release we added "Trunk" and "Branches" folders there. So until some moment there were no branches, and after this moment they have appeared. But hg convert can convert repositories either with or without branches and trunk folder.
So I exported two "slices" of original repository: "pre-trunk" and "post-trunk", and main problem there was to stack the changes from the second one over the first one. Initially this seems really simple: just export the revision history of the second one as patch (or patches) and apply them to the first one. But this never worked, and I couldn't figure out why until I wrote .bat file doing this individually for each revision. I discovered that I can't apply few of such patches:
  • "Pre-trunk" repository contains a snapshot of original repository before "Trunk" folder was created. Later this folder was added and content of some outer folders was moved to it. After this moment "post-trunk" history starts, implying that all the files from "pre-trunk" root are moved to "Trunk" here. But later we moved few more folders to "Trunk", and these actions were reflected as "new files added" in "post-trunk" history. On the other hand, since "post-trunk" base is "pre-trunk", it already contains these files. So I should simply ignore such patches.
  • As I discovered, Mercurial fails on renames affecting just file name case. Again, that was simply ignored.
  • It also does not allow to commit patches with empty comments, although SVN allows this. I implemented a workaround for this.
  • By some reason patch tool was unable to import tags.
  • There was an issue with branches as well. But since there were was just one active (i.e. not yet deleted) branch, we merged it and imported just trunk.
Here is .bat file I used to convert our repository. Various ifs shows the troubles I faced :)
@echo off
rmdir /S /Q Diffs
mkdir Diffs

:ConvertPart1
echo Converting part 1:
rmdir /S /Q Xtensive1
hg convert "D:\Users\Common\Repositories\Xtensive" Xtensive1 --authors Authors.txt --filemap Filemap-Xtensive.txt --config convert.svn.startrev=8156 --rev 11539
echo  Done.

:ConvertPart2
echo Converting part 2:
rmdir /S /Q Xtensive2
hg convert "D:\Users\Common\Repositories\Xtensive" Xtensive2 --authors Authors.txt --filemap Filemap-Xtensive.txt --branchmap Branchmap-Xtensive.txt --config convert.svn.trunk=Trunk --config convert.svn.branches=Empty --config convert.svn.tags=Tags
echo  Done.

:MigratePart1
echo Migrating part 1:
rmdir /S /Q DataObjects.Net
hg clone Xtensive1 DataObjects.Net
echo  Done.

:MigratePart2
echo Migrating part 2:
for /L %%i IN (1,1,2000) do (
  if "%%i"=="4" (
    echo Skipping revision %%i - it moves existing files into Trunk from the outside, but they're already there in Xtensive1
  ) else if "%%i"=="210" (
    echo Skipping revision %%i - there is case-sensitive rename, which is not supported by Mercurial.
  ) else call :Migrate Xtensive2 %%i
)
echo  Done.
goto :End

:Migrate
echo Migrating %2 revision:
set diff=..\Diffs\%1-%2.diff
pushd %1
  if not exist "%diff%" (
    echo   Exporting %2...
    hg export %2:%2 -o "%diff%" --git
    if not "%ERRORLEVEL%"=="0" exit
  )
  call :DetectCommentAndTag %2
popd
pushd DataObjects.Net
  echo   Importing %2...
  hg patch "%diff%" %Comment% --import-branch
  if not "%ERRORLEVEL%"=="0" exit
  if not "%Tag%"=="" (
    echo   Tagging %2 as %Tag%
    hg up tip
    hg tag -f -m "Tag created: %Tag%" "%Tag%"
    if not "%ERRORLEVEL%"=="0" exit
  )
popd
echo   Done.
goto :End

:DetectCommentAndTag
set Comment=-m "No comment."
set Tag=
for /F "tokens=1,2* delims=: eol=" %%i in ('hg log -r %1') do (
  if "%%i"=="summary" call :ResetComment
  if "%%i"=="tag"     call :SetTag "%%j"
)
goto :End

:ResetComment
set Comment=
goto :End

:SetTag
set Tag=%~1
set Tag=%Tag:~9%
goto :End

:End
So now we're ready to upload our source code to Mercurial repository at Google Code. But this will happen only on the next week - we must make few more decisions before this historical moment ;) My next post will describe one of them.