Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
11 changes: 8 additions & 3 deletions bin/gs
Original file line number Diff line number Diff line change
Expand Up @@ -24,8 +24,9 @@ gs - gentle-shell on main, plus one disposable worktree per PR
gs pr [repo] <n> One step to review PR <n>: worktree (reused and
updated if it exists), then Pi on it for shell PRs
gs issue [repo] <n> One step to start issue <n>: race check (stops if the
issue is closed, assigned, or referenced by a PR; GS_FORCE=1
skips it), branch <type>/<n>-<slug> from origin/main,
issue is closed, assigned to someone other than you,
or referenced by a PR; GS_FORCE=1 skips it),
branch <type>/<n>-<slug> from origin/main,
then Pi on it for shell issues

gs new <repo> <name> New branch <name> from fresh origin/main
Expand Down Expand Up @@ -231,7 +232,11 @@ cmd_issue() {
fi
if [ "${GS_FORCE:-}" != 1 ]; then
[ "$state" = OPEN ] || die "$gh_repo#$NUM is $state (GS_FORCE=1 to continue)"
[ -z "$assignees" ] || die "$gh_repo#$NUM is assigned to $assignees (GS_FORCE=1 to continue)"
# Your own assignment is your claim, not a race: only others block.
local me others=""
me=$(gh api user --jq .login 2>/dev/null || true)
others=$(printf '%s' "$assignees" | tr ',' '\n' | grep -vxF -- "${me:-}" | paste -sd, - || true)
[ -z "$others" ] || die "$gh_repo#$NUM is assigned to $others (GS_FORCE=1 to continue)"
prs=$(gh pr list -R "$gh_repo" --state all --search "$NUM in:title,body" --json number,state,title \
--jq '.[] | " #\(.number) \(.state) \(.title)"')
[ -z "$prs" ] || die "PRs may already reference #$NUM (GS_FORCE=1 to continue):
Expand Down
26 changes: 26 additions & 0 deletions tests/gs_test.sh
Original file line number Diff line number Diff line change
Expand Up @@ -144,6 +144,32 @@ check "workshop: gs issue reuses the worktree and opens it isolated, never --lin
check "workshop: gs issue kickoff names the cortex skill without a legacy home path" \
'[[ "$L" == *"cortex-maintainer-delivery skill"* ]] && [[ "$L" != *".pi/agent"* ]]'

# gs issue race check: only someone else's assignment blocks a new worktree.
race_stub() {
cat > "$S/gh" <<EOF
#!/bin/sh
case "\$1 \$2" in
"issue view") printf 'OPEN\037$1\037fix: thing\037type:bug\n' ;;
"api user") echo me ;;
"pr list") echo "pr list" >> "$CALLS" ;;
esac
exit 0
EOF
chmod +x "$S/gh"
}
setup workshop; race_stub me
OUT=$("$GS" issue shell 9 2>&1); L=$(cat "$CALLS" 2>/dev/null)
check "workshop: gs issue assigned only to you passes the race check" \
'[[ "$OUT" != *"is assigned"* ]] && [[ "$L" == *"pr list"* ]]'
setup workshop; race_stub other
OUT=$("$GS" issue shell 9 2>&1); rc=$?
check "workshop: gs issue assigned to someone else still stops" \
'[ "$rc" = 1 ] && [[ "$OUT" == *"#9 is assigned to other (GS_FORCE=1 to continue)"* ]]'
setup workshop; race_stub me,other
OUT=$("$GS" issue shell 9 2>&1); rc=$?
check "workshop: gs issue shared with someone else stops and names only them" \
'[ "$rc" = 1 ] && [[ "$OUT" == *"#9 is assigned to other (GS_FORCE=1 to continue)"* ]]'

# --- sync (workshop) ----------------------------------------------------------

setup workshop
Expand Down
Loading