Pushing a new feature from a mercurial repo into an SVN repo

Update: Added optional code to show how you would put feature in a branch first rather than just committing directly to the trunk.

#!/bin/bash
# 
# Merging a separate feature repository into a folder in an SVN repository by 
# way of two hg repositories.
# 
# Inspired by: http://hgtip.com/tips/advanced/2009-11-17-combining-repositories/
# 
# Requires:
#   subversion installed
#   convert extension
#   mq extension (for strip)
#   rebase extension
#   hgsubversion extension
#
# We have the following four repositories:
#     SVN repo  hg-svn  hg-svn-work
#     feature
# 
# SVN repo:       company subversion repo on remote server, https://ip/svn
# hg-svn:         local developer copy of SVN repo using hgsubversion 
# hg-svn-work:    a local working copy of hg-svn. (Why? In my case,
#                 it's inside a Linux virtual machine running on a Mac.
#                 hg-svn is on the Mac.)
# feature:        a completely separate hg repository containing some new 
#                 program developed in isolation. (Perhaps a contract job?)
# 
# Our goal is to put feature and all of its history into SVN, but we must
# place it into a sub-folder /new/feature.
# 
# Steps:
# 1. Use hg convert to re-map feature/* to feature/new/feature/*.
# 2. Hg pull the converted feature repo into hg-svn-work.
# 3. Hg rebase the pulled feature onto the previous tip.
# 4. Hg push the changes to hg-svn
# 5. Hg push from hg-svn into the SVN repo.
# 6. Hg pull changes from hg-svn into hg-svn-work
# 7. Hg strip the pulled feature changesets from step 2.
# 
# Using a feature branch:
#   It would be nice to put feature into its own subversion branch
#   first and then merge it back into the trunk. If you'd like to do
#   this, enable USE_A_BRANCH below.
USE_A_BRANCH=1  # enable = 1

# Example:

# Turn on debugging so we can see the commands as they're issued.
set -x
# Clean up after any previous run.
rm -rf feature_test

mkdir feature_test
cd feature_test

# Create a "company" SVN repository.
svnadmin create SVN
svn co file://`pwd`/SVN SVN_temp

# Put something in the SVN repository or we won't be able
# to see why some of the steps are necessary.
cd SVN_temp/
svn mkdir trunk tags branches
svn ci -m "Initial folders"
echo "Company file" > trunk/company.txt
svn add trunk/company.txt
svn ci -m "Added company.txt"
cd ..

if [ "$USE_A_BRANCH" = "1" ]; then
    # Create a feature branch in subversion
    svn copy file://`pwd`/SVN/trunk file://`pwd`/SVN/branches/feature -m "Added feature branch"
fi

# Create our first and second level clones of SVN.
if [ "$USE_A_BRANCH" = "1" ]; then
    # Make sure to clone the new branch!
    hg clone file://`pwd`/SVN/branches/feature hg-svn
else
    hg clone file://`pwd`/SVN/trunk hg-svn
fi
hg clone hg-svn hg-svn-work

# Create a new feature in its own repository. It needs more than one
# changeset to show how hgsubversion will rebase after pushing each 
# changeset.
hg init feature
cd feature
echo "Some text" >> readme.txt
hg add readme.txt 
hg com -m "Added readme.txt"
echo "Some more text" >> readme.txt
hg com -m "Added more text"
cd ..

# Create a clone/copy of feature where we move everything into the
# subfolder that it needs to be in for the company SVN layout.
echo "rename . new/feature" > map.txt
hg convert --filemap map.txt feature feature-conv
rm map.txt

# Pull the converted feature repository into our working
# copy of the SVN repository. Save the current tip since we will
# need it to rebase.
cd hg-svn-work/
OLD_TIP=`hg log --template "{rev}\n" -r tip`
hg pull -f ../feature-conv
# All of the new changesets start from OLD_TIP + 1. Rebase them on
# OLD_TIP.
hg rebase --source $(($OLD_TIP + 1)) --dest $OLD_TIP
# Push the changes up one level to hg-svn
hg push
cd ..

# Clean up the feature copy.
rm -rf feature-conv

cd hg-svn/
# Update hg-svn since hgsubversion will not detect any outgoing 
# changesets otherwise.
hg up
# Push the changes to SVN. Each changeset pushed will cause a rebase.
# If you are pushing a lot of changesets (e.g. I did 85), this will take a
# while since the algorithm is O(n^2). The first push of N changesets rebases
# N-1 changesets, then the next rebases N-2, etc. It will appear pleasantly
# faster as it goes.
hg push
cd ..

# Pull the newly rebased changesets back to our working repository. 
# Unfortunately, we've now done half of an svn push/rebase here. We need
# to manually strip away all of the revisions we pulled from feature-conv
# earlier as they've all been duplicated now.
cd hg-svn-work/
hg pull
hg strip -f -n $(($OLD_TIP + 1))
cd ..

cd SVN_temp
svn up

if [ "$USE_A_BRANCH" = "1" ]; then
    # Optionally, merge the svn feature branch back to the trunk
    # Note: I don't know (yet) of a simpler way to do this.
    DIR=$(dirname $(pwd))
    svn merge file://$DIR/SVN/branches/feature trunk
    svn ci -m "Merging feature branch back to trunk"
    svn delete branches/feature
    svn ci -m "Closing feature branch"
fi

set +x
echo "=========================="
echo "Results:"
find . | egrep -v '\.svn'
cd ..

# Use the following command to see the log of changes:
# svn log -v SVN_temp

Comments

Leave a Reply

Your email address will not be published. Required fields are marked *