Migrating from SVN to GIT. It has been done before, …, but has it?

Recent changes in policy re-ignited our quest to migrate away from Subversion to GIT, as GIT is much more powerful for branching and merging e.t.c.

Migrating from Subversion is not very hard; you just start over, right? So you want to lose all the project history? I don’t think so.

The migrated GIT-repository should include all history, all commit messages, all tags, all branches. How do you do that?

David Zych wrote a great article describing how to migrate from SVN to GIT.

(by the way, David; if you ever read this; you have a typo in your article; the switch for branches is not -B but -b -- lowercase as opposed to uppercase.)

As it turns out; GIT has tools for that! But the steps in the article did not work for us;

After running the command, I got a folder with trunk, branches, tags e.t.c. instead if the actual project. Turns out, this is OUR mistake.

When we set-up our Subversion server, we did not want to go through the hassle of creating a repository for every single plugin, theme, tool we created. So we just created 3 repositories; plugins, themes, other.

The tools as described by David do not take this into account. These are the changes to Davids article we had to make;

Because our project is one directory deep in a “global” repository, as opposed to the root of that repository, we do not point to the project-url, but to the actual repository the project is in;

Let’s see the git init command:

git svn init http://some.svn.server/my-project --prefix svn \
-T Trunk -b Branches -t Tags

or

git svn init <URL to repository> --prefix svn \
-T <name of trunk> -b <name of branches> -t <name of tags>

Example of a normal SVN repo:

svn://some.svn.server/my-project

Example of one of our repositories:
svn://some.svn.server/plugins/my-project

Which is in fact a sub-directory of the actual repository
svn://some.svn.server/plugins

and my-project is the name of that sub-directory.

One would think you need to use

svn://some.svn.server/plugins/my-project
as the repository URL, but in this case, the repo is
svn://some.svn.server/plugins

So;

git svn init svn://some.svn.server/plugins --prefix svn \
-T <name of trunk> -b <name of branches> -t <name of tags>

As we surely do not want to migrate EVERYTHING we have in our plugins repository into our new project-level GIT repository, we specify the project by adding it as the tags/branches/trunk directory;

git svn init svn://some.svn.server/plugins --prefix svn \
-T my-project/trunk -b my-project/branches -t my-project/tags

Because our folders don’t start with a /, GIT will complain we need a / at the end of our prefix, so --prefix svn becomes --prefix svn/

Now the complete command reads:

git svn init svn://some.svn.server/plugins --prefix svn/ \
-T my-project/trunk -b my-project/branches -t my-project/tags

And that’s it; now we continue with the rest of Davids article :)

Now a hint; to link all the branches, the article states to

git branch -a
 and either manually or automatically do
git branch <new branch name> remotes/svn/<current branch name>
 where the last part is taken from the output of
git branch -a

This command lists tags too, so it might be handy to filter that list;

git branch -a | egrep -v 'tags|trunk|master'

And now we can automate the branch-linking;

for branch in $(git branch -a | egrep -v 'tags|trunk|master'); do \
branchname=$(echo $branch | sed 's_remotes/svn/__g'); \
git branch $branchname $branch ; done

And for the tags, we do the same, or, almost the same ;)

for tag in $(git branch -a | grep 'remotes/svn/tags/'); do \
tagname=$(echo $tag | sed 's_remotes/svn/tags/__g'); \
git tag -a -m "Migrating SVN tag $tagname" $tagname $tag ; done

Now, a note here; for some reason, David writes in his article; refs/tags/tag-name but that gives me an error. This looks to be a typo as well as I think it should read:

refs/remotes/svn/tags/tag-name

A final note, on what I think the beauty of GIT is; it is the distributed-ness of the repositories; all we did up til this point is all local. Nothing is pushed to GitHub or Bitbucket or GitLab, or whatever service you prefer to use. It is all just on your computer. If you messed up; delete the directory, and start again. To check if you did things right, I suggest using a powerful git GUI application, like SourceTree or GitHub Desktop.

When you are ready to make things permanent, we continue to the final command sequence;

git remote add newrepo https://url.to.git/repo.git

git push --all newrepo

git push --tags newrepo

newrepo is a name for the remote, it’s not something you will be dealing with after this. And of course; https://url.to.git/repo.git should be an existing, empty repository on [insert your favourite GIT service here] :)

Good luck, and happy migrating!

Addendum: the cliff-notes version:

  1. create an empty repository on (for example) GitHub.com, copy the git-repository-url, for example:
    git@github.com:username/my-project.git
  2. create a directory on your computer and go there (MacOS, Linux or WSL on windows);
    mkdir ~/Desktop/svn-to-git-migration && \
    cd ~/Desktop/svn-to-git-migration
  3. Initialize the git-svn repo;
    git svn init svn://some.svn.server/plugins --prefix svn/ \
    -T my-project/trunk -b my-project/branches -t my-project/tags
  4. Point git to your author-map (see Davids article for details);
    git config svn.authorsfile /path/to/authors.txt
  5. Fetch the SVN data;
    git svn fetch
  6. Get a cuppa while you wait
  7. Migrate branches;
    for branch in $(git branch -a | egrep -v 'tags|trunk|master'); do \
    branchname=$(echo $branch | sed 's_remotes/svn/__g'); \
    git branch $branchname $branch ; done
  8. Migrate tags;
    for tag in $(git branch -a | grep 'remotes/svn/tags/'); do \
    tagname=$(echo $tag | sed 's_remotes/svn/tags/__g'); \
    git tag -a -m "Migrating SVN tag $tagname" $tagname refs/$tag ; done
  9. Link your local git repo to the upstream repo;
    git remote add newrepo <insert repo-url from 1 here>
  10. Push data and branches;
    git push --all newrepo
  11. Push tags;
    git push --tags newrepo
  12. Checkout your remote in a fresh directory to work with (or check your migration);
    git clone <insert repo-url from 1 here> ~/Desktop/fresh-checkout

Author: Remon Pel

WebDeveloper though not WebDesigner

Leave a Reply

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.