#!/bin/sh # # AUtomated conversion of FreeBSD doc/ to Mercurial. # # Copyright (C) 2013, Giorgos Keramidas # All rights reserved. # # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions # are met: # 1. Redistributions of source code must retain the above copyright # notice, this list of conditions and the following disclaimer. # 2. Redistributions in binary form must reproduce the above copyright # notice, this list of conditions and the following disclaimer in the # documentation and/or other materials provided with the distribution. # # THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND # ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE # ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE # FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL # DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS # OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) # HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT # LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY # OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF # SUCH DAMAGE. # ----- startup -------------------------------------------------------- # Save some script startup environment information. We'll need it later # when we have to look for configuration files and other data associated # with the script. progname=$( basename $0 ) dirname=$( dirname $0 ) dirname=$( ( cd ${dirname} ; pwd -P ) ) # Set up a very basic locale environment, so our output doesn't look # very 'alien' depending on where/how we were spawned. export LANGUAGE='C' export LANG='C' export LC_ALL='C' unset LC_CTYPE LC_COLLATE LC_MESSAGES LC_MONETARY LC_NUMERIC LC_TIME # ----- misc script functions ------------------------------------------ # # checkyesno var # Test $1 variable, and warn if not set to YES or NO. # Return 0 if it's "yes" (et al), nonzero otherwise. # checkyesno() { eval _value=\$${1} case $_value in # "yes", "true", "on", or "1" [Yy][Ee][Ss]|[Tt][Rr][Uu][Ee]|[Oo][Nn]|1) return 0 ;; # "no", "false", "off", or "0" [Nn][Oo]|[Ff][Aa][Ll][Ss][Ee]|[Oo][Ff][Ff]|0) return 1 ;; *) warn "\$${1} is not set properly." return 1 ;; esac } # # err exitval message # Display $message to stderr, and exit with $exitval. # err() { exitval=$1 shift echo 1>&2 "$0: ERROR: $*" exit $exitval } # # warn message # Display message to stderr. # warn() { echo 1>&2 "$0: WARNING: $*" } # # info message # Display informational message to stderr. # info() { echo 1>&2 "$0: INFO: $*" } # # debug message # Display $message to stderr if $debug_enable is set. # debug_enable=${debug_enable:-0} debug() { if checkyesno debug_enable ; then echo 1>&2 "$0: DEBUG: $*" fi } # # cleanup # Cleanup handler for asynchronous script termination events. # This should take care of removing any files and/or directories # we have created to do our own work. # cleanup() { if test -n "${CLEANUPFILES}" ; then rm -fr ${CLEANUPFILES} fi } trap 'cleanup' 0 trap 'cleanup' 1 2 3 6 11 14 15 # ----- script environment setup --------------------------------------- # The mapping we will use when we convert subversion committer usernames # to 'Real Name ' committer ids. AUTHORMAP=${AUTHORMAP:-"${dirname}/authormap.txt"} debug "AUTHORMAP set to '${AUTHORMAP}'" # Where the subversion mirror of the FreeBSD doc/ repository lives. SVNREPO=${SVNREPO:-'file:///home/svn/doc'} debug "SVNREPO set to '${SVNREPO}'" # Where the converted mercurial repository lives. HGREPO=${HGREPO:-'/hg/freebsd/doc-head'} debug "HGREPO set to '${HGREPO}'" # Optional remote mirror of the mercurial repository. If enabled, then # an 'hg push' command will be issued from the converted repository to # wherever PUSHREPO points. If SSHKEY is also set, it will be used as # the default ssh key file for the push operation. PUSHREPO='ssh://hg@bitbucket.org/keramida/freebsd-doc-head' SSHKEY='/home/keramida/.ssh/bb_rsa' debug "PUSHREPO set to '${PUSHREPO}'" debug "SSHKEY set to '${SSHKEY}'" # Logfile for long-running commands, like svnsync or 'hg push'. We trap # their output in this logfile when we run them, and if something breaks # we can display the output later. TMPLOG=$( mktemp "/tmp/tmplog-$$-XXXX" ) CLEANFILES="${CLEANFILES} ${TMPLOG}" # ----- main script body ----------------------------------------------- # The synchronization of the Subversion repository is pretty mundane and # will not, under normal conditions, cause any sort of serious problems. # We can run it an arbitrary number of times, as long as it doesn't fail # and leave the repository in an inconsistent state. debug 'Synchronizing Subversion repository.' svnsync sync "${SVNREPO}" > "${TMPLOG}" 2>&1 if test $? -ne 0 ; then cat "${TMPLOG}" 1>&2 err 1 'Subversion synchronization failed. Aborting.' fi # If we are supposed to use a custom authormap for the conversion from # subversion to mercurial, check that the authormap exists. If not, # then ${authormap_flags} should remain empty. authormap_flags='' if test -n "${AUTHORMAP}" ; then if test ! -f "${AUTHORMAP}" ; then err 1 "Author map missing from ${AUTHORMAP}" fi authormap_flags="--authormap ${AUTHORMAP}" fi # Converting from subversion to the _same_ place that we are later going # to use for our own work is a bit silly, because the conversion process # is a 'destructive' operation: it can add an unpredictable number and # type of changesets; it can modify .hg/shamap and .hg/authormap in the # target repository. If we later find out that the current authormap is # not exhaustive, and we have to update it first, there's no safe way of # recovering from the partial conversion. # # To avoid this sort of problem, we are creating a temporary clone of # the target HGREPO repository, running the conversion in that clone, # and if all goes well we can safely pull from the remporary clone into # the final HGREPO and update the HGREPO/.hg/{author,sha}map files. HGTEMP=$( mktemp -d "/tmp/hgrepo-$$-XXXX" ) CLEANUPFILES="${CLEANUPFILES} ${HGTEMP}" debug "Creating temporary mercurial clone at ${HGTEMP}" hg clone -U "${HGREPO}" "${HGTEMP}" > "${TMPLOG}" 2>&1 if test $? -ne 0 ; then cat "${TMPLOG}" 1>&2 err 1 "Failed to clone '${HGREPO}' to '${HGTEMP}'" fi debug "Copying author map from ${HGREPO}" if test -f "${HGREPO}/.hg/authormap" ; then cp "${HGREPO}/.hg/authormap" "${HGTEMP}/.hg/" if test $? -ne 0 ; then err 1 "Cannot copy author map from $HGREPO to $HGTEMP" fi fi debug "Copying SHA map from ${HGREPO}" if test -f "${HGREPO}/.hg/shamap" ; then cp "${HGREPO}/.hg/shamap" "${HGTEMP}/.hg/" if test $? -ne 0 ; then err 1 "Cannot copy SHA map from $HGREPO to $HGTEMP" fi fi debug "Converting from ${SVNREPO} to temp clone ${HGTEMP}" hg convert \ --branchsort \ --config convert.svn.branches='' \ --config convert.svn.tag='' \ --config convert.svn.trunk='head' \ --config convert.localtimezone='False' \ ${authormap_flags} \ \ "${SVNREPO}" \ "${HGTEMP}" \ > "${TMPLOG}" 2>&1 if test $? -ne 0 ; then cat "${TMPLOG}" 1>&2 err 1 'Conversion from subversion failed.' fi debug "Checking for unmapped user names in ${HGREPO}" unmapped=$( hg -R "${HGREPO}" log --template '{author}\n' | \ grep -vi '.* <.*@freebsd.org>' | \ wc -l | awk '{print $1}' ) debug "$unmapped users found" if test "${unmapped}" -gt 0 ; then err 1 "${unmapped} users found;" \ "author map must be updated for the new committers" fi debug "Copying new author map into $HGREPO" cp "${HGTEMP}/.hg/shamap" "${HGREPO}/.hg/" if test $? -ne 0 ; then err 1 "Cannot copy new author map into $HGREPO" fi # Before copying the new SHA map blindly over the old one, make a backup # copy. Then if we have later some problem pulling the new changesets # into the target repository debug "Copying new SHA map into $HGREPO" cp "${HGREPO}/.hg/shamap" "${HGREPO}/.hg/shamap-$$" || \ err 1 "Failed to save backup of ${HGREPO}/.hg/shamap" CLEANFILES="${CLEANFILES} ${HGREPO}/.hg/shamap-$$" cp "${HGTEMP}/.hg/shamap" "${HGREPO}/.hg/" || \ err 1 "Cannot copy new SHA map into $HGREPO" debug "Pulling new changesets into ${HGREPO}" hg -R "${HGREPO}" pull "${HGTEMP}" > "${TMPLOG}" 2>&1 if test $? -ne 0 ; then # Restore old shamap, since we failed to pull any new commits and we # dont' want the final shamap to include stuff that's not available # in the repository. cp "${HGREPO}/.hg/shamap-$$" "${HGREPO}/.hg/shamap" || \ err 1 "Pull of new history and SHA map restore failed." \ "$HGREPO requires MANUAL cleanup" cat "${TMPLOG}" 1>&2 err 1 "Cannot pull new history into $HGREPO" fi # If PUSHREPO is set to a non-empty path, when we're done with the # conversion process push everything from HGREPO to PUSHREPO. # TODO(keramida): This process doesn't necessarily do anything about # authormap or shamap in the destination repository. Find out if # there's a generic way of handling that (e.g. for bitbucket repos). if test -n "$PUSHREPO" ; then debug "Pushing new history from $HGREPO to $PUSHREPO" # If SSHKEY is set to a valid key-file, set up a custom ssh command # that uses this key for connecting to the remote PUSHREPO. # By 'valid' we mean that the file exists and a matching '.pub' # public key exists, and they are both recognized by file(1) as ssh # key files. This is a relatively naive check, but it should work # for most existing key files. debug "Checking if ${SSHKEY} looks like a valid SSH key." ssh_command='' if test -f "${SSHKEY}" && test -f "${SSHKEY}.pub" && file "${SSHKEY}" 2>&1 | grep -qi '.*: .*private.*key' && file "${SSHKEY}.pub" 2>&1 | grep -qi '.*: .*public.*key' then debug "Enabling SSH key ${SSHKEY}" ssh_command="ssh -i ${SSHKEY}" fi # Now that we have everything in place for the push, do it. debug "Sending changesets over to $PUSHREPO" hg -R "${HGREPO}" \ push --ssh "${ssh_command}" \ "${PUSHREPO}" \ > "${TMPLOG}" 2>&1 if test $? -ne 0 ; then if grep -q 'no changes found' "${TMPLOG}" ; then # No harm done; this is just a push that had no work # to do and returned 1 from hg. true else cat "${TMPLOG}" 1>&2 err 1 "Push from ${HGREPO} to ${PUSHREPO} failed." fi fi fi exit 0 # vim: set ts=8 sts=4 sw=4 et: