Skip to content

Instantly share code, notes, and snippets.

@torbjoernk
Last active December 15, 2015 21:19
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save torbjoernk/5325343 to your computer and use it in GitHub Desktop.
Save torbjoernk/5325343 to your computer and use it in GitHub Desktop.
Bash script for converting an existing SVN repository to Mercurial and splitting it up into a hierarchy of subrepositories. See http://blog.torbjoern-klatt.de/article/2013/04/03/struggling-with-git-submodules-and-mercurial-subrepositories/ for the story around it.
#!/bin/bash
#******************************************************************************#
# ugconvert.sh #
# Script to convert and split ug4's SVN repo to hg repos #
# by Torbjörn Klatt #
# 2013-03-30 #
# Inspired by #
# https://blogs.atlassian.com/2011/03/goodbye_subversion_hello_mercurial/ #
# #
# #
# DESCRIPTION #
# Script converting an existing SVN repository into a Mercurial (hg) #
# repository and splitting up the full repository into several #
# subrepositories. #
# A local SVN mirror of the to-be-converted repository and a hg clone of #
# this repo is required. #
# #
# Create the local SVN mirror with #
# svnadmin create $SVN_REPO #
# and make an initial sync (this might take a while) #
# echo '#!/bin/sh' > $SVN_REPO/hooks/pre-revprop-change #
# chmod +x $SVN_REPO/hooks/pre-revprop-change #
# svnsync init file://$SVN_REPO https://url.to/svn/repo #
# svnsync sync file://$SVN_REPO #
# #
# Now create the author mappings for the hgsubversion extension #
# svn log file://$SVN_REPO --quiet --xml | grep author | \ #
# sed -E "s:</?author>;::g" | sort | uniq #
# and save it in $UTIL_FILES/author_map #
# #
# Then create the initial hg clone of that local SVN mirror #
# hg clone --config extensions.hgsubversion= --config \ #
# hgsubversion.authormap=$UTIL_FILES/author_map file://$SVN_REPO \ #
# $FULL_HG #
# #
# PARAMETER #
# 1st full path to the SVN repositorie's local mirror #
# 2nd full path to the HG clone of that SVN mirror #
# 3rd full path to the base path desired to contain the separated #
# repositories #
# 4th full path to a directory containing several configuration and helper #
# files (see section UTIL_FILES) #
# #
# RETURNS #
# 0 on success (might contain warnings) #
# 1 on failure #
# #
# UTIL_FILES #
# <FILE> <DESCRIPTION> #
# author_map file mapping SVN author names to the new hg author #
# names including email addresses #
# repo_separations file with some desired subrepositories and their #
# filemap files (see FILEMAPS) #
# <filemaps> files containing the file maps for all repositories #
# filemaps/ directory containing all filemap files #
# logs/ directory for logfiles #
# #
# FILEMAPS #
# Filemaps are files containing file mappings used by `hg convert` to #
# include, exclude or rename files and directories when separating #
# individual repositories from the full one. See `hg help convert` for more #
# details. #
# #
# DEPENDENCIES / REQUIREMENTS #
# Bash (not a stone-aged version) #
# Mercurial (>= 2.5; with convert and hgsubversion extensions) #
# Subversion and svnsync (>= 1.6; with ra_local) #
# #
# CAVEAT #
# This script was developed and tested on a openSUSE 12.2 machine using #
# Bash 4.2.10(1). #
# #
# TODOS #
# - add better error handling (to reduce copy-paste) #
# - see below for further TODOs #
#******************************************************************************#
################################################################################
## START: Colourized Output
## Taken from http://tldp.org/LDP/abs/html/colorizing.html#COLORECHO
# Colours
black='\E[30m'
red='\E[31m'
green='\E[32m'
yellow='\E[33m'
blue='\E[34m'
magenta='\E[35m'
cyan='\E[36m'
white='\E[37m'
bold='\033[1m'
unstyle='\033[0m'
# Reset text attributes to normal without clearing screen.
reset_colour='\E[0m'
reset_style='\033[0m'
#------------------------------------------------------------------------------#
# file_exists() #
# Coloured output via `echo` #
# Parameters: #
# $message - message; defaults to "No message passed." #
# $color - colour; defaults to black if not specified #
# $style - let it be $bold to enable bold fold; defaults to $unstyle #
# Available Colours: #
# $black, $red, $green, $yellow, $blue, $magenta, $cyan, $white #
#------------------------------------------------------------------------------#
cecho () {
local default_msg="No message passed." # Doesn't really need to be a local variable.
message=${1:-$default_msg} # Defaults to default message.
color=${2:-$black} # Defaults to black, if not specified.
style=${3:-$unbold}
# switch to desired colour, print message, switch back to normal colour
echo -e "${color}${style}${message}${reset_style}${reset_colour}"
return 0
}
## END: Colourized Output
################################################################################
################################################################################
## START: Parameter checks
if [[ $# -ne 4 ]]; then
cecho "ERROR: Specify four parameters. Not ${$@}." $red $bold; exit 1
fi
GLOBAL_START_TIME=`date +%s`
# path from where this script is called; should be general base to SVN mirror,
#+ full hg and new hg repos and util dir
CALL_PATH=`pwd`
# full path to the SVN mirror
SVN_REPO=$1
if [[ ! -e ${SVN_REPO} || ! -d ${SVN_REPO} || `grep -q "This is a Subversion repository" ${SVN_REPO}/README.txt` -ne 0 ]]; then
cecho "ERROR: '${SVN_REPO}' is not a SVN repo" $red $bold; exit 1
#TODO create if non-existend
fi
# full path to the full hg clone
FULL_HG=$2
if [[ ! -e ${FULL_HG} || ! -d ${FULL_HG}/.hg ]]; then
cecho "ERROR: '${FULL_HG}' is not a HG repo" $red $bold; exit 1
#TODO create if non-existend
fi
# full path to the base dir for all new hg repos
DEST_HG=$3
if [[ ! -e ${DEST_HG} || ! -d ${DEST_HG} ]]; then
cecho "ERROR: '${DEST_HG}' not found." $red $bold; exit 1
#TODO create if non-existend
fi
# full path to the utility files
UTIL_FILES=$4
if [[ ! -d ${UTIL_FILES} || ! -d ${UTIL_FILES}/logs || ! -d ${UTIL_FILES}/filemaps ]]; then
cecho "ERROR: '${UTIL_FILES}' or 'logs' and 'filemaps' directory in there not found." $red $bold; exit 1
fi
## END: Paramter checks
################################################################################
# used in string subsitutions
slash="/"
dash="-"
################################################################################
## START: Functions
#------------------------------------------------------------------------------#
# file_exists() #
# Checks file existence. #
# Parameters: #
# $file_name - file name to be checked #
# $file_desc - file description to be displayed on error #
# $exit_flag - if set exit instead of return #
# Return: #
# 0 on success #
# 1 on failure #
#------------------------------------------------------------------------------#
function file_exists {
file_name=$1
file_desc=$2
exit_flag=$3
if [[ ! -e ${file_name} ]]; then
cecho "ERROR: ${file_desc} '${file_name}' not found." $red $bold
if [[ $exit_flag -eq 1 ]]; then
exit 1
else
return 1
fi
fi
return 0
}
#------------------------------------------------------------------------------#
# hg_convert() #
# Converts part or all of an existing hg repo into a new one based on a given #
# file map. #
# Will also call `hg update` in the newly created hg repo. #
# Parameters: #
# $filemap - filemap to be used #
# $src - path to the existing hg repo #
# $dest - path/name of the new hg repo #
# Return: #
# 0 on success #
# 1 on failure #
#------------------------------------------------------------------------------#
function hg_convert {
filemap=$1
src=$2
dest=$3
if [[ `file_exists ${filemap} "Filemap" 0` -ne 0 ]]; then return $?; fi
echo -e " >> Separating to '${dest}'"
echo -e " (logging to '${UTIL_FILES}/logs/${dest//$slash/$dash}.log')"
start_time=`date +%s`
hg convert --source-type hg --dest-type hg --filemap ${filemap} ${src} ${DEST_HG}/${dest} &> ${UTIL_FILES}/logs/${dest//$slash/$dash}.log
if [[ $? -ne 0 ]]; then cecho "ERROR: Something went wrong on convertion: $?" $red $bold; return 1; fi
end_time=`date +%s`
echo -e " Took $(($end_time-$start_time)) seconds"
cd ${DEST_HG}/${dest}
echo -e " >> Updating new hg repo"
hg update
if [[ $? -ne 0 ]]; then cecho "ERROR: Something went wrong on update: $?" $red $bold; return 1; fi
cd ${CALL_PATH}
return $?
}
#------------------------------------------------------------------------------#
# delete_if_there() #
# Checks, if given directory exists and deletes it. #
# Parameters: #
# $dir - directory to delete #
# Return: #
# 0 if non-existend or successfully deleted #
# 1 on failure #
#------------------------------------------------------------------------------#
function delete_if_there {
DIR=$1
if [[ -e $DIR && -d $DIR ]]; then
cecho " WARNING: Deleting existing subrepo: ${DIR}" $yellow $bold
rm -rf ${DIR}
if [[ $? -ne 0 ]]; then cecho "ERROR: Something went wrong on remove: $?" $red $bold; return 1; fi
fi
return 0
}
#------------------------------------------------------------------------------#
# separate_repo() #
# Separates a new repository from full repo based on filemap. #
# Parameters: #
# $filemap - filemap to be used #
# $dest - path/name of the new hg repo #
# $src - source repo to separate from; optional; defaults to $FULL_HG #
# $nodelete - do not delete if existing #
# Return: #
# 0 on success #
# 1 on failure #
# Calls: #
# delete_if_there() #
# hg_convert() #
#------------------------------------------------------------------------------#
function separate_repo {
filemap=$1
dest=$2
src=${3:-$FULL_HG}
nodelete=${4:-0}
cecho " >> Separating '${DEST_HG}/${dest}' from '${src}'" $magenta
if [[ $nodelete -eq 0 ]]; then
delete_if_there "${DEST_HG}/${dest}"
if [[ $? -ne 0 ]]; then cecho "ERROR: Something went wrong: $?" $red $bold; return 1; fi
fi
hg_convert ${filemap} ${src} ${dest}
if [[ $? -ne 0 ]]; then cecho "ERROR: Something went wrong on conversion: $?" $red $bold; return 1; fi
return 0
}
#------------------------------------------------------------------------------#
# split_each_in_dir() #
# Separates each directory inside given path into a new repository from full #
# repo. #
# Parameters: #
# $base_dir - base path to all directories #
# Return: #
# 0 #
# Calls: #
# separate_repo() #
#------------------------------------------------------------------------------#
function split_each_in_dir {
base_dir=$1
cd ${FULL_HG}
splittdirs=`find ${base_dir} -maxdepth 1 -mindepth 1 -type d`
cd ${CALL_PATH}
for splitdir in $splittdirs
do
filemap="${UTIL_FILES}/filemaps/${splitdir//$slash/$dash}_filemap"
echo -e "include ${splitdir}\nrename ${splitdir} ." > ${filemap}
separate_repo ${filemap} ${splitdir}
done
return 0
}
## END: Functions
################################################################################
# Author Map
author_map="${UTIL_FILES}/author_map"
file_exists ${author_map} "Author map file" 1
# Repo-separation File
repo_separation="${UTIL_FILES}/repo_separations"
file_exists ${repo_separation} "Repository separation file" 1
# get latest changes from subversion
cecho ">> Step 1: Updating SVN repo" $green $bold
svnsync sync file://${SVN_REPO}
if [[ $? -ne 0 ]]; then cecho "ERROR: Something went wrong on svnsync: $?" $red $bold; exit 1; fi
# update local hg-clone of svn
cecho ">> Step 2: Updating full hg conversion" $green $bold
cd ${FULL_HG}
hg pull
if [[ $? -ne 0 ]]; then cecho "ERROR: Something went wrong while updating the conversion: $?" $red $bold; exit 1; fi
hg update
if [[ $? -ne 0 ]]; then cecho "ERROR: Something went wrong on hg update: $?" $red $bold; exit 1; fi
cd ..
# splitting up the repos
cecho ">> Step 3: Splitting repos based on config file" $green $bold
while read filemap_file subrepo_name
do
filemap_file="${filemap_file}_filemap"
separate_repo ${UTIL_FILES}/filemaps/${filemap_file} ${subrepo_name}
done < $repo_separation
# splitting up plugins
cecho ">> Step 4: Splitting up plugins" $green $bold
for group in "core" "experimental"
do
split_each_in_dir "plugins/${group}"
done
# splitting up apps
cecho ">> Step 5: Splitting up apps" $green $bold
split_each_in_dir "apps"
# 2nd pass of plugins-meta
cecho ">> Step 6: 2nd pass separations" $green $bold
separate_repo ${UTIL_FILES}/filemaps/plugins-meta_filemap plugins/meta ${DEST_HG}/plugins/tempmeta 1
# add additional meta repositories
cecho ">> Step 7: Adding additional meta repositories" $green $bold
echo -e " Nothing to do here yet."
#for meta_repo in ""
#do
# cecho " - ${meta_repo} " $magenta
# mkdir ${DEST_HG}/${meta_repo}
# cd ${DEST_HG}/${meta_repo}
# hg init
# cd ${CALL_PATH}
#done
# add subrepo hierarchy
cecho ">> Step 8: Adding subrepository hierarchy" $green $bold
for meta_repo in "core/ugbase" "plugins/meta"
do
cecho " - ${meta_repo}" $magenta
cd ${DEST_HG}/${meta_repo}
cp ${UTIL_FILES}/dot-hgsub-${meta_repo//$slash/$dash} ${DEST_HG}/${meta_repo}/.hgsub
hg add .hgsub
hg commit -m "adding subrepositories"
cd ${CALL_PATH}
done
GLOBAL_END_TIME=`date +%s`
# we are done, finally
cecho ">> Everything was done in $(($GLOBAL_END_TIME-$GLOBAL_START_TIME)) seconds." $green $bold
exit 0
# EOF
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment