#!/bin/bash # issue-edit.sh - Edit an issue on GitHub or Gitea # Usage: issue-edit.sh -i [-t ] [-b <body>] [-l <labels>] [-m <milestone>] set -e SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" source "$SCRIPT_DIR/detect-platform.sh" # Parse arguments ISSUE_NUMBER="" TITLE="" BODY="" BODY_FILE="" LABELS="" MILESTONE="" # Usage-error contract (R4, 2026-08-28): usage errors print to STDERR and exit 2, # distinct from provider, credential, and verification failures (exit 1), so a # caller or stop gate can tell an invocation defect from a delivery blocker. usage_error() { echo "Error: $*" >&2 echo "Usage: issue-edit.sh -i <issue_number> [-t <title>] [-b <body>] [-l <labels>] [-m <milestone>] (see --help)" >&2 exit 2 } while [[ $# -gt 0 ]]; do case $1 in -i|--issue) [[ $# -ge 2 && "$2" != - && "$2" != --* && ! "$2" =~ ^-[[:alnum:]] ]] || usage_error "option $1 requires a value (option-like values are rejected; bare - is reserved)" ISSUE_NUMBER="$2" shift 2 ;; -t|--title) [[ $# -ge 2 && "$2" != - && "$2" != --* && ! "$2" =~ ^-[[:alnum:]] ]] || usage_error "option $1 requires a value (option-like values are rejected; bare - is reserved)" TITLE="$2" shift 2 ;; -b|--body) [[ $# -ge 2 && "$2" != - && "$2" != --* && ! "$2" =~ ^-[[:alnum:]] ]] || usage_error "option $1 requires a value (option-like values are rejected; bare - is reserved)" BODY="$2" shift 2 ;; --body-file) # R3: body from file (or '-' = stdin); mutually exclusive with --body. [[ $# -ge 2 && "$2" != --* ]] || usage_error "option $1 requires a path (or - for stdin)" BODY_FILE="$2" shift 2 ;; -l|--labels) [[ $# -ge 2 && "$2" != - && "$2" != --* && ! "$2" =~ ^-[[:alnum:]] ]] || usage_error "option $1 requires a value (option-like values are rejected; bare - is reserved)" LABELS="$2" shift 2 ;; -m|--milestone) [[ $# -ge 2 && "$2" != - && "$2" != --* && ! "$2" =~ ^-[[:alnum:]] ]] || usage_error "option $1 requires a value (option-like values are rejected; bare - is reserved)" MILESTONE="$2" shift 2 ;; -h|--help) echo "Usage: issue-edit.sh -i <issue_number> [-t <title>] [-b <body>] [-l <labels>] [-m <milestone>]" echo "" echo "Options:" echo " -i, --issue Issue number (required)" echo " -t, --title New title" echo " -b, --body New body/description" echo " -l, --labels Labels (comma-separated, replaces existing)" echo " -m, --milestone Milestone name" echo " -h, --help Show this help" echo "" echo "Exit codes: 0 success; 2 usage error (stderr); 1 provider/credential/verification failure." exit 0 ;; *) usage_error "unknown option: $1" ;; esac done # R3 (2026-08-29): resolve --body-file into BODY (file or stdin '-'); # exclusive with an explicit --body/--comment value. if [[ -n "$BODY_FILE" ]]; then [[ -z "$BODY" ]] || usage_error "--body-file and --body are mutually exclusive" if [[ "$BODY_FILE" == "-" ]]; then BODY=$(cat) || usage_error "could not read body from stdin" else [[ -r "$BODY_FILE" ]] || usage_error "body file not readable: $BODY_FILE" BODY=$(cat "$BODY_FILE") || usage_error "could not read body file: $BODY_FILE" fi fi if [[ -z "$ISSUE_NUMBER" ]]; then usage_error "issue number is required (-i/--issue)" fi detect_platform >/dev/null if [[ "$PLATFORM" == "github" ]]; then CMD=(gh issue edit "$ISSUE_NUMBER") [[ -n "$TITLE" ]] && CMD+=(--title "$TITLE") [[ -n "$BODY" ]] && CMD+=(--body "$BODY") [[ -n "$LABELS" ]] && CMD+=(--add-label "$LABELS") [[ -n "$MILESTONE" ]] && CMD+=(--milestone "$MILESTONE") prov_rc=0 "${CMD[@]}" || prov_rc=$? [[ "$prov_rc" -eq 0 ]] || { echo "Error: provider command failed (exit ${prov_rc}; provider failure, not a usage error)" >&2; exit 1; } echo "Updated GitHub issue #$ISSUE_NUMBER" elif [[ "$PLATFORM" == "gitea" ]]; then REPO_SLUG=$(get_repo_slug) || { echo "Error: Could not resolve Gitea repo slug from remote" >&2 exit 1 } REPO_LOGIN=$(get_gitea_login) || { echo "Error: Could not resolve Gitea login for remote host" >&2 exit 1 } CMD=(tea issue edit "$ISSUE_NUMBER" --repo "$REPO_SLUG" --login "$REPO_LOGIN") [[ -n "$TITLE" ]] && CMD+=(--title "$TITLE") [[ -n "$BODY" ]] && CMD+=(--description "$BODY") [[ -n "$LABELS" ]] && CMD+=(--add-labels "$LABELS") [[ -n "$MILESTONE" ]] && CMD+=(--milestone "$MILESTONE") prov_rc=0 "${CMD[@]}" || prov_rc=$? [[ "$prov_rc" -eq 0 ]] || { echo "Error: provider command failed (exit ${prov_rc}; provider failure, not a usage error)" >&2; exit 1; } echo "Updated Gitea issue #$ISSUE_NUMBER" else echo "Error: Unknown platform" exit 1 fi