Skip to content

Instantly share code, notes, and snippets.

@badukaire
Last active August 15, 2016 15:26
Show Gist options
  • Save badukaire/42d11d7b573b37fc408a4b3488640963 to your computer and use it in GitHub Desktop.
Save badukaire/42d11d7b573b37fc408a4b3488640963 to your computer and use it in GitHub Desktop.

the poor man's svn-git bridge

This article describes a simple way to work with git against a svn repository, without any additional tool nor installation procedures.

Please note that this text is in draft status. This notice should be removed soon.

the scenario

If, like me, you need or like to work with git against a svn repository, you have (at least) 2 well-known options: git-svn (included in git) and subgit. The subgit authors emphasize that their tool is better and much easier to use than git-svn (that's easy to believe). I had long ago tried git-svn and soon got frustrated about it, it's complicated and you may get your offline work lost or messed up. I have not been able to use subgit, it does not look like it's easy to install (at least when I tried I did not have a machine with the required latest debian 8), and also from its documentation looks a bit daunting and an overkill for simple user needs. It seems to start by installing a subgit server (what?).

So I tried to figure out a simpler workaround to be able to use git with my svn repos. I did not need anything of commercial grade, just the possibility of committing offline, or committing without publishing for later rebasing, or using the powerful git merge and rebase features. I didn't neither need to have access to older history, I just intented to work in a short time span sandbox (at home, on an airline trip, on the road, on the mountain ...). What I tried looked heretic and clumsy, but also simple:

How about initializing a git repo inside a svn working copy?

The cool thing is that it works. I guess I am not the only one who has tried it. However, I am really amazed not to have read about it on the internet.

I will not be surprised if this idea gets dismissed and bashed (for impurity or hackyness), but it works for me, and the more I use it the more I like it.

prerequisites

As a prerequisite, of course you need to have both svn and git installed. Only the basic packages, no plugins and extras are required.

You also need to be a bit fluent with svn and git basic workflow, including git add/commit, git remote creation, use of git push and fetch, and also basic git merge/rebase.

how to do it

First, get a new, pristine and clean svn WC with svn checkout. It does not need to be a root/trunk WC, just down to the folder with the files you need to work with. Tip: choose a svn URL from a branch where nobody may commit into, in order to minimize merging and conflicts (i.e. don't use an integration / public branch like trunk). If in doubt, create a new svn branch.

Then cd inside it, and run git init. As you may know this will initialize an empty git repo, creating a .git/ folder.

We shall configure the git WC to ignore the .svn/ folder, editing the .gitignore file and adding there a line with text ".svn". Optionally, create it adding the contents of the svn:ignore subversion property (which is the svn equivalent of the git ignore file.

Next step is to set your svn WC for not panicking with the new git files: We must ignore the git folder, and add the .gitignore file that will be created next. The commands so far below:

$ svn checkout <SVN URL> myProj_svngit
$ cd myProj_svngit
$ # this creates the .git/ folder
$ git init
$
$ # get svn ignore configuration, and edit it
$ svn propget svn:ignore . >>.gitignore # optional
$
$ # MUST add .svn, remove/add whatever
$ vim .gitignore
$
$ # store git ignore file into svn
$ svn add .gitignore
$
$ # tell svn to ignore git metadata
$ svn propedit svn:ignore . # add .git
$ svn status
 M      .
A       .gitignore
$
$ svn commit -m "allow coworking with git"

Now you are done with setting svn. It's time to import the project into git. Let's start just adding the ignore file which at least contains a line with .svn:

$ # tell git to ignore svn metadata
$ git add .gitignore
$ git commit -m "init repo with .gitignore" .gitignore

Now you have to add the other files using git add and commit. This is a one-time task that we may call git-import. Be careful with what you are adding because default system-wide ignore setups on svn and git sometimes differ. For example, if you use the vim editor it may be helpful to ignore the vim backup files (.*.swp) that are ignored by default in most svn configurations.

$ # import files into git
$ git add -A file1 folder2/
$ git commit -m "add file1 and folder2"
$ # and so on -- perhaps just using git add -A works

When you are done importing, it's time to move along and leave this folder, which will be used later as the gateway between your git and svn repos. Go up on folder and clone it with git.

$ cd ..
$ git clone myProj_svngit myProj

Now that will be your git-only folder. Verify that you can work there (did you forget to import any files from svn?) and you are ready to work with git.

inter-repo communication setup

In order to pass changes (commits) from git to svn (or eventually the other way), you have to create and link the repos using git remotes. The remotes setup that I suggest is as follows:

We will use the git-only WC for the work, and then have another git-only mirror as a centralized repo for your backups (it may also help to prevent losing work if you mess up with git). That mirror will be the gateway between the git repo and the git-svn repo.

Let's do it. Notice how the remote names and URLs are simplified but that's a personal choice, use your own naming and URLs conventions and style if you prefer.

$ # this will create a mirror clone in folder myProj.git
$ git clone --mirror myProj
$ cd myProj.git
$ git remote rm origin
$ cd ../myProj
$ git remote rm origin
$ git remote add mirror ../myProj.git
$ cd ../myProj_svngit
$ git remote add mirror ../myProj.git

With that, both the git-only and the git-svn WCs have a common central mirror from where to get (and send to) curated commits. You can type git remote -v to view the relationship between repos.

This configuration with a central repo is not necessary but I find it quite convenient. Of course you can avoid the use of that mirror but if you are a svn user I think you will like it. You can also add extra mirror remotes to the git-only repo, even external ones in hosting sites like github or other ISPs of your choice.

the workflow

Now you are ready to start your off-line work with git. Assuming you have your editor with your files, and your compilation or testing console somewhere else, you would perform a set of commands like this inside the myProj folder:

$ # edit files ...
$ git add parseDateISO.py
$ git commit -m "add feat X"
$
$ # testing, discover some bugs ...
$ # more edits ...
$ git add -A
$ git commit -m "fix feat X"
$ git rebase -i HEAD~2

You may do that with feat Y and bug Z, and now it's time to publish your work. For that, you just do:

$ git push mirror master # or whatever other branch

Now your commits are on the intermediate mirror. It's time to go to the mixed svn-git folder, and get the changes.

$ cd ../myProj_svngit
$ git fetch mirror
$ git merge mirror/master

That's it, now the changes from your git commits are in the svn WC ready to be committed (in the svn sense). You can check with svn status and svn diff what you've got before committing.

Note that if you do a git status there will be no changes.

$ git status
On branch master
nothing to commit, working directory clean
$

But of course, svn detects there are changes in the files, those brought over by the git fetch and merge:

$ svn status
M       parseDateISO.py
$ svn diff --diff-cmd <your fav diff tool>
Index: parseDateISO.py
======================================
$ svn commit -m "feat X, feat Y, and bug Z"
$ svn status
$

After doing a svn commit there won't be any changes for svn too, and that means that the git and svn repos are synchronized.

If you'd like to perform a commit for each one of these features or bugs, you can do selective git checkouts at each commit related to each issue. You are supposed to have performed interactive rebases prior to pushing to the mirror as suggested above, in order to beautify your git log.

the opposite direction

It is also possible to transfer commits to svn to git.

The above detailed procedure was assuming no one (including you) had commited to the svn branch. However it would not have been a disaster if that had happened, conflict solving in svn should not be painful.

The workflow for bringing some svn commits to your git repo you just would:

$ cd ../myProj_svngit
$ svn update
$ git add -A
$ git commit -m "svn revision nnnn"
$ git push mirror master
$
$ # now go to the git-only WC
$ cd ../myProj_svngit
$ git fetch mirror
$ git merge mirror master

If you have set other external remote mirrors (in other servers), you can publish there too:

$ git push github master # or whatever branch you are into

going further

The examples here are intended to be simple so that you focus on the exchange of commits between both systems. Work is only done on the default master branch, just a few commits are done.

But then when you are using git you can go as far as you want to. Create as many branches as you want, merge and rebase them, etc.

There is another consideration to make. If on git you create copies of similar files (you would use the svn copy command for that), it may be nice to be aware of that when getting back to svn.

Example, let's assume that while working on git you created a new implementation (subclass) of some base class, from another similar implementation class. Like parseDateUSA.py from parseDateISO.py, both inheriting from parseDate.py.

When you get into the mixed svn-git WC, do the git fetch and then the git merge mirror/master, you get the following output on svn status.

$ svn status
?       parseDateUSA.py
$

If you don't care much about the svn repo size or svn merging metadata, you could just do svn add parseDateUSA.py, and you'd be fine. You probably have other things to care about. But if you want to be proper, and you know this file is almost equal to parseDateISO.py, then a svn copy would be called for. For that just temporarily rename the file and do :

$ mv parseDateUSA.py XparseDateUSA.py
$ svn cp parseDateISO.py parseDateUSA.py
A         parseDateUSA.py
$ mv XparseDateUSA.py parseDateUSA.py
$ svn status
A  +      parseDateUSA.py
$

You can see that after the svn copy the temporary file is renamed back to its original name, and you are ready to commit with svn. A svn diff with or without the svn copy is completely different.

conclusion

This is a disclaimer more than a conclusion. This procedure is just an hack that fulfills my needs for using git on a svn repo. It may be very obvious but I decided to publish it because I have never read about something like that, and perhaps it's useful to somebody else.

The key point is that each VCS should ignore the metadata of the other (the .git and .svn folders, respectively), and also add (store) the ignore file for convenience. Since svn ignore data is inside the .svn folder (and perhaps not as a file), it can not be added, but the .git file is added to svn because it is small and to avoid having unchecked files show up when checking the status.

If you will be doing many sparse checkouts you will end out with many folders with svn:ignore properties containing the .git entry. If you think that will be the case, then just do a minimal svn checkout of the root folder and set the ignore property there.

If some of the commands in this text are unclear for you, perhaps you should get a bit of practice using git. Specially using remotes and merging. It may take time to understand and some hours of practice with try and error (and retry ...).

And needless to say, your comments are welcome.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment