Wrap $TARGET in braces in disconnect().
[dotfiles-framework.git] / dotfiles.sh
index 4734bc34f83b5c8933b36299724650f81d4e536a..900fcf57a910c4de9b5b0b9d2e42019296c58b0c 100755 (executable)
@@ -2,6 +2,21 @@
 #
 # Dotfiles management script.  For details, run
 #   $ dotfiles.sh --help
+#
+# Copyright (C) 2011-2012 W. Trevor King <wking@tremily.us>
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program.  If not, see <http://www.gnu.org/licenses/>.
 
 VERSION='0.2'
 DOTFILES_DIR="${PWD}"
@@ -23,6 +38,19 @@ TAR=$(which tar)
 TOUCH=$(which touch)
 WGET=$(which wget)
 
+#####
+# Compatibility checks
+
+BASH="${BASH_VERSION%.*}"
+BASH_MAJOR="${BASH%.*}"
+BASH_MINOR="${BASH#*.}"
+
+if [ "${BASH_MAJOR}" -eq 3 ] && [ "${BASH_MINOR}" -eq 0 ]; then
+       echo "ERROR: ${0} requires Bash version >= 3.1" >&2
+       echo "you're running ${BASH}, which doesn't support += array assignment" >&2
+       exit 1
+fi
+
 #####
 # Utility functions
 
@@ -81,8 +109,10 @@ function run_on_all_repos()
                for REPO in *; do
                        if [ "${REPO}" = '*' ]; then
                                break  # no known repositories
+                       elif [ -f "${REPO}" ]; then
+                               continue  # repositories are directories
                        fi
-                       "${COMMAND}" "${REPO}" "${@}" || return 1
+                       "${COMMAND}" "${@}" "${REPO}" || return 1
                done
                return
        fi
@@ -101,10 +131,18 @@ function list_files()
 }
 
 # Global variable to allow passing associative arrats between functions
-declare -A REPO_SOURCE_DATA
+
+if [ "${BASH_MAJOR}" -ge 4 ]; then
+       declare -A REPO_SOURCE_DATA
+fi
 
 function set_repo_source()
 {
+       if [ "${BASH_MAJOR}" -lt 4 ]; then
+               echo "ERROR: ${0}'s set_repo_source requires Bash version >= 4.0" >&2
+               echo "you're running ${BASH}, which doesn't support associative arrays" >&2
+               return 1
+       fi
        REPO=$(nonempty_option 'set_repo_source' 'REPO' "${1}") || return 1
        > "${REPO}/source_cache" || return 1
        for KEY in "${!REPO_SOURCE_DATA[@]}"; do
@@ -115,6 +153,11 @@ function set_repo_source()
 # usage: get_repo_source REPO
 function get_repo_source()
 {
+       if [ "${BASH_MAJOR}" -lt 4 ]; then
+               echo "ERROR: ${0}'s get_repo_source() requires Bash version >= 4.0" >&2
+               echo "you're running ${BASH}, which doesn't support associative arrays" >&2
+               return 1
+       fi
        REPO=$(nonempty_option 'get_repo_source' 'REPO' "${1}") || return 1
        REPO_SOURCE_DATA=()
        if [ -f "${REPO}/source_cache" ]; then
@@ -137,6 +180,17 @@ function get_repo_source()
        fi
 }
 
+function git_fetch()
+{
+       REPO=$(nonempty_option 'git_fetch' 'REPO' "${1}") || return 1
+       REMOTES=$(cd "${REPO}" && "${GIT}" remote) || return 1
+       if [ -n "${REMOTES}" ]; then
+               (cd "${REPO}" && "${GIT}" pull) || return 1
+       else
+               echo "no remote repositories found for ${REPO}"
+       fi
+}
+
 function wget_fetch()
 {
        REPO=$(nonempty_option 'wget_fetch' 'REPO' "${1}") || return 1
@@ -162,11 +216,9 @@ function wget_fetch()
                if [ -n "${SERVER_ETAG}" ]; then  # store new ETag
                        REPO_SOURCE_DATA['etag']="${SERVER_ETAG}"
                        set_repo_source "${REPO}" || return 1
-               else
-                       if [ -n "${ETAG}" ]; then  # clear old ETag
-                               unset "${REPO_SOURCE_DATA['etag']}"
-                               set_repo_source "${REPO}" || return 1
-                       fi
+               elif [ -n "${ETAG}" ]; then  # clear old ETag
+                       unset "${REPO_SOURCE_DATA['etag']}"
+                       set_repo_source "${REPO}" || return 1
                fi
                echo "extracting ${BUNDLE} to ${REPO}"
                "${TAR}" -xf "${BUNDLE}" -C "${REPO}" --strip-components 1 --overwrite || return 1
@@ -176,6 +228,7 @@ function wget_fetch()
        fi
 }
 
+
 # usage: link_file REPO FILE
 #
 # Create the symbolic link to the version of FILE in the REPO
@@ -204,7 +257,7 @@ function link_file()
                echo "link ${TARGET}/${FILE} to ${DOTFILES_DIR}/${REPO}/patched-src/${FILE}"
        else
                echo -n 'link '
-               "${LN}" -sv "${DOTFILES_DIR}/${REPO}/patched-src/${FILE}" "${TARGET}/${FILE}" || return 1
+               "${LN}" -rsv "${DOTFILES_DIR}/${REPO}/patched-src/${FILE}" "${TARGET}/${FILE}" || return 1
        fi
 }
 
@@ -302,10 +355,15 @@ function fetch()
        # multi-repo case handled in main() by run_on_all_repos()
        REPO=$(nonempty_option 'fetch' 'REPO' "${1}") || return 1
        maxargs 'fetch' 1 "${@}" || return 1
-       get_repo_source "${REPO}" || return 1
-       TRANSFER=$(nonempty_option 'fetch' 'TRANSFER' "${REPO_SOURCE_DATA['transfer']}") || return 1
+       if [ "${BASH_MAJOR}" -ge 4 ]; then
+               get_repo_source "${REPO}" || return 1
+               TRANSFER=$(nonempty_option 'fetch' 'TRANSFER' "${REPO_SOURCE_DATA['transfer']}") || return 1
+       else
+               echo "WARNING: Bash version < 4.0, assuming all repos use git transfer" >&2
+               TRANSFER='git'
+       fi
        if [ "${TRANSFER}" = 'git' ]; then
-               "${GIT}" --git-dir "${REPO}/.git" pull || return 1
+               git_fetch "${REPO}" || return 1
        elif [ "${TRANSFER}" = 'wget' ]; then
                wget_fetch "${REPO}" || return 1
        else
@@ -390,10 +448,8 @@ function diff()
                        if [ ! -e "${TARGET}/${FILE}" ]; then
                                echo "${FILE}"
                        fi
-               else
-                       if [ -f "${TARGET}/${FILE}" ]; then
-                               (cd "${REPO}/src" && "${DIFF}" -u "${FILE}" "${TARGET}/${FILE}")
-                       fi
+               elif [ -f "${TARGET}/${FILE}" ]; then
+                       (cd "${REPO}/src" && "${DIFF}" -u "${FILE}" "${TARGET}/${FILE}")
                fi
        done <<-EOF
                $(list_files "${REPO}/src")
@@ -433,7 +489,7 @@ function patch()
                if [ -f "${FILE}" ]; then
                        echo "apply ${FILE}"
                        pushd "${REPO}/patched-src/" > /dev/null || return 1
-                       "${PATCH}" -p0 < "../../${FILE}" || return 1
+                       "${PATCH}" -p1 < "../../${FILE}" || return 1
                        popd > /dev/null || return 1
                fi
        done
@@ -441,6 +497,7 @@ function patch()
        # remove any files marked for removal in local-patch
        for REMOVE in "${REPO}/local-patch"/*.remove; do
                if [ -f "${REMOVE}" ]; then
+                       echo "apply ${FILE}"
                        while read LINE; do
                                if [ -z "${LINE}" ] || [ "${LINE:0:1}" = '#' ]; then
                                        continue  # ignore blank lines and comments
@@ -570,7 +627,7 @@ function disconnect()
        BASHRC='no'
 
        while read FILE; do
-               if [ "${FILE}" = '.bashrc' ] && [ "$TARGET" -ef "${HOME}" ]; then
+               if [ "${FILE}" = '.bashrc' ] && [ "${TARGET}" -ef "${HOME}" ]; then
                        BASHRC='yes'
                fi
                if [ "${DOTFILES_SRC}/${FILE}" -ef "${TARGET}/${FILE}" ] && [ -h "${TARGET}/${FILE}" ]; then