Configuration #
The rules are one file,
mq.toml, on a branch of its own,mq/config. It is a branch like any other: a rule changes by a land into it.
mq/config is an orphan branch of the repository, as
gh-pages holds a site beside the code it documents. Its name is the ref
refs/heads/mq/config, which reserves the prefix: no branch can then be
called plain mq, since Git cannot hold both refs. On the first machine, mq init creates it, with the branch HEAD named as the whole DAG. It adds one
check when it recognises how the project already checks itself, and lists it
on that branch. That is the repository's own pre-commit hook when it has
one. Otherwise it is the build system's own check (dune build @check for a
Dune project, cargo check for Cargo, go vet ./... for Go, make check
for a Makefile with that target). Otherwise it adds none. In a repository
with a remote it adds [sync] too (Sync). A DAG with no check
is a valid configuration, and each land then states it in its outcome (no checks configured) as a fact. On every later machine, mq init after git clone finds origin/mq/config and adopts it rather than creating a second
one, and sets up each branch the rules name that the clone lacks from
origin's, tracking it, as git switch staging does in a clone. It adopts
rules that do not load too, and ends unavailable with next: mq doctor,
whose remedy lands the fix. A machine takes a later value of
origin/mq/config only by
fast-forward, as git merge --ff-only does, honouring
merge.verifySignatures: a copy that does not hold the value it last saw
there, a rewind or a read that lags a push, moves nothing and is named, and
the next push sends the newer rules back; a divergence is refused, naming
it.
In a repository where mq has run, the rules branch deleted with Git is not a
queue mq init never set up. The journal records the landings: each note on
a landed tree names the commit of the rules that judged it (rules=), and a
landing into the rules branch names the commit it moved them to. Every
command, mq init among them, is then refused with the newest of these, the
rules last read here, and next: git branch mq/config COMMIT, the command
that restores them; mq init creates no second set over them. A repair of
rules that do not load is judged under them, so the commit restored is the
repair's own, never the one that broke them.
The first mq push creates on the remote whatever
branches of the DAG it does not hold yet. One copy describes every branch:
[platforms]
supported = ["Darwin arm64", "Linux x86_64"]
[check.build]
command = "dune build @check"
read = ["~/.opam"]
write = ["~/.cache/dune"]
[check.test]
command = "dune runtest"
read = ["~/.opam"]
[check.suite]
command = "./scripts/suite.sh"
network = true
wall = 1800
platforms = ["Darwin arm64", "Linux x86_64"]
[branch.staging]
checks = ["pre-commit", "build", "test"]
[branch.main]
takes = "staging"
checks = ["pre-commit", "pre-push", "build", "test", "suite"]
[branch."release/*"]
checks = ["build", "test", "suite"]
[bounds]
check_wall = 3600
check_grace = 30
wait_wall = 7200
run_start = 300
hook_wall = 30
resolve_wall = 600
remote_bytes = 17179869184
remote_stall = 60
store_log = 67108864
max_batch = 128
trace_wall = 2
slot_overdraft = 1
check_memory = 4294967296
[resolve]
command = "mq resolve"
default = "stop"
models = ["deepseek-v4p1-flash"]
max_tokens = 32768
read = ["~/.config/model"]
network = true
[hook]
command = "./scripts/tracker.sh"
[cone]
command = "mq ocaml cone"
[lock]
command = "dune pkg lock"
paths = ["dune.lock"]
[generated]
files = ["*.opam"]
command = "dune build @install"
The [cone] section names the build adapter's answer to what a queued
land's change can affect. A run asks it for a queued tip, from the
repository's top, with MQ_TARGET, MQ_TARGET_TIP, MQ_LAND_TIP (the
queued tip) and MQ_LAND_SINCE (the tip it was queued from: the land's own
commits are those after it, whatever a rebase of the target replaced
under them) set, bounded by bounds.hook_wall. Its first line is whole,
or the directories the change can affect, separated by spaces. A second
line reads P1 P2 ... names, as gitignore patterns, the paths of the
target's tree the answer was read from; once the target moves, the run
asks again only when the move changes a path one of them matches or the
queued tip's merge base with the target, and with no such line at every
move. The run also sets MQ_LAND_TIPS: the asked tip, then every other
tip queued into the target with no answer that holds on the target's tip
and none the call before is still keeping, each as TIP:SINCE,
separated by spaces. A command that answers them all
prints, for each, a line tip TIP and its cone's line, as it answers it,
then the one reads line; the run keeps every answer printed, even by a
call stopped at its bound, so one call answers the queue. A command that
prints one answer answers MQ_LAND_TIP alone, as before. Each ask is a
task, mq task cone, which keeps
each answer under the repository's mq/cone-answers directory, named by
the command, the queued tip and the tip it was queued from, so a run
started again, as an install of mq starts one, asks no cone it was
answered, and a land dequeued and queued again at the same tip asks none
either. An answer no queued land keys is removed once nothing has written
it for a week. Lands into one target whose cones
are disjoint are checked at once. With no [cone], a land is the
whole tree, and lands into one target are checked one at a time.
bounds.hook_wall is the deadline of one call of the command: an ask past
it, or a command that fails, keeps the whole tree for that tip, and no
run waits for an answer (runs).
The rule a cone answers is set overlap. A land writes the paths its commits change; its check reads everything the build of its scopes reads. Two lands conflict when one's writes meet the other's reads, and a cone is the land's writes together with every directory whose build reads them, so two lands whose cones are disjoint write nothing the other reads.
mq ocaml cone takes the change from the queued tip's merge base with
the target's tip, the commits the land replays, and reads the dune graph
from the target's tree: the change's packages and every package whose
stanzas use one of their libraries. A dune file the change touches adds
the packages of the libraries its stanzas name at the queued tip, since a
use the land adds is not in the target's graph and the check builds it.
Its second line reads dune, dune-project, dune-workspace and
*.opam, the graph's files. The answer is kept under the repository's
mq/cone directory, keyed by the target's dune and opam files and the
land's change, never by the executable, so a target move that leaves the
graph alone moves no checkout and reads no index, and an install of mq
answers nothing again. Asks on one target tip share one reading of its
graph: the tip's graph files are read from Git's objects, and the project
index keeps each top-level directory's part by the object ids of the graph
files under it. The checkout a part not yet read is scanned from is a
worktree of the target's tip under mq/cone-target holding the graph's
files alone, made the tip's once and kept while every directory and graph
file it read stats the same. One call answers every land the run names
(MQ_LAND_TIPS) on that checkout. The rules of the graph's dune files are
read once a graph and kept beside the project index's parts, keyed by the
graph files' paths and object ids, so a call for a land queued after the
others reads no dune file; each land's project index is the one an ask of it alone builds, its
roots the directories of its change. An ask after the first lists no
directory of the tree but those of the packages no ask on the tip has
scanned.
The edges' checks. A land along an edge is a batch: every commit the
branch taken holds and the target lacks, each of them checked already by
its own land, or its own batch, into that branch. The edge's checks run on
the batch's tree
as any land's do, and a check that reads its change, as mq ocaml does,
reads the batch's paths. A land into the entry branch runs the tests of
the packages it touches and of those that read one of their libraries
directly, mq ocaml suite --direct: a library most of the tree reaches
costs its land its own readers, not the tree, and a red in the land's
own package is found at the land, never by the edge. Past its check's wall
the suite goes on while its build ends processes, as every check does ("A check",
below): it runs to its verdict, so no test of the land's scope is left
to a serve no land waits on (Delivery at full speed). The edge's suite
runs the batch's whole cone, so it confirms its lands' tests and adds
the packages that reach a change through another. A cone is a function
of the paths a change writes, so the batch's is the union of its
commits' cones: a batch that touches one package of fifty runs that package's tests and its readers',
and a batch that touches a library half the tree reads runs it and that
half. Google's TAP runs a change's affected tests and no others, and
Bazel's target determination reruns only a target whose action keys
changed. A root file that every rule reads, a workspace's env or a
project's version, is the whole tree; a root file whose change no rule
reads, dune's cache setting or a package's opam metadata, is no scope at
all (mq ocaml's build_neutral). Main's lint compares the findings of
the batch's touched files with the target's, as staging's lands did. The
bisection of a red edge probes each prefix under the same checks, so a
probe reads the prefix's cone, never more than the batch's.
What no cone sees, a test reading a file no rule declares or a tool the build takes from the machine, the whole tree sees. A branch that takes the checked branch, under checks that read every rule whatever changed, keeps that guarantee:
[check.suite-whole]
command = "mq ocaml suite --whole"
wall = 14400
[check.lint-whole]
command = "mq ocaml lint --whole"
wall = 14400
[branch.full]
takes = "main"
report = true
checks = ["suite-whole", "lint-whole"]
Its land moves only full, to main's tip when that tree passed, as
Chromium's last known good revision moves. No land reads it and no edge
takes it, so a red there holds no work back: the red is an event the hook
delivers, charged to main's tip and naming the red check and its log.
A red serve into a branch that reports is not bisected, since each probe
would be one more run of checks that read the whole tree; the edge waits
until main moves. A branch that reports is served whenever main holds
commits full lacks and no serve of it is under way, whatever else waits.
Its serve is of the reporting class, which is guaranteed one build slot:
when it holds none, the next slot freed is its own, before any land's, and
it takes more only when no fix, land or prediction waits for one
(runs, "The work classes"). Each of its checks runs to its
verdict. The whole-tree checks cost the queue one slot under load and never
wait for it to idle. A serve queued into a branch that reports checks the tip it was
queued at, through the end of a run and an install, so a main that moves
faster than one whole-tree run still has its tips checked, one serve
behind; the next serve takes main's newest tip.
Its checks carry their own wall, since one of them reads the tree whole.
report = true says the branch only reports; a branch that reports takes from
another, and waits for no dependency's edge: it reads what its source locks,
which the dependency's branch paired with its source holds already
(commands, "The edges").
A [check.NAME] section names the command that runs the check in the tree
being checked; it passes when the command exits 0. A command that exits 75,
EX_TEMPFAIL (sysexits.h), says the machine could not run it, its first
words on standard error saying why: that says nothing of the tree, so the
check is not run again at once, no bisection names a culprit by it, and its
land stays queued for a later run, which counts it as an attempt on the
tree, as a check that could not start is counted (runs, "A
check the machine stopped"): nothing the next round reads differs. A check
whose supervisor another process stops with TERM or INT, an operator's
stop or the stop of a run that read the command as left (runs),
is a task that keeps no answer: its group is stopped, its log names that
signal and the process that sent it, and the run that reads it starts the
check again, an attempt it neither counts nor waits on. A
command that exits 79 and names at least one failure (What a red
names) says it fails only as the target's tip does, every failure
it names being the tip's: the run is inherited, and its land goes ahead,
not charged, since it makes the target no worse (outcomes,
landed). A command that exits 80 says it
is red and that its errors name no scope of the change in play, its check
tree or the machine around it at fault, its last line the first error, as
mq ocaml says it (commands): the run is no result on the
tree, the check runs once more on its check worktree's build directory set
aside, and a second such run stalls the check and charges no entry
(runs, "A red outside the change"). 79 and 80 lie outside every
convention a command exits by for another reason: sysexits.h's 64 to 78,
Cmdliner's 123 to 125 (125 is also mq's own defect), the shell's 126 and 127
and the signals' 128 and above. A run that exits 79 naming no failure is
failed, as any other status is. Its wall, bounds.check_wall by
default, is given to its command as MQ_DEADLINE and stops no run: a run
stops once it makes no progress for bounds.stall seconds, the slot it
took its first progress. A run whose processes ran within the last
bounds.stall seconds (one of its group or their descendants with no
child under way consumed CPU time for at least a hundredth of the time
between two readings, or one gained the time of a child it waited for, as
the kernel counts them), or one of whose build processes ended within
them, as dune's trace records it (a rule's action or a test), is making
progress and is never stuck; one that made none that long is sent TERM,
then KILL bounds.check_grace seconds later, and ends stuck
(runs, "A check the machine stopped"). Its
priority, "background" by default, runs the command and every process it
starts 19 nice steps below the run, as nice -n 19 runs a program, so a check
yields the cores to interactive work, mq status among it, and takes them
back as soon as that work waits: a check is never held off a core that no
other work wants. "normal" runs it at the run's own
priority. mq's own commands keep their caller's priority. A system that
refuses the change, as a sandbox may, runs the check at the run's priority,
and the check's log ends with mq: the check ran at the run's priority: and
why. Its repair, a command line, repairs the check's confirmed red on an
entry's own land before that red is the entry's verdict: its commit is a
candidate the land checks, and once the land's checks pass on it the
branch's next, the entry queued again at it (runs, "A repair");
with none, the red is the verdict. It is a red bump's first
repairer too, before the model's (Commands, "When a
dependency moves"). A check runs in a worktree of the tree under check, HEAD detached at the
replayed commit, with an environment built from a fixed list and never
inherited from the caller: PATH, HOME, LANG, MQ_CHECK (the check's
name), MQ_REPOSITORY (the repository's common Git directory, whose run
lock a mq land inside the check would wait for: such a land into that
repository is refused, and into any other proceeds), MQ_SOURCE (the branch landed: the work branch, or for the serve of
an edge the branch the target takes), MQ_TARGET (the branch landed into), MQ_TARGET_TIP (the target's
commit the tree was replayed onto), MQ_SCOPE_TIP (set only on a probe
of the target's own tip, whose HEAD is MQ_TARGET_TIP: by a red edge's
bisection, the range's head; by a land red under its check's inputs, the
land's candidate. A check scoped by its change builds that commit's change
from MQ_TARGET_TIP on the tip's tree; its result is stored under that
commit too, so it is never read as the tip's own), MQ_DEADLINE (its wall as an RFC
3339 time), MQ_STALL (bounds.stall, the seconds mq lets the run make no
progress past MQ_DEADLINE before it stops it, so a wait of the check's
own, for a lock another check holds, lasts until the later of the two:
mq ocaml waits for the target's verdict that long), MQ_JOBS (the jobs its builds run at: its share of the cores,
the machine's cores times the slots the check may use when it starts, its
own and those free, over mq.slots, so that a check alone takes all the
cores and the checks side by side share them, over the share of a core one
of its processes used, the CPU of the processes of the check's runs over the
last day over their wall, as their build figures sum them; the share itself
when no run of the check left build figures; at least 1. A check whose
tests wait on processes, Git and the disk runs more of them at once than
one that compiles, and either spends its share; mq ocaml passes it to
dune as -j),
MQ_TRACE_DIR, a directory of the
run's own where it leaves the trace of each build it runs, and
MQ_TRACE_REPORT, the file beside its log an mq command it runs writes
its span tree to (trace_wall below). When the check
ends, mq reads each trace, copies it beside the check log as
<log>.trace-<name>, writes the rules the builds ran and the tests the
shared cache restored, then each test they ran with its time, one JSON
object a line, to the run's trace beside the log
(check-...-<invocation>.trace, kept as long as the log),
reports the measured build cost in the log, then removes the check
worktree's trace directory. The run's check event records what
mq stats reads of it: its tests' times as a histogram, its tests of
ten seconds or more, and the rules it ran although an earlier run that
had ended before its command started recorded their digests, found
against the traces of the runs the journal holds, which mq reads once a
process and then as each run ends. A check with inputs is
also given, for each, MQ_INPUT_NAME, the commit its result is keyed by
(empty when it has none), and MQ_REPO_NAME, the absolute path of its
repository, NAME being the input's name in upper case. Dune's _build/trace.csexp is one, recorded with
DUNE_TRACE=+cache for the shared cache's answers. The checks of a land
run side by side, up to mq.slots at once, each in a worktree of its own
taken from the pool, its build directory kept from that check's last use:
each finds the replayed commit's files and nothing a run before it wrote, but
what the build directory holds, as git clean -dx would leave it but for
that directory. The rerun of a failure runs in the same worktree and also
carries MQ_CHECK_RERUN, a key no run carried before: a build system answers
a second run of a tree from its record of the first, a recorded failure
included, and an action keyed on the variable executes again instead (dune's
cram pages, whose recorded output a rerun would replay, declare it with
(deps (env_var MQ_CHECK_RERUN)); dune records no failed action, so a
failed test executes again without it, and a test that passed is answered
from the build directory; in each phase, @check as @runtest, mq ocaml
builds again only the build that failed, keyed as the first run was: a
staged suite (mq ocaml suite --direct) builds its stages again in their
order, the ones before the red stage, which passed, answered from the build
directory, and the ones after it only once the red stage passes on its
rerun, as the first run would have built them, so a red is known once the
red stage alone is built again, never by the whole suite. In that build it
sets the variable for one build, the aliases of the cram pages that failed,
and before it, unless its first run recorded failed pages alone, builds the
stage again without the variable, finding there the pages that failed when
no record of the first run is left, so no page that passed executes again,
and names on standard error each failure the rerun does not repeat, N flaked and passed on its rerun: NAME, ...). A rerun stopped before it
has built its red stage again leaves the first run's failures standing,
since no rerun has passed them. A first run
carries none, so the build directory and the build cache answer
it, a green as a red: dune stores a page's transcript whatever it says,
so a red one execution left there, of this tree or of another with the
same inputs, is answered to every later build that reads them. A red
answered so is no observation of this run, so mq ocaml executes again,
under a key no build carried before, each failed cram page that its
first run's dune trace does not record running, and that execution is
the first run's verdict on it; a page the build ran keeps its red for
the rerun, and a build whose trace does not record dune's exit leaves
every failure standing. The run after a pass that a repair's candidate
asks (runs, "A repair") carries MQ_CHECK_RERUN and MQ_CHECK_AGAIN, the files the
repair rewrote that the red's log named, separated by spaces: with no
failure of its first run recorded, mq ocaml builds the cram pages among
them under the variable, which executes each again, and the rest of the
phase keyed as the first run was, which the build directory answers. A rule whose
verdict reads commit messages, authors or trailers is not a check, since a
check runs on a tree and its result is reused for every commit of that tree:
such a rule belongs in the hook's queue-time refusal. takes makes an edge
of the DAG, served as its source moves: a scheduled run serves it on the
landing that moves the source, once no serve of it is under way, and serves
it again at the end of one whose source moved meanwhile
(runs). No key sets an interval: a rule is a condition on the
queue's state, and the build slots bound what the serves cost. A release
series has checks and no edge (Review and
releases). read, write and network
declare what a check needs beyond its worktree, as a path relative to it,
under ~/ or absolute. inputs declares the other repositories a check's
verdict reads, a table from a name ([a-z][a-z0-9_]*) to the repository's
path, relative to the repository's top, under ~/ or absolute: the commit
read of each is part of the key its result is stored under, so a move of any
of them runs the check again (A check that reads other
repositories).
vet lists checks among a branch's checks that each entry into it runs
on its replay onto the branch's tip, beside the batches, so that an entry
red on its own is kept out of every batch: vet = ["fmt", "lint-touched"]
under [branch.staging] checks each entry's own commits, replayed onto
staging's tip as the run found it, with those two checks, in the build
slots no land holds, and its results are kept by that tree, so a land onto
a tip that did not move reuses them. No entry waits for a vet: one whose
vet is out rides as any other. A red there stops the batch that holds the
entry, and goes to the repairs at the entry's land (runs, "A
repair"). mq land runs them in the worktree that has the branch out
before it queues, and a red there queues nothing (mq land;
--force skips it). A name that is not one of the section's checks is
refused, naming it. With no vet, the default, nothing is vetted. A target with
batches off (bounds.max_batch = 1) vets nothing (A
vet).
after lists checks among a branch's checks that its lands leave to the
edges that take it: after = ["suite-touched"] under [branch.staging]
lands each entry once its other checks pass, and each serve of main, which
takes staging, runs suite-touched on the batch of every commit staging
holds and main lacks. A land into the branch passes its checks less its
after, and a serve along an edge out of it passes the target's checks,
then each check of the source's after the target does not list, so the
target holds only trees those checks passed and the source never waits for
them. A red serve names its culprits by what its log names, and each
culprit's repair lands into the source expedited (A check left to the
edge). A name that is not one of the
section's checks or that its vet lists is refused, naming it, and so is
an after on a branch that only branches that report take, or none.
With no after, the default, every check runs before the land.
drain lists checks among a branch's checks that a drain's lane runs
before it moves the branch (A drain):
drain = ["check"] under [branch.staging] builds the chain of every
queued entry once; the branch's other checks run after, where the branches
that take it run theirs, a test red at a tip repaired forward. A chain
that changes the package the [install] executable is built in runs every
check of checks whatever drain names, so a change to mq meets its own
suite before the branch it installs from moves. A name that is not one of
the section's checks is refused, naming it. With no drain, the default,
a drain's lane runs the checks every land into the branch runs.
[platforms] supported names, once, the platforms the repository supports,
each as uname -sm prints it ("Darwin arm64", "Linux x86_64"): the
columns of mq checks, and the platforms whose results a gate counts. With
no [platforms] section every platform is supported. A check is portable
unless its section names platforms: its verdict cannot depend on the
platform, so one confirmed pass on any supported platform passes it for
every machine, and a result on a platform the rules do not support counts
for nothing. platforms lists the platforms a check's verdict must hold on,
each one of supported: a land passes it only once each of them has a
confirmed pass of the tree under the check's definition, whichever machine
recorded it, and a result on another platform is not required. The platform
is part of what a check requires and never of the key its result is stored
under (Check results),
so listing it or removing it reruns nothing. A platforms that names a
platform supported lacks, a platforms with no [platforms] section, an
empty list, a platform named twice and a name that is not uname -sm's two
words are refused when the rules are read, naming the check and the
platform.
A confirmed red on any supported platform holds a portable check, even
beside a pass on another: the check is then not portable, and the outcome
says so, naming the two results with their platforms and machines (check suite is not portable: passed on Darwin arm64 (laptop), failed on Linux x86_64 (build-host)), so that the check's section lists its platforms or
the check is fixed. A land on a machine whose platform a check lists runs
the check when its own platform has no result; a land on any machine whose
check still needs a pass on a platform it is not ends unavailable, naming
the platforms still missing, since another machine's result
decides it and nothing in the change does. A check runs with the run's rights, as a Git hook does (What mq
trusts), and only a machine whose mq.confine is
auto enforces them (How a check is
confined). [resolve] takes the same three
keys for the resolver, which such a machine confines as it confines a check. The bounds are in seconds of the host awake: a sleep of the
host moves every bound a run holds later by its length
(design, "A sleep of the host is an event").
MQ_CLOCK names a file holding a count of seconds, as a harness that
plays the clock sets it: an mq given it reads every instant, and counts
every bound and wait, that many seconds ahead of the host, reading the
file again at each reading of the clock and, while it waits, at each
write to it the kernel reports, never on a tick, so a waiting mq consumes
no CPU. A larger count is that much time passing, so a
bound it passes ends at once in every mq that reads the file; a missing
file counts 0, a count smaller than one read before changes nothing, and
a file that holds no count ends the command, naming it. check_wall,
check_grace, wait_wall, run_start and
hook_wall default to the values shown; wait_wall bounds a caller's wait
for a lock, mq slot's wait and a dependency's preparation; a check's wait
for a build slot has no bound of its own.
Every deadline is the deadline of one external call (a check's wall, the
cone command's, the resolver's, a hook's): a round is bounded by what it
does, so there is no silence bound and no count of passes
(design).
remote_bytes, in bytes, bounds what one session with a remote reads, its
listing of branches, the pack a pull fetches and the status a push reads
back, 16 GiB by default, with the
objects decoded to at most eight times that: a remote that sends more is
refused, naming the bound, and nothing moves.
remote_stall, in seconds, 60 by default, bounds how long a session with a
remote waits on a remote that makes no progress, where the user's own
configuration states no bound. Over SSH, as OpenSSH's client reads
ssh_config(5): ConnectTimeout bounds the connection and the server's
identification string, and a server silent for ServerAliveInterval is
sent a keepalive, the session ending once more than ServerAliveCountMax
went unanswered; where ssh_config sets no ConnectTimeout it is
remote_stall, and where it sets no positive ServerAliveInterval the
interval is remote_stall divided by one more than the count (3, OpenSSH's
default, unless ssh_config sets it), so a server that stops answering ends
the session remote_stall after it last spoke; and since a server whose
command hangs still answers keepalives, the channel itself ends once it
moves under 1 byte a second over remote_stall, as an HTTP transfer does.
Over HTTP and HTTPS, as
curl's low-speed check ends a transfer: a transfer under
http.lowSpeedLimit bytes a second for http.lowSpeedTime seconds
(git-config(1), or GIT_HTTP_LOW_SPEED_LIMIT and _TIME) ends, and where
Git's configuration sets neither the limit is 1 byte a second over
remote_stall; the connection is bounded at 10 s. Over git://, the
connection's reads and writes end under 1 byte a second over
remote_stall. Each low-speed window starts with the session's first
bytes, so a connection, a key exchange or an authentication spends none of
it. A session that ends so leaves the remote unreached, its operation
unavailable and naming the remote and why (commands), and
wait_wall still bounds a whole session.
store_log, in bytes, bounds the write-ahead log of the journal's index,
64 MiB by default, two of the largest commits mq makes: a commit that leaves
the log at 1000 frames copies it back into the index and cuts it to no
bytes, and a log past the bound is a copy-back a reader kept from running,
which mq doctor reports.
check_memory, in bytes, bounds the resident size of each process of a
check's group, 4 GiB by default: while the check runs, mq reads its group
every half second and sends a process over the bound TERM, then KILL
check_grace seconds later, and the check's log names the process, its
command, its resident size and the bound.
max_batch, a count, bounds the lands one batch checks as one tree, 128 by
default: the entries a branch admits at once, their changes commuting, and
the entries behind them that overlap no land under way ride in one batch
once they are more than the build slots check at once, landed
in one move, a red bisected over them (A batch); 1 checks each
land alone.
A round that starts no task and writes nothing ends the run, and a red
tree is not checked again under the same rules, since the results on the
candidates' trees are what a round reads. stall, in seconds, 120 by
default, is how long a check run past its wall may make no progress before
it is stuck (above).
slot_overdraft, a count, 1 by default, is the build slots past
mq.slots that an expedited land (mq land --expedite) or a land into
the rules branch takes when every slot is held by serves, lands and
predictions it ranks above, so that it never waits behind work under way;
0 holds every check to mq.slots. Each overdraft journals an
overdrawn event naming the slot, and each prediction or probe refused
the last free slot, left to a land queued whose checks have not begun
while the run says one is, a slot-kept event.
A command prints, as it exits, its span tree on standard error when
asked, mq status --why, or when it is slow against its own history on
this machine: its wall is past trace_wall, 2 by default, and in a band
of powers of two two or more above the band of its median wall there,
at least twice that median. The machine is loaded at all times, so a
command that always takes 5 s there takes them quietly, and one that
takes 20 s where it took 5 says where they went. Each command keeps its
walls in the repository's mq/walls directory, a count for each band,
and one with no history yet prints nothing. The tree is one line a
span, indented under the span it opened within, with its milliseconds,
its name and what it waited on (a lock and its holder's pid, the index
and the bytes of its log, a child and its pid and arguments), under a
first line naming the command, its wall and the median it is past.
MQ_TRACE_WALL, in seconds, takes the history's place for one process,
as a harness whose output must not depend on the machine's speed sets
it: the command prints past that wall, 0 every time, and keeps no
history. MQ_TRACE_REPORT names a file
that takes the span tree in place of standard error: mq sets it for every
command a run starts (a check, the resolver, the lock command) to the
command's log with the extension .spans, so that an mq command a run
starts leaves its span tree beside its log and the reason mq reads from
its standard error is the command's own last line.
resolve_wall has no default, and the resolver is refused without it.
--wait-for is refused when no edge leads from the target to its branch. A
top-level entry names the entry branch when more than one named branch
takes no other.
Other repositories are no rule either. A repository names the ones it builds
with in its own tree, in its lock (The lock), and a
submodule's gitlink is Git's record, which mq reads and never lands into
(Several repositories). A top-level
name is the name the repository's branches take in its dependencies
(app/fix-ttl for name = "app"); it is required once a lock declares a
dependency, and the rules of two consumers on one machine must not share it.
A check named after a Git hook is that hook. pre-commit or pre-push
in a branch's list needs no section: mq runs the same hook script Git would,
with no copy and no parallel gate. A section is written for it only to grant
it a path or the network, or to set its priority. A branch's list is how
hooks are chosen per branch, which Git alone cannot do: staging runs the
pre-commit hook, and main adds pre-push. mq finds the script as Git
does, in the directory core.hooksPath names or else in hooks/ of the Git
directory, and resolves that with ocaml-git, never by running git. An
interop test holds the two equal, with git rev-parse --git-path hooks/pre-commit as the oracle. A core.hooksPath inside the work tree
(.githooks) resolves against the check worktree, so the hook comes from the
tree being checked, like any check. The hook runs with nothing staged, so a
hook that reads only staged files has nothing to check and needs a whole-tree
mode to be useful here.
A rule is changed like code. mq new looser-lint mq/config adds a worktree
that holds mq.toml. Its author edits and commits, and mq land lands it. A land into mq/config runs one
check, rules, which mq itself provides, so a [check.rules] section and a
branch that lists rules are refused. It passes when the file loads,
every branch it names exists, every check it names resolves at the tip of
each branch it is required on, and a branch a bump serves whose tree locks
dependencies (.mqmodule) has a repairer: a [repair] section, or a
[resolve] command with bounds.resolve_wall. Without one, a red bump's
repair request would wait for nobody, so the rules check refuses it, naming
the branch. A check that resolves and then fails at some
tip is that tip's own red, recorded the first time it runs there and charged
to no change. git log mq/config is the history of the rules, a team that
wants rule changes reviewed sends them through its forge like any branch, and
mq status BRANCH prints the rules in force for that branch.
Rules on the rules branch that do not load end every command but mq doctor
unavailable, naming mq.toml at its commit and the line, with next: mq doctor, which names the remedy. A land into the rules branch needs no rule
of the rules it replaces, as it is never put to the hook: mq new NAME mq/config hands the branch out whatever it holds, and mq land NAME lands
the fix under the rules check alone. The doctor's remedy is that land:
the mq.toml of the commit the rules last loaded at, the newest of the
branch's reflog or else of its history, landed into the rules branch. Every
machine that follows the rules takes it as a fast-forward; mq never rewinds
the rules branch.
A journal record or a record under refs/mq/ that this mq cannot read,
one another mq wrote, is passed over and named by every reader
(commands.md, "What a reader passes over"). Nothing is read
with a default, and the rules check refuses an mq.toml the landing mq
cannot load in full. In the repository that builds mq, each land into the
install branch is asked mq doctor --state of the live queue before it
lands (install.md, "The rules"), so the mq it installs meets
no rule it cannot load and no record it cannot read.
Four properties follow from keeping the rules apart from the trees they judge.
- A change never chooses the rules that judge it. A product change cannot
touch
mq/config, so nothing a change edits alters the rules that judge it. - A broken rule can be fixed. When a check
mainrequires is itself broken, the land that removes it goes intomq/configand passes one check, the validation. Rules kept in the product tree would have to pass the rule they repair. - One copy governs every branch. A release series cut months ago is
governed by
[branch."release/*"]as it reads today, with its checks run by the scripts of its own tree. A series that needs other rules gets a section of its own, landed like any rule. - A round decides under the rules it read. Each round reads the rules
at the rules branch's tip with the rest of its reading, and each result
names the rules it was checked under. A rule that lands governs the next
round, and since results are stored by tree, check and build
configuration, only a check the new rule adds or redefines is run again. A
pull that brings new rules sends this machine's unpublished landings back
through the queue, checked under the rules the next round reads, which on
a machine that follows
mq/configare the pulled ones. Published landings are never judged again.
Keeping the rules apart gives one guarantee up. A target's compare-and-swap
no longer sees a rule change, since the rules are not in the target's tree.
Inside one machine, what keeps a land under one set of rules is the
round: every rules read of a round uses the commit of the rules branch its
reading took, and a move needs a pass under those rules. Between machines,
a rule change is detected only as any concurrent change is: mq push finds
the remote's mq/config moved and names mq pull, and a machine learns of
a rule another machine landed at its next pull. Nothing judges a published
landing again when the rules change: a landing published under rules the
team has since changed is accepted, and only the note on its tree, which
names the commit of the rules that judged it, says which rules those were.
Zuul's config projects keep the policy apart from the change in the same way,
and so does Mergify, which reads the default branch's file. bors read
bors.toml from the change being merged, which let a change choose its
rules. The rules are a plain branch, where Gerrit keeps a project's
configuration on refs/meta/config, outside refs/heads/. A plain git clone fetches no ref kept there, and git branch, git log and a forge's
branch list do not show one.
Each machine lands under the rules it chooses. A machine reads the rules
from the branch its Git configuration names as mq.rules, mq/config by
default; the key chooses the rules and places no state. A teammate can
therefore run another DAG: a laptop that lands into main under fast checks, beside a build host whose DAG is staging -> main -> release/* with the edges. A personal DAG is a personal branch (git branch my-rules mq/config, then git config mq.rules my-rules). What a team
accepts from whom is the team's workflow, as it is for any push. mq/config
is not a branch of the DAG, and nothing lands from it into another branch.
mq push and mq pull carry it with the DAG's branches, so machines that
follow it share one set of rules, and mq drop refuses it. A machine that
writes the agreed copy is best left on the agreed rules, since what it
publishes is what its own rules checked. mq advises this and enforces
nothing.
On a machine with a scheduler, the run reads the rules branch that mq schedule start recorded, and never the repository's configuration. Every
caller there shares one configuration file, and one agent that tries a looser
DAG must not change the rules for all the others.
How a check runs (the script, the build command) lives in the product tree and is taken from the tree being checked. A check of tree T must come from T, since a script changes with the code it tests. Which checks run, and which command starts them, come from the rules. What mq trusts says what that leaves open.
The rules also name the resolver ([resolve], Conflicts):
its command, its default for a land no terminal typed, its layout,
what its worktree holds of the tree: "conflicts", the default, the paths
in conflict alone, every other one in the index with the skip-worktree bit,
as Git's mergetool reads only the conflict, or "tree", the whole tree, for
a command that reads more of it, and what it asks,
models, the models asked at once, in order, and max_tokens, the most
tokens a model may write in one request, its reasoning included. mq gives the
command the last two as MQ_RESOLVE_MODELS, the names separated by spaces,
and MQ_RESOLVE_MAX_TOKENS when the rules set them; mq resolve reads
them, and without them asks deepseek-v4p1-flash-1017 alone for at most
32768. A models that names no model,
or a name with a space, does not load. They may name a repairer the same
way, [repair] with a command, which it requires, models and
max_tokens, given as MQ_REPAIR_MODELS and
MQ_REPAIR_MAX_TOKENS, and the resolver's three grants: what answers a red
bump's repair request after the red check's own repair
(Commands, "When a dependency moves"), and an entry's own red
no check's repair answers (Runs, "A repair"). Without [repair] the
resolver is the model's repairer: the [resolve] command,
given MQ_RESOLVE_TASK=repair and the repair's brief on its standard input,
with its own model, token bound and grants, bounded by the red check's wall
and bounds.resolve_wall, which it needs here as it does for a conflict. A
repository whose rules name either never waits on a person for a
dependency's move; with neither, and no repair on the red check, each
request is journaled repair-failed, saying no section names a repairer,
for the hook to tell one.
The rules also name one hook, a command that receives every event and may
refuse a branch when it is queued or handed out. A hand-out is mq new's:
once the worktree is made, and before its path is printed, the
handed-out event (the branch, its base, its description's fields, the
worktree and where it came from) is put to the hook in the same way, and a refusal makes mq new
remove the worktree, the branch it created and that branch's record,
so a tracker can claim the issue the branch's Bug field names, or refuse
it, with no command of its own. The refusal is the hook's verdict on the
queued event's line, given before the pin is written and bounded by
bounds.hook_wall: exit 0 admits, any other exit refuses with the hook's
first line of standard error as the reason, and a hook past its bound ends
the land unavailable. A land into the rules branch is never put to it, so
the land that fixes a broken hook always lands; nor is a serve of a branch
the rules name: its commits were put to the hook when they landed into it,
so mq land main --onto full costs no hook call however many commits it
carries, and its queued event is journaled at once. The queued event the journal
then holds is the line the hook judged, byte for byte, and the hook's
environment says which call it is: MQ_HOOK=verdict at queue or hand-out time,
MQ_HOOK=delivery otherwise. judges names the event kinds the hook gives a
verdict on, queued and handed-out, both by default; an event of a kind it
does not name is not put to it as a verdict, so a hook that admits every
hand-out says judges = ["queued"] and mq new starts no process for it.
A hook that only judges says
deliver = false in its section: it is asked for its verdicts and is
delivered no event, so a land starts it once, and mq status counts no
pending delivery, those written before the key included; the delivery's
position stays where it stood, so removing the key delivers from there what the journal
still holds. A hook that acts on a few types
names them, as a webhook names the events it subscribes to (GitHub's
events, Stripe's enabled_events): deliver = ["outcome.landed", "culprit"] puts to it the events of those types alone, each name a
CloudEvents type after merge-queue., and the rules check refuses a name
no event is written as. The delivery passes an event of another type with no call, it
waits behind no event of its subject, and mq status counts it in no
pending delivery; naming a type later delivers its events from then on,
not those passed before. Otherwise every event is delivered
by the task mq task hook, under its lease, given the records the hook
owes that are due, oldest first (design,
"The delivery"): one CloudEvents JSON line a call on standard input, at
least once, with the same id and bytes on every redelivery, acknowledged
only by exit 0, each call bounded by bounds.hook_wall. Each event is
delivered on its own, as GitHub, Stripe and Knative deliver a webhook, and
the events of one subject reach the hook in journal order, as a Kafka
consumer reads one key's records in order: an event waits while an earlier
event of its branch is held, and an event with no branch waits for none.
The task answers with one hook-delivered record naming the events the hook
acknowledged and those it held: refused, or behind a refused event of their
subject. A held event is put again once a run starts after that answer, and
on no schedule. A delivery call is given MQ_HOOK_ANSWER, the path of an
empty file in which the hook may write its answer, one word and its argument
a line, as a GitHub Actions step writes $GITHUB_OUTPUT; mq reads the file
once the call ends and deletes it. A final line, with a refusal, says no
retry can change it: the event is recorded once, as a hook-failed event
naming it, its reason and its calls, and passed over. A file with any other
line is no answer, and its call is a refusal. Each refusal is recorded, as a
hook-refused event naming the event, its call, its reason and its log; a
hook-refused or hook-delivered event is not itself put to the hook. A call
past its bound or that cannot start is recorded as a refused one is. mq status says hooks N pending while N events the hook owes are due and
the hook's task does not hold its lease (mq/lease/hook), with the events
the hook held and the oldest of them, its reason and when it was refused,
and the events passed over that mq doctor --repair has not delivered
since. No delivery delays a land: the delivery is a task beside the lands'
tasks, which a land and a run each start as they begin, as they end and as
each of their rounds writes an event the hook takes, and a drop as it ends,
whenever an event is due; none waits for it. A call a signal stops keeps its
input in mq/logs/, and its standard error ends with the stop. The hook
runs in the repository's main checkout with the caller's environment,
unconfined, whatever mq.confine says. A tracker is such a hook, and it reads the
fields mq new stored in the branch's description, from the event or with
mq field NAME KEY. mq itself has no word for
a tracker, and --field Issue=123 is a key and a value to it. A description
is never pushed, so a published history carries no tracker id unless its
author writes one. A public repository that must refuse a private tracker's
identifiers says so in its hook: it reads a message's trailers as Git parses
them (git interpret-trailers --parse) and asks the tracker whether a token
is one of its issues. A pattern decides nothing, since a prefix that names a
tracker's issues names packages and English words as well.
One machine's facts are not rules. They live in that machine's Git
configuration and are set with git config.
| Key | What it sets |
|---|---|
mq.rules |
the branch that holds this machine's rules (default mq/config) |
mq.pool |
the pool's directory (default <repository>.worktrees, beside the repository), one directory however it is spelled: mq reads it with no trailing slash and as realpath(3) resolves it when it exists, and compares every path under it the same way |
mq.poolBudget |
the disk the parked worktrees may use; a hand-out evicts them, least recently used first, to keep within it, and a worktree in use is outside it |
mq.freeSpace |
the free space the volume must keep; a hand-out under it evicts the parked worktrees, and ends unavailable with the measured free space and floor when the volume stays under it with none left |
mq.slots |
how many builds the repository runs at once |
mq.generations |
read by the code as how many generations of each dependency the pool keeps beside the ones a check reads (default 4); a generation is a worktree locked with git worktree lock and reaped when no worktree links it, never by count (The state, Where the code stands) |
mq.checkLogs |
the disk the logs of ended checks may use (default 512M, two hours of eight lanes' checks); a writer of a log deletes the oldest past it, and the logs of a check under way in any process are outside it |
mq.private |
a repository on this machine whose content must not reach this one, one key each (Several repositories) |
mq.dependencies |
where mq init clones a consumer's dependencies this machine lacks (default the parent of the main checkout; A fresh clone) |
mq.confine |
whether checks and the resolver run confined: off (default), as a Git hook runs, or auto where the machine can (How a check is confined) |
mq.evidenceKey |
the Ed25519 private key, a PEM file, that signs this machine's evidence (Evidence) |
mq.evidenceName |
the package this machine's evidence names, a word of letters, digits, ., _ and -; set exactly when mq.evidenceKey is |
Each key is read as git config --get reads it, from every file Git reads,
the last value counting, except mq.private, read as git config --get-all
reads it: every value counts, so a repository's own declaration adds to a
global one rather than hiding it. A path is read as git config --type=path reads it and is absolute; a size or a
count as git config --type=int reads it, a size not negative and a count
positive. A value that is not one is refused, naming the key, the value and
the file that set it, with next: git config --file FILE --unset-all KEY,
which leaves the value an earlier file sets, or the default; a value set in
the environment is in no file, and its refusal names no next.
The remote a branch is published to is the one git push would send it to,
from Git's own configuration (mq push), and the
scheduler's registration is the operating system's. mq has no config
command: the shared rules are edited with an editor and landed, and the local
facts are Git's.
The pool's stock #
mq keeps worktrees parked at the tip of each branch the rules name, so
that mq new takes one whose files are the tip's and writes only what its
branch changes. The restock is a task, mq task restock, under the lease
restock, which no run waits for. A run starts it while a place of a
branch's stock holds no parked worktree: each worktree parked under
.idle/BRANCH/ takes the first place of that branch's stock that holds
none (design.md, "The machine's interface"). The task reads each branch's stock and, while one is short of its target,
takes one step at a time: it moves a parked worktree off the tip there by
the diff, or adds one worktree at the tip, journaled as parked with the
reason restock (a landed worktree parked by its tidy has the reason
landed); it ends once every stock is at its target. A hand-out that
finds nothing parked adds its own, and its handed-out event says
added: nothing parked.
The [pool] section's stock is the target's floor, 2 when the rules have
no [pool] section, 0 for none. The hand-outs on a branch in the last ten
minutes raise its target, up to stock_max, 8 when unset (or stock, when
that is higher), never under stock: a burst of lanes finds worktrees
parked, and a peer that asks for hand-outs makes the run park at most
stock_max worktrees a branch ahead of them.
[pool]
stock = 2
stock_max = 8
The pool's volume #
The pool's worktree adds and removes and the checks' _build trees make and
delete hundreds of thousands of files an hour, and so does the build system's
cache. On macOS, fseventsd logs every file event of a volume whose root holds
no .fseventsd/no_log, and XProtect assesses each new executable; on a busy
host the three spend cores between them. The pool and the build cache share
one dedicated APFS volume whose root holds .fseventsd/no_log, with
.noindex in its name so that Spotlight passes over it. They share it
because a build restores each cached file into a worktree by cloning it
(clonefile(2), dune's copy storage mode), and a clone cannot cross volumes:
from a cache on another volume every restore copies the file's bytes. A
dune restoring 54803 rules from a cache on the startup disk took 182 s and
199 s into a pool worktree on its own volume, and 82 s and 99 s into a clone
on the cache's volume, interleaved on one host. The build cache's directory
is a link onto the volume, so every build on the host, a check's or a
lane's, restores by clone with no setting of its own:
diskutil apfs addVolume <container> APFS mq -mountpoint /tmp/mq.noindex
mkdir -p /tmp/mq.noindex/.fseventsd /tmp/mq.noindex/dune-cache
touch /tmp/mq.noindex/.fseventsd/no_log
mv ~/.cache/dune ~/.cache/dune.old && ln -s /tmp/mq.noindex/dune-cache ~/.cache/dune
git config mq.pool /tmp/mq.noindex/<repository>
and dune's configuration, ~/.config/dune/config, keeps the copy mode, in
which a restore is a clone and a build that writes into its output never
writes into the cache:
(cache enabled)
(cache-storage-mode copy)
<container> is the APFS container of the startup disk, the
APFS Container that diskutil info / names. The log stops once fseventsd
next mounts the volume: unmount and mount it again
(diskutil unmount mq && diskutil mount mq). mq doctor reads the
volume the pool is on, the topmost directory on the pool's device, and names a
pool on a volume fseventsd logs as a volume finding, and a build cache on
another device than the pool, where every restore copies, as another.
The cache on the volume grows with every build on the host, and dune bounds
it only when told to: dune's configuration has no trimming policy. Eight lanes and six check slots grew
it by 1.5 GB a minute, to 177 GB in seven hours, until the volume fell under
mq.freeSpace and every hand-out was refused while evicting every parked
worktree could not help. The [pool] section's cache_size bounds it, in
bytes, 60 GB (60000000000, dune's GB) when unset:
[pool]
cache_size = 60000000000
The trim is a task, mq task trim, under the lease trim. A run starts
it once the cache entries a check added are owned by no tree and
configuration a train or a target's tip still reads (design.md, "The
machine's interface"). The task reads
the cache's size, the bytes of the files no build directory links to as
dune cache size counts them, and over the bound runs dune cache trim --size=BOUNDB with
DUNE_CACHE_ROOT set to the cache's root (dune keeps its entries under the
root's db), its output in mq/logs/cache-trim.log, and journals a
trimmed event with the cache's bytes before and after and the bound; a
trim that leaves the cache over its bound says it is still over it, with
the bytes freed or "nothing freed". The task holds no run lock, its
lease, trim, names no deadline, and no run waits for it, so no land
waits behind a trim. A trim costs what dune's own does, which
reads every entry and every metadata entry of the cache: 3440 s of wall at
load 72 for a cache of 325000 entries and 1.47 million metadata entries.
One trim of a cache runs at a time, whichever repository's queue or hand
starts it: the task holds an exclusive flock on mq-trim.lock in the
cache's directory from its first count to its last, dune's processes
inheriting it, and beside a trim that holds it runs no dune and prints the
repository and process the lock's file names.
The pool's reading under mq.freeSpace and the doctor's are dune's count
under a wall: a hand-out refused for mq.freeSpace names the cache's size
and bound when the cache is over it and dune counted it within ten seconds,
and mq doctor names a cache over its bound as a volume finding.
Each check worktree under the pool's .check/ keeps its _build between
checks, so that the next check of its target starts warm. Its files are
links into the cache or outputs only it holds, and a cold one restores from
the cache in seconds, so the worktrees' builds are bounded too: every five
minutes into a scheduled run, the run reads the bytes of each
_build no other directory links to, and while they total more than the
[pool] section's check_build_size, half of cache_size when unset, it sets aside
under .reap/, for the reap to delete, the _build of the worktrees
no check runs in, least recently used first, until the rest are within it.
A worktree a check runs in, or is being taken by one, keeps its _build. The reading walks a _build,
one stat per file, only when nothing records its bytes: a walk writes them
into _build/.mq-bytes, every later reading reads that record and stats no
file of the build, and a check that ends in the worktree, however it ends,
removes the record, so the next reading walks that build once again. A
cleared _build takes its record along and counts nothing. A worktree a
check runs in is never walked, its build being written: it counts its
record, or nothing until its check ends. Parsimoni's .check/main/.1.suite
alone held 50170 files, walked by every reading of every five minutes
before the record. A cleared event names each worktree cleared with its
bytes, the pass's wall from its reading of the builds to the event, and
how many build directories it walked.
[pool]
check_build_size = 30000000000
Where the state lives #
mq keeps two places. The repository's common directory holds mq/, the
records a run and a command read and write, each a file or a directory of
small files; the pool holds every tree mq checks out, builds in and deletes,
on the pool's volume, so that a build there restores from the cache by clone
and the trees' file events stay off the startup disk.
| Where | What |
|---|---|
mq/journal, mq/index.N.db |
the journal and the index folded from it, one file per schema identity |
mq/run.lock, mq/lease/, mq/*.lock, mq/slots.*, mq/resolvers.* |
the locks, leases and slots of the runs and checks |
mq/requests/, mq/schedule, mq/last-run, mq/wake, mq/walls |
the requests to the next round, the scheduler's records, the commands' walls |
mq/logs/, mq/run.log, mq/sync.log |
the check logs and their traces, bounded by mq.checkLogs, and the tasks' logs |
mq/cone/, mq/cone-answers/, mq/cone-graph, mq/cone-target/ |
the cones asked and their answers, and the checkout of the graph files alone that a cone reads |
mq/tip-failures/ |
the target tip's verdicts, one small file each |
mq/tip-lint/TARGET/TIP/ |
the lint readings of the tip of TARGET a check last compared with, one small file each: a reading kept for a newer tip of TARGET removes those of every other |
mq/tip-red |
the runs of suites and of their comparisons at commits each target holds or may yet hold, from which each read derives the tests red at its tip (mq ocaml) |
.idle/, .stock/ |
the parked worktrees and those being parked |
.check/TARGET/.N.CHECK |
the check worktrees |
.tip/TARGET/TIP/.N.CHECK, .tip/TARGET/.N.CHECK.lock |
the trees a check builds the target's tip in, under the target and tip that own them, and their slots' locks (mq ocaml) |
.deps/, .resolve/ |
the dependencies' generations and the resolver's worktrees |
.reap/ |
the trees set aside for the reap (mq task reap) |
Sync #
A machine reaches the agreed copy when someone types mq pull and mq push, or mq sync, which is the one then the other. The [sync] section,
which takes no key, has the queue do it, the same on every machine that
reads these rules:
[sync]
A round starts mq sync whenever a branch the rules name, the rules
branch or a remote-tracking copy of one holds another commit than it did
when the last sync started, and no sync runs: a landing is pushed as it
lands, and a pull runs once the copy moved, as a fetch moves it. No sync
polls the remote: what another machine publishes reaches this one at its
next sync, which a move here, a fetch or a typed mq sync starts. The
sync is a task beside the lands' tasks, in a process of its own and a
session of its own, its output in mq/sync.log, its start and process in
mq/synced and the commits it started from in mq/synced-tips. Neither
waits for the other: a pull and a push take no run lock and run no check.
A freeze stops the sync with everything else: no run starts it, and a sync
typed by hand fetches but moves no branch and pushes nothing, each refusal
naming the freeze (commands.md, mq freeze).
A round reads whether a sync is due when the run starts, each time its
work appends to the journal, as a landing does, and once more as the run's
work ends; a sync that ends wakes the scheduler's job, so a landing made
while it ran starts the next one. A commit made with Git on the rules
branch changes mq/wake (the reference-transaction hook changes it when
a branch the rules name or the rules branch moves), and the run that wake
starts publishes it: a run lasts seconds and waits on no check. A
sync's own push moves the copy it pushes to, so it leaves one more sync to
find nothing to do. Whatever starts it, a sync holds mq/sync.lock from
its pull's fetch to its push's end, as a typed pull and push each hold it,
and a consumer's push holds
each dependency's while it publishes the dependency: a second sync, or a
consumer's sync that publishes this repository, waits for the first to end
rather than read the remote while it publishes. A remote that stops
answering ends the sync's pull or push within bounds.remote_stall,
unavailable and naming the remote, so the lock is released and the next
run starts a sync again. A machine with no scheduler registered syncs only
when typed.
A repository with a remote wants the rule: the queue is what publishes its
landings, as a merge queue that lands on the remote publishes as it lands,
and a landing the agreed copy lacks is an incident, not a setting
(invariants, liveness 12). mq init writes
[sync] into the rules it creates when the repository has a remote.
Without the rule, mq status says so on a line of its own, in the alert
colour, with the rule to add (sync overdue: the rules have no [sync] section: nothing publishes landings to origin but a typed mq push · add [sync] to mq/config), and mq doctor names it with the same remedy; each
branch whose copy lacks a landing is then overdue since that landing, from
the landing on. The time is the branch's own: the first line of its reflog
after its copy last changed, so a commit made with Git on the rules branch,
which no landing record names, is overdue from that commit, never from the
copy's older change. With the rule, a branch is overdue once a push since its
landing was not taken, or once its copy lacks the landing past
bounds.run_start, or at once on a machine with no scheduler registered,
with why no sync published it: none did (no sync published it within bounds.run_start (5m)), or its last push was not taken, refused or its
remote out of reach, with the push's own words (its last push was not taken: origin could not be reached: ...). A push refused on a divergence
no pull ends is journalled once for that pair of values, and the line
names it with the command that ends it until the branch or its copy moves;
each sync's push reads the remote again, which is how it sees the
divergence end.
Each repository syncs on its own, the dependency as the consumer. A consumer's push refuses a lock that names a dependency commit the dependency's agreed copy lacks (commands), and its pull refuses one no landing record maps; either goes through at a later sync of the consumer, once the dependency's own has published or pulled it. No sync pulls or pushes a dependency on a consumer's behalf.
The lock #
A consumer's dependencies are part of its tree, so each branch, each commit
and each release series says what it builds with. The lock, a file at the
root of the tree, holds one section per dependency, in Git's configuration
syntax (git-config(1), "CONFIGURATION FILE"). Its filename is .mqmodule.
The url is the repository identity and fetch location, in Git's remote
syntax, including local paths. Section names label dependency links.
Relative local sources resolve against the declaring repository's root.
One function classifies every source through nox-git's remote codec; mq
never guesses that different mirrors or renamed URLs are the same identity.
The declared path binds the source to a local checkout whose Git common
directory supplies its run lock. Commit reachability proves availability,
never identity or authenticity.
[dependency "lib"]
path = vendor/lib
url = https://example.org/lib.git
commit = <the 40 hexadecimal digits of a commit of lib>
back = site
| Key | What it says | Written by |
|---|---|---|
path |
where the dependency's symbolic link stands in a checkout of the consumer, relative to its root | the consumer's author |
url |
repository identity and source a fresh clone fetches from, including local paths | the consumer's author |
commit |
the commit of the dependency that goes with this commit of the consumer | mq's lock commit, at every land whose paired branch's tip differs from it (Several repositories) |
back |
optional: the path, in the dependency's tree, of a tracked relative link that names this consumer | the consumer's author |
The section's name labels a link in this consumer; another consumer may
use the same label for a different URL. Two declared links with one URL
that resolve to different local Git common directories are refused at the
land that meets both, naming the two paths. A section with a key
missing, a path that leaves the tree, a commit that is not a full commit
id or two sections with one path are refused at mq new and mq land,
naming the file, the section and the key. A lock is read from a tree with
Git alone (git config --blob REV:.mqmodule --list); mq reads and writes it
through nox-git's configuration codec, and an interoperability test holds
the bytes it writes equal to those git config -f .mqmodule dependency.NAME.commit C writes.
The development loop reads the link at path, never the lock: the link
names whatever the developer has there, and a check, a fresh clone and a
release read the lock. A check's build reads the dependency through the
link at path as the development loop does, so Dune, Make or Cargo build it
with no adapter beyond that path, and a build that needs the commit itself
reads the lock. A build system's own lock file and a .gitmodules for
Git's submodule tools are derived from the lock, and choose no commit of
their own.
The [lock] section of the rules names how: command, run by /bin/sh -c
at the root of a worktree of each lock commit mq writes, whose dependencies'
paths read the commits it selects, and of each land's replayed commits whose
lock moves no dependency, since the command reads the consumer's tree too,
bounded by bounds.check_wall, with
MQ_TARGET set; and paths, the files and directories, relative to the
root, that the command derives. The lock commit then holds each of paths
as the command left it (a file with its executable bit, a link, a directory,
or nothing), and nothing else the command wrote: a landed tree's lock and
what it derives never disagree, and nobody regenerates them by hand. A
command that does not exit 0 ends the land unavailable, naming its log,
relock-COMMIT.log beside the checks' logs. A path that leaves the tree, the
lock itself, a section without a command or with no path is refused when
the rules are read. With no [lock], the lock commit changes the lock
alone.
Generated files #
A file a build writes from the tree's sources and the tree also commits,
as dune build @install writes each .opam from dune-project, has one
producer: its generator. The [generated] section names them: files,
path patterns relative to the root in the syntax of .gitattributes
(gitattributes(5), "PATTERN FORMAT"), and command, the generator, run by
/bin/sh -c at the root of a worktree of a replayed commit. A replay whose
pick conflicts in these files alone is regenerated rather than held
(Conflicts, "Generated files"). A section without a
command, with no file or with an empty pattern is refused when the rules
are read. With no [generated], every conflict is a conflict.
The install #
The repository that builds mq names, in an [install] section, how the
scheduled run keeps the mq it runs built from a branch it checks
(Installing from the queue's own branch): branch, a branch
the rules name by its own name; check, the check that builds the
executable and starts it, a [check.NAME] section that no branch lists,
so the check worktree it runs in is its own; executable, the file that
check leaves, a path inside the tree; and trial, how long after an
install a run that could not run or ended as a defect rolls it back, 30m
when unset. A
section that lacks one of the first three, names a branch or a check the
rules do not, a check a branch lists, or a path that leaves the tree is
refused when the rules are read. With no [install], the run installs
nothing.
[check.install]
command = "dune build bin/main.exe @test/startup"
[install]
branch = "staging"
check = "install"
executable = "_build/default/bin/main.exe"
trial = "30m"