SwarmOps
ReferenceCommandsswarmops ref set
View as Markdown

swarmops ref set#

Point ref NAME at REV: move it from --expect, or --create it.

Metadata
Group
refs
Since
0.1.0
Mutates
yes
Identity required
yes
MCP tool
ref_set
Envelope op
ref_set
Exit codes
01245

Syntax#

swarmops ref set [OPTIONS] <--expect <REV>|--create> <NAME> <REV>

Required arguments#

Syntax: <NAME>.

main or release/<name>.

Syntax: <REV>.

The revision to point it at.

Exactly one of --expect <REV> or --create is required.

Syntax: --expect <REV>.

The changeset the ref holds now.

Give the full changeset id you read (from ref list, show, log or a commit's result). A ref name or a relative revision such as main~1 is read from the store when the command runs instead of pinning the id you read: the ref's own name always matches (a deleted ref errors instead), so the compare-and-swap protects nothing, and main~1 on main never matches. An id prefix pins the id you read only until another changeset shares it; then it is E_AMBIGUOUS_ID.

After E_CAS_MISMATCH, land your change instead (swarmops land merges it onto the ref's head), or re-apply your edits to the content at the error's actual, commit with --parent <actual>, then retry with --expect <actual>. Do not re-send your old whole-file puts: they would revert the winner's edits to shared files.

Syntax: --create.

Create the ref; if it holds REV already that is a replay (already_applied), at another target E_CAS_MISMATCH.

Optional arguments#

The command also takes the global options.

Examples#

swarmops --repo [tmp]/store --as agent/worker-17 ref set main [c1] --create
{
  "ok": true,
  "op": "ref_set",
  "api": 1,
  "result": {
    "name": "main",
    "new": "[c1]",
    "already_applied": false
  },
  "meta": {
    "repo": "[tmp]/store",
    "elapsed_ms": "[elapsed_ms]",
    "resolved": {
      "[c1]": "[c1]"
    },
    "auth": "trust"
  }
}

Exit code 0. Recorded by the CLI tests. A name in square brackets, such as [tmp] or [c1], stands for what differs from run to run: the test's temporary directory, an id, a time.

No recorded example yet.

Details#

ref set moves one ref to a changeset, or creates it, by compare-and-swap: you say what the ref holds now (--expect) or that it does not exist yet (--create), and the store compares that with the ref under its write lock. It is the low-level move. To publish a change on main, agents land it, which merges onto whatever main holds by then; ref set is for operators and scripts that point a ref at a changeset they have already chosen, such as a release ref.

What happens, in order#

  1. Arguments. Exactly one of --expect and --create is required; both, or neither, is E_INVALID_ARG (exit 2).
  2. Identity. Without --as or SWARMOPS_AGENT the command fails with E_IDENTITY_REQUIRED (exit 5) before it opens the store.
  3. Revisions. REV and the --expect revision are resolved in one snapshot of the store. A revision that names nothing is E_REV_NOT_FOUND (exit 6), a malformed one E_INVALID_ARG (exit 2), a prefix more than one changeset shares E_AMBIGUOUS_ID (exit 1). Both appear in meta.resolved with the full ids they named.
  4. The name. NAME must be main or release/<x>, where x is one or more of letters, digits, ., _ and -, does not start with ., and the whole name is at most 255 bytes; anything else is E_REF_NAME_INVALID (exit 1).
  5. The compare-and-swap, under the store's write lock, after the write has checked the journal's tail, so against the ref as committed, never as a reader's snapshot showed it:
The ref holdsWith --expect EWith --create
E, which is not REVMoved to REV; old is EE_CAS_MISMATCH, actual is E
REV alreadyReplay: already_applied: true, nothing writtenReplay: already_applied: true, nothing written
Another changeset XE_CAS_MISMATCH, actual is XE_CAS_MISMATCH, actual is X
Nothing (no such ref)E_CAS_MISMATCH, actual is nullCreated at REV, version 1

A replay writes no journal frame: op log does not list it and the ref's version stays. So the same ref set run twice is safe, whichever run gets there first.

E_CAS_MISMATCH is exit 4 and retryable: true, but do not rerun the same command: it would fail again. Read details.actual (the hint, swarmops show <actual>, only reads) and decide with that. The --expect help above says what to do for each case, and When a compare-and-swap fails walks through it.

What ref set does not check#

  • No ancestry. REV can be any changeset in the store: a descendant of the ref's target, an ancestor (moving the ref back), or one on another line of history. Moving main drops from its history every changeset the new target is not built on. Those changesets stay in the store, readable by id, and op undo can move the ref back.
  • No merge. Nothing of the ref's old target is carried over. land is the command that merges.

After the move#

  • Durability. The move commits when its journal frame is durable. If a step after that fails (such as listing the frame in journal.off), the move still stands, warnings says what failed, and the next write to the store finishes it. meta.recovered reports what this write repaired of an earlier writer that died mid-write (Monitoring).
  • The record. The journal records the move with the agent's identity and the time, in seconds; op log shows it as an operation of kind ref_move, which op undo can take back.
  • The lock. A write waits up to 10 s for the store's lock. If another writer holds it the whole time, the command fails with E_BUSY (exit 4, retryable) and nothing is written: run the same command again.

Patterns#

Move a release ref to main's head, expecting the target you read:

OLD=$(swarmops resolve release/1.0 | jq -r '.result.id')
NEW=$(swarmops resolve main | jq -r '.result.id')
swarmops ref set release/1.0 "$NEW" --expect "$OLD"

Create a ref, safe to retry: a second run with the same REV is a replay, and one with another REV fails with E_CAS_MISMATCH, so no ref is ever silently re-pointed:

swarmops ref set release/2.0 "$NEW" --create

Branch on the outcome in a script:

out=$(swarmops ref set release/1.0 "$NEW" --expect "$OLD")
case $? in
  0) echo "$out" | jq -r '.result.already_applied' ;;
  4) echo "$out" | jq -r '.error.code, .error.details.actual' ;;
  *) echo "$out" | jq -r '.error.code, .error.message' ;;
esac

Caveats#

  • Expect a full id. A ref name or a relative revision as --expect is read when the command runs, so it does not pin what you saw: --expect release/1.0 on release/1.0 always matches, and the compare-and-swap protects nothing. An id prefix pins the id only while no other changeset shares it. The CLI accepts all three; the --expect help warns.
  • Changing the trunk. If main is exported to a git trunk, moving it by hand is a change of the trunk: keep to One writer per trunk.

Through MCP#

The MCP tool is ref_set, with name, new, and either expected_old or create: true. The server refuses an expected_old that is not a full 64-hex changeset id with E_INVALID_ARG (hint swarmops ref list), so a ref name, a relative revision or a prefix never reaches the store.

Performance#

A ref move is one durable write transaction. The performance ledger counts its syncs: two for the journal (the frame and its entry in journal.off) and two for each ref it moves (the ref's new file and its directory) (Sync calls per commit).

Guides#

Return information#

result is a RefSetResp (swarmops schema RefSetResp prints its JSON Schema).

FieldTypeRequiredDescription
already_appliedbooleanyesThe ref was already in the requested state, so nothing changed and a replay is safe; old and new are then the request's expected_old and new, resolved to full ids.
namestringyesThe ref.
newstring or nullnoIts target after; omitted when the ref was deleted.
oldstring or nullnoWhat the ref held before a move that was applied. On a replay (already_applied) it is expected_old as resolved, which the ref need not have held; omitted when create was given.
warningsarray of stringnoProblems found after the move became durable, such as failed housekeeping; the move itself succeeded. Omitted when empty.

Envelope op: ref_set.

Exit codes:

  • 0: Success.
  • 1: The operation failed.
  • 2: The request or the command line is malformed.
  • 4: The operation can succeed when it is run again.
  • 5: An identity or policy requirement refused the operation.

Errors#

  • E_IDENTITY_REQUIRED (exit 5): A mutation was asked for without an identity.
  • E_CAS_MISMATCH (exit 4): The ref no longer holds the expected target (or, with create, exists at another one); details.actual holds what it does, null when the ref does not exist.
  • E_REF_NAME_INVALID (exit 1): The ref name is not main or release/<name>.
  • E_INVALID_ARG (exit 2): A malformed argument or request, including a command-line usage error.

Any command that opens a store may also fail with a store error, such as E_NOT_A_REPO, E_IO or E_STORE_CORRUPT; a command line the grammar refuses is E_INVALID_ARG (exit 2).

See also#

Edit on GitHub

On this page