pax_global_header00006660000000000000000000000064131740004270014510gustar00rootroot0000000000000052 comment=09a018ea5a081923a8d39f5f0bb02b138284230a yadm-1.12.0/000077500000000000000000000000001317400042700125235ustar00rootroot00000000000000yadm-1.12.0/.gitattributes000066400000000000000000000000211317400042700154070ustar00rootroot00000000000000yadm text eol=lf yadm-1.12.0/.gitignore000066400000000000000000000000551317400042700145130ustar00rootroot00000000000000.DS_Store .jekyll-metadata .sass-cache _site yadm-1.12.0/.notes.md000066400000000000000000000023641317400042700142600ustar00rootroot00000000000000## New release checklist ○ Version bump EVERYTHING ○ Copyright year update? ○ Rebuild contribs ○ Rebuild man ○ Update specfile ○ Update CHANGES ○ Tag X.XX ○ Merge dev → master ○ Update Homebrew ○ Update Copr ○ Tweet ## Homebrew update brew update cd $(brew --repo homebrew/core) git fetch --unshallow origin # only if still a shallow clone, #this might just fail if this was already done git remote add TheLocehiliosan git@github.com:TheLocehiliosan/homebrew-core.git git push -f TheLocehiliosan master:master vim Formula/yadm.rb brew install --build-from-source yadm brew reinstall --verbose --debug yadm (✗₂) brew audit --strict yadm brew test yadm git add Formula/yadm.rb git commit -S -m 'yadm X.XX' git push TheLocehiliosan master:yadm-X.XX Open pull request ## Copr update checkout yadm-rpm bring in yadm.spec from yadm repo update version in Makefile make tarball make buildhost cd yadm-rpm because centos 6,7... add 'Group: Development/Tools' disable BuildRequires disable %check fedpkg --dist f25 local that should leave a src RPM in the yadm-rpm dir create a new build by uploading the src rpm to copr yadm-1.12.0/.travis.yml000066400000000000000000000002471317400042700146370ustar00rootroot00000000000000--- sudo: required language: bash services: - docker before_install: - docker pull yadm/testbed:latest script: - docker run --rm -v "$PWD:/yadm:ro" yadm/testbed yadm-1.12.0/CHANGES000066400000000000000000000044301317400042700135170ustar00rootroot000000000000001.12.0 * Add basic Zsh completion (#71, #79) * Support directories in `.yadm/encrypt` (#81, #82) * Support exclusions in `.yadm/encrypt` (#86) * Improve portability with printf (#87) * Eliminate usage of `eval` and `ls` 1.11.1 * Create private dirs prior to merge (#74) 1.11.0 * Option for Cygwin to copy files instead of symlink (#62) * Support `YADM_DISTRO` in Jinja templates (#68) * Support pre/post hooks for every command (#70) 1.10.0 * Fix `COMP_WORDS bad array subscript` bug (#64) * Transition to semantic versioning 1.09 * Add Bash completion script (#60) * Support WSL detection (#61) * Add introspect command (used by completion) 1.08 * Fix bug alternates based on `CLASS` (#51) * Support globs and paths with space in .yadm/encrypt (#53, #54) * Add support for alternate files using Jinja templates (#56, #58) * Add `enter` command, for creating a sub-shell (#57) * Support local.hostname properly (#59) 1.07 * Add `CLASS` to supported alt-link patterns (#21) * Add bootstrap command (#42) * Support wildcards for alt-links (#43) * Stash conflicting data during clone (#44) * Offer bootstrap after successful clone (#45) * Display supported configs for `yadm config` (#46) * Add "curl-pipe" program to clone without installation (#48) * Fix bug in alt-link regular expressions (#49) 1.06 * Improve portability of `hostname` (#23) * Fix incompatibilities between Cygwin and Git for Windows (#26) * Allow Git program to be configured via yadm.git-program (#30) * Support alt-links for encrypted files (#34) * Exit with the same return value as Git (#35) * Support spaces in alt-link paths (#36) * Ignore empty lines in .yadm/encrypt (#40) * Fix typos (#41) 1.05 * Improve portability of shebang line (#14) * Support for symlinked directories (#17) * Improve portability of tar parameters (#18) * Support alternate gpg program (#19) * Fallback to using `ls` if `/bin/ls` does not exist (#22) 1.04 * Support alternate paths for yadm data (#4, #5) * Support asymmetric encryption (#7, #8) * Prevent the mixing of output and gpg prompts 1.03 * Add username matching for alternate files (#1) 1.02 * Handle permissions for `~/.gnupg/*gpg` 1.01 * Set `status.showUntrackedFiles` to "no" 1.00 * Initial public release yadm-1.12.0/CONTRIBUTORS000066400000000000000000000003341317400042700144030ustar00rootroot00000000000000CONTRIBUTORS Tim Byrne Espen Henriksen Cameron Eagans Klas Mellbourn Jan Schulz Siôn Le Roux Sébastien Gross Thomas Luzat Tomas Cernaj Uroš Golja japm48 Franciszek Madej Paraplegic Racehorse Patrick Hof Satoshi Ohki yadm-1.12.0/Dockerfile000066400000000000000000000013071317400042700145160ustar00rootroot00000000000000FROM ubuntu:yakkety MAINTAINER Tim Byrne # Install prerequisites RUN apt-get update && apt-get install -y git gnupg1 make shellcheck bats expect curl python-pip lsb-release RUN pip install envtpl # Force GNUPG version 1 at path /usr/bin/gpg RUN ln -fs /usr/bin/gpg1 /usr/bin/gpg # /yadm will be the work directory for all tests # docker commands should mount the local yadm project as /yadm WORKDIR /yadm # Create a Makefile to be used if no /yadm volume is mounted RUN echo "test:\n\t@echo 'The yadm project must be mounted at /yadm'\n\t@echo 'Try using a docker parameter like -v \"\$\$PWD:/yadm:ro\"'\n\t@false" > /yadm/Makefile # By default, run all tests defined CMD make test yadm-1.12.0/LICENSE000066400000000000000000000011761317400042700135350ustar00rootroot00000000000000yadm - 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 . yadm-1.12.0/Makefile000066400000000000000000000026551317400042700141730ustar00rootroot00000000000000.PHONY: all all: yadm.md contrib yadm.md: yadm.1 @groff -man -Tascii ./yadm.1 | col -bx | sed 's/^[A-Z]/## &/g' | sed '/yadm(1)/d' > yadm.md .PHONY: contrib contrib: @echo "CONTRIBUTORS\n" > CONTRIBUTORS @git shortlog -ns master gh-pages dev dev-pages | cut -f2 >> CONTRIBUTORS .PHONY: pdf pdf: @groff -man -Tps ./yadm.1 > yadm.ps @open yadm.ps @sleep 1 @rm yadm.ps .PHONY: test test: bats shellcheck .PHONY: parallel parallel: ls test/*bats | time parallel -q -P0 -- docker run --rm -v "$$PWD:/yadm:ro" yadm/testbed bash -c 'bats {}' .PHONY: bats bats: @echo Running all bats tests @GPG_AGENT_INFO= bats test .PHONY: shellcheck shellcheck: @echo Running shellcheck @shellcheck --version || true @shellcheck -s bash yadm bootstrap test/*.bash completion/yadm.bash_completion @cd test; \ for bats_file in *bats; do \ sed 's/^@test.*{/function test() {/' "$$bats_file" > "/tmp/$$bats_file.bash"; \ shellcheck -s bash "/tmp/$$bats_file.bash"; \ test_result="$$?"; \ rm -f "/tmp/$$bats_file.bash"; \ [ "$$test_result" -ne 0 ] && exit 1; \ done; true .PHONY: testhost testhost: @target=HEAD @rm -rf /tmp/testhost @git show $(target):yadm > /tmp/testhost @chmod a+x /tmp/testhost @echo Starting testhost target=\"$$target\" @docker run -w /root --hostname testhost --rm -it -v "/tmp/testhost:/bin/yadm:ro" yadm/testbed:latest bash .PHONY: man man: groff -man -Tascii ./yadm.1 | less .PHONY: wide wide: man ./yadm.1 yadm-1.12.0/README.md000066400000000000000000000006261317400042700140060ustar00rootroot00000000000000# yadm - Yet Another Dotfiles Manager [![Build Status](https://travis-ci.org/TheLocehiliosan/yadm.svg?branch=master)](https://travis-ci.org/TheLocehiliosan/yadm) Features, usage, examples and installation instructions can be found on the [website](https://thelocehiliosan.github.io/yadm/). [https://thelocehiliosan.github.io/yadm/](https://thelocehiliosan.github.io/yadm/) yadm-1.12.0/bootstrap000077500000000000000000000065761317400042700145040ustar00rootroot00000000000000#!/bin/bash # # This script can be "curl-piped" into bash to bootstrap a dotfiles repo when # yadm is not locally installed. Read below for instructions. # # DISCLAIMER: In general, I would advise against piping someone's code directly # from the Internet into an interpreter (like Bash). You should # probably review any code like this prior to executing it. I leave # it to you to decide if this is risky behavior or not. The main # reason this script exists is because I find it to be a pragmatic # way to bootstrap my dotfiles, and install yadm in one step # (allowing the yadm project to be a submodule of my dotfiles # repo). # # Invoke with: # # curl -fsSL 'https://tinyurl.com/yadm-bootstrap' | bash # # OR # # curl -fsSL 'https://github.com/TheLocehiliosan/yadm/raw/master/bootstrap' | bash [-s -- REPO_URL [YADM_RELEASE]] YADM_REPO="https://github.com/TheLocehiliosan/yadm" YADM_RELEASE="master" REPO_URL="" function yadm() { if command -v which >/dev/null 2>&1 && which yadm >/dev/null 2>&1; then echo "Found yadm installed locally, removing remote yadm() function" unset -f yadm command yadm "$@" else echo WARNING: Using yadm remotely. You should install yadm locally. curl -fsSL "$YADM_REPO/raw/$YADM_RELEASE/yadm" | bash -s -- "$@" fi } export -f yadm # if being sourced, return here, otherwise continue processing return 2>/dev/null unset -f yadm function remote_yadm() { curl -fsSL "$YADM_REPO/raw/$YADM_RELEASE/yadm" | bash -s -- "$@" } function ask_about_source() { if ! command -v yadm >/dev/null 2>&1; then echo echo "***************************************************" echo "yadm is NOT currently installed." echo "You should install it locally, this link may help:" echo "https://thelocehiliosan.github.io/yadm/docs/install" echo "***************************************************" echo echo "If installation is not possible right now, you can temporarily \"source\"" echo "in a yadm() function which fetches yadm remotely each time it is called." echo echo " source <(curl -fsSL '$YADM_REPO/raw/$YADM_RELEASE/bootstrap')" echo fi } function build_url() { echo "No repo URL provided." echo echo "Where is your repo?" echo echo " 1. GitHub" echo " 2. Bitbucket" echo " 3. GitLab" echo " 4. Other" echo read -r -p "Where is your repo? (1/2/3/4) ->" choice < /dev/tty case $choice in 1) REPO_URL="https://github.com/" ;; 2) REPO_URL="https://bitbucket.org/" ;; 3) REPO_URL="https://gitlab.com/" ;; *) echo echo Please specify the full URL of your dotfiles repo read -r -p "URL ->" choice < /dev/tty REPO_URL="$choice" return ;; esac echo echo "Provide your user and repo separated by '/'" echo "For example: UserName/dotfiles" echo read -r -p "User/Repo ->" choice < /dev/tty [[ "$choice" =~ ^[^[:space:]]+/[^[:space:]]+$ ]] || { echo "Not formatted as USER/REPO" REPO_URL= return } REPO_URL="${REPO_URL}${choice}.git" } function main() { [ -n "$1" ] && REPO_URL="$1" [ -n "$2" ] && YADM_RELEASE="$2" [ -z "$REPO_URL" ] && build_url [ -z "$REPO_URL" ] && echo "Unable to determine the repo URL" && exit 1 echo "Using URL: $REPO_URL" remote_yadm clone "$REPO_URL" ask_about_source } main "$@" yadm-1.12.0/completion/000077500000000000000000000000001317400042700146745ustar00rootroot00000000000000yadm-1.12.0/completion/README.md000066400000000000000000000023501317400042700161530ustar00rootroot00000000000000# Installation ## Bash completions ### Prerequisites **yadm** completion only works if Git completions are also enabled. ### Homebrew If using `homebrew` to install **yadm**, completions should automatically be handled if you also install `brew install bash-completion`. This might require you to include the main completion script in your own bashrc file like this: ``` [ -f /usr/local/etc/bash_completion ] && source /usr/local/etc/bash_completion ``` ### Manual installation Copy the completion script locally, and add this to you bashrc: ``` [ -f /full/path/to/yadm.bash_completion ] && source /full/path/to/yadm.bash_completion ``` ## Zsh completions ### Homebrew If using `homebrew` to install **yadm**, completions should handled automatically. ### Manual installation Copy the completion script `yadm.zsh_completion` locally, rename it to `_yadm`, and add the containing folder to `$fpath` in `.zshrc`: ``` fpath=(/path/to/folder/containing_yadm $fpath) autoload -U compinit compinit ``` ### Installation using [zplug](https://github.com/b4b4r07/zplug) Load `_yadm` as a plugin in your `.zshrc`: ``` fpath=("$ZPLUG_HOME/bin" $fpath) zplug "TheLocehiliosan/yadm", rename-to:_yadm, use:"completion/yadm.zsh_completion", as:command, defer:2 ``` yadm-1.12.0/completion/yadm.bash_completion000066400000000000000000000047231317400042700207240ustar00rootroot00000000000000# test if git completion is missing, but loader exists, attempt to load if ! declare -F _git > /dev/null && declare -F _completion_loader > /dev/null; then _completion_loader git fi # only operate if git completion is present if declare -F _git > /dev/null; then _yadm() { local current=${COMP_WORDS[COMP_CWORD]} local penultimate if [ "$((COMP_CWORD-1))" -ge "0" ]; then penultimate=${COMP_WORDS[COMP_CWORD-1]} fi local antepenultimate if [ "$((COMP_CWORD-2))" -ge "0" ]; then antepenultimate=${COMP_WORDS[COMP_CWORD-2]} fi local GIT_DIR # shellcheck disable=SC2034 GIT_DIR="$(yadm introspect repo 2>/dev/null)" case "$penultimate" in bootstrap) COMPREPLY=() return 0 ;; config) COMPREPLY=( $(compgen -W "$(yadm introspect configs 2>/dev/null)") ) return 0 ;; decrypt) COMPREPLY=( $(compgen -W "-l" -- "$current") ) return 0 ;; init) COMPREPLY=( $(compgen -W "-f -w" -- "$current") ) return 0 ;; introspect) COMPREPLY=( $(compgen -W "commands configs repo switches" -- "$current") ) return 0 ;; help) COMPREPLY=() # no specific help yet return 0 ;; list) COMPREPLY=( $(compgen -W "-a" -- "$current") ) return 0 ;; esac case "$antepenultimate" in clone) COMPREPLY=( $(compgen -W "-f -w --bootstrap --no-bootstrap" -- "$current") ) return 0 ;; esac # this condition is so files are completed properly for --yadm-xxx options if [[ ! "$penultimate" =~ ^- ]]; then # TODO: somehow solve the problem with [--yadm-xxx option] being # incompatible with what git expects, namely [--arg=option] _git fi if [[ "$current" =~ ^- ]]; then local matching matching=$(compgen -W "$(yadm introspect switches 2>/dev/null)" -- "$current") __gitcompappend "$matching" fi if [ "$COMP_CWORD" == 1 ] || [[ "$antepenultimate" =~ ^- ]] ; then local matching matching=$(compgen -W "$(yadm introspect commands 2>/dev/null)" -- "$current") __gitcompappend "$matching" fi # remove duplicates found in COMPREPLY (a native bash way could be better) if [ -n "${COMPREPLY[*]}" ]; then COMPREPLY=($(echo "${COMPREPLY[@]}" | sort -u)) fi } complete -o bashdefault -o default -F _yadm yadm 2>/dev/null \ || complete -o default -F _yadm yadm fi yadm-1.12.0/completion/yadm.zsh_completion000066400000000000000000000020471317400042700206100ustar00rootroot00000000000000#compdef yadm _yadm(){ local -a _1st_arguments _1st_arguments=( 'help:Display yadm command help' 'init:Initialize an empty repository' 'config:Configure a setting' 'list:List tracked files' 'alt:Create links for alternates' 'bootstrap:Execute $HOME/.yadm/bootstrap' 'encrypt:Encrypt files' 'decrypt:Decrypt files' 'perms:Fix perms for private files' 'add:git add' 'push:git push' 'pull:git pull' 'diff:git diff' 'checkout:git checkout' 'co:git co' 'commit:git commit' 'ci:git ci' 'status:git status' 'st:git st' 'reset:git reset' 'log:git log' ) local context state line expl local -A opt_args _arguments '*:: :->subcmds' && return 0 if (( CURRENT == 1 )); then _describe -t commands "yadm commands" _1st_arguments -V1 return fi case "$words[1]" in *) _arguments ':filenames:_files' ;; esac } _yadm "$@" yadm-1.12.0/test/000077500000000000000000000000001317400042700135025ustar00rootroot00000000000000yadm-1.12.0/test/000_unit_syntax.bats000066400000000000000000000002251317400042700173200ustar00rootroot00000000000000load common load_fixtures @test "Syntax check" { echo " $T_YADM must parse correctly " #; check the syntax of yadm bash -n "$T_YADM" } yadm-1.12.0/test/001_unit_paths.bats000066400000000000000000000126331317400042700171200ustar00rootroot00000000000000load common load_fixtures function configuration_test() { # shellcheck source=/dev/null YADM_TEST=1 source "$T_YADM" status=0 output=$( process_global_args "$@" ) || { status=$? true } if [ "$status" == 0 ]; then process_global_args "$@" configure_paths fi echo -e "STATUS:$status\nOUTPUT:$output" echo "CONFIGURED PATHS:" echo " YADM_DIR:$YADM_DIR" echo " YADM_REPO:$YADM_REPO" echo " YADM_CONFIG:$YADM_CONFIG" echo " YADM_ENCRYPT:$YADM_ENCRYPT" echo " YADM_ARCHIVE:$YADM_ARCHIVE" echo "YADM_BOOTSTRAP:$YADM_BOOTSTRAP" echo " GIT_DIR:$GIT_DIR" } @test "Default paths" { echo " Default paths should be defined YADM_DIR=$DEFAULT_YADM_DIR YADM_REPO=$DEFAULT_YADM_DIR/$DEFAULT_REPO YADM_CONFIG=$DEFAULT_YADM_DIR/$DEFAULT_CONFIG YADM_ENCRYPT=$DEFAULT_YADM_DIR/$DEFAULT_ENCRYPT YADM_ARCHIVE=$DEFAULT_YADM_DIR/$DEFAULT_ARCHIVE YADM_BOOTSTRAP=$DEFAULT_YADM_DIR/$DEFAULT_BOOTSTRAP GIT_DIR=$DEFAULT_YADM_DIR/$DEFAULT_REPO " configuration_test [ "$status" == 0 ] [ "$YADM_DIR" = "$HOME/.yadm" ] [ "$YADM_REPO" = "$DEFAULT_YADM_DIR/$DEFAULT_REPO" ] [ "$YADM_CONFIG" = "$DEFAULT_YADM_DIR/$DEFAULT_CONFIG" ] [ "$YADM_ENCRYPT" = "$DEFAULT_YADM_DIR/$DEFAULT_ENCRYPT" ] [ "$YADM_ARCHIVE" = "$DEFAULT_YADM_DIR/$DEFAULT_ARCHIVE" ] [ "$YADM_BOOTSTRAP" = "$DEFAULT_YADM_DIR/$DEFAULT_BOOTSTRAP" ] [ "$GIT_DIR" = "$DEFAULT_YADM_DIR/$DEFAULT_REPO" ] } @test "Override YADM_DIR" { echo " Override YADM_DIR using -Y $T_DIR_YADM YADM_DIR should become $T_DIR_YADM " TEST_ARGS=(-Y $T_DIR_YADM) configuration_test "${TEST_ARGS[@]}" [ "$status" == 0 ] [ "$YADM_DIR" = "$T_DIR_YADM" ] [ "$YADM_REPO" = "$T_DIR_YADM/$DEFAULT_REPO" ] [ "$YADM_CONFIG" = "$T_DIR_YADM/$DEFAULT_CONFIG" ] [ "$YADM_ENCRYPT" = "$T_DIR_YADM/$DEFAULT_ENCRYPT" ] [ "$YADM_ARCHIVE" = "$T_DIR_YADM/$DEFAULT_ARCHIVE" ] [ "$YADM_BOOTSTRAP" = "$T_DIR_YADM/$DEFAULT_BOOTSTRAP" ] [ "$GIT_DIR" = "$T_DIR_YADM/$DEFAULT_REPO" ] } @test "Override YADM_DIR (not fully-qualified)" { echo " Override YADM_DIR using -Y 'relative/path' yadm should fail, and report the error " TEST_ARGS=(-Y relative/path) configuration_test "${TEST_ARGS[@]}" [ "$status" == 1 ] [[ "$output" =~ must\ specify\ a\ fully\ qualified ]] } @test "Override YADM_REPO" { echo " Override YADM_REPO using --yadm-repo /custom/repo YADM_REPO should become /custom/repo GIT_DIR should become /custom/repo " TEST_ARGS=(--yadm-repo /custom/repo) configuration_test "${TEST_ARGS[@]}" [ "$YADM_REPO" = "/custom/repo" ] [ "$GIT_DIR" = "/custom/repo" ] } @test "Override YADM_REPO (not fully qualified)" { echo " Override YADM_REPO using --yadm-repo relative/repo yadm should fail, and report the error " TEST_ARGS=(--yadm-repo relative/repo) configuration_test "${TEST_ARGS[@]}" [ "$status" == 1 ] [[ "$output" =~ must\ specify\ a\ fully\ qualified ]] } @test "Override YADM_CONFIG" { echo " Override YADM_CONFIG using --yadm-config /custom/config YADM_CONFIG should become /custom/config " TEST_ARGS=(--yadm-config /custom/config) configuration_test "${TEST_ARGS[@]}" [ "$YADM_CONFIG" = "/custom/config" ] } @test "Override YADM_CONFIG (not fully qualified)" { echo " Override YADM_CONFIG using --yadm-config relative/config yadm should fail, and report the error " TEST_ARGS=(--yadm-config relative/config) configuration_test "${TEST_ARGS[@]}" [ "$status" == 1 ] [[ "$output" =~ must\ specify\ a\ fully\ qualified ]] } @test "Override YADM_ENCRYPT" { echo " Override YADM_ENCRYPT using --yadm-encrypt /custom/encrypt YADM_ENCRYPT should become /custom/encrypt " TEST_ARGS=(--yadm-encrypt /custom/encrypt) configuration_test "${TEST_ARGS[@]}" [ "$YADM_ENCRYPT" = "/custom/encrypt" ] } @test "Override YADM_ENCRYPT (not fully qualified)" { echo " Override YADM_ENCRYPT using --yadm-encrypt relative/encrypt yadm should fail, and report the error " TEST_ARGS=(--yadm-encrypt relative/encrypt) configuration_test "${TEST_ARGS[@]}" [ "$status" == 1 ] [[ "$output" =~ must\ specify\ a\ fully\ qualified ]] } @test "Override YADM_ARCHIVE" { echo " Override YADM_ARCHIVE using --yadm-archive /custom/archive YADM_ARCHIVE should become /custom/archive " TEST_ARGS=(--yadm-archive /custom/archive) configuration_test "${TEST_ARGS[@]}" [ "$YADM_ARCHIVE" = "/custom/archive" ] } @test "Override YADM_ARCHIVE (not fully qualified)" { echo " Override YADM_ARCHIVE using --yadm-archive relative/archive yadm should fail, and report the error " TEST_ARGS=(--yadm-archive relative/archive) configuration_test "${TEST_ARGS[@]}" [ "$status" == 1 ] [[ "$output" =~ must\ specify\ a\ fully\ qualified ]] } @test "Override YADM_BOOTSTRAP" { echo " Override YADM_BOOTSTRAP using --yadm-bootstrap /custom/bootstrap YADM_BOOTSTRAP should become /custom/bootstrap " TEST_ARGS=(--yadm-bootstrap /custom/bootstrap) configuration_test "${TEST_ARGS[@]}" [ "$YADM_BOOTSTRAP" = "/custom/bootstrap" ] } @test "Override YADM_BOOTSTRAP (not fully qualified)" { echo " Override YADM_BOOTSTRAP using --yadm-bootstrap relative/bootstrap yadm should fail, and report the error " TEST_ARGS=(--yadm-bootstrap relative/bootstrap) configuration_test "${TEST_ARGS[@]}" [ "$status" == 1 ] [[ "$output" =~ must\ specify\ a\ fully\ qualified ]] } yadm-1.12.0/test/002_unit_gpg_program.bats000066400000000000000000000023221317400042700203000ustar00rootroot00000000000000load common T_YADM_CONFIG=; # populated by load_fixtures load_fixtures status=;output=; # populated by bats run() setup() { destroy_tmp make_parents "$T_YADM_CONFIG" } teardown() { destroy_tmp } function configuration_test() { # shellcheck source=/dev/null YADM_TEST=1 source "$T_YADM" # shellcheck disable=SC2034 YADM_CONFIG="$T_YADM_CONFIG" status=0 { output=$( require_gpg ) && require_gpg; } || { status=$? true } echo -e "STATUS:$status\nGPG_PROGRAM:$GPG_PROGRAM\nOUTPUT:$output" } @test "Default gpg program" { echo " Default gpg program should be 'gpg' " configuration_test [ "$status" == 0 ] [ "$GPG_PROGRAM" = "gpg" ] } @test "Override gpg program (valid program)" { echo " Override gpg using yadm.gpg-program Program should be 'cat' " git config --file="$T_YADM_CONFIG" "yadm.gpg-program" "cat" configuration_test [ "$status" == 0 ] [ "$GPG_PROGRAM" = "cat" ] } @test "Override gpg program (invalid program)" { echo " Override gpg using yadm.gpg-program Program should be 'badprogram' " git config --file="$T_YADM_CONFIG" "yadm.gpg-program" "badprogram" configuration_test [ "$status" == 1 ] [[ "$output" =~ badprogram ]] } yadm-1.12.0/test/003_unit_git_program.bats000066400000000000000000000023221317400042700203070ustar00rootroot00000000000000load common T_YADM_CONFIG=; # populated by load_fixtures load_fixtures status=;output=; # populated by bats run() setup() { destroy_tmp make_parents "$T_YADM_CONFIG" } teardown() { destroy_tmp } function configuration_test() { # shellcheck source=/dev/null YADM_TEST=1 source "$T_YADM" # shellcheck disable=SC2034 YADM_CONFIG="$T_YADM_CONFIG" status=0 { output=$( require_git ) && require_git; } || { status=$? true } echo -e "STATUS:$status\nGIT_PROGRAM:$GIT_PROGRAM\nOUTPUT:$output" } @test "Default git program" { echo " Default git program should be 'git' " configuration_test [ "$status" == 0 ] [ "$GIT_PROGRAM" = "git" ] } @test "Override git program (valid program)" { echo " Override git using yadm.git-program Program should be 'cat' " git config --file="$T_YADM_CONFIG" "yadm.git-program" "cat" configuration_test [ "$status" == 0 ] [ "$GIT_PROGRAM" = "cat" ] } @test "Override git program (invalid program)" { echo " Override git using yadm.git-program Program should be 'badprogram' " git config --file="$T_YADM_CONFIG" "yadm.git-program" "badprogram" configuration_test [ "$status" == 1 ] [[ "$output" =~ badprogram ]] } yadm-1.12.0/test/004_unit_bootstrap_available.bats000066400000000000000000000017241317400042700220200ustar00rootroot00000000000000load common T_YADM_BOOTSTRAP=; # populated by load_fixtures load_fixtures status=; # populated by bats run() setup() { destroy_tmp make_parents "$T_YADM_BOOTSTRAP" } teardown() { destroy_tmp } function available_test() { # shellcheck source=/dev/null YADM_TEST=1 source "$T_YADM" # shellcheck disable=SC2034 YADM_BOOTSTRAP="$T_YADM_BOOTSTRAP" status=0 { bootstrap_available; } || { status=$? true } echo -e "STATUS:$status" } @test "Bootstrap missing" { echo " When bootstrap command is missing return 1 " available_test [ "$status" == 1 ] } @test "Bootstrap not executable" { echo " When bootstrap command is not executable return 1 " touch "$T_YADM_BOOTSTRAP" available_test [ "$status" == 1 ] } @test "Bootstrap executable" { echo " When bootstrap command is not executable return 0 " touch "$T_YADM_BOOTSTRAP" chmod a+x "$T_YADM_BOOTSTRAP" available_test [ "$status" == 0 ] } yadm-1.12.0/test/005_unit_set_operating_system.bats000066400000000000000000000027651317400042700222610ustar00rootroot00000000000000load common load_fixtures @test "Default OS" { echo " By default, the value of OPERATING_SYSTEM should be reported by uname -s " # shellcheck source=/dev/null YADM_TEST=1 source "$T_YADM" status=0 output=$( set_operating_system; echo "$OPERATING_SYSTEM" ) || { status=$? true } expected=$(uname -s 2>/dev/null) echo "output=$output" echo "expect=$expected" [ "$status" == 0 ] [ "$output" = "$expected" ] } @test "Detect no WSL" { echo " When /proc/version does not contain Microsoft, report uname -s " echo "proc version exists" > "$BATS_TMPDIR/proc_version" # shellcheck source=/dev/null YADM_TEST=1 source "$T_YADM" # shellcheck disable=SC2034 PROC_VERSION="$BATS_TMPDIR/proc_version" status=0 output=$( set_operating_system; echo "$OPERATING_SYSTEM" ) || { status=$? true } expected=$(uname -s 2>/dev/null) echo "output=$output" echo "expect=$expected" [ "$status" == 0 ] [ "$output" = "$expected" ] } @test "Detect WSL" { echo " When /proc/version contains Microsoft, report WSL " echo "proc version contains Microsoft in it" > "$BATS_TMPDIR/proc_version" # shellcheck source=/dev/null YADM_TEST=1 source "$T_YADM" # shellcheck disable=SC2034 PROC_VERSION="$BATS_TMPDIR/proc_version" status=0 output=$( set_operating_system; echo "$OPERATING_SYSTEM" ) || { status=$? true } expected="WSL" echo "output=$output" echo "expect=$expected" [ "$status" == 0 ] [ "$output" = "$expected" ] } yadm-1.12.0/test/006_unit_query_distro.bats000066400000000000000000000015121317400042700205310ustar00rootroot00000000000000load common load_fixtures @test "Query distro (lsb_release present)" { echo " Use value of lsb_release -si " #shellcheck source=/dev/null YADM_TEST=1 source "$T_YADM" status=0 { output=$( query_distro ); } || { status=$? true } expected="${T_DISTRO}" echo "output=$output" echo "expect=$expected" [ "$status" == 0 ] [ "$output" = "$expected" ] } @test "Query distro (lsb_release missing)" { echo " Empty value if lsb_release is missing " #shellcheck source=/dev/null YADM_TEST=1 source "$T_YADM" LSB_RELEASE_PROGRAM="missing_lsb_release" echo "Using $LSB_RELEASE_PROGRAM as lsb_release" status=0 { output=$( query_distro ); } || { status=$? true } expected="" echo "output=$output" echo "expect=$expected" [ "$status" == 0 ] [ "$output" = "$expected" ] } yadm-1.12.0/test/007_unit_parse_encrypt.bats000066400000000000000000000162521317400042700206660ustar00rootroot00000000000000load common load_fixtures setup() { # SC2153 is intentional # shellcheck disable=SC2153 make_parents "$T_YADM_ENCRYPT" make_parents "$T_DIR_WORK" make_parents "$T_DIR_REPO" mkdir "$T_DIR_WORK" git init --shared=0600 --bare "$T_DIR_REPO" >/dev/null 2>&1 GIT_DIR="$T_DIR_REPO" git config core.bare 'false' GIT_DIR="$T_DIR_REPO" git config core.worktree "$T_DIR_WORK" GIT_DIR="$T_DIR_REPO" git config yadm.managed 'true' } teardown() { destroy_tmp } function run_parse() { # shellcheck source=/dev/null YADM_TEST=1 source "$T_YADM" YADM_ENCRYPT="$T_YADM_ENCRYPT" export YADM_ENCRYPT GIT_DIR="$T_DIR_REPO" export GIT_DIR # shellcheck disable=SC2034 status=0 { output=$( parse_encrypt) && parse_encrypt; } || { status=$? true } if [ "$1" == "twice" ]; then GIT_DIR="$T_DIR_REPO" parse_encrypt fi echo -e "OUTPUT:$output\n" echo "ENCRYPT_INCLUDE_FILES:" echo " Size: ${#ENCRYPT_INCLUDE_FILES[@]}" echo " Items: ${ENCRYPT_INCLUDE_FILES[*]}" echo "EXPECT_INCLUDE:" echo " Size: ${#EXPECT_INCLUDE[@]}" echo " Items: ${EXPECT_INCLUDE[*]}" } @test "parse_encrypt (not called)" { echo " parse_encrypt() is not called Array should be 'unparsed' " # shellcheck source=/dev/null YADM_TEST=1 source "$T_YADM" echo "ENCRYPT_INCLUDE_FILES=$ENCRYPT_INCLUDE_FILES" [ "$ENCRYPT_INCLUDE_FILES" == "unparsed" ] } @test "parse_encrypt (short-circuit)" { echo " Parsing should not happen more than once " run_parse "twice" echo "PARSE_ENCRYPT_SHORT: $PARSE_ENCRYPT_SHORT" [ "$status" == 0 ] [ "$output" == "" ] [[ "$PARSE_ENCRYPT_SHORT" =~ not\ reprocessed ]] } @test "parse_encrypt (file missing)" { echo " .yadm/encrypt is empty Array should be empty " EXPECT_INCLUDE=() run_parse [ "$status" == 0 ] [ "$output" == "" ] [ "${#ENCRYPT_INCLUDE_FILES[@]}" -eq "${#EXPECT_INCLUDE[@]}" ] [ "${ENCRYPT_INCLUDE_FILES[*]}" == "${EXPECT_INCLUDE[*]}" ] } @test "parse_encrypt (empty file)" { echo " .yadm/encrypt is empty Array should be empty " touch "$T_YADM_ENCRYPT" EXPECT_INCLUDE=() run_parse [ "$status" == 0 ] [ "$output" == "" ] [ "${#ENCRYPT_INCLUDE_FILES[@]}" -eq "${#EXPECT_INCLUDE[@]}" ] [ "${ENCRYPT_INCLUDE_FILES[*]}" == "${EXPECT_INCLUDE[*]}" ] } @test "parse_encrypt (files)" { echo " .yadm/encrypt is references present and missing files Array should be as expected " echo "file1" > "$T_DIR_WORK/file1" echo "file3" > "$T_DIR_WORK/file3" echo "file5" > "$T_DIR_WORK/file5" { echo "file1" echo "file2" echo "file3" echo "file4" echo "file5" } > "$T_YADM_ENCRYPT" EXPECT_INCLUDE=("file1" "file3" "file5") run_parse [ "$status" == 0 ] [ "$output" == "" ] [ "${#ENCRYPT_INCLUDE_FILES[@]}" -eq "${#EXPECT_INCLUDE[@]}" ] [ "${ENCRYPT_INCLUDE_FILES[*]}" == "${EXPECT_INCLUDE[*]}" ] } @test "parse_encrypt (files and dirs)" { echo " .yadm/encrypt is references present and missing files .yadm/encrypt is references present and missing dirs Array should be as expected " mkdir -p "$T_DIR_WORK/dir1" mkdir -p "$T_DIR_WORK/dir2" echo "file1" > "$T_DIR_WORK/file1" echo "file2" > "$T_DIR_WORK/file2" echo "a" > "$T_DIR_WORK/dir1/a" echo "b" > "$T_DIR_WORK/dir1/b" { echo "file1" echo "file2" echo "file3" echo "dir1" echo "dir2" echo "dir3" } > "$T_YADM_ENCRYPT" EXPECT_INCLUDE=("file1" "file2" "dir1" "dir2") run_parse [ "$status" == 0 ] [ "$output" == "" ] [ "${#ENCRYPT_INCLUDE_FILES[@]}" -eq "${#EXPECT_INCLUDE[@]}" ] [ "${ENCRYPT_INCLUDE_FILES[*]}" == "${EXPECT_INCLUDE[*]}" ] } @test "parse_encrypt (comments/empty lines)" { echo " .yadm/encrypt is references present and missing files .yadm/encrypt is references present and missing dirs .yadm/encrypt contains comments / blank lines Array should be as expected " mkdir -p "$T_DIR_WORK/dir1" mkdir -p "$T_DIR_WORK/dir2" echo "file1" > "$T_DIR_WORK/file1" echo "file2" > "$T_DIR_WORK/file2" echo "file3" > "$T_DIR_WORK/file3" echo "a" > "$T_DIR_WORK/dir1/a" echo "b" > "$T_DIR_WORK/dir1/b" { echo "file1" echo "file2" echo "#file3" echo " #file3" echo "" echo "dir1" echo "dir2" echo "dir3" } > "$T_YADM_ENCRYPT" EXPECT_INCLUDE=("file1" "file2" "dir1" "dir2") run_parse [ "$status" == 0 ] [ "$output" == "" ] [ "${#ENCRYPT_INCLUDE_FILES[@]}" -eq "${#EXPECT_INCLUDE[@]}" ] [ "${ENCRYPT_INCLUDE_FILES[*]}" == "${EXPECT_INCLUDE[*]}" ] } @test "parse_encrypt (w/spaces)" { echo " .yadm/encrypt is references present and missing files .yadm/encrypt is references present and missing dirs .yadm/encrypt references contain spaces Array should be as expected " mkdir -p "$T_DIR_WORK/di r1" mkdir -p "$T_DIR_WORK/dir2" echo "file1" > "$T_DIR_WORK/file1" echo "fi le2" > "$T_DIR_WORK/fi le2" echo "file3" > "$T_DIR_WORK/file3" echo "a" > "$T_DIR_WORK/di r1/a" echo "b" > "$T_DIR_WORK/di r1/b" { echo "file1" echo "fi le2" echo "#file3" echo " #file3" echo "" echo "di r1" echo "dir2" echo "dir3" } > "$T_YADM_ENCRYPT" EXPECT_INCLUDE=("file1" "fi le2" "di r1" "dir2") run_parse [ "$status" == 0 ] [ "$output" == "" ] [ "${#ENCRYPT_INCLUDE_FILES[@]}" -eq "${#EXPECT_INCLUDE[@]}" ] [ "${ENCRYPT_INCLUDE_FILES[*]}" == "${EXPECT_INCLUDE[*]}" ] } @test "parse_encrypt (wildcards)" { echo " .yadm/encrypt contains wildcards Array should be as expected " mkdir -p "$T_DIR_WORK/di r1" mkdir -p "$T_DIR_WORK/dir2" echo "file1" > "$T_DIR_WORK/file1" echo "fi le2" > "$T_DIR_WORK/fi le2" echo "file2" > "$T_DIR_WORK/file2" echo "file3" > "$T_DIR_WORK/file3" echo "a" > "$T_DIR_WORK/di r1/a" echo "b" > "$T_DIR_WORK/di r1/b" { echo "fi*" echo "#file3" echo " #file3" echo "" echo "#dir2" echo "di r1" echo "dir2" echo "dir3" } > "$T_YADM_ENCRYPT" EXPECT_INCLUDE=("fi le2" "file1" "file2" "file3" "di r1" "dir2") run_parse [ "$status" == 0 ] [ "$output" == "" ] [ "${#ENCRYPT_INCLUDE_FILES[@]}" -eq "${#EXPECT_INCLUDE[@]}" ] [ "${ENCRYPT_INCLUDE_FILES[*]}" == "${EXPECT_INCLUDE[*]}" ] } @test "parse_encrypt (excludes)" { echo " .yadm/encrypt contains exclusions Array should be as expected " mkdir -p "$T_DIR_WORK/di r1" mkdir -p "$T_DIR_WORK/dir2" mkdir -p "$T_DIR_WORK/dir3" echo "file1" > "$T_DIR_WORK/file1" echo "file1.ex" > "$T_DIR_WORK/file1.ex" echo "fi le2" > "$T_DIR_WORK/fi le2" echo "file3" > "$T_DIR_WORK/file3" echo "test" > "$T_DIR_WORK/test" echo "a.txt" > "$T_DIR_WORK/di r1/a.txt" echo "b.txt" > "$T_DIR_WORK/di r1/b.txt" echo "c.inc" > "$T_DIR_WORK/di r1/c.inc" { echo "fi*" echo "#file3" echo " #file3" echo "" echo " #test" echo "#dir2" echo "di r1/*" echo "dir2" echo "dir3" echo "dir4" echo "!*.ex" echo "!di r1/*.txt" } > "$T_YADM_ENCRYPT" EXPECT_INCLUDE=("fi le2" "file1" "file3" "di r1/c.inc" "dir2" "dir3") run_parse [ "$status" == 0 ] [ "$output" == "" ] [ "${#ENCRYPT_INCLUDE_FILES[@]}" -eq "${#EXPECT_INCLUDE[@]}" ] [ "${ENCRYPT_INCLUDE_FILES[*]}" == "${EXPECT_INCLUDE[*]}" ] } yadm-1.12.0/test/100_accept_version.bats000066400000000000000000000011161317400042700177400ustar00rootroot00000000000000load common load_fixtures status=;output=; #; populated by bats run() @test "Command 'version'" { echo " When 'version' command is provided, Print the current version with format 'yadm x.x.x' Exit with 0 " #; run yadm with 'version' command run "$T_YADM" version # shellcheck source=/dev/null #; load yadm variables (including VERSION) YADM_TEST=1 source "$T_YADM" #; validate status and output [ $status -eq 0 ] [ "$output" = "yadm $VERSION" ] version_regex="^yadm [[:digit:]]+\.[[:digit:]]+\.[[:digit:]]+$" [[ "$output" =~ $version_regex ]] } yadm-1.12.0/test/101_accept_help.bats000066400000000000000000000011351317400042700172050ustar00rootroot00000000000000load common load_fixtures status=;lines=; #; populated by bats run() @test "Missing command" { echo " When no command is provided, Produce usage instructions Exit with 1 " #; run yadm with no command run "$T_YADM" #; validate status and output [ $status -eq 1 ] [[ "${lines[0]}" =~ ^Usage: ]] } @test "Command 'help'" { echo " When 'help' command is provided, Produce usage instructions Exit with value 1 " #; run yadm with 'help' command run "$T_YADM" help #; validate status and output [ $status -eq 1 ] [[ "${lines[0]}" =~ ^Usage: ]] } yadm-1.12.0/test/102_accept_clean.bats000066400000000000000000000006471317400042700173470ustar00rootroot00000000000000load common load_fixtures status=;lines=; #; populated by bats run() @test "Command 'clean'" { echo " When 'clean' command is provided, Do nothing, this is a dangerous Git command when managing dot files Report the command as disabled Exit with 1 " #; run yadm with 'clean' command run "$T_YADM" clean #; validate status and output [ $status -eq 1 ] [[ "${lines[0]}" =~ disabled ]] } yadm-1.12.0/test/103_accept_git.bats000066400000000000000000000043301317400042700170420ustar00rootroot00000000000000load common load_fixtures status=;output=;lines=; #; populated by bats run() IN_REPO=(.bash_profile .vimrc) function setup_environment() { destroy_tmp build_repo "${IN_REPO[@]}" } @test "Passthru unknown commands to Git" { echo " When the command 'bogus' is provided Report bogus is not a command Exit with 1 " #; start fresh setup_environment #; run bogus run "${T_YADM_Y[@]}" bogus #; validate status and output [ "$status" -eq 1 ] [[ "$output" =~ .bogus..is.not.a.git.command ]] } @test "Git command 'add' - badfile" { echo " When the command 'add' is provided And the file specified does not exist Exit with 128 " #; start fresh setup_environment #; define a non existig testfile local testfile="$T_DIR_WORK/does_not_exist" #; run add run "${T_YADM_Y[@]}" add -v "$testfile" #; validate status and output [ "$status" -eq 128 ] [[ "$output" =~ pathspec.+did.not.match ]] } @test "Git command 'add'" { echo " When the command 'add' is provided Files are added to the index Exit with 0 " #; start fresh setup_environment #; create a testfile local testfile="$T_DIR_WORK/testfile" echo "$testfile" > "$testfile" #; run add run "${T_YADM_Y[@]}" add -v "$testfile" #; validate status and output [ "$status" -eq 0 ] [ "$output" = "add 'testfile'" ] } @test "Git command 'status'" { echo " When the command 'status' is provided Added files are shown Exit with 0 " #; run status run "${T_YADM_Y[@]}" status #; validate status and output [ "$status" -eq 0 ] [[ "$output" =~ new\ file:[[:space:]]+testfile ]] } @test "Git command 'commit'" { echo " When the command 'commit' is provided Index is commited Exit with 0 " #; run commit run "${T_YADM_Y[@]}" commit -m 'Add testfile' #; validate status and output [ "$status" -eq 0 ] [[ "${lines[1]}" =~ 1\ file\ changed ]] [[ "${lines[1]}" =~ 1\ insertion ]] } @test "Git command 'log'" { echo " When the command 'log' is provided Commits are shown Exit with 0 " #; run log run "${T_YADM_Y[@]}" log --oneline #; validate status and output [ "$status" -eq 0 ] [[ "${lines[0]}" =~ Add\ testfile ]] } yadm-1.12.0/test/104_accept_init.bats000066400000000000000000000107451317400042700172320ustar00rootroot00000000000000load common load_fixtures status=;output=; #; populated by bats run() setup() { destroy_tmp create_worktree "$T_DIR_WORK" } @test "Command 'init'" { echo " When 'init' command is provided, Create new repo with attributes: - 0600 permissions - not bare - worktree = \$HOME - showUntrackedFiles = no - yadm.managed = true Report the repo as initialized Exit with 0 " #; run init run "${T_YADM_Y[@]}" init #; validate status and output [ $status -eq 0 ] [[ "$output" =~ Initialized ]] #; validate repo attributes test_perms "$T_DIR_REPO" "drw.--.--." test_repo_attribute "$T_DIR_REPO" core.bare false test_repo_attribute "$T_DIR_REPO" core.worktree "$HOME" test_repo_attribute "$T_DIR_REPO" status.showUntrackedFiles no test_repo_attribute "$T_DIR_REPO" yadm.managed true } @test "Command 'init' -w (alternate worktree)" { echo " When 'init' command is provided, and '-w' is provided, Create new repo with attributes: - 0600 permissions - not bare - worktree = \$YADM_WORK - showUntrackedFiles = no - yadm.managed = true Report the repo as initialized Exit with 0 " #; run init run "${T_YADM_Y[@]}" init -w "$T_DIR_WORK" #; validate status and output [ $status -eq 0 ] [[ "$output" =~ Initialized ]] #; validate repo attributes test_perms "$T_DIR_REPO" "drw.--.--." test_repo_attribute "$T_DIR_REPO" core.bare false test_repo_attribute "$T_DIR_REPO" core.worktree "$T_DIR_WORK" test_repo_attribute "$T_DIR_REPO" status.showUntrackedFiles no test_repo_attribute "$T_DIR_REPO" yadm.managed true } @test "Command 'init' (existing repo)" { echo " When 'init' command is provided, and a repo already exists, Refuse to create a new repo Exit with 1 " #; create existing repo content mkdir -p "$T_DIR_REPO" local testfile="$T_DIR_REPO/testfile" touch "$testfile" #; run init run "${T_YADM_Y[@]}" init #; validate status and output [ $status -eq 1 ] [[ "$output" =~ already.exists ]] #; verify existing repo is intact if [ ! -e "$testfile" ]; then echo "ERROR: existing repo has been changed" return 1 fi } @test "Command 'init' -f (force overwrite repo)" { echo " When 'init' command is provided, and '-f' is provided and a repo already exists, Remove existing repo Create new repo with attributes: - 0600 permissions - not bare - worktree = \$HOME - showUntrackedFiles = no - yadm.managed = true Report the repo as initialized Exit with 0 " #; create existing repo content mkdir -p "$T_DIR_REPO" local testfile="$T_DIR_REPO/testfile" touch "$testfile" #; run init run "${T_YADM_Y[@]}" init -f #; validate status and output [ $status -eq 0 ] [[ "$output" =~ Initialized ]] #; verify existing repo is gone if [ -e "$testfile" ]; then echo "ERROR: existing repo files remain" return 1 fi #; validate repo attributes test_perms "$T_DIR_REPO" "drw.--.--." test_repo_attribute "$T_DIR_REPO" core.bare false test_repo_attribute "$T_DIR_REPO" core.worktree "$HOME" test_repo_attribute "$T_DIR_REPO" status.showUntrackedFiles no test_repo_attribute "$T_DIR_REPO" yadm.managed true } @test "Command 'init' -f -w (force overwrite repo with alternate worktree)" { echo " When 'init' command is provided, and '-f' is provided and '-w' is provided and a repo already exists, Remove existing repo Create new repo with attributes: - 0600 permissions - not bare - worktree = \$YADM_WORK - showUntrackedFiles = no - yadm.managed = true Report the repo as initialized Exit with 0 " #; create existing repo content mkdir -p "$T_DIR_REPO" local testfile="$T_DIR_REPO/testfile" touch "$testfile" #; run init run "${T_YADM_Y[@]}" init -f -w "$T_DIR_WORK" #; validate status and output [ $status -eq 0 ] [[ "$output" =~ Initialized ]] #; verify existing repo is gone if [ -e "$testfile" ]; then echo "ERROR: existing repo files remain" return 1 fi #; validate repo attributes test_perms "$T_DIR_REPO" "drw.--.--." test_repo_attribute "$T_DIR_REPO" core.bare false test_repo_attribute "$T_DIR_REPO" core.worktree "$T_DIR_WORK" test_repo_attribute "$T_DIR_REPO" status.showUntrackedFiles no test_repo_attribute "$T_DIR_REPO" yadm.managed true } yadm-1.12.0/test/105_accept_clone.bats000066400000000000000000000404621317400042700173670ustar00rootroot00000000000000load common load_fixtures status=;output=; #; populated by bats run() IN_REPO=(.bash_profile .vimrc) T_DIR_REMOTE="$T_TMP/remote" REMOTE_URL="file:///$T_TMP/remote" setup() { destroy_tmp build_repo "${IN_REPO[@]}" cp -rp "$T_DIR_REPO" "$T_DIR_REMOTE" } create_bootstrap() { make_parents "$T_YADM_BOOTSTRAP" { echo "#!/bin/bash" echo "echo Bootstrap successful" echo "exit 123" } > "$T_YADM_BOOTSTRAP" chmod a+x "$T_YADM_BOOTSTRAP" } @test "Command 'clone' (bad remote)" { echo " When 'clone' command is provided, and the remote is bad, Report error Remove the YADM_REPO Exit with 1 " #; remove existing worktree and repo rm -rf "$T_DIR_WORK" mkdir -p "$T_DIR_WORK" rm -rf "$T_DIR_REPO" #; run clone run "${T_YADM_Y[@]}" clone -w "$T_DIR_WORK" "file:///bogus-repo" #; validate status and output [ "$status" -eq 1 ] [[ "$output" =~ Unable\ to\ fetch\ origin ]] #; confirm repo directory is removed [ ! -d "$T_DIR_REPO" ] } @test "Command 'clone'" { echo " When 'clone' command is provided, Create new repo with attributes: - 0600 permissions - not bare - worktree = \$YADM_WORK - showUntrackedFiles = no - yadm.managed = true Report the repo as cloned A remote named origin exists Exit with 0 " #; remove existing worktree and repo rm -rf "$T_DIR_WORK" mkdir -p "$T_DIR_WORK" rm -rf "$T_DIR_REPO" #; run clone run "${T_YADM_Y[@]}" clone -w "$T_DIR_WORK" "$REMOTE_URL" #; validate status and output [ "$status" -eq 0 ] [[ "$output" =~ Initialized ]] #; validate repo attributes test_perms "$T_DIR_REPO" "drw.--.--." test_repo_attribute "$T_DIR_REPO" core.bare false test_repo_attribute "$T_DIR_REPO" core.worktree "$T_DIR_WORK" test_repo_attribute "$T_DIR_REPO" status.showUntrackedFiles no test_repo_attribute "$T_DIR_REPO" yadm.managed true #; test the remote local remote_output remote_output=$(GIT_DIR="$T_DIR_REPO" git remote show) [ "$remote_output" = "origin" ] } @test "Command 'clone' (existing repo)" { echo " When 'clone' command is provided, and a repo already exists, Report error Exit with 1 " #; run clone run "${T_YADM_Y[@]}" clone -w "$T_DIR_WORK" "$REMOTE_URL" #; validate status and output [ "$status" -eq 1 ] [[ "$output" =~ Git\ repo\ already\ exists ]] } @test "Command 'clone' -f (force overwrite)" { echo " When 'clone' command is provided, and '-f' is provided, and a repo already exists, Overwrite the repo with attributes: - 0600 permissions - not bare - worktree = \$YADM_WORK - showUntrackedFiles = no - yadm.managed = true Report the repo as cloned A remote named origin exists Exit with 0 " #; remove existing worktree rm -rf "$T_DIR_WORK" mkdir -p "$T_DIR_WORK" #; run clone run "${T_YADM_Y[@]}" clone -w "$T_DIR_WORK" -f "$REMOTE_URL" #; validate status and output [ "$status" -eq 0 ] [[ "$output" =~ Initialized ]] #; validate repo attributes test_perms "$T_DIR_REPO" "drw.--.--." test_repo_attribute "$T_DIR_REPO" core.bare false test_repo_attribute "$T_DIR_REPO" core.worktree "$T_DIR_WORK" test_repo_attribute "$T_DIR_REPO" status.showUntrackedFiles no test_repo_attribute "$T_DIR_REPO" yadm.managed true #; test the remote local remote_output remote_output=$(GIT_DIR="$T_DIR_REPO" git remote show) [ "$remote_output" = "origin" ] } @test "Command 'clone' (existing conflicts)" { echo " When 'clone' command is provided, and '-f' is provided, and a repo already exists, Overwrite the repo with attributes: - 0600 permissions - not bare - worktree = \$YADM_WORK - showUntrackedFiles = no - yadm.managed = true Report the repo as cloned A remote named origin exists Exit with 0 " #; remove existing repo rm -rf "$T_DIR_REPO" #; cause a conflict echo "conflict" >> "$T_DIR_WORK/.bash_profile" #; run clone run "${T_YADM_Y[@]}" clone -w "$T_DIR_WORK" "$REMOTE_URL" #; validate status and output [ "$status" -eq 0 ] [[ "$output" =~ Initialized ]] #; validate merging note [[ "$output" =~ Merging\ origin/master\ failed ]] [[ "$output" =~ NOTE ]] #; validate repo attributes test_perms "$T_DIR_REPO" "drw.--.--." test_repo_attribute "$T_DIR_REPO" core.bare false test_repo_attribute "$T_DIR_REPO" core.worktree "$T_DIR_WORK" test_repo_attribute "$T_DIR_REPO" status.showUntrackedFiles no test_repo_attribute "$T_DIR_REPO" yadm.managed true #; test the remote local remote_output remote_output=$(GIT_DIR="$T_DIR_REPO" git remote show) [ "$remote_output" = "origin" ] #; confirm yadm repo is clean cd "$T_DIR_WORK" ||: clean_status=$("${T_YADM_Y[@]}" status -uno --porcelain) echo "clean_status:'$clean_status'" [ -z "$clean_status" ] #; confirm conflicts are stashed existing_stash=$("${T_YADM_Y[@]}" stash list) echo "existing_stash:'$existing_stash'" [[ "$existing_stash" =~ Conflicts\ preserved ]] stashed_conflicts=$("${T_YADM_Y[@]}" stash show -p) echo "stashed_conflicts:'$stashed_conflicts'" [[ "$stashed_conflicts" =~ \+conflict ]] } @test "Command 'clone' (force bootstrap, missing)" { echo " When 'clone' command is provided, with the --bootstrap parameter and bootstrap does not exists Create new repo with attributes: - 0600 permissions - not bare - worktree = \$YADM_WORK - showUntrackedFiles = no - yadm.managed = true Report the repo as cloned A remote named origin exists Exit with 0 " #; remove existing worktree and repo rm -rf "$T_DIR_WORK" mkdir -p "$T_DIR_WORK" rm -rf "$T_DIR_REPO" #; run clone run "${T_YADM_Y[@]}" clone --bootstrap -w "$T_DIR_WORK" "$REMOTE_URL" #; validate status and output [ "$status" -eq 0 ] [[ "$output" =~ Initialized ]] #; validate repo attributes test_perms "$T_DIR_REPO" "drw.--.--." test_repo_attribute "$T_DIR_REPO" core.bare false test_repo_attribute "$T_DIR_REPO" core.worktree "$T_DIR_WORK" test_repo_attribute "$T_DIR_REPO" status.showUntrackedFiles no test_repo_attribute "$T_DIR_REPO" yadm.managed true #; test the remote local remote_output remote_output=$(GIT_DIR="$T_DIR_REPO" git remote show) [ "$remote_output" = "origin" ] } @test "Command 'clone' (force bootstrap, existing)" { echo " When 'clone' command is provided, with the --bootstrap parameter and bootstrap exists Create new repo with attributes: - 0600 permissions - not bare - worktree = \$YADM_WORK - showUntrackedFiles = no - yadm.managed = true Report the repo as cloned A remote named origin exists Run the bootstrap Exit with bootstrap's exit code " #; remove existing worktree and repo rm -rf "$T_DIR_WORK" mkdir -p "$T_DIR_WORK" rm -rf "$T_DIR_REPO" #; create the bootstrap create_bootstrap #; run clone run "${T_YADM_Y[@]}" clone --bootstrap -w "$T_DIR_WORK" "$REMOTE_URL" #; validate status and output [ "$status" -eq 123 ] [[ "$output" =~ Initialized ]] [[ "$output" =~ Bootstrap\ successful ]] #; validate repo attributes test_perms "$T_DIR_REPO" "drw.--.--." test_repo_attribute "$T_DIR_REPO" core.bare false test_repo_attribute "$T_DIR_REPO" core.worktree "$T_DIR_WORK" test_repo_attribute "$T_DIR_REPO" status.showUntrackedFiles no test_repo_attribute "$T_DIR_REPO" yadm.managed true #; test the remote local remote_output remote_output=$(GIT_DIR="$T_DIR_REPO" git remote show) [ "$remote_output" = "origin" ] } @test "Command 'clone' (prevent bootstrap)" { echo " When 'clone' command is provided, with the --no-bootstrap parameter and bootstrap exists Create new repo with attributes: - 0600 permissions - not bare - worktree = \$YADM_WORK - showUntrackedFiles = no - yadm.managed = true Report the repo as cloned A remote named origin exists Do NOT run bootstrap Exit with 0 " #; remove existing worktree and repo rm -rf "$T_DIR_WORK" mkdir -p "$T_DIR_WORK" rm -rf "$T_DIR_REPO" #; create the bootstrap create_bootstrap #; run clone run "${T_YADM_Y[@]}" clone --no-bootstrap -w "$T_DIR_WORK" "$REMOTE_URL" #; validate status and output [ "$status" -eq 0 ] [[ $output =~ Initialized ]] [[ ! $output =~ Bootstrap\ successful ]] #; validate repo attributes test_perms "$T_DIR_REPO" "drw.--.--." test_repo_attribute "$T_DIR_REPO" core.bare false test_repo_attribute "$T_DIR_REPO" core.worktree "$T_DIR_WORK" test_repo_attribute "$T_DIR_REPO" status.showUntrackedFiles no test_repo_attribute "$T_DIR_REPO" yadm.managed true #; test the remote local remote_output remote_output=$(GIT_DIR="$T_DIR_REPO" git remote show) [ "$remote_output" = "origin" ] } @test "Command 'clone' (existing bootstrap, answer n)" { echo " When 'clone' command is provided, and bootstrap exists Create new repo with attributes: - 0600 permissions - not bare - worktree = \$YADM_WORK - showUntrackedFiles = no - yadm.managed = true Report the repo as cloned A remote named origin exists Do NOT run bootstrap Exit with 0 " #; remove existing worktree and repo rm -rf "$T_DIR_WORK" mkdir -p "$T_DIR_WORK" rm -rf "$T_DIR_REPO" #; create the bootstrap create_bootstrap #; run clone run expect < "$T_YADM_CONFIG" #; run config run "${T_YADM_Y[@]}" config "$T_KEY" #; validate status and output [ $status -eq 0 ] if [ "$output" != "$T_VALUE" ]; then echo "ERROR: Incorrect value returned. Expected '$T_VALUE', got '$output'" return 1 fi } @test "Command 'config' (update)" { echo " When 'config' command is provided, and an attribute is provided and the attribute is already configured Report no output Update configuration file Exit with 0 " #; manually load a value into the configuration make_parents "$T_YADM_CONFIG" echo -e "${T_EXPECTED}_with_extra_data" > "$T_YADM_CONFIG" #; run config run "${T_YADM_Y[@]}" config "$T_KEY" "$T_VALUE" #; validate status and output [ $status -eq 0 ] [ "$output" = "" ] #; validate configuration local config config=$(cat "$T_YADM_CONFIG") local expected expected=$(echo -e "$T_EXPECTED") if [ "$config" != "$expected" ]; then echo "ERROR: Config does not match expected" echo "$config" return 1 fi } @test "Command 'config' (local read)" { echo " When 'config' command is provided, and an attribute is provided and the attribute is configured and the attribute is local.* Fetch the value from the repo config Report the requested value Exit with 0 " #; write local attributes build_repo for loption in class os hostname user; do GIT_DIR="$T_DIR_REPO" git config "local.$loption" "custom_$loption" done #; run config for loption in class os hostname user; do run "${T_YADM_Y[@]}" config "local.$loption" #; validate status and output [ $status -eq 0 ] if [ "$output" != "custom_$loption" ]; then echo "ERROR: Incorrect value returned. Expected 'custom_$loption', got '$output'" return 1 fi done } @test "Command 'config' (local write)" { echo " When 'config' command is provided, and an attribute is provided and a value is provided and the attribute is local.* Report no output Write the value to the repo config Exit with 0 " build_repo local expected local linecount expected="[local]\n" linecount=1 for loption in class os hostname user; do #; update expected expected="$expected\t$loption = custom_$loption\n" ((linecount+=1)) #; write local attributes run "${T_YADM_Y[@]}" config "local.$loption" "custom_$loption" #; validate status and output [ $status -eq 0 ] [ "$output" = "" ] done #; validate data local config config=$(tail "-$linecount" "$T_DIR_REPO/config") expected=$(echo -ne "$expected") if [ "$config" != "$expected" ]; then echo "ERROR: Config does not match expected" echo -e "$config" echo -e "EXPECTED:\n$expected" return 1 fi } yadm-1.12.0/test/107_accept_list.bats000066400000000000000000000035321317400042700172410ustar00rootroot00000000000000load common load_fixtures status=;lines=; #; populated by bats run() IN_REPO=(.bash_profile .hammerspoon/init.lua .vimrc) SUBDIR=".hammerspoon" IN_SUBDIR=(init.lua) function setup() { destroy_tmp build_repo "${IN_REPO[@]}" } @test "Command 'list' -a" { echo " When 'list' command is provided, and '-a' is provided, List tracked files Exit with 0 " #; run list -a run "${T_YADM_Y[@]}" list -a #; validate status and output [ "$status" -eq 0 ] local line=0 for f in "${IN_REPO[@]}"; do [ "${lines[$line]}" = "$f" ] ((line++)) || true done } @test "Command 'list' (outside of worktree)" { echo " When 'list' command is provided, and while outside of the worktree List tracked files Exit with 0 " #; run list run "${T_YADM_Y[@]}" list #; validate status and output [ "$status" -eq 0 ] local line=0 for f in "${IN_REPO[@]}"; do [ "${lines[$line]}" = "$f" ] ((line++)) || true done } @test "Command 'list' (in root of worktree)" { echo " When 'list' command is provided, and while in root of the worktree List tracked files Exit with 0 " #; run list run bash -c "(cd '$T_DIR_WORK'; ${T_YADM_Y[*]} list)" #; validate status and output [ "$status" -eq 0 ] local line=0 for f in "${IN_REPO[@]}"; do [ "${lines[$line]}" = "$f" ] ((line++)) || true done } @test "Command 'list' (in subdirectory of worktree)" { echo " When 'list' command is provided, and while in subdirectory of the worktree List tracked files for current directory Exit with 0 " #; run list run bash -c "(cd '$T_DIR_WORK/$SUBDIR'; ${T_YADM_Y[*]} list)" #; validate status and output [ "$status" -eq 0 ] local line=0 for f in "${IN_SUBDIR[@]}"; do echo "'${lines[$line]}' = '$f'" [ "${lines[$line]}" = "$f" ] ((line++)) || true done } yadm-1.12.0/test/108_accept_alt.bats000066400000000000000000000236731317400042700170570ustar00rootroot00000000000000load common load_fixtures status=;output=; #; populated by bats run() IN_REPO=(alt* "dir one") export TEST_TREE_WITH_ALT=1 EXCLUDED_NAME="excluded-base" function create_encrypt() { for efile in "encrypted-base##" "encrypted-system##$T_SYS" "encrypted-host##$T_SYS.$T_HOST" "encrypted-user##$T_SYS.$T_HOST.$T_USER"; do echo "$efile" >> "$T_YADM_ENCRYPT" echo "$efile" >> "$T_DIR_WORK/$efile" mkdir -p "$T_DIR_WORK/dir one/$efile" echo "dir one/$efile/file1" >> "$T_YADM_ENCRYPT" echo "dir one/$efile/file1" >> "$T_DIR_WORK/dir one/$efile/file1" done echo "$EXCLUDED_NAME##" >> "$T_YADM_ENCRYPT" echo "!$EXCLUDED_NAME##" >> "$T_YADM_ENCRYPT" echo "$EXCLUDED_NAME##" >> "$T_DIR_WORK/$EXCLUDED_NAME##" } setup() { destroy_tmp build_repo "${IN_REPO[@]}" create_encrypt } function test_alt() { local alt_type="$1" local test_overwrite="$2" local auto_alt="$3" #; detemine test parameters case $alt_type in base) link_name="alt-base" link_match="$link_name##" ;; system) link_name="alt-system" link_match="$link_name##$T_SYS" ;; host) link_name="alt-host" link_match="$link_name##$T_SYS.$T_HOST" ;; user) link_name="alt-user" link_match="$link_name##$T_SYS.$T_HOST.$T_USER" ;; encrypted_base) link_name="encrypted-base" link_match="$link_name##" ;; encrypted_system) link_name="encrypted-system" link_match="$link_name##$T_SYS" ;; encrypted_host) link_name="encrypted-host" link_match="$link_name##$T_SYS.$T_HOST" ;; encrypted_user) link_name="encrypted-user" link_match="$link_name##$T_SYS.$T_HOST.$T_USER" ;; override_system) link_name="alt-override-system" link_match="$link_name##custom_system" ;; override_host) link_name="alt-override-host" link_match="$link_name##$T_SYS.custom_host" ;; override_user) link_name="alt-override-user" link_match="$link_name##$T_SYS.$T_HOST.custom_user" ;; class_aaa) link_name="alt-system" link_match="$link_name##aaa" ;; class_zzz) link_name="alt-system" link_match="$link_name##zzz" ;; class_AAA) link_name="alt-system" link_match="$link_name##AAA" ;; class_ZZZ) link_name="alt-system" link_match="$link_name##ZZZ" ;; esac dir_link_name="dir one/${link_name}" dir_link_match="dir one/${link_match}" if [ "$test_overwrite" = "true" ]; then #; create incorrect links (to overwrite) ln -nfs "$T_DIR_WORK/dir2/file2" "$T_DIR_WORK/$link_name" ln -nfs "$T_DIR_WORK/dir2" "$T_DIR_WORK/$dir_link_name" else #; verify link doesn't already exist if [ -L "$T_DIR_WORK/$link_name" ] || [ -L "$T_DIR_WORK/$dir_link_name" ]; then echo "ERROR: Link already exists before running yadm" return 1 fi fi #; configure yadm.auto_alt=false if [ "$auto_alt" = "false" ]; then git config --file="$T_YADM_CONFIG" yadm.auto-alt false fi #; run yadm (alt or status) if [ -z "$auto_alt" ]; then run "${T_YADM_Y[@]}" alt #; validate status and output echo "TEST:Link Name:$link_name" echo "TEST:DIR Link Name:$dir_link_name" if [ "$status" != 0 ] || [[ ! "$output" =~ Linking.+$link_name ]] || [[ ! "$output" =~ Linking.+$dir_link_name ]]; then echo "OUTPUT:$output" echo "STATUS:$status" echo "ERROR: Could not confirm status and output of alt command" return 1; fi else #; running any passed through Git command should trigger auto-alt run "${T_YADM_Y[@]}" status if [ -n "$auto_alt" ] && [[ "$output" =~ Linking.+$link_name ]] && [[ "$output" =~ Linking.+$dir_link_name ]]; then echo "ERROR: Reporting of link should not happen" return 1 fi fi if [ -L "$T_DIR_WORK/$EXCLUDED_NAME" ] ; then echo "ERROR: Found link: $T_DIR_WORK/$EXCLUDED_NAME" echo "ERROR: Excluded files should not be linked" return 1 fi #; validate link content if [[ "$alt_type" =~ none ]] || [ "$auto_alt" = "false" ]; then #; no link should be present if [ -L "$T_DIR_WORK/$link_name" ] || [ -L "$T_DIR_WORK/$dir_link_name" ]; then echo "ERROR: Links should not exist" return 1 fi else #; correct link should be present local link_content local dir_link_content link_content=$(cat "$T_DIR_WORK/$link_name") dir_link_content=$(cat "$T_DIR_WORK/$dir_link_name/file1") if [ "$link_content" != "$link_match" ] || [ "$dir_link_content" != "$dir_link_match/file1" ]; then echo "link_content: $link_content" echo "dir_link_content: $dir_link_content" echo "ERROR: Link content is not correct" return 1 fi fi } @test "Command 'alt' (select base)" { echo " When the command 'alt' is provided and file matches only ## Report the linking Verify correct file is linked Exit with 0 " test_alt 'base' 'false' '' } @test "Command 'alt' (select system)" { echo " When the command 'alt' is provided and file matches only ##SYSTEM Report the linking Verify correct file is linked Exit with 0 " test_alt 'system' 'false' '' } @test "Command 'alt' (select host)" { echo " When the command 'alt' is provided and file matches only ##SYSTEM.HOST Report the linking Verify correct file is linked Exit with 0 " test_alt 'host' 'false' '' } @test "Command 'alt' (select user)" { echo " When the command 'alt' is provided and file matches only ##SYSTEM.HOST.USER Report the linking Verify correct file is linked Exit with 0 " test_alt 'user' 'false' '' } @test "Command 'alt' (select none)" { echo " When the command 'alt' is provided and no file matches Verify there is no link Exit with 0 " test_alt 'none' 'false' '' } @test "Command 'alt' (select class - aaa)" { echo " When the command 'alt' is provided and file matches only ##CLASS - aaa Report the linking Verify correct file is linked Exit with 0 " GIT_DIR="$T_DIR_REPO" git config local.class aaa test_alt 'class_aaa' 'false' '' } @test "Command 'alt' (select class - zzz)" { echo " When the command 'alt' is provided and file matches only ##CLASS - zzz Report the linking Verify correct file is linked Exit with 0 " GIT_DIR="$T_DIR_REPO" git config local.class zzz test_alt 'class_zzz' 'false' '' } @test "Command 'alt' (select class - AAA)" { echo " When the command 'alt' is provided and file matches only ##CLASS - AAA Report the linking Verify correct file is linked Exit with 0 " GIT_DIR="$T_DIR_REPO" git config local.class AAA test_alt 'class_AAA' 'false' '' } @test "Command 'alt' (select class - ZZZ)" { echo " When the command 'alt' is provided and file matches only ##CLASS - ZZZ Report the linking Verify correct file is linked Exit with 0 " GIT_DIR="$T_DIR_REPO" git config local.class ZZZ test_alt 'class_ZZZ' 'false' '' } @test "Command 'auto-alt' (enabled)" { echo " When a command possibly changes the repo and auto-alt is configured true automatically process alternates report no linking (not loud) verify alternate created " test_alt 'base' 'false' 'true' } @test "Command 'auto-alt' (disabled)" { echo " When a command possibly changes the repo and auto-alt is configured false do no linking verify no links " test_alt 'base' 'false' 'false' } @test "Command 'alt' (overwrite existing link)" { echo " When the command 'alt' is provided and the link exists, and is wrong Report the linking Verify correct file is linked Exit with 0 " test_alt 'base' 'true' '' } @test "Command 'alt' (select encrypted base)" { echo " When the command 'alt' is provided and encrypted file matches only ## Report the linking Verify correct encrypted file is linked Exit with 0 " test_alt 'encrypted_base' 'false' '' } @test "Command 'alt' (select encrypted system)" { echo " When the command 'alt' is provided and encrypted file matches only ##SYSTEM Report the linking Verify correct encrypted file is linked Exit with 0 " test_alt 'encrypted_system' 'false' '' } @test "Command 'alt' (select encrypted host)" { echo " When the command 'alt' is provided and encrypted file matches only ##SYSTEM.HOST Report the linking Verify correct encrypted file is linked Exit with 0 " test_alt 'encrypted_host' 'false' '' } @test "Command 'alt' (select encrypted user)" { echo " When the command 'alt' is provided and encrypted file matches only ##SYSTEM.HOST.USER Report the linking Verify correct encrypted file is linked Exit with 0 " test_alt 'encrypted_user' 'false' '' } @test "Command 'alt' (select encrypted none)" { echo " When the command 'alt' is provided and no encrypted file matches Verify there is no link Exit with 0 " test_alt 'encrypted_none' 'false' '' } @test "Command 'alt' (override-system)" { echo " When the command 'alt' is provided and file matches only ##SYSTEM after setting local.os Report the linking Verify correct file is linked Exit with 0 " GIT_DIR="$T_DIR_REPO" git config local.os custom_system test_alt 'override_system' 'false' '' } @test "Command 'alt' (override-host)" { echo " When the command 'alt' is provided and file matches only ##SYSTEM.HOST after setting local.hostname Report the linking Verify correct file is linked Exit with 0 " GIT_DIR="$T_DIR_REPO" git config local.hostname custom_host test_alt 'override_host' 'false' '' } @test "Command 'alt' (override-user)" { echo " When the command 'alt' is provided and file matches only ##SYSTEM.HOST.USER after setting local.user Report the linking Verify correct file is linked Exit with 0 " GIT_DIR="$T_DIR_REPO" git config local.user custom_user test_alt 'override_user' 'false' '' } yadm-1.12.0/test/109_accept_encryption.bats000066400000000000000000000536761317400042700205000ustar00rootroot00000000000000load common load_fixtures status=;output=; #; populated by bats run() T_PASSWD="ExamplePassword" T_ARCHIVE_SYMMETRIC="$T_TMP/build_archive.symmetric" T_ARCHIVE_ASYMMETRIC="$T_TMP/build_archive.asymmetric" T_KEY_NAME="yadm-test1" T_KEY_FINGERPRINT="F8BBFC746C58945442349BCEBA54FFD04C599B1A" T_RECIPIENT_GOOD="[yadm]\n\tgpg-recipient = yadm-test1" T_RECIPIENT_BAD="[yadm]\n\tgpg-recipient = invalid" T_RECIPIENT_ASK="[yadm]\n\tgpg-recipient = ASK" #; use gpg1 if it's available T_GPG_PROGRAM="gpg" if command -v gpg1 >/dev/null 2>&1; then T_GPG_PROGRAM="gpg1" fi function import_keys() { "$T_GPG_PROGRAM" --import "test/test_key" >/dev/null 2>&1 || true "$T_GPG_PROGRAM" --import-ownertrust < "test/ownertrust.txt" >/dev/null 2>&1 } function remove_keys() { "$T_GPG_PROGRAM" --batch --yes --delete-secret-keys "$T_KEY_FINGERPRINT" >/dev/null 2>&1 || true "$T_GPG_PROGRAM" --batch --yes --delete-key "$T_KEY_FINGERPRINT" >/dev/null 2>&1 || true } setup() { #; start fresh destroy_tmp #; import test keys import_keys #; create a worktree & repo build_repo #; define a YADM_ENCRYPT make_parents "$T_YADM_ENCRYPT" echo -e ".ssh/*.key\n.gnupg/*.gpg" > "$T_YADM_ENCRYPT" #; create a YADM_ARCHIVE ( if cd "$T_DIR_WORK"; then # shellcheck disable=2013 # (globbing is desired) for f in $(sort "$T_YADM_ENCRYPT"); do tar rf "$T_TMP/build_archive.tar" "$f" echo "$f" >> "$T_TMP/archived_files" done fi ) #; encrypt YADM_ARCHIVE (symmetric) expect </dev/null set timeout 2; spawn "$T_GPG_PROGRAM" --yes -c --output "$T_ARCHIVE_SYMMETRIC" "$T_TMP/build_archive.tar" expect "passphrase:" {send "$T_PASSWD\n"} expect "passphrase:" {send "$T_PASSWD\n"} expect "$" foreach {pid spawnid os_error_flag value} [wait] break EOF #; encrypt YADM_ARCHIVE (asymmetric) "$T_GPG_PROGRAM" --yes --batch -e -r "$T_KEY_NAME" --output "$T_ARCHIVE_ASYMMETRIC" "$T_TMP/build_archive.tar" #; configure yadm to use T_GPG_PROGRAM git config --file="$T_YADM_CONFIG" yadm.gpg-program "$T_GPG_PROGRAM" } teardown() { remove_keys } function validate_archive() { #; inventory what's in the archive if [ "$1" = "symmetric" ]; then expect </dev/null set timeout 2; spawn bash -c "($T_GPG_PROGRAM -q -d '$T_YADM_ARCHIVE' || echo 1) | tar t | sort > $T_TMP/archive_list" expect "passphrase:" {send "$T_PASSWD\n"} expect "$" foreach {pid spawnid os_error_flag value} [wait] break EOF else "$T_GPG_PROGRAM" -q -d "$T_YADM_ARCHIVE" | tar t | sort > "$T_TMP/archive_list" fi excluded="$2" #; inventory what is expected in the archive ( if cd "$T_DIR_WORK"; then # shellcheck disable=2013 # (globbing is desired) while IFS='' read -r glob || [ -n "$glob" ]; do if [[ ! $glob =~ ^# && ! $glob =~ ^[[:space:]]*$ ]] ; then if [[ ! $glob =~ ^!(.+) ]] ; then local IFS=$'\n' for matching_file in $glob; do if [ -e "$matching_file" ]; then if [ "$matching_file" != "$excluded" ]; then if [ -d "$matching_file" ]; then echo "$matching_file/" for subfile in "$matching_file"/*; do echo "$subfile" done else echo "$matching_file" fi fi fi done fi fi done < "$T_YADM_ENCRYPT" | sort > "$T_TMP/expected_list" fi ) #; compare the archive vs expected if ! cmp -s "$T_TMP/archive_list" "$T_TMP/expected_list"; then echo "ERROR: Archive does not contain the correct files" echo "Contains:" cat "$T_TMP/archive_list" echo "Expected:" cat "$T_TMP/expected_list" return 1 fi return 0 } function validate_extraction() { #; test each file which was archived while IFS= read -r f; do local contents contents=$(cat "$T_DIR_WORK/$f") if [ "$contents" != "$f" ]; then echo "ERROR: Contents of $T_DIR_WORK/$f is incorrect" return 1 fi done < "$T_TMP/archived_files" return 0 } @test "Command 'encrypt' (missing YADM_ENCRYPT)" { echo " When 'encrypt' command is provided, and YADM_ENCRYPT does not exist Report problem Exit with 1 " #; remove YADM_ENCRYPT rm -f "$T_YADM_ENCRYPT" #; run encrypt run "${T_YADM_Y[@]}" encrypt #; validate status and output [ "$status" -eq 1 ] [[ "$output" =~ does\ not\ exist ]] } @test "Command 'encrypt' (mismatched password)" { echo " When 'encrypt' command is provided, and YADM_ENCRYPT is present and the provided passwords do not match Report problem Exit with 1 " #; run encrypt run expect <> "$T_YADM_ENCRYPT" #; run encrypt run expect < "$T_YADM_ENCRYPT" #; validate the archive validate_archive symmetric } @test "Command 'encrypt' (empty lines and space lines in YADM_ENCRYPT)" { echo " When 'encrypt' command is provided, and YADM_ENCRYPT is present Create YADM_ARCHIVE Report the archive created Archive should be valid Exit with 0 " #; add empty lines to YADM_ARCHIVE local original_encrypt original_encrypt=$(cat "$T_YADM_ENCRYPT") echo -e " \n\n \n" >> "$T_YADM_ENCRYPT" #; run encrypt run expect < "$T_YADM_ENCRYPT" #; validate the archive validate_archive symmetric } @test "Command 'encrypt' (paths with spaces/globs in YADM_ENCRYPT)" { echo " When 'encrypt' command is provided, and YADM_ENCRYPT is present Create YADM_ARCHIVE Report the archive created Archive should be valid Exit with 0 " #; add paths with spaces to YADM_ARCHIVE local original_encrypt original_encrypt=$(cat "$T_YADM_ENCRYPT") echo -e "space test/file*" >> "$T_YADM_ENCRYPT" #; run encrypt run expect <> "$T_YADM_ENCRYPT" echo -e "!.ssh/sec*.pub" >> "$T_YADM_ENCRYPT" #; run encrypt run expect <> "$T_YADM_ENCRYPT" #; run encrypt run expect < "$T_YADM_ARCHIVE" #; run encrypt run expect <> "$T_DIR_WORK/$f" done < "$T_TMP/archived_files" #; run decrypt run expect < "$T_YADM_CONFIG" #; run encrypt run "${T_YADM_Y[@]}" encrypt #; validate status and output [ "$status" -eq 1 ] [[ "$output" =~ public\ key\ not\ found ]] || [[ "$output" =~ No\ public\ key ]] [[ "$output" =~ Unable\ to\ write ]] } @test "Command 'encrypt' (asymmetric)" { echo " When 'encrypt' command is provided, and YADM_ENCRYPT is present and yadm.gpg-recipient refers to a valid private key Create YADM_ARCHIVE Report the archive created Archive should be valid Exit with 0 " #; manually set yadm.gpg-recipient in configuration make_parents "$T_YADM_CONFIG" echo -e "$T_RECIPIENT_GOOD" > "$T_YADM_CONFIG" #; run encrypt run "${T_YADM_Y[@]}" encrypt #; validate status and output [ "$status" -eq 0 ] [[ "$output" =~ Wrote\ new\ file:.+$T_YADM_ARCHIVE ]] #; validate the archive validate_archive asymmetric } @test "Command 'encrypt' (asymmetric, overwrite)" { echo " When 'encrypt' command is provided, and YADM_ENCRYPT is present and yadm.gpg-recipient refers to a valid private key and YADM_ARCHIVE already exists Overwrite YADM_ARCHIVE Report the archive created Archive should be valid Exit with 0 " #; manually set yadm.gpg-recipient in configuration make_parents "$T_YADM_CONFIG" echo -e "$T_RECIPIENT_GOOD" > "$T_YADM_CONFIG" #; Explicitly create an invalid archive echo "EXISTING ARCHIVE" > "$T_YADM_ARCHIVE" #; run encrypt run "${T_YADM_Y[@]}" encrypt #; validate status and output [ "$status" -eq 0 ] [[ "$output" =~ Wrote\ new\ file:.+$T_YADM_ARCHIVE ]] #; validate the archive validate_archive asymmetric } @test "Command 'encrypt' (asymmetric, ask)" { echo " When 'encrypt' command is provided, and YADM_ENCRYPT is present and yadm.gpg-recipient is set to ASK Ask for recipient Create YADM_ARCHIVE Report the archive created Archive should be valid Exit with 0 " #; manually set yadm.gpg-recipient in configuration make_parents "$T_YADM_CONFIG" echo -e "$T_RECIPIENT_ASK" > "$T_YADM_CONFIG" #; run encrypt run expect < "$T_YADM_CONFIG" #; run decrypt run "${T_YADM_Y[@]}" decrypt #; validate status and output [ "$status" -eq 1 ] [[ "$output" =~ does\ not\ exist ]] } @test "Command 'decrypt' (asymmetric, missing key)" { echo " When 'decrypt' command is provided, and yadm.gpg-recipient refers to a valid private key and YADM_ARCHIVE is present and the private key is not present Report problem Exit with 1 " #; manually set yadm.gpg-recipient in configuration make_parents "$T_YADM_CONFIG" echo -e "$T_RECIPIENT_GOOD" > "$T_YADM_CONFIG" #; use the asymmetric archive cp -f "$T_ARCHIVE_ASYMMETRIC" "$T_YADM_ARCHIVE" #; remove the private key remove_keys #; run decrypt run "${T_YADM_Y[@]}" decrypt #; validate status and output [ "$status" -eq 1 ] [[ "$output" =~ decryption\ failed ]] [[ "$output" =~ Unable\ to\ extract ]] } @test "Command 'decrypt' -l (asymmetric, missing key)" { echo " When 'decrypt' command is provided, and '-l' is provided, and yadm.gpg-recipient refers to a valid private key and YADM_ARCHIVE is present and the private key is not present Report problem Exit with 1 " #; manually set yadm.gpg-recipient in configuration make_parents "$T_YADM_CONFIG" echo -e "$T_RECIPIENT_GOOD" > "$T_YADM_CONFIG" #; use the asymmetric archive cp -f "$T_ARCHIVE_ASYMMETRIC" "$T_YADM_ARCHIVE" #; remove the private key remove_keys #; run decrypt run "${T_YADM_Y[@]}" decrypt #; validate status and output [ "$status" -eq 1 ] [[ "$output" =~ decryption\ failed ]] [[ "$output" =~ Unable\ to\ extract ]] } @test "Command 'decrypt' (asymmetric)" { echo " When 'decrypt' command is provided, and yadm.gpg-recipient refers to a valid private key and YADM_ARCHIVE is present Report the data created Data should be valid Exit with 0 " #; manually set yadm.gpg-recipient in configuration make_parents "$T_YADM_CONFIG" echo -e "$T_RECIPIENT_GOOD" > "$T_YADM_CONFIG" #; use the asymmetric archive cp -f "$T_ARCHIVE_ASYMMETRIC" "$T_YADM_ARCHIVE" #; empty the worktree rm -rf "$T_DIR_WORK" mkdir -p "$T_DIR_WORK" #; run decrypt run "${T_YADM_Y[@]}" decrypt #; validate status and output [ "$status" -eq 0 ] [[ "$output" =~ All\ files\ decrypted ]] #; validate the extracted files validate_extraction } @test "Command 'decrypt' (asymmetric, overwrite)" { echo " When 'decrypt' command is provided, and yadm.gpg-recipient refers to a valid private key and YADM_ARCHIVE is present and archived content already exists Report the data overwritten Data should be valid Exit with 0 " #; manually set yadm.gpg-recipient in configuration make_parents "$T_YADM_CONFIG" echo -e "$T_RECIPIENT_GOOD" > "$T_YADM_CONFIG" #; use the asymmetric archive cp -f "$T_ARCHIVE_ASYMMETRIC" "$T_YADM_ARCHIVE" #; alter the values of the archived files while IFS= read -r f; do echo "changed" >> "$T_DIR_WORK/$f" done < "$T_TMP/archived_files" #; run decrypt run "${T_YADM_Y[@]}" decrypt #; validate status and output [ "$status" -eq 0 ] [[ "$output" =~ All\ files\ decrypted ]] #; validate the extracted files validate_extraction } @test "Command 'decrypt' -l (asymmetric)" { echo " When 'decrypt' command is provided, and '-l' is provided, and yadm.gpg-recipient refers to a valid private key and YADM_ARCHIVE is present Report the contents of YADM_ARCHIVE Exit with 0 " #; manually set yadm.gpg-recipient in configuration make_parents "$T_YADM_CONFIG" echo -e "$T_RECIPIENT_GOOD" > "$T_YADM_CONFIG" #; use the asymmetric archive cp -f "$T_ARCHIVE_ASYMMETRIC" "$T_YADM_ARCHIVE" #; run decrypt run "${T_YADM_Y[@]}" decrypt -l #; validate status [ "$status" -eq 0 ] #; validate every file is listed in output while IFS= read -r f; do if [[ ! "$output" =~ $f ]]; then echo "ERROR: Did not find '$f' in output" return 1 fi done < "$T_TMP/archived_files" } yadm-1.12.0/test/110_accept_perms.bats000066400000000000000000000070061317400042700174060ustar00rootroot00000000000000load common load_fixtures status=;output=; #; populated by bats run() setup() { destroy_tmp build_repo } function is_restricted() { local p for p in "${restricted[@]}"; do [ "$p" = "$1" ] && return 0; done return 1 } function validate_perms() { local perms="$*" #; determine which paths should have restricted permissions restricted=() local p for p in $perms; do case $p in ssh) restricted=("${restricted[@]}" $T_DIR_WORK/.ssh $T_DIR_WORK/.ssh/*) ;; gpg) restricted=("${restricted[@]}" $T_DIR_WORK/.gnupg $T_DIR_WORK/.gnupg/*) ;; *) restricted=("${restricted[@]}" $T_DIR_WORK/$p) ;; esac done #; validate permissions of each path in the worktere local testpath while IFS= read -r -d '' testpath; do local perm_regex="....rwxrwx" if is_restricted "$testpath"; then perm_regex="....------" fi test_perms "$testpath" "$perm_regex" || return 1 done < <(find "$T_DIR_WORK" -print0) } @test "Command 'perms'" { echo " When the command 'perms' is provided Update permissions for ssh/gpg Verify correct permissions Exit with 0 " #; run perms run "${T_YADM_Y[@]}" perms #; validate status and output [ "$status" -eq 0 ] [ "$output" = "" ] #; validate permissions validate_perms ssh gpg } @test "Command 'perms' (with encrypt)" { echo " When the command 'perms' is provided And YADM_ENCRYPT is present Update permissions for ssh/gpg/encrypt Support comments in YADM_ENCRYPT Verify correct permissions Exit with 0 " #; this version has a comment in it echo -e "#.vimrc\n.tmux.conf\n.hammerspoon/*\n!.tmux.conf" > "$T_YADM_ENCRYPT" #; run perms run "${T_YADM_Y[@]}" perms #; validate status and output [ "$status" -eq 0 ] [ "$output" = "" ] #; validate permissions validate_perms ssh gpg ".hammerspoon/*" } @test "Command 'perms' (ssh-perms=false)" { echo " When the command 'perms' is provided And yadm.ssh-perms=false Update permissions for gpg only Verify correct permissions Exit with 0 " #; configure yadm.ssh-perms git config --file="$T_YADM_CONFIG" "yadm.ssh-perms" "false" #; run perms run "${T_YADM_Y[@]}" perms #; validate status and output [ "$status" -eq 0 ] [ "$output" = "" ] #; validate permissions validate_perms gpg } @test "Command 'perms' (gpg-perms=false)" { echo " When the command 'perms' is provided And yadm.gpg-perms=false Update permissions for ssh only Verify correct permissions Exit with 0 " #; configure yadm.gpg-perms git config --file="$T_YADM_CONFIG" "yadm.gpg-perms" "false" #; run perms run "${T_YADM_Y[@]}" perms #; validate status and output [ "$status" -eq 0 ] [ "$output" = "" ] #; validate permissions validate_perms ssh } @test "Command 'auto-perms' (enabled)" { echo " When a command possibly changes the repo Update permissions for ssh/gpg Verify correct permissions " #; run status run "${T_YADM_Y[@]}" status #; validate status [ "$status" -eq 0 ] #; validate permissions validate_perms ssh gpg } @test "Command 'auto-perms' (disabled)" { echo " When a command possibly changes the repo And yadm.auto-perms=false Take no action Verify permissions are intact " #; configure yadm.auto-perms git config --file="$T_YADM_CONFIG" "yadm.auto-perms" "false" #; run status run "${T_YADM_Y[@]}" status #; validate status [ "$status" -eq 0 ] #; validate permissions validate_perms } yadm-1.12.0/test/111_accept_wildcard_alt.bats000066400000000000000000000143051317400042700207120ustar00rootroot00000000000000load common load_fixtures status=;output=; #; populated by bats run() IN_REPO=(wild*) export TEST_TREE_WITH_WILD=1 setup() { destroy_tmp build_repo "${IN_REPO[@]}" } function test_alt() { local link_name="$1" local link_match="$2" #; run yadm alt run "${T_YADM_Y[@]}" alt #; validate status and output if [ "$status" != 0 ] || [[ ! "$output" =~ Linking.+$link_name ]]; then echo "OUTPUT:$output" echo "ERROR: Could not confirm status and output of alt command" return 1; fi #; correct link should be present local link_content link_content=$(cat "$T_DIR_WORK/$link_name") if [ "$link_content" != "$link_match" ]; then echo "OUTPUT:$output" echo "ERROR: Link content is not correct" return 1 fi } @test "Command 'alt' (wild none)" { echo " When the command 'alt' is provided and file matches only ## Report the linking Verify correct file is linked Exit with 0 " test_alt 'wild-none' 'wild-none##' } @test "Command 'alt' (wild system)" { echo " When the command 'alt' is provided and file matches only ##SYSTEM with possible wildcards Report the linking Verify correct file is linked Exit with 0 " for WILD_S in 'local' 'wild'; do local s_base="wild-system-$WILD_S" case $WILD_S in local) WILD_S="$T_SYS";; wild) WILD_S="%";; esac local match="${s_base}##${WILD_S}" echo test_alt "$s_base" "$match" test_alt "$s_base" "$match" done } @test "Command 'alt' (wild class)" { echo " When the command 'alt' is provided and file matches only ##CLASS with possible wildcards Report the linking Verify correct file is linked Exit with 0 " GIT_DIR="$T_DIR_REPO" git config local.class set_class for WILD_C in 'local' 'wild'; do local c_base="wild-class-$WILD_C" case $WILD_C in local) WILD_C="set_class";; wild) WILD_C="%";; esac local match="${c_base}##${WILD_C}" echo test_alt "$c_base" "$match" test_alt "$c_base" "$match" done } @test "Command 'alt' (wild host)" { echo " When the command 'alt' is provided and file matches only ##SYSTEM.HOST with possible wildcards Report the linking Verify correct file is linked Exit with 0 " for WILD_S in 'local' 'wild'; do local s_base="wild-host-$WILD_S" case $WILD_S in local) WILD_S="$T_SYS";; wild) WILD_S="%";; esac for WILD_H in 'local' 'wild'; do local h_base="${s_base}-$WILD_H" case $WILD_H in local) WILD_H="$T_HOST";; wild) WILD_H="%";; esac local match="${h_base}##${WILD_S}.${WILD_H}" echo test_alt "$h_base" "$match" test_alt "$h_base" "$match" done done } @test "Command 'alt' (wild class-system)" { echo " When the command 'alt' is provided and file matches only ##CLASS.SYSTEM with possible wildcards Report the linking Verify correct file is linked Exit with 0 " GIT_DIR="$T_DIR_REPO" git config local.class set_class for WILD_C in 'local' 'wild'; do local c_base="wild-class-system-$WILD_C" case $WILD_C in local) WILD_C="set_class";; wild) WILD_C="%";; esac for WILD_S in 'local' 'wild'; do local s_base="${c_base}-$WILD_S" case $WILD_S in local) WILD_S="$T_SYS";; wild) WILD_S="%";; esac local match="${s_base}##${WILD_C}.${WILD_S}" echo test_alt "$s_base" "$match" test_alt "$s_base" "$match" done done } @test "Command 'alt' (wild user)" { echo " When the command 'alt' is provided and file matches only ##SYSTEM.HOST.USER with possible wildcards Report the linking Verify correct file is linked Exit with 0 " for WILD_S in 'local' 'wild'; do local s_base="wild-user-$WILD_S" case $WILD_S in local) WILD_S="$T_SYS";; wild) WILD_S="%";; esac for WILD_H in 'local' 'wild'; do local h_base="${s_base}-$WILD_H" case $WILD_H in local) WILD_H="$T_HOST";; wild) WILD_H="%";; esac for WILD_U in 'local' 'wild'; do local u_base="${h_base}-$WILD_U" case $WILD_U in local) WILD_U="$T_USER";; wild) WILD_U="%";; esac local match="${u_base}##${WILD_S}.${WILD_H}.${WILD_U}" echo test_alt "$u_base" "$match" test_alt "$u_base" "$match" done done done } @test "Command 'alt' (wild class-system-host)" { echo " When the command 'alt' is provided and file matches only ##CLASS.SYSTEM.HOST with possible wildcards Report the linking Verify correct file is linked Exit with 0 " GIT_DIR="$T_DIR_REPO" git config local.class set_class for WILD_C in 'local' 'wild'; do local c_base="wild-class-system-host-$WILD_C" case $WILD_C in local) WILD_C="set_class";; wild) WILD_C="%";; esac for WILD_S in 'local' 'wild'; do local s_base="${c_base}-$WILD_S" case $WILD_S in local) WILD_S="$T_SYS";; wild) WILD_S="%";; esac for WILD_H in 'local' 'wild'; do local h_base="${s_base}-$WILD_H" case $WILD_H in local) WILD_H="$T_HOST";; wild) WILD_H="%";; esac local match="${h_base}##${WILD_C}.${WILD_S}.${WILD_H}" echo test_alt "$h_base" "$match" test_alt "$h_base" "$match" done done done } @test "Command 'alt' (wild class-system-host-user)" { echo " When the command 'alt' is provided and file matches only ##CLASS.SYSTEM.HOST.USER with possible wildcards Report the linking Verify correct file is linked Exit with 0 " GIT_DIR="$T_DIR_REPO" git config local.class set_class for WILD_C in 'local' 'wild'; do local c_base="wild-class-system-host-user-$WILD_C" case $WILD_C in local) WILD_C="set_class";; wild) WILD_C="%";; esac for WILD_S in 'local' 'wild'; do local s_base="${c_base}-$WILD_S" case $WILD_S in local) WILD_S="$T_SYS";; wild) WILD_S="%";; esac for WILD_H in 'local' 'wild'; do local h_base="${s_base}-$WILD_H" case $WILD_H in local) WILD_H="$T_HOST";; wild) WILD_H="%";; esac for WILD_U in 'local' 'wild'; do local u_base="${h_base}-$WILD_U" case $WILD_U in local) WILD_U="$T_USER";; wild) WILD_U="%";; esac local match="${u_base}##${WILD_C}.${WILD_S}.${WILD_H}.${WILD_U}" echo test_alt "$u_base" "$match" test_alt "$u_base" "$match" done done done done } yadm-1.12.0/test/112_accept_bootstrap.bats000066400000000000000000000030571317400042700203010ustar00rootroot00000000000000load common load_fixtures status=;output=; #; populated by bats run() setup() { destroy_tmp build_repo } @test "Command 'bootstrap' (missing file)" { echo " When 'bootstrap' command is provided, and the bootstrap file is missing Report error Exit with 1 " #; run clone run "${T_YADM_Y[@]}" bootstrap echo "STATUS:$status" echo "OUTPUT:$output" #; validate status and output [[ "$output" =~ Cannot\ execute\ bootstrap ]] [ "$status" -eq 1 ] } @test "Command 'bootstrap' (not executable)" { echo " When 'bootstrap' command is provided, and the bootstrap file is present but is not executable Report error Exit with 1 " touch "$T_YADM_BOOTSTRAP" #; run clone run "${T_YADM_Y[@]}" bootstrap echo "STATUS:$status" echo "OUTPUT:$output" #; validate status and output [[ "$output" =~ is\ not\ an\ executable\ program ]] [ "$status" -eq 1 ] } @test "Command 'bootstrap' (bootstrap run)" { echo " When 'bootstrap' command is provided, and the bootstrap file is present and is executable Announce the execution Execute bootstrap Exit with the exit code of bootstrap " { echo "#!/bin/bash" echo "echo Bootstrap successful" echo "exit 123" } > "$T_YADM_BOOTSTRAP" chmod a+x "$T_YADM_BOOTSTRAP" #; run clone run "${T_YADM_Y[@]}" bootstrap echo "STATUS:$status" echo "OUTPUT:$output" #; validate status and output [[ "$output" =~ Executing\ $T_YADM_BOOTSTRAP ]] [[ "$output" =~ Bootstrap\ successful ]] [ "$status" -eq 123 ] } yadm-1.12.0/test/113_accept_jinja_alt.bats000066400000000000000000000124311317400042700202140ustar00rootroot00000000000000load common load_fixtures status=;output=; #; populated by bats run() IN_REPO=(alt* "dir one") export TEST_TREE_WITH_ALT=1 setup() { destroy_tmp build_repo "${IN_REPO[@]}" echo "excluded-encrypt##yadm.j2" > "$T_YADM_ENCRYPT" echo "included-encrypt##yadm.j2" >> "$T_YADM_ENCRYPT" echo "!excluded-encrypt*" >> "$T_YADM_ENCRYPT" echo "included-encrypt" > "$T_DIR_WORK/included-encrypt##yadm.j2" echo "excluded-encrypt" > "$T_DIR_WORK/excluded-encrypt##yadm.j2" } function test_alt() { local alt_type="$1" local test_overwrite="$2" local auto_alt="$3" #; detemine test parameters case $alt_type in base) real_name="alt-jinja" file_content_match="-${T_SYS}-${T_HOST}-${T_USER}-${T_DISTRO}" ;; override_all) real_name="alt-jinja" file_content_match="custom_class-custom_system-custom_host-custom_user-${T_DISTRO}" ;; encrypt) real_name="included-encrypt" file_content_match="included-encrypt" missing_name="excluded-encrypt" ;; esac if [ "$test_overwrite" = "true" ] ; then #; create incorrect links (to overwrite) echo "BAD_CONTENT" "$T_DIR_WORK/$real_name" else #; verify real file doesn't already exist if [ -e "$T_DIR_WORK/$real_name" ] ; then echo "ERROR: real file already exists before running yadm" return 1 fi fi #; configure yadm.auto_alt=false if [ "$auto_alt" = "false" ]; then git config --file="$T_YADM_CONFIG" yadm.auto-alt false fi #; run yadm (alt or status) if [ -z "$auto_alt" ]; then run "${T_YADM_Y[@]}" alt #; validate status and output if [ "$status" != 0 ] || [[ ! "$output" =~ Creating.+$real_name ]]; then echo "OUTPUT:$output" echo "ERROR: Could not confirm status and output of alt command" return 1; fi else #; running any passed through Git command should trigger auto-alt run "${T_YADM_Y[@]}" status if [ -n "$auto_alt" ] && [[ "$output" =~ Creating.+$real_name ]]; then echo "ERROR: Reporting of jinja processing should not happen" return 1 fi fi if [ -n "$missing_name" ] && [ -f "$T_DIR_WORK/$missing_name" ]; then echo "ERROR: File should not have been created '$missing_name'" return 1 fi #; validate link content if [[ "$alt_type" =~ none ]] || [ "$auto_alt" = "false" ]; then #; no real file should be present if [ -L "$T_DIR_WORK/$real_name" ] ; then echo "ERROR: Real file should not exist" return 1 fi else #; correct real file should be present local file_content file_content=$(cat "$T_DIR_WORK/$real_name") if [ "$file_content" != "$file_content_match" ]; then echo "file_content: ${file_content}" echo "expected_content: ${file_content_match}" echo "ERROR: Link content is not correct" return 1 fi fi } @test "Command 'alt' (envtpl missing)" { echo " When the command 'alt' is provided and file matches ##yadm.j2 Report jinja template as unprocessed Exit with 0 " # shellcheck source=/dev/null YADM_TEST=1 source "$T_YADM" process_global_args -Y "$T_DIR_YADM" set_operating_system configure_paths status=0 output=$( ENVTPL_PROGRAM='envtpl_missing' main alt ) || { status=$? true } [ $status -eq 0 ] [[ "$output" =~ envtpl.not.available ]] } @test "Command 'alt' (select jinja)" { echo " When the command 'alt' is provided and file matches ##yadm.j2 Report jinja template processing Verify that the correct content is written Exit with 0 " test_alt 'base' 'false' '' } @test "Command 'auto-alt' (enabled)" { echo " When a command possibly changes the repo and auto-alt is configured true and file matches ##yadm.j2 automatically process alternates report no linking (not loud) Verify that the correct content is written " test_alt 'base' 'false' 'true' } @test "Command 'auto-alt' (disabled)" { echo " When a command possibly changes the repo and auto-alt is configured false and file matches ##yadm.j2 Report no jinja template processing Verify no content " test_alt 'base' 'false' 'false' } @test "Command 'alt' (overwrite existing content)" { echo " When the command 'alt' is provided and file matches ##yadm.j2 and the real file exists, and is wrong Report jinja template processing Verify that the correct content is written Exit with 0 " test_alt 'base' 'true' '' } @test "Command 'alt' (overwritten settings)" { echo " When the command 'alt' is provided and file matches ##yadm.j2 after setting local.* Report jinja template processing Verify that the correct content is written Exit with 0 " GIT_DIR="$T_DIR_REPO" git config local.os custom_system GIT_DIR="$T_DIR_REPO" git config local.user custom_user GIT_DIR="$T_DIR_REPO" git config local.hostname custom_host GIT_DIR="$T_DIR_REPO" git config local.class custom_class test_alt 'override_all' 'false' '' } @test "Command 'alt' (select jinja within .yadm/encrypt)" { echo " When the command 'alt' is provided and file matches ##yadm.j2 within .yadm/encrypt and file excluded within .yadm/encrypt Report jinja template processing Verify that the correct content is written Exit with 0 " test_alt 'encrypt' 'false' '' } yadm-1.12.0/test/114_accept_enter.bats000066400000000000000000000025431317400042700174020ustar00rootroot00000000000000load common load_fixtures status=;output=; #; populated by bats run() setup() { build_repo } @test "Command 'enter' (SHELL not set)" { echo " When 'enter' command is provided, And SHELL is not set Report error Exit with 1 " SHELL= export SHELL run "${T_YADM_Y[@]}" enter #; validate status and output [ $status -eq 1 ] [[ "$output" =~ does.not.refer.to.an.executable ]] } @test "Command 'enter' (SHELL not executable)" { echo " When 'enter' command is provided, And SHELL is not executable Report error Exit with 1 " touch "$T_TMP/badshell" SHELL="$T_TMP/badshell" export SHELL run "${T_YADM_Y[@]}" enter #; validate status and output [ $status -eq 1 ] [[ "$output" =~ does.not.refer.to.an.executable ]] } @test "Command 'enter' (SHELL executable)" { echo " When 'enter' command is provided, And SHELL is set Execute SHELL command Expose GIT variables Set prompt variables Announce entering/leaving shell Exit with 0 " SHELL=$(command -v env) export SHELL run "${T_YADM_Y[@]}" enter #; validate status and output [ $status -eq 0 ] [[ "$output" =~ GIT_DIR= ]] [[ "$output" =~ PROMPT=yadm.shell ]] [[ "$output" =~ PS1=yadm.shell ]] [[ "$output" =~ Entering.yadm.repo ]] [[ "$output" =~ Leaving.yadm.repo ]] } yadm-1.12.0/test/115_accept_introspect.bats000066400000000000000000000042641317400042700204620ustar00rootroot00000000000000load common load_fixtures status=;output=; #; populated by bats run() function count_introspect() { local category="$1" local expected_status="$2" local expected_words="$3" local expected_regex="$4" run "${T_YADM_Y[@]}" introspect "$category" local output_words output_words=$(wc -w <<< "$output") if [ "$status" -ne "$expected_status" ]; then echo "ERROR: Unexpected exit code (expected $expected_status, got $status)" return 1; fi if [ "$output_words" -ne "$expected_words" ]; then echo "ERROR: Unexpected number of output words (expected $expected_words, got $output_words)" return 1; fi if [ -n "$expected_regex" ]; then if [[ ! "$output" =~ $expected_regex ]]; then echo "OUTPUT:$output" echo "ERROR: Output does not match regex: $expected_regex" return 1; fi fi } @test "Command 'introspect' (no category)" { echo " When 'introspect' command is provided, And no category is provided Produce no output Exit with 0 " count_introspect "" 0 0 } @test "Command 'introspect' (invalid category)" { echo " When 'introspect' command is provided, And an invalid category is provided Produce no output Exit with 0 " count_introspect "invalid_cat" 0 0 } @test "Command 'introspect' (commands)" { echo " When 'introspect' command is provided, And category 'commands' is provided Produce command list Exit with 0 " count_introspect "commands" 0 15 'version' } @test "Command 'introspect' (configs)" { echo " When 'introspect' command is provided, And category 'configs' is provided Produce switch list Exit with 0 " count_introspect "configs" 0 13 'yadm\.auto-alt' } @test "Command 'introspect' (repo)" { echo " When 'introspect' command is provided, And category 'repo' is provided Output repo Exit with 0 " count_introspect "repo" 0 1 "$T_DIR_REPO" } @test "Command 'introspect' (switches)" { echo " When 'introspect' command is provided, And category 'switches' is provided Produce switch list Exit with 0 " count_introspect "switches" 0 7 '--yadm-dir' } yadm-1.12.0/test/116_accept_cygwin_copy.bats000066400000000000000000000061341317400042700206210ustar00rootroot00000000000000load common load_fixtures status=;output=; #; populated by bats run() IN_REPO=(alt*) export TEST_TREE_WITH_CYGWIN=1 export SIMULATED_CYGWIN="CYGWIN_NT-6.1-WOW64" setup() { destroy_tmp build_repo "${IN_REPO[@]}" } test_alt() { local cygwin_copy="$1" local is_cygwin="$2" local expect_link="$3" local preexisting_link="$4" case "$cygwin_copy" in true|false) git config --file="$T_YADM_CONFIG" "yadm.cygwin-copy" "$cygwin_copy" ;; esac if [ "$is_cygwin" = "true" ]; then echo '#!/bin/sh' > "$T_TMP/uname" echo "echo $SIMULATED_CYGWIN" >> "$T_TMP/uname" chmod a+x "$T_TMP/uname" fi local expected_content expected_content="$T_DIR_WORK/alt-test##$(PATH="$T_TMP:$PATH" uname -s)" if [ "$preexisting_link" = 'symlink' ]; then ln -s "$expected_content" "$T_DIR_WORK/alt-test" elif [ "$preexisting_link" = 'file' ]; then touch "$T_DIR_WORK/alt-test" fi PATH="$T_TMP:$PATH" run "${T_YADM_Y[@]}" alt echo "Alt output:$output" echo "Alt status:$status" if [ -L "$T_DIR_WORK/alt-test" ] && [ "$expect_link" != 'true' ] ; then echo "ERROR: Alt should be a simple file, but isn't" return 1 fi if [ ! -L "$T_DIR_WORK/alt-test" ] && [ "$expect_link" = 'true' ] ; then echo "ERROR: Alt should use symlink, but doesn't" return 1 fi if ! diff "$T_DIR_WORK/alt-test" "$expected_content"; then echo "ERROR: Alt contains different data than expected" return 1 fi } @test "Option 'yadm.cygwin-copy' (unset, non-cygwin)" { echo " When the option 'yadm.cygwin-copy' is unset and the OS is not CYGWIN Verify alternate is a symlink " test_alt 'unset' 'false' 'true' } @test "Option 'yadm.cygwin-copy' (true, non-cygwin)" { echo " When the option 'yadm.cygwin-copy' is true and the OS is not CYGWIN Verify alternate is a symlink " test_alt 'true' 'false' 'true' } @test "Option 'yadm.cygwin-copy' (false, non-cygwin)" { echo " When the option 'yadm.cygwin-copy' is false and the OS is not CYGWIN Verify alternate is a symlink " test_alt 'false' 'false' 'true' } @test "Option 'yadm.cygwin-copy' (unset, cygwin)" { echo " When the option 'yadm.cygwin-copy' is unset and the OS is CYGWIN Verify alternate is a symlink " test_alt 'unset' 'true' 'true' } @test "Option 'yadm.cygwin-copy' (true, cygwin)" { echo " When the option 'yadm.cygwin-copy' is true and the OS is CYGWIN Verify alternate is a copy " test_alt 'true' 'true' 'false' } @test "Option 'yadm.cygwin-copy' (false, cygwin)" { echo " When the option 'yadm.cygwin-copy' is false and the OS is CYGWIN Verify alternate is a symlink " test_alt 'false' 'true' 'true' } @test "Option 'yadm.cygwin-copy' (preexisting symlink) " { echo " When the option 'yadm.cygwin-copy' is true and the OS is CYGWIN Verify alternate is a copy " test_alt 'true' 'true' 'false' 'symlink' } @test "Option 'yadm.cygwin-copy' (preexisting file) " { echo " When the option 'yadm.cygwin-copy' is true and the OS is CYGWIN Verify alternate is a copy " test_alt 'true' 'true' 'false' 'file' } yadm-1.12.0/test/117_accept_hooks.bats000066400000000000000000000071321317400042700174120ustar00rootroot00000000000000load common load_fixtures status=;output=; #; populated by bats run() version_regex="yadm [[:digit:]]+\.[[:digit:]]+\.[[:digit:]]+" setup() { destroy_tmp build_repo mkdir -p "$T_DIR_HOOKS" } function create_hook() { hook_name="$1" hook_exit="$2" hook_file="$T_DIR_HOOKS/$hook_name" { echo "#!/bin/sh" echo "echo ran $hook_name" echo "env" echo "exit $hook_exit" } > "$hook_file" chmod a+x "$hook_file" } @test "Hooks (no hook)" { echo " When no hook is present do no not run the hook run command Exit with 0 " #; run yadm with no command run "${T_YADM_Y[@]}" version [ $status -eq 0 ] [[ "$output" =~ $version_regex ]] } @test "Hooks (successful pre hook)" { echo " When hook is present run hook run command Exit with 0 " create_hook "pre_version" "0" #; run yadm with no command run "${T_YADM_Y[@]}" version [ $status -eq 0 ] [[ "$output" =~ ran\ pre_version ]] [[ "$output" =~ $version_regex ]] } @test "Hooks (unsuccessful pre hook)" { echo " When hook is present run hook report hook failure do no not run command Exit with 13 " create_hook "pre_version" "13" #; run yadm with no command run "${T_YADM_Y[@]}" version [ $status -eq 13 ] [[ "$output" =~ ran\ pre_version ]] [[ "$output" =~ pre_version\ was\ not\ successful ]] [[ ! "$output" =~ $version_regex ]] } @test "Hooks (successful post hook)" { echo " When hook is present run command run hook Exit with 0 " create_hook "post_version" "0" #; run yadm with no command run "${T_YADM_Y[@]}" version [ $status -eq 0 ] [[ "$output" =~ $version_regex ]] [[ "$output" =~ ran\ post_version ]] } @test "Hooks (unsuccessful post hook)" { echo " When hook is present run command run hook Exit with 0 " create_hook "post_version" "13" #; run yadm with no command run "${T_YADM_Y[@]}" version [ $status -eq 0 ] [[ "$output" =~ $version_regex ]] [[ "$output" =~ ran\ post_version ]] } @test "Hooks (successful pre hook + post hook)" { echo " When hook is present run hook run command run hook Exit with 0 " create_hook "pre_version" "0" create_hook "post_version" "0" #; run yadm with no command run "${T_YADM_Y[@]}" version [ $status -eq 0 ] [[ "$output" =~ ran\ pre_version ]] [[ "$output" =~ $version_regex ]] [[ "$output" =~ ran\ post_version ]] } @test "Hooks (unsuccessful pre hook + post hook)" { echo " When hook is present run hook report hook failure do no not run command do no not run post hook Exit with 13 " create_hook "pre_version" "13" create_hook "post_version" "0" #; run yadm with no command run "${T_YADM_Y[@]}" version [ $status -eq 13 ] [[ "$output" =~ ran\ pre_version ]] [[ "$output" =~ pre_version\ was\ not\ successful ]] [[ ! "$output" =~ $version_regex ]] [[ ! "$output" =~ ran\ post_version ]] } @test "Hooks (environment variables)" { echo " When hook is present run command run hook hook should have access to environment variables Exit with 0 " create_hook "post_version" "0" #; run yadm with no command run "${T_YADM_Y[@]}" version extra_args [ $status -eq 0 ] [[ "$output" =~ $version_regex ]] [[ "$output" =~ ran\ post_version ]] [[ "$output" =~ YADM_HOOK_COMMAND=version ]] [[ "$output" =~ YADM_HOOK_EXIT=0 ]] [[ "$output" =~ YADM_HOOK_FULL_COMMAND=version\ extra_args ]] [[ "$output" =~ YADM_HOOK_REPO=${T_DIR_REPO} ]] [[ "$output" =~ YADM_HOOK_WORK=${T_DIR_WORK} ]] } yadm-1.12.0/test/118_accept_assert_private_dirs.bats000066400000000000000000000051231317400042700223420ustar00rootroot00000000000000load common load_fixtures status=;output=; #; populated by bats run() IN_REPO=(.bash_profile .vimrc) setup() { destroy_tmp build_repo "${IN_REPO[@]}" rm -rf "$T_DIR_WORK" mkdir -p "$T_DIR_WORK" } @test "Private dirs (private dirs missing)" { echo " When a git command is run And private directories are missing Create private directories prior to command " #; confirm directories are missing at start [ ! -e "$T_DIR_WORK/.gnupg" ] [ ! -e "$T_DIR_WORK/.ssh" ] #; run status export DEBUG=yes run "${T_YADM_Y[@]}" status #; validate status and output [ "$status" -eq 0 ] [[ "$output" =~ On\ branch\ master ]] #; confirm private directories are created [ -d "$T_DIR_WORK/.gnupg" ] test_perms "$T_DIR_WORK/.gnupg" "drwx------" [ -d "$T_DIR_WORK/.ssh" ] test_perms "$T_DIR_WORK/.ssh" "drwx------" #; confirm directories are created before command is run [[ "$output" =~ Creating.+/.gnupg/.+Creating.+/.ssh/.+Running\ git\ command\ git\ status ]] } @test "Private dirs (private dirs missing / yadm.auto-private-dirs=false)" { echo " When a git command is run And private directories are missing But auto-private-dirs is false Do not create private dirs " #; confirm directories are missing at start [ ! -e "$T_DIR_WORK/.gnupg" ] [ ! -e "$T_DIR_WORK/.ssh" ] #; set configuration run "${T_YADM_Y[@]}" config --bool "yadm.auto-private-dirs" "false" #; run status run "${T_YADM_Y[@]}" status #; validate status and output [ "$status" -eq 0 ] [[ "$output" =~ On\ branch\ master ]] #; confirm private directories are not created [ ! -e "$T_DIR_WORK/.gnupg" ] [ ! -e "$T_DIR_WORK/.ssh" ] } @test "Private dirs (private dirs exist / yadm.auto-perms=false)" { echo " When a git command is run And private directories exist And yadm is configured not to auto update perms Do not alter directories " #shellcheck disable=SC2174 mkdir -m 0777 -p "$T_DIR_WORK/.gnupg" "$T_DIR_WORK/.ssh" #; confirm directories are preset and open [ -d "$T_DIR_WORK/.gnupg" ] test_perms "$T_DIR_WORK/.gnupg" "drwxrwxrwx" [ -d "$T_DIR_WORK/.ssh" ] test_perms "$T_DIR_WORK/.ssh" "drwxrwxrwx" #; set configuration run "${T_YADM_Y[@]}" config --bool "yadm.auto-perms" "false" #; run status run "${T_YADM_Y[@]}" status #; validate status and output [ "$status" -eq 0 ] [[ "$output" =~ On\ branch\ master ]] #; confirm directories are still preset and open [ -d "$T_DIR_WORK/.gnupg" ] test_perms "$T_DIR_WORK/.gnupg" "drwxrwxrwx" [ -d "$T_DIR_WORK/.ssh" ] test_perms "$T_DIR_WORK/.ssh" "drwxrwxrwx" } yadm-1.12.0/test/common.bash000066400000000000000000000350461317400042700156410ustar00rootroot00000000000000 #; common fixtures function load_fixtures() { export DEFAULT_YADM_DIR="$HOME/.yadm" export DEFAULT_REPO="repo.git" export DEFAULT_CONFIG="config" export DEFAULT_ENCRYPT="encrypt" export DEFAULT_ARCHIVE="files.gpg" export DEFAULT_BOOTSTRAP="bootstrap" export T_YADM="$PWD/yadm" export T_TMP="$BATS_TMPDIR/ytmp" export T_DIR_YADM="$T_TMP/.yadm" export T_DIR_WORK="$T_TMP/yadm-work" export T_DIR_REPO="$T_DIR_YADM/repo.git" export T_DIR_HOOKS="$T_DIR_YADM/hooks" export T_YADM_CONFIG="$T_DIR_YADM/config" export T_YADM_ENCRYPT="$T_DIR_YADM/encrypt" export T_YADM_ARCHIVE="$T_DIR_YADM/files.gpg" export T_YADM_BOOTSTRAP="$T_DIR_YADM/bootstrap" export T_YADM_Y T_YADM_Y=( "$T_YADM" -Y "$T_DIR_YADM" ) export T_SYS T_SYS=$(uname -s) export T_HOST T_HOST=$(hostname -s) export T_USER T_USER=$(id -u -n) export T_DISTRO T_DISTRO=$(lsb_release -si 2>/dev/null || true) } function configure_git() { (git config user.name || git config --global user.name 'test') >/dev/null (git config user.email || git config --global user.email 'test@test.test') > /dev/null } function make_parents() { local parent_dir parent_dir=$(dirname "$@") mkdir -p "$parent_dir" } function test_perms() { local test_path="$1" local regex="$2" local ls ls=$(ls -ld "$test_path") local perms="${ls:0:10}" if [[ ! $perms =~ $regex ]]; then echo "ERROR: Found permissions $perms for $test_path" return 1 fi return 0 } function test_repo_attribute() { local repo_dir="$1" local attribute="$2" local expected="$3" local actual actual=$(GIT_DIR="$repo_dir" git config --local "$attribute") if [ "$actual" != "$expected" ]; then echo "ERROR: repo attribute $attribute set to $actual" return 1 fi return 0 } #; create worktree at path function create_worktree() { local DIR_WORKTREE="$1" if [ -z "$DIR_WORKTREE" ]; then echo "ERROR: create_worktree() called without a path" return 1 fi if [[ ! "$DIR_WORKTREE" =~ ^$T_TMP ]]; then echo "ERROR: create_worktree() called with a path outside of $T_TMP" return 1 fi #; remove any existing data rm -rf "$DIR_WORKTREE" #; create some standard files if [ ! -z "$TEST_TREE_WITH_ALT" ] ; then for f in \ "alt-none##S" \ "alt-none##S.H" \ "alt-none##S.H.U" \ "alt-base##" \ "alt-base##S" \ "alt-base##S.H" \ "alt-base##S.H.U" \ "alt-system##" \ "alt-system##S" \ "alt-system##S.H" \ "alt-system##S.H.U" \ "alt-system##$T_SYS" \ "alt-system##AAA" \ "alt-system##ZZZ" \ "alt-system##aaa" \ "alt-system##zzz" \ "alt-host##" \ "alt-host##S" \ "alt-host##S.H" \ "alt-host##S.H.U" \ "alt-host##$T_SYS.$T_HOST" \ "alt-host##${T_SYS}_${T_HOST}" \ "alt-user##" \ "alt-user##S" \ "alt-user##S.H" \ "alt-user##S.H.U" \ "alt-user##$T_SYS.$T_HOST.$T_USER" \ "alt-user##${T_SYS}_${T_HOST}_${T_USER}" \ "alt-override-system##" \ "alt-override-system##$T_SYS" \ "alt-override-system##custom_system" \ "alt-override-host##" \ "alt-override-host##$T_SYS.$T_HOST" \ "alt-override-host##$T_SYS.custom_host" \ "alt-override-user##" \ "alt-override-user##S.H.U" \ "alt-override-user##$T_SYS.$T_HOST.custom_user" \ "dir one/alt-none##S/file1" \ "dir one/alt-none##S/file2" \ "dir one/alt-none##S.H/file1" \ "dir one/alt-none##S.H/file2" \ "dir one/alt-none##S.H.U/file1" \ "dir one/alt-none##S.H.U/file2" \ "dir one/alt-base##/file1" \ "dir one/alt-base##/file2" \ "dir one/alt-base##S/file1" \ "dir one/alt-base##S/file2" \ "dir one/alt-base##S.H/file1" \ "dir one/alt-base##S.H/file2" \ "dir one/alt-base##S.H.U/file1" \ "dir one/alt-base##S.H.U/file2" \ "dir one/alt-system##/file1" \ "dir one/alt-system##/file2" \ "dir one/alt-system##S/file1" \ "dir one/alt-system##S/file2" \ "dir one/alt-system##S.H/file1" \ "dir one/alt-system##S.H/file2" \ "dir one/alt-system##S.H.U/file1" \ "dir one/alt-system##S.H.U/file2" \ "dir one/alt-system##$T_SYS/file1" \ "dir one/alt-system##$T_SYS/file2" \ "dir one/alt-system##AAA/file1" \ "dir one/alt-system##AAA/file2" \ "dir one/alt-system##ZZZ/file1" \ "dir one/alt-system##ZZZ/file2" \ "dir one/alt-system##aaa/file1" \ "dir one/alt-system##aaa/file2" \ "dir one/alt-system##zzz/file1" \ "dir one/alt-system##zzz/file2" \ "dir one/alt-host##/file1" \ "dir one/alt-host##/file2" \ "dir one/alt-host##S/file1" \ "dir one/alt-host##S/file2" \ "dir one/alt-host##S.H/file1" \ "dir one/alt-host##S.H/file2" \ "dir one/alt-host##S.H.U/file1" \ "dir one/alt-host##S.H.U/file2" \ "dir one/alt-host##$T_SYS.$T_HOST/file1" \ "dir one/alt-host##$T_SYS.$T_HOST/file2" \ "dir one/alt-host##${T_SYS}_${T_HOST}/file1" \ "dir one/alt-host##${T_SYS}_${T_HOST}/file2" \ "dir one/alt-user##/file1" \ "dir one/alt-user##/file2" \ "dir one/alt-user##S/file1" \ "dir one/alt-user##S/file2" \ "dir one/alt-user##S.H/file1" \ "dir one/alt-user##S.H/file2" \ "dir one/alt-user##S.H.U/file1" \ "dir one/alt-user##S.H.U/file2" \ "dir one/alt-user##$T_SYS.$T_HOST.$T_USER/file1" \ "dir one/alt-user##$T_SYS.$T_HOST.$T_USER/file2" \ "dir one/alt-user##${T_SYS}_${T_HOST}_${T_USER}/file1" \ "dir one/alt-user##${T_SYS}_${T_HOST}_${T_USER}/file2" \ "dir one/alt-override-system##/file1" \ "dir one/alt-override-system##/file2" \ "dir one/alt-override-system##$T_SYS/file1" \ "dir one/alt-override-system##$T_SYS/file2" \ "dir one/alt-override-system##custom_system/file1" \ "dir one/alt-override-system##custom_system/file2" \ "dir one/alt-override-host##/file1" \ "dir one/alt-override-host##/file2" \ "dir one/alt-override-host##$T_SYS.$T_HOST/file1" \ "dir one/alt-override-host##$T_SYS.$T_HOST/file2" \ "dir one/alt-override-host##$T_SYS.custom_host/file1" \ "dir one/alt-override-host##$T_SYS.custom_host/file2" \ "dir one/alt-override-user##/file1" \ "dir one/alt-override-user##/file2" \ "dir one/alt-override-user##S.H.U/file1" \ "dir one/alt-override-user##S.H.U/file2" \ "dir one/alt-override-user##$T_SYS.$T_HOST.custom_user/file1" \ "dir one/alt-override-user##$T_SYS.$T_HOST.custom_user/file2" \ "dir2/file2" \ ; do make_parents "$DIR_WORKTREE/$f" echo "$f" > "$DIR_WORKTREE/$f" done echo "{{ YADM_CLASS }}-{{ YADM_OS }}-{{ YADM_HOSTNAME }}-{{ YADM_USER }}-{{ YADM_DISTRO }}" > "$DIR_WORKTREE/alt-jinja##yadm.j2" fi #; for some cygwin tests if [ ! -z "$TEST_TREE_WITH_CYGWIN" ] ; then for f in \ "alt-test##" \ "alt-test##$T_SYS" \ "alt-test##$SIMULATED_CYGWIN" \ ; do make_parents "$DIR_WORKTREE/$f" echo "$f" > "$DIR_WORKTREE/$f" done fi if [ ! -z "$TEST_TREE_WITH_WILD" ] ; then #; wildcard test data - yes this is a big mess :( #; none for f in "wild-none##"; do make_parents "$DIR_WORKTREE/$f" echo "$f" > "$DIR_WORKTREE/$f" done #; system for WILD_S in 'local' 'wild' 'other'; do local s_base="wild-system-$WILD_S" case $WILD_S in local) WILD_S="$T_SYS";; wild) WILD_S="%";; esac local f="${s_base}##${WILD_S}" make_parents "$DIR_WORKTREE/$f" echo "$f" > "$DIR_WORKTREE/$f" done #; system.host for WILD_S in 'local' 'wild' 'other'; do local s_base="wild-host-$WILD_S" case $WILD_S in local) WILD_S="$T_SYS";; wild) WILD_S="%";; esac for WILD_H in 'local' 'wild' 'other'; do local h_base="${s_base}-$WILD_H" case $WILD_H in local) WILD_H="$T_HOST";; wild) WILD_H="%";; esac local f="${h_base}##${WILD_S}.${WILD_H}" make_parents "$DIR_WORKTREE/$f" echo "$f" > "$DIR_WORKTREE/$f" done done #; system.host.user for WILD_S in 'local' 'wild' 'other'; do local s_base="wild-user-$WILD_S" case $WILD_S in local) WILD_S="$T_SYS";; wild) WILD_S="%";; esac for WILD_H in 'local' 'wild' 'other'; do local h_base="${s_base}-$WILD_H" case $WILD_H in local) WILD_H="$T_HOST";; wild) WILD_H="%";; esac for WILD_U in 'local' 'wild' 'other'; do local u_base="${h_base}-$WILD_U" case $WILD_U in local) WILD_U="$T_USER";; wild) WILD_U="%";; esac local f="${u_base}##${WILD_S}.${WILD_H}.${WILD_U}" make_parents "$DIR_WORKTREE/$f" echo "$f" > "$DIR_WORKTREE/$f" done done done #; class for WILD_C in 'local' 'wild' 'other'; do local c_base="wild-class-$WILD_C" case $WILD_C in local) WILD_C="set_class";; wild) WILD_C="%";; esac local f="${c_base}##${WILD_C}" make_parents "$DIR_WORKTREE/$f" echo "$f" > "$DIR_WORKTREE/$f" done #; class.system for WILD_C in 'local' 'wild' 'other'; do local c_base="wild-class-system-$WILD_C" case $WILD_C in local) WILD_C="set_class";; wild) WILD_C="%";; esac for WILD_S in 'local' 'wild' 'other'; do local s_base="${c_base}-$WILD_S" case $WILD_S in local) WILD_S="$T_SYS";; wild) WILD_S="%";; esac local f="${s_base}##${WILD_C}.${WILD_S}" make_parents "$DIR_WORKTREE/$f" echo "$f" > "$DIR_WORKTREE/$f" done done #; class.system.host for WILD_C in 'local' 'wild' 'other'; do local c_base="wild-class-system-host-$WILD_C" case $WILD_C in local) WILD_C="set_class";; wild) WILD_C="%";; esac for WILD_S in 'local' 'wild' 'other'; do local s_base="${c_base}-$WILD_S" case $WILD_S in local) WILD_S="$T_SYS";; wild) WILD_S="%";; esac for WILD_H in 'local' 'wild' 'other'; do local h_base="${s_base}-$WILD_H" case $WILD_H in local) WILD_H="$T_HOST";; wild) WILD_H="%";; esac local f="${h_base}##${WILD_C}.${WILD_S}.${WILD_H}" make_parents "$DIR_WORKTREE/$f" echo "$f" > "$DIR_WORKTREE/$f" done done done #; class.system.host.user for WILD_C in 'local' 'wild' 'other'; do local c_base="wild-class-system-host-user-$WILD_C" case $WILD_C in local) WILD_C="set_class";; wild) WILD_C="%";; esac for WILD_S in 'local' 'wild' 'other'; do local s_base="${c_base}-$WILD_S" case $WILD_S in local) WILD_S="$T_SYS";; wild) WILD_S="%";; esac for WILD_H in 'local' 'wild' 'other'; do local h_base="${s_base}-$WILD_H" case $WILD_H in local) WILD_H="$T_HOST";; wild) WILD_H="%";; esac for WILD_U in 'local' 'wild' 'other'; do local u_base="${h_base}-$WILD_U" case $WILD_U in local) WILD_U="$T_USER";; wild) WILD_U="%";; esac local f="${u_base}##${WILD_C}.${WILD_S}.${WILD_H}.${WILD_U}" make_parents "$DIR_WORKTREE/$f" echo "$f" > "$DIR_WORKTREE/$f" done done done done fi for f in \ .bash_profile \ .gnupg/gpg.conf \ .gnupg/pubring.gpg \ .gnupg/secring.gpg \ .hammerspoon/init.lua \ .ssh/config \ .ssh/secret.key \ .ssh/secret.pub \ .tmux.conf \ .vimrc \ "space test/file one" \ "space test/file two" \ ; do make_parents "$DIR_WORKTREE/$f" echo "$f" > "$DIR_WORKTREE/$f" done #; change all perms (so permission updates can be observed) find "$DIR_WORKTREE" -exec chmod 0777 '{}' ';' } #; create a repo in T_DIR_REPO function build_repo() { local files_to_add=( "$@" ) #; create a worktree create_worktree "$T_DIR_WORK" #; remove the repo if it exists if [ -e "$T_DIR_REPO" ]; then rm -rf "$T_DIR_REPO" fi #; create the repo git init --shared=0600 --bare "$T_DIR_REPO" >/dev/null 2>&1 #; standard repo config GIT_DIR="$T_DIR_REPO" git config core.bare 'false' GIT_DIR="$T_DIR_REPO" git config core.worktree "$T_DIR_WORK" GIT_DIR="$T_DIR_REPO" git config status.showUntrackedFiles no GIT_DIR="$T_DIR_REPO" git config yadm.managed 'true' if [ ${#files_to_add[@]} -ne 0 ]; then for f in "${files_to_add[@]}"; do GIT_DIR="$T_DIR_REPO" git add "$T_DIR_WORK/$f" >/dev/null done GIT_DIR="$T_DIR_REPO" git commit -m 'Create repo template' >/dev/null fi } #; remove all tmp files function destroy_tmp() { load_fixtures rm -rf "$T_TMP" } configure_git yadm-1.12.0/test/ownertrust.txt000066400000000000000000000000541317400042700164760ustar00rootroot00000000000000F8BBFC746C58945442349BCEBA54FFD04C599B1A:6: yadm-1.12.0/test/test_key000066400000000000000000000066231317400042700152630ustar00rootroot00000000000000-----BEGIN PGP PRIVATE KEY BLOCK----- Version: GnuPG v1 lQOYBFcWplIBCACyT3gCpP6QKuDGnSd1xsCydJhI1KnLPFR/YxuznkDfXVXMY6WC f29WiknfpqwARkNEt2j5o0AxoYKVtZSeLAR2dIwMRJMMfZerezMbMTizLA9Dc+U4 NzEWoJwr+p1PnQcz5IdIT/O95UFswyBlkk6m7oWtZ8eYHDr8O+DYvj8B2fcm8rfq 7c5IcwuzTgPMfz+VJynuB4WarS71Qh84t7eWhCbAZAiC8OEdSqHRli/0T02o04Mx jVRdxwImJfOc81B4oZr60tdsadwfvcW5dXdNL/kavCH25+QAfEobRU+/y1JI0yx+ tGYlQ1hkVQYDUt7eA5/9sK9AMTYM0plnJk73ABEBAAEAB/9GeBKxVNzIRDHePKim KrzoKh0vF2DdUcQBLj158K6pt/zbEHyOROfPF0sXyQqL9zjJlQS3OBX8J1zw5rjM BBBlci0RAh7tXktNOZzaf8rtQJntqgVqgKF1VFc0KFD4cFIy53uxj+t/3nVLUxhg HADah0SsYennSyzil5WGgzVqeL1zct+fFf+MSPSIiQJqZbD2QbyLk8IRNcnRyes+ 78brrZkPYNiNv6k/aZejKCAwjSqU6kMNHr1rwxvaY3g5oL4662bOZXBTsp4qvaJK jb7LtB72Mtj++T+qBJzDdhty/OQGrsJjMDi6IdIllW7cc+s0FFCH3b+biB4BoKW7 bnvpBADOb8gALC8v1WD7cEFZ12gIk3IrRcDJD8taozS7jWna83rga9W7qz+eW2Gb vOVS+rNG5n/O0Bm1Uvr+y0+i7l21+8iECA3KlP09k+7XDGZUu+IzO4S8guzAu33k hlQFj5KwRaXx4nNEGUMZfX75NVHvpcN5W1eKTg1t27I+K1R2mQQA3R73F9FZmnVg 4VKvfPTgiwQcns8tOXnv/23BNpHqu14qG2E0Dh9xa5FTvtq6hrsKVdH61AU8dptX BnLTzG7xF0qEecFpYkmCuyqlVdVPrxBc+Q2PLxK66QpUX+/0m1R3pKGFJ/g+WLdz 8yMSwMX4W8pSH7QmxVhh4zojmYbTvA8EALE7JmahLUcU/GLs//0sd06XcdS42ENn cB2TpqtzLqR9im8tx1/rImWGJFzAvoaAsk4ATXwSoKBiUjmt0jRtVU0Etbm7QTRg ub247h4SNKcQyNBZ5eKIn93Cpt2vaTH7rKJ9y5UYAXmsgVrdW9lihaGOgHrgqkMO nZV5j17elMNfRl20J1lBRE0gVGVzdCAxIDx5YWRtLXRlc3QxQGxvY2VoaWxpb3Mu Y29tPokBOAQTAQIAIgUCVxamUgIbAwYLCQgHAwIGFQgCCQoLBBYCAwECHgECF4AA CgkQulT/0ExZmxprzQf9HxoC10h0/GKlzMoNqVhGcrknCD0LMYmx+A8n2qEKVqGG 9+Hsc5BNI/TQNKJUUsh3G/NGvIDhATKeKrGPI1ezIdpxubtynVJ5qPFOFe/tDFp3 iMN00v0b64E8OLHXXM26D+fX5/5N6OI+UFaeUT8omrbXy67aAFy74Vm1Ybac2zni LuMtXLS65g23plAn509SXl/g1KPnXDIO8ccCn6/5o8s5ZSA3LKTQEtgwN2gX14rN n/9DvudpscelkWUWv6wxXOb9p9N/JmNOSGrQ2zyT1u6UWMBxkdgQ90+BZ+Y/wiCs lgBjC+dqU9ooJy7EtGD6PjJPunUBi3YjSteMOXnax50DmARXFqZSAQgA22z0PzyT 6hFfioVVax7zppRJDPQwW+l4+2N7eYUCNoSELhC/uKYwQIZfhRJlX4rkaVv8PgwK LdtPyZhHckxGNfsq6w2V/orVFc46dwCiYGsuqIXlu9+KVCsBB4/it8D56koBPPET kz5yZDqR7WtoKLbjTPjwOlJwPk/7o87d6CyAcWP6bzVTIiFM3XAXtvdDfXwL9Mj8 wgTrDc6GFGiwz2VCMVNWASLPvPrGiqEjrt7zaLUrRaLwK81FJUtGcNu06KbZRP6G +Iu/9+UZ3hmIcZMJZtqNO87q7VHW6NecGRlrg/EZP6XyMTtk83w5aFrOvtzym0xc jkTOKGEE72UXVwARAQABAAf7BwcXT4suJZoG2FXq5XJpVVV8fXi4r8jrggmuo7a5 2msmHJ+WtGBGPVrQZl+vdX7qT+GNU6NpFAzpIkjJSQTeXs47kqmtuyhRKNChGLyh drsYFHetYvYG5Sk3cDmQhlgc6P8TyRLjkJy4ZzNlBxigjmVFJGr4rrWDOMuxAI8Y ll3/TFa+XrFeBUoFakiC1C8jIanaVCK21kQ2Qam3EKCfuASvxGiCLb/nZ84mDF2d GrLiUGA2GumP2cXS/ml8Q/YCjOmQMSTYkM9zFAUkLtfrIZY0/cqIotDOuAY7H3lJ u4NlJrenRUnYerjS2QOxm6DdXKu9ChtHJKOrlDMkl3z1SQQA3hQx/DI2BJeSnQLI CeO1yMvUf52Dg0e66t7yE0dUcgn4eaIRChMi8aWX3fv3CBVBqPrH5o1BLpqDSHt6 fGg/za1sMljrtWslnE17UPPl9ZTnS5c1mcNkg3YoyHjGa9RAiEbEMwWF3mPyS+YT NuqL6F+KGmTRcTi3eTLEWOf5ltMEAPzxAldXAeconblzupQkuyjhlnlwJYYKzx7P nJK2rQW8eOJIPjNC/1xbvWw25Hh/ZNIFN/kWk+lol9PmIPVGp4yfWMOegCH3v6xz YZarAyhTqlRQQEeVBddyp2RV6r6+6pz5goTJLGyFiNCgTzMdhZn1U14lnE6ABJW8 z62Jm/LtA/sFqOSV5PYOdaRRZ7kTBKRmQNQKyJhT5yjnYiI6ME6ds8n5f3lLDnte VMUt/IULRIRKQ3JExgciGaDYLhYIy0ZALrpeh5jshM9jPJGK6heaM90h8bnPAdxM waNbo+DtTGbHLqqMbVDMSPjO7wSrCuSzfRvTBgaC1puz2YjsN5C/CD9liQEfBBgB AgAJBQJXFqZSAhsMAAoJELpU/9BMWZsabE8IAI+z0v6Y+TPoJR7vHAu8twaEWV8E z2BAkLabe0IvZH3lvXtlJyhGKm9XIfKINKruwwM+ty+XRXzl3llPUEeylkkPZ4TV isKmCazO/M3+2AZ8lexNeJqzUitf5tStapkhoyZOfjbEtpddR9vqUoJQ6aWjYk/y YV9Uh5Za5YAb7QcaDIwxGHnCmxovwyUr2T7Z3b4k4O9lqwgjOCezZYYb6+BTnVmz +C2h9Pk+M1Fuh9fMCmNEL4pCGcCiRtSbeuUvXUtMcZNOuUjcdULw/vuPVko57YLH 8Wd/F3ckIUEVbKlVYHFdl7DGysDQ08lZ2lvbJE+9L4I+emvgpVt33isXav0= =2hap -----END PGP PRIVATE KEY BLOCK----- yadm-1.12.0/yadm000077500000000000000000000665751317400042700134260ustar00rootroot00000000000000#!/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.12.0 YADM_WORK="$HOME" YADM_DIR="$HOME/.yadm" 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 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 #; 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 new_link="${BASH_REMATCH[1]}" debug "Linking $alt_path to $new_link" [ -n "$loud" ] && echo "Linking $alt_path to $new_link" 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 real_file="${BASH_REMATCH[1]}" if envtpl_available; then debug "Creating $real_file from template $tracked_file" [ -n "$loud" ] && echo "Creating $real_file from template $tracked_file" 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/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.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-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-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_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_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 yadm-1.12.0/yadm.1000066400000000000000000000461621317400042700135500ustar00rootroot00000000000000." vim: set spell so=8: .TH yadm 1 "25 October 2017" "1.12.0" .SH NAME yadm \- Yet Another Dotfiles Manager .SH SYNOPSIS .B yadm .I command .RI [ options ] .B yadm .I git-command-or-alias .RI [ options ] .B yadm init .RB [ -f ] .RB [ -w .IR directory ] .B yadm .RI clone " url .RB [ -f ] .RB [ -w .IR directory ] .RB [ --bootstrap ] .RB [ --no-bootstrap ] .B yadm .RI config " name .RI [ value ] .B yadm config .RB [ -e ] .B yadm list .RB [ -a ] .BR yadm " bootstrap .BR yadm " encrypt .BR yadm " enter .BR yadm " decrypt .RB [ -l ] .BR yadm " alt .BR yadm " perms .BR yadm " introspect .I category .SH DESCRIPTION .B yadm is a tool for managing a collection of files across multiple computers, using a shared Git repository. In addition, .B yadm provides a feature to select alternate versions of files based on the operating system or host name. Lastly, .B yadm supplies the ability to manage a subset of secure files, which are encrypted before they are included in the repository. .SH COMMANDS .TP .IR git-command " or " git-alias Any command not internally handled by .B yadm is passed through to .BR git (1). Git commands or aliases are invoked with the .B yadm managed repository. The working directory for Git commands will be the configured .IR work-tree " (usually .IR $HOME ). Dotfiles are managed by using standard .B git commands; .IR add , .IR commit , .IR push , .IR pull , etc. .RI The " config command is not passed directly through. Instead use the .I gitconfig command (see below). .TP .B alt Create symbolic links and process Jinja templates for any managed files matching the naming rules described in the ALTERNATES and JINJA sections. It is usually unnecessary to run this command, as .B yadm automatically processes alternates by default. This automatic behavior can be disabled by setting the configuration .I yadm.auto-alt to "false". .TP .B bootstrap Execute .I $HOME/.yadm/bootstrap if it exists. .TP .BI clone " url Clone a remote repository for tracking dotfiles. After the contents of the remote repository have been fetched, a "merge" of .I origin/master is attempted. If there are conflicting files already present in the .IR work-tree , this merge will fail and instead a "reset" of .I origin/master will be done, followed by a "stash". This "stash" operation will preserve the original data. You can review the stashed conflicts by running the command .RS .RS yadm stash show -p .RE from within your .I $HOME directory. If you want to restore the stashed data, you can run .RS yadm stash apply .RE or .RS yadm stash pop .RE The repository is stored in .IR $HOME/.yadm/repo.git . By default, .I $HOME will be used as the .IR work-tree , but this can be overridden with the .BR -w " option. .B yadm can be forced to overwrite an existing repository by providing the .BR -f " option. By default .B yadm will ask the user if the bootstrap program should be run (if it exists). The options .BR --bootstrap " or " --no-bootstrap will either force the bootstrap to be run, or prevent it from being run, without prompting the user. .RE .TP .B config This command manages configurations for .BR yadm . This command works exactly they way .BR git-config (1) does. See the CONFIGURATION section for more details. .TP .B decrypt Decrypt all files stored in .IR $HOME/.yadm/files.gpg . Files decrypted will be relative to the configured .IR work-tree " (usually .IR $HOME ). Using the .B -l option will list the files stored without extracting them. .TP .B encrypt Encrypt all files matching the patterns found in .IR $HOME/.yadm/encrypt . See the ENCRYPTION section for more details. .TP .B enter Run a sub-shell with all Git variables set. Exit the sub-shell the same way you leave your normal shell (usually with the "exit" command). This sub-shell can be used to easily interact with your .B yadm repository using "git" commands. This could be useful if you are using a tool which uses Git directly. For example, Emacs Tramp and Magit can manage files by using this configuration: .RS (add-to-list 'tramp-methods '("yadm" (tramp-login-program "yadm") (tramp-login-args (("enter"))) (tramp-remote-shell "/bin/sh") (tramp-remote-shell-args ("-c")))) .RE .TP .B gitconfig Pass options to the .B git config command. Since .B yadm already uses the .I config command to manage its own configurations, this command is provided as a way to change configurations of the repository managed by .BR yadm . One useful case might be to configure the repository so untracked files are shown in status commands. .B yadm initially configures its repository so that untracked files are not shown. If you wish use the default Git behavior (to show untracked files and directories), you can remove this configuration. .RS .RS yadm gitconfig --unset status.showUntrackedFiles .RE .RE .TP .B help Print a summary of .BR yadm " commands. .TP .B init Initialize a new, empty repository for tracking dotfiles. The repository is stored in .IR $HOME/.yadm/repo.git . By default, .I $HOME will be used as the .IR work-tree , but this can be overridden with the .BR -w " option. .B yadm can be forced to overwrite an existing repository by providing the .BR -f " option. .TP .B list Print a list of files managed by .BR yadm . .RB The " -a option will cause all managed files to be listed. Otherwise, the list will only include files from the current directory or below. .TP .BI introspect " category Report internal .B yadm data. Supported categories are .IR commands , .IR configs , .IR repo, and .IR switches . The purpose of introspection is to support command line completion. .TP .B perms Update permissions as described in the PERMISSIONS section. It is usually unnecessary to run this command, as .B yadm automatically processes permissions by default. This automatic behavior can be disabled by setting the configuration .I yadm.auto-perms to "false". .TP .B version Print the version of .BR yadm . .SH OPTIONS .B yadm supports a set of universal options that alter the paths it uses. The default paths are documented in the FILES section. Any path specified by these options must be fully qualified. If you always want to override one or more of these paths, it may be useful to create an alias for the .B yadm command. For example, the following alias could be used to override the repository directory. .RS alias yadm='yadm --yadm-repo /alternate/path/to/repo' .RE The following is the full list of universal options. Each option should be followed by a fully qualified path. .TP .B -Y,--yadm-dir Override the .B yadm directory. .B yadm stores its data relative to this directory. .TP .B --yadm-repo Override the location of the .B yadm repository. .TP .B --yadm-config Override the location of the .B yadm configuration file. .TP .B --yadm-encrypt Override the location of the .B yadm encryption configuration. .TP .B --yadm-archive Override the location of the .B yadm encrypted files archive. .TP .B --yadm-bootstrap Override the location of the .B yadm bootstrap program. .SH CONFIGURATION .B yadm uses a configuration file named .IR $HOME/.yadm/config . This file uses the same format as .BR git-config (1). Also, you can control the contents of the configuration file via the .B yadm config command (which works exactly like .BR git-config ). For example, to disable alternates you can run the command: .RS yadm config yadm.auto-alt false .RE The following is the full list of supported configurations: .TP .B yadm.auto-alt Disable the automatic linking described in the section ALTERNATES. If disabled, you may still run .B yadm alt manually to create the alternate links. This feature is enabled by default. .TP .B yadm.auto-perms Disable the automatic permission changes described in the section PERMISSIONS. If disabled, you may still run .B yadm perms manually to update permissions. This feature is enabled by default. .TP .B yadm.auto-private-dirs Disable the automatic creating of private directories described in the section PERMISSIONS. .TP .B yadm.ssh-perms Disable the permission changes to .IR $HOME/.ssh/* . This feature is enabled by default. .TP .B yadm.gpg-perms Disable the permission changes to .IR $HOME/.gnupg/* . This feature is enabled by default. .TP .B yadm.gpg-recipient Asymmetrically encrypt files with a gpg public/private key pair. Provide a "key ID" to specify which public key to encrypt with. The key must exist in your public keyrings. If left blank or not provided, symmetric encryption is used instead. If set to "ASK", gpg will interactively ask for recipients. See the ENCRYPTION section for more details. This feature is disabled by default. .TP .B yadm.gpg-program Specify an alternate program to use instead of "gpg". By default, the first "gpg" found in $PATH is used. .TP .B yadm.git-program Specify an alternate program to use instead of "git". By default, the first "git" found in $PATH is used. .TP .B yadm.cygwin-copy If set to "true", for Cygwin hosts, alternate files will be copies instead of symbolic links. This might be desirable, because non-Cygwin software may not properly interpret Cygwin symlinks. .RE These last four "local" configurations are not stored in the .IR $HOME/.yadm/config, they are stored in the local repository. .TP .B local.class Specify a CLASS for the purpose of symlinking alternate files. By default, no CLASS will be matched. .TP .B local.os Override the OS for the purpose of symlinking alternate files. .TP .B local.hostname Override the HOSTNAME for the purpose of symlinking alternate files. .TP .B local.user Override the USER for the purpose of symlinking alternate files. .SH ALTERNATES When managing a set of files across different systems, it can be useful to have an automated way of choosing an alternate version of a file for a different operating system, host, or user. .B yadm implements a feature which will automatically create a symbolic link to the appropriate version of a file, as long as you follow a specific naming convention. .B yadm can detect files with names ending in any of the following: ## ##CLASS ##CLASS.OS ##CLASS.OS.HOSTNAME ##CLASS.OS.HOSTNAME.USER ##OS ##OS.HOSTNAME ##OS.HOSTNAME.USER If there are any files managed by .BR yadm \'s repository, or listed in .IR $HOME/.yadm/encrypt , which match this naming convention, symbolic links will be created for the most appropriate version. This may best be demonstrated by example. Assume the following files are managed by .BR yadm \'s repository: - $HOME/path/example.txt## - $HOME/path/example.txt##Work - $HOME/path/example.txt##Darwin - $HOME/path/example.txt##Darwin.host1 - $HOME/path/example.txt##Darwin.host2 - $HOME/path/example.txt##Linux - $HOME/path/example.txt##Linux.host1 - $HOME/path/example.txt##Linux.host2 If running on a Macbook named "host2", .B yadm will create a symbolic link which looks like this: .IR $HOME/path/example.txt " -> " $HOME/path/example.txt##Darwin.host2 However, on another Mackbook named "host3", .B yadm will create a symbolic link which looks like this: .IR $HOME/path/example.txt " -> " $HOME/path/example.txt##Darwin Since the hostname doesn't match any of the managed files, the more generic version is chosen. If running on a Linux server named "host4", the link will be: .IR $HOME/path/example.txt " -> " $HOME/path/example.txt##Linux If running on a Solaris server, the link use the default "##" version: .IR $HOME/path/example.txt " -> " $HOME/path/example.txt## If running on a system, with CLASS set to "Work", the link will be: .IR $HOME/path/example.txt " -> " $HOME/path/example.txt##WORK If no "##" version exists and no files match the current CLASS/OS/HOSTNAME/USER, then no link will be created. Links are also created for directories named this way, as long as they have at least one .B yadm managed file within them. CLASS must be manually set using .BR yadm\ config\ local.class\ . OS is determined by running .BR uname\ -s , HOSTNAME by running .BR hostname , and USER by running .BR id\ -u\ -n . .B yadm will automatically create these links by default. This can be disabled using the .I yadm.auto-alt configuration. Even if disabled, links can be manually created by running .BR yadm\ alt . It is possible to use "%" as a "wildcard" in place of CLASS, OS, HOSTNAME, or USER. For example, The following file could be linked for any host when the user is "harvey". .IR $HOME/path/example.txt##%.%.harvey CLASS is a special value which is stored locally on each host (inside the local repository). To use alternate symlinks using CLASS, you must set the value of class using the configuration .BR local.class . This is set like any other .B yadm configuration with the .B yadm config command. The following sets the CLASS to be "Work". yadm config local.class Work Similarly, the values of OS, HOSTNAME, and USER can be manually overridden using the configuration options .BR local.os , .BR local.hostname , and .BR local.user . .SH JINJA If the .B envtpl command is available, .B Jinja templates will also be processed to create or overwrite real files. .B yadm will treat files ending in ##yadm.j2 as Jinja templates. During processing, the following variables are set according to the rules explained in the ALTERNATES section: YADM_CLASS YADM_OS YADM_HOSTNAME YADM_USER In addition YADM_DISTRO is exposed as the value of .I lsb_release -si if .B lsb_release is locally available. For example, a file named .I whatever##yadm.j2 with the following content {% if YADM_USER == 'harvey' -%} config={{YADM_CLASS}}-{{ YADM_OS }} {% else -%} config=dev-whatever {% endif -%} would output a file named .I whatever with the following content if the user is "harvey": config=work-Linux and the following otherwise: config=dev-whatever See http://jinja.pocoo.org/ for an overview of .BR Jinja . .SH ENCRYPTION It can be useful to manage confidential files, like SSH or GPG keys, across multiple systems. However, doing so would put plain text data into a Git repository, which often resides on a public system. .B yadm implements a feature which can make it easy to encrypt and decrypt a set of files so the encrypted version can be maintained in the Git repository. This feature will only work if the .BR gpg (1) command is available. To use this feature, a list of patterns must be created and saved as .IR $HOME/.yadm/encrypt . This list of patterns should be relative to the configured .IR work-tree " (usually .IR $HOME ). For example: .RS .ssh/*.key .gnupg/*.gpg .RE Standard filename expansions (*, ?, [) are supported. Other shell expansions like brace and tilde are not supported. Spaces in paths are supported, and should not be quoted. If a directory is specified, its contents will be included, but not recursively. Paths beginning with a "!" will be excluded. The .B yadm encrypt command will find all files matching the patterns, and prompt for a password. Once a password has confirmed, the matching files will be encrypted and saved as .IR $HOME/.yadm/files.gpg . The patterns and files.gpg should be added to the .B yadm repository so they are available across multiple systems. To decrypt these files later, or on another system run .BR yadm\ decrypt and provide the correct password. After files are decrypted, permissions are automatically updated as described in the PERMISSIONS section. Symmetric encryption is used by default, but asymmetric encryption may be enabled using the .I yadm.gpg-recipient configuration. .BR NOTE : It is recommended that you use a private repository when keeping confidential files, even though they are encrypted. .SH PERMISSIONS When files are checked out of a Git repository, their initial permissions are dependent upon the user's umask. Because of this, .B yadm will automatically update the permissions of some file paths. The "group" and "others" permissions will be removed from the following files: .RI - " $HOME/.yadm/files.gpg - All files matching patterns in .I $HOME/.yadm/encrypt - The SSH directory and files, .I .ssh/* - The GPG directory and files, .I .gnupg/* .B yadm will automatically update permissions by default. This can be disabled using the .I yadm.auto-perms configuration. Even if disabled, permissions can be manually updated by running .BR yadm\ perms . The .I .ssh directory processing can be disabled using the .I yadm.ssh-perms configuration. The .I .gnupg directory processing can be disabled using the .I yadm.gpg-perms configuration. When cloning a repo which includes data in a .IR .ssh " or " .gnupg directory, if those directories do not exist at the time of cloning, .B yadm will create the directories with mask 0700 prior to merging the fetched data into the work-tree. When running a Git command and .IR .ssh " or " .gnupg directories do not exist, .B yadm will create those directories with mask 0700 prior to running the Git command. This can be disabled using the .I yadm.auto-private-dirs configuration. .SH HOOKS For every command .B yadm supports, a program can be provided to run before or after that command. These are referred to as "hooks". .B yadm looks for hooks in the directory .IR $HOME/.yadm/hooks . Each hook is named using a prefix of .I pre_ or .IR post_ , followed by the command which should trigger the hook. For example, to create a hook which is run after every .I yadm pull command, create a hook named .IR post_pull. Hooks must have the executable file permission set. If a .I pre_ hook is defined, and the hook terminates with a non-zero exit status, .B yadm will refuse to run the .B yadm command. For example, if a .I pre_commit hook is defined, but that command ends with a non-zero exit status, the .I yadm commit will never be run. This allows one to "short-circuit" any operation using a .I pre_ hook. Hooks have the following environment variables available to them at runtime: .TP .B YADM_HOOK_COMMAND The command which triggered the hook .TP .B YADM_HOOK_EXIT The exit status of the .B yadm command .TP .B YADM_HOOK_FULL_COMMAND The .B yadm command with all command line arguments .TP .B YADM_HOOK_REPO The path to the .B yadm repository .TP .B YADM_HOOK_WORK The path to the work-tree .SH FILES The following are the default paths .B yadm uses for its own data. These paths can be altered using universal options. See the OPTIONS section for details. .TP .I $HOME/.yadm The .B yadm directory. By default, all data .B yadm stores is relative to this directory. .TP .I $YADM_DIR/config Configuration file for .BR yadm . .TP .I $YADM_DIR/repo.git Git repository used by .BR yadm . .TP .I $YADM_DIR/encrypt List of globs used for encrypt/decrypt .TP .I $YADM_DIR/files.gpg All files encrypted with .B yadm encrypt are stored in this file. .SH EXAMPLES .TP .B yadm init Create an empty repo for managing files .TP .B yadm add .bash_profile ; yadm commit Add .I .bash_profile to the Git index and create a new commit .TP .B yadm remote add origin Add a remote origin to an existing repository .TP .B yadm push -u origin master Initial push of master to origin .TP .B echo ".ssh/*.key" >> $HOME/.yadm/encrypt Add a new pattern to the list of encrypted files .TP .B yadm encrypt ; yadm add ~/.yadm/files.gpg ; yadm commit Commit a new set of encrypted files .SH REPORTING BUGS Report issues or create pull requests at GitHub: https://github.com/TheLocehiliosan/yadm/issues .SH AUTHOR Tim Byrne .SH SEE ALSO .BR git (1), .BR gpg (1) https://thelocehiliosan.github.io/yadm/ yadm-1.12.0/yadm.md000066400000000000000000000537771317400042700140220ustar00rootroot00000000000000 ## NAME yadm - Yet Another Dotfiles Manager ## SYNOPSIS yadm command [options] yadm git-command-or-alias [options] yadm init [-f] [-w directory] yadm clone url [-f] [-w directory] [--bootstrap] [--no-bootstrap] yadm config name [value] yadm config [-e] yadm list [-a] yadm bootstrap yadm encrypt yadm enter yadm decrypt [-l] yadm alt yadm perms yadm introspect category ## DESCRIPTION yadm is a tool for managing a collection of files across multiple com- puters, using a shared Git repository. In addition, yadm provides a feature to select alternate versions of files based on the operating system or host name. Lastly, yadm supplies the ability to manage a subset of secure files, which are encrypted before they are included in the repository. ## COMMANDS git-command or git-alias Any command not internally handled by yadm is passed through to git(1). Git commands or aliases are invoked with the yadm man- aged repository. The working directory for Git commands will be the configured work-tree (usually $HOME). Dotfiles are managed by using standard git commands; add, com- mit, push, pull, etc. The config command is not passed directly through. Instead use the gitconfig command (see below). alt Create symbolic links and process Jinja templates for any man- aged files matching the naming rules described in the ALTERNATES and JINJA sections. It is usually unnecessary to run this com- mand, as yadm automatically processes alternates by default. This automatic behavior can be disabled by setting the configu- ration yadm.auto-alt to "false". bootstrap Execute $HOME/.yadm/bootstrap if it exists. clone url Clone a remote repository for tracking dotfiles. After the con- tents of the remote repository have been fetched, a "merge" of origin/master is attempted. If there are conflicting files already present in the work-tree, this merge will fail and instead a "reset" of origin/master will be done, followed by a "stash". This "stash" operation will preserve the original data. You can review the stashed conflicts by running the command yadm stash show -p from within your $HOME directory. If you want to restore the stashed data, you can run yadm stash apply or yadm stash pop The repository is stored in $HOME/.yadm/repo.git. By default, $HOME will be used as the work-tree, but this can be overridden with the -w option. yadm can be forced to overwrite an existing repository by providing the -f option. By default yadm will ask the user if the bootstrap program should be run (if it exists). The options --bootstrap or --no-bootstrap will either force the bootstrap to be run, or prevent it from being run, without prompting the user. config This command manages configurations for yadm. This command works exactly they way git-config(1) does. See the CONFIGURA- TION section for more details. decrypt Decrypt all files stored in $HOME/.yadm/files.gpg. Files decrypted will be relative to the configured work-tree (usually $HOME). Using the -l option will list the files stored without extracting them. encrypt Encrypt all files matching the patterns found in $HOME/.yadm/encrypt. See the ENCRYPTION section for more details. enter Run a sub-shell with all Git variables set. Exit the sub-shell the same way you leave your normal shell (usually with the "exit" command). This sub-shell can be used to easily interact with your yadm repository using "git" commands. This could be useful if you are using a tool which uses Git directly. For example, Emacs Tramp and Magit can manage files by using this configuration: (add-to-list 'tramp-methods '("yadm" (tramp-login-program "yadm") (tramp-login-args (("enter"))) (tramp-remote-shell "/bin/sh") (tramp-remote-shell-args ("-c")))) gitconfig Pass options to the git config command. Since yadm already uses the config command to manage its own configurations, this com- mand is provided as a way to change configurations of the repos- itory managed by yadm. One useful case might be to configure the repository so untracked files are shown in status commands. yadm initially configures its repository so that untracked files are not shown. If you wish use the default Git behavior (to show untracked files and directories), you can remove this con- figuration. yadm gitconfig --unset status.showUntrackedFiles help Print a summary of yadm commands. init Initialize a new, empty repository for tracking dotfiles. The repository is stored in $HOME/.yadm/repo.git. By default, $HOME will be used as the work-tree, but this can be overridden with the -w option. yadm can be forced to overwrite an existing repository by providing the -f option. list Print a list of files managed by yadm. The -a option will cause all managed files to be listed. Otherwise, the list will only include files from the current directory or below. introspect category Report internal yadm data. Supported categories are commands, configs, repo, and switches. The purpose of introspection is to support command line completion. perms Update permissions as described in the PERMISSIONS section. It is usually unnecessary to run this command, as yadm automati- cally processes permissions by default. This automatic behavior can be disabled by setting the configuration yadm.auto-perms to "false". version Print the version of yadm. ## OPTIONS yadm supports a set of universal options that alter the paths it uses. The default paths are documented in the FILES section. Any path speci- fied by these options must be fully qualified. If you always want to override one or more of these paths, it may be useful to create an alias for the yadm command. For example, the following alias could be used to override the repository directory. alias yadm='yadm --yadm-repo /alternate/path/to/repo' The following is the full list of universal options. Each option should be followed by a fully qualified path. -Y,--yadm-dir Override the yadm directory. yadm stores its data relative to this directory. --yadm-repo Override the location of the yadm repository. --yadm-config Override the location of the yadm configuration file. --yadm-encrypt Override the location of the yadm encryption configuration. --yadm-archive Override the location of the yadm encrypted files archive. --yadm-bootstrap Override the location of the yadm bootstrap program. ## CONFIGURATION yadm uses a configuration file named $HOME/.yadm/config. This file uses the same format as git-config(1). Also, you can control the con- tents of the configuration file via the yadm config command (which works exactly like git-config). For example, to disable alternates you can run the command: yadm config yadm.auto-alt false The following is the full list of supported configurations: yadm.auto-alt Disable the automatic linking described in the section ALTER- NATES. If disabled, you may still run yadm alt manually to cre- ate the alternate links. This feature is enabled by default. yadm.auto-perms Disable the automatic permission changes described in the sec- tion PERMISSIONS. If disabled, you may still run yadm perms manually to update permissions. This feature is enabled by default. yadm.auto-private-dirs Disable the automatic creating of private directories described in the section PERMISSIONS. yadm.ssh-perms Disable the permission changes to $HOME/.ssh/*. This feature is enabled by default. yadm.gpg-perms Disable the permission changes to $HOME/.gnupg/*. This feature is enabled by default. yadm.gpg-recipient Asymmetrically encrypt files with a gpg public/private key pair. Provide a "key ID" to specify which public key to encrypt with. The key must exist in your public keyrings. If left blank or not provided, symmetric encryption is used instead. If set to "ASK", gpg will interactively ask for recipients. See the ENCRYPTION section for more details. This feature is disabled by default. yadm.gpg-program Specify an alternate program to use instead of "gpg". By default, the first "gpg" found in $PATH is used. yadm.git-program Specify an alternate program to use instead of "git". By default, the first "git" found in $PATH is used. yadm.cygwin-copy If set to "true", for Cygwin hosts, alternate files will be copies instead of symbolic links. This might be desirable, because non-Cygwin software may not properly interpret Cygwin symlinks. These last four "local" configurations are not stored in the $HOME/.yadm/config, they are stored in the local repository. local.class Specify a CLASS for the purpose of symlinking alternate files. By default, no CLASS will be matched. local.os Override the OS for the purpose of symlinking alternate files. local.hostname Override the HOSTNAME for the purpose of symlinking alternate files. local.user Override the USER for the purpose of symlinking alternate files. ## ALTERNATES When managing a set of files across different systems, it can be useful to have an automated way of choosing an alternate version of a file for a different operating system, host, or user. yadm implements a feature which will automatically create a symbolic link to the appropriate ver- sion of a file, as long as you follow a specific naming convention. yadm can detect files with names ending in any of the following: ## ##CLASS ##CLASS.OS ##CLASS.OS.HOSTNAME ##CLASS.OS.HOSTNAME.USER ##OS ##OS.HOSTNAME ##OS.HOSTNAME.USER If there are any files managed by yadm's repository, or listed in $HOME/.yadm/encrypt, which match this naming convention, symbolic links will be created for the most appropriate version. This may best be demonstrated by example. Assume the following files are managed by yadm's repository: - $HOME/path/example.txt## - $HOME/path/example.txt##Work - $HOME/path/example.txt##Darwin - $HOME/path/example.txt##Darwin.host1 - $HOME/path/example.txt##Darwin.host2 - $HOME/path/example.txt##Linux - $HOME/path/example.txt##Linux.host1 - $HOME/path/example.txt##Linux.host2 If running on a Macbook named "host2", yadm will create a symbolic link which looks like this: $HOME/path/example.txt -> $HOME/path/example.txt##Darwin.host2 However, on another Mackbook named "host3", yadm will create a symbolic link which looks like this: $HOME/path/example.txt -> $HOME/path/example.txt##Darwin Since the hostname doesn't match any of the managed files, the more generic version is chosen. If running on a Linux server named "host4", the link will be: $HOME/path/example.txt -> $HOME/path/example.txt##Linux If running on a Solaris server, the link use the default "##" version: $HOME/path/example.txt -> $HOME/path/example.txt## If running on a system, with CLASS set to "Work", the link will be: $HOME/path/example.txt -> $HOME/path/example.txt##WORK If no "##" version exists and no files match the current CLASS/OS/HOST- NAME/USER, then no link will be created. Links are also created for directories named this way, as long as they have at least one yadm managed file within them. CLASS must be manually set using yadm config local.class . OS is determined by running uname -s, HOSTNAME by running hostname, and USER by running id -u -n. yadm will automatically create these links by default. This can be disabled using the yadm.auto-alt configuration. Even if disabled, links can be manually created by running yadm alt. It is possible to use "%" as a "wildcard" in place of CLASS, OS, HOST- NAME, or USER. For example, The following file could be linked for any host when the user is "harvey". $HOME/path/example.txt##%.%.harvey CLASS is a special value which is stored locally on each host (inside the local repository). To use alternate symlinks using CLASS, you must set the value of class using the configuration local.class. This is set like any other yadm configuration with the yadm config command. The following sets the CLASS to be "Work". yadm config local.class Work Similarly, the values of OS, HOSTNAME, and USER can be manually over- ridden using the configuration options local.os, local.hostname, and local.user. ## JINJA If the envtpl command is available, Jinja templates will also be pro- cessed to create or overwrite real files. yadm will treat files ending in ##yadm.j2 as Jinja templates. During processing, the following variables are set according to the rules explained in the ALTERNATES section: YADM_CLASS YADM_OS YADM_HOSTNAME YADM_USER In addition YADM_DISTRO is exposed as the value of lsb_release -si if lsb_release is locally available. For example, a file named whatever##yadm.j2 with the following content {% if YADM_USER == 'harvey' -%} config={{YADM_CLASS}}-{{ YADM_OS }} {% else -%} config=dev-whatever {% endif -%} would output a file named whatever with the following content if the user is "harvey": config=work-Linux and the following otherwise: config=dev-whatever See http://jinja.pocoo.org/ for an overview of Jinja. ## ENCRYPTION It can be useful to manage confidential files, like SSH or GPG keys, across multiple systems. However, doing so would put plain text data into a Git repository, which often resides on a public system. yadm implements a feature which can make it easy to encrypt and decrypt a set of files so the encrypted version can be maintained in the Git repository. This feature will only work if the gpg(1) command is available. To use this feature, a list of patterns must be created and saved as $HOME/.yadm/encrypt. This list of patterns should be relative to the configured work-tree (usually $HOME). For example: .ssh/*.key .gnupg/*.gpg Standard filename expansions (*, ?, [) are supported. Other shell expansions like brace and tilde are not supported. Spaces in paths are supported, and should not be quoted. If a directory is specified, its contents will be included, but not recursively. Paths beginning with a "!" will be excluded. The yadm encrypt command will find all files matching the patterns, and prompt for a password. Once a password has confirmed, the matching files will be encrypted and saved as $HOME/.yadm/files.gpg. The pat- terns and files.gpg should be added to the yadm repository so they are available across multiple systems. To decrypt these files later, or on another system run yadm decrypt and provide the correct password. After files are decrypted, permissions are automatically updated as described in the PERMISSIONS section. Symmetric encryption is used by default, but asymmetric encryption may be enabled using the yadm.gpg-recipient configuration. NOTE: It is recommended that you use a private repository when keeping confidential files, even though they are encrypted. ## PERMISSIONS When files are checked out of a Git repository, their initial permis- sions are dependent upon the user's umask. Because of this, yadm will automatically update the permissions of some file paths. The "group" and "others" permissions will be removed from the following files: - $HOME/.yadm/files.gpg - All files matching patterns in $HOME/.yadm/encrypt - The SSH directory and files, .ssh/* - The GPG directory and files, .gnupg/* yadm will automatically update permissions by default. This can be dis- abled using the yadm.auto-perms configuration. Even if disabled, per- missions can be manually updated by running yadm perms. The .ssh directory processing can be disabled using the yadm.ssh-perms configu- ration. The .gnupg directory processing can be disabled using the yadm.gpg-perms configuration. When cloning a repo which includes data in a .ssh or .gnupg directory, if those directories do not exist at the time of cloning, yadm will create the directories with mask 0700 prior to merging the fetched data into the work-tree. When running a Git command and .ssh or .gnupg directories do not exist, yadm will create those directories with mask 0700 prior to running the Git command. This can be disabled using the yadm.auto-private-dirs configuration. ## HOOKS For every command yadm supports, a program can be provided to run before or after that command. These are referred to as "hooks". yadm looks for hooks in the directory $HOME/.yadm/hooks. Each hook is named using a prefix of pre_ or post_, followed by the command which should trigger the hook. For example, to create a hook which is run after every yadm pull command, create a hook named post_pull. Hooks must have the executable file permission set. If a pre_ hook is defined, and the hook terminates with a non-zero exit status, yadm will refuse to run the yadm command. For example, if a pre_commit hook is defined, but that command ends with a non-zero exit status, the yadm commit will never be run. This allows one to "short- circuit" any operation using a pre_ hook. Hooks have the following environment variables available to them at runtime: YADM_HOOK_COMMAND The command which triggered the hook YADM_HOOK_EXIT The exit status of the yadm command YADM_HOOK_FULL_COMMAND The yadm command with all command line arguments YADM_HOOK_REPO The path to the yadm repository YADM_HOOK_WORK The path to the work-tree ## FILES The following are the default paths yadm uses for its own data. These paths can be altered using universal options. See the OPTIONS section for details. $HOME/.yadm The yadm directory. By default, all data yadm stores is relative to this directory. $YADM_DIR/config Configuration file for yadm. $YADM_DIR/repo.git Git repository used by yadm. $YADM_DIR/encrypt List of globs used for encrypt/decrypt $YADM_DIR/files.gpg All files encrypted with yadm encrypt are stored in this file. ## EXAMPLES yadm init Create an empty repo for managing files yadm add .bash_profile ; yadm commit Add .bash_profile to the Git index and create a new commit yadm remote add origin Add a remote origin to an existing repository yadm push -u origin master Initial push of master to origin echo .ssh/*.key >> $HOME/.yadm/encrypt Add a new pattern to the list of encrypted files yadm encrypt ; yadm add ~/.yadm/files.gpg ; yadm commit Commit a new set of encrypted files ## REPORTING BUGS Report issues or create pull requests at GitHub: https://github.com/TheLocehiliosan/yadm/issues ## AUTHOR Tim Byrne ## SEE ALSO git(1), gpg(1) https://thelocehiliosan.github.io/yadm/ yadm-1.12.0/yadm.spec000066400000000000000000000044511317400042700143350ustar00rootroot00000000000000Summary: Yet Another Dotfiles Manager Name: yadm Version: 1.12.0 Release: 1%{?dist} URL: https://github.com/TheLocehiliosan/yadm License: GPLv3 BuildRequires: hostname git gnupg bats expect Requires: bash hostname git Source: https://github.com/TheLocehiliosan/%{name}/archive/%{version}.tar.gz#/%{name}-%{version}.tar.gz BuildArch: noarch %description yadm is a tool for managing a collection of files across multiple computers, using a shared Git repository. In addition, yadm provides a feature to select alternate versions of files based on the operation system or host name. Lastly, yadm supplies the ability to manage a subset of secure files, which are encrypted before they are included in the repository. %prep %setup -q %build %check bats test %install mkdir -p ${RPM_BUILD_ROOT}%{_bindir} mkdir -p ${RPM_BUILD_ROOT}%{_mandir}/man1 install -m 755 yadm ${RPM_BUILD_ROOT}%{_bindir} install -m 644 yadm.1 ${RPM_BUILD_ROOT}%{_mandir}/man1 %files %attr(755,root,root) %{_bindir}/yadm %attr(644,root,root) %{_mandir}/man1/* %license LICENSE %doc CHANGES CONTRIBUTORS README.md completion/* %changelog * Wed Oct 25 2017 Tim Byrne - 1.12.0-1 - Bump version to 1.12.0 - Include zsh completion * Wed Aug 23 2017 Tim Byrne - 1.11.1-1 - Bump version to 1.11.1 * Mon Jul 10 2017 Tim Byrne - 1.11.0-1 - Bump version to 1.11.0 * Wed May 10 2017 Tim Byrne - 1.10.0-1 - Bump version to 1.10.0 - Transition to semantic versioning * Thu May 4 2017 Tim Byrne - 1.09-1 - Bump version to 1.09 - Add yadm.bash_completion * Mon Apr 3 2017 Tim Byrne - 1.08-1 - Bump version to 1.08 * Fri Feb 10 2017 Tim Byrne - 1.07-1 - Bump version to 1.07 * Fri Jan 13 2017 Tim Byrne - 1.06-1 - Bump version to 1.06 * Tue May 17 2016 Tim Byrne - 1.04-3 - Add missing docs - Fix changelog format - Remove file attribute for docs and license * Mon May 16 2016 Tim Byrne - 1.04-2 - Add %%check - Add %%{?dist} - Add build dependencies - Add license and docs - Remove %%defattr - Remove group tag - Sync RPM description with man page * Fri Apr 22 2016 Tim Byrne - 1.04-1 - Initial RPM release