Getting started
Install
With Go on the machine, one command installs the latest release:
go install github.com/stephenc/git-scaffold@latest
Or take a prebuilt binary from the releases page. On Linux and macOS:
dir=$(mktemp -d) &&
archive="git-scaffold-$(uname -s)-$(uname -m).tar.gz" &&
curl -fsSL -o "$dir/$archive" \
"https://github.com/stephenc/git-scaffold/releases/latest/download/$archive" &&
curl -fsSL -o "$dir/$archive.sha256" \
"https://github.com/stephenc/git-scaffold/releases/latest/download/$archive.sha256" &&
if command -v sha256sum >/dev/null
then (cd "$dir" && sha256sum -c "$archive.sha256")
else (cd "$dir" && shasum -a 256 -c "$archive.sha256")
fi &&
tar -xzf "$dir/$archive" -C "$dir" git-scaffold &&
mkdir -p ~/.local/bin &&
install -m 755 "$dir/git-scaffold" ~/.local/bin/git-scaffold
[ -n "$dir" ] && rm -rf "$dir"
command -v git-scaffold
These commands write into a temporary directory, not the directory you are
in. The two curl lines take the archive and the
.sha256 file that the release holds beside it, and the check
compares the two. install puts git-scaffold in
~/.local/bin, replacing a git-scaffold that is
already there. If command -v git-scaffold shows nothing, add
~/.local/bin to the path of this shell.
On Windows, take git-scaffold-Windows-x86_64.zip (or
git-scaffold-Windows-arm64.zip for an ARM machine), check it
against the .sha256 file beside it, and put the
git-scaffold.exe that it holds in a directory on your
PATH.
The binaries hold no dynamic library, so each one runs on any machine of
its system and architecture. Every command needs git on the
path. Once git-scaffold is on the path, Git finds it as a
subcommand: git scaffold ….
Initialize from a scaffold
Inside a Git worktree, point init at an upstream scaffold
repository — any repository whose own .git-scaffold/config.toml
declares the files it manages:
git scaffold init https://github.com/acme/go-template.git \
--arg project_name=orders \
--arg module=github.com/acme/orders
This creates .git-scaffold/config.toml, resolves the source,
materializes every managed file, and writes the exact upstream commit to
.git-scaffold/lock. Arguments without a source-side default are
required; if one is missing, init fails clearly and tells you which. Add
--ref <ref> to track a branch or tag other than the remote
HEAD.
Commit all of .git-scaffold/ along with the materialized
files.
The everyday cycle
git scaffold check # exit 0: managed files match the locked materialization
git scaffold diff # unified diff of working tree vs. locked materialization
git scaffold apply # make the working tree match what is locked
git scaffold update # re-resolve the ref and move to the new commit
git scaffold outdated # exit 1 when an update is available
check, diff, and apply work against
the locked commit — they never advance the lock just because the
configured branch moved, and they avoid network access when the locked commit
is available locally. Moving forward is always an explicit act:
$ git scaffold update
Updating scaffold
source: https://github.com/acme/go-template.git
ref: main
71db8b7 → 93ca782
M .golangci.yml
M Makefile
A .github/workflows/security.yml
D .github/workflows/old-ci.yml
Updates are transactional: the complete new result is constructed and
validated first, and on any failure — an argument problem, a patch that no
longer applies — no files change and the lock stays where it was. Use
git scaffold outdated in CI or cron to detect available updates
cheaply, and git scaffold propose to turn an update into a
pushed branch and pull request.
Adopting an existing repository
A repository that already has content — its own Makefile, its
own workflows — can come under scaffold management without a scary overwrite
step:
git scaffold init --existing https://github.com/acme/go-template.git \
--arg project_name=orders \
--arg module=github.com/acme/orders
Any managed file that differs from the scaffold's materialization is
captured as an explicit override under .git-scaffold/patches/:
a structured json-patch where the scaffold permits it for a
JSON or YAML file and both sides parse (the file is normalized to the
canonical serialization), otherwise a text-patch — a unified
diff from materialized to existing content — with the pre-existing file
left untouched. Pass --text-patch to capture everything as text
patches. git scaffold check is clean immediately, and every
divergence is now a visible patch you can whittle away over time.
Later, when you hand-edit a managed file and want to keep the change,
git scaffold repatch rewrites the overrides from the working
tree so check is clean again — see the
command reference.
--force alongside --existing overwrites only
those binary files — text-adoptable files are still adopted, not overwritten.
Next steps
- Command reference — flags, exit codes, and behavior for every subcommand.
- Configuration — write your own scaffold source, declare patches, tune the tool.