ifchanged

command module
v1.0.1 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Apr 4, 2026 License: MIT Imports: 12 Imported by: 0

README

ifchanged

Run shell commands when files change.

Overview

ifchanged hashes files on each run and executes configured commands for any that changed since last time. It's for automation that should trigger on file change. applying SQL migrations, regenerating code, syncing assets, without setting up a full watch daemon or CI pipeline.

It is not a daemon. It does not watch in the background. Run it manually, from a Makefile, or in a pre-commit hook.

Installation

With a Go toolchain installed:

go install github.com/Lexographics/ifchanged@latest

The binary is placed in $(go env GOPATH)/bin. Ensure that directory is on your PATH.

Build from source:

git clone github.com/Lexographics/ifchanged
cd ifchanged
go build -o ifchanged .

Usage

ifchanged [flags] [dir]

Without arguments, ifchanged searches the current directory for *.ifchanged.yaml files, hashes the matched files, and runs commands for any that changed.

# Run in current directory
ifchanged

# Run against a specific directory (searches that dir for *.ifchanged.yaml files)
ifchanged ./src

# Preview what would run without executing
ifchanged --dry-run

# Clear stored hashes then immediately run commands (force re-run)
ifchanged --clean

# Clear stored hashes and exit (next run will treat all files as changed)
ifchanged clean

# Use a specific config file
ifchanged --config deploy.ifchanged.yaml

# Use a custom state file
ifchanged --state ./.ifchanged-state.yaml

Examples

See ./examples/ for runnable example directories (SQL migrations, SQL RLS policies etc.).

Config file format

Config files must match *.ifchanged.yaml. Place them in the directory you run ifchanged from.

# examples/sql-migrations/migrations.ifchanged.yaml
include:
  - sql/migrations/**/*.sql

exclude:
  - sql/migrations/scratch.sql

command:
  - psql -U postgres -d myapp -f {{file}}
  • include — glob patterns for files to watch. ** matches any number of path segments.
  • exclude — glob patterns to skip. Evaluated after include.
  • command — shell commands to run for each changed matched file. Placeholders are expanded per file (see Command placeholders). Commands run sequentially; if one fails, the rest are skipped for that file and the hash is not updated.
  • has-changes — optional. Shell commands run once after processing if any matched file changed. Only global placeholders apply.
  • no-changes — optional. Shell commands run once if no matched file changed. Only global placeholders apply.
  • state — optional. Override the state file path for this config.

If any per-file command fails, ifchanged does not update the stored hash for that file, so it will be retried on the next run.

Command placeholders

These strings are substituted in command strings before they are passed to the shell.

Per-file placeholders (command)

Expanded once per changed file; paths use the host’s native separators except {{relFile}} and {{relFileDir}}, which use forward slashes.

Placeholder Meaning
{{file}} Absolute path to the matched file
{{fileName}} Base name (e.g. user_triggers.sql)
{{fileStem}} Filename without extension (e.g. user_triggers)
{{fileExt}} Extension including the dot (e.g. .sql)
{{fileDir}} Directory containing the file (absolute)
{{relFile}} Path to the file relative to the directory you run ifchanged against
{{relFileDir}} Directory part of {{relFile}}
Global placeholders (has-changes, no-changes)
Placeholder Meaning
{{workingDirectory}} The directory ifchanged uses as the run root (the dir argument, or the current working directory)

Flags

Flag Default Description
-config - Path to a specific *.ifchanged.yaml file
-state ~/.ifchanged.yaml Path to state file
-env-file - Path to a .env file to load for all commands
-dry-run false Print commands that would run; don't execute or update state
-clean false Clear stored hashes for matched files, then run commands

Subcommands

ifchanged clean [dir]

Clears stored hashes for all matched files and exits without running any commands. Use this to force a full re-run on the next invocation.

Unlike --clean, which clears hashes and then immediately runs commands, the clean subcommand only resets state.

State file

State is stored at ~/.ifchanged.yaml by default — a YAML map of configpath:filepath → SHA256 hash. Written atomically (temp file + rename). You can commit it, ignore it, or set a per-project path with --state.

Real-world examples

Apply SQL migrations only when files change:

# examples/sql-migrations/migrations.ifchanged.yaml
include:
  - db/migrations/**/*.sql
command:
  - psql $DATABASE_URL -f {{file}}
ifchanged  # only runs psql for new/modified migration files

Regenerate protobuf bindings:

# examples/protobuf-generation/proto.ifchanged.yaml
include:
  - proto/**/*.proto
command:
  - protoc --go_out=. --go-grpc_out=. {{file}}

Reset and force re-run everything:

ifchanged --clean        # clear hashes, then run commands immediately
ifchanged clean          # clear hashes and exit (commands run on next invocation)

Dry run to audit what would execute:

ifchanged --dry-run

Contributing

Contributions are welcome! Please open issues or pull requests for bug fixes, features, or documentation improvements.

License

This project is licensed under the MIT License - see the LICENSE file for details.

Documentation

The Go Gopher

There is no documentation for this package.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL