90

An svn repository I'm mirroring through git-svn has changed URL.

In vanilla svn you'd just do svn switch --relocate old_url_base new_url_base.

How can I do this using git-svn?

Simply changing the svn url in the config file fails.

kch
  • 77,385
  • 46
  • 136
  • 148
  • You should try and possibly accept this answer: https://stackoverflow.com/a/4061493/1221661 – Fritz Jul 10 '17 at 14:48
  • 1
    Most up to date answer: https://stackoverflow.com/a/40523789/537554. Same question, but asked from a Git user's perspective. – ryenus Mar 21 '18 at 01:19

8 Answers8

64

This handles my situation pretty well:

https://git.wiki.kernel.org/index.php/GitSvnSwitch

I cloned using the file:// protocol, and wanted to switch to the http:// protocol.

It is tempting to edit the url setting in the [svn-remote "svn"] section of .git/config, but on its own this does not work. In general you need to follow the following procedure:

  1. Switch the svn-remote url setting to the new name.
  2. Run git svn fetch. This needs to fetch at least one new revision from svn!
  3. Change the svn-remote url setting back to the original URL.
  4. Run git svn rebase -l to do a local rebase (with the changes that came in with the last fetch operation).
  5. Change the svn-remote url setting back to the new URL.
  6. Now, git svn rebase should work again.

Adventurous souls may want to try --rewrite-root.

Gilles 'SO- stop being evil'
  • 104,111
  • 38
  • 209
  • 254
kch
  • 77,385
  • 46
  • 136
  • 148
  • 2
    to be fair, this actually failed for me, and I ended up re-cloning the repo. It is hard to get git to handle it when the svn directory is renamed. – Gregg Lind Dec 18 '08 at 21:51
  • I would prefer to accept a more detailed writeup, but fair enough, I'll accept it until a new answer comes along. – kch Mar 22 '10 at 01:42
  • 2
    Here's another description of that procedure: http://theadmin.org/articles/git-svn-switch-to-a-different-a-svn-url/ – n8gray Oct 03 '11 at 17:20
  • the link provided in the accepted answer is outdated and the new one as of now is: https://git.wiki.kernel.org/articles/g/i/t/GitSvnSwitch_8828.html I followed the "General Case" and it was simple and really worked fine. – TcMaster Nov 14 '11 at 09:23
  • 2
    @TcMaster: worked for me as well... but that's why answers should not contain *only* links, they get outdated and become useless... I'm going to add a community wiki answer. – UncleZeiv Nov 15 '11 at 12:20
  • it might be important to mention that the steps 4 to 6 will have to be applied on all locally tracked svn branches, otherwse git-svn operations on those branches will fail with the message 'Unable to determine upstream SVN information from working tree history'. – sfera Jun 17 '16 at 09:27
  • I had problems with this solution, on step 2 the error _Invalid filesystem path syntax: Cannot replace a directory from within at /usr/share/perl5/Git/SVN/Ra.pm line 312._ Anyone knows hot to fix this? why does this happen? – cody Jul 11 '18 at 15:29
40

You can see if the following works OK:

  1. If svn-remote.svn.rewriteRoot does not exist in config file (.git/config):

    git config svn-remote.svn.rewriteRoot <currentRepositoryURL>
    
  2. If svn-remote.svn.rewriteUUID does not exist in config file:

    git config svn-remote.svn.rewriteUUID <currentRepositoryUUID>
    

    The currentRepositoryUUID can be obtained from .git/svn/.metadata.

  3. git config svn-remote.svn.url <newRepositoryURL>

Rafareino
  • 2,515
  • 1
  • 19
  • 26
H Krishnan
  • 896
  • 9
  • 12
  • Fantastic - thanks, this worked great for me (cloned via `file://`, switch to `svn+ssh`); just noting that: this procedure does not need to "fetch at least one new revision from svn"; also `./.git/svn/.metadata` after first `svn rebase` contains the `` as `reposRoot` - but this isn't enough to remove the `rewrite*` keys from `.git/config`; thus those keys should be kept permanently there, as far as I understand it. – sdaau Apr 22 '13 at 06:21
  • 1
    It worked to me too, like a charm. The OP should try this one and get it as the correct answer. – Rafareino Nov 05 '15 at 22:43
  • Perfect. I had a big project with thousands of commits in the history so a new clone would have destroyed the history (or take a really long time to checkout). I'm using `svn+ssh://` and our svn-server just changed domain from `.se` to `.com` to sanitize our internal naming. – UlfR Mar 01 '16 at 10:06
  • Worked a treat for me. Had a 2 year old repo with 1,000s of commits, the repo was moved to a new host, so this avoided a (dreaded) full svn clone. – David Victor Apr 13 '16 at 10:35
21

Unfortunately most of the links in these answers aren't working, so I'm going to duplicate a bit of information from the git wiki for future reference.

This solution worked for me:

  • Edit the svn-remote url (or fetch path) in .git/config to point to the new domain/url/path

  • Run git git svn fetch. This needs to fetch at least one new revision from svn!

  • If you attempt git svn rebase now, you'll get an error message like this:

    Unable to determine upstream SVN information from working tree history
    

    I think this is because git svn is confused by the fact that your latest commit prior to the fetch will have a git-svn-id pointing to the old path, which doesn't match the one found in .git/config.

  • As a workaround, change svn-remote url (or fetch path) back to the original domain/url/path

  • Now run git svn rebase -l again to do a local rebase with the changes that came in with the last fetch operation. This time it will work, apparently because git svn won't be confused by the fact that the git-svn-id of the new head doesn't match with that found in .git/config.

  • Finally, change svn-remote url (or fetch path) back to the new domain/url/path

  • At this point git svn rebase should work again!

The original information was found here.

UncleZeiv
  • 18,272
  • 7
  • 49
  • 77
3

Git svn relies heavily on the svn URL. Every commit that is imported from svn has a git-svn-id that includes the svn URL.

A valid relocations strategy is to call git-svn clone on the new repository and merge the changes onto that new close. For a more detailed procedure, see this article:

http://www.sanityinc.com/articles/relocating-git-svn-repositories

Gilles 'SO- stop being evil'
  • 104,111
  • 38
  • 209
  • 254
Adam Alexander
  • 15,132
  • 5
  • 42
  • 41
2

git filter-branch

This script, taken from a blog entry, has worked for me. Supply old and new repo URL as parameter, just like for svn switch --relocate.

The script calls git filter-branch to replace Subversion URLs in the git-svn-id in the commit messages, updates .git/config, and also updates git-svn metadata by recreating it using git svn rebase. While git svn clone might be the more robust solution, the filter-branch approach works much faster for huge repositories (hours vs. days).

#!/bin/sh

# Must be called with two command-line args.
# Example: git-svn-relocate.sh http://old.server https://new.server
if [ $# -ne 2 ]
then
  echo "Please invoke this script with two command-line arguments (old and new SVN URLs)."
  exit $E_NO_ARGS
fi

# Prepare URLs for regex search and replace.
oldUrl=`echo $1 | awk '{gsub("[\\\.]", "\\\\\\\&");print}'`
newUrl=`echo $2 | awk '{gsub("[\\\&]", "\\\\\\\&");print}'`

filter="sed \"s|^git-svn-id: $oldUrl|git-svn-id: $newUrl|g\""
git filter-branch --msg-filter "$filter" -- --all

sed -i.backup -e "s|$oldUrl|$newUrl|g" .git/config

rm -rf .git/svn
git svn rebase
Jean-Francois T.
  • 11,549
  • 7
  • 68
  • 107
krlmlr
  • 25,056
  • 14
  • 120
  • 217
1

git_fast_filter

Yet faster than git-filter-branch (i.e., minutes instead of hours), but similar in spirit, is to use git_fast_filter. However, this requires a bit more coding, and no neat ready-packed solution exists. In contrast to git-filter-branch, this will create a new repo from an old one. It is assumed that master points to the last SVN commit.

  1. Clone git_fast_filter from the Gitorious repo.
  2. Create a Python script in the same directory where you cloned git_fast_filter based on this Gist, set the executable bit using chmod +x. Adapt old and new repository paths. (Contents of the script are pasted below, too.)
  3. Initialize a new target repository using git init, change working directory to this new repo.
  4. Execute the following pipe:

    (cd path/to/old/repo && git-fast-export --branches --tags --progress=100) | \
        path/to/git_fast_filter/commit_filter.py | git-fast-import
    
  5. Copy .git/config, and perhaps other relevant files in .git/info from the old repo to the new repo.

  6. Remove .git/svn.
  7. Make git-svn aware of the new revision number mapping

    1. Execute git branch refs/remotes/git-svn master

      • Your git-svn remotes might be called different than refs/remotes/git-svn, consult .git/config, svn-remote sections
    2. Execute git svn info. If this command freezes, something's wrong. It should rebuild the revision number mapping.

    3. Remove the fake branch refs/remotes/git-svn, it will be recreated by git-svn

  8. Synchronize by calling git svn rebase.

Below are the contents of commit_filter.py, replace the values of IN_REPO and OUT_REPO as appropriate:

#!/usr/bin/python

from git_fast_filter import Commit, FastExportFilter
import re
import sys

IN_REPO = "https://svn.code.sf.net/p/matsim/code"
OUT_REPO = "https://svn.code.sf.net/p/matsim/source"

IN_REPO_RE = re.compile("^git-svn-id: %s" % re.escape(IN_REPO), re.M)
OUT_REPO_RE = "git-svn-id: %s" % OUT_REPO

def my_commit_callback(commit):
  commit.message = IN_REPO_RE.sub(OUT_REPO_RE, commit.message)
  sys.stderr.write(".")

filter = FastExportFilter(commit_callback = my_commit_callback)
filter.run()
Community
  • 1
  • 1
krlmlr
  • 25,056
  • 14
  • 120
  • 217
0

The above git svn rebase -l solution didn't work for me. I decided to go about it a different way:

  1. Clone old SVN repo into git repo old and new SVN into git repo new
  2. Fetch old into new
    • cd new
    • git fetch ../old
    • git tag old FETCH_HEAD
  3. Rebase new on top of old (should succeed because the trees in the root of new and the tip of old are identical)
    • git checkout master (Assumes that the master branch is pointing at the SVN head. This will be the case with a clean clone; otherwise dcommit before you start.)
    • git rebase --root --onto old
  4. Rebuild the git-svn metadata of new to account for the rebase
    • git update-ref --no-deref refs/remotes/git-svn master (adjust the remote ref depending on how you cloned, e.g. it could be refs/remotes/svn/trunk)
    • rm -r .git/svn
    • git svn info
Ben Challenor
  • 3,365
  • 1
  • 35
  • 35
  • 1. Do you start with a fresh clone of the new location to new? If yes, why are you not done at that point? 2. If you rebase new on old, do your old commits all mention the old URL in their svn-id commit log entry? 3. Is there any documentation that it is save to remove .git/svn? (3b: which command is rebuilding the git-svn metadata, git svn info?) – Micha Wiedenmann Jul 29 '16 at 07:01
0

Based off of some of the other responses to this question, I have come up with a Ruby script that handles the git-svn relocating. You can find it at https://gist.github.com/henderea/6e779b66be3580c9a584.

It handles the relocate without checking out another copy, and it even handles the case where there are un-pushed changes in one or more branches (since that breaks the regular logic). It uses stuff from the git filter-branch answer (for the main logic) and the answer about copying branches from one instance of the repo to another (for copying branches with un-pushed changes).

I've been using this to relocate a bunch of git-svn repos that I have for work, and this version of the script (I've been through countless iterations) seems to work for me. It isn't super-fast, but it does seem to handle all of the cases I've encountered and result in a fully-relocated repo.

The script gives you the option to create a copy of the repo before making any changes, so you can use this option to create a backup. Creating a copy is required if you have un-pushed changes in any branches.

The script does not use any gems or other libraries not included in the normal MRI Ruby installation. It does use the readline and fileutils libraries included in MRI.

Hopefully my script will prove useful to someone else. Feel free to make changes to the script.

NOTE: I've only tested this script with git 2.3.0/2.3.1 and Ruby 2.2.0 on OS X 10.10 Yosemite (since that's the environment I use), but I would expect it to work on other environments as well. No guarantees about Windows, though.