Pushing an existing Git repository to SVN

Viewed 115679

I've been doing all my work in Git and pushing to GitHub. I've been very happy with both the software and the site, and I have no wish to change my working practices at this point.

My PhD adviser is asking all students to keep their work in an SVN repository that's hosted at the university. I've found tons of documentation and tutorials about to pull down an existing SVN repository into Git, but nothing about pushing a Git repository to a fresh SVN repository. I expect there must be some way to do this with a combination of git-svn and a fresh branch and rebasing and all those wonderful terms, but I'm a Git newbie and don't feel confident with any of them.

I then want to just run a couple of commands to push commits to that SVN repository when I choose. I wish to keep using Git and just have the SVN repository mirror what's in Git.

I'll be the only person ever committing to SVN, if this makes any difference.

18 Answers

I needed this as well, and with the help of Bombe's answer + some fiddling around, I got it working. Here's the recipe:

Import Git -> Subversion

1. cd /path/to/git/localrepo
2. svn mkdir --parents protocol:///path/to/repo/PROJECT/trunk -m "Importing git repo"
3. git svn init protocol:///path/to/repo/PROJECT -s
4. git svn fetch
5. git rebase origin/trunk
5.1.  git status
5.2.  git add (conflicted-files)
5.3.  git rebase --continue
5.4.  (repeat 5.1.)
6. git svn dcommit

After #3 you'll get a cryptic message like this:

Using higher level of URL: protocol:///path/to/repo/PROJECT => protocol:///path/to/repo

Just ignore that.

When you run #5, you might get conflicts. Resolve these by adding files with state "unmerged" and resuming rebase. Eventually, you'll be done; then sync back to the SVN repository, using dcommit. That's all.

Keeping repositories in sync

You can now synchronise from SVN to Git, using the following commands:

git svn fetch
git rebase trunk

And to synchronise from Git to SVN, use:

git svn dcommit

Final note

You might want to try this out on a local copy, before applying to a live repository. You can make a copy of your Git repository to a temporary place; simply use cp -r, as all data is in the repository itself. You can then set up a file-based testing repository, using:

svnadmin create /home/name/tmp/test-repo

And check a working copy out, using:

svn co file:///home/name/tmp/test-repo svn-working-copy

That'll allow you to play around with things before making any lasting changes.

Addendum: If you mess up git svn init

If you accidentally run git svn init with the wrong URL, and you weren't smart enough to take a backup of your work (don't ask ...), you can't just run the same command again. You can however undo the changes by issuing:

rm -rf .git/svn
edit .git/config

And remove the section [svn-remote "svn"] section.

You can then run git svn init anew.

Using git rebase directly will lose the first commit. Git treats it different and can't rebase it.

There is a procedure that will preserve full history: http://kerneltrap.org/mailarchive/git/2008/10/26/3815034

I will transcribe the solution here, but credits are for Björn.

Initialize git-svn:

git svn init -s --prefix=svn/ https://svn/svn/SANDBOX/warren/test2

The --prefix gives you remote tracking branches like "svn/trunk" which is nice because you don't get ambiguous names if you call your local branch just "trunk" then. And -s is a shortcut for the standard trunk/tags/branches layout.

Fetch the initial stuff from SVN:

git svn fetch

Now look up the hash of your root commit (should show a single commit):

git rev-list --parents master | grep '^.\{40\}$'

Then get the hash of the empty trunk commit:

git rev-parse svn/trunk

Create the graft:

git replace --graft <root-commit-hash> <svn-trunk-commit-hash>

Now, "gitk" should show svn/trunk as the first commit on which your master branch is based.

Make the graft permanent:

git filter-branch -- ^svn/trunk --all

Drop the graft:

git replace -d <root-commit-hash>

gitk should still show svn/trunk in the ancestry of master.

Linearize your history on top of trunk:

git svn rebase

And now "git svn dcommit -n" should tell you that it is going to commit to trunk.

git svn dcommit

Create a new directory in the Subversion repository for your project.

# svn mkdir --parents svn://ip/path/project/trunk

Change to your Git-managed project and initialize git-svn.

# git svn init svn://ip/path/project -s
# git svn fetch

This will create a single commit because your SVN project directory is still empty. Now rebase everything on that commit, git svn dcommit and you should be done. It will seriously mess up your commit dates, though.

If you want to keep on working with Git as your main repository and just need to "export" the revisions to SVN from time to time, you could use Tailor to keep the SVN repository in sync. It can copy revisions between different source control systems and would update the SVN with the changes you make in Git.

I haven't tried a Git-to-SVN conversion, but for a SVN -> SVN example see this answer.

Yet another sequence that worked (with some comments on each step):

  1. Install git-svn and subversion toolkits:

    sudo apt-get install git-svn subversion
    
  2. Switch inside the PROJECT_FOLDER

    cd PROJECT_FOLDER
    
  3. Create the project path on the Subversion server (unfortunately the current git-svn plugin has a defect in comparison with TortoiseSVN). It is unable to store source code directly into the PROJECT_FOLDER. Instead, by default, it will upload all the code into PROJECT_FOLDER/trunk.

    svn mkdir --parents protocol:///path/to/repo/PROJECT_FOLDER/trunk -m "creating git repo placeholder"

This is the place where trunk at the end of the path is mandatory

  1. Initialize the git-svn plugin context inside the .git folder

    git svn init -s protocol:///path/to/repo/PROJECT_FOLDER
    

    This is the place where trunk at the end of the path is unnecessary

  2. Fetch an empty Subversion repository information

    git svn fetch
    

    This step is helping to synchronize the Subversion server with the git-svn plugin. This is the moment when git-svn plugin establishes remotes/origin path and associates it with the trunk subfolder on the server side.

  3. Rebase old Git commits happened before the git-svn plugin became involved in the process (this step is optional)

    git rebase origin/trunk
    
  4. Add new/modified files to commit (this step is regular for Git activities and is optional)

    git add .
    
  5. Commit freshly added files into the local Git repository (this step is optional and is only applicable if step 7 has been used):

    git commit -m "Importing Git repository"
    
  6. Pushing all the project changes history into the Subversion server:

    git svn dcommit
    

You can make a new SVN repository. Export your Git project (fleshing out the .git files). Add it to the SVN repository (initializing the repository with what you had so far in Git). Then use the instructions for importing SVN repositories in a fresh Git project.

But this will lose your previous Git history.

there are three methods:

  1. rebase: as the other answers

  2. commit id: find svn first commit id and git first commit id , echo their into .git/info/grafts:echo "git_id svn_id}" > .git/info/grafts then git svn dcommit

  3. checkout every git commit,copy files into svn_repo, svn commit

bash demo: github demo

v1.x: use rebase and commit id

v2.x: use copy files,then svn commit

This is what I would do. Assuming my local branch is called main. Fill in the gaps, cause I do not remember the exact git svn commands, as I have not used it in a while.

  • create in svn the branch that will be used for the project.
  • use git svn to clone the svn repo you want (at least, the branch you want to use so that you do not have to fetch the other millions of revisions that we do not care about for this endeavor).
  • checkout the svn branch in the new git repo.
  • Add to this repository a remote that points to the original git repo you are using for the project. Let's say the remote is called the-real-stuff.
  • git fetch the-real-stuff # so that we get to see what's in the real repo
  • Given that this is the first time we will be pulling our code into the svn one, we have to trick git so that it can "merge" that code: git merge --allow-unrelated-histories the-real-stuff/main -m "Whatever comment I want in the svn revision". This will "merge" both branches locally.
  • git svn dcommit so that you can push the content of the project as it is right now.

Continue working on the original git repo. Then, when you want to push into svn, go to the git svn clone and do:

git fetch the-real-stuff # get visibility to the changes in the original git repo
git merge the-real-stuff/main -m "Whatever comment I want to show on this svn revision"
git svn dcommit

And you are done.

I like the idea of using 2 repos so that we do not fill out the original repo with svn stuff, though it's possible to keep the whole thing in a single git repo.

Related