git-scaffold

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

Learn more