diff --git a/nix/packages/hive-forge-tools.nix b/nix/packages/hive-forge-tools.nix index 188b279..33eb6e5 100644 --- a/nix/packages/hive-forge-tools.nix +++ b/nix/packages/hive-forge-tools.nix @@ -27,21 +27,28 @@ pkgs.writeShellApplication { _token=$(cat "$_token_file") FORGE_API="$HIVE_FORGE_URL/api/v1" + # `-sS --fail-with-body` makes curl quiet on success, print errors to + # stderr on transport failures, AND surface the response body for HTTP + # error codes (4xx/5xx) before exiting non-zero. Without this, a 500 / + # 404 from forge silently returned an empty stdout — combined with the + # script's `set -o pipefail` + the `| jq ...` consumer, the failure + # propagated as "no output" with no clue about what went wrong (closes + # #353). forge_get() { - ${pkgs.curl}/bin/curl -sf \ + ${pkgs.curl}/bin/curl -sS --fail-with-body \ -H "Authorization: token $_token" \ -H "Accept: application/json" \ "$1" } forge_post() { - ${pkgs.curl}/bin/curl -sf -X POST \ + ${pkgs.curl}/bin/curl -sS --fail-with-body -X POST \ -H "Authorization: token $_token" \ -H "Content-Type: application/json" \ -H "Accept: application/json" \ -d "$2" "$1" } forge_patch() { - ${pkgs.curl}/bin/curl -sf -X PATCH \ + ${pkgs.curl}/bin/curl -sS --fail-with-body -X PATCH \ -H "Authorization: token $_token" \ -H "Content-Type: application/json" \ -H "Accept: application/json" \ @@ -51,13 +58,13 @@ pkgs.writeShellApplication { local _url="$1" local _body="''${2:-}" if [ -n "$_body" ]; then - ${pkgs.curl}/bin/curl -sf -X DELETE \ + ${pkgs.curl}/bin/curl -sS --fail-with-body -X DELETE \ -H "Authorization: token $_token" \ -H "Content-Type: application/json" \ -H "Accept: application/json" \ -d "$_body" "$_url" else - ${pkgs.curl}/bin/curl -sf -X DELETE \ + ${pkgs.curl}/bin/curl -sS --fail-with-body -X DELETE \ -H "Authorization: token $_token" \ -H "Accept: application/json" \ "$_url" @@ -189,17 +196,29 @@ pkgs.writeShellApplication { cmd_assign() { # assign [--remove] # Assign or unassign a user on an issue or PR. + # + # The Forgejo API has no dedicated `POST /issues/{n}/assignees` + # endpoint (closes #353) — assignees are an EditIssueOption field + # on the issue itself. We patch the full assignee list: + # - add: GET current assignees, append `_user`, PATCH back + # - remove: GET current assignees, drop `_user`, PATCH back + # The PATCH is idempotent: re-adding an existing assignee or + # removing one that's not on the list is a no-op (the resulting + # assignee list is unchanged). if [ $# -lt 2 ]; then echo "usage: hive-forge assign [--remove]" >&2; exit 1; fi local _n="$1" _user="$2" _remove="''${3:-}" - local _payload - _payload=$(jq -n --arg u "$_user" '{assignees:[$u]}') + local _current _payload + _current=$(forge_get "$FORGE_API/repos/$HIVE_FORGE_REPO/issues/$_n" \ + | jq -c '[.assignees[]?.login]') if [ "$_remove" = "--remove" ]; then - forge_delete "$FORGE_API/repos/$HIVE_FORGE_REPO/issues/$_n/assignees" "$_payload" \ - | jq '{number,assignees:[.assignees[]?.login]}' + _payload=$(jq -n --argjson cur "$_current" --arg u "$_user" \ + '{assignees: ($cur - [$u])}') else - forge_post "$FORGE_API/repos/$HIVE_FORGE_REPO/issues/$_n/assignees" "$_payload" \ - | jq '{number,assignees:[.assignees[]?.login]}' + _payload=$(jq -n --argjson cur "$_current" --arg u "$_user" \ + '{assignees: (($cur + [$u]) | unique)}') fi + forge_patch "$FORGE_API/repos/$HIVE_FORGE_REPO/issues/$_n" "$_payload" \ + | jq '{number,assignees:[.assignees[]?.login]}' } cmd_close() {