Files changed: - CHANGES.md - VERSION - docs/reference/commands/git.md - tools/CONTRACT.md - tools/chemenu/commands/git_publish.py - tools/chemenu/dist_templates/log.md Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01SnAJ7Z3CpVD3PRbN73QtU2
270 lines
19 KiB
Markdown
270 lines
19 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. When only `kb/log.md` stood in the way - an open log entry, nothing else generated - the catalog is not rebuilt: the incoming one stays, and just the local entries are appended again. 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, unless `kb/log.md` was the only one
|
|
- 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 unless the log alone stood in the way) 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.
|
|
- Export after publish: every target of the `export` section with `after_publish: true` is refreshed once a commit was made - the same step as `export run`, `--no-push` included, since the export reads the committed `kb/`. A failure there prints a `WARN` line with the reason and still exits 0, since the commit stands; `export run` repeats it. A malformed `export` section stops the call with exit 1 before anything is committed, but only when some target sets `after_publish`.
|
|
- 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
|
|
- `0` - The export after the commit failed: a `WARN` line names the target and the reason, and the commit stands
|
|
- `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, the `backup` section is malformed, or the `export` section is malformed while a target sets `after_publish`; 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
|
|
- `wikitool export run` - the page export 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 `quarantine/`, `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 -->
|