Scaffolding that stays maintained
git-scaffold maintains a selected set of files in a Git
repository from an upstream Git repository — not a one-shot
generator. The relationship with the upstream scaffold persists, and
targets explicitly update to later versions of their configured source.
# installed as git-scaffold, it runs as a Git subcommand
git scaffold init https://github.com/acme/go-template.git
git scaffold init --existing https://github.com/acme/go-template.git
git scaffold check # do managed files match the locked materialization?
git scaffold diff # show the differences
git scaffold update # move to the latest upstream commit, transactionally
git scaffold outdated # has the upstream ref advanced past the lock?
git scaffold repatch # turn local edits into explicit override patches
git scaffold propose # push an update as a branch and open a pull request
Sixty seconds
A scaffold is an ordinary Git repository with a descriptor that names the files it manages and the arguments consumers fill in:
# template/.git-scaffold/config.toml
[scaffold]
version = 1
[[arguments]]
name = "project"
token = "@@PROJECT@@"
[[files]]
path = "Makefile"
[[files]]
path = ".github/workflows/*.yml"
Wherever @@PROJECT@@ appears in Makefile or a
workflow, the consumer's value is substituted. A consumer points at the
scaffold and supplies the arguments:
# orders/.git-scaffold/config.toml
[scaffold]
version = 1
[source]
git = "https://github.com/acme/template.git"
ref = "main"
[args]
project = "orders"
git scaffold init writes that file for you; from then on:
git scaffold check # do my managed files match the locked scaffold commit?
git scaffold outdated # has the scaffold moved on since I locked it?
git scaffold update # bring the managed files to the current scaffold commit
The scaffold commit in use is pinned in .git-scaffold/lock, so
update is an explicit, reviewable step — never a surprise.
Already have 37 slightly different repositories?
That is the normal case, and it is the one
git scaffold init --existing is built for:
cd orders
git scaffold init --existing https://github.com/acme/template.git --arg project=orders
git scaffold check # ✅️ clean, immediately
Every managed file that already differs from the scaffold is captured as an
explicit override under .git-scaffold/patches/ and registered in
.git-scaffold/config.toml. Nothing you already have is lost,
check is clean from the first second, and every divergence is now
a visible patch you can whittle away over time — or keep, deliberately, as the
documented way this repository differs. Repeat across the fleet and
git scaffold update starts working for all of them.
JSON and YAML files the scaffold permits json-patch for are
captured as structured RFC 6902 patches where both sides parse; everything
else becomes a text-patch with the file left byte-for-byte
untouched. See init --existing for the
details and the --text-patch escape hatch.
How it works
A target repository declares, in .git-scaffold/config.toml, an
upstream scaffold repository, values for the arguments that scaffold defines,
and explicit local patches where the target intentionally differs. The
upstream repository's own .git-scaffold/config.toml declares which
files it manages, which arguments targets may or must provide, and which files
may be patched.
The exact upstream commit in use is recorded in
.git-scaffold/lock. Given the configuration, the locked commit,
the argument values, and the patches, the contents of every managed file are
deterministic. Manual edits to managed files are not a customization
mechanism — git scaffold check reports them as discrepancies.
Highlights
-
Deterministic materialization
A lock file pins the exact upstream commit. Config + lock + arguments + patches fully determine every managed file, byte for byte. A moving branch never silently changes output.
-
Divergence is explicit
Local differences live in argument values and declared patches, never in silent edits.
text-patchis always available as the universal escape hatch; structured strategies likejson-patchare opt-in per file rule. -
Transactional updates
Every modifying operation validates the complete result before touching the working tree. A failed update changes nothing — no half-updated files, no advanced lock.
-
Adopt existing repositories
git scaffold init --existingbrings a repository that already has content under management, guaranteed: every divergence is captured as a visible patch andcheckis clean immediately. -
Fleet-friendly
git-scaffoldoperates on one repository. Fleet tools such asgitscan run it across an organization:gits --repo-contains example-org git scaffold update. -
No template language
Configuration is declarative TOML; substitution is literal token replacement. No expressions, no hooks, no scripting — scaffold behavior a reader can read, not compute.
Learn more
- Getting started — install and walk through the full cycle.
- Commands — reference for every subcommand.
- Configuration — target config, source descriptor, patches, lock file.
- Design — the principles and the central invariant.