Migrating SVN to Git with Branches

In my previous post I did a simple migration of a Subversion project to Git.  I didn't want to keep any branches or history so purposely got rid of the history - it was more of a copy than a proper migration.  This time I will show you how to pull across some branches, and check that your commit history survives.

Instead of starting with an export of the SVN project, we can use a Git tool designed for Subversion.  Run this command to tell Git where your SVN repo is, and a little about its structure:

git svn init http://svn.example.com/subversion/Project ↩
    --prefix svn/ -T trunk --b=branch --t=tag

Initialized empty Git repository in C:/tmp/Project/.git/

My repository had slightly odd names for the branches and tags folders so I had to use the --b and --t flags tell Git what where to look.  Next you can associate any Subversion accounts with the new Git logins.  To do this, create an authors.txt file, formatted like this:

svn_username = Git User <git@fantail.io>
svn_user2 = Git User 2 <git2@fantail.io>
...

with your SVN usernames and the Git user's name and email address in place as appropriate.  You can get the full list of Subversion users from the server's authentication files.  If you don't have access to the server you can dump all the committers from the history, using:

svn log -q |  %{ $_.Split('|')[1]; } | sort-object -unique

or
svn log -q | grep r.* | awk -F\| -- '{ print $2 }' | uniq

These commands must be run back in your Subversion directory.  Once you've put all of these usernames into your authors file and set the Git details, you can apply those logins to Git:

git config svn.authorsfile authors.txt

Now to bring across the code.

git svn fetch

r5 = 9ebfdd9fd56b512e1a7f7560099048ca2d2bae1a (refs/remotes/svn/trunk)
        A       src/BranchExample.java
r7 = 026d475e8659205bd85425bbf824b5e0c2cd058e (refs/remotes/svn/trunk)
r8 = 65b946dcc045613270f412a5de60988fc3d28aa4 (refs/remotes/svn/trunk)
Found possible branch point: http://svn.example.com/subversion/Project/trunk => 
 http://svn.example.com/subversion/Project/branch/Feature1, 8
Found branch parent: (refs/remotes/svn/Feature1) 65b946dcc045613270f412a5de60988fc3d28aa4
Following parent with do_switch
Successfully followed parent
r9 = 1905234195364335c44aa23349f0de9a3876206e (refs/remotes/svn/Feature1)
Found possible branch point: http://svn.example.com/subversion/Project/trunk => 
 http://svn.example.com/subversion/Project/branch/Feature2, 9
Found branch parent: (refs/remotes/svn/Feature2) 65b946dcc045613270f412a5de60988fc3d28aa4
Following parent with do_switch
Successfully followed parent
r10 = 3c4d13c82e946524ff3cf4949d92db6077db2d1a (refs/remotes/svn/Feature2)
        M       src/BranchExample.java
        A       src/Feature2.java
r11 = 6c9ca531c74a4b9537b5691dae4dd4cc56659f50 (refs/remotes/svn/Feature2)
        M       src/BranchExample.java
r12 = 7c9a8ef5f5f79b15d510734d0c428f069aec12d8 (refs/remotes/svn/trunk)
Found possible branch point: http://svn.example.com/subversion/Project/trunk => 
 http://svn.example.com/subversion/Project/branch/Feature3, 12
Found branch parent: (refs/remotes/svn/Feature3) 7c9a8ef5f5f79b15d510734d0c428f069aec12d8
Following parent with do_switch
Successfully followed parent
r13 = 6ed0cd2ce5a9220eb681ce29ebd00d1e6d7b0e7e (refs/remotes/svn/Feature3)
        M       src/BranchExample.java
        A       src/Feature3.java
r14 = 56344f2e3d8941c1748912f2f5f28bb109975fbd (refs/remotes/svn/Feature3)
        M       src/BranchExample.java
r15 = 1fab22054768c056c7d3317aed495bf3e77b0116 (refs/remotes/svn/Feature1)
        M       src/BranchExample.java
        A       src/Feature1.java
r16 = 855456ab9817e173bd014d47a26a94e6d5cd5a85 (refs/remotes/svn/Feature1)
Found possible branch point: http://svn.example.com/subversion/Project/trunk => 
 http://svn.example.com/subversion/Project/branch/UnwantedFeature, 16
Found branch parent: (refs/remotes/svn/UnwantedFeature) 7c9a8ef5f5f79b15d510734d0c428f069aec12d8
Following parent with do_switch
Successfully followed parent
r17 = d554f2b7a2a8ee79a8d052ee9286501919966e17 (refs/remotes/svn/UnwantedFeature)
        M       src/BranchExample.java
r18 = f28e6dd9b3f0929b55d2d92a3785de35db1036d3 (refs/remotes/svn/UnwantedFeature)
Checked out HEAD:
  http://svn.example.com/subversion/Project/trunk r12

The git svn fetch command pulls down the code, including any branches and commit history associated with the files.  You can check this out by running git log on one of the files:

git log src\BranchExample.java

commit 7c9a8ef5f5f79b15d510734d0c428f069aec12d8
Author: Git User <git@fantail.io>
Date:   Wed May 10 01:52:57 2017 +0000

    Added a hotfix

    git-svn-id: http://svn.example.com/subversion/Project/trunk@12 
 ac2ac16-9d07-11e6-a42d-b97f5eeee0cb

commit 026d475e8659205bd85425bbf824b5e0c2cd058e
Author: Git User <git@fantail.io>
Date:   Wed May 10 01:38:27 2017 +0000

    git-svn-id: http://svn.example.com/subversion/Project/trunk@7 
 cac2ac16-9d07-11e6-a42d-b97f5eeee0cb

and by looking at all of the branches available to you:

git branch -a

* master
  remotes/svn/Feature1
  remotes/svn/Feature2
  remotes/svn/Feature3
  remotes/svn/UnwantedFeature
  remotes/svn/trunk

As you can see, I have a couple of branches containing important features but one branch that I don't want to bring across.  I'm going to turn these Feature branches into Development branches in my Git repository, so I can work on them and eventually check them in.

git branch dev/Feature1 remotes/svn/Feature1

git branch dev/Feature2 remotes/svn/Feature2

git branch dev/Feature3 remotes/svn/Feature3

git branch

  dev/Feature1
  dev/Feature2
  dev/Feature3
* master

The reason I'm not calling them "feature" branches is that I want to use Git Flow in future (follow the link for the best explanation I have ever seen) and this assigns a special meaning to the feature branch.  I don't want conflicts later on, so I've decided that these SVN branches will become "dev" branches.

Once you've linked all the branches you need you can share your repository with the rest of your team.  I covered creating a remote repo in Visual Studio Team Services in the previous post, and of course you can use a different hosted Git repo such as BitBucket or GitHub if you want to.  Once you have created your remote, link it using:

git remote add origin ↩
    https://x.visualstudio.com/GitDemo/_git/Project
git fetch

warning: no common commits
remote:
remote:                    vSTs
remote:                  vSTSVSTSv
remote:                vSTSVSTSVST
remote: VSTS         vSTSVSTSVSTSV
remote: VSTSVS     vSTSVSTSV STSVS
remote: VSTSVSTSvsTSVSTSVS   TSVST
remote: VS  tSVSTSVSTSv      STSVS
remote: VS   tSVSTSVST       SVSTS
remote: VS tSVSTSVSTSVSts    VSTSV
remote: VSTSVST    SVSTSVSTs VSTSV
remote: VSTSv        STSVSTSVSTSVS
remote:                VSTSVSTSVST
remote:                  VSTSVSTs
remote:                    VSTs    (TM)
remote:
remote:  Microsoft (R) Visual Studio (R) Team Services
remote:
Unpacking objects: 100% (3/3), done.
From https://x.visualstudio.com/GitDemo/_git/Project
 * [new branch]      master     -> origin/master
git push --all

Counting objects: 33, done.
Delta compression using up to 4 threads.
Compressing objects: 100% (23/23), done.
Writing objects: 100% (33/33), 3.25 KiB | 0 bytes/s, done.
Total 33 (delta 11), reused 0 (delta 0)
remote: Analyzing objects... (33/33) (3 ms)
remote: Storing packfile... done (112 ms)
remote: Storing index... done (26 ms)
To https://x.visualstudio.com/GitDemo/_git/Project
 * [new branch]      dev/Feature1 -> dev/Feature1
 * [new branch]      dev/Feature2 -> dev/Feature2
 * [new branch]      dev/Feature3 -> dev/Feature3
 ! [rejected]        master -> master (non-fast-forward)
error: failed to push some refs to 'https://x.visualstudio.com/GitDemo/_git/Project'

hint: Updates were rejected because the tip of your current branch is behind
hint: its remote counterpart. Integrate the remote changes (e.g.
hint: 'git pull ...') before pushing again.
hint: See the 'Note about fast-forwards' in 'git push --help' for details.
git pull origin master

From https://x.visualstudio.com/GitDemo/_git/Project
 * branch            master     -> FETCH_HEAD
fatal: refusing to merge unrelated histories

You'll see I got an error - my master branch and the origin's are different because I told VSTS to add a .gitignore file for me and it committed that as a first change.  This means that my remote and local repositories have different commit trees.  Fortunately we can merge them together using:

git pull origin master --allow-unrelated-histories

From https://x.visualstudio.com/GitDemo/_git/Project
 * branch            master     -> FETCH_HEAD
Merge made by the 'recursive' strategy.
 .gitignore | 12 ++++++++++++
 1 file changed, 12 insertions(+)
 create mode 100644 .gitignore
git push --set-upstream origin master
Counting objects: 2, done.
Delta compression using up to 4 threads.
Compressing objects: 100% (2/2), done.
Writing objects: 100% (2/2), 351 bytes | 0 bytes/s, done.
Total 2 (delta 0), reused 0 (delta 0)
remote: Analyzing objects... (2/2) (1 ms)
remote: Storing packfile... done (69 ms)
remote: Storing index... done (28 ms)
To https://x.visualstudio.com/GitDemo/_git/Project
   481b41c..3925ba0  master -> master
Branch master set up to track remote branch master from origin.

All your branches have been saved on the remote repo now, and you can check them out via the VSTS user interface.





Comments

Popular posts from this blog

Feature Toggles on a .Net Core API

Setup a Kubernetes Cluster on Azure

Automatically Logging PowerShell in to Azure