Files
ClawSkills/git-hangman-lab/scripts/pr

337 lines
8.8 KiB
Bash
Executable File

#!/usr/bin/env bash
set -euo pipefail
# Get the directory where this script is located
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
# Ensure access token exists
ensure_token() {
# Check if git-access-token exists, if not generate it
if ! secret-mgr list 2>/dev/null | grep -q "git-access-token"; then
echo "Access token not found, generating..."
"$SCRIPT_DIR/generate-access-token"
fi
# Get the access token (extract actual token from "Access token was successfully created: <token>")
GIT_TOKEN="$(secret-mgr get-secret --key git-access-token | awk '{print $NF}')"
if [[ -z "$GIT_TOKEN" ]]; then
echo "Error: Failed to get git-access-token"
exit 1
fi
echo "$GIT_TOKEN"
}
# Get owner and repo from local git repo
get_repo_info() {
local repo_path="$1"
if [[ ! -d "$repo_path/.git" ]]; then
echo "Error: Not a git repository: $repo_path"
exit 1
fi
REMOTE_URL="$(git -C "$repo_path" remote get-url origin)"
if [[ "$REMOTE_URL" =~ https://git\.hangman-lab\.top/([^/]+)/([^/]+)\.git ]]; then
OWNER="${BASH_REMATCH[1]}"
REPO_NAME="${BASH_REMATCH[2]}"
else
echo "Error: Invalid remote URL format: $REMOTE_URL"
echo "Expected: https://git.hangman-lab.top/\${owner}/\${repo-name}.git"
exit 1
fi
}
# Subcommand: create
cmd_create() {
local repo_path="$1"
local head_branch="$2"
local base_branch="$3"
local pr_title="${4:-untitled pull request}"
local pr_body="${5:-}"
get_repo_info "$repo_path"
local token
token="$(ensure_token)"
echo "Creating PR: $OWNER/$REPO_NAME ($head_branch -> $base_branch)"
RESPONSE=$(curl -s -X POST "https://git.hangman-lab.top/api/v1/repos/${OWNER}/${REPO_NAME}/pulls" \
-H 'accept: application/json' \
-H 'Content-Type: application/json' \
-H "Authorization: token ${token}" \
-d "$(jq -n \
--arg head "$head_branch" \
--arg base "$base_branch" \
--arg title "$pr_title" \
--arg body "$pr_body" \
'{head: $head, base: $base, title: $title, body: $body}')")
if echo "$RESPONSE" | jq -e '.message' >/dev/null 2>&1; then
ERROR_MSG=$(echo "$RESPONSE" | jq -r '.message')
echo "Error: $ERROR_MSG"
exit 1
fi
PR_URL=$(echo "$RESPONSE" | jq -r '.html_url // empty')
if [[ -n "$PR_URL" ]]; then
echo "Pull request created successfully!"
echo "URL: $PR_URL"
else
echo "Error: Failed to create pull request"
echo "Response: $RESPONSE"
exit 1
fi
}
# Subcommand: list
cmd_list() {
local repo_path="$1"
get_repo_info "$repo_path"
local token
token="$(ensure_token)"
echo "Listing PRs for: $OWNER/$REPO_NAME"
RESPONSE=$(curl -s -X GET "https://git.hangman-lab.top/api/v1/repos/${OWNER}/${REPO_NAME}/pulls" \
-H 'accept: application/json' \
-H "Authorization: token ${token}")
if echo "$RESPONSE" | jq -e '.message' >/dev/null 2>&1; then
ERROR_MSG=$(echo "$RESPONSE" | jq -r '.message')
echo "Error: $ERROR_MSG"
exit 1
fi
echo "$RESPONSE" | jq -r '.[] | "\(.number)\t\(.title)\t\(.state)\t\(.html_url)"' 2>/dev/null || echo "$RESPONSE"
}
# Subcommand: commits
cmd_commits() {
local repo_path="$1"
local pr_index="$2"
get_repo_info "$repo_path"
local token
token="$(ensure_token)"
echo "Fetching commits for PR #$pr_index in: $OWNER/$REPO_NAME"
RESPONSE=$(curl -s -X GET "https://git.hangman-lab.top/api/v1/repos/${OWNER}/${REPO_NAME}/pulls/${pr_index}/commits" \
-H 'accept: application/json' \
-H "Authorization: token ${token}")
if echo "$RESPONSE" | jq -e '.message' >/dev/null 2>&1; then
ERROR_MSG=$(echo "$RESPONSE" | jq -r '.message')
echo "Error: $ERROR_MSG"
exit 1
fi
echo "$RESPONSE" | jq -r '.[] | "\(.sha[0:7])\t\(.commit.message | split("\n")[0])"' 2>/dev/null || echo "$RESPONSE"
}
# Subcommand: show
cmd_show() {
local repo_path="$1"
local pr_index="$2"
get_repo_info "$repo_path"
local token
token="$(ensure_token)"
echo "Showing PR #$pr_index in: $OWNER/$REPO_NAME"
RESPONSE=$(curl -s -X GET "https://git.hangman-lab.top/api/v1/repos/${OWNER}/${REPO_NAME}/pulls/${pr_index}" \
-H 'accept: application/json' \
-H "Authorization: token ${token}")
if echo "$RESPONSE" | jq -e '.message' >/dev/null 2>&1; then
ERROR_MSG=$(echo "$RESPONSE" | jq -r '.message')
echo "Error: $ERROR_MSG"
exit 1
fi
echo "$RESPONSE" | jq '{
id, number, title, body, state, draft, mergeable, merged,
additions, deletions, changed_files, comments,
html_url, diff_url, patch_url,
user: .user | {login, full_name, html_url},
labels,
milestone,
assignee,
base: .base | {label, ref, sha},
head: .head | {label, ref, sha},
merge_base,
created_at, updated_at, closed_at
}'
}
# Subcommand: merge
cmd_merge() {
local repo_path="$1"
local pr_index="$2"
local do="$3"
local commit_id="${4:-}"
local title="${5:-}"
local message="${6:-}"
get_repo_info "$repo_path"
local token
token="$(ensure_token)"
echo "Merging PR #$pr_index in: $OWNER/$REPO_NAME (Do: $do)"
# Get PR head sha first
PR_INFO=$(curl -s -X GET "https://git.hangman-lab.top/api/v1/repos/${OWNER}/${REPO_NAME}/pulls/${pr_index}" \
-H 'accept: application/json' \
-H "Authorization: token ${token}")
HEAD_SHA=$(echo "$PR_INFO" | jq -r '.head.sha')
if [[ -z "$HEAD_SHA" || "$HEAD_SHA" == "null" ]]; then
echo "Error: Failed to get PR head sha"
exit 1
fi
# Build JSON body
local json
json=$(jq -n \
--arg do "$do" \
--arg commit_id "$commit_id" \
--arg title "$title" \
--arg message "$message" \
--arg head_sha "$HEAD_SHA" \
'{
Do: $do,
MergeCommitID: (if $commit_id != "" then $commit_id else null end),
MergeMessageField: (if $message != "" then $message else null end),
MergeTitleField: (if $title != "" then $title else null end),
delete_branch_after_merge: true,
force_merge: false,
head_commit_id: $head_sha,
merge_when_checks_succeed: true
}')
TEMP_FILE=$(mktemp)
HTTP_CODE=$(curl -s -o "$TEMP_FILE" -w "%{http_code}" -X POST "https://git.hangman-lab.top/api/v1/repos/${OWNER}/${REPO_NAME}/pulls/${pr_index}/merge" \
-H 'accept: application/json' \
-H 'Content-Type: application/json' \
-H "Authorization: token ${token}" \
-d "$json")
RESPONSE=$(cat "$TEMP_FILE")
rm -f "$TEMP_FILE"
case "$HTTP_CODE" in
200)
MERGED=$(echo "$RESPONSE" | jq -r '.merged')
if [[ "$MERGED" == "true" ]]; then
echo "merge success"
else
echo "merge failed"
echo "$RESPONSE" | jq '.'
exit 1
fi
;;
409)
ERROR_MSG=$(echo "$RESPONSE" | jq -r '.message // "Conflicting changes"')
echo "Error [$HTTP_CODE]: $ERROR_MSG"
exit 1
;;
404)
ERROR_MSG=$(echo "$RESPONSE" | jq -r '.message // "Not found"')
echo "Error [$HTTP_CODE]: $ERROR_MSG"
exit 1
;;
423)
ERROR_MSG=$(echo "$RESPONSE" | jq -r '.message // "Repository is archived"')
echo "Error [$HTTP_CODE]: $ERROR_MSG"
exit 1
;;
405)
echo "merge failed check the pr status"
exit 1
;;
*)
if echo "$RESPONSE" | jq -e '.message' >/dev/null 2>&1; then
ERROR_MSG=$(echo "$RESPONSE" | jq -r '.message')
echo "Error [$HTTP_CODE]: $ERROR_MSG"
else
echo "Error [$HTTP_CODE]: Unknown error"
fi
exit 1
;;
esac
}
# Usage
usage() {
echo "Usage: $0 <command> [options]"
echo ""
echo "Commands:"
echo " create <repo-local-path> <head-branch> <base-branch> [pr-title] [pr-body]"
echo " Create a pull request"
echo " list <repo-local-path> List pull requests"
echo " commits <repo-local-path> <pr-index> List commits in a PR"
echo " merge <repo-local-path> <pr-index> <do> [commit-id] [title] [message]"
echo " Merge a pull request"
echo " show <repo-local-path> <pr-index> Show PR details"
echo ""
echo " <do> can be: merge, squash, rebase, manually-merged"
exit 2
}
# Main
if [[ $# -lt 2 ]]; then
usage
fi
COMMAND="$1"
shift
case "$COMMAND" in
create)
if [[ $# -lt 3 ]]; then
echo "Error: create requires <repo-local-path> <head-branch> <base-branch>"
exit 2
fi
cmd_create "$@"
;;
list)
if [[ $# -lt 1 ]]; then
echo "Error: list requires <repo-local-path>"
exit 2
fi
cmd_list "$@"
;;
commits)
if [[ $# -lt 2 ]]; then
echo "Error: commits requires <repo-local-path> <pr-index>"
exit 2
fi
cmd_commits "$@"
;;
merge)
if [[ $# -lt 3 ]]; then
echo "Error: merge requires <repo-local-path> <pr-index> <do>"
exit 2
fi
cmd_merge "$@"
;;
show)
if [[ $# -lt 2 ]]; then
echo "Error: show requires <repo-local-path> <pr-index>"
exit 2
fi
cmd_show "$@"
;;
*)
echo "Error: Unknown command: $COMMAND"
usage
;;
esac