#!/bin/sh
# $Id: manage-conf-files.sh 21 2005-08-22 14:24:02Z erik.taal $
#
# To start using this script, make sure that you have install
# subversion and rsync. Then setup a repository to use for this
# script. This can be as easy as doing the following in your
# home directory:
#
# $ mkdir svn && svnadmin create svn/configfiles
# $ mkdir work && cd work && svn co file://$HOME/svn/configfiles
#
# Change the following if you checked out the repository somewhere else:
CHECKOUTDIR="${HOME}/work/configfiles"

if [ ! -d "${CHECKOUTDIR}" ]; then
  echo "Directory: ${CHECKOUTDIR} not found."
  echo "Please edit this script and specify CHECKOUTDIR."
  exit 1
fi

# The rest shouldn't need changing

HOSTNAME="`hostname`-`uname -s | tr A-Z a-z | tr / _`"
BASEDIR="${CHECKOUTDIR}/${HOSTNAME}"
FILELIST="${BASEDIR}/filelist.txt"

if [ ! -d "$BASEDIR" ]; then
  mkdir "$BASEDIR" || exit 1
  svn add "$BASEDIR"
fi

if [ ! -f "$FILELIST" ]; then
  touch "$FILELIST" || exit 1
  svn add "$FILELIST"
fi

if [ -x /usr/bin/rsync ]; then
  RSYNC=/usr/bin/rsync
elif [ -x /usr/local/bin/rsync ]; then
  RSYNC=/usr/local/bin/rsync
else
  echo "!! Could not find the rsync binary"
  exit 1
fi

RSYNCOPTIONS="-arP --files-from=${FILELIST}"

usage() {
  cat << EOF
  `basename $0` \$command [\$file] ...
  Commands:
    add       \$system_file ...
        adds \$system_file to the list of files to be synced,
        sync the files into the directory and schedule them for
        addition but do not commit them yet.
    addci|ciadd
        svn commits the file(s) that have been scheduled for
        addition into the repository.
    st(atus) 
        syncs system files into the repository directory
        and does a svn status action.
    l(ist) 
        Lists all the files currently being managed in the repository
    ci|commit \$repo_file ... 
        syncs system files into the repository directory
        and does a svn commit action.
    diff      \$repo_file
        syncs system files into the repository directory
        and performs a svn diff action on the given file.
    svn       \$svn_parameters ...
        cds to the repository directory and executes the
        svn \$svn_parameters command.
    NOTE:
      Due to permission denied errors etc, it should be safe
      to use the 'add' and 'st' commands to add/update files.
      Remember to restore permissions to the repository 
      directory before committing etc.
EOF
}

add() {
  ADDLIST=/tmp/manage-conf-files.addlist.$$
  ADDEDFILES=
  rm -f "$ADDLIST"
  for SYSFILE in "$@"; do
    if [ ! -f "$SYSFILE" ]; then
      echo "!! Can't add $SYSFILE because it's not a regular file or doesn't exist"
      continue
    fi
    if [ ! -r "$SYSFILE" ]; then
      echo "!! Can't read $SYSFILE, try sudo"
      continue
    fi
    if grep -q "^${SYSFILE}\$" "$FILELIST"; then
  	  echo "=> already added: $SYSFILE"
    else
      echo "$SYSFILE" >> "$FILELIST"
      ADDEDFILES="$SYSFILE $ADDEDFILES"
    fi
  done
  sync_files
  for SYSFILE in $ADDEDFILES; do
    do_svn add ".$SYSFILE" 2> /dev/null || {
      DIRADD=$(dirname ".$SYSFILE")
      while [ "$DIRADD" != "." ]; do
        do_svn add "$DIRADD" 2> /dev/null && break
        DIRADD=$(dirname "$DIRADD")
      done
    }
  done
}

commit_added() {
  ADDLIST=/tmp/manage-conf-files.addlist.$$
  rm -f "$ADDLIST"
  do_svn st | grep ^A | cut -b8- > "$ADDLIST"
  if [ -s "$ADDLIST" ]; then
    do_svn ci "$FILELIST" --targets "$ADDLIST" && \
      rm -f "$ADDLIST"
  fi
}

sync_files() {
  "$RSYNC" $RSYNCOPTIONS / "$BASEDIR"
  RET=$?
  if [ x"$RET" != x"0" ]; then
    echo "!! Return code: $RET"
  fi
  if [ -n "$SUDO_USER" ]; then
    echo "=> Detected use of sudo, changing file permissions to $SUDO_USER..."
    chown -R "${SUDO_USER}:${SUDO_GID}" "$BASEDIR"
  fi
  return $RET
}

do_svn_diff() {
  LARGEDIFF=/tmp/manage-conf-files.diff.$$
  rm -f "$LARGEDIFF"
  FILES="$@"
  if [ -z "$FILES" ]; then
    FILES=.
  fi
  for FILE in "$FILES"; do
    REPFILE="${BASEDIR}/${FILE}"
    #if [ ! -f "$REPFILE" ]; then
    #  echo "!! Can't find: $REPFILE in the repository"
    #  continue
    #fi
    MINPLUS=$(svn diff "$REPFILE" | grep "^[-+]" | grep -v "^---\|^+++" | cut -b 1 | sort | uniq -c | xargs echo)
    echo $MINPLUS >> "$LARGEDIFF"
    svn diff "$REPFILE" >> "$LARGEDIFF"
    echo "======" >> "$LARGEDIFF"
  done
  # Now view the diff
  echo '# vim:ft=diff' >> "$LARGEDIFF"
  vim -R -c 'se ts=2' -c 'se ft=diff ro nomod ic' -c 'nmap q :q!<CR>' "$LARGEDIFF"
  rm -f "$LARGEDIFF"
}

do_svn() {
  CURDIR="`pwd`"
  cd "$BASEDIR" > /dev/null || return 1
  echo "=> doing: svn $@"
  svn "$@"
  RET=$?
  cd "$CURDIR" > /dev/null
  return $RET
}

list() {
  echo "=> Raw filelist (sorted):"
  cat ${FILELIST} | sort
  #echo "=> All files in repository:"
  #do_svn st -vu
}

status() {
  ### Option 1:
  ### Do a dry-run rsync operation and show what's up to date and what not:
  ###
  ## I: ignore times O: ignore dir. times c: checksum n: dry run
  #RSYNCOPTIONS="-OIcnvv $RSYNCOPTIONS"
  #sync_files | \
  #  egrep -v '^building file list|^delta-transmission disabled|^total: matches|^sent |^total size is'
  ### End of option 1
  ### Option 2
  ### Sync the files to the repository and let 'svn st' show what the
  ### status is. A better option IMHO.
  sync_files && {
    do_svn st
  }
}

CMD=$1
if [ -z "$CMD" ]; then
  usage
  exit 1
fi

shift
FILES="$@"
if [ -n "$FILES" -a ! "$CMD"="diff" -a ! "$CMD"="svn" ]; then
  for file in "$FILES"; do
    withoutslash=${file#/*}
    if [ "$withoutslash" = "$file" ]; then
      echo "!! Must specify absolute path for file '$file'"
      exit 1
    fi
  done
fi

case $CMD in
  di*)
    do_svn_diff "$@"
    ;;
  add)
    add "$@"
    ;;
  ciadd|addci)
    commit_added
    ;;
  st*)
    status
    ;;
  l*)
    list
    ;;
  svn)
    do_svn "$@"
    ;;
  ci|commit)
    sync_files && do_svn ci "$@"
    ;;
  *)
    usage
    ;;
esac

exit 0
