stack-test
Use when you need to run tests or formatters across commits in a stack. Use INSTEAD of manual git test run or looping git checkout + test. Prevents: testing every commit when only the tip has to pass, unbounded parallel jobs exhausting memory, cache misunderstandings.
- License 许可
- Unlicense
- 最近更新
- 2026-11-13
- Stars
- ★ 2
- Forks
- 0
01 前言 Front Matter
- name
- stack-test
- description
- Use when you need to run tests or formatters across commits in a stack. Use INSTEAD of manual git test run or looping git checkout + test. Prevents: testing every commit when only the tip has to pass, unbounded parallel jobs exhausting memory, cache misunderstandings.
- argument-hint
- <command> [--fix] [--stack|--tip] [--jobs N] [revset]
- compatibility
- Requires git-branchless
- disable-model-invocation
- false
02 资源文件 1
- 参考文档references/git-branchless.md37 B
03 SKILL.md 正文
Run a test command or formatter across commits in the current stack.
Pre-flight
-
Load references — read
references/git-branchless.md(relative to this skill's directory) before proceeding. The sections Choosing the test revset and Sizing--jobs— by memory, not by cores are the rules this skill applies; the rest of this file is how to apply them. -
Confirm repository initialization. A devenv project with
git.branchless.enablerunsgit branchless initon every shell entry. Anywhere else, check and stop if it is missing:git config --get branchless.core.mainBranch >/dev/null || echo "not initialized: ask the user to run git branchless init"Do not run
git branchless inityourself: it rewrites the repository's shared configuration and hooks for every worktree.
Arguments
- First argument: the command to run (optional — auto-detected if omitted)
--fix: use fix mode (apply changes per commit, e.g., formatters)--stack: test every commit — for a stack of independent PRs/MRs--tip: test only the stack tip — for one PR/MR with several commits--jobs N: parallelism override for run mode. Never0(see step 5). Fix mode always runs--jobs 1(see Fix Mode)- Remaining args: an explicit revset, which overrides
--stack/--tip
Steps
-
Parse
$ARGUMENTS. If no command is provided, detect from the project:package.json→npm testorpnpm testCargo.toml→cargo testMakefile→make testflake.nix→nix flake check- Otherwise, ask the user.
-
Determine mode. If
--fixis in arguments, use fix mode. Otherwise use run mode. -
Determine the target revset. This is a coverage decision — get it right before worrying about speed.
Select this worktree's own stack first, and use
$STACKwherever this skill says "the stack" — the table below, fix mode, and every revset you pass:main_branch="$(git config branchless.core.mainBranch || echo main)" remote="$(git config "branch.${main_branch}.remote" || echo origin)" base="$(git rev-parse --abbrev-ref "${main_branch}@{upstream}" 2>/dev/null \ || echo "${remote}/${main_branch}")" git rev-parse --verify -q "${base}^{commit}" >/dev/null || base="$main_branch" STACK="descendants(roots((stack() & ::HEAD) - ::$base)) - ::$base"Plain
stack()is not your stack whenever localmainis behind its upstream, the normal state when worktrees branch fromorigin/main: the upstream commits count as draft, sostack()holds them and every stack that another worktree branched from them.git test runthen tests other agents' work, andgit test fixrewrites it, including a forged copy of an upstream commit under your own stack.$STACKstops at$baseand at HEAD's own line, so neither can happen. The one exception is a worktree stacked on yours, which the fix-mode checks below catch; see Selecting your own stack inreferences/git-branchless.md. This does not fetch; the last fetched$baseis enough to tell your commits from upstream ones. With no upstream ref (no remote, or a remote with no main branch yet),$basefalls back to localmain.Precedence, highest first: an explicit revset argument, then
--stack/--tip, then the mode default. The mode default differs: run mode defaults to the tip (the table below), fix mode defaults to$STACK(see Fix Mode, step 6). So in--fixthe table below does not apply unless--tipor an explicit revset was given.Fix mode rewrites every commit it selects and moves the branches on them, so check before running:
: "${STACK:?run the selection block first}" stack_commits="$(git query -r "$STACK")" || exit 1 test -n "$stack_commits" || { echo "empty stack selection" >&2; exit 1; } outside="$(git query -r "(<revset>) - ($STACK)")" || exit 1 test -z "$outside" || { printf '%s\n' "$outside"; exit 1; } current_worktree="$(git rev-parse --show-toplevel)" || exit 1 other_heads="$(git worktree list --porcelain | awk -v current="$current_worktree" ' $1 == "worktree" { if (path != "" && path != current && head != "") print head path = substr($0, 10); head = "" } $1 == "HEAD" { head = $2 } END { if (path != "" && path != current && head != "") print head } ')" || exit 1 selected_commits="$(git query -r "<revset>")" || exit 1 test -n "$selected_commits" || { echo "empty fix selection" >&2; exit 1; } overlap="" for other_head in $other_heads; do for selected_commit in $selected_commits; do if git merge-base --is-ancestor "$selected_commit" "$other_head"; then overlap="${overlap}${overlap:+ }${other_head}" fi done done test -z "$overlap" || { printf '%s\n' "$overlap"; exit 1; }- The query prints commits: stop and show them. The revset reaches
another worktree's stack or upstream commits (
draft()andstack()both can). Run mode only reads, so an explicit revset may reach further there; say so in the report. - The worktree check prints a commit: another worktree, including a detached one, has its HEAD inside your stack. Stop and ask. Fixing would rewrite or abandon the commit under that worktree.
Every guard must print nothing and exit 0. Re-run the selection block and this guard in the same shell before fix mode.
With no revset and no flag, read the structure of the work — every PR/MR needs its own branch, so branch refs inside the stack count the PRs:
git query "$STACK" | wc -l # commits in the stack git query --branches "$STACK" # and their branchesBranches in stack Situation Revset $STACKis emptyNo commits of your own — on main or fresh @0 or 1 One PR/MR, several WIP commits heads($STACK)2+ A stack of independent PRs/MRs $STACKCheck the empty case first and mechanically — on
main,heads($STACK)resolves to zero commits and would test nothing at all.Use
heads($STACK)rather than@for the tip: it stays correct when the user has navigated back withgit prev, where@is a middle commit.When the count is 2+, name the branches you found in your report. The count cannot tell a real PR stack from one PR plus a stale branch left over from earlier work — both look like 2. Over-testing is the safe direction, so still default to
$STACK, but listing the names lets the user spot a stale branch immediately instead of wondering why seven commits are being tested.Why the tip is the default. In a single PR only the merged result ships: the tip is what reviewers read and what CI gates, and the commits underneath are checkpoints. Testing all seven commits of a seven-commit PR runs six evaluations that buy nothing. Small, frequent commits inside one PR are a deliberate, good habit — never discourage them, and never read them as N things to validate.
Say which revset you chose and why, before running. Widening and narrowing are both coverage changes, and a silent one is a bug.
- The query prints commits: stop and show them. The revset reaches
another worktree's stack or upstream commits (
-
Reject
nixper-commit testing. If the command is a Nix command (nix flake check,nix build, …) and the revset covers more than one commit, stop and tell the user the cost before running:Per-commit testing does not fit Nix at any
--jobsvalue. Each job is a multi-GB evaluator in its own worktree with no shared eval cache, so at--jobs 0this fans out until it exhausts RAM (7 commits measured ~24 GB, OOM-killing a 30 GB workstation), and at--jobs 1it is N sequential full evaluations.Then confirm the user really wants every commit evaluated. If not, use
heads($STACK). -
Size
--jobsby memory, not by cores. Each job runs in its own worktree with no shared cache, so peak usage isjobs x per-job footprint.Use an explicit
--jobs Non every invocation. Never pass--jobs 0: it means one job per physical CPU, and it overrides whatever bound the machine's config had set. Omitting the flag inheritsbranchless.test.jobs, which is only safe if you know what it says — this repo's own preset shipped0for a while, so do not rely on it.Derive the number with the snippet under Sizing
--jobsinreferences/git-branchless.md. It already clamps by CPUs and by commit count; the one value you must supply isfootprint_mb, taken from that section's table for the command you detected in step 1 — not the Nix default. Leaving it at Nix's ~3500 forcargo testornpm testwould serialize a run that could safely use every core.When the revset resolves to a single commit,
--jobs 1is the whole answer and no arithmetic is needed. Fix mode is always--jobs 1; skip this step there.
Run Mode (testing)
-
Run tests:
git test run -x '<command>' --jobs <N> '<revset>' -
Report results:
- State the revset and job count used, and why.
- If all pass: confirm with commit count.
- If some fail: list which commits failed with their hashes and messages.
- Suggest
git test run -x '<command>' --jobs 1 --search binaryto bisect if the failure pattern is unclear.
Fix Mode (formatting)
-
Apply formatter across the stack:
git test fix --strategy working-copy --jobs 1 --no-cache -x '<command>' '<revset>'Always pass
--strategy working-copy --jobs 1 --no-cache. With theworktreestrategy (this repo's git preset),git test fixsilently fixes nothing: it reportsNo commits to fix. A cached result from any earlier worktree-strategy run has the same effect, and a--jobsabove 1 selectsworktreetoo. Seegit test fixinreferences/git-branchless.md.working-copychecks each commit out in this worktree, so it needs a clean working tree. If tracked files have changes, it exits 1 and changes nothing; ask the user to commit them first.Fix mode replaces tree OIDs directly and never produces merge conflicts.
Formatters are the one case where the tip is not enough: an unformatted intermediate commit stays unformatted forever. Default to
$STACKhere even for a single PR. -
Verify with
git slto show any commits that were modified. -
Optionally re-run tests to confirm the formatting didn't break anything:
git test run -x '<test-command>' --jobs <N> '<revset>'
Examples
/stack-test "npm test" # tip only, auto-sized jobs
/stack-test "nix flake check" # tip only — see step 4
/stack-test "cargo test" --stack # every commit: a PR stack
/stack-test "cargo fmt --all" --fix # every commit (formatter)
/stack-test "prettier --write ." --fix '@' # fix one commit
/stack-test "make lint" '@' # explicit revset wins
/stack-test "npm test" --jobs 4 'draft()' # run mode only: every worktree's drafts
Notes
- Test results are cached by command + tree ID, under
.git/branchless/test/<tree-oid>/. Use--no-cacheto bypass the cache for one run without clearing stored results (useful after environment changes). - Cache cleanup affects the common Git directory shared by every worktree. Stop and ask before clearing cached test results.
strategy = worktree(set by this repo's git preset; upstream defaults toworking-copy) is what makes parallelism possible at all. In run mode it also means--jobs 1still runs in an isolated worktree, so a dirty working copy is fine. Fix mode overrides it with--strategy working-copy(step 6).--jobs Non the command line overridesbranchless.test.jobsin both directions: it lowers a configured0and raises a configured1.- The
BRANCHLESS_TEST_COMMITenv var is available inside the test command.
04 同仓库更多 Skills
stack-summary
Use when you need to audit, analyze, or review stack quality before restructuring. Produces a structured summary with per-commit classification, philosophy audit, and violation flags. Output feeds directly into /stack-plan restructure mode. Use INSTEAD of manual git log inspection.
stack-submit
Use when you need to push, submit, or create pull requests for a commit stack. Rebases only the current stack onto upstream main, validates, and pushes INSTEAD of manual git sync + git submit + PR/MR creation. Handles branch creation, stacked PR/MR creation with correct base branches.
stack-split
Use when you need to break up, split, or decompose a large commit into smaller atomic commits. Use INSTEAD of manual git rebase -i + edit or git reset HEAD^. Prevents: non-working intermediate commits, wrong split ordering, missed downstream restack.
stack-plan
Use when you need to plan commits, restructure a stack, or commit uncommitted work as organized atomic commits. Builds a stack from a description, uncommitted work, or existing commits INSTEAD of dumping everything into one monolithic commit with git add -A && git commit, or manual git rebase -i, git reset --soft, or ad-hoc git move sequences. Prevents: wrong commit ordering, forward references, git move -F panics, untested intermediates.
stack-fix
Use when you need to fix, correct, or update content in an earlier commit — line-level edits, content moves, or structural changes. Absorbs staged changes into correct stack commits INSTEAD of running git absorb or git commit --fixup via Bash, or hand-running git prev + edit + git amend + git restack --merge. Prevents: missed dry-run preview, leftover staged changes going unnoticed, forgetting to restack. Falls back to guided manual amend when absorb cannot route hunks.
peer-communication
How to write replies a person reads: answer first, plain words, easy to scan. Load before your first reply in an interactive session.