From 8bd51d410c6a8292716b10580ed4d1b1b893067b Mon Sep 17 00:00:00 2001 From: Claude Date: Sun, 27 Sep 2026 21:16:57 +0000 Subject: [PATCH 1/2] Worktrees by default: code repos live only under worktrees/ setup.sh fetches each repo into a bare store at worktrees//.bare and checks out the default branch as worktrees///; no more repos/ clone. new-worktree.sh makes worktrees// from that store (existing origin branches now track origin). Template, Argos defaults, argos-dev copies, README, design, goals, and e2e tests updated. Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01818NEiW3BJCTrVCTDgaYDf --- README.md | 6 ++-- docs/design.md | 10 +++--- docs/goals.md | 4 +-- .../.claude/skills/new-worktree/SKILL.md | 6 ++-- .../argos/defaults/CLAUDE.md | 6 ++-- .../.claude/skills/implement/SKILL.md | 2 +- .../.claude/skills/new-worktree/SKILL.md | 6 ++-- .../argos-dev/.delphi/new-worktree.sh | 15 ++++---- .../workspaces/argos-dev/.delphi/setup.sh | 30 ++++++++++------ .../argos/workspaces/argos-dev/CLAUDE.md | 10 +++--- .../.claude/skills/new-worktree/SKILL.md | 6 ++-- templates/workspace/.delphi/new-worktree.sh | 15 ++++---- templates/workspace/.delphi/setup.sh | 30 ++++++++++------ templates/workspace/CLAUDE.md | 2 +- tests/e2e.sh | 35 ++++++++++++------- 15 files changed, 108 insertions(+), 75 deletions(-) diff --git a/README.md b/README.md index b00d4d8..1574ea8 100644 --- a/README.md +++ b/README.md @@ -40,14 +40,14 @@ histories stay joined and edits on either side meet in normal three-way merges. ```sh git clone -b ws/argos-dev https://github.com/Northeastern-Electric-Racing/Delphi.git argos-dev -cd argos-dev && .delphi/setup.sh # clones workspace.yml's repos into repos/ (git-ignored) +cd argos-dev && .delphi/setup.sh # checks out workspace.yml's repos under worktrees/ (git-ignored) git switch -c my-change # edit, commit, push, open a PR into ws/argos-dev git fetch origin && git merge origin/ws/argos-dev # update your branch any time ``` After your PR merges into `ws/`, CI proposes it: a PR `ws/ → main` from `propose/`. Merge that with a merge commit or squash, never rebase. Never push to `ws/*` or -`main` directly (protect them). Code changes go in `repos/`, through that repo's own PRs. +`main` directly (protect them). Code changes go in a worktree, `worktrees//` (`.delphi/new-worktree.sh`), through that repo's own PRs. **Conflicts.** If `main` and `ws/` changed the same lines, CI lists the files and skips that workspace. Fix it in a PR into `ws/`: on a branch cut from `ws/`, run @@ -57,7 +57,7 @@ workspace. Fix it in a PR into `ws/`: on a branch cut from `ws/`, ru ```yaml harness: claude-code -repos: # cloned into repos/ by .delphi/setup.sh +repos: # checked out under worktrees// by .delphi/setup.sh argos: https://github.com/Northeastern-Electric-Racing/Argos.git ``` diff --git a/docs/design.md b/docs/design.md index c8512c9..9064d23 100644 --- a/docs/design.md +++ b/docs/design.md @@ -94,12 +94,14 @@ Nobody pushes to `ws/*` or `main` directly (branch protection; CI's token is the ## 5. `.delphi/setup.sh` (in every workspace) -Reads `repos:` from `workspace.yml`, clones each into `repos/` unless present, and adds -`/repos/` to the clone's `.git/info/exclude` once. Nothing else. Must run on macOS `/bin/bash` 3.2 +Reads `repos:` from `workspace.yml` and, for each not yet present, fetches it into a bare store at +`worktrees//.bare` and checks out its default branch as a worktree at +`worktrees///`. Adds `/worktrees/` to the clone's `.git/info/exclude` once. +Nothing else: code lives only in worktrees, never in a plain clone. Must run on macOS `/bin/bash` 3.2 and Git Bash: no bash-4 features, POSIX awk only. -`.delphi/new-worktree.sh []` creates or reuses `repos/worktrees//`: -checks out an existing branch, else starts one from `` (default `origin/HEAD`). Same shell rules. +`.delphi/new-worktree.sh []` creates or reuses `worktrees//` +from that store: checks out an existing branch, else starts one from `` (default `origin/HEAD`). Same shell rules. The template's `new-worktree` skill makes worktrees the default way to start a branch. `.delphi/link.sh |main` checks out `origin/ws/` (or `origin/main`) as a diff --git a/docs/goals.md b/docs/goals.md index bfce84a..8878816 100644 --- a/docs/goals.md +++ b/docs/goals.md @@ -12,7 +12,7 @@ anyone, person or agent, work on a workspace with plain git and send improvement - **G1. Workspaces live in Delphi.** Each workspace is a folder on `main` with every file at its normal harness location. - **G2. The workspace is the repo root.** Checking out `ws/` gives exactly that workspace at - the root. `.delphi/setup.sh` clones the code repos it lists. + the root. `.delphi/setup.sh` checks out the code repos it lists as worktrees. - **G3. Both directions, automatically.** Refresh: changes on `main` reach `ws/`. Propose: merged workspace changes reach `main` as a PR. - **G4. Conflicts are reported, never guessed.** A conflicting workspace is skipped with its files @@ -25,6 +25,6 @@ anyone, person or agent, work on a workspace with plain git and send improvement - **I1.** Changes reach `main` and `ws/*` only through PRs (CI's refreshes excepted). - **I2.** `ws/` history is joined to `main`; no unrelated histories, no force-pushes to `ws/*`. -- **I3.** Code repos in `repos/` never appear as workspace changes. +- **I3.** Code repos in `worktrees/` never appear as workspace changes. - **I4.** `setup.sh` works on macOS bash 3.2 and Git Bash. - **I5.** Everything is testable end to end without GitHub (`tests/e2e.sh`). diff --git a/software/application-software/argos/defaults/.claude/skills/new-worktree/SKILL.md b/software/application-software/argos/defaults/.claude/skills/new-worktree/SKILL.md index 49d2459..3abacbe 100644 --- a/software/application-software/argos/defaults/.claude/skills/new-worktree/SKILL.md +++ b/software/application-software/argos/defaults/.claude/skills/new-worktree/SKILL.md @@ -1,9 +1,9 @@ --- name: new-worktree -description: Create or reuse a worktree for a branch of a repo in repos/ (repos/worktrees//). Worktrees are the default way to make or check out any new branch; use this before starting a ticket, reviewing or fixing a PR branch, or any other branch work, unless the user says not to use worktrees. +description: Create or reuse a worktree for a branch of a code repo (worktrees//). Worktrees are the default way to make or check out any new branch; use this before starting a ticket, reviewing or fixing a PR branch, or any other branch work, unless the user says not to use worktrees. --- -Make every new branch as a worktree, never by branching in `repos//`, unless the user explicitly says not to use worktrees. `repos//` stays a clean checkout of the default branch. +Every checkout of a code repo is a worktree under `worktrees//`. `.delphi/setup.sh` makes the default branch's (`worktrees///`); keep it a clean reference: fetch and fast-forward only. Make every other branch with this script, never by switching branches in an existing worktree, unless the user explicitly says not to use worktrees. Run from the workspace root: @@ -13,6 +13,6 @@ bash .delphi/new-worktree.sh [] An existing branch (local or on origin, e.g. a PR's head) is checked out; anything else is created from `` (default: the repo's default branch). It's safe to re-run. It prints the worktree path: `cd` there and do all work in it. -After the branch merges, remove it with `git -C repos/ worktree remove ../worktrees//`. +After the branch merges, remove it with `git -C worktrees//.bare worktree remove ../`. Argos: always pass `origin/develop` as ``. New ticket branches follow `{issue-number}-{kebab-case-title}`. Run `npm ci` in the worktree's `angular-client/` before building, testing, or running the client. diff --git a/software/application-software/argos/defaults/CLAUDE.md b/software/application-software/argos/defaults/CLAUDE.md index 8d987d8..6a3a6f7 100644 --- a/software/application-software/argos/defaults/CLAUDE.md +++ b/software/application-software/argos/defaults/CLAUDE.md @@ -15,12 +15,12 @@ Argos is a real-time telemetry platform for Northeastern Electric Racing (NER). Angular 19 frontend (`angular-client/`) and Rust backend (`scylla-server/`), with schema tooling in `charybdis/` and MQTT broker config in `siren-base/`. -The Argos repo is checked out at `repos/argos/`. Paths below are relative to a checkout of it. The ticket number is the branch's leading number (`533-csv-upload` → `#533`). +The Argos repo's `develop` is checked out at `worktrees/argos/develop/`. Paths below are relative to any Argos worktree. The ticket number is the branch's leading number (`533-csv-upload` → `#533`). ## Worktrees -- `repos/argos/` is a clean reference to `develop`. Never edit, branch, commit, or run dev servers there. Only fetch, fast-forward `develop`, and manage worktrees from it. -- Every ticket gets its own worktree at `repos/worktrees/argos//`, and every workflow (implement, test, run, commit, PR) runs there. Create or reuse one with the `new-worktree` skill, based on `origin/develop`: `bash .delphi/new-worktree.sh argos origin/develop`. +- `worktrees/argos/develop/` is a clean reference to `develop`. Never edit, branch, commit, or run dev servers there; only fetch and fast-forward it. +- Every ticket gets its own worktree at `worktrees/argos//`, and every workflow (implement, test, run, commit, PR) runs there. Create or reuse one with the `new-worktree` skill, based on `origin/develop`: `bash .delphi/new-worktree.sh argos origin/develop`. - A new worktree has no `node_modules`: run `npm ci` in its `angular-client/` before testing or running the client. ## Local Development diff --git a/software/application-software/argos/workspaces/argos-dev/.claude/skills/implement/SKILL.md b/software/application-software/argos/workspaces/argos-dev/.claude/skills/implement/SKILL.md index d456f78..ca033f3 100644 --- a/software/application-software/argos/workspaces/argos-dev/.claude/skills/implement/SKILL.md +++ b/software/application-software/argos/workspaces/argos-dev/.claude/skills/implement/SKILL.md @@ -4,7 +4,7 @@ description: Implement a piece of work based on a spec or set of tickets. disable-model-invocation: true --- -Implement the ticket(s) the user names, one ticket per fresh context, each in its own worktree at `repos/worktrees/argos//` (never in `repos/argos/`). Create the worktree first if it doesn't exist. +Implement the ticket(s) the user names, one ticket per fresh context, each in its own worktree at `worktrees/argos//` (never in `worktrees/argos/develop/`). Create the worktree first if it doesn't exist. 1. Work test-first at the spec's agreed seams, one behavior at a time: write a failing test, then the minimum code to pass it, and refactor only while green. Test behavior through public interfaces, and mock only at system boundaries. 2. Check as you go: diff --git a/software/application-software/argos/workspaces/argos-dev/.claude/skills/new-worktree/SKILL.md b/software/application-software/argos/workspaces/argos-dev/.claude/skills/new-worktree/SKILL.md index 49d2459..3abacbe 100644 --- a/software/application-software/argos/workspaces/argos-dev/.claude/skills/new-worktree/SKILL.md +++ b/software/application-software/argos/workspaces/argos-dev/.claude/skills/new-worktree/SKILL.md @@ -1,9 +1,9 @@ --- name: new-worktree -description: Create or reuse a worktree for a branch of a repo in repos/ (repos/worktrees//). Worktrees are the default way to make or check out any new branch; use this before starting a ticket, reviewing or fixing a PR branch, or any other branch work, unless the user says not to use worktrees. +description: Create or reuse a worktree for a branch of a code repo (worktrees//). Worktrees are the default way to make or check out any new branch; use this before starting a ticket, reviewing or fixing a PR branch, or any other branch work, unless the user says not to use worktrees. --- -Make every new branch as a worktree, never by branching in `repos//`, unless the user explicitly says not to use worktrees. `repos//` stays a clean checkout of the default branch. +Every checkout of a code repo is a worktree under `worktrees//`. `.delphi/setup.sh` makes the default branch's (`worktrees///`); keep it a clean reference: fetch and fast-forward only. Make every other branch with this script, never by switching branches in an existing worktree, unless the user explicitly says not to use worktrees. Run from the workspace root: @@ -13,6 +13,6 @@ bash .delphi/new-worktree.sh [] An existing branch (local or on origin, e.g. a PR's head) is checked out; anything else is created from `` (default: the repo's default branch). It's safe to re-run. It prints the worktree path: `cd` there and do all work in it. -After the branch merges, remove it with `git -C repos/ worktree remove ../worktrees//`. +After the branch merges, remove it with `git -C worktrees//.bare worktree remove ../`. Argos: always pass `origin/develop` as ``. New ticket branches follow `{issue-number}-{kebab-case-title}`. Run `npm ci` in the worktree's `angular-client/` before building, testing, or running the client. diff --git a/software/application-software/argos/workspaces/argos-dev/.delphi/new-worktree.sh b/software/application-software/argos/workspaces/argos-dev/.delphi/new-worktree.sh index b4f856b..7327d9b 100755 --- a/software/application-software/argos/workspaces/argos-dev/.delphi/new-worktree.sh +++ b/software/application-software/argos/workspaces/argos-dev/.delphi/new-worktree.sh @@ -1,16 +1,19 @@ #!/usr/bin/env bash -# .delphi/new-worktree.sh []: create or reuse repos/worktrees// -# for repos/ and print its path. An existing branch (local or on origin) is checked out; a new -# one starts from (default: origin's default branch). Runs on bash 3.2 and Git Bash. +# .delphi/new-worktree.sh []: create or reuse worktrees// from +# the store .delphi/setup.sh made at worktrees//.bare, and print its path. An existing branch +# (local or on origin) is checked out; a new one starts from (default: origin's default +# branch). Runs on bash 3.2 and Git Bash. set -euo pipefail root=$(cd "$(dirname "$0")/.." && pwd) repo=${1:?usage: new-worktree.sh []} branch=${2:?usage: new-worktree.sh []} -base=${3:-origin/HEAD} dest="$root/repos/worktrees/$repo/$branch" -g() { git -C "$root/repos/$repo" "$@"; } +base=${3:-origin/HEAD} dest="$root/worktrees/$repo/$branch" +g() { git -C "$root/worktrees/$repo/.bare" "$@"; } g fetch -q origin if [ -d "$dest" ]; then : -elif g rev-parse -q --verify "refs/heads/$branch" >/dev/null || g rev-parse -q --verify "refs/remotes/origin/$branch" >/dev/null; then +elif g rev-parse -q --verify "refs/heads/$branch" >/dev/null; then g worktree add -q "$dest" "$branch" +elif g rev-parse -q --verify "refs/remotes/origin/$branch" >/dev/null; then + g worktree add -q --track -b "$branch" "$dest" "origin/$branch" else g worktree add -q --no-track -b "$branch" "$dest" "$base" fi diff --git a/software/application-software/argos/workspaces/argos-dev/.delphi/setup.sh b/software/application-software/argos/workspaces/argos-dev/.delphi/setup.sh index 22ceb98..5516f74 100755 --- a/software/application-software/argos/workspaces/argos-dev/.delphi/setup.sh +++ b/software/application-software/argos/workspaces/argos-dev/.delphi/setup.sh @@ -1,8 +1,9 @@ #!/usr/bin/env bash -# .delphi/setup.sh: run in a checkout of ws/ (the workspace root). Clones each repo listed -# under `repos:` in workspace.yml into repos/ (skipping ones already there) and adds /repos/ -# to this clone's .git/info/exclude. Safe to re-run. Keep it bash 3.2 (macOS) and Git Bash safe: -# no bash-4 features, POSIX awk only. +# .delphi/setup.sh: run in a checkout of ws/ (the workspace root). For each repo listed under +# `repos:` in workspace.yml, fetches it into a bare store at worktrees//.bare and checks out +# its default branch as a worktree at worktrees// (skipping repos already +# there), then adds /worktrees/ to this clone's .git/info/exclude. Safe to re-run. Keep it bash 3.2 +# (macOS) and Git Bash safe: no bash-4 features, POSIX awk only. set -euo pipefail cd "$(dirname "$0")/.." @@ -16,15 +17,22 @@ list=$(awk ' while read -r name url; do [ -n "$name" ] || continue case "$name" in . | .. | *[!A-Za-z0-9._-]*) echo "setup: skipping unsafe repo name '$name'" >&2 && continue ;; esac - if [ -e "repos/$name" ]; then - echo "repos/$name: already present" - else - git clone "$url" "repos/$name" /dev/null /dev/null || echo /repos/ >>"$exclude" +grep -qxF /worktrees/ "$exclude" 2>/dev/null || echo /worktrees/ >>"$exclude" diff --git a/software/application-software/argos/workspaces/argos-dev/CLAUDE.md b/software/application-software/argos/workspaces/argos-dev/CLAUDE.md index e3fabc6..cdf2397 100644 --- a/software/application-software/argos/workspaces/argos-dev/CLAUDE.md +++ b/software/application-software/argos/workspaces/argos-dev/CLAUDE.md @@ -1,6 +1,6 @@ # Delphi workspace -You're in Delphi workspace `argos-dev` (branch `ws/argos-dev`); code repos live in `repos/`. Make new branches as worktrees (`new-worktree` skill) unless the user says not to. Work here unless the task clearly belongs to another project. +You're in Delphi workspace `argos-dev` (branch `ws/argos-dev`); code repos are checked out as worktrees under `worktrees//` (`.delphi/setup.sh` makes the default branch's). Make new branches as worktrees (`new-worktree` skill) unless the user says not to. Work here unless the task clearly belongs to another project. # NER Software conventions @@ -19,12 +19,12 @@ You're in Delphi workspace `argos-dev` (branch `ws/argos-dev`); code repos live Argos is a real-time telemetry platform for Northeastern Electric Racing (NER). Angular 19 frontend (`angular-client/`) and Rust backend (`scylla-server/`), with schema tooling in `charybdis/` and MQTT broker config in `siren-base/`. -The Argos repo is checked out at `repos/argos/`. Paths below are relative to a checkout of it. The ticket number is the branch's leading number (`533-csv-upload` → `#533`). +The Argos repo's `develop` is checked out at `worktrees/argos/develop/`. Paths below are relative to any Argos worktree. The ticket number is the branch's leading number (`533-csv-upload` → `#533`). ## Worktrees -- `repos/argos/` is a clean reference to `develop`. Never edit, branch, commit, or run dev servers there. Only fetch, fast-forward `develop`, and manage worktrees from it. -- Every ticket gets its own worktree at `repos/worktrees/argos//`, and every workflow (implement, test, run, commit, PR) runs there. Create or reuse one with the `new-worktree` skill, based on `origin/develop`: `bash .delphi/new-worktree.sh argos origin/develop`. +- `worktrees/argos/develop/` is a clean reference to `develop`. Never edit, branch, commit, or run dev servers there; only fetch and fast-forward it. +- Every ticket gets its own worktree at `worktrees/argos//`, and every workflow (implement, test, run, commit, PR) runs there. Create or reuse one with the `new-worktree` skill, based on `origin/develop`: `bash .delphi/new-worktree.sh argos origin/develop`. - A new worktree has no `node_modules`: run `npm ci` in its `angular-client/` before testing or running the client. ## Local Development @@ -69,4 +69,4 @@ Issues live in GitHub Issues on `Northeastern-Electric-Racing/Argos` via the `gh ## Domain docs -The glossary and ADRs are workspace docs, not files in `repos/argos/`: `docs/CONTEXT.md` and `docs/adr/` at the workspace root. Edit them there and they're proposed back to Delphi. Read `docs/CONTEXT.md` and the relevant ADRs before exploring, use the glossary's terms, and flag any conflict with an ADR. ADR filenames follow `repos/argos/docs/agents/domain.md` (`--.md`). +The glossary and ADRs are workspace docs, not files in the Argos repo: `docs/CONTEXT.md` and `docs/adr/` at the workspace root. Edit them there and they're proposed back to Delphi. Read `docs/CONTEXT.md` and the relevant ADRs before exploring, use the glossary's terms, and flag any conflict with an ADR. ADR filenames follow `docs/agents/domain.md` in any Argos worktree (`--.md`). diff --git a/templates/workspace/.claude/skills/new-worktree/SKILL.md b/templates/workspace/.claude/skills/new-worktree/SKILL.md index 3bbd319..5eb74b9 100644 --- a/templates/workspace/.claude/skills/new-worktree/SKILL.md +++ b/templates/workspace/.claude/skills/new-worktree/SKILL.md @@ -1,9 +1,9 @@ --- name: new-worktree -description: Create or reuse a worktree for a branch of a repo in repos/ (repos/worktrees//). Worktrees are the default way to make or check out any new branch; use this before starting a ticket, reviewing or fixing a PR branch, or any other branch work, unless the user says not to use worktrees. +description: Create or reuse a worktree for a branch of a code repo (worktrees//). Worktrees are the default way to make or check out any new branch; use this before starting a ticket, reviewing or fixing a PR branch, or any other branch work, unless the user says not to use worktrees. --- -Make every new branch as a worktree, never by branching in `repos//`, unless the user explicitly says not to use worktrees. `repos//` stays a clean checkout of the default branch. +Every checkout of a code repo is a worktree under `worktrees//`. `.delphi/setup.sh` makes the default branch's (`worktrees///`); keep it a clean reference: fetch and fast-forward only. Make every other branch with this script, never by switching branches in an existing worktree, unless the user explicitly says not to use worktrees. Run from the workspace root: @@ -13,4 +13,4 @@ bash .delphi/new-worktree.sh [] An existing branch (local or on origin, e.g. a PR's head) is checked out; anything else is created from `` (default: the repo's default branch). It's safe to re-run. It prints the worktree path: `cd` there and do all work in it. -After the branch merges, remove it with `git -C repos/ worktree remove ../worktrees//`. +After the branch merges, remove it with `git -C worktrees//.bare worktree remove ../`. diff --git a/templates/workspace/.delphi/new-worktree.sh b/templates/workspace/.delphi/new-worktree.sh index b4f856b..7327d9b 100755 --- a/templates/workspace/.delphi/new-worktree.sh +++ b/templates/workspace/.delphi/new-worktree.sh @@ -1,16 +1,19 @@ #!/usr/bin/env bash -# .delphi/new-worktree.sh []: create or reuse repos/worktrees// -# for repos/ and print its path. An existing branch (local or on origin) is checked out; a new -# one starts from (default: origin's default branch). Runs on bash 3.2 and Git Bash. +# .delphi/new-worktree.sh []: create or reuse worktrees// from +# the store .delphi/setup.sh made at worktrees//.bare, and print its path. An existing branch +# (local or on origin) is checked out; a new one starts from (default: origin's default +# branch). Runs on bash 3.2 and Git Bash. set -euo pipefail root=$(cd "$(dirname "$0")/.." && pwd) repo=${1:?usage: new-worktree.sh []} branch=${2:?usage: new-worktree.sh []} -base=${3:-origin/HEAD} dest="$root/repos/worktrees/$repo/$branch" -g() { git -C "$root/repos/$repo" "$@"; } +base=${3:-origin/HEAD} dest="$root/worktrees/$repo/$branch" +g() { git -C "$root/worktrees/$repo/.bare" "$@"; } g fetch -q origin if [ -d "$dest" ]; then : -elif g rev-parse -q --verify "refs/heads/$branch" >/dev/null || g rev-parse -q --verify "refs/remotes/origin/$branch" >/dev/null; then +elif g rev-parse -q --verify "refs/heads/$branch" >/dev/null; then g worktree add -q "$dest" "$branch" +elif g rev-parse -q --verify "refs/remotes/origin/$branch" >/dev/null; then + g worktree add -q --track -b "$branch" "$dest" "origin/$branch" else g worktree add -q --no-track -b "$branch" "$dest" "$base" fi diff --git a/templates/workspace/.delphi/setup.sh b/templates/workspace/.delphi/setup.sh index 22ceb98..5516f74 100755 --- a/templates/workspace/.delphi/setup.sh +++ b/templates/workspace/.delphi/setup.sh @@ -1,8 +1,9 @@ #!/usr/bin/env bash -# .delphi/setup.sh: run in a checkout of ws/ (the workspace root). Clones each repo listed -# under `repos:` in workspace.yml into repos/ (skipping ones already there) and adds /repos/ -# to this clone's .git/info/exclude. Safe to re-run. Keep it bash 3.2 (macOS) and Git Bash safe: -# no bash-4 features, POSIX awk only. +# .delphi/setup.sh: run in a checkout of ws/ (the workspace root). For each repo listed under +# `repos:` in workspace.yml, fetches it into a bare store at worktrees//.bare and checks out +# its default branch as a worktree at worktrees// (skipping repos already +# there), then adds /worktrees/ to this clone's .git/info/exclude. Safe to re-run. Keep it bash 3.2 +# (macOS) and Git Bash safe: no bash-4 features, POSIX awk only. set -euo pipefail cd "$(dirname "$0")/.." @@ -16,15 +17,22 @@ list=$(awk ' while read -r name url; do [ -n "$name" ] || continue case "$name" in . | .. | *[!A-Za-z0-9._-]*) echo "setup: skipping unsafe repo name '$name'" >&2 && continue ;; esac - if [ -e "repos/$name" ]; then - echo "repos/$name: already present" - else - git clone "$url" "repos/$name" /dev/null /dev/null || echo /repos/ >>"$exclude" +grep -qxF /worktrees/ "$exclude" 2>/dev/null || echo /worktrees/ >>"$exclude" diff --git a/templates/workspace/CLAUDE.md b/templates/workspace/CLAUDE.md index 7caacfe..87dc67a 100644 --- a/templates/workspace/CLAUDE.md +++ b/templates/workspace/CLAUDE.md @@ -1,3 +1,3 @@ # Delphi workspace -You're in Delphi workspace `{{name}}` (branch `ws/{{name}}`); code repos live in `repos/`. Make new branches as worktrees (`new-worktree` skill) unless the user says not to. Work here unless the task clearly belongs to another project. +You're in Delphi workspace `{{name}}` (branch `ws/{{name}}`); code repos are checked out as worktrees under `worktrees//` (`.delphi/setup.sh` makes the default branch's). Make new branches as worktrees (`new-worktree` skill) unless the user says not to. Work here unless the task clearly belongs to another project. diff --git a/tests/e2e.sh b/tests/e2e.sh index 43b66c2..268732f 100755 --- a/tests/e2e.sh +++ b/tests/e2e.sh @@ -163,20 +163,25 @@ expect_bad defaults-manifest "defaults/workspace.yml: unexpected line" sh -c "pr expect_bad defaults-named-workspace "defaults/.delphi/link.sh: differs" sh -c "mkdir -p software/x/workspaces && cp -R $A software/x/workspaces/defaults && echo x >>software/x/workspaces/defaults/.delphi/link.sh" expect_bad template-workflow-drift "differs from .github/workflows/delphi.yml" sh -c "echo x >>.github/workflows/delphi.yml" -# --- 8. setup.sh clones workspace.yml repos into repos/ and is idempotent +# --- 8. setup.sh fetches workspace.yml repos into worktrees//.bare, checks out the default branch, and is idempotent for r in lib1 lib2; do git init -q "$t/$r" && git -C "$t/$r" commit -q --allow-empty -m "$r" && git clone -q --bare "$t/$r" "$t/$r.git" done +def=$(git -C "$t/lib1" branch --show-current) git clone -q -b ws/argos-dev "$t/origin.git" "$t/ws" printf 'harness: claude-code\nrepos:\n # a comment\n lib1: %s # trailing\n lib2: %s\n' \ "$t/lib1.git" "$t/lib2.git" >"$t/ws/workspace.yml" /bin/bash "$t/ws/.delphi/setup.sh" >/dev/null 2>&1 || fail "setup.sh failed" /bin/bash "$t/ws/.delphi/setup.sh" >"$t/out" 2>&1 || fail "setup.sh re-run failed" -{ [ -d "$t/ws/repos/lib1/.git" ] && [ -d "$t/ws/repos/lib2/.git" ]; } || fail "repos not cloned" -grep -q "repos/lib1: already present" "$t/out" || fail "re-run did not skip" -[ "$(grep -cx /repos/ "$t/ws/.git/info/exclude")" = 1 ] || fail "exclude not exactly once" -[ "$(git -C "$t/ws" status --porcelain)" = " M workspace.yml" ] || fail "repos/ shows in status" -ok "setup.sh clones repos into repos/, excludes them once, and is idempotent" +for r in lib1 lib2; do + [ "$(git -C "$t/ws/worktrees/$r/$def" rev-parse HEAD)" = "$(git -C "$t/$r" rev-parse HEAD)" ] || fail "$r default worktree missing" + [ "$(git -C "$t/ws/worktrees/$r/$def" branch --show-current)" = "$def" ] || fail "$r default worktree not on $def" +done +[ ! -e "$t/ws/repos" ] || fail "setup.sh made repos/" +grep -q "worktrees/lib1: already present" "$t/out" || fail "re-run did not skip" +[ "$(grep -cx /worktrees/ "$t/ws/.git/info/exclude")" = 1 ] || fail "exclude not exactly once" +[ "$(git -C "$t/ws" status --porcelain)" = " M workspace.yml" ] || fail "worktrees/ shows in status" +ok "setup.sh makes worktrees//.bare plus a default-branch worktree, excludes them once, and is idempotent" git clone -q -c core.autocrlf=true -b ws/argos-dev "$t/origin.git" "$t/ws-crlf" cp "$t/ws/workspace.yml" "$t/ws-crlf/" /bin/bash "$t/ws-crlf/.delphi/setup.sh" >/dev/null 2>&1 || fail "setup.sh fails with core.autocrlf=true" @@ -230,16 +235,20 @@ ok "a proposal failing check.sh is reported and not pushed; exit 1" # --- 11. new-worktree.sh: new branch from the default branch or ; existing branch checked out; reruns reuse git init -q "$t/code" && git -C "$t/code" commit -q --allow-empty -m one && git -C "$t/code" branch feat && git -C "$t/code" branch develop && git -C "$t/code" commit -q --allow-empty -m two -w=$t/nw && git clone -q -b ws/bms-dev "$t/origin.git" "$w" && git clone -q "$t/code" "$w/repos/code" +def=$(git -C "$t/code" branch --show-current) +w=$t/nw && git clone -q -b ws/bms-dev "$t/origin.git" "$w" +printf 'harness: claude-code\nrepos:\n code: %s\n' "$t/code" >"$w/workspace.yml" +bash "$w/.delphi/setup.sh" >/dev/null 2>&1 || fail "setup.sh failed for new-worktree test" nw() { bash "$w/.delphi/new-worktree.sh" "$@"; } -[ "$(nw code 1-new)" = "$w/repos/worktrees/code/1-new" ] || fail "new-worktree path" -[ "$(git -C "$w/repos/worktrees/code/1-new" rev-parse HEAD)" = "$(git -C "$t/code" rev-parse main)" ] || fail "new branch not from default" -nw code feat >/dev/null && [ "$(git -C "$w/repos/worktrees/code/feat" rev-parse HEAD)" = "$(git -C "$t/code" rev-parse feat)" ] || +[ "$(nw code 1-new)" = "$w/worktrees/code/1-new" ] || fail "new-worktree path" +[ "$(git -C "$w/worktrees/code/1-new" rev-parse HEAD)" = "$(git -C "$t/code" rev-parse "$def")" ] || fail "new branch not from default" +nw code feat >/dev/null && [ "$(git -C "$w/worktrees/code/feat" rev-parse HEAD)" = "$(git -C "$t/code" rev-parse feat)" ] || fail "existing branch not checked out" -nw code 2-dev origin/develop >/dev/null && [ "$(git -C "$w/repos/worktrees/code/2-dev" rev-parse HEAD)" = "$(git -C "$t/code" rev-parse develop)" ] || +[ "$(git -C "$w/worktrees/code/feat" rev-parse --abbrev-ref '@{u}')" = origin/feat ] || fail "existing branch not tracking origin" +nw code 2-dev origin/develop >/dev/null && [ "$(git -C "$w/worktrees/code/2-dev" rev-parse HEAD)" = "$(git -C "$t/code" rev-parse develop)" ] || fail " ignored" -[ "$(nw code feat)" = "$w/repos/worktrees/code/feat" ] || fail "rerun did not reuse" -[ "$(git -C "$w/repos/code" branch --show-current)" = main ] || fail "repos/code left its branch" +[ "$(nw code feat)" = "$w/worktrees/code/feat" ] || fail "rerun did not reuse" +[ "$(git -C "$w/worktrees/code/$def" branch --show-current)" = "$def" ] || fail "default worktree left its branch" ok "new-worktree.sh makes new branches from the default or , checks out existing ones, reuses on rerun" # --- 12. link.sh: other workspaces and main as detached worktrees under linked/, updated on re-run From 49bbb757374a6cebf970e86ca54d5b2edefda14a Mon Sep 17 00:00:00 2001 From: Claude Date: Sun, 27 Sep 2026 21:30:00 +0000 Subject: [PATCH 2/2] Keep repos/ as the bare store; worktrees/ holds every checkout setup.sh stores each repo bare in repos/ and checks out the default branch as the first worktree, worktrees// (re-created if missing; an older plain clone in repos/ gets a warning). Both dirs excluded. Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01818NEiW3BJCTrVCTDgaYDf --- README.md | 4 +- docs/design.md | 6 +-- docs/goals.md | 2 +- .../.claude/skills/new-worktree/SKILL.md | 4 +- .../.claude/skills/new-worktree/SKILL.md | 4 +- .../argos-dev/.delphi/new-worktree.sh | 4 +- .../workspaces/argos-dev/.delphi/setup.sh | 40 ++++++++++++------- .../.claude/skills/new-worktree/SKILL.md | 4 +- templates/workspace/.delphi/new-worktree.sh | 4 +- templates/workspace/.delphi/setup.sh | 40 ++++++++++++------- tests/e2e.sh | 18 ++++++--- 11 files changed, 78 insertions(+), 52 deletions(-) diff --git a/README.md b/README.md index 1574ea8..a093332 100644 --- a/README.md +++ b/README.md @@ -40,7 +40,7 @@ histories stay joined and edits on either side meet in normal three-way merges. ```sh git clone -b ws/argos-dev https://github.com/Northeastern-Electric-Racing/Delphi.git argos-dev -cd argos-dev && .delphi/setup.sh # checks out workspace.yml's repos under worktrees/ (git-ignored) +cd argos-dev && .delphi/setup.sh # repos/ stores + default-branch worktrees in worktrees/ (git-ignored) git switch -c my-change # edit, commit, push, open a PR into ws/argos-dev git fetch origin && git merge origin/ws/argos-dev # update your branch any time ``` @@ -57,7 +57,7 @@ workspace. Fix it in a PR into `ws/`: on a branch cut from `ws/`, ru ```yaml harness: claude-code -repos: # checked out under worktrees// by .delphi/setup.sh +repos: # stored in repos/, checked out in worktrees// by .delphi/setup.sh argos: https://github.com/Northeastern-Electric-Racing/Argos.git ``` diff --git a/docs/design.md b/docs/design.md index 9064d23..66b2622 100644 --- a/docs/design.md +++ b/docs/design.md @@ -95,9 +95,9 @@ Nobody pushes to `ws/*` or `main` directly (branch protection; CI's token is the ## 5. `.delphi/setup.sh` (in every workspace) Reads `repos:` from `workspace.yml` and, for each not yet present, fetches it into a bare store at -`worktrees//.bare` and checks out its default branch as a worktree at -`worktrees///`. Adds `/worktrees/` to the clone's `.git/info/exclude` once. -Nothing else: code lives only in worktrees, never in a plain clone. Must run on macOS `/bin/bash` 3.2 +`repos/`, then checks out its default branch as the first worktree, +`worktrees///` (re-created if missing). Adds `/repos/` and `/worktrees/` to +the clone's `.git/info/exclude` once. Nothing else: code is edited only in worktrees. Must run on macOS `/bin/bash` 3.2 and Git Bash: no bash-4 features, POSIX awk only. `.delphi/new-worktree.sh []` creates or reuses `worktrees//` diff --git a/docs/goals.md b/docs/goals.md index 8878816..7f9f4da 100644 --- a/docs/goals.md +++ b/docs/goals.md @@ -25,6 +25,6 @@ anyone, person or agent, work on a workspace with plain git and send improvement - **I1.** Changes reach `main` and `ws/*` only through PRs (CI's refreshes excepted). - **I2.** `ws/` history is joined to `main`; no unrelated histories, no force-pushes to `ws/*`. -- **I3.** Code repos in `worktrees/` never appear as workspace changes. +- **I3.** Code repos in `repos/` and `worktrees/` never appear as workspace changes. - **I4.** `setup.sh` works on macOS bash 3.2 and Git Bash. - **I5.** Everything is testable end to end without GitHub (`tests/e2e.sh`). diff --git a/software/application-software/argos/defaults/.claude/skills/new-worktree/SKILL.md b/software/application-software/argos/defaults/.claude/skills/new-worktree/SKILL.md index 3abacbe..633143f 100644 --- a/software/application-software/argos/defaults/.claude/skills/new-worktree/SKILL.md +++ b/software/application-software/argos/defaults/.claude/skills/new-worktree/SKILL.md @@ -3,7 +3,7 @@ name: new-worktree description: Create or reuse a worktree for a branch of a code repo (worktrees//). Worktrees are the default way to make or check out any new branch; use this before starting a ticket, reviewing or fixing a PR branch, or any other branch work, unless the user says not to use worktrees. --- -Every checkout of a code repo is a worktree under `worktrees//`. `.delphi/setup.sh` makes the default branch's (`worktrees///`); keep it a clean reference: fetch and fast-forward only. Make every other branch with this script, never by switching branches in an existing worktree, unless the user explicitly says not to use worktrees. +`repos/` is a bare store (no files); every checkout of a code repo is a worktree under `worktrees//`. `.delphi/setup.sh` makes the default branch's (`worktrees///`); keep it a clean reference: fetch and fast-forward only. Make every other branch with this script, never by switching branches in an existing worktree, unless the user explicitly says not to use worktrees. Run from the workspace root: @@ -13,6 +13,6 @@ bash .delphi/new-worktree.sh [] An existing branch (local or on origin, e.g. a PR's head) is checked out; anything else is created from `` (default: the repo's default branch). It's safe to re-run. It prints the worktree path: `cd` there and do all work in it. -After the branch merges, remove it with `git -C worktrees//.bare worktree remove ../`. +After the branch merges, remove it with `git -C repos/ worktree remove ../../worktrees//`. Argos: always pass `origin/develop` as ``. New ticket branches follow `{issue-number}-{kebab-case-title}`. Run `npm ci` in the worktree's `angular-client/` before building, testing, or running the client. diff --git a/software/application-software/argos/workspaces/argos-dev/.claude/skills/new-worktree/SKILL.md b/software/application-software/argos/workspaces/argos-dev/.claude/skills/new-worktree/SKILL.md index 3abacbe..633143f 100644 --- a/software/application-software/argos/workspaces/argos-dev/.claude/skills/new-worktree/SKILL.md +++ b/software/application-software/argos/workspaces/argos-dev/.claude/skills/new-worktree/SKILL.md @@ -3,7 +3,7 @@ name: new-worktree description: Create or reuse a worktree for a branch of a code repo (worktrees//). Worktrees are the default way to make or check out any new branch; use this before starting a ticket, reviewing or fixing a PR branch, or any other branch work, unless the user says not to use worktrees. --- -Every checkout of a code repo is a worktree under `worktrees//`. `.delphi/setup.sh` makes the default branch's (`worktrees///`); keep it a clean reference: fetch and fast-forward only. Make every other branch with this script, never by switching branches in an existing worktree, unless the user explicitly says not to use worktrees. +`repos/` is a bare store (no files); every checkout of a code repo is a worktree under `worktrees//`. `.delphi/setup.sh` makes the default branch's (`worktrees///`); keep it a clean reference: fetch and fast-forward only. Make every other branch with this script, never by switching branches in an existing worktree, unless the user explicitly says not to use worktrees. Run from the workspace root: @@ -13,6 +13,6 @@ bash .delphi/new-worktree.sh [] An existing branch (local or on origin, e.g. a PR's head) is checked out; anything else is created from `` (default: the repo's default branch). It's safe to re-run. It prints the worktree path: `cd` there and do all work in it. -After the branch merges, remove it with `git -C worktrees//.bare worktree remove ../`. +After the branch merges, remove it with `git -C repos/ worktree remove ../../worktrees//`. Argos: always pass `origin/develop` as ``. New ticket branches follow `{issue-number}-{kebab-case-title}`. Run `npm ci` in the worktree's `angular-client/` before building, testing, or running the client. diff --git a/software/application-software/argos/workspaces/argos-dev/.delphi/new-worktree.sh b/software/application-software/argos/workspaces/argos-dev/.delphi/new-worktree.sh index 7327d9b..34a2f1d 100755 --- a/software/application-software/argos/workspaces/argos-dev/.delphi/new-worktree.sh +++ b/software/application-software/argos/workspaces/argos-dev/.delphi/new-worktree.sh @@ -1,13 +1,13 @@ #!/usr/bin/env bash # .delphi/new-worktree.sh []: create or reuse worktrees// from -# the store .delphi/setup.sh made at worktrees//.bare, and print its path. An existing branch +# the store .delphi/setup.sh made at repos/, and print its path. An existing branch # (local or on origin) is checked out; a new one starts from (default: origin's default # branch). Runs on bash 3.2 and Git Bash. set -euo pipefail root=$(cd "$(dirname "$0")/.." && pwd) repo=${1:?usage: new-worktree.sh []} branch=${2:?usage: new-worktree.sh []} base=${3:-origin/HEAD} dest="$root/worktrees/$repo/$branch" -g() { git -C "$root/worktrees/$repo/.bare" "$@"; } +g() { git -C "$root/repos/$repo" "$@"; } g fetch -q origin if [ -d "$dest" ]; then : elif g rev-parse -q --verify "refs/heads/$branch" >/dev/null; then diff --git a/software/application-software/argos/workspaces/argos-dev/.delphi/setup.sh b/software/application-software/argos/workspaces/argos-dev/.delphi/setup.sh index 5516f74..9cc1d1e 100755 --- a/software/application-software/argos/workspaces/argos-dev/.delphi/setup.sh +++ b/software/application-software/argos/workspaces/argos-dev/.delphi/setup.sh @@ -1,9 +1,9 @@ #!/usr/bin/env bash # .delphi/setup.sh: run in a checkout of ws/ (the workspace root). For each repo listed under -# `repos:` in workspace.yml, fetches it into a bare store at worktrees//.bare and checks out -# its default branch as a worktree at worktrees// (skipping repos already -# there), then adds /worktrees/ to this clone's .git/info/exclude. Safe to re-run. Keep it bash 3.2 -# (macOS) and Git Bash safe: no bash-4 features, POSIX awk only. +# `repos:` in workspace.yml, fetches it into a bare store at repos/ (unless present) and checks +# out its default branch as the first worktree, worktrees//. Adds /repos/ and +# /worktrees/ to this clone's .git/info/exclude. Safe to re-run. Keep it bash 3.2 (macOS) and Git +# Bash safe: no bash-4 features, POSIX awk only. set -euo pipefail cd "$(dirname "$0")/.." @@ -17,22 +17,32 @@ list=$(awk ' while read -r name url; do [ -n "$name" ] || continue case "$name" in . | .. | *[!A-Za-z0-9._-]*) echo "setup: skipping unsafe repo name '$name'" >&2 && continue ;; esac - store=worktrees/$name/.bare + store=repos/$name if [ -e "$store" ]; then - echo "worktrees/$name: already present" - continue + echo "repos/$name: already present" + else + git init -q --bare "$store" + git -C "$store" remote add origin "$url" + git -C "$store" fetch -q origin /dev/null &2 && continue; } + def=${def#origin/} + dest=worktrees/$name/$def + if [ -e "$dest" ]; then continue + elif git -C "$store" rev-parse -q --verify "refs/heads/$def" >/dev/null; then + git -C "$store" worktree add -q "$PWD/$dest" "$def" || + echo "setup: could not check out $dest (is $def checked out in repos/$name?)" >&2 + else + git -C "$store" worktree add -q --track -b "$def" "$PWD/$dest" "origin/$def" fi - git init -q --bare "$store" - git -C "$store" remote add origin "$url" - git -C "$store" fetch -q origin /dev/null /dev/null || echo /worktrees/ >>"$exclude" +for d in /repos/ /worktrees/; do + grep -qxF "$d" "$exclude" 2>/dev/null || echo "$d" >>"$exclude" +done diff --git a/templates/workspace/.claude/skills/new-worktree/SKILL.md b/templates/workspace/.claude/skills/new-worktree/SKILL.md index 5eb74b9..bdf76e8 100644 --- a/templates/workspace/.claude/skills/new-worktree/SKILL.md +++ b/templates/workspace/.claude/skills/new-worktree/SKILL.md @@ -3,7 +3,7 @@ name: new-worktree description: Create or reuse a worktree for a branch of a code repo (worktrees//). Worktrees are the default way to make or check out any new branch; use this before starting a ticket, reviewing or fixing a PR branch, or any other branch work, unless the user says not to use worktrees. --- -Every checkout of a code repo is a worktree under `worktrees//`. `.delphi/setup.sh` makes the default branch's (`worktrees///`); keep it a clean reference: fetch and fast-forward only. Make every other branch with this script, never by switching branches in an existing worktree, unless the user explicitly says not to use worktrees. +`repos/` is a bare store (no files); every checkout of a code repo is a worktree under `worktrees//`. `.delphi/setup.sh` makes the default branch's (`worktrees///`); keep it a clean reference: fetch and fast-forward only. Make every other branch with this script, never by switching branches in an existing worktree, unless the user explicitly says not to use worktrees. Run from the workspace root: @@ -13,4 +13,4 @@ bash .delphi/new-worktree.sh [] An existing branch (local or on origin, e.g. a PR's head) is checked out; anything else is created from `` (default: the repo's default branch). It's safe to re-run. It prints the worktree path: `cd` there and do all work in it. -After the branch merges, remove it with `git -C worktrees//.bare worktree remove ../`. +After the branch merges, remove it with `git -C repos/ worktree remove ../../worktrees//`. diff --git a/templates/workspace/.delphi/new-worktree.sh b/templates/workspace/.delphi/new-worktree.sh index 7327d9b..34a2f1d 100755 --- a/templates/workspace/.delphi/new-worktree.sh +++ b/templates/workspace/.delphi/new-worktree.sh @@ -1,13 +1,13 @@ #!/usr/bin/env bash # .delphi/new-worktree.sh []: create or reuse worktrees// from -# the store .delphi/setup.sh made at worktrees//.bare, and print its path. An existing branch +# the store .delphi/setup.sh made at repos/, and print its path. An existing branch # (local or on origin) is checked out; a new one starts from (default: origin's default # branch). Runs on bash 3.2 and Git Bash. set -euo pipefail root=$(cd "$(dirname "$0")/.." && pwd) repo=${1:?usage: new-worktree.sh []} branch=${2:?usage: new-worktree.sh []} base=${3:-origin/HEAD} dest="$root/worktrees/$repo/$branch" -g() { git -C "$root/worktrees/$repo/.bare" "$@"; } +g() { git -C "$root/repos/$repo" "$@"; } g fetch -q origin if [ -d "$dest" ]; then : elif g rev-parse -q --verify "refs/heads/$branch" >/dev/null; then diff --git a/templates/workspace/.delphi/setup.sh b/templates/workspace/.delphi/setup.sh index 5516f74..9cc1d1e 100755 --- a/templates/workspace/.delphi/setup.sh +++ b/templates/workspace/.delphi/setup.sh @@ -1,9 +1,9 @@ #!/usr/bin/env bash # .delphi/setup.sh: run in a checkout of ws/ (the workspace root). For each repo listed under -# `repos:` in workspace.yml, fetches it into a bare store at worktrees//.bare and checks out -# its default branch as a worktree at worktrees// (skipping repos already -# there), then adds /worktrees/ to this clone's .git/info/exclude. Safe to re-run. Keep it bash 3.2 -# (macOS) and Git Bash safe: no bash-4 features, POSIX awk only. +# `repos:` in workspace.yml, fetches it into a bare store at repos/ (unless present) and checks +# out its default branch as the first worktree, worktrees//. Adds /repos/ and +# /worktrees/ to this clone's .git/info/exclude. Safe to re-run. Keep it bash 3.2 (macOS) and Git +# Bash safe: no bash-4 features, POSIX awk only. set -euo pipefail cd "$(dirname "$0")/.." @@ -17,22 +17,32 @@ list=$(awk ' while read -r name url; do [ -n "$name" ] || continue case "$name" in . | .. | *[!A-Za-z0-9._-]*) echo "setup: skipping unsafe repo name '$name'" >&2 && continue ;; esac - store=worktrees/$name/.bare + store=repos/$name if [ -e "$store" ]; then - echo "worktrees/$name: already present" - continue + echo "repos/$name: already present" + else + git init -q --bare "$store" + git -C "$store" remote add origin "$url" + git -C "$store" fetch -q origin /dev/null &2 && continue; } + def=${def#origin/} + dest=worktrees/$name/$def + if [ -e "$dest" ]; then continue + elif git -C "$store" rev-parse -q --verify "refs/heads/$def" >/dev/null; then + git -C "$store" worktree add -q "$PWD/$dest" "$def" || + echo "setup: could not check out $dest (is $def checked out in repos/$name?)" >&2 + else + git -C "$store" worktree add -q --track -b "$def" "$PWD/$dest" "origin/$def" fi - git init -q --bare "$store" - git -C "$store" remote add origin "$url" - git -C "$store" fetch -q origin /dev/null /dev/null || echo /worktrees/ >>"$exclude" +for d in /repos/ /worktrees/; do + grep -qxF "$d" "$exclude" 2>/dev/null || echo "$d" >>"$exclude" +done diff --git a/tests/e2e.sh b/tests/e2e.sh index 268732f..2c490e4 100755 --- a/tests/e2e.sh +++ b/tests/e2e.sh @@ -163,7 +163,7 @@ expect_bad defaults-manifest "defaults/workspace.yml: unexpected line" sh -c "pr expect_bad defaults-named-workspace "defaults/.delphi/link.sh: differs" sh -c "mkdir -p software/x/workspaces && cp -R $A software/x/workspaces/defaults && echo x >>software/x/workspaces/defaults/.delphi/link.sh" expect_bad template-workflow-drift "differs from .github/workflows/delphi.yml" sh -c "echo x >>.github/workflows/delphi.yml" -# --- 8. setup.sh fetches workspace.yml repos into worktrees//.bare, checks out the default branch, and is idempotent +# --- 8. setup.sh fetches workspace.yml repos into repos/, checks out the default branch under worktrees/, and is idempotent for r in lib1 lib2; do git init -q "$t/$r" && git -C "$t/$r" commit -q --allow-empty -m "$r" && git clone -q --bare "$t/$r" "$t/$r.git" done @@ -177,11 +177,17 @@ for r in lib1 lib2; do [ "$(git -C "$t/ws/worktrees/$r/$def" rev-parse HEAD)" = "$(git -C "$t/$r" rev-parse HEAD)" ] || fail "$r default worktree missing" [ "$(git -C "$t/ws/worktrees/$r/$def" branch --show-current)" = "$def" ] || fail "$r default worktree not on $def" done -[ ! -e "$t/ws/repos" ] || fail "setup.sh made repos/" -grep -q "worktrees/lib1: already present" "$t/out" || fail "re-run did not skip" -[ "$(grep -cx /worktrees/ "$t/ws/.git/info/exclude")" = 1 ] || fail "exclude not exactly once" -[ "$(git -C "$t/ws" status --porcelain)" = " M workspace.yml" ] || fail "worktrees/ shows in status" -ok "setup.sh makes worktrees//.bare plus a default-branch worktree, excludes them once, and is idempotent" +[ "$(git -C "$t/ws/repos/lib1" rev-parse --is-bare-repository)" = true ] || fail "repos/lib1 not a bare store" +grep -q "repos/lib1: already present" "$t/out" || fail "re-run did not skip" +for d in /repos/ /worktrees/; do [ "$(grep -cx "$d" "$t/ws/.git/info/exclude")" = 1 ] || fail "$d not excluded exactly once"; done +[ "$(git -C "$t/ws" status --porcelain)" = " M workspace.yml" ] || fail "repos/ or worktrees/ shows in status" +rm -rf "$t/ws/worktrees/lib2" && git -C "$t/ws/repos/lib2" worktree prune && /bin/bash "$t/ws/.delphi/setup.sh" >/dev/null 2>&1 && + [ -d "$t/ws/worktrees/lib2/$def" ] || fail "re-run did not restore a missing default worktree" +ok "setup.sh makes a bare store in repos/ plus a default-branch worktree in worktrees/, excludes both once, and is idempotent" +w=$t/legacy && git clone -q -b ws/argos-dev "$t/origin.git" "$w" && cp "$t/ws/workspace.yml" "$w/" && git clone -q "$t/lib1.git" "$w/repos/lib1" +/bin/bash "$w/.delphi/setup.sh" >/dev/null 2>"$t/err" || fail "setup.sh failed on a pre-existing clone" +{ grep -q "could not check out worktrees/lib1/$def" "$t/err" && [ -d "$w/worktrees/lib2/$def" ]; } || fail "pre-existing clone not reported" +ok "setup.sh warns and carries on when repos/ is an older plain clone" git clone -q -c core.autocrlf=true -b ws/argos-dev "$t/origin.git" "$t/ws-crlf" cp "$t/ws/workspace.yml" "$t/ws-crlf/" /bin/bash "$t/ws-crlf/.delphi/setup.sh" >/dev/null 2>&1 || fail "setup.sh fails with core.autocrlf=true"