Skip to content

Atomic conflict pre-flight, --force hardening, and release tooling#1

Merged
davetothek merged 7 commits into
mainfrom
safety-atomicity-and-release-tooling
Jun 21, 2026
Merged

Atomic conflict pre-flight, --force hardening, and release tooling#1
davetothek merged 7 commits into
mainfrom
safety-atomicity-and-release-tooling

Conversation

@davetothek

Copy link
Copy Markdown
Owner

Summary

Hardens stow.sh's safety model to match GNU Stow's core guarantee, makes the test suite actually gate changes, and adds real release artifacts.

Safety (the headline)

  • Atomic, all-or-nothing operations. main() runs every stow/unstow/restow through a mutation-free pre-flight that detects conflicts across all packages; if any unresolved conflict exists it aborts with zero changes, then applies for real only when the pre-flight is clean. Previously a conflict mid-run left a half-applied tree.
  • --force hardening. No longer recursively deletes a real directory or anything resolving outside the target dir (rm -rfrm -f). Guards run during pre-flight, so a --force conflict aborts cleanly instead of half-applying.
  • Removed --defer/--override. They were parsed and documented but never read anywhere — accepting them did nothing. They now error as unknown options.

Making tests matter

  • New ci.yml: shellcheck + bats on every push/PR (previously tests only ran at tag time).
  • All shell sources are now shellcheck-clean.
  • Fixed a latent filter.bats bug (non-exported TEST_REPO) that failed all 32 filter tests on modern bats.
  • New hooks/pre-commit runs shellcheck + tests.
  • New atomicity / force-guard integration tests.

Release artifacts

  • scripts/bundle.sh (+ make bundle) assembles all modules and built-in conditions into one self-contained stow.sh.
  • release.yml now lints, tests, and attaches: single-file bundle, source tarball, and SHA256SUMS.

Docs

  • README: newcomer "What is this?" intro, honest "Differences from GNU Stow" table, single-file install, atomic behavior.
  • CONTRIBUTING/AGENTS: pre-commit hook, lint/bundle workflow, pre-flight architecture, corrected test counts.

Breaking changes

  • A conflict now aborts the entire run instead of applying what it can.
  • --force no longer overwrites real directories.
  • --defer / --override removed.

Verification

  • make lint passes; 276/276 tests pass.
  • Bundle builds, is shellcheck-clean, and passes all 68 integration tests when swapped in as the entrypoint.

🤖 Generated with Claude Code

davidKristiansen and others added 7 commits June 21, 2026 20:21
Add `# shellcheck shell=bash` directives to the sourced modules and
condition plugins (they have no shebang, so shellcheck couldn't infer the
dialect), remove a dead `candidate_set` map in fold.sh, and add scoped,
justified disables for intentional glob matches (filter.sh) and nameref
indirection / cross-module use (fold.sh, version.sh).

No behavior change.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Three related safety changes to the core engine:

1. Atomic all-or-nothing operations. main() now runs every stow/unstow/
   restow through a mutation-free pre-flight pass (_stow_sh_preflight +
   dry-run) that detects conflicts across all packages; if any unresolved
   conflict exists it aborts with ZERO changes, then applies for real only
   when the pre-flight is clean. Previously a conflict mid-run left a
   partially-applied tree. This matches GNU Stow's core guarantee.

2. Harden --force. __create_link now refuses to delete a path that resolves
   outside the target directory, and refuses to recursively delete a real
   directory (rm -rf -> rm -f). Both guards run before the dry-run return so
   they are caught during pre-flight rather than half-applied.

3. Remove --defer/--override. They were parsed and documented but never read
   anywhere — accepting them silently did nothing. They now error as unknown
   options instead of lying.

BREAKING CHANGE: a conflict now aborts the entire run instead of applying
what it can; --force no longer overwrites real directories; --defer and
--override are removed.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
bats runs setup_file() in a separate shell from setup() and the tests, so
the non-exported TEST_REPO was empty there — every filter test failed with
"cd: null directory" on modern bats (1.13). Export it.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The only workflow ran at tag time, so regressions on main were invisible
until a release. Add a CI workflow that runs shellcheck and the bats suite
on every push and PR.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
- make lint (shellcheck, required; shfmt -d, advisory), make fmt, make
  bundle, make clean; release target now gates on lint.
- scripts/bundle.sh assembles all modules + built-in conditions into one
  self-contained stow.sh executable.
- release.yml now lints, tests, and attaches three artifacts: the single-file
  bundle, the source tarball, and SHA256SUMS.
- .gitignore now ignores /dist/.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Installed by `make hooks`. Runs shellcheck then the bats suite and blocks
the commit on failure; missing tools are skipped with a warning (CI is the
backstop).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…methods

- README: add a newcomer "What is this?" intro with a before/after example;
  replace "Everything GNU Stow does" with an honest "Differences from GNU
  Stow" table; document the single-file install and atomic behavior.
- CONTRIBUTING/AGENTS: document the pre-commit hook, lint/bundle workflow,
  the atomic pre-flight architecture, scripts/, and corrected test counts.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@davetothek
davetothek merged commit c0c2b73 into main Jun 21, 2026
4 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants