Files changed: - CHANGES.md - INSTALL.md - VERSION - docs/explanation/configuration-tiers.md - docs/how-to/README.md - docs/how-to/back-up-to-a-synced-folder.md - docs/reference/commands/README.md - docs/reference/commands/git.md - docs/reference/commands/instance-health.md - docs/reference/configuration.md - instructions/publish-cycle.md - instructions/setup-instance.md - tools/CONTRACT.md - tools/README.md - tools/chemenu/backup.py - tools/chemenu/cli.py - tools/chemenu/cli_contract.py - tools/chemenu/commands/backup_cmd.py - tools/chemenu/commands/doctor.py - tools/chemenu/commands/git_publish.py - tools/chemenu/config_shapes.py - tools/chemenu/reference_doc.py - tools/chemenu/tests/conftest.py - tools/chemenu/tests/test_backup.py - tools/chemenu/tests/test_config_shapes.py Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01SnAJ7Z3CpVD3PRbN73QtU2
267 lines
18 KiB
Markdown
267 lines
18 KiB
Markdown
# Git
|
|
|
|
Pulling in the remote and publishing a change - the only way a change leaves this checkout - and
|
|
backing the checkout up into a synced folder.
|
|
|
|
<!-- wikitool:toc -->
|
|
## Contents
|
|
|
|
- [`sync`](#sync)
|
|
- [`publish`](#publish)
|
|
- [`backup init`](#backup-init)
|
|
- [`backup run`](#backup-run)
|
|
<!-- /wikitool:toc -->
|
|
|
|
<!-- wikitool:reference-commands -->
|
|
## `sync`
|
|
|
|
Fetch `<remote>/<branch>` and bring the local branch up to date with it.
|
|
|
|
**Synopsis**
|
|
|
|
- `wikitool sync [--remote origin] [--branch main] [--confirm-rebase TOKEN]`
|
|
|
|
**Options**
|
|
|
|
```text
|
|
--remote <str> Git remote to reconcile against [default: origin]
|
|
--branch <str> Branch to reconcile [default: main]
|
|
--confirm-rebase TOKEN Clear the Rebase-Review Gate for the exact upstream
|
|
state and file overlap this token was issued for. The
|
|
token comes from the gate's own refusal output.
|
|
-h, --help Show this message and exit.
|
|
```
|
|
|
|
**Examples**
|
|
|
|
```bash
|
|
tools/wikitool sync
|
|
tools/wikitool sync --confirm-rebase <token> # re-run after exit 42, once the user approved
|
|
```
|
|
|
|
**Behaviour**
|
|
|
|
- Fetches `<remote>/<branch>`, then: fast-forwards when only the remote moved; rebases the local commits on top when both sides moved but touched disjoint files; exits 42 (Rebase-Review Gate) when both sides touched the same file.
|
|
- Files `wikitool` generates (`kb/index.md`, `kb/log.md`, `kb/provenance.md`, every `kb/**/INDEX.md`) never count as an overlap and never reach the gate. Where they are what stops git - uncommitted ones in the way of a fast-forward or rebase, or a rebase conflict in them - they are merged mechanically: `kb/log.md` becomes the incoming log plus the entries only this side appended, and the catalog and `kb/provenance.md` are regenerated from the merged pages and left as an uncommitted change. A reconcile git can do on its own is unchanged and regenerates nothing.
|
|
- Uncommitted changes that no incoming commit touches do not stop a rebase: they are carried through it (`git rebase --autostash`) and come back unchanged.
|
|
- Before anything is set aside, the working tree is stored as a commit under `refs/wikitool/reconcile-backup`. A successful reconcile drops it; a failed one restores the working tree and names the ref.
|
|
- A refused call performs no rebase attempt and leaves the branch where it was.
|
|
- The `--confirm-rebase` token covers the exact upstream state and the set of files touched on both sides, generated files excluded; either one moving makes it stale.
|
|
- Makes no commit, no push, and no forced operation of any kind.
|
|
- Three messages for a fetch that fails: no remote of that name, a remote that answers but has no such branch yet (a new, empty repository), and a remote that cannot be reached. All three exit 0 here; `publish` stops on the first and the last.
|
|
- Run it once at the start of a writing session.
|
|
|
|
**Properties**
|
|
|
|
- Changes files: yes
|
|
- Idempotent: yes
|
|
- Atomic: No - fetch, then at most one merge/rebase attempt, aborted cleanly on failure with the working tree restored; a success that merged generated files then rewrites the catalog
|
|
- Counts toward the session call budget: yes
|
|
- Can reach the network: yes
|
|
- Gates: rebase-review - see [exit codes and gates](../exit-codes.md)
|
|
|
|
**Exit codes**
|
|
|
|
- `0` - success
|
|
- `0` - No remote configured - reported and skipped, not a failure
|
|
- `0` - The remote cannot be reached - reported and skipped, not a failure
|
|
- `1` - The automatic rebase hit a real conflict outside the generated files (git failed), or `kb/log.md` was edited rather than appended to; it is aborted cleanly and the working tree restored
|
|
- `1` - An uncommitted change - or an untracked file - sits on a path the incoming commits also change; nothing was changed
|
|
- `42` - Rebase-Review Gate: `<remote>/<branch>` moved and both sides changed the same file, generated files aside; the output lists the upstream commits, the overlapping files and their diff
|
|
|
|
**See also**
|
|
|
|
- `wikitool publish` - runs the same reconcile before it commits and pushes
|
|
- `instructions/session-setup.md` - where a session runs `sync`
|
|
- `instructions/gates.md` - the gate procedure
|
|
|
|
## `publish`
|
|
|
|
Reconcile with `<remote>/<branch>`, then stage all changes, commit, and push.
|
|
|
|
**Synopsis**
|
|
|
|
- `wikitool publish --message "<op>: <desc>" [--no-push] [--confirm TOKEN] [--confirm-rebase TOKEN] [--threshold N] [--remote origin] [--branch main] [--path P ...]`
|
|
|
|
**Options**
|
|
|
|
```text
|
|
--message <str> Commit message summary, e.g. 'ingest: docker-
|
|
cheatsheet' [required]
|
|
--push / --no-push [default: push]
|
|
--confirm TOKEN Clear the Mass-Update Gate for the exact changeset
|
|
this token was issued for. The token comes from the
|
|
gate's own refusal output - run without it first to
|
|
see the file list, show that output to the user, and
|
|
only pass this once they have approved it.
|
|
--confirm-rebase TOKEN Clear the Rebase-Review Gate raised by the pre-push
|
|
reconcile step. The token comes from that gate's own
|
|
refusal output - see `tools/wikitool sync`.
|
|
--threshold <int> Mass-Update Gate threshold: number of counted files
|
|
at/above which clearance is required. [default: 10]
|
|
--remote <str> Git remote to push to [default: origin]
|
|
--branch <str> Branch to push [default: main]
|
|
--path <str> Limit this publish to a path (repeatable). Lets a
|
|
large change be committed in reviewable batches
|
|
instead of one opaque commit - the Mass-Update Gate
|
|
then counts only the files in scope.
|
|
-h, --help Show this message and exit.
|
|
```
|
|
|
|
**Examples**
|
|
|
|
```bash
|
|
tools/wikitool publish --message "ingest: docker-cheatsheet"
|
|
tools/wikitool publish --confirm <token> --message "ingest: docker-cheatsheet" # re-run after a Mass-Update exit 42, once the user approved
|
|
tools/wikitool publish --confirm-rebase <token> --message "ingest: docker-cheatsheet" # re-run after a rebase-review exit 42, once the user approved
|
|
```
|
|
|
|
**Behaviour**
|
|
|
|
- Order: branch check and Publish-Remote Gate, then the reconcile with `<remote>/<branch>`, then the Mass-Update Gate, then `git add -A`, commit and push. `--no-push` skips all but the Mass-Update Gate and the commit.
|
|
- Without `--no-push`, a remote that is not configured or cannot be reached ends the call with exit 1 at the reconcile - before the gate, `git add` and the commit, and also on a clean tree. Nothing is committed, the index and the working tree are unchanged, and the message names `--no-push` as the way to a local commit. The next `publish` that reaches the remote pushes that commit along with whatever is new. A remote that answers but has no `<branch>` yet (a new, empty repository) is not this case: the first publish of an instance commits and pushes as before.
|
|
- Reconcile: fetches `<remote>/<branch>`, fast-forwards when only the remote moved, rebases the local commits on top when both sides moved but touched disjoint files, and exits 42 (Rebase-Review Gate) when both sides touched the same file. A refused reconcile performs no rebase attempt. The `--confirm-rebase` token covers the exact upstream state and the set of files touched on both sides.
|
|
- Generated files are never an overlap: where they are all that stops the reconcile, it merges them as `sync` does (both sides' log entries kept, the catalog and `kb/provenance.md` regenerated) and carries uncommitted changes no incoming commit touches through the rebase. The proactive reconcile runs before staging, so this publish commits the regenerated files - with `--path` too, see there; on the retry after a rejected push they get a commit of their own before the second push.
|
|
- The push target must be the checked-out branch; this is checked before anything is staged. The unborn branch of a fresh `git init -b main` counts as checked out, so the first publish of a new instance works; a real detached HEAD is refused.
|
|
- With nothing new to stage, a local commit the remote lacks is still pushed: one left behind by an earlier publish whose push failed, or every commit when the remote answers but does not have the branch yet (a new, empty remote repository).
|
|
- A rejected push that finds the remote unreachable on its one retry reports the original push error.
|
|
- A rejected push gets exactly one more reconcile-and-push; never more than one.
|
|
- Mass-Update Gate: counts the files that would be committed, refuses with exit 42 at `--threshold` (default 10) or more, and prints a review report - a scale line (file count, total lines added/removed, status breakdown), attention notes where they apply (deletions by name, control-plane and harness-config touches, published pages, the largest single change, binaries), and every counted path grouped by area with its status and churn. The list is what the commit will hold: it is computed from a scratch copy of the index after `git add -A`, so a path that is staged as deleted and back in the working tree is not counted twice, and a rename counts as its old path deleted plus its new path added. The real index and the working tree are not touched, so a refused publish leaves both byte-identical.
|
|
- Never counted and never shown for approval, but committed like everything else: anything under `work/`, and the files `wikitool` generates itself (`kb/index.md`, `kb/log.md`, `kb/provenance.md`, every `INDEX.md` under `kb/`). The refusal line accounts for both, by reason.
|
|
- The `--confirm` token covers each counted path, the blob id of its contents and the publish target: a different file list or edited contents need a new clearance.
|
|
- Publish-Remote Gate: when `.wikitool-local.json` carries `remotes.allowed_push_urls` and the push URL of `--remote` is not listed in it, exits 42 before the reconcile fetches anything. The URL is read with `git remote get-url --push`, so a repointed remote does not pass on its name. An absent section means unrestricted; a malformed one is an error, not permission.
|
|
- The remotes settings file of an earlier version is not read: while it exists this exits 42 and names `tools/wikitool config migrate`, so a restriction is never lost silently.
|
|
- `--path` (repeatable) scopes the whole operation - gate count, staging and commit - to that subtree, with one exception: uncommitted generated files outside `--path` (`kb/index.md`, `kb/log.md`, `kb/provenance.md`, every `INDEX.md` under `kb/`) are staged and committed too, and a line names them - whether a reconcile or the change itself wrote them. They stay out of the gate count, and the `--confirm` token and the re-run line cover the `--path` given. Every other file outside `--path` is left as it is.
|
|
- Commit message: `--message`, then a `Files changed:` paragraph listing every committed path. When `--message` ends in a paragraph git reads as a trailer block (`Co-Authored-By:` and the like), that block stays the last paragraph and the list goes in front of it, since git reads trailers from the last paragraph only. `git interpret-trailers` decides whether there is such a block, and the list moves only when git reads the same trailers from the result; otherwise it is appended at the end. `--message` is not part of any gate token.
|
|
- Machine-data backup: with a `backup` section in `.wikitool-local.json` whose `machine_data` is true, a call that pushed then mirrors this machine's files that are not in git into `<target>/<machine>/` - the same step as `backup run`. A failure there prints a `WARN` line with the reason and still exits 0, since the commit and the push stand. No backup with `--no-push`, after a failed push, or when there was nothing to push. A malformed `backup` section stops the call with exit 1 before anything is committed, `--no-push` included.
|
|
- After a successful commit or push whose changed files include `tools/`, `types/`, `instructions/`, `AGENTS.md` or a path ending in `CONTRACT.md`, prints one reminder line: the phase past this point (an issue-body rewrite, `docs/` staleness, a changelog entry's accuracy) is not covered by `docs verify`, `instructions verify` or `pytest`. It is not a gate: no exit code change, nothing to clear, and silent for an ordinary content publish.
|
|
|
|
**Properties**
|
|
|
|
- Changes files: yes
|
|
- Idempotent: no
|
|
- Atomic: No - sequential git operations. Every gate runs before staging, except on the one retry of a rejected push, where the Rebase-Review Gate can exit 42 after the commit: nothing is pushed, and the re-run with `--confirm-rebase` pushes that commit
|
|
- Counts toward the session call budget: yes
|
|
- Can reach the network: yes
|
|
- Gates: mass-update, publish-remote, rebase-review - see [exit codes and gates](../exit-codes.md)
|
|
|
|
**Exit codes**
|
|
|
|
- `0` - success
|
|
- `0` - The machine-data backup after the push failed: a `WARN` line names the reason, and the commit and the push stand
|
|
- `1` - git failed - `git add`, `git commit`, `git push`, or the reconcile's automatic rebase (a conflict outside the generated files, an edited `kb/log.md`, or an uncommitted change on a path the incoming commits change)
|
|
- `1` - The push target (`--branch`) is not the checked-out branch, or HEAD is detached; the unborn branch of a fresh `git init` is not this case
|
|
- `1` - No `--no-push`, and the remote is not configured or cannot be reached; nothing was committed
|
|
- `1` - `--yes`/`-y` was passed - the flag does not exist and fails with an explicit error
|
|
- `1` - The settings files do not pass their check, `remotes.allowed_push_urls` is not a list of strings, or the `backup` section is malformed; nothing was committed
|
|
- `42` - The remotes settings file of an earlier version is still there
|
|
- `42` - Mass-Update Gate: `--threshold` (default 10) or more counted files would be committed, or the `--confirm` token does not match this changeset
|
|
- `42` - Rebase-Review Gate: `<remote>/<branch>` moved and both sides changed the same file; the output lists the upstream commits, the overlapping files and their diff
|
|
- `42` - Publish-Remote Gate: `remotes.allowed_push_urls` is set and the push URL of `--remote` is not listed in it, or `--remote` resolves to no push URL
|
|
|
|
**See also**
|
|
|
|
- `wikitool sync` - the same reconcile on its own, without committing or pushing
|
|
- `instructions/gates.md` - the gate procedure
|
|
- `instructions/setup-instance.md` - the first publish of a new instance
|
|
- `wikitool backup run` - the machine-data backup on its own
|
|
|
|
## `backup init`
|
|
|
|
Create the backup target's bare repository and make it `origin` when there is none.
|
|
|
|
**Synopsis**
|
|
|
|
- `wikitool backup init`
|
|
|
|
**Options**
|
|
|
|
```text
|
|
-h, --help Show this message and exit.
|
|
```
|
|
|
|
**Examples**
|
|
|
|
```bash
|
|
tools/wikitool backup init
|
|
```
|
|
|
|
**Behaviour**
|
|
|
|
- Reads the `backup` section of `.wikitool-local.json` and creates `<target>/` and `<target>/git`, the latter as a bare repository (`git init --bare -b main`). An existing bare repository there, or an empty directory, is used as it is.
|
|
- Sets `origin` to `<target>/git` when the checkout has no `origin`. An `origin` that already pushes there is left as it is; one that pushes anywhere else stops the command.
|
|
- When `remotes.allowed_push_urls` exists and lacks the URL `origin` pushes to, prints the `remotes` section with that URL added, for the user to copy into `.wikitool-local.json`. It never writes that file: the field is a gate.
|
|
- Pushes nothing: the next `publish` sends the history to the new `origin`.
|
|
|
|
**Properties**
|
|
|
|
- Changes files: yes
|
|
- Idempotent: yes
|
|
- Atomic: Every precondition is checked first; a refusal changes nothing
|
|
- Counts toward the session call budget: yes
|
|
- Can reach the network: no
|
|
|
|
**Exit codes**
|
|
|
|
- `0` - success
|
|
- `1` - No `backup` section in `.wikitool-local.json`, or a malformed one - a target that is not absolute or lies inside the instance, a `machine_name` that cleans to nothing or to `git`, an `extra` path that resolves outside the instance or into `.git/`
|
|
- `1` - `<target>/git` exists and is not a bare repository; nothing was changed
|
|
- `1` - `origin` already pushes to another URL; the message names both and nothing was changed
|
|
|
|
**See also**
|
|
|
|
- `wikitool backup run` - the machine-data half of the backup
|
|
- `wikitool publish` - pushes to the bare repository and runs the machine-data backup after it
|
|
- `docs/how-to/back-up-to-a-synced-folder.md` - the whole procedure, restoring included
|
|
|
|
## `backup run`
|
|
|
|
Mirror this machine's files that are not in git into `<target>/<machine>/`.
|
|
|
|
**Synopsis**
|
|
|
|
- `wikitool backup run`
|
|
|
|
**Options**
|
|
|
|
```text
|
|
-h, --help Show this message and exit.
|
|
```
|
|
|
|
**Examples**
|
|
|
|
```bash
|
|
tools/wikitool backup run
|
|
```
|
|
|
|
**Behaviour**
|
|
|
|
- Mirrors `.wikitool-local.json`, `ENVIRONMENT.md` if present, every file in `incoming/` but `.gitkeep`, and the `extra` paths of the `backup` section into `<target>/<machine>/`, then writes the manifest `.chemenu-backup.json` there: time, stack version, machine name, and per file its relative path, size and sha256.
|
|
- Never mirrors `mcp-upload/`, `reports/`, `.wikitool-tools.json`, `tools/.venv/`, the published skills or `.git/` - unless an `extra` path names one of the first ones. The secrets file lies outside the instance and is never read.
|
|
- Writes and deletes only under `<target>/<machine>/`. Deletes only a file the previous manifest lists and the instance no longer has; a file there the manifest does not list stays as it is. A file whose size and sha256 match the previous manifest is not rewritten.
|
|
- A symbolic link or junction inside a mirrored directory is skipped and named in the output, never followed.
|
|
- `publish` runs the same step after every push when `machine_data` is true.
|
|
|
|
**Properties**
|
|
|
|
- Changes files: yes
|
|
- Idempotent: yes
|
|
- Atomic: Per file - each is written to a temporary file and renamed, the manifest last; an interrupted run leaves whole files and the previous manifest
|
|
- Counts toward the session call budget: yes
|
|
- Can reach the network: no
|
|
|
|
**Exit codes**
|
|
|
|
- `0` - success
|
|
- `1` - No `backup` section in `.wikitool-local.json`, or a malformed one - a target that is not absolute or lies inside the instance, a `machine_name` that cleans to nothing or to `git`, an `extra` path that resolves outside the instance or into `.git/`
|
|
- `1` - `machine_data` is false
|
|
- `1` - `<target>/<machine>/` is not empty and has no manifest, or its manifest cannot be read; nothing was written
|
|
- `1` - The target is not a directory (the synced folder is missing or not mounted), or a write failed
|
|
|
|
**See also**
|
|
|
|
- `wikitool backup init` - sets up the target's bare repository
|
|
- `wikitool doctor` - the `backup` line reports the age of the last run
|
|
- `docs/how-to/back-up-to-a-synced-folder.md` - the whole procedure, restoring included
|
|
<!-- /wikitool:reference-commands -->
|