How to Setup an Alternate CM Workflow when Constrained to Use Subversion
Background: I recently started work at a company that uses subversion for configuration management. It has been well over 5 years since I last used the tool, and I have no fond memories of it. I wanted to find a workaround that would allow me to work with git locally and then commit changes to the remote server when I was satisfied that my changes and supporting tests were ready for review. I’m not going to get in to centralized vs. decentralized CM strategies here; the purpose of this post is just to present the workflow I came across and how I verified that it works.
All materials for reproducing the steps I discuss below can be downloaded from here.
Software Prerequisites:
- Ubuntu 16.04/18.04 - you can probably run with Windows 10 + Docker Desktop but I have not verified
- a zip client - to extract the archive
- docker - I run with –network=host option enabled!
- git
- git-svn
- subversion
In the shell snippets I show below, host
means you should run your command on your host machine and container
means you should run the command inside of the running docker container that hosts the running subversion server.
Setting up the Mock Environment
Build and Run the Mock Server
Before getting into the workflow, I wanted to create a subversion server that would allow me to setup some dummy subverion repositories to play around with. If you, the reader, have read some of my previous blog posts, you’d now that I am a big Docker advocate, so I found a repo that could serve as a starting point for setting up a subversion server with a Docker container. My apologies to the original author of the repo; I could not find the original repo that I cloned the Dockerfile and supporting files from before making my minor modifications. If you know who the author is, let me know and I will attribute the original work to you. The basic steps for building an image from the Dockerfile, and launching a container based on that image, provided in the link above are:
(host)$ cd {path-to-extracted-zip-contents}
(host)$ ./cd svn-test/svn-server
(host)$ ./build-docker.sh {username} {password} # provide desired username and password as command line args
(host)$ ./run-docker.sh
Setting Up the Mock Repositories
Server-Side
Remember, I am approaching this post from the perspective of my particular problem: working with existing subversion repos. So, to test candidate workflows, I need representative repositories. We can create one or more such repositories by executing the three steps below:
(host)$ docker exec -it svn-server-c /bin/bash # STEP 1: attach terminal to svn-server-c
(container)$ svnadmin create /home/svn/{project-name} # STEP 2: create new repo using svnadmin
(container)$ chown -R www-data:subversion /home/svn/{project-name} # STEP 3: change permissions so user={svn-username} can push/pull from svn server
(container)$ chmod -R g+rws /home/svn/{project-name} # STEP 3: (continued)
So far, only empty repositories have been created; they don’t even have the standard branches/tags/trunk
layout common in subversion repos.
Client-Side
To create a repo with the standard branches/tags/trunk
layout:
(host)$ svn co --user {username} http://localhost/svn/{project-name} # STEP 1: checkout repo (you will be prompted for username's password)
(host)$ cd {project-name} && mkdir branches tags trunk # STEP 2: create branches tags trunks
(host)$ svn add branches tags trunk && svn ci -m "adding standard layout" # STEP 3: add and commit local changes to the server
Should you desire, you can also checkout trunk, add files, and create branches using the normal subversion commands, like checkout
, checkin
, copy
, etc… I chose not to, but feel free to do so. I just wanted the standard layout for integration with git-svn
.
Simplifying CM with git-svn
We are now ready to move on to the real objective of this post: working locally with subversion repositories without using subversion! What I am trying to achieve is the following:
- I want bi-directional communication; e.g. push and pull capability between my local working copy and the remote subversion server
- I want to be able to work locally with git to get all of its niceties:
squash
,rebase
,cherry-pick
, etc… - The workflow should make it simple to keep my working copies up-to-date with the remote
Since this is intended to be a how-to post at its core, I’m going to show what solutions I found for achieving the following with git-svn
and git
with the above criteria in mind. Specifically, I will show how to do the following:
- Clone an existing subversion repo
- Create a branch globally with subversion and track it locally with git
- Merge branches
- Ignore files
Note: all commands from now on are to be run on your host machine, not inside the container!
Clone Existing Subversion Repo
$ cd {desired-project-parent-dir} # e.g. one level above where you want to clone the code to locally
$ git svn clone http://localhost/svn/{repo-name} {repo-name} -s # the command is "git svn" NOT "git-svn"!
s
is for standard layout; e.g. branches/tags/trunk
This sets up a git-like local repo to work with. Quite handy!
Create and Track a Branch
$ cd {local-repo-root-dir} # make sure that you clone the repo into {local-repo-root-dir} first!
$ git svn branch -m "{your-branch-create-message-here}" {name-of-branch} # create the branch on the remote server; complete with commit message
$ #git branch -a # list remote branches that git knows about. THIS COMMAND IS VERY USEFUL
$ git svn fetch {name-of-branch} # fetch newly-made branch. Exclusion of {name-of-branch} fetches everything: all branches/tags/trunk, so use cautiously. This step sets up {name-of-branch} as the remote branch for git to track
$ git checkout -b {name-of-branch}-local remotes/origin/{name-of-branch} # This step creates a local working branch. Note: adding `-local` removes git warning about ambiguity.
$ ... (do bunch of stuff locally, use git workflow to manage CM (squash/rebase/merge/etc...).
$ git svn dcommit # commits to branch, updates the revision count (only once)
Merge branches
This includes merging from a branch into trunk. See this post’s accepted answer.
$ git checkout {to-branch} # if you have not created a local copy, use the "-b" flag with checkout here
$ git reset --hard {remote-to-to-branch} # e.g. remotes/origin/trunk. This sets up your local copy to match the remote AND to track the remote branch!
$ git merge --no-ff {remote-to-from-branch} # merge from "from" remote. The "--no-ff" option is important; see post answer, brings up editor with a default merge message
$ git svn rebase {remote-to-to-branch} # rebase is apparently required, but shouldn't change anything here
$ git svn dcommit # commits to branch, updates the revision count (only once)
Ignore Files
For now, just add a .gitignore
file before doing local git workflow stuff. Do git svn dcommit
after satisfied with local git changes. Things you want to ignore won’t be committed to git and not committed to subversion either.
There’s probably a more elegant solution, but this meets my need.
Wrap-Up
Hopefully, I’ve presented you with some new information that, should you choose, will empower you to confidently use git with legacy subversion repos! If you think of anything that could be modified or added to improve this post, I am receptive; let me know what you think in the comments. In conclusion, here are some links I distilled into aspects of this post:
- git-svn from git docs
- sample tutorial
- getting started with git-svn
- git-svn cheatsheet
- sample workflow
Hopefully, some of you out there will find this useful. Thanks for reading!