#!/usr/bin/env bash BASEDIR="$PWD" ENVDIR="$PWD/env" usage() { cat <<EOF Usage: $0 [options] <command> [arguments] Commands: help This help text list List environments clear Delete all environment and revert to flat config/files new <name> Create a new environment switch <name> Switch to a different environment delete <name> Delete an environment rename <newname> Rename the current environment diff Show differences between current state and environment save Save your changes to the environment revert Revert your changes since last save Options: EOF exit ${1:-1} } error() { echo "$0: $*" exit 1 } ask_bool() { local DEFAULT="$1"; shift local def defstr val case "$DEFAULT" in 1) def=0; defstr="Y/n";; 0) def=1; defstr="y/N";; *) def=; defstr="y/n";; esac while [ -z "$val" ]; do local VAL echo -n "$* ($defstr): " read VAL case "$VAL" in y*|Y*) val=0;; n*|N*) val=1;; *) val="$def";; esac done return "$val" } env_init() { local CREATE="$1" if [ -z "$CREATE" ]; then [ -d "$ENVDIR" ] || exit 0 fi [ -x "$(which git 2>/dev/null)" ] || error "Git is not installed" mkdir -p "$ENVDIR" || error "Failed to create the environment directory" cd "$ENVDIR" || error "Failed to switch to the environment directory" [ -d .git ] || { git init && touch .config && mkdir files && git add . && git commit -q -m "Initial import" } || { rm -rf .git error "Failed to initialize the environment directory" } } env_sync_data() { [ \! -L "$BASEDIR/.config" -a -f "$BASEDIR/.config" ] && mv "$BASEDIR/.config" "$ENVDIR" git add . git add -u } env_sync() { local STR="$1" env_sync_data git commit -m "${STR:-Update} at $(date)" } env_link_config() { rm -f "$BASEDIR/.config" ln -s env/.config "$BASEDIR/.config" mkdir -p "$ENVDIR/files" [ -L "$BASEDIR/files" ] || ln -s env/files "$BASEDIR/files" } env_do_reset() { git reset --hard HEAD git clean -d -f } env_list() { env_init git branch --color | grep -vE '^. master$' } env_diff() { env_init env_sync_data git diff --cached --color env_link_config } env_save() { env_init env_sync env_link_config } env_revert() { env_init env_do_reset env_link_config } env_ask_sync() { env_sync_data LINES="$(env_diff | wc -l)" # implies env_init [ "$LINES" -gt 0 ] && { if ask_bool 1 "Do you want to save your changes"; then env_sync else env_do_reset fi } } env_clear() { env_init [ -L "$BASEDIR/.config" ] && rm -f "$BASEDIR/.config" [ -L "$BASEDIR/files" ] && rm -f "$BASEDIR/files" [ -f "$ENVDIR/.config" ] || ( cd "$ENVDIR/files" && find | grep -vE '^\.$' > /dev/null ) env_sync_data if ask_bool 1 "Do you want to keep your current config and files"; then mkdir -p "$BASEDIR/files" shopt -s dotglob cp -a "$ENVDIR/files/"* "$BASEDIR/files" 2>/dev/null >/dev/null shopt -u dotglob cp "$ENVDIR/.config" "$BASEDIR/" else rm -rf "$BASEDIR/files" "$BASEDIR/.config" fi cd "$BASEDIR" rm -rf "$ENVDIR" } env_delete() { local name="${1##*/}" env_init [ -z "$name" ] && usage branch="$(git branch | grep '^\* ' | awk '{print $2}')" [ "$name" = "$branch" ] && error "cannot delete the currently selected environment" git branch -D "$name" } env_switch() { local name="${1##*/}" [ -z "$name" ] && usage env_init env_ask_sync git checkout "$name" || error "environment '$name' not found" env_link_config } env_rename() { local NAME="${1##*/}" env_init git branch -m "$NAME" } env_new() { local NAME="$1" local branch local from="master" [ -z "$NAME" ] && usage env_init 1 branch="$(git branch | grep '^\* ' | awk '{print $2}')" if [ -n "$branch" -a "$branch" != "master" ]; then env_ask_sync if ask_bool 0 "Do you want to clone the current environment?"; then from="$branch" fi rm -f "$BASEDIR/.config" "$BASEDIR/files" fi git checkout -b "$1" "$from" if [ -f "$BASEDIR/.config" -o -d "$BASEDIR/files" ]; then if ask_bool 1 "Do you want to start your configuration repository with the current configuration?"; then [ -d "$BASEDIR/files" -a \! -L "$BASEDIR/files" ] && { mkdir -p "$ENVDIR/files" shopt -s dotglob mv "$BASEDIR/files/"* "$ENVDIR/files/" 2>/dev/null shopt -u dotglob rmdir "$BASEDIR/files" } env_sync else rm -rf "$BASEDIR/.config" "$BASEDIR/files" fi fi env_link_config } COMMAND="$1"; shift case "$COMMAND" in help) usage 0;; new) env_new "$@";; list) env_list "$@";; clear) env_clear "$@";; switch) env_switch "$@";; delete) env_delete "$@";; rename) env_rename "$@";; diff) env_diff "$@";; save) env_save "$@";; revert) env_revert "$@";; *) usage;; esac