#!/bin/bash # 2010-07-19 # inspired by "setup" from mnemonikk (http://mnemonikk.org/2009/03/23/finally-putting-my-personal-configuration-files-under-version-control) # # documentation, source & howto: http://struction.de/projects/dotVC # # DEPEND dirname find grep sed sort uniq basename pwd ln mv cat # # $Id: dotVC,v 28:5be6849e75a6 2011-06-08 14:35 +0200 pille $ # patterns of version controll directories to ignore (space seperated, including trailing slash) VC_PATTERNS=".hg/ .git/" # base for version control repositories (location of this script) VC_DIR=`dirname $0` cd "${VC_DIR}" VC_REPOS=`find . -maxdepth 1 -type d |grep "^\./" |sort` SELF=`basename $0` VC_CONFIG=".${SELF}" # set InputFieldSeperator to \n function setIFSnewline { IFS=' ' } # set InputFieldSeperator to blank function setIFSblank { IFS=' ' } # backup the file in first parameter, if it exists by moving the original out of way function backupIfExists() { filename="$1" if [ -e "${filename}" ]; then echo "target exists. backing up ${filename} to ${filename}.vc_backup first." mv "${filename}" "${filename}.vc_backup" fi } # create parent directory of file, if it doesn't exist already function createParent() { parent_dir="$(dirname $1)" if [ ! -d "${parent_dir}" ]; then echo "creating parent directory first (${parent_dir})" mkdir -p "${parent_dir}" fi } # prepend appropriate amount of "../" to fix keep relative symlinks correct function correctRelativeSymlink { filename="$1" c=0 while [ ${#filename} -gt 1 ]; do c=$(($c+1)) filename="$(dirname ${filename})" done # prepend appropriate amount of "../" to fix keep relative symlinks correct rel_dir_prefix="" for i in `seq 1 $((c-1))`; do rel_dir_prefix="${rel_dir_prefix}../" done echo ${rel_dir_prefix} } # try to figure out the absolute path of your .vc dirctory by keeping the shortest match and cd into # this is hardcoded, since it cannot be determined on the fly during bootstrap. # symlink it inside $VC_DIR function bootstrap { # get absolute path to this script ABS_SELF="$(pwd)/${SELF}" # change to .vc dir cd `pwd |sed 's_/.vc/.*$_/.vc/_'` # strip absolute .vc path from absolute path of this script REL_SELF=`echo "${ABS_SELF}" |sed "s_$(pwd)/__"` echo "symlinking ${REL_SELF} to $(pwd)/${SELF}" backupIfExists "${SELF}" ln -s "${REL_SELF}" "${SELF}" # try to find a config file in one of the repositories. echo "looking for an existing .${SELF} file inside repositories." CONF=`find $(pwd) -type f -name ".${SELF}" |sed "s_$(pwd)/__"` case `echo "${CONF}" |wc -l` in 1) if [ "${CONF}" == "" ]; then echo "WARNING: no config file could be found." echo echo "generate a template config using '${SELF} -t >.${SELF}' and edit it." echo "place it at the same directory as your symlinked ${SELF} ($(pwd)) or" echo "take advantage of checking it into ONE repository and symlinking from there." echo "afterwards this step is done automatically during bootstrap on new systems." echo "it's best practise to check it in as '.vc/.${SELF}', so it becomes self-contained." else echo "found ${CONF} and symlinking it to $(pwd)/.${SELF}" ln -s "${CONF}" fi ;; *) echo "WARNING: more than one config file was found." echo echo "you have to decide which one to symlink to $(pwd)/" echo "candidates are:" echo "${CONF}" esac } # generate config from all files found in repositories. function generateConfigFromRepos { echo "# in this file you can define the mappings which file to take from which repository." echo "# lines starting with a hashmark (#) are ignored." echo "#" echo "# for each file there is a mapping in the following form:" echo "# relative/path/to/file" echo "# repository" echo "#" echo "# which means that the file \"relative/path/to/file\" is symlinked from the repository \"repository\"" echo "# to the parent directory." echo "# if more than one repository is specified, the corresponding file is generated by concatenating" echo "# a combined version from all repositories in the order given." echo "#" echo "# a newline seperates single file definitions." echo "#" echo "# this file was generated to include all files from all repositories found." echo "# all repositories are commented out by default, so you just need to uncomment from which one to pick a file." echo "#" echo "# place this file (${VC_CONFIG}) in your $(basename $(pwd)) directory (the same as this script)" echo VC_FILES=`find . -mindepth 2 -type f |grep '^\./' |sed 's_^\./[^/]*/__' |sort |uniq` setIFSnewline for vc_file in ${VC_FILES}; do # skip files starting with VC_PATTERNS setIFSblank for vc_pattern in ${VC_PATTERNS}; do vc_pattern_length=${#vc_pattern} if [ "${vc_file:0:${vc_pattern_length}}" = "${vc_pattern}" ]; then continue 2; fi done echo "${vc_file}" # print repositories $vc_file is in setIFSnewline for vc_repo in ${VC_REPOS}; do if [ -f "${vc_repo}/${vc_file}" ]; then echo "#${vc_repo:2}" fi done echo done echo "# TO AVOID PARSING ERRORS, MAKE SURE THE LAST LINE IS A COMMENT! THANKS FOR BUYING QUALITY SOFTWARE!" } # parse config and do symlinking/concanating based on the rules found function generateFiles { if [ ! -f "${VC_CONFIG}" ]; then echo "ERROR: ${SELF} config file not found (${VC_CONFIG})" >&2 echo "You can generate a template running '${SELF} generateTemplate'." >&2 exit 2 fi STATE=LookingForFile cat "${VC_CONFIG}" | \ while read cfgline; do # ignore comment lines if [ "${cfgline:0:1}" = "#" ]; then continue; fi if [ "${#cfgline}" != "0" ]; then if [ "${STATE}" = "LookingForFile" ]; then FILE="${cfgline}" STATE=LookingForRepository continue fi if [ "${STATE}" = "LookingForRepository" ]; then REPOSITORY[${#REPOSITORY[@]}]=${cfgline} continue fi elif [ "${STATE}" = "LookingForRepository" ]; then case "${#REPOSITORY[@]}" in 0) echo "WARNING: file ${FILE} is listed in config file (${VC_CONFIG}), but without any repository." >&2 ;; 1) echo "symlinking ${REPOSITORY}/${FILE} to ../${FILE}" if [ -e "${REPOSITORY}/${FILE}" ]; then backupIfExists "../${FILE}" createParent "../${FILE}" ln -s "$(correctRelativeSymlink ${FILE})$(basename $(pwd))/${REPOSITORY}/${FILE}" "../${FILE}" else echo "WARNING: ${FILE} is referenced by config from repository ${REPOSITORY} but does not exist (${REPOSITORY}/${FILE}). skipping." >&2 #continue fi ;; *) backupIfExists "../${FILE}" createParent "../${FILE}" echo "concatenating files " setIFSnewline for vc_repo in "${REPOSITORY[@]}"; do if [ -e "${vc_repo}/${FILE}" ]; then echo " ${vc_repo}/${FILE}" cat "${vc_repo}/${FILE}" >> "../${FILE}" else echo "WARNING: ${FILE} is referenced by config from repository ${vc_repo} but does not exist (${vc_repo}/${FILE}). skipping." >&2 #continue fi done echo "to ../${FILE}" esac # reset parsed information for next item unset FILE unset REPOSITORY STATE=LookingForFile fi done } case "$1" in "bootstrap"|"-b") bootstrap ;; "generateTemplate"|"-t") generateConfigFromRepos ;; "generateFiles"|"-f") generateFiles ;; *) echo "${SELF} " echo " ACTION:" echo " bootstrap (-b) - bootstrap this script by symlinking it from the repository it's inside." echo " generateTemplate (-t) - generate a config file template from all files found in repositories. output goes to STDOUT." echo " generateFiles (-f) - generate symlinks or concanate files based on the rules of config file (${VC_CONFIG})." exit 1 esac