74

I have got a central SVN repository I must commit to, but I've got a passion for git (like any other developer I know). The case is well known.

Then I read about git-svn and gave it a try. Since I don't need the full history, just from two months or so, I did like this:

git svn clone -r 34000 -s https://svn.ourdomain.com/svn/repos/Project/SubProject

SubProject had, as usual, the subdirectories trunk, tags and branches. Great.

Then, in order to get the last revision, I did

git svn rebase

Some downloads later, great. Last revision, logs, etc. Ok, now I'll switch to my feature branch.

$ git branch 
* master
$ git branch -r  
  trunk
$ git branch -a  
* master
  remotes/trunk

The questions are: Where are my branches? Have I done something wrong? How should I do in order to get my branches in the new git repo?

git-svn, wherever I have read about it, dealt wisely with branches and tags, but the behaviour is not what I expected. Thanks!

EDIT: I have just found out that git svn fetch will do it. But it will get all revisions, which is something I wouldn't like.

H. Pauwelyn
  • 13,575
  • 26
  • 81
  • 144
Luís Guilherme
  • 2,620
  • 6
  • 26
  • 41
  • 2
    Well, this won't answer your question, hence the comment: When using git-svn you'll break subversion's merge tracking features, as git-svn does not support them. IMHO this problem alone disqualifies git-svn for serious use with a subversion repository. I haven't found any info whether this feature will ever be developed -- probably not, as people like to switch to a DVCS instead of using such hacks. – gimpf Feb 11 '10 at 12:33
  • I have read somewhere that transforming git merges in commits using --squash won't break subversion merge tracking. – Luís Guilherme Feb 11 '10 at 13:16
  • 5
    For the standard "trunk/branches/tags" layout, which you seem to be using, you could try [`--stdlayout`](http://www.kernel.org/pub/software/scm/git/docs/git-svn.html), as in `git svn clone --stdlayout svn://...` - see http://stackoverflow.com/questions/5361559/what-does-the-stdlayout-do-in-git-svn-clone – Joel Purra Aug 13 '12 at 19:44
  • @JoelPurra don't know about others but `--stdlayout` was what I needed – slf Mar 15 '13 at 19:52
  • 4
    Isn't `-s` a short for `--stdlayout`? – GabrielF Jan 21 '15 at 22:06

9 Answers9

82

You'll need several steps.

  1. supply proper trunk, branches and tags folder names and fetch svn repo:

    git svn init -t tags -b branches -T trunk https://mysvn.com/svnrepo
    git svn fetch
    
  2. Since tags in svn are real branches, create git tags from tag branches:

    git for-each-ref --format="%(refname:short) %(objectname)" refs/remotes/tags |  cut -d / -f 3- |
    while read ref
    do
      echo git tag -a $ref -m 'import tag from svn'
    done
    
  3. Delete tag branches

    git for-each-ref --format="%(refname:short)" refs/remotes/tags | cut -d / -f 2- |
    while read ref
    do 
      echo git branch -rd $ref
    done
    
  4. Since tags marked in the previous step point to a commit "create tag", we need to derive "real" tags, i.e. parents of "create tag" commits.

    git for-each-ref --format="%(refname:short)" refs/tags |
    while read ref
    do
      tag=`echo $ref | sed 's/_/./g'` # give tags a new name
      echo $ref -\> $tag
      git tag -a $tag `git rev-list -2 $ref | tail -1` -m "proper svn tag"
    done
    
  5. All we have to do now is to remove old tags.

sebastian-c
  • 15,057
  • 3
  • 47
  • 93
Vanuan
  • 31,770
  • 10
  • 98
  • 102
  • 3
    The "cut" command wasn't working for me for the tags migration, probably because of a different output of "git" command. This modified command line worked for me : `git for-each-ref --format="%(refname:short) %(objectname)" refs/remotes/tags | while read tag ref; do echo git tag -a $tag -m \"Import $tag from svn\" $ref; done` – Stéphane Dec 18 '12 at 14:20
  • 6
    Same here. Also note, that you need to remove the 'echo' in the commands once you are happy with what it would do and want to enable the actual command. I ended up with the following line which directly tags the parent and uses the SVN message: `git for-each-ref --format="%(refname:short) %(objectname)" refs/remotes/tags | cut -d / -f 2- | while read tag ref ; do msg=$(git log --pretty=format:'%s' -1 ${ref}) ; git tag -f -a $tag -m "$msg" ${ref}^ ; done` – Florian Sep 05 '13 at 13:02
  • What's the output of `git for-each-ref --format="%(refname:short)" refs/remotes/tags` in your case? – Vanuan Sep 05 '13 at 13:50
  • 2
    So, it's not the git output. Because `echo tags/v0.0.1 | cut -d / -f 2-` outputs `v0.0.1` for me. Could you run this command? – Vanuan Sep 13 '13 at 10:28
  • 11
    Step 2: On my machine the tags are not in `refs/remotes/tags` instead `refs/remotes/origin/tags`. – Weltraumschaf Jul 12 '17 at 06:44
  • @Weltraumschaf Indeed, it changed in Git 2.0. You can modify the "prefix" using `--prefix`, it now defaults to `origin`. – Bouke Sep 04 '17 at 08:21
  • Good answer, but not compatible with recent git versions in 2019. See other answers and comments. – Dirk Sep 01 '19 at 22:27
25

This draws on Vanuan's answer above, but it preserves the message of the original svn tag in the new git tag.

$ git for-each-ref --format="%(refname:short) %(objectname)" refs/remotes/tags \
| while read BRANCH REF
  do
        TAG_NAME=${BRANCH#*/}
        BODY="$(git log -1 --format=format:%B $REF)"

        echo "ref=$REF parent=$(git rev-parse $REF^) tagname=$TAG_NAME body=$BODY" >&2

        git tag -a -m "$BODY" $TAG_NAME $REF^  &&\
        git branch -r -d $BRANCH
  done
n.r.
  • 1,900
  • 15
  • 20
21

This is the same as nicolai.rostov's answer above, but i just change the refs path I replaced refs/remotes/tags by refs/remotes/origin/tags I'm using git version 2.1.1 into cygwin terminal.

$ git for-each-ref --format="%(refname:short) %(objectname)" refs/remotes/origin/tags \
| while read BRANCH REF
  do
        TAG_NAME=${BRANCH#*/}
        BODY="$(git log -1 --format=format:%B $REF)"

        echo "ref=$REF parent=$(git rev-parse $REF^) tagname=$TAG_NAME body=$BODY" >&2

        git tag -a -m "$BODY" $TAG_NAME $REF^  &&\
        git branch -r -d $BRANCH
  done
Mohamed EL HABIB
  • 622
  • 7
  • 10
  • Confirmed to work with Git v2.17.1 in GNOME terminal. – zett42 Feb 06 '19 at 16:23
  • Confirm this is needed to work with git 2.21.0 as well. – Dirk Sep 01 '19 at 22:23
  • 4
    I found that this creates the tag names as "tag/" - I wanted the tag name just to be the name (mainly because of OCD, to match what gitkraken shows!), so I changed it to `TAG_NAME=${BRANCH#*/*/}` – ChrisW Sep 23 '19 at 11:37
9

You say that you haven't gotten your branches in your checkout.

This is likely a problem with the layout of your svn repo.

The 'standard layout' is:

branches/

tags/

trunk/

If you have your layout like this:

branches/user1/

branches/user2/

Then, you will lose your branches when you do git svn fetch / clone.

To fix this, you should give the argument

--branches=branches/*/* to git clone.

rmk
  • 4,395
  • 3
  • 27
  • 32
  • But what if the branches are located in the root like here http://svn.code.sf.net/p/azconvert/code/ ? The folders `phpport` and `otherfiles` seem to be branches. – thorn0 Oct 25 '16 at 12:25
  • Having the exact same question. Did you find a solution for this scenario? – bigge Sep 01 '21 at 11:55
7

I wrote a script to help migrate as you want. The script is not perfect, but I hope this could help you:

For more information, you can visit: https://github.com/MPDFT/svn-to-git

#!/bin/bash

####### Project name 
PROJECT_NAME="myproject"
EMAIL="mycompany.com"

###########################
####### SVN 
# SVN repository to be migrated
BASE_SVN="http://svn.mycompany.com/svn/repo/sistemas/myproject"

# Organization inside BASE_SVN
BRANCHES="branches"
TAGS="tags"
TRUNK="trunk"

###########################
####### GIT 
# Git repository to migrate
GIT_URL="https://git.mycompany.com/git/repo/sistemas/myproject.git"

###########################
#### Don't need to change from here
###########################

# Geral Configuration
ABSOLUTE_PATH=$(pwd)
TMP=$ABSOLUTE_PATH/"migration-"$PROJECT_NAME

# Branchs Configuration
SVN_BRANCHES=$BASE_SVN/$BRANCHES
SVN_TAGS=$BASE_SVN/$TAGS
SVN_TRUNK=$BASE_SVN/$TRUNK

AUTHORS=$PROJECT_NAME"-authors.txt"

echo '[LOG] Starting migration of '$SVN_TRUNK
echo '[LOG] Using: '$(git --version)
echo '[LOG] Using: '$(svn --version | grep svn,)

mkdir $TMP
cd $TMP

echo
echo '[LOG] Getting authors'
svn log -q $BASE_SVN | awk -F '|' '/^r/ {sub("^ ", "", $2); sub(" $", "", $2); print $2" = "$2" <"$2"@"$EMAIL">"}' | sort -u >> $AUTHORS

echo
echo '[RUN] git svn clone --authors-file='$AUTHORS' --trunk='$TRUNK' --branches='$BRANCHES' --tags='$TAGS $BASE_SVN $TMP
git svn clone --authors-file=$AUTHORS --trunk=$TRUNK --branches=$BRANCHES --tags=$TAGS $BASE_SVN $TMP

echo
echo '[LOG] Getting first revision'
FIRST_REVISION=$( svn log -r 1:HEAD --limit 1 $BASE_SVN | awk -F '|' '/^r/ {sub("^ ", "", $1); sub(" $", "", $1); print $1}' )

echo
echo '[RUN] git svn fetch -'$FIRST_REVISION':HEAD'
git svn fetch -$FIRST_REVISION:HEAD

echo
echo '[RUN] git remote add origin '$GIT_URL
git remote add origin $GIT_URL

echo
echo '[RUN] svn ls '$SVN_BRANCHES
for BRANCH in $(svn ls $SVN_BRANCHES); do
    echo git branch ${BRANCH%/} remotes/svn/${BRANCH%/}
    git branch ${BRANCH%/} remotes/svn/${BRANCH%/}
done

git for-each-ref --format="%(refname:short) %(objectname)" refs/remotes/origin/tags | grep -v "@" | cut -d / -f 3- |
while read ref
do
  echo git tag -a $ref -m 'import tag from svn'
  git tag -a $ref -m 'import tag from svn'
done

git for-each-ref --format="%(refname:short)" refs/remotes/origin/tags | cut -d / -f 1- |
while read ref
do
  git branch -rd $ref
done

echo
echo '[RUN] git push'
git push origin --all --force
git push origin --tags

echo 'Sucessufull.'
carolnogueira
  • 499
  • 5
  • 10
6

If you want to see your branches when doing a git branch after a import from svn, you should use the ruby script svn2git (and git2svn)

It is better than git svn clone because if you have this code in svn:

  trunk
    ...
  branches
    1.x
    2.x
  tags
    1.0.0
    1.0.1
    1.0.2
    1.1.0
    2.0.0

git-svn will go through the commit history to build a new git repo.
It will import all branches and tags as remote SVN branches, whereas what you really want is git-native local branches and git tag objects. So after importing this project, you would get:

  $ git branch
  * master
  $ git branch -a
  * master
    1.x
    2.x
    tags/1.0.0
    tags/1.0.1
    tags/1.0.2
    tags/1.1.0
    tags/2.0.0
    trunk
  $ git tag -l
  [ empty ]

After svn2git is done with your project, you'll get this instead:

  $ git branch
  * master
    1.x
    2.x
  $ git tag -l
    1.0.0
    1.0.1
    1.0.2
    1.1.0
    2.0.0
Community
  • 1
  • 1
VonC
  • 1,262,500
  • 529
  • 4,410
  • 5,250
  • But doing like this don't I kill every possibility of commiting to the svn repo? Also, my question was not that. I haven't got my branches. So it's not a matter of code organization. – Luís Guilherme Feb 11 '10 at 12:48
  • I used svn2git and it treated all my branches and tags as tags ... So in other words, when am doing git branch it just shows me * master. And when am doing git tag -l it shows me all the branches and tags from SVN ... Doesn't make sense. Did I miss something ? – Sharique Abdullah Oct 30 '13 at 18:44
  • @ShariqueAbdullah not sure. I use SubGit this days: http://stackoverflow.com/a/18693798/6309 – VonC Oct 30 '13 at 20:43
2

For those who have to work on Windws at work, here is a solution up-to-date with git version 2.17.0 (and theoretically also works for versions before)

git svn init -t tags -b branches -T trunk https://mysvn.com/svnrepo

git svn fetch

for /f "tokens=1-2 delims= " %a in ('git for-each-ref --format="%(refname:lstrip=-1) %(objectname)" refs/remotes/origin/tags') do git tag -a %a %b -m "import tag from svn"
Qianlong
  • 269
  • 2
  • 12
0

I had the same problem - tags and branches were missing when I specified the revision:

$ git svn clone -r 34000 -s https://...

The fix was to specifiy a range of revisions, -r 34000:HEAD:

$ git svn clone -r 34000:HEAD -s https://...

The git mailing list gave me the hint.

cweiske
  • 30,033
  • 14
  • 133
  • 194
0

To pull a new SVN branch into your local Git-SVN repository, I've found an easy method, which does not require editing any config files or so.

You only need the revision number of the last commit in the branch that you want to add to your local git-svn repository.

If you have an up-to-date SVN checkout of the new branch and TortoiseSVN installed, you can right-click on this folder and use "TortoiseSVN / Show Log".

Make a note of the topmost commit (e.g. 45065).

Now use in Git Bash the following command:

git svn fetch -r45065

NOTE: You need to replace the number 45065 against your commit number.
git will now pull the new remote branch into your local repository.

You can then check it out with creating a local Branch. I'm using Git Extension for checking out the new branch, using "Remote-Branch" and the option "Create local branch with name: ...".
Hint: You can remove the "origin/" prefix from your local branch name, as this avoids the warning "refname 'origin/V8.0' is ambiguous.".

Dirksche
  • 21
  • 3