From ead3ef6b75a96c9b0b6f15f67baad3f840beea73 Mon Sep 17 00:00:00 2001 From: Simonas Narbutas Date: Tue, 10 Jul 2018 17:11:15 +0200 Subject: [PATCH] tmp yadm Change-Id: Id2631f87528113e39aa3c12c5ff263920cecda03 --- yadm/yadm | 1159 +++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 1159 insertions(+) create mode 100755 yadm/yadm diff --git a/yadm/yadm b/yadm/yadm new file mode 100755 index 0000000..f0d5f58 --- /dev/null +++ b/yadm/yadm @@ -0,0 +1,1159 @@ +#!/bin/sh +# yadm - Yet Another Dotfiles Manager +# Copyright (C) 2015-2017 Tim Byrne + +# 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, version 3 of the License. + +# 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 . + +#; execute script with bash (shebang line is /bin/sh for portability) +if [ -z "$BASH_VERSION" ]; then + [ "$YADM_TEST" != 1 ] && exec bash "$0" "$@" +fi + +VERSION=1.11.1 + +YADM_WORK="$HOME" +YADM_DIR="$HOME/.yadm" + +YADM_ALT="alt" +YADM_REPO="repo.git" +YADM_CONFIG="config" +YADM_ENCRYPT="encrypt" +YADM_ARCHIVE="files.gpg" +YADM_BOOTSTRAP="bootstrap" + +HOOK_COMMAND="" +FULL_COMMAND="" + +GPG_PROGRAM="gpg" +GIT_PROGRAM="git" +ENVTPL_PROGRAM="envtpl" +LSB_RELEASE_PROGRAM="lsb_release" + +PROC_VERSION="/proc/version" +OPERATING_SYSTEM="Unknown" + +ENCRYPT_INCLUDE_FILES="unparsed" + +#; flag causing path translations with cygpath +USE_CYGPATH=0 + +#; flag when something may have changes (which prompts auto actions to be performed) +CHANGES_POSSIBLE=0 + +#; flag when a bootstrap should be performed after cloning +#; 0: skip auto_bootstrap, 1: ask, 2: perform bootstrap, 3: prevent bootstrap +DO_BOOTSTRAP=0 + +function main() { + + require_git + + #; capture full command, for passing to hooks + FULL_COMMAND="$*" + + #; create the YADM_DIR if it doesn't exist yet + [ -d "$YADM_DIR" ] || mkdir -p "$YADM_DIR" + + #; parse command line arguments + local retval=0 + internal_commands="^(alt|bootstrap|clean|clone|config|decrypt|encrypt|enter|help|init|introspect|list|perms|version)$" + if [ -z "$*" ] ; then + #; no argumnts will result in help() + help + elif [[ "$1" =~ $internal_commands ]] ; then + #; for internal commands, process all of the arguments + YADM_COMMAND="$1" + YADM_ARGS=() + shift + + while [[ $# -gt 0 ]] ; do + key="$1" + case $key in + -a) #; used by list() + LIST_ALL="YES" + ;; + -d) #; used by all commands + DEBUG="YES" + ;; + -f) #; used by init() and clone() + FORCE="YES" + ;; + -l) #; used by decrypt() + DO_LIST="YES" + ;; + -w) #; used by init() and clone() + if [[ ! "$2" =~ ^/ ]] ; then + error_out "You must specify a fully qualified work tree" + fi + YADM_WORK="$2" + shift + ;; + *) #; any unhandled arguments + YADM_ARGS+=("$1") + ;; + esac + shift + done + [ ! -d "$YADM_WORK" ] && error_out "Work tree does not exist: [$YADM_WORK]" + HOOK_COMMAND="$YADM_COMMAND" + invoke_hook "pre" + $YADM_COMMAND "${YADM_ARGS[@]}" + else + #; any other commands are simply passed through to git + HOOK_COMMAND="$1" + invoke_hook "pre" + git_command "$@" + retval="$?" + fi + + #; process automatic events + auto_alt + auto_perms + auto_bootstrap + + exit_with_hook $retval + +} + +#; ****** yadm Commands ****** + +function alt() { + + require_repo + parse_encrypt + + if [ -n "$YADM_OVERRIDE_ALT" ]; then + yadm_alt="$YADM_ALT" + else + yadm_alt_config=$(config yadm.alt) + + if [ -n "$yadm_alt_config" ]; then + yadm_alt="$YADM_WORK/$yadm_alt_config" + else + yadm_alt="$YADM_ALT" + fi + fi + yadm_alt="${yadm_alt%/}/" + + local_class="$(config local.class)" + if [ -z "$local_class" ] ; then + match_class="%" + else + match_class="$local_class" + fi + match_class="(%|$match_class)" + + local_system="$(config local.os)" + if [ -z "$local_system" ] ; then + local_system="$OPERATING_SYSTEM" + fi + match_system="(%|$local_system)" + + local_host="$(config local.hostname)" + if [ -z "$local_host" ] ; then + local_host=$(hostname) + local_host=${local_host%%.*} #; trim any domain from hostname + fi + match_host="(%|$local_host)" + + local_user="$(config local.user)" + if [ -z "$local_user" ] ; then + local_user=$(id -u -n) + fi + match_user="(%|$local_user)" + + #; regex for matching "##CLASS.SYSTEM.HOSTNAME.USER" + match1="^(.+)##(()|$match_system|$match_system\.$match_host|$match_system\.$match_host\.$match_user)$" + match2="^(.+)##($match_class|$match_class\.$match_system|$match_class\.$match_system\.$match_host|$match_class\.$match_system\.$match_host\.$match_user)$" + + cd_work "Alternates" || return + + #; only be noisy if the "alt" command was run directly + [ "$YADM_COMMAND" = "alt" ] && loud="YES" + + #; decide if a copy should be done instead of a symbolic link + local do_copy=0 + if [[ $OPERATING_SYSTEM == CYGWIN* ]] ; then + if [[ $(config --bool yadm.cygwin-copy) == "true" ]] ; then + do_copy=1 + fi + fi + + if [[ $OPERATING_SYSTEM == CYGWIN* ]] ; then + if [[ $(config --bool yadm.cygwin-copy) == "true" ]] ; then + do_copy=1 + fi + fi + + #; loop over all "tracked" files + #; for every file which matches the above regex, create a symlink + for match in $match1 $match2; do + last_linked='' + local IFS=$'\n' + for tracked_file in $("$GIT_PROGRAM" ls-files | sort) "${ENCRYPT_INCLUDE_FILES[@]}"; do + tracked_file="$YADM_WORK/$tracked_file" + #; process both the path, and it's parent directory + for alt_path in "$tracked_file" "${tracked_file%/*}"; do + if [ -e "$alt_path" ] ; then + if [[ $alt_path =~ $match ]] ; then + if [ "$alt_path" != "$last_linked" ] ; then + local dir="" + + new_link="${BASH_REMATCH[1]}" + + if [[ $new_link == $yadm_alt* ]]; then + new_link="$YADM_WORK/${new_link#$yadm_alt}" + dir="${new_link%/*}" + fi + + debug "Linking $alt_path to $new_link" + [ -n "$loud" ] && echo "Linking $alt_path to $new_link" + + if [ -n "$dir" ] && [ ! -e "$dir" ]; then + mkdir -p "$dir" + fi + + if [ "$do_copy" -eq 1 ]; then + if [ -L "$new_link" ]; then + rm -f "$new_link" + fi + cp -f "$alt_path" "$new_link" + else + ln -nfs "$alt_path" "$new_link" + fi + last_linked="$alt_path" + fi + fi + fi + done + done + done + + #; loop over all "tracked" files + #; for every file which is a *##yadm.j2 create a real file + local IFS=$'\n' + local match="^(.+)##yadm\\.j2$" + for tracked_file in $("$GIT_PROGRAM" ls-files | sort) "${ENCRYPT_INCLUDE_FILES[@]}"; do + tracked_file="$YADM_WORK/$tracked_file" + if [ -e "$tracked_file" ] ; then + if [[ $tracked_file =~ $match ]] ; then + local dir="" + + real_file="${BASH_REMATCH[1]}" + + if [[ $real_file == $yadm_alt* ]]; then + real_file="$YADM_WORK/${real_file#$yadm_alt}" + dir="${real_file%/*}" + fi + + if envtpl_available; then + debug "Creating $real_file from template $tracked_file" + [ -n "$loud" ] && echo "Creating $real_file from template $tracked_file" + + if [ -n "$dir" ] && [ ! -e "$dir" ]; then + mkdir -p "$dir" + fi + + YADM_CLASS="$local_class" \ + YADM_OS="$local_system" \ + YADM_HOSTNAME="$local_host" \ + YADM_USER="$local_user" \ + YADM_DISTRO=$(query_distro) \ + "$ENVTPL_PROGRAM" < "$tracked_file" > "$real_file" + else + debug "envtpl not available, not creating $real_file from template $tracked_file" + [ -n "$loud" ] && echo "envtpl not available, not creating $real_file from template $tracked_file" + fi + fi + fi + done + +} + +function bootstrap() { + + bootstrap_available || error_out "Cannot execute bootstrap\n'$YADM_BOOTSTRAP' is not an executable program." + + # GIT_DIR should not be set for user's bootstrap code + unset GIT_DIR + + echo "Executing $YADM_BOOTSTRAP" + exec "$YADM_BOOTSTRAP" + +} + +function clean() { + + error_out "\"git clean\" has been disabled for safety. You could end up removing all unmanaged files." + +} + +function clone() { + + DO_BOOTSTRAP=1 + + clone_args=() + while [[ $# -gt 0 ]] ; do + key="$1" + case $key in + --bootstrap) #; force bootstrap, without prompt + DO_BOOTSTRAP=2 + ;; + --no-bootstrap) #; prevent bootstrap, without prompt + DO_BOOTSTRAP=3 + ;; + *) #; main arguments are kept intact + clone_args+=("$1") + ;; + esac + shift + done + + [ -n "$DEBUG" ] && display_private_perms "initial" + + #; clone will begin with a bare repo + local empty= + init $empty + + #; add the specified remote, and configure the repo to track origin/master + debug "Adding remote to new repo" + "$GIT_PROGRAM" remote add origin "${clone_args[@]}" + debug "Configuring new repo to track origin/master" + "$GIT_PROGRAM" config branch.master.remote origin + "$GIT_PROGRAM" config branch.master.merge refs/heads/master + + #; fetch / merge (and possibly fallback to reset) + debug "Doing an initial fetch of the origin" + "$GIT_PROGRAM" fetch origin || { + debug "Removing repo after failed clone" + rm -rf "$YADM_REPO" + error_out "Unable to fetch origin ${clone_args[0]}" + } + debug "Determining if repo tracks private directories" + for private_dir in .ssh/ .gnupg/; do + found_log=$("$GIT_PROGRAM" log -n 1 origin/master -- "$private_dir" 2>/dev/null) + if [ -n "$found_log" ]; then + debug "Private directory $private_dir is tracked by repo" + assert_private_dirs "$private_dir" + fi + done + [ -n "$DEBUG" ] && display_private_perms "pre-merge" + debug "Doing an initial merge of origin/master" + "$GIT_PROGRAM" merge origin/master || { + debug "Merge failed, doing a reset and stashing conflicts." + "$GIT_PROGRAM" reset origin/master + if cd "$YADM_WORK"; then # necessary because of a bug in Git + "$GIT_PROGRAM" -c user.name='yadm clone' -c user.email='yadm' stash save Conflicts preserved from yadm clone command 2>&1 + cat </dev/null) + archive_regex="^\?\?" + if [[ $archive_status =~ $archive_regex ]] ; then + echo "It appears that $YADM_ARCHIVE is not tracked by yadm's repository." + echo "Would you like to add it now? (y/n)" + read -r answer < /dev/tty + if [[ $answer =~ ^[yY]$ ]] ; then + "$GIT_PROGRAM" add "$(mixed_path "$YADM_ARCHIVE")" + fi + fi + + CHANGES_POSSIBLE=1 + +} + +function enter() { + require_shell + require_repo + + shell_opts="" + shell_path="" + if [[ "$SHELL" =~ bash$ ]]; then + shell_opts="--norc" + shell_path="\w" + elif [[ "$SHELL" =~ [cz]sh$ ]]; then + shell_opts="-f" + shell_path="%~" + fi + + echo "Entering yadm repo" + + yadm_prompt="yadm shell ($YADM_REPO) $shell_path > " + PROMPT="$yadm_prompt" PS1="$yadm_prompt" "$SHELL" $shell_opts + + echo "Leaving yadm repo" +} + +function git_command() { + + require_repo + + #; translate 'gitconfig' to 'config' -- 'config' is reserved for yadm + if [ "$1" = "gitconfig" ] ; then + set -- "config" "${@:2}" + fi + + #; ensure private .ssh and .gnupg directories exist first + #; TODO: consider restricting this to only commands which modify the work-tree + + auto_private_dirs=$(config --bool yadm.auto-private-dirs) + if [ "$auto_private_dirs" != "false" ] ; then + assert_private_dirs .gnupg/ .ssh/ + fi + + CHANGES_POSSIBLE=1 + + #; pass commands through to git + debug "Running git command $GIT_PROGRAM $*" + "$GIT_PROGRAM" "$@" + return "$?" +} + +function help() { + + cat << EOF +Usage: yadm [options...] + +Manage dotfiles maintained in a Git repository. Manage alternate files +for specific systems or hosts. Encrypt/decrypt private files. + +Git Commands: +Any Git command or alias can be used as a . It will operate +on yadm's repository and files in the work tree (usually \$HOME). + +Commands: + yadm init [-f] - Initialize an empty repository + yadm clone [-f] - Clone an existing repository + yadm config - Configure a setting + yadm list [-a] - List tracked files + yadm alt - Create links for alternates + yadm bootstrap - Execute \$HOME/.yadm/bootstrap + yadm encrypt - Encrypt files + yadm decrypt [-l] - Decrypt files + yadm perms - Fix perms for private files + +Files: + \$HOME/.yadm/alt - yadm's additional alternate files + \$HOME/.yadm/config - yadm's configuration file + \$HOME/.yadm/repo.git - yadm's Git repository + \$HOME/.yadm/encrypt - List of globs used for encrypt/decrypt + \$HOME/.yadm/files.gpg - Encrypted data stored here + +Use "man yadm" for complete documentation. +EOF + + exit_with_hook 1 + +} + +function init() { + + #; safety check, don't attempt to init when the repo is already present + [ -d "$YADM_REPO" ] && [ -z "$FORCE" ] && \ + error_out "Git repo already exists. [$YADM_REPO]\nUse '-f' if you want to force it to be overwritten." + + #; remove existing if forcing the init to happen anyway + [ -d "$YADM_REPO" ] && { + debug "Removing existing repo prior to init" + rm -rf "$YADM_REPO" + } + + #; init a new bare repo + debug "Init new repo" + "$GIT_PROGRAM" init --shared=0600 --bare "$(mixed_path "$YADM_REPO")" "$@" + configure_repo + + CHANGES_POSSIBLE=1 + +} + +function introspect() { + case "$1" in + commands|configs|repo|switches) + "introspect_$1" + ;; + esac +} + +function introspect_commands() { + cat <<-EOF +alt +bootstrap +clean +clone +config +decrypt +encrypt +enter +gitconfig +help +init +introspect +list +perms +version +EOF +} + +function introspect_configs() { + cat << EOF +local.class +local.hostname +local.os +local.user +yadm.alt +yadm.auto-alt +yadm.auto-perms +yadm.auto-private-dirs +yadm.cygwin-copy +yadm.git-program +yadm.gpg-perms +yadm.gpg-program +yadm.gpg-recipient +yadm.ssh-perms +EOF +} + +function introspect_repo() { + echo "$YADM_REPO" +} + +function introspect_switches() { + cat <<-EOF +--yadm-alt +--yadm-archive +--yadm-bootstrap +--yadm-config +--yadm-dir +--yadm-encrypt +--yadm-repo +-Y +EOF +} + +function list() { + + require_repo + + #; process relative to YADM_WORK when --all is specified + if [ -n "$LIST_ALL" ] ; then + cd_work "List" || return + fi + + #; list tracked files + "$GIT_PROGRAM" ls-files + +} + +function perms() { + + parse_encrypt + + #; TODO: prevent repeats in the files changed + + cd_work "Perms" || return + + GLOBS=() + + #; include the archive created by "encrypt" + [ -f "$YADM_ARCHIVE" ] && GLOBS+=("$YADM_ARCHIVE") + + #; include all .ssh files (unless disabled) + if [[ $(config --bool yadm.ssh-perms) != "false" ]] ; then + GLOBS+=(".ssh" ".ssh/*") + fi + + #; include all gpg files (unless disabled) + if [[ $(config --bool yadm.gpg-perms) != "false" ]] ; then + GLOBS+=(".gnupg" ".gnupg/*") + fi + + #; include any files we encrypt + GLOBS+=("${ENCRYPT_INCLUDE_FILES[@]}") + + #; remove group/other permissions from collected globs + #shellcheck disable=SC2068 + #(SC2068 is disabled because in this case, we desire globbing) + chmod -f go-rwx ${GLOBS[@]} >/dev/null 2>&1 + #; TODO: detect and report changing permissions in a portable way + +} + +function version() { + + echo "yadm $VERSION" + exit_with_hook 0 + +} + +#; ****** Utility Functions ****** + +function query_distro() { + distro="" + if command -v "$LSB_RELEASE_PROGRAM" >/dev/null 2>&1; then + distro=$($LSB_RELEASE_PROGRAM -si 2>/dev/null) + fi + echo "$distro" +} + +function process_global_args() { + + #; global arguments are removed before the main processing is done + MAIN_ARGS=() + while [[ $# -gt 0 ]] ; do + key="$1" + case $key in + -Y|--yadm-dir) #; override the standard YADM_DIR + if [[ ! "$2" =~ ^/ ]] ; then + error_out "You must specify a fully qualified yadm directory" + fi + YADM_DIR="$2" + shift + ;; + --yadm-alt) #; override the standard YADM_ALT + if [[ ! "$2" =~ ^/ ]] ; then + error_out "You must specify a fully qualified alt path" + fi + YADM_OVERRIDE_ALT="$2" + shift + ;; + --yadm-repo) #; override the standard YADM_REPO + if [[ ! "$2" =~ ^/ ]] ; then + error_out "You must specify a fully qualified repo path" + fi + YADM_OVERRIDE_REPO="$2" + shift + ;; + --yadm-config) #; override the standard YADM_CONFIG + if [[ ! "$2" =~ ^/ ]] ; then + error_out "You must specify a fully qualified config path" + fi + YADM_OVERRIDE_CONFIG="$2" + shift + ;; + --yadm-encrypt) #; override the standard YADM_ENCRYPT + if [[ ! "$2" =~ ^/ ]] ; then + error_out "You must specify a fully qualified encrypt path" + fi + YADM_OVERRIDE_ENCRYPT="$2" + shift + ;; + --yadm-archive) #; override the standard YADM_ARCHIVE + if [[ ! "$2" =~ ^/ ]] ; then + error_out "You must specify a fully qualified archive path" + fi + YADM_OVERRIDE_ARCHIVE="$2" + shift + ;; + --yadm-bootstrap) #; override the standard YADM_BOOTSTRAP + if [[ ! "$2" =~ ^/ ]] ; then + error_out "You must specify a fully qualified bootstrap path" + fi + YADM_OVERRIDE_BOOTSTRAP="$2" + shift + ;; + *) #; main arguments are kept intact + MAIN_ARGS+=("$1") + ;; + esac + shift + done + +} + +function configure_paths() { + + #; change all paths to be relative to YADM_DIR + YADM_ALT="$YADM_DIR/$YADM_ALT" + YADM_REPO="$YADM_DIR/$YADM_REPO" + YADM_CONFIG="$YADM_DIR/$YADM_CONFIG" + YADM_ENCRYPT="$YADM_DIR/$YADM_ENCRYPT" + YADM_ARCHIVE="$YADM_DIR/$YADM_ARCHIVE" + YADM_BOOTSTRAP="$YADM_DIR/$YADM_BOOTSTRAP" + + #; independent overrides for paths + if [ -n "$YADM_OVERRIDE_ALT" ]; then + YADM_ALT="$YADM_OVERRIDE_ALT" + fi + if [ -n "$YADM_OVERRIDE_REPO" ]; then + YADM_REPO="$YADM_OVERRIDE_REPO" + fi + if [ -n "$YADM_OVERRIDE_CONFIG" ]; then + YADM_CONFIG="$YADM_OVERRIDE_CONFIG" + fi + if [ -n "$YADM_OVERRIDE_ENCRYPT" ]; then + YADM_ENCRYPT="$YADM_OVERRIDE_ENCRYPT" + fi + if [ -n "$YADM_OVERRIDE_ARCHIVE" ]; then + YADM_ARCHIVE="$YADM_OVERRIDE_ARCHIVE" + fi + if [ -n "$YADM_OVERRIDE_BOOTSTRAP" ]; then + YADM_BOOTSTRAP="$YADM_OVERRIDE_BOOTSTRAP" + fi + + #; use the yadm repo for all git operations + GIT_DIR=$(mixed_path "$YADM_REPO") + export GIT_DIR + +} + +function configure_repo() { + + debug "Configuring new repo" + + #; change bare to false (there is a working directory) + "$GIT_PROGRAM" config core.bare 'false' + + #; set the worktree for the yadm repo + "$GIT_PROGRAM" config core.worktree "$(mixed_path "$YADM_WORK")" + + #; by default, do not show untracked files and directories + "$GIT_PROGRAM" config status.showUntrackedFiles no + + #; possibly used later to ensure we're working on the yadm repo + "$GIT_PROGRAM" config yadm.managed 'true' + +} + +function set_operating_system() { + + #; special detection of WSL (windows subsystem for linux) + local proc_version + proc_version=$(cat "$PROC_VERSION" 2>/dev/null) + if [[ "$proc_version" =~ Microsoft ]]; then + OPERATING_SYSTEM="WSL" + else + OPERATING_SYSTEM=$(uname -s) + fi + + case "$OPERATING_SYSTEM" in + CYGWIN*) + git_version=$(git --version 2>/dev/null) + if [[ "$git_version" =~ windows ]] ; then + USE_CYGPATH=1 + fi + ;; + *) + ;; + esac + +} + +function debug() { + + [ -n "$DEBUG" ] && echo_e "DEBUG: $*" + +} + +function error_out() { + + echo_e "ERROR: $*" + exit_with_hook 1 + +} + +function exit_with_hook() { + + invoke_hook "post" "$1" + exit "$1" + +} + +function invoke_hook() { + + mode="$1" + exit_status="$2" + hook_command="$YADM_DIR/hooks/${mode}_$HOOK_COMMAND" + + if [ -x "$hook_command" ] ; then + debug "Invoking hook: $hook_command" + + #; expose some internal data to all hooks + work=$(unix_path "$("$GIT_PROGRAM" config core.worktree)") + YADM_HOOK_COMMAND=$HOOK_COMMAND + YADM_HOOK_EXIT=$exit_status + YADM_HOOK_FULL_COMMAND=$FULL_COMMAND + YADM_HOOK_REPO=$YADM_REPO + YADM_HOOK_WORK=$work + export YADM_HOOK_COMMAND + export YADM_HOOK_EXIT + export YADM_HOOK_FULL_COMMAND + export YADM_HOOK_REPO + export YADM_HOOK_WORK + + "$hook_command" + hook_status=$? + + #; failing "pre" hooks will prevent commands from being run + if [ "$mode" = "pre" ] && [ "$hook_status" -ne 0 ]; then + echo "Hook $hook_command was not successful" + echo "$HOOK_COMMAND will not be run" + exit "$hook_status" + fi + + fi + +} + +function assert_private_dirs() { + work=$(unix_path "$("$GIT_PROGRAM" config core.worktree)") + for private_dir in "$@"; do + if [ ! -d "$work/$private_dir" ]; then + debug "Creating $work/$private_dir" + #shellcheck disable=SC2174 + mkdir -m 0700 -p "$work/$private_dir" >/dev/null 2>&1 + fi + done +} + +function display_private_perms() { + when="$1" + for private_dir in .ssh .gnupg; do + if [ -d "$YADM_WORK/$private_dir" ]; then + private_perms=$(ls -ld "$YADM_WORK/$private_dir") + debug "$when" private dir perms "$private_perms" + fi + done +} + +function cd_work() { + YADM_WORK=$(unix_path "$("$GIT_PROGRAM" config core.worktree)") + cd "$YADM_WORK" || { + debug "$1 not processed, unable to cd to $YADM_WORK" + return 1 + } + return 0 +} + +function parse_encrypt() { + if [ "$ENCRYPT_INCLUDE_FILES" != "unparsed" ]; then + #shellcheck disable=SC2034 + PARSE_ENCRYPT_SHORT="parse_encrypt() not reprocessed" + return + fi + + ENCRYPT_INCLUDE_FILES=() + ENCRYPT_EXCLUDE_FILES=() + + cd_work "Parsing encrypt" || return + + exclude_pattern="^!(.+)" + if [ -f "$YADM_ENCRYPT" ] ; then + #; parse both included/excluded + while IFS='' read -r line || [ -n "$line" ]; do + if [[ ! $line =~ ^# && ! $line =~ ^[[:space:]]*$ ]] ; then + local IFS=$'\n' + for pattern in $line; do + if [[ "$pattern" =~ $exclude_pattern ]]; then + for ex_file in ${BASH_REMATCH[1]}; do + if [ -e "$ex_file" ]; then + ENCRYPT_EXCLUDE_FILES+=("$ex_file") + fi + done + else + for in_file in $pattern; do + if [ -e "$in_file" ]; then + ENCRYPT_INCLUDE_FILES+=("$in_file") + fi + done + fi + done + fi + done < "$YADM_ENCRYPT" + + #; remove excludes from the includes + #(SC2068 is disabled because in this case, we desire globbing) + FINAL_INCLUDE=() + #shellcheck disable=SC2068 + for included in "${ENCRYPT_INCLUDE_FILES[@]}"; do + skip= + #shellcheck disable=SC2068 + for ex_file in ${ENCRYPT_EXCLUDE_FILES[@]}; do + [ "$included" == "$ex_file" ] && { skip=1; break; } + done + [ -n "$skip" ] || FINAL_INCLUDE+=("$included") + done + ENCRYPT_INCLUDE_FILES=("${FINAL_INCLUDE[@]}") + fi + +} + +#; ****** Auto Functions ****** + +function auto_alt() { + + #; process alternates if there are possible changes + if [ "$CHANGES_POSSIBLE" = "1" ] ; then + auto_alt=$(config --bool yadm.auto-alt) + if [ "$auto_alt" != "false" ] ; then + [ -d "$YADM_REPO" ] && alt + fi + fi + +} + +function auto_perms() { + + #; process permissions if there are possible changes + if [ "$CHANGES_POSSIBLE" = "1" ] ; then + auto_perms=$(config --bool yadm.auto-perms) + if [ "$auto_perms" != "false" ] ; then + [ -d "$YADM_REPO" ] && perms + fi + fi + +} + +function auto_bootstrap() { + + bootstrap_available || return + + [ "$DO_BOOTSTRAP" -eq 0 ] && return + [ "$DO_BOOTSTRAP" -eq 3 ] && return + [ "$DO_BOOTSTRAP" -eq 2 ] && bootstrap + if [ "$DO_BOOTSTRAP" -eq 1 ] ; then + echo "Found $YADM_BOOTSTRAP" + echo "It appears that a bootstrap program exists." + echo "Would you like to execute it now? (y/n)" + read -r answer < /dev/tty + if [[ $answer =~ ^[yY]$ ]] ; then + bootstrap + fi + fi + +} + +#; ****** Prerequisites Functions ****** + +function require_archive() { + [ -f "$YADM_ARCHIVE" ] || error_out "$YADM_ARCHIVE does not exist. did you forget to create it?" +} +function require_encrypt() { + [ -f "$YADM_ENCRYPT" ] || error_out "$YADM_ENCRYPT does not exist. did you forget to create it?" +} +function require_git() { + local alt_git + alt_git="$(config yadm.git-program)" + + local more_info + more_info="" + + if [ "$alt_git" != "" ] ; then + GIT_PROGRAM="$alt_git" + more_info="\nThis command has been set via the yadm.git-program configuration." + fi + command -v "$GIT_PROGRAM" >/dev/null 2>&1 || \ + error_out "This functionality requires Git to be installed, but the command '$GIT_PROGRAM' cannot be located.$more_info" +} +function require_gpg() { + local alt_gpg + alt_gpg="$(config yadm.gpg-program)" + + local more_info + more_info="" + + if [ "$alt_gpg" != "" ] ; then + GPG_PROGRAM="$alt_gpg" + more_info="\nThis command has been set via the yadm.gpg-program configuration." + fi + command -v "$GPG_PROGRAM" >/dev/null 2>&1 || \ + error_out "This functionality requires GPG to be installed, but the command '$GPG_PROGRAM' cannot be located.$more_info" +} +function require_repo() { + [ -d "$YADM_REPO" ] || error_out "Git repo does not exist. did you forget to run 'init' or 'clone'?" +} +function require_shell() { + [ -x "$SHELL" ] || error_out "\$SHELL does not refer to an executable." +} +function bootstrap_available() { + [ -f "$YADM_BOOTSTRAP" ] && [ -x "$YADM_BOOTSTRAP" ] && return + return 1 +} +function envtpl_available() { + command -v "$ENVTPL_PROGRAM" >/dev/null 2>&1 && return + return 1 +} + +#; ****** Directory tranlations ****** + +function unix_path() { + #; for paths used by bash/yadm + if [ "$USE_CYGPATH" = "1" ] ; then + cygpath -u "$1" + else + echo "$1" + fi +} +function mixed_path() { + #; for paths used by Git + if [ "$USE_CYGPATH" = "1" ] ; then + cygpath -m "$1" + else + echo "$1" + fi +} + +#; ****** echo replacements ****** +function echo() { + IFS=' ' + printf '%s\n' "$*" +} +function echo_n() { + IFS=' ' + printf '%s' "$*" +} +function echo_e() { + IFS=' ' + printf '%b\n' "$*" +} + +#; ****** Main processing (when not unit testing) ****** + +if [ "$YADM_TEST" != 1 ] ; then + process_global_args "$@" + set_operating_system + configure_paths + main "${MAIN_ARGS[@]}" +fi