git-scaffold

command module
v0.1.1 Latest Latest
Warning

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

Go to latest
Published: Aug 28, 2026 License: Apache-2.0 Imports: 1 Imported by: 0

README

git-scaffold

CI Release Go Reference License

git-scaffold maintains a selected set of files in a Git repository from an upstream Git repository. It is not a one-shot project generator: the relationship with the upstream scaffold persists, and targets can 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
git scaffold diff
git scaffold apply
git scaffold update
git scaffold outdated
git scaffold repatch
git scaffold propose

See DESIGN.md for the full specification.

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.

Where the scaffold permits json-patch for a JSON or YAML file and both sides parse, the difference is captured as a structured RFC 6902 patch and the file is normalized to the canonical serialization; everything else becomes a text-patch with the file left byte-for-byte untouched. text-patch is always available to targets as the universal escape hatch; structured strategies such as json-patch require the scaffold to permit them per file rule.

YAML and automatic json-patch. Applying a json-patch to YAML canonicalizes the whole document: comments are dropped, anchors expanded, and YAML 1.1 scalars such as on, yes or 0755 resolved. So init --existing and repatch generate a json-patch for a YAML file only when the scaffold's own file is already canonical — in practice, free of comments and anchors. A commented .golangci.yml falls back to text-patch even when its rule says patch = "json-patch". This is deliberately conservative for v0.1; pass --text-patch to opt out of structured adoption entirely. If the target already declares a json-patch override for the file, repatch honours that explicit choice regardless of comments. JSON files are unaffected.

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.

Evolving overrides

Managed files are outputs, so hand edits show up as discrepancies in git scaffold check. To keep an edit, run git scaffold repatch: it reads the current content of every managed file and rewrites the overrides and patch files to reproduce it — one patch per file, json-patch where permitted (or text-patch with --text-patch), files that check already accepts left alone, overrides dropped for files returned to the scaffold's content, stale patch files deleted, and the comments in config.toml preserved. check passes right afterwards.

Tool configuration

Global defaults live in $XDG_CONFIG_HOME/git-scaffold/config.toml (~/.config/git-scaffold/config.toml by default):

cache-dir = "~/.cache/git-scaffold"  # where source repos are cached

[update-check]
enabled = true
interval = "24h"

At most once per interval, and only when stderr is a terminal, git-scaffold checks GitHub for a newer release and prints a one-line notice to stderr. Set enabled = false, or the GIT_SCAFFOLD_NO_UPDATE_CHECK or CI environment variables, to disable the check entirely.

Design notes

Rule precedence is deliberately blunt

When file rules in a scaffold descriptor overlap, exactly one form of precedence exists: an exact path overrides a glob that also matches it. There is no "more specific glob beats less specific glob" — two globs (or two exact rules) matching the same file must imply identical behavior, or the descriptor is rejected as ambiguous.

This is by design, not a missing feature. Specificity ordering between patterns invites descriptors whose behavior a reader has to compute rather than read. If one file in a globbed directory needs different treatment, name it — the carve-out is then visible at a glance. The nudge is intentional: scaffold only what you need, with the smallest, most explicit rule set that says it.

License

Apache License 2.0

Documentation

The Go Gopher

There is no documentation for this package.

Directories

Path Synopsis
Package cmd holds the command-line interface of git-scaffold.
Package cmd holds the command-line interface of git-scaffold.
internal
config
Package config parses and validates the shared TOML configuration format of DESIGN.md: the source descriptor (§8, §10, §13-§16) and the target configuration (§4-§6, §11, §23-§24, §44).
Package config parses and validates the shared TOML configuration format of DESIGN.md: the source descriptor (§8, §10, §13-§16) and the target configuration (§4-§6, §11, §23-§24, §44).
engine
Package engine implements the git-scaffold commands over the pure materialization core: locate the worktree, load target configuration and lock, reconstruct expected trees via the source cache, and mutate the working tree transactionally (§31).
Package engine implements the git-scaffold commands over the pure materialization core: locate the worktree, load target configuration and lock, reconstruct expected trees via the source cache, and mutate the working tree transactionally (§31).
gitx
Package gitx shells out to the installed git binary (§6: any Git URL syntax git supports is accepted): worktree discovery, remote ref resolution, and a bare-repository cache of scaffold sources (§50).
Package gitx shells out to the installed git binary (§6: any Git URL syntax git supports is accepted): worktree discovery, remote ref resolution, and a bare-repository cache of scaffold sources (§50).
glob
Package glob matches the path-oriented glob syntax of DESIGN.md §9.1: `*` matches zero or more non-separator characters, `?` exactly one non-separator character, and a `**` path component zero or more complete path components.
Package glob matches the path-oriented glob syntax of DESIGN.md §9.1: `*` matches zero or more non-separator characters, `?` exactly one non-separator character, and a `**` path component zero or more complete path components.
globalcfg
Package globalcfg parses the optional per-user tool configuration in $XDG_CONFIG_HOME/git-scaffold/config.toml (falling back to ~/.config/git-scaffold/config.toml — plain XDG on every platform, matching git's own XDG handling).
Package globalcfg parses the optional per-user tool configuration in $XDG_CONFIG_HOME/git-scaffold/config.toml (falling back to ~/.config/git-scaffold/config.toml — plain XDG on every platform, matching git's own XDG handling).
materialize
Package materialize is the pure materialization core of DESIGN.md §52: source tree + descriptor + target config + patch files in, expected managed tree out.
Package materialize is the pure materialization core of DESIGN.md §52: source tree + descriptor + target config + patch files in, expected managed tree out.
updatecheck
Package updatecheck implements the opt-out self update-check (§56): at most once per configured interval, and only when stderr is a terminal, it asks GitHub for the latest release tag by reading the redirect Location of .../releases/latest — no API client, no JSON — and prints a single notice line to stderr when a newer semver release exists.
Package updatecheck implements the opt-out self update-check (§56): at most once per configured interval, and only when stderr is a terminal, it asks GitHub for the latest release tag by reading the redirect Location of .../releases/latest — no API client, no JSON — and prints a single notice line to stderr when a newer semver release exists.
version
Package version reports the version that `git-scaffold version` prints.
Package version reports the version that `git-scaffold version` prints.

Jump to

Keyboard shortcuts

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