git notes main page | gitolite main page | license

IMPORTANT NOTE: although this page has a "" URL, this is not about gitolite. That's just an artifact of "" being translated to "" and so ALL my git related stuff gets carried over. Gitolite documentation has another /gitolite in the URL, so you can tell. My apologies for this confusion.

converting Visual Source Shredder to GIT

[Thanks to for the new expansion of VSS]

This is a 2 step process. For large values of 2, as they say :) But the nice thing is it can all be done in Linux; no need to use a Windows machine as long as you have a copy of the full VSS repo.

1 build vss2svn

The base for all this is What I did is the following:

And that should be that…

2 run vss2svn

This should be easy enough, for most normal repos. You need to get the entire VSS repo onto your system (the one that contains something called ‘srcsafe.ini’). Put that in some “work” directory, and do this: --vssdir PATH_TO_VSS_REPO --revtimerange 60

I have no idea how to deal with labels etc., properly in this command, but the basic stuff should work fine.

3 load the SVN dump file

The previous step should produce just an SVN dump file, which you need to load, and that gives you an SVN repo.

svnadmin create svnrepo
svnadmin load svnrepo < SVN_DUMPFILE

You may want to checkout this repo and at least see what “projects” it has, because I do not know any other way of doing this if you don’t already know (from the VSS side).

mkdir svnco
svn checkout file://$PWD/svnrepo svnco

4 convert svn to git

This is, naturally, the easiest of all, especially if you don’t intend to go back :)

mkdir gitrepo
cd gitrepo
git svn clone file://PATH_TO_SVN_REPO

Of course, if you have used the VSS repo to store multiple projects, this will not work right – VSS/SVN can happpily dump unrelated projects in one repository (or subdirectories masquerading as projects for that matter), whereas in git each project is in its own repository.

So do it this way:

git svn clone file://PATH_TO_SVN_REPO/projectname
    # for each project

5 checking the results

5.1 tags

Tags (labels in VSS) don’t seem to carry across properly.

^1 My idea of a tag is a symbolic name for a certain revision, so if you check it out you should see approximately the same number of files that the repo had around that time frame. What I actually see is that the number of files representing the various tags (as seen from an SVN checkout) range from 5% to 75% of the actual number of files in the repo! So – until I see a VSS repo where the tags are actually used (and I mean used, as in, “these tags do get checked out when needed; they’re not just for show!”) I’m not going to worry about it.^^

5.2 deleted files

I’ve also not tested if renamed/deleted files etc also work OK, but I think they ought to.

5.3 that damn CRLF thing

When you do it this way, you don’t have an opportunity to fix the CRLFs as they go in – they’ll all go into the repo as CRLF. If you try to force it on the “git svn clone” itself (by prepping a blank git repo with git config core.autocrlf input and then svn-cloning into that), you get checksum errors!

You have to do this after the fact. Here’s the sequence:

git config --unset core.autocrlf
git checkout branch
git config core.autocrlf input
git filter-branch --tree-filter 'find . -type f -print0|xargs -0 touch' HEAD
git config --unset core.autocrlf

The first ‘unset’ is just a precaution. If you do a checkout with autocrlf set to input, the work tree will immediately look dirty, so the filter-branch will refuse to run. So you checkout under “default” conditions, then set the autocrlf config, then run the tree filter.

The last ‘unset’ is required; you really don’t want autocrlf to be anything on a Unix box.

Note: in theory, a dummy filter that does nothing should work. But it seems to miss a lot of files, which still go in with CRLFs. Doing an explicit touch seems to be the only way. I have no idea why this is so.

5.4 bottom line

Anyway, your bottom line should be:

  1. confirm that the tip is OK: make sure that the actual trees generated at the tip of the git repository match those in a fresh “get” from the corresponding project in VSS
  2. try to confirm past history is OK: if possible, make the same check for a couple of intermediate revisions also
  3. save the VSS repo in a read-only location for future reference, should there ever (much later in time) be a question on the accuracy of the history at some revision that was not explicitly checked during the migration.

6 alternative method using perl on windows

There is another method, achieved by hacking a perl script that was supposed to convert to SVN, to make it go to git instead. Pros and cons:

One reason why I’m keeping this around is if a VSS repo is corrupt enough (and it seems that happens quite often) then maybe only the original VSS can read it. To recover usable data from such an instance may be a futile attempt but no harm keeping two tools around…!

7 Appendices

7.1 installing the perl modules

Note: I did indeed have to install DBD::SQLite2 manually. Also, Text::Glob had to be installed using perl -MCPAN -e shell followed by install Text::Glob; for some reason the “-e install …” thing didn’t work

for m in Module::Build XML::Simple Time::CTime DBD::SQLite2 DBI Tie::IxHash Text::Glob Data::UUID; do
   perl -MCPAN -e "install $m"

# SQLite2 failed to install, even with "force install ...", so:
cd ~/.cpan/build/DBD-SQLite2-0.33
# May need to create the makefile...
# perl Makefile.PL
make install

# the Config:Ini module is not in CPAN, so:
tar xzf Config-Ini-1.08.tar.gz 
cd Config-Ini-1.08
perl Makefile.PL
make install

7.2 patch for libboost 1.36

commit 4a3a283593bf8a991e0553e3ac3252c75a1349db
Author: Sitaram Chamarty <>
Date:   Thu Apr 16 09:27:00 2009 +0530

    (touch wood) fixed up a weird incompatibility with my *specific* version of boost

diff --git a/vss2svn/ssphys/SSPhysLib/SSItemInfoObject.cpp b/vss2svn/ssphys/SSPhysLib/SSItemInfoObject.cpp
index 7d88a8e..0cd12ea 100644
--- a/vss2svn/ssphys/SSPhysLib/SSItemInfoObject.cpp
+++ b/vss2svn/ssphys/SSPhysLib/SSItemInfoObject.cpp
@@ -162,7 +162,7 @@ std::string SSItemInfoObject::GetDataFileName () const
   std::string fileName = GetFile ()->GetFileName () + GetLatestExt ();
   boost::filesystem::path fpath(fileName, boost::filesystem::native);

-  if (!boost::filesystem::exists(fpath) && fpath.has_leaf() && fpath.has_branch_path())
+  if (!boost::filesystem::exists(fpath) && fpath.has_filename() && fpath.has_parent_path())
     std::string lcLeaf = fpath.leaf();
     std::string ucLeaf = fpath.leaf();

  1. rant↩︎