Last active
December 15, 2015 21:19
-
-
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.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/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