From 51abffbf6a4e5e230e6064ef0095f21e0d3c8712 Mon Sep 17 00:00:00 2001 From: Matthew Dolan Date: Wed, 4 Mar 2026 14:58:31 -0800 Subject: [PATCH 1/5] Add include wrappers for dotfiles --- home/{.gitconfig => .gitconfig.dolan} | 0 home/{.zshenv => .zshenv.dolan} | 0 home/{.zshrc => .zshrc.dolan} | 0 home/bin/dol | 79 ++++++++++++++ install.sh | 0 tests/install.bats | 147 ++++++++++++++++++++++++++ tests/lint.bats | 13 +++ 7 files changed, 239 insertions(+) rename home/{.gitconfig => .gitconfig.dolan} (100%) rename home/{.zshenv => .zshenv.dolan} (100%) rename home/{.zshrc => .zshrc.dolan} (100%) mode change 100755 => 100644 install.sh create mode 100644 tests/install.bats diff --git a/home/.gitconfig b/home/.gitconfig.dolan similarity index 100% rename from home/.gitconfig rename to home/.gitconfig.dolan diff --git a/home/.zshenv b/home/.zshenv.dolan similarity index 100% rename from home/.zshenv rename to home/.zshenv.dolan diff --git a/home/.zshrc b/home/.zshrc.dolan similarity index 100% rename from home/.zshrc rename to home/.zshrc.dolan diff --git a/home/bin/dol b/home/bin/dol index 0ddda8d..7543e66 100755 --- a/home/bin/dol +++ b/home/bin/dol @@ -117,6 +117,83 @@ parse_common_flags() { done } +managed_block_start="# >>> ~/.dotfiles/install.sh >>>" +managed_block_end="# <<< ~/.dotfiles/install.sh <<<" + +ensure_local_dotfile() { + local target="$1" + + if [[ -d "${target}" ]]; then + echo "Cannot manage ${target}: it is a directory." + return 1 + fi + + # Older installs symlinked these files directly; replace with a fresh local file. + if [[ -L "${target}" ]]; then + echo " Replacing symlinked $(basename "${target}") with a local file..." + rm -f "${target}" + : > "${target}" + return 0 + fi + + if [[ ! -e "${target}" ]]; then + : > "${target}" + fi +} + +strip_managed_block() { + local source_file="$1" + local destination_file="$2" + + awk -v start="${managed_block_start}" -v end="${managed_block_end}" ' + $0 == start { + in_block=1 + next + } + in_block && $0 == end { + in_block=0 + next + } + !in_block { + print + } + ' "${source_file}" > "${destination_file}" +} + +prepend_managed_block() { + local target="$1" + shift + local block_lines=("$@") + local stripped_file + local line + + ensure_local_dotfile "${target}" + + stripped_file="$(mktemp "${TMPDIR:-/tmp}/dotfiles-install.XXXXXX")" + strip_managed_block "${target}" "${stripped_file}" + + { + printf '%s\n' "${managed_block_start}" + for line in "${block_lines[@]}"; do + printf '%s\n' "${line}" + done + printf '%s\n' "${managed_block_end}" + if [[ -s "${stripped_file}" ]]; then + printf '\n' + cat "${stripped_file}" + fi + } > "${target}" + + rm -f "${stripped_file}" +} + +configure_managed_dotfiles() { + echo "Configuring managed include blocks..." + prepend_managed_block "${HOME}/.zshrc" "[ -f \"\$HOME/.zshrc.dolan\" ] && . \"\$HOME/.zshrc.dolan\"" + prepend_managed_block "${HOME}/.zshenv" "[ -f \"\$HOME/.zshenv.dolan\" ] && . \"\$HOME/.zshenv.dolan\"" + prepend_managed_block "${HOME}/.gitconfig" '[include]' ' path = ~/.gitconfig.dolan' +} + run_dotfiles_install() { local home_src local home_dst @@ -165,6 +242,8 @@ run_dotfiles_install() { ln -sf "${file}" "${dest_file}" done + configure_managed_dotfiles + os_name="$(uname)" if [[ "${os_name}" == "Darwin" ]]; then echo "Installing Mac OS Software..." diff --git a/install.sh b/install.sh old mode 100755 new mode 100644 diff --git a/tests/install.bats b/tests/install.bats new file mode 100644 index 0000000..b713a48 --- /dev/null +++ b/tests/install.bats @@ -0,0 +1,147 @@ +#!/usr/bin/env bats + +repo_root="$(git rev-parse --show-toplevel)" + +setup() { + tmp_home="$(mktemp -d "${TMPDIR:-/tmp}/dotfiles-install-test.XXXXXX")" + fake_bin="${tmp_home}/fake-bin" + mkdir -p "${fake_bin}" "${tmp_home}/.oh-my-zsh" + + cat > "${fake_bin}/uname" <<'EOF' +#!/bin/zsh +echo "Linux" +EOF + chmod +x "${fake_bin}/uname" + + real_git="$(command -v git)" + cat > "${fake_bin}/git" < "${tmp_home}/.zshrc" <<'EOF' +export KEEP_THIS_LINE=1 +EOF + + run run_install + [ "${status}" -eq 0 ] + + run tail -n 1 "${tmp_home}/.zshrc" + [ "${status}" -eq 0 ] + [ "${output}" = "export KEEP_THIS_LINE=1" ] +} + +@test "stale managed blocks are replaced with current include content" { + cat > "${tmp_home}/.zshrc" <<'EOF' +# >>> ~/.dotfiles/install.sh >>> +[ -f "$HOME/.old-zshrc" ] && . "$HOME/.old-zshrc" +# <<< ~/.dotfiles/install.sh <<< + +export STILL_HERE=1 +EOF + + run run_install + [ "${status}" -eq 0 ] + + run grep -c '^# >>> ~/.dotfiles/install.sh >>>$' "${tmp_home}/.zshrc" + [ "${status}" -eq 0 ] + [ "${output}" -eq 1 ] + + run grep -c 'old-zshrc' "${tmp_home}/.zshrc" + [ "${status}" -eq 1 ] + + run sed -n '1,3p' "${tmp_home}/.zshrc" + [ "${status}" -eq 0 ] + [ "${lines[1]}" = "[ -f \"\$HOME/.zshrc.dolan\" ] && . \"\$HOME/.zshrc.dolan\"" ] +} + +@test "existing symlinked dotfiles are unlinked and replaced with local files" { + ln -s "${repo_root}/home/.zshrc.dolan" "${tmp_home}/.zshrc" + ln -s "${repo_root}/home/.zshenv.dolan" "${tmp_home}/.zshenv" + ln -s "${repo_root}/home/.gitconfig.dolan" "${tmp_home}/.gitconfig" + + run run_install + [ "${status}" -eq 0 ] + + [ -f "${tmp_home}/.zshrc" ] + [ ! -L "${tmp_home}/.zshrc" ] + [ -f "${tmp_home}/.zshenv" ] + [ ! -L "${tmp_home}/.zshenv" ] + [ -f "${tmp_home}/.gitconfig" ] + [ ! -L "${tmp_home}/.gitconfig" ] +} + +@test "re-running install does not duplicate managed blocks" { + run run_install + [ "${status}" -eq 0 ] + + run run_install + [ "${status}" -eq 0 ] + + run grep -c '^# >>> ~/.dotfiles/install.sh >>>$' "${tmp_home}/.zshrc" + [ "${status}" -eq 0 ] + [ "${output}" -eq 1 ] + + run grep -c '^# >>> ~/.dotfiles/install.sh >>>$' "${tmp_home}/.zshenv" + [ "${status}" -eq 0 ] + [ "${output}" -eq 1 ] + + run grep -c '^# >>> ~/.dotfiles/install.sh >>>$' "${tmp_home}/.gitconfig" + [ "${status}" -eq 0 ] + [ "${output}" -eq 1 ] +} diff --git a/tests/lint.bats b/tests/lint.bats index fa59662..faf84bc 100644 --- a/tests/lint.bats +++ b/tests/lint.bats @@ -5,6 +5,12 @@ repo_root="$(git rev-parse --show-toplevel)" check_script() { local file="$1" local shebang + + if [[ "${file}" == *.bats ]]; then + bats --count "${file}" >/dev/null + return 0 + fi + shebang="$(head -n1 "${file}")" if [[ "${shebang}" == *zsh* ]] && command -v zsh >/dev/null 2>&1; then zsh -n "${file}" @@ -31,6 +37,13 @@ done < <(find "${repo_root}/tests" -name '*.bats' -type f || true) scripts+=("${bats_tests[@]}") +# De-duplicate script paths in case a Bats test contains heredocs with shebangs. +unique_scripts=() +while IFS= read -r line; do + unique_scripts+=("${line}") +done < <(printf '%s\n' "${scripts[@]}" | awk 'NF && !seen[$0]++') +scripts=("${unique_scripts[@]}") + for script in "${scripts[@]}"; do bats_test_function --description "${script}" -- check_script "${script}" done From 83645b0a4c6adffeaaf8faf93ef270f6fe9c7ed5 Mon Sep 17 00:00:00 2001 From: Matthew Dolan Date: Wed, 4 Mar 2026 15:11:32 -0800 Subject: [PATCH 2/5] Restore install.sh executable mode --- install.sh | 0 1 file changed, 0 insertions(+), 0 deletions(-) mode change 100644 => 100755 install.sh diff --git a/install.sh b/install.sh old mode 100644 new mode 100755 From 72017a37f16f4b757479f80f2631215164b82fc5 Mon Sep 17 00:00:00 2001 From: Matthew Dolan Date: Wed, 4 Mar 2026 15:16:53 -0800 Subject: [PATCH 3/5] Make install tests portable when zsh is unavailable --- tests/install.bats | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/tests/install.bats b/tests/install.bats index b713a48..d16b817 100644 --- a/tests/install.bats +++ b/tests/install.bats @@ -35,7 +35,15 @@ teardown() { } run_install() { - env "${install_env[@]}" zsh "${repo_root}/install.sh" + local shell_bin + + if command -v zsh >/dev/null 2>&1; then + shell_bin="$(command -v zsh)" + else + shell_bin="$(command -v bash)" + fi + + env "${install_env[@]}" "${shell_bin}" "${repo_root}/install.sh" } @test "fresh install prepends managed include blocks" { From c292320cb166f93475ab9c1031d33889dbe8b2c1 Mon Sep 17 00:00:00 2001 From: Matthew Dolan Date: Wed, 4 Mar 2026 15:19:26 -0800 Subject: [PATCH 4/5] Avoid bats run helper warning in install tests --- tests/install.bats | 28 +++++++++++----------------- 1 file changed, 11 insertions(+), 17 deletions(-) diff --git a/tests/install.bats b/tests/install.bats index d16b817..8604385 100644 --- a/tests/install.bats +++ b/tests/install.bats @@ -28,26 +28,20 @@ EOF "PATH=${fake_bin}:${PATH}" "DOLAN_USE_HERMIT=false" ) -} - -teardown() { - rm -rf "${tmp_home}" -} - -run_install() { - local shell_bin if command -v zsh >/dev/null 2>&1; then - shell_bin="$(command -v zsh)" + install_shell="$(command -v zsh)" else - shell_bin="$(command -v bash)" + install_shell="$(command -v bash)" fi +} - env "${install_env[@]}" "${shell_bin}" "${repo_root}/install.sh" +teardown() { + rm -rf "${tmp_home}" } @test "fresh install prepends managed include blocks" { - run run_install + run env "${install_env[@]}" "${install_shell}" "${repo_root}/install.sh" [ "${status}" -eq 0 ] [ -L "${tmp_home}/.zshrc.dolan" ] @@ -86,7 +80,7 @@ run_install() { export KEEP_THIS_LINE=1 EOF - run run_install + run env "${install_env[@]}" "${install_shell}" "${repo_root}/install.sh" [ "${status}" -eq 0 ] run tail -n 1 "${tmp_home}/.zshrc" @@ -103,7 +97,7 @@ EOF export STILL_HERE=1 EOF - run run_install + run env "${install_env[@]}" "${install_shell}" "${repo_root}/install.sh" [ "${status}" -eq 0 ] run grep -c '^# >>> ~/.dotfiles/install.sh >>>$' "${tmp_home}/.zshrc" @@ -123,7 +117,7 @@ EOF ln -s "${repo_root}/home/.zshenv.dolan" "${tmp_home}/.zshenv" ln -s "${repo_root}/home/.gitconfig.dolan" "${tmp_home}/.gitconfig" - run run_install + run env "${install_env[@]}" "${install_shell}" "${repo_root}/install.sh" [ "${status}" -eq 0 ] [ -f "${tmp_home}/.zshrc" ] @@ -135,10 +129,10 @@ EOF } @test "re-running install does not duplicate managed blocks" { - run run_install + run env "${install_env[@]}" "${install_shell}" "${repo_root}/install.sh" [ "${status}" -eq 0 ] - run run_install + run env "${install_env[@]}" "${install_shell}" "${repo_root}/install.sh" [ "${status}" -eq 0 ] run grep -c '^# >>> ~/.dotfiles/install.sh >>>$' "${tmp_home}/.zshrc" From 9c1054b4f5f0304c832ace4df68cbd59a4360680 Mon Sep 17 00:00:00 2001 From: Matthew Dolan Date: Wed, 4 Mar 2026 15:22:12 -0800 Subject: [PATCH 5/5] Stabilize install tests on runners without zsh --- tests/install.bats | 29 ++++++++++++++++++----------- 1 file changed, 18 insertions(+), 11 deletions(-) diff --git a/tests/install.bats b/tests/install.bats index 8604385..dd97bc0 100644 --- a/tests/install.bats +++ b/tests/install.bats @@ -8,15 +8,15 @@ setup() { mkdir -p "${fake_bin}" "${tmp_home}/.oh-my-zsh" cat > "${fake_bin}/uname" <<'EOF' -#!/bin/zsh +#!/bin/sh echo "Linux" EOF chmod +x "${fake_bin}/uname" real_git="$(command -v git)" cat > "${fake_bin}/git" </dev/null 2>&1; then - install_shell="$(command -v zsh)" + install_command=( + "$(command -v zsh)" + "${repo_root}/install.sh" + ) else - install_shell="$(command -v bash)" + install_command=( + "$(command -v bash)" + "${repo_root}/home/bin/dol" + install + ) fi } @@ -41,7 +48,7 @@ teardown() { } @test "fresh install prepends managed include blocks" { - run env "${install_env[@]}" "${install_shell}" "${repo_root}/install.sh" + run env "${install_env[@]}" "${install_command[@]}" [ "${status}" -eq 0 ] [ -L "${tmp_home}/.zshrc.dolan" ] @@ -80,7 +87,7 @@ teardown() { export KEEP_THIS_LINE=1 EOF - run env "${install_env[@]}" "${install_shell}" "${repo_root}/install.sh" + run env "${install_env[@]}" "${install_command[@]}" [ "${status}" -eq 0 ] run tail -n 1 "${tmp_home}/.zshrc" @@ -97,7 +104,7 @@ EOF export STILL_HERE=1 EOF - run env "${install_env[@]}" "${install_shell}" "${repo_root}/install.sh" + run env "${install_env[@]}" "${install_command[@]}" [ "${status}" -eq 0 ] run grep -c '^# >>> ~/.dotfiles/install.sh >>>$' "${tmp_home}/.zshrc" @@ -117,7 +124,7 @@ EOF ln -s "${repo_root}/home/.zshenv.dolan" "${tmp_home}/.zshenv" ln -s "${repo_root}/home/.gitconfig.dolan" "${tmp_home}/.gitconfig" - run env "${install_env[@]}" "${install_shell}" "${repo_root}/install.sh" + run env "${install_env[@]}" "${install_command[@]}" [ "${status}" -eq 0 ] [ -f "${tmp_home}/.zshrc" ] @@ -129,10 +136,10 @@ EOF } @test "re-running install does not duplicate managed blocks" { - run env "${install_env[@]}" "${install_shell}" "${repo_root}/install.sh" + run env "${install_env[@]}" "${install_command[@]}" [ "${status}" -eq 0 ] - run env "${install_env[@]}" "${install_shell}" "${repo_root}/install.sh" + run env "${install_env[@]}" "${install_command[@]}" [ "${status}" -eq 0 ] run grep -c '^# >>> ~/.dotfiles/install.sh >>>$' "${tmp_home}/.zshrc"