v0.2.0
Added
-
[[render]]on a resource substitutes per-instance values into a file the
project commits (#10).[ports]reached commands as{{ports.x}}and as an env var, which assumes
the tool takes its port on argv. Most do not: Supabase reads
supabase/config.toml, Expo reads.env, Compose readscompose.yaml.
Everyone who hit this wrote the same section-aware config rewriter inside
theirpreparehook.[[render]] path = "supabase/config.toml" replace = [ { find = 'project_id = "faretable"', with = 'project_id = "faretable-{{branch.slug}}"' }, { find = "port = 54321", with = "port = {{ports.api}}" }, ]
There is no template file.
port = 54321is the project's working default,
so a clone without newgit still starts on it; newgit substitutes into the
committed content and writes the result into one workspace.findis a
literal string, never a regex, and must match exactly once — which is
also the drift detector: when the default changes upstream, the bind fails
naming the file and the string instead of quietly doing nothing. A
multi-linefinddisambiguates two sections sharing a value, andcount = N
declares a genuine repeat.A render reads committed content —
HEAD, or the bound lane rev for a
tracker-owned path — never the working file, so it is idempotent:undo
andtracker pullre-render off the binding record and values never
compound.Instance values stay out of everything downstream. Source-owned targets are
marked--skip-worktree, so they never show ingit statusandgit add -A
cannot commit them;newgit exportand a checkpoint's uncommitted-state
capture both take them fromHEAD; andnewgit tracker capturereverses
the substitution for tracker-owned targets, so a key you add to.env.local
reaches the shared lane and this instance's port does not.On a source-owned path, skip-worktree also means real edits to that file in
this workspace do not survive it. newgit reports that precisely rather
than as a caveat: a render is a pure function of committed content and the
binding record, so the expected bytes are recomputable, and newgit compares
against them at checkpoint and before every re-render — naming the file and
how many lines a re-render will discard, and saying nothing when the file is
what the render produced.Replacements are simultaneous: every
findis located in the committed
content and the whole batch applies in one pass, so a replacement's output
is never a match target and reordering thereplacearray cannot change the
result. Two rules claiming overlapping text are refused by name. -
newgit referenceprints the definition format — every tracker and resource
key with its type and default, the checkpoint/restore/ownership tables, and
which template variables are in scope for which hook
(#13).All of it was documented in
newgit-v1-mvp.md, and none of it travelled:
cargo install newgitleaves a binary and a README on disk, and the
README's relative link to that file resolved to nothing on crates.io,
docs.rs, or in the registry directory. Reading the crate source — or running
stringson the binary to enumerate{{...}}variables — was the only way
to answer whatcheckpoint.modeaccepts. The reference now ships inside the
binary, which is the one copy guaranteed to be wherever the definitions are,
andinit,tracker create, andresource addeach name it. The README's
links to the design documents are absolute, and say they live on GitHub. -
newgit status <instance> --pathprints that instance's workspace path and
nothing else (#12).A bootstrap script, a README snippet, or an editor integration wanting the
path had to parse table output or read.newgit/branches/<name>.toml, which
makes the store layout someone else's API. Now:
W=$(newgit status auth-refactor --path). The instance is inferred inside a
workspace, and warnings stay on stderr so stdout is a path. -
newgit remove <name> --purgeandnewgit cleanup --purge-archivedrelease
the snapshot revs an archived instance's checkpoints were pinning
(#12).Cleanup never prunes a rev a checkpoint points at, which is right for a live
instance and a dead end for a removed one:undoneeds a binding record, so
those checkpoints are unreachable while their revs are permanent. Purging
drops the checkpoint log and itsrefs/newgit/checkpoints/<slug>/*store
refs, and the same pass reclaims what they held; a purging--dry-run
reports exactly that. It stays opt-in and never touches a live instance's
checkpoints. Ordinarycleanupnow reports how many of its retained revs
are held only by archived instances, andremovesays how many checkpoints
it kept. -
newgit tracker capture <tracker> --from-storeseeds a lane from the store
repo's working tree and sets the lane head, so the firstspawncomes up
with the content (#9).A lane starts empty and
capturereads from an instance workspace, so
adopting newgit for a tracker carrying.envfiles meant spawning an
instance guaranteed to come up without them, copying the files in, capturing,
merging, and re-running the resource action that had already failed. The
content was in the store repo at the same relative paths the whole time.
That bootstrap is now two commands:tracker track, then
tracker capture --from-store.Declared paths with nothing behind them are reported as warnings rather than
silently seeding a partial lane; a tracker with nothing at all on disk is an
error, not an empty lane head.newgit tracker tracknow suggests
--from-storewhen the paths it just added already have content. -
.newgit/scripts/and a{{scripts}}template variable, so a resource's
script lives under the same rule as the definition that calls it
(#8).A resource definition is read from the store, but anything its commands
shelled out to was read from the workspace, where it is subject to source
materialization. The two halves of one definition lived under different
rules and only the TOML half was editable in place, so iterating on a
preparemeant committing every attempt or copying the script into the
workspace by hand between runs.{{scripts}}resolves to.newgit/scripts/in the store. Edit a script
and the nextnewgit actionruns it — nothing to commit, nothing to copy,
and it works on the first spawn. The directory is control plane and is
listed ininit's "commit these" output;initwrites aREADME.mdthere
because Git will not track an empty directory. Project-owned scripts are
unaffected: they stay in the project tree, are read from the workspace, and
must still be committed before a spawn that calls them.
Changed
- The
command-snapshottemplate's[actions.migrate]says what it is: a
convenience command you invoke withnewgit action <resource>.migrate, not
a lifecycle hook. No stage ever ran it, and sitting besideprepare— which
spawnand arecomputerestore do run — it read like one
(#12).
Fixed
-
A declared
capturesname that never appeared in an action's stdout failed
in total silence: the command exited 0, newgit reportedprepare: ok,
marked the resourceready, and published an empty handle that went
unnoticed until an API call returned 401
(#7).Each missing capture is now warned for by name, with the log to look in and
the convention that explains nearly every occurrence — whencapturesis
set, stdout belongs to newgit, so everything else the command prints should
go to stderr. Warnings appear atspawn, onnewgit action, and during a
recompute restore. A missing name is still not an error: a resource may
legitimately publish a handle only on some runs. Theexternaltemplate's
comments now state the stdout convention too. -
An undo where a resource restore failed reported
Restoredon its first
line andFAILEDon its fourth, describing one operation two ways
(#11). A restore command
is not transactional — one that rebuilds a schema and then fails to load
the rows leaves its resource in neither the pre-undo state nor the
checkpoint state — so the summary no longer implies the instance is in a
known state:Undo of `smoke` to ckpt_001 ("before agent") INCOMPLETE: 0 of 1 resources restored `db` may be in a partial state — a failed restore command is not rolled backnewgit undonow exits non-zero when the undo was incomplete. -
A failed undo left a pre-undo checkpoint indistinguishable from one a human
named, so three failed attempts left three of them, each pinning its
tracker revs. The safety checkpoint now recordsundo_completedonce the
undo it preceded finishes, andnewgit checkpointsshows those entries as
failed-undorather thanbefore-undo— they are not redo points. The
record is annotated rather than deleted: newgit cannot know at save time
whether the undo will succeed, and discarding the only record of a state is
what checkpoints exist to prevent. Releasing the revs those entries pin is
acleanupconcern, tracked in
#12. -
A resource whose
depends_onnamed something that did not exist yet made
every newgit command fail, includingtracker createandtracker track
— the commands that create the missing name. The only way out was to hand-
edit thedepends_online, run the command, and put the line back
(#6).The dependency graph is now resolved leniently at load and its problems
reported rather than raised. Commands that act on the graph —spawn,
run,action,checkpoint,undo— still refuse, with the same error
naming the missing dependency. Commands that build it (tracker create,
tracker track,resource add) and commands that inspect it (status,
tracker list,resource list) now run and print the problem as a warning.
removestays reachable too, so teardown never depends on the graph holding
together. Dependency cycles are handled the same way.
Documentation
- Say what a package manager costs under newgit. Every instance installs its
own dependencies — that independence is the point, and a shared installed
tree across branches with different lockfiles is the thing that would be
wrong — but the size of that cost is the package manager's call, and
nothing said so. pnpm hardlinks from one content-addressed store, so
instance ten adds directory entries;npm ciexpands a full copy per
instance, which on a monorepo is gigabytes each. Documented in the README's
adoption section, next to the install resource innewgit-v1-mvp.md, and
in thepnpmtemplate's own comments, since that is what someone reads
when they hand-edit the definition.