#!/bin/sh # yadm - Yet Another Dotfiles Manager # Copyright (C) 2015-2024 Tim Byrne # Copyright (C) 2025 Erik Flodin # 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 . # shellcheck shell=bash # execute script with bash (shebang line is /bin/sh for portability) if [ -z "$BASH_VERSION" ]; then [ "$YADM_TEST" != 1 ] && exec bash "$0" "$@" fi VERSION=3.5.0 YADM_WORK="$HOME" YADM_DIR= YADM_DATA= YADM_LEGACY_DIR="${HOME}/.yadm" YADM_LEGACY_ARCHIVE="files.gpg" # these are the default paths relative to YADM_DIR YADM_CONFIG="config" YADM_ENCRYPT="encrypt" YADM_BOOTSTRAP="bootstrap" YADM_HOOKS="hooks" YADM_ALT="alt" # these are the default paths relative to YADM_DATA YADM_REPO="repo.git" YADM_ARCHIVE="archive" HOOK_COMMAND="" FULL_COMMAND="" GPG_PROGRAM="gpg" OPENSSL_PROGRAM="openssl" GIT_PROGRAM="git" AWK_PROGRAM=("gawk" "awk") GIT_CRYPT_PROGRAM="git-crypt" TRANSCRYPT_PROGRAM="transcrypt" J2CLI_PROGRAM="j2" ENVTPL_PROGRAM="envtpl" ESH_PROGRAM="esh" LSB_RELEASE_PROGRAM="lsb_release" OS_RELEASE="/etc/os-release" PROC_VERSION="/proc/version" OPERATING_SYSTEM="Unknown" ENCRYPT_INCLUDE_FILES="unparsed" NO_ENCRYPT_TRACKED_FILES=() LEGACY_WARNING_ISSUED=0 INVALID_ALT=() GPG_OPTS=() OPENSSL_OPTS=() # 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 # the parameters will be space delimited and # spaces, tabs, and backslashes will be escaped _tab=$'\t' for param in "$@"; do param="${param//\\/\\\\}" param="${param//$_tab/\\$_tab}" param="${param// /\\ }" _fc+=("$param") done FULL_COMMAND="${_fc[*]}" # create the YADM_DIR & YADM_DATA if they doesn't exist yet [ -d "$YADM_DIR" ] || mkdir -p "$YADM_DIR" [ -d "$YADM_DATA" ] || mkdir -p "$YADM_DATA" # parse command line arguments local retval=0 internal_commands="^(alt|bootstrap|clean|clone|config|decrypt|encrypt|enter|git-crypt|help|--help|init|introspect|list|perms|transcrypt|upgrade|version|--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_COMMAND="${YADM_COMMAND/__/}" YADM_ARGS=() shift # commands listed below do not process any of the parameters if [[ "$YADM_COMMAND" =~ ^(enter|git_crypt)$ ]]; then YADM_ARGS=("$@") else 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(), clone() and upgrade() FORCE="YES" ;; -l) # used by decrypt() DO_LIST="YES" [[ "$YADM_COMMAND" =~ ^(clone|config)$ ]] && YADM_ARGS+=("$1") ;; -w) # used by init() and clone() YADM_WORK="$(qualify_path "$2" "work tree")" shift ;; *) # any unhandled arguments YADM_ARGS+=("$1") ;; esac shift done fi [ ! -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 } # ****** Alternate Processing ****** function score_file() { local source="$1" local target="$2" local conditions="${source#*##}" score=0 local template_processor="" IFS=',' read -ra fields <<<"$conditions" for field in "${fields[@]}"; do local label=${field%%.*} local value=${field#*.} [ "$field" = "$label" ] && value="" # when .value is omitted # Check for negative condition prefix (e.g., "~