Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
62 commits
Select commit Hold shift + click to select a range
a86ba10
Amendment 23 S3: every writer fetches, checks and fast-forwards its c…
Oct 20, 2026
ef1377f
Amendment 23 S4: every write commits by pathspec, pushes with no pull…
Oct 20, 2026
e3b6810
Amendment 23 S3 for the sweeps and `rename-lane`: each takes S3 under…
Oct 20, 2026
139144e
Merge slice 1a's fix round 2 (eead86d) into slice 1b: git found no co…
Oct 20, 2026
7da3d61
R4-1: under the opt-in, `log`, `commit`, `request-handoff` and `appen…
Oct 20, 2026
3905009
B23: a confirmed push still says `pushed origin/<branch> (attempt N)`…
Oct 20, 2026
2e2eee4
Merge slice 1a's fix round 3 (15c8e3f, R4-2) into slice 1b: git found…
Oct 21, 2026
a41762b
B31: supervised_restart's fixture no longer exports `LANES_NO_FETCH=1…
Oct 21, 2026
6ecfc52
MUST 3 (rv1b's pre-review): `lane-worktrees sweep --yes` writes its N…
Oct 21, 2026
c454247
MUST 4 (rv1b's pre-review): a refused push whose commit is no longer …
Oct 21, 2026
10169f3
MUSTs 1 and 2 (rv1b's pre-review): `claim` and `add-row` run again fr…
Oct 21, 2026
92fbe46
SHOULD 1 and NIT 1 (rv1b's pre-review): the manual no longer describe…
Oct 21, 2026
3c6b240
rv1b's second report: one tracked and one untracked log is named as t…
Oct 21, 2026
6471fd1
MUST-N and SHOULD B (rv1b-c's re-check at 10169f3): `claim` and `add-…
Oct 21, 2026
d9ff4e6
SHOULD A and NITs 1-2 (rv1b-c's re-check at 10169f3): a commit anothe…
Oct 21, 2026
c71621f
SHOULD C (rv1b-c2's re-check at d9ff4e6): the replay's drop is a comp…
Oct 21, 2026
e6ea8f8
Amendment 23 S3-S4 in the shell suite: every case that pinned the pul…
Oct 21, 2026
948e1e0
NIT 3 (rv1b-c2's re-check at d9ff4e6, re-aimed by SHOULD C): the carr…
Oct 21, 2026
9683fd1
NIT 4 (rv1b-c2's re-check at d9ff4e6): migrate-state-cells' skip line…
Oct 21, 2026
9cdf8ab
A37, slice 2a's, built in 1b: `CP_KEEP_IF_GONE` names the one file a …
Oct 21, 2026
4b94f87
B04 (m1b-c2's mutant run): the replay's HEAD check has a case that on…
Oct 21, 2026
638b13a
The refusal with origin unmoved reads HEAD before it says "kept on HE…
Oct 22, 2026
514b0c4
migrate-state-cells' line for two logs whose names differ only by cas…
Oct 22, 2026
0c822f3
The carry form's tree step refused has a case: the branch is put back…
Oct 22, 2026
f9b1de9
The last two places that said "kept on HEAD" without reading HEAD rea…
Oct 22, 2026
3a4bc4a
A drop's compare-and-swap refused for a reason that is not a moved br…
Oct 22, 2026
251e900
`migrate-state-cells` calls a case-only rename "committed here" only …
Oct 22, 2026
24efc8a
A file a write itself creates has a case in the A37 check: the `^` of…
Oct 22, 2026
5588307
The manual says what `add-row`'s refusal of a row a peer pushed says:…
Oct 22, 2026
c71068b
S3 fast-forwards to the sha its own fetch read, not to `refs/remotes/…
Oct 22, 2026
dba848a
A write knows its own commit by what it is, not by reading HEAD after…
Oct 22, 2026
db4d2a4
`commit`'s carry retries a hand edit with `git apply -C0` only where …
Oct 22, 2026
2165551
The read-back that stops a doubled log line has a case: a push git ca…
Oct 22, 2026
85575ee
The managed seam is asked again right after S3, at the five legacy si…
Oct 22, 2026
c2fa058
Under the opt-in, `claim`, `release` and the sweeps' runner lane clon…
Oct 22, 2026
a465cb5
A claim that runs again from the top with the lock held makes no netw…
Oct 22, 2026
333af4e
The manual names the re-run decisions 19 and 22 made: where S3's fetc…
Oct 22, 2026
93475ae
S3's diverged refusal tells a checkout holding a hand merge to archiv…
Oct 22, 2026
afad3e5
The NOT TRACKED recoveries of the migration, `retire-rows` and `archi…
Oct 22, 2026
7faa577
The publish act's refusal over a stack origin has moved past fills th…
Oct 22, 2026
fc6f34d
Exit row 3, in both tables and in the manual's step 5, says the uncon…
Oct 22, 2026
15e71d9
`commit`'s carry tries `git apply -C0` only where every line the hand…
Oct 22, 2026
53108b4
Under the opt-in, the NOT TRACKED recovery a dry run reads in W gives…
Oct 22, 2026
aa0f864
A claim's re-run is said to make no GitHub read, not no network read:…
Oct 22, 2026
921dd58
S3's list and reset recoveries and the publish act's list line name `…
Oct 22, 2026
29103a5
Merge slice 1a's SC2093 disable (6ca3158) into slice 1b: git found no…
Oct 22, 2026
a44b73d
A hand edit that is also staged is carried against HEAD's version, ne…
Oct 22, 2026
0e56301
The comment over `cs_removed_unique` says `.git/lanes-edit/` holds fi…
Oct 22, 2026
aa45be1
A claim's re-run carries attempt 1's reads capped at 32,768 bytes, so…
Oct 22, 2026
50e8916
Once the read-back finds a carried hand edit on origin, each copy of …
Oct 22, 2026
af29be6
`cp_under` names the commits under this write's as ones origin lacks …
Oct 22, 2026
173f3c8
The refusal with origin unmoved lists the commits under this write's …
Oct 22, 2026
40c57b9
A writer whose checkout, or the directory its lock lives in, is remov…
Oct 22, 2026
126bccd
The two routing `exec` lines slice 1b added, `cs_recheck`'s re-run an…
Oct 22, 2026
59551dd
A write whose `LANES_REPO` names a directory below its checkout's top…
Oct 23, 2026
9137569
A sidecar act whose `<W>-lanes/` folder is moved aside while it waits…
Oct 23, 2026
9785ad9
S6.3's archive, as `cp_under` and the publish act print it, names the…
Oct 23, 2026
0ddde5e
A claim's re-run posts Rule 1's reads whole, from a file attempt 1 ke…
Oct 23, 2026
458cdde
The recovery commands S4's refusals print name origin's tracking ref …
Oct 23, 2026
f97723c
Where origin moved, the bound's, the keep-if-gone and `cp_not_head`'s…
Oct 23, 2026
80ae89e
After a lost race, a `commit` whose re-run carry no longer applies na…
Oct 23, 2026
4b79d67
The two-copies test asserts one removal note per copy the run recorde…
Oct 24, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
263 changes: 131 additions & 132 deletions docs/README-lanes.md

Large diffs are not rendered by default.

11 changes: 8 additions & 3 deletions lane-worktrees
Original file line number Diff line number Diff line change
Expand Up @@ -490,13 +490,18 @@ class Helper:
self._fetched = False

def call(self, *args: str, env_extra: dict | None = None, fetch: bool = False,
timeout: int = 300):
write: bool = False, timeout: int = 300):
"""`fetch` asks for one fetch per session; every later read is made
with `LANES_NO_FETCH=1`. A `write` is never given it: for a writer, a
fetch that did not run is a fetch that failed, and lanes-edit.sh's
Amendment 23 S3 refuses it (2, ratified choice 2), so the sweep's
NOTED line was refused after its first removal (#198, slice 1b)."""
if not self.path:
return subprocess.CompletedProcess(args, 127, "", "no lanes-edit.sh beside this "
"command or on PATH")
env = dict(os.environ)
env["LANES_INDEX"] = "off"
if not fetch or self._fetched:
if not write and (not fetch or self._fetched):
env["LANES_NO_FETCH"] = "1"
if env_extra:
env.update(env_extra)
Expand Down Expand Up @@ -669,7 +674,7 @@ class Helper:
if session:
extra["LANES_SESSION"] = session
return self.call("log", "NOTED", f"lane:{lane}", register_text(text),
env_extra=extra, fetch=False)
env_extra=extra, write=True)


LANE_LINE_RE = re.compile(r"^(?:STARTED|PAUSED|RESUMED|ENDED|RETIRED) — lane [^,]+, "
Expand Down
2,282 changes: 1,616 additions & 666 deletions lanes-edit.sh

Large diffs are not rendered by default.

79 changes: 79 additions & 0 deletions tests/a23_box.py
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,22 @@
exit 90
"""

#: `LANES_GIT`: every NETWORK call the helper makes, logged one per line, then
#: an optional hook (`FAKE_NET_HOOK`, run as `<hook> <op> <git args…>`), then the
#: real git. The hook decides: exit 0 runs the real git; exit 97 means the hook
#: ran git itself and the call succeeded; any other exit is the call's own
#: (a failure). `<op>` is the git subcommand: fetch, push, ls-remote or clone.
NET_GIT = """#!/usr/bin/env bash
op=""
for a in "$@"; do case "$a" in fetch|push|pull|ls-remote|clone) op="$a"; break ;; esac; done
printf '%s %s\\n' "$op" "$*" >> "$FAKE_NET_LOG"
if [ -n "${FAKE_NET_HOOK:-}" ]; then
rc=0; "$FAKE_NET_HOOK" "$op" "$@" || rc=$?
case "$rc" in 0) : ;; 97) exit 0 ;; *) exit "$rc" ;; esac
fi
exec git "$@"
"""


def register_text(rows) -> str:
return HEADER + "".join(f"{r}\n" for r in rows)
Expand Down Expand Up @@ -150,6 +166,69 @@ def peer_pushes(self, rel: str, text: str) -> None:
self.commit(self.seed, f"a peer writes {rel}", rel)
self.git("-C", str(self.seed), "push", "-q", "origin", "main")

def peer_edits(self, rel: str, old: str, new: str, message: str = "") -> None:
"""The seed clone, brought up to date, replaces `old` with `new` in one
file and publishes it: another writer's commit landing first."""
self.git("-C", str(self.seed), "pull", "-q", "--ff-only", "origin", "main")
path = self.seed / rel
text = path.read_text(encoding="utf-8")
assert text.count(old) == 1, (rel, old)
path.write_text(text.replace(old, new), encoding="utf-8")
self.commit(self.seed, message or f"a peer edits {rel}", rel)
self.git("-C", str(self.seed), "push", "-q", "origin", "main")

# ------------------------------------------------------------- the seam
def seam(self, name: str = "c") -> Path:
"""A clone of origin named by the explicit `LANES_REPO` (S2.1, the
test seam): every write of this box then goes through it, in place."""
checkout = self.clone(self.root / name)
self.env["LANES_REPO"] = str(checkout)
return checkout

def use_net(self, hook: str = "") -> Path:
"""Route the helper's network calls through `NET_GIT`, logged to
`self.net_log`; `hook` is the body of `FAKE_NET_HOOK`, if any."""
shim = self.fakebin / "net-git"
shim.write_text(NET_GIT, encoding="utf-8")
shim.chmod(0o755)
self.net_log = self.root / "net.log"
self.net_log.write_text("", encoding="utf-8")
self.env["LANES_GIT"] = str(shim)
self.env["FAKE_NET_LOG"] = str(self.net_log)
if hook:
body = self.fakebin / "net-hook"
body.write_text("#!/usr/bin/env bash\n" + hook, encoding="utf-8")
body.chmod(0o755)
self.env["FAKE_NET_HOOK"] = str(body)
return shim

def net_ops(self) -> list:
"""The network operations the helper made, in order."""
return [line.split(" ", 1)[0] for line in
self.net_log.read_text(encoding="utf-8").splitlines()]

def snapshot(self, checkout: Path) -> tuple:
"""Everything a refusal must leave as it was: HEAD and the branch it
is on, the index, the working tree, and origin's tip."""
c = ["-C", str(checkout)]
return (self.git(*c, "rev-parse", "HEAD").strip(),
self.git(*c, "symbolic-ref", "-q", "HEAD", check=False).strip(),
self.git(*c, "status", "--porcelain", "--untracked-files=all"),
self.git(*c, "diff"),
self.git(*c, "diff", "--cached"),
self.origin_head())

@staticmethod
def lock_of(checkout: Path) -> Path:
return checkout / "lanes" / ".lanes-edit.lock"

def unpublished(self, checkout: Path) -> list:
"""`git cherry -v origin/main HEAD`'s `+` lines: what origin lacks."""
self.git("-C", str(checkout), "fetch", "-q", "origin")
return [line for line in self.git("-C", str(checkout), "cherry", "-v",
"refs/remotes/origin/main", "HEAD").splitlines()
if line.startswith("+")]

# ------------------------------------------------------------- the helper
def cmd(self, *args: str) -> list:
return ["bash", str(REPO / "lanes-edit.sh"), *args]
Expand Down
Loading
Loading