From 0031f8209e9b37056afd525410affefeadda65c9 Mon Sep 17 00:00:00 2001 From: CI Date: Wed, 29 Jul 2026 18:20:25 -0400 Subject: [PATCH 1/2] Install the agents template on curl and warn when it is missing. Curl installs previously left TREES_AGENTS_TEMPLATE pointing at a nonexistent file, so init silently skipped seeding AGENTS.md. Co-authored-by: Cursor --- AGENTS.md | 16 ++++++++++------ README.md | 17 +++++++---------- git-trees | 5 ++++- tests/smoke.sh | 40 +++++++++++++++++++++++++++++++++++++--- 4 files changed, 58 insertions(+), 20 deletions(-) diff --git a/AGENTS.md b/AGENTS.md index 0f620d7..b43004f 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -92,15 +92,16 @@ What the suite covers: - **help/dispatch** — `help`, `--help`, and unknown command - **outside a repo** — `list` and `root` both exit nonzero - **init** — happy path over `file://`; the gitdir pointer, bare store, seeded - `AGENTS.md`, and `origin/*` refs it must produce; the container root having no - work tree; refusal on an existing directory; **rollback when the post-clone - fetch fails**, and that a retry then works; `--host`/`--dir` with a missing - value exiting promptly rather than hanging + `AGENTS.md` content matching the template, and `origin/*` refs it must produce; + the container root having no work tree; refusal on an existing directory; + **rollback when the post-clone fetch fails**, and that a retry then works; + `--host`/`--dir` with a missing value exiting promptly rather than hanging; + missing template warns and writes no `AGENTS.md` - **root** — printing the container, adopting a bare container by writing the `.git` pointer under the store's own name, rejecting a plain directory - **root --agents** — seeds when absent; never overwrites a regular file; treats - a **broken symlink** as occupied; no-ops without a template; keeps stdout to - the path alone + a **broken symlink** as occupied; warns and no-ops without a template; keeps + stdout to the path alone - **add** — `.`, `..` and `'has space'` reported as bad branch names rather than directory collisions, and rejected before `Preparing worktree`; upstream exactly `origin/feature-x` for an existing remote branch and exactly @@ -114,6 +115,9 @@ What the suite covers: - **list** — text output, branches with no worktree shown as `(none)`, `--json` parsing, and a worktree whose path contains `\` and `"` round-tripping through `json.load` +- **install.sh** — places the binary; seeds `~/.config/git-trees/AGENTS.md` from + the template under a redirected `HOME`; does not overwrite an existing config + file Two assertion shapes are easy to get wrong: diff --git a/README.md b/README.md index cd05377..9500beb 100644 --- a/README.md +++ b/README.md @@ -117,22 +117,19 @@ cd git-trees && ./install.sh # → ~/.local/bin ./install.sh /usr/local/bin # or anywhere else ``` -**Convenience — one-line curl.** Fetches only the script: +**Convenience — curl.** Fetches the script and the agents template (skips the +template if `~/.config/git-trees/AGENTS.md` already exists): ```bash -mkdir -p ~/.local/bin +mkdir -p ~/.local/bin ~/.config/git-trees curl -o ~/.local/bin/git-trees \ https://raw.githubusercontent.com/brightdigit/git-trees/v1.0.0/git-trees chmod +x ~/.local/bin/git-trees +[ -f ~/.config/git-trees/AGENTS.md ] || curl -o ~/.config/git-trees/AGENTS.md \ + https://raw.githubusercontent.com/brightdigit/git-trees/v1.0.0/AGENTS.md.template ``` -One thing to know about this path: it does **not** create -`~/.config/git-trees/AGENTS.md`, so the documented default for -`TREES_AGENTS_TEMPLATE` points at a file you don't have — seeding is simply -skipped, which is harmless, but `init` will not write an `AGENTS.md`. Use the -installer if you want the template too. - -The URL above is pinned to the `v1.0.0` tag, so it gives the same script every +The URLs above are pinned to the `v1.0.0` tag, so they give the same files every time. Swapping `v1.0.0` for `main` tracks the development branch instead — a moving target, and not what you want for an install you intend to keep. @@ -156,7 +153,7 @@ export TREES_ORG=your-org That's usually all you need — `TREES_HOST` defaults to `github.com` and `TREES_AGENTS_TEMPLATE` defaults to `~/.config/git-trees/AGENTS.md`, which is -where `install.sh` puts the template. +where `install.sh` and the curl install put the template. With `TREES_ORG` set, `git trees init my-repo` expands to `your-org/my-repo`. Without it, bare repo names are rejected and you must pass `org/repo`. diff --git a/git-trees b/git-trees index 120f355..386efbd 100755 --- a/git-trees +++ b/git-trees @@ -131,7 +131,10 @@ _ensure_git_pointer() { # 1 when neither test holds — an inverted error path waiting to happen. Both # guards are written out. _seed_agents() { - [ -f "$TREES_AGENTS_TEMPLATE" ] || return 0 + if [ ! -f "$TREES_AGENTS_TEMPLATE" ]; then + echo "git trees: no agents template at $TREES_AGENTS_TEMPLATE — skipping AGENTS.md seed (run ./install.sh or set TREES_AGENTS_TEMPLATE)" >&2 + return 0 + fi # Any existing path (including a broken symlink) is occupied — never overwrite. # -e is false for a broken symlink; -L is true. Both tests are needed. if [ -e "$1/AGENTS.md" ] || [ -L "$1/AGENTS.md" ]; then diff --git a/tests/smoke.sh b/tests/smoke.sh index a84de23..27a3d37 100755 --- a/tests/smoke.sh +++ b/tests/smoke.sh @@ -158,6 +158,8 @@ assert_contains "init reports the default branch" "$out" "initialized init-ok (d assert_ok "init wrote the gitdir pointer" test -f "$TMP/init-ok/.git" assert_ok "init created the bare store" test -d "$TMP/init-ok/trees-bare.git" assert_ok "init seeded AGENTS.md" test -f "$TMP/init-ok/AGENTS.md" +assert_eq "init seeded content from the template" \ + "$(cat "$TMP/init-ok/AGENTS.md")" "seeded-agents-template" # The refspec is what gives a bare clone its remote-tracking refs. assert_ok "init set up origin/* refs" \ git -C "$TMP/init-ok" show-ref --verify --quiet refs/remotes/origin/main @@ -193,6 +195,15 @@ assert_ok "init --dir with a value" bash "$T" init "file://$ORIGIN" --dir init-f assert_fail "init with no argument" bash "$T" init assert_fail "init unknown option" bash "$T" init "file://$ORIGIN" --nope +# Missing template: init still succeeds, warns, and writes no AGENTS.md. +out=$(env TREES_AGENTS_TEMPLATE="$TMP/no-such-template" \ + bash "$T" init "file://$ORIGIN" --dir init-notemplate 2>&1) +assert_contains "init still reports success without a template" "$out" \ + "initialized init-notemplate" +assert_contains "init warns when the template is missing" "$out" "no agents template" +assert_ok "init without a template writes no AGENTS.md" \ + test '!' -e "$TMP/init-notemplate/AGENTS.md" + # --- root -------------------------------------------------------------------- section "root" @@ -233,11 +244,13 @@ assert_ok "root --agents succeeds with a broken symlink present" bash "$T" root assert_ok "a broken symlink counts as occupied" test -L "$S3/AGENTS.md" assert_eq "the symlink was not replaced" "$(readlink "$S3/AGENTS.md")" "$TMP/definitely-absent" -# No template configured → no-op, still succeeds. +# No template configured → no-op with a warning, still succeeds. S4=$(new_container seed-notemplate) rm -f "$S4/AGENTS.md" -assert_ok "root --agents is a no-op without a template" \ - env TREES_AGENTS_TEMPLATE="$TMP/no-such-template" bash "$T" root "$S4" --agents +out=$(env TREES_AGENTS_TEMPLATE="$TMP/no-such-template" bash "$T" root "$S4" --agents 2>&1) +rc=$? +assert_eq "root --agents exits 0 without a template" "$rc" "0" +assert_contains "root --agents warns when the template is missing" "$out" "no agents template" assert_ok "nothing was written without a template" test '!' -e "$S4/AGENTS.md" # stdout stays clean for $(git trees root) even while seeding. @@ -440,6 +453,27 @@ else fail "could not create a worktree with a backslash in its path" fi +# --- install.sh -------------------------------------------------------------- + +section "install.sh" +REPO=$(dirname "$T") +IHOME="$TMP/install-home" +IDEST="$TMP/install-bin" +mkdir -p "$IHOME" "$IDEST" +out=$(HOME="$IHOME" bash "$REPO/install.sh" "$IDEST" 2>&1) +rc=$? +assert_eq "install.sh exits 0" "$rc" "0" +assert_ok "install.sh placed the binary" test -x "$IDEST/git-trees" +assert_ok "install.sh seeded the agents template" \ + test -f "$IHOME/.config/git-trees/AGENTS.md" +assert_eq "install.sh template matches AGENTS.md.template" \ + "$(cat "$IHOME/.config/git-trees/AGENTS.md")" \ + "$(cat "$REPO/AGENTS.md.template")" +echo CUSTOM > "$IHOME/.config/git-trees/AGENTS.md" +HOME="$IHOME" bash "$REPO/install.sh" "$IDEST" >/dev/null 2>&1 +assert_eq "install.sh does not overwrite an existing template" \ + "$(cat "$IHOME/.config/git-trees/AGENTS.md")" "CUSTOM" + # --- summary ----------------------------------------------------------------- cd "$TMP" || exit 1 From 139f7eece6a8720d7ad918b48e2622aecf4c5ab0 Mon Sep 17 00:00:00 2001 From: CI Date: Wed, 29 Jul 2026 18:27:08 -0400 Subject: [PATCH 2/2] Address CodeRabbit review on curl install and smoke assertions. Fail-closed curl install (fsSL + temp then mv, occupied-path guards), and assert exit status with stdout/stderr captured separately in the new tests. Co-authored-by: Cursor --- README.md | 14 +++++++++----- tests/smoke.sh | 22 ++++++++++++++++------ 2 files changed, 25 insertions(+), 11 deletions(-) diff --git a/README.md b/README.md index 9500beb..8cda3ae 100644 --- a/README.md +++ b/README.md @@ -118,15 +118,19 @@ cd git-trees && ./install.sh # → ~/.local/bin ``` **Convenience — curl.** Fetches the script and the agents template (skips the -template if `~/.config/git-trees/AGENTS.md` already exists): +template if that path is already occupied, including a broken symlink): ```bash mkdir -p ~/.local/bin ~/.config/git-trees -curl -o ~/.local/bin/git-trees \ - https://raw.githubusercontent.com/brightdigit/git-trees/v1.0.0/git-trees +tmp=$(mktemp) && curl -fsSL -o "$tmp" \ + https://raw.githubusercontent.com/brightdigit/git-trees/v1.0.0/git-trees \ + && mv "$tmp" ~/.local/bin/git-trees chmod +x ~/.local/bin/git-trees -[ -f ~/.config/git-trees/AGENTS.md ] || curl -o ~/.config/git-trees/AGENTS.md \ - https://raw.githubusercontent.com/brightdigit/git-trees/v1.0.0/AGENTS.md.template +if [ ! -e ~/.config/git-trees/AGENTS.md ] && [ ! -L ~/.config/git-trees/AGENTS.md ]; then + tmp=$(mktemp) && curl -fsSL -o "$tmp" \ + https://raw.githubusercontent.com/brightdigit/git-trees/v1.0.0/AGENTS.md.template \ + && mv "$tmp" ~/.config/git-trees/AGENTS.md +fi ``` The URLs above are pinned to the `v1.0.0` tag, so they give the same files every diff --git a/tests/smoke.sh b/tests/smoke.sh index 27a3d37..e150c53 100755 --- a/tests/smoke.sh +++ b/tests/smoke.sh @@ -195,12 +195,16 @@ assert_ok "init --dir with a value" bash "$T" init "file://$ORIGIN" --dir init-f assert_fail "init with no argument" bash "$T" init assert_fail "init unknown option" bash "$T" init "file://$ORIGIN" --nope -# Missing template: init still succeeds, warns, and writes no AGENTS.md. +# Missing template: init still succeeds, warns on stderr, and writes no AGENTS.md. +err=$(mktemp) out=$(env TREES_AGENTS_TEMPLATE="$TMP/no-such-template" \ - bash "$T" init "file://$ORIGIN" --dir init-notemplate 2>&1) + bash "$T" init "file://$ORIGIN" --dir init-notemplate 2>"$err") +rc=$? +assert_eq "init exits 0 without a template" "$rc" "0" assert_contains "init still reports success without a template" "$out" \ "initialized init-notemplate" -assert_contains "init warns when the template is missing" "$out" "no agents template" +assert_contains "init warns on stderr when the template is missing" "$(cat "$err")" \ + "no agents template" assert_ok "init without a template writes no AGENTS.md" \ test '!' -e "$TMP/init-notemplate/AGENTS.md" @@ -244,13 +248,17 @@ assert_ok "root --agents succeeds with a broken symlink present" bash "$T" root assert_ok "a broken symlink counts as occupied" test -L "$S3/AGENTS.md" assert_eq "the symlink was not replaced" "$(readlink "$S3/AGENTS.md")" "$TMP/definitely-absent" -# No template configured → no-op with a warning, still succeeds. +# No template configured → no-op with a stderr warning, still succeeds. S4=$(new_container seed-notemplate) rm -f "$S4/AGENTS.md" -out=$(env TREES_AGENTS_TEMPLATE="$TMP/no-such-template" bash "$T" root "$S4" --agents 2>&1) +err=$(mktemp) +out=$(env TREES_AGENTS_TEMPLATE="$TMP/no-such-template" \ + bash "$T" root "$S4" --agents 2>"$err") rc=$? assert_eq "root --agents exits 0 without a template" "$rc" "0" -assert_contains "root --agents warns when the template is missing" "$out" "no agents template" +assert_eq "root --agents stdout is only the path without a template" "$out" "$S4" +assert_contains "root --agents warns on stderr when the template is missing" \ + "$(cat "$err")" "no agents template" assert_ok "nothing was written without a template" test '!' -e "$S4/AGENTS.md" # stdout stays clean for $(git trees root) even while seeding. @@ -471,6 +479,8 @@ assert_eq "install.sh template matches AGENTS.md.template" \ "$(cat "$REPO/AGENTS.md.template")" echo CUSTOM > "$IHOME/.config/git-trees/AGENTS.md" HOME="$IHOME" bash "$REPO/install.sh" "$IDEST" >/dev/null 2>&1 +rc=$? +assert_eq "install.sh rerun exits 0" "$rc" "0" assert_eq "install.sh does not overwrite an existing template" \ "$(cat "$IHOME/.config/git-trees/AGENTS.md")" "CUSTOM"