Skip to content

residue v0.0.6

Choose a tag to compare

@github-actions github-actions released this 07 Nov 13:15
· 129 commits to main since this release
v0.0.6
88a7835
  • Tests (#10): scripts/recovery-check.sh checks what remove leaves when it is stopped right before git worktree remove, when the check after the move fails, when the move back fails or the original path is taken, and when a second remover of the same worktree runs after the first (with and without the trash), each fault with a control that removes when nothing is injected. A git script first on PATH holds or fails one Git command at a time; residue has no test hook. 8 cases. The test scripts unset GIT_CONFIG_GLOBAL and GIT_CONFIG_SYSTEM, so a caller's global Git config is never read or written. scripts/race-check.sh now stops a run that does not end within 60 s of the answer, with everything it started, and fails it instead of waiting forever. Outside both: a kill during git worktree remove itself.

  • Tests (#8): scripts/contract-check.sh runs remove with no prompt against real Git, one repository per case, and pins its contract: the exit status (0; 3 for a refusal, a skip or an invalid target; 2 for a usage error), one residue.remove/v1 document on stdout with the per-target result, reason and failing check ids, diagnostics on stderr only, and afterwards the file bytes, the registration, the branch, an unrelated worktree and no leftovers. 17 cases; a setup failure stops it with status 2, never as a product result. CI runs it on macOS, Linux and FreeBSD (as root and as an ordinary user).

  • Tests (#9): the first in-crate tests, for the verdict fold in Report::finish, on synthetic checks only. No checks is unknown; a failed check keeps; an unknown one without a failure is unknown; a failed check outranks an unknown one; stale outranks every check; only passing checks make a candidate. Every set of up to five checks is compared with that contract, order never changes the verdict, and adding a failed or unknown check never yields a candidate. CI runs cargo test --locked on macOS and both Linux runners.

  • README: FreeBSD has a prebuilt x86_64 binary on every release since 0.0.5; the README still said it had none.

  • FreeBSD aarch64 (#2): every release from 0.0.6 on gets residue-vX.Y.Z-aarch64-unknown-freebsd.tar.gz next to the x86_64 archive, attested and listed in SHA256SUMS. packages.yml builds each from the tag's source in a FreeBSD 14 virtual machine of its own architecture (aarch64 is emulated on an x86_64 runner), checks that the toolchain's host triple matches the archive name, and runs --version and .github/smoke.sh there before packing it. The two builds are independent: one failing keeps the other's archive, and neither holds back the Linux packages.

  • NetBSD, OpenBSD, DragonFly and illumos (#2): activity probes. Before, residue built there but every worktree's activity was unknown, so nothing was ever a candidate. NetBSD, OpenBSD and DragonFly: the base system's ps lists the processes, then one sysctl per process asks the kernel for its working directory (kern.proc_args.PID.cwd, kern.proc_cwd.PID, kern.proc.cwd.PID), and on NetBSD and DragonFly for its executable. Decision, not fstat: on NetBSD and OpenBSD it names an open file only by mount point and inode (fstat(1): "Normally the name cannot be determined"), so it cannot say whether a process is inside a worktree. Open files are not read on these three, so a worktree with no use found is unknown there (see the decision below). illumos: /proc/PID/path/, the working directory, every open descriptor and every mapped object (proc(5)); not /proc/PID/cwd, whose readlink is empty. On all four, a process whose working directory the kernel does not name does not count as inspected, a ps line residue cannot read makes activity unknown, and so does a probe that inspected no process at all. JSON activity.backend is sysctl on the three BSDs and procfs on illumos.

  • Decision (#2), a probe that cannot see open files never clears a worktree: residue must never remove work that is in use. The NetBSD, OpenBSD and DragonFly probes read each process's working directory (and executable on NetBSD and DragonFly), not its open files, so a process that holds a file open inside a worktree while it works elsewhere was not seen, and the worktree could be a removal candidate. Now a backend that reads less than working directories and open files (Seen::limits) answers in_use when it finds a process inside the worktree and unknown otherwise, never not_observed: activity unknown (partial_probe), with the detail sysctl reads working directories only, not open files, so it cannot show that no process is using it (NetBSD and DragonFly: working directories and executables only). The decision is in Snapshot::evidence, the one place every check, re-check and check after the move asks, not a per-platform branch. So on those three systems nothing is a removal candidate, and remove, remove --all and watch remove nothing there; remove a worktree with git worktree remove. macOS (lsof), Linux (/proc), FreeBSD (procstat -f) and illumos (/proc/PID/path, which names open files) are unchanged. JSON: the shape is the same; activity.reason gains the value partial_probe, and activity.state is never not_observed on those three. The README documents the activity object. CI there: each script skips every case that expects a removal or a prompt, by name with the reason (scripts/partial-probe.sh, gated on uname -s), and checks instead that the clean, idle worktree is kept as unknown for that reason in scan, explain, remove --yes, remove under a terminal and watch, with nothing moved aside or put in the trash. Assertions on the other platforms are unchanged; the in-crate tests pin the decision on synthetic snapshots.

  • CI (#2): NetBSD 10, OpenBSD 7.8 and DragonFly 6.4 virtual machines run the build, both smoke tests and the race, contract and recovery checks, and an OmniOS r151054 (illumos) virtual machine runs the build, the smoke test and the contract and recovery checks, each as root and as an ordinary user. illumos skips the watch smoke test, because without inotify or kqueue a running watch wakes on its five-minute timer, past the test's two-minute limit, and the race check, because illumos script cannot run a command. NetBSD and OpenBSD run the race check through .github/script-adapter/script, which turns its script -q FILE CMD... into their script -c form. .github/smoke.sh leaves its scratch directory before deleting it, which illumos rm requires.

  • Fix (#7): watch chose a worktree because its upstream branch was gone, but the removal child did not know why, so an upstream that came back between the scan and the removal did not keep the worktree. remove --only-if-upstream-gone now carries that reason: the child removes a worktree only when the local refs show its branch's upstream gone, checked at the plan, in the re-check right before the move, and after the move right before the deletion. An upstream that exists, no upstream, a detached HEAD or a failed lookup keeps the worktree, with the reason in JSON and the text output, and exit status 3. watch passes it for work-landed removals only. Manual remove and disk-pressure removals are unchanged. residue still never fetches, so a remote change that has not reached the local refs is not seen. --only-if-upstream-gone with --all, or outside remove, is a usage error.

  • The third read comes after the move aside and the re-measure, which can take seconds on a large worktree. If the upstream is back by then, the worktree goes back in place. Every way the requirement keeps a worktree, including a failed lookup, is skipped.

  • Work landed now needs a remote-tracking upstream. A local upstream (branch.NAME.remote set to .) whose branch was deleted made a worktree look landed; --only-if-upstream-gone keeps it, and watch no longer picks it. scan --json and explain --json gain upstream_ref, the upstream's full refname (refs/remotes/origin/fix), and watch reads it.

  • Fix: an upstream counted as gone whenever Git marked it [gone], which Git also does when the ref exists but does not resolve to a commit (it names a tree, or it is a symbolic ref to a missing ref). It now counts as gone only when its ref is absent; anything else is a lookup error. scan's upstream_gone follows, so watch no longer picks such a worktree, and --only-if-upstream-gone keeps it.