X-Git-Url: http://git.tremily.us/?p=dotfiles-framework.git;a=blobdiff_plain;f=dotfiles.sh;h=5f0c5e5cb9cb8f4202fa42db8fcd4ff7926d65ca;hp=53dbe00c288ef39aa467d3e7942073b79ee3c998;hb=HEAD;hpb=75a3866dda4400567ff3be3ec116ad3aa6f7ed4d diff --git a/dotfiles.sh b/dotfiles.sh index 53dbe00..5f0c5e5 100755 --- a/dotfiles.sh +++ b/dotfiles.sh @@ -3,7 +3,7 @@ # Dotfiles management script. For details, run # $ dotfiles.sh --help # -# Copyright (C) 2011-2012 W. Trevor King +# Copyright (C) 2011-2015 W. Trevor King # # 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 @@ -18,7 +18,7 @@ # You should have received a copy of the GNU General Public License # along with this program. If not, see . -VERSION='0.2' +VERSION='0.4' DOTFILES_DIR="${PWD}" TARGET=~ CHECK_WGET_TYPE_AND_ENCODING='no' @@ -26,17 +26,17 @@ CHECK_WGET_TYPE_AND_ENCODING='no' ##### # External utilities -DIFF=$(which diff) -GIT=$(which git) -LN=$(which ln) -MV=$(which mv) -PATCH=$(which patch) -SED=$(which sed) -RM=$(which rm) -RSYNC=$(which rsync) -TAR=$(which tar) -TOUCH=$(which touch) -WGET=$(which wget) +DIFF=${DOTFILES_DIFF:-$(command -v diff)} +GIT=${DOTFILES_GIT:-$(command -v git)} +LN=${DOTFILES_LN:-$(command -v ln)} +MV=${DOTFILES_MV:-$(command -v mv)} +PATCH=${DOTFILES_PATCH:-$(command -v patch)} +SED=${DOTFILES_SED:-$(command -v sed)} +RM=${DOTFILES_RM:-$(command -v rm)} +RSYNC=${DOTFILES_RSYNC:-$(command -v rsync)} +TAR=${DOTFILES_TAR:-$(command -v tar)} +TOUCH=${DOTFILES_TOUCH:-$(command -v touch)} +WGET=${DOTFILES_WGET:-$(command -v wget)} ##### # Compatibility checks @@ -106,7 +106,7 @@ function run_on_all_repos() COMMAND="${1}" shift if [ -z "${REPO}" ]; then # run on all repositories - for REPO in *; do + for REPO in *; do if [ "${REPO}" = '*' ]; then break # no known repositories elif [ -f "${REPO}" ]; then @@ -130,7 +130,7 @@ function list_files() done < <(cd "${DIR}" && find .) } -# Global variable to allow passing associative arrats between functions +# Global variable to allow passing associative arrays between functions if [ "${BASH_MAJOR}" -ge 4 ]; then declare -A REPO_SOURCE_DATA @@ -172,6 +172,8 @@ function get_repo_source() REPO_SOURCE_DATA['repo']="${REPO}" if [ -d "${REPO}/.git" ]; then REPO_SOURCE_DATA['transfer']='git' + REPO_SOURCE_DATA['url']=$( + git --git-dir "${REPO}/.git/" config remote.origin.url) else echo "ERROR: no source location found for ${REPO}" >&2 return 1 @@ -232,11 +234,13 @@ function wget_fetch() # usage: link_file REPO FILE # # Create the symbolic link to the version of FILE in the REPO -# repository, overriding the target if it exists. +# repository, overriding the target if it exists. If you want to +# override the options passed to ${LN}, set LINK_OPTS. function link_file() { REPO=$(nonempty_option 'link_file' 'REPO' "${1}") || return 1 FILE=$(nonempty_option 'link_file' 'FILE' "${2}") || return 1 + LINK_OPTS="${LINK_OPTS:--sv}" # default to `-sv` if [ "${BACKUP}" = 'yes' ]; then if [ -e "${TARGET}/${FILE}" ] || [ -h "${TARGET}/${FILE}" ]; then if [ "${DRY_RUN}" = 'yes' ]; then @@ -256,8 +260,9 @@ function link_file() if [ "${DRY_RUN}" = 'yes' ]; then echo "link ${TARGET}/${FILE} to ${DOTFILES_DIR}/${REPO}/patched-src/${FILE}" else + SOURCE="${DOTFILES_DIR}/${REPO}/patched-src/${FILE}" echo -n 'link ' - "${LN}" -sv "${DOTFILES_DIR}/${REPO}/patched-src/${FILE}" "${TARGET}/${FILE}" || return 1 + "${LN}" ${LINK_OPTS} "${SOURCE}" "${TARGET}/${FILE}" || return 1 fi } @@ -331,6 +336,49 @@ function clone() fi } +### +# list command + +COMMANDS+=('list') + +function list_help() +{ + echo 'List current dotfiles repositories.' + if [ "${1}" = '--one-line' ]; then return; fi + + cat <<-EOF + + usage: $0 ${COMMAND} [REPO] + + List information for 'REPO' in a form simular to the 'clone' + command's arguments. If 'REPO' is not give, all repositories will + be listed. Examples: + + $0 list public + public wget http://example.com/public-dotfiles.tar.gz + $0 list + public wget http://example.com/public-dotfiles.tar.gz + private git ssh://example.com/~/private-dotfiles.git + EOF +} + +function list() +{ + # multi-repo case handled in main() by run_on_all_repos() + REPO=$(nonempty_option 'list' 'REPO' "${1}") || return 1 + maxargs 'list' 1 "${@}" || return 1 + if [ "${BASH_MAJOR}" -ge 4 ]; then + get_repo_source "${REPO}" || return 1 + TRANSFER=$(nonempty_option 'list' 'TRANSFER' "${REPO_SOURCE_DATA['transfer']}") || return 1 + URL=$(nonempty_option 'list' 'URL' "${REPO_SOURCE_DATA['url']}") || return 1 + else + echo "WARNING: Bash version < 4.0, cannot use assuming all repos use git transfer" >&2 + TRANSFER='git' + URL=$(git --git-dir "${REPO}/.git/" config remote.origin.url) + fi + echo "${REPO} ${TRANSFER} ${URL}" +} + ### # fetch command @@ -373,7 +421,7 @@ function fetch() } ### -# fetch command +# diff command COMMANDS+=('diff') @@ -411,7 +459,7 @@ function diff() '--removed') MODE='removed' ;; - '--local-patch') + '--local-patch') MODE='local-patch' ;; *) @@ -523,36 +571,54 @@ function link_help() cat <<-EOF - usage: $0 ${COMMAND} [--force|--force-file] [--dry-run] [--no-backup] [REPO] + usage: $0 ${COMMAND} [--force] [--force-dir] [--force-file] [--force-link] + [--dry-run] [--no-backup] [--relative] [REPO] Where 'REPO' is the name the dotfiles repository to link. If it is not given, all repositories will be linked. - By default, link.sh only replaces missing files and simlinks. You - can optionally overwrite any local files by passing the --force - option. + By default, ${COMMAND} only replaces missing directories, files, + simlinks. You can optionally overwrite any local stuff by passing + the --force option. If you only want to overwrite a particular + type, use the more granular --force-dir, etc. + + If you have coreutils 8.16 (2012-03-26) or greater, you can set + the --relative option to create symlinks that use relative paths. EOF } function link() { - FORCE='no' # If 'file', overwrite existing files. - # If 'yes', overwrite existing files and dirs. + FORCE_DIR='no' # If 'yes', overwrite existing directories. + FORCE_FILE='no' # If 'yes', overwrite existing files. + FORCE_LINK='no' # If 'yes', overwrite existing symlinks. DRY_RUN='no' # If 'yes', disable any actions that change the filesystem - BACKUP='yes' + BACKUP_OPT='yes' + LINK_OPTS='-sv' while [ "${1::2}" = '--' ]; do case "${1}" in '--force') - FORCE='yes' + FORCE_DIR='yes' + FORCE_FILE='yes' + FORCE_LINK='yes' + ;; + '--force-dir') + FORCE_DIR='yes' ;; '--force-file') - FORCE='file' + FORCE_FILE='yes' + ;; + '--force-link') + FORCE_LINK='yes' ;; '--dry-run') DRY_RUN='yes' ;; '--no-backup') - BACKUP='no' + BACKUP_OPT='no' + ;; + '--relative') + LINK_OPTS="${LINK_OPTS} --relative" ;; *) echo "ERROR: invalid option to link (${1})" >&2 @@ -566,17 +632,27 @@ function link() DOTFILES_SRC="${DOTFILES_DIR}/${REPO}/patched-src" while read FILE; do + BACKUP="${BACKUP_OPT}" if [ "${DOTFILES_SRC}/${FILE}" -ef "${TARGET}/${FILE}" ]; then - continue # already simlinked - fi - if [ -d "${DOTFILES_SRC}/${FILE}" ] && [ -d "${TARGET}/${FILE}" ] && \ - [ "${FORCE}" != 'yes' ]; then - echo "use --force to override the existing directory: ${TARGET}/${FILE}" - continue # allow unlinked directories - fi - if [ -e "$TARGET/${FILE}" ] && [ "${FORCE}" = 'no' ]; then - echo "use --force to override the existing target: ${TARGET}/${FILE}" - continue # target already exists + if [ "${FORCE_LINK}" = 'no' ]; then + # don't prompt about --force-link, because this will happen a lot + continue # already simlinked + elif [ ! -h "${TARGET}/${FILE}" ]; then + # target file/dir underneath an already symlinked dir + continue + else + # don't backup links that already point to the right place + BACKUP='no' + fi + else + if [ -d "${DOTFILES_SRC}/${FILE}" ] && [ -d "${TARGET}/${FILE}" ] && \ + [ "${FORCE_DIR}" = 'no' ]; then + echo "use --force-dir to override the existing directory: ${TARGET}/${FILE}" + continue # allow unlinked directories + elif [ -f "${TARGET}/${FILE}" ] && [ "${FORCE_FILE}" = 'no' ]; then + echo "use --force-file to override the existing target: ${TARGET}/${FILE}" + continue # target already exists + fi fi link_file "${REPO}" "${FILE}" || return 1 done <<-EOF @@ -612,7 +688,7 @@ function disconnect_help() function disconnect() { # multi-repo case handled in main() by run_on_all_repos() - REPO=$(nonempty_option 'link' 'REPO' "${1}") || return 1 + REPO=$(nonempty_option 'disconnect' 'REPO' "${1}") || return 1 maxargs 'disconnect' 1 "${@}" || return 1 DOTFILES_SRC="${DOTFILES_DIR}/${REPO}/patched-src" @@ -627,7 +703,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 @@ -677,7 +753,7 @@ function update_help() cat <<-EOF - usage: $0 ${COMMAND} [REPO] + usage: $0 ${COMMAND} [options] [REPO] Where 'REPO' is the name the dotfiles repository to update. If it is not given, all repositories will be updateed. @@ -686,13 +762,21 @@ function update_help() to bring them in sync with the central repositories. Keeps track of the last update time to avoid multiple fetches in the same week. + + ${COMMAND} passes any options it receives through to the link + command. EOF } function update() { + LINK_OPTS='' + while [ "${1::2}" = '--' ]; do + LINK_OPTS="${LINK_FN_OPTS} ${1}" + shift + done # multi-repo case handled in main() by run_on_all_repos() - REPO=$(nonempty_option 'link' 'REPO' "${1}") || return 1 + REPO=$(nonempty_option 'update' 'REPO' "${1}") || return 1 maxargs 'disconnect' 1 "${@}" || return 1 # Update once a week from our remote repository. Mark updates by @@ -705,7 +789,7 @@ function update() "${TOUCH}" "${UPDATE_FILE}" || return 1 fetch "${REPO}" || return 1 patch "${REPO}" || return 1 - link "${REPO}" || return 1 + link ${LINK_OPTS} "${REPO}" || return 1 echo "${REPO} dotfiles updated" fi } @@ -756,8 +840,8 @@ function main() echo "${VERSION}" return ;; - '--dotfiles-dir') - DOTFILES_DIR="${2}" + '--dotfiles-dir') + DOTFILES_DIR="${2}" shift ;; '--target') @@ -786,10 +870,10 @@ function main() shift done if [ "${#}" -eq 0 ]; then - run_on_all_repos "${COMMAND}" "${OPTIONS[@]}" || return 1 + run_on_all_repos "${COMMAND}" "${OPTIONS[@]}" || return 1 else maxargs "${0}" 1 "${@}" || return 1 - "${COMMAND}" "${OPTIONS[@]}" "${1}" || return 1 + "${COMMAND}" "${OPTIONS[@]}" "${1}" || return 1 fi fi }