#!/usr/bin/env zsh # Mock curl function function curl() { echo "MOCK_CURL_CALL: $*" >&2 # Simulate a successful response for GET requests, especially for issue data if [[ "$1" == "-s" && "$3" == "GET" ]]; then if [[ "$6" == *"issues/"* ]]; then # Simulate issue data for update_task_status echo '{"labels": [{"name": "status::pending"}, {"name": "type::development"}], "id": 123}' else echo '[]' # Empty array for find_tasks fi elif [[ "$1" == "-s" && "$3" == "POST" && "$6" == *"pulls/"* ]]; then echo '{"merged": true}' # Simulate successful PR merge else echo '{}' # Generic successful response for other POST/PATCH/DELETE fi } #!/usr/bin/env zsh # [PACKAGE: 'homebox_lens'] # [FILE: 'gitea-client.zsh'] # [SEMANTICS] # [ENTITY: 'File'('gitea-client.zsh')] # [ENTITY: 'Function'('api_request')] # [ENTITY: 'Function'('find_tasks')] # [ENTITY: 'Function'('update_task_status')] # [ENTITY: 'Function'('create_pr')] # [ENTITY: 'Function'('create_task')] # [ENTITY: 'Function'('add_comment')] # [ENTITY: 'Function'('merge_and_complete')] # [ENTITY: 'Function'('return_to_dev')] # [ENTITY: 'EntryPoint'('main_dispatch')] # [ENTITY: 'Configuration'('GITEA_URL')] # [ENTITY: 'Configuration'('GITEA_TOKEN')] # [ENTITY: 'Configuration'('GITEA_OWNER')] # [ENTITY: 'Configuration'('GITEA_REPO')] # [ENTITY: 'ExternalCommand'('jq')] # [ENTITY: 'ExternalCommand'('curl')] # [RELATION: 'File'('gitea-client.zsh')] -> [DEPENDS_ON] -> ['ExternalCommand'('jq')] # [RELATION: 'File'('gitea-client.zsh')] -> [DEPENDS_ON] -> ['ExternalCommand'('curl')] # [RELATION: 'Function'('api_request')] -> [DEPENDS_ON] -> ['ExternalCommand'('curl')] # [RELATION: 'Function'('api_request')] -> [READS_FROM] -> ['Configuration'('GITEA_URL')] # [RELATION: 'Function'('api_request')] -> [READS_FROM] -> ['Configuration'('GITEA_TOKEN')] # [RELATION: 'Function'('find_tasks')] -> [CALLS] -> ['Function'('api_request')] # [RELATION: 'Function'('update_task_status')] -> [CALLS] -> ['Function'('api_request')] # [RELATION: 'Function'('update_task_status')] -> [DEPENDS_ON] -> ['ExternalCommand'('jq')] # [RELATION: 'Function'('create_pr')] -> [CALLS] -> ['Function'('api_request')] # [RELATION: 'Function'('create_pr')] -> [DEPENDS_ON] -> ['ExternalCommand'('jq')] # [RELATION: 'Function'('create_task')] -> [CALLS] -> ['Function'('api_request')] # [RELATION: 'Function'('create_task')] -> [DEPENDS_ON] -> ['ExternalCommand'('jq')] # [RELATION: 'Function'('add_comment')] -> [CALLS] -> ['Function'('api_request')] # [RELATION: 'Function'('add_comment')] -> [DEPENDS_ON] -> ['ExternalCommand'('jq')] # [RELATION: 'Function'('merge_and_complete')] -> [CALLS] -> ['Function'('api_request')] # [RELATION: 'Function'('merge_and_complete')] -> [DEPENDS_ON] -> ['ExternalCommand'('jq')] # [RELATION: 'Function'('return_to_dev')] -> [CALLS] -> ['Function'('api_request')] # [RELATION: 'Function'('return_to_dev')] -> [DEPENDS_ON] -> ['ExternalCommand'('jq')] # [RELATION: 'EntryPoint'('main_dispatch')] -> [CALLS] -> ['Function'('find_tasks')] # [RELATION: 'EntryPoint'('main_dispatch')] -> [CALLS] -> ['Function'('update_task_status')] # [RELATION: 'EntryPoint'('main_dispatch')] -> [CALLS] -> ['Function'('create_pr')] # [RELATION: 'EntryPoint'('main_dispatch')] -> [CALLS] -> ['Function'('create_task')] # [RELATION: 'EntryPoint'('main_dispatch')] -> [CALLS] -> ['Function'('add_comment')] # [RELATION: 'EntryPoint'('main_dispatch')] -> [CALLS] -> ['Function'('merge_and_complete')] # [RELATION: 'EntryPoint'('main_dispatch')] -> [CALLS] -> ['Function'('return_to_dev')] # [END_SEMANTICS] set -x # [DEPENDENCIES] # Gitea Client Script # Version: 1.0 if ! command -v jq &> /dev/null; then echo "jq could not be found. Please install jq to use this script." exit 1 fi # [END_DEPENDENCIES] # [CONFIGURATION] # IMPORTANT: Replace with your Gitea URL, API Token, repository owner and repository name. # You can also set these as environment variables: GITEA_URL, GITEA_TOKEN, GITEA_OWNER, GITEA_REPO : ${GITEA_URL:="https://gitea.bebesh.ru"} : ${GITEA_TOKEN:="c6fb6d73a18b2b4ddf94b67f2da6b6bb832164ce"} : ${GITEA_OWNER:="busya"} : ${GITEA_REPO:="homebox_lens"} # [END_CONFIGURATION] # [HELPERS] # [ENTITY: 'Function'('api_request')] # [CONTRACT] # Generic function to make requests to the Gitea API. # This is the central communication point with the Gitea instance. # # @param $1: method - The HTTP method (GET, POST, PATCH). # @param $2: endpoint - The API endpoint (e.g., "repos/owner/repo/issues"). # @param $3: json_data - The JSON payload for POST/PATCH requests. # # @stdout The body of the API response on success. # @stderr Error messages on failure. # # @returns 0 on success, 1 on unsupported method. Curl exit code on curl failure. # [/CONTRACT] function api_request() { local method="$1" local endpoint="$2" local data="$3" local url="$GITEA_URL/api/v1/$endpoint" local -a curl_opts curl_opts=("-s" "-H" "Authorization: token $GITEA_TOKEN" "-H" "Content-Type: application/json") case "$method" in GET) curl "${curl_opts[@]}" "$url" ;; POST|PATCH) curl "${curl_opts[@]}" -X "$method" -d @- "$url" <<< "$data" ;; *) echo "Unsupported HTTP method: $method" >&2 return 1 ;; esac } # [END_ENTITY: 'Function'('api_request')] # [END_HELPERS] # [COMMANDS] # [ENTITY: 'Function'('find_tasks')] # [CONTRACT] # Finds open issues with a specific type and 'status::pending' label. # # @param --type: The label to filter issues by (e.g., "type::development"). # # @stdout A JSON array of Gitea issues matching the criteria. # [/CONTRACT] function find_tasks() { local type="" # Parsing arguments like --type "type::development" while [[ $# -gt 0 ]]; do case "$1" in --type) type="$2"; shift 2 ;; *) echo "Unknown parameter: $1"; return 1 ;; esac done # In Gitea, we can filter issues by labels. # The protocol uses "type::development" and "status::pending" # We will treat these as labels. local labels="type::development,status::pending" if [[ -n "$type" ]]; then labels="status::pending,${type}" fi api_request "GET" "repos/$GITEA_OWNER/$GITEA_REPO/issues?labels=$labels&state=open" } # [END_ENTITY: 'Function'('find_tasks')] # [ENTITY: 'Function'('update_task_status')] # [CONTRACT] # Atomically changes the status of a task by removing an old status label and adding a new one. # # @param --issue-id: The ID of the issue to update. # @param --old: The old status label to remove (e.g., "status::pending"). # @param --new: The new status label to add (e.g., "status::in-progress"). # # @stdout The JSON representation of the updated issue. # [/CONTRACT] function update_task_status() { local issue_id="" local old_status="" local new_status="" # Parsing arguments like --issue-id 123 while [[ $# -gt 0 ]]; do case "$1" in --issue-id) issue_id="$2"; shift 2 ;; --old) old_status="$2"; shift 2 ;; --new) new_status="$2"; shift 2 ;; *) echo "Unknown parameter: $1"; return 1 ;; esac done if [[ -z "$issue_id" || -z "$old_status" || -z "$new_status" ]]; then echo "Usage: update-task-status --issue-id --old --new " >&2 return 1 fi # In Gitea, we manage status with labels. # This function will remove the old status label and add the new one. # First, get existing labels for the issue. local issue_data=$(api_request "GET" "repos/$GITEA_OWNER/$GITEA_REPO/issues/$issue_id") if [[ -z "$issue_data" ]]; then echo "Error: Could not retrieve issue data for issue ID $issue_id. The issue may not exist or there might be a problem with the Gitea API or your token." >&2 return 1 fi local existing_labels=$(echo "$issue_data" | jq -r '.labels | .[].name') local -a new_labels for label in ${=existing_labels}; do if [[ "$label" != "$old_status" ]]; then new_labels+=($label) fi done new_labels+=($new_status) local new_labels_json=$(printf '%s\n' "${new_labels[@]}" | jq -R . | jq -s .) local data=$(jq -n --argjson labels "$new_labels_json" '{labels: $labels}') api_request "PATCH" "repos/$GITEA_OWNER/$GITEA_REPO/issues/$issue_id" "$data" } # [END_ENTITY: 'Function'('update_task_status')] # [ENTITY: 'Function'('create_pr')] # [CONTRACT] # Creates a new Pull Request in the repository. # # @param --title: The title of the pull request. # @param --head: The source branch for the pull request. # @param --body: (Optional) The body/description of the pull request. # @param --base: (Optional) The target branch. Defaults to 'main'. # # @stdout The JSON representation of the newly created pull request. # [/CONTRACT] function create_pr() { local title="" local body="" local head_branch="" local base_branch="main" # Assuming 'main' is the default base while [[ $# -gt 0 ]]; do case "$1" in --title) title="$2"; shift 2 ;; --body) body="$2"; shift 2 ;; --head) head_branch="$2"; shift 2 ;; --base) base_branch="$2"; shift 2 ;; *) echo "Unknown parameter: $1"; return 1 ;; esac done if [[ -z "$title" || -z "$head_branch" ]]; then echo "Usage: create-pr --title --head <head_branch> [--body <body>] [--base <base_branch>]" >&2 return 1 fi local data=$(jq -n \ --arg title "$title" \ --arg body "$body" \ --arg head "$head_branch" \ --arg base "$base_branch" \ '{title: $title, body: $body, head: $head, base: $base}') api_request "POST" "repos/$GITEA_OWNER/$GITEA_REPO/pulls" "$data" } # [END_ENTITY: 'Function'('create_pr')] # [ENTITY: 'Function'('create_task')] # [CONTRACT] # Creates a new issue (task) in the repository. # # @param --title: The title of the issue. # @param --body: (Optional) The body/description of the issue. # @param --assignee: (Optional) Comma-separated list of usernames to assign. # @param --labels: (Optional) Comma-separated list of labels to add. # # @stdout The JSON representation of the newly created issue. # [/CONTRACT] function create_task() { local title="" local body="" local assignee="" local labels="" while [[ $# -gt 0 ]]; do case "$1" in --title) title="$2"; shift 2 ;; --body) body="$2"; shift 2 ;; --assignee) assignee="$2"; shift 2 ;; --labels) labels="$2"; shift 2 ;; *) echo "Unknown parameter: $1"; return 1 ;; esac done if [[ -z "$title" ]]; then echo "Usage: create-task --title <title> [--body <body>] [--assignee <assignee>] [--labels <labels>]" >&2 return 1 fi local labels_json="[]" if [[ -n "$labels" ]]; then # Split by comma local -a labels_arr IFS=',' read -rA labels_arr <<< "$labels" labels_json=$(printf '%s\n' "${labels_arr[@]}" | jq -R . | jq -s .) fi local assignees_json="[]" if [[ -n "$assignee" ]]; then # Split by comma local -a assignees_arr IFS=',' read -rA assignees_arr <<< "$assignee" assignees_json=$(printf '%s\n' "${assignees_arr[@]}" | jq -R . | jq -s .) fi local data=$(jq -n \ --arg title "$title" \ --arg body "$body" \ --argjson assignees "$assignees_json" \ --argjson labels "$labels_json" \ '{title: $title, body: $body, assignees: $assignees, labels: $labels}') api_request "POST" "repos/$GITEA_OWNER/$GITEA_REPO/issues" "$data" } # [END_ENTITY: 'Function'('create_task')] # [ENTITY: 'Function'('add_comment')] # [CONTRACT] # Adds a comment to an existing issue or pull request. # # @param --issue-id: The ID of the issue/PR to comment on. # @param --body: The content of the comment. # # @stdout The JSON representation of the newly created comment. # [/CONTRACT] function add_comment() { local issue_id="" local comment_body="" while [[ $# -gt 0 ]]; do case "$1" in --issue-id) issue_id="$2"; shift 2 ;; --body) comment_body="$2"; shift 2 ;; *) echo "Unknown parameter: $1"; return 1 ;; esac done if [[ -z "$issue_id" || -z "$comment_body" ]]; then echo "Usage: add-comment --issue-id <id> --body <comment_body>" >&2 return 1 fi local data=$(jq -n --arg body "$comment_body" '{body: $body}') api_request "POST" "repos/$GITEA_OWNER/$GITEA_REPO/issues/$issue_id/comments" "$data" } # [END_ENTITY: 'Function'('add_comment')] # [ENTITY: 'Function'('merge_and_complete')] # [CONTRACT] # Atomic operation to merge a PR, delete its source branch, and close the associated issue. # # @param --issue-id: The ID of the issue to close. # @param --pr-id: The ID of the pull request to merge. # @param --branch: The name of the source branch to delete after merging. # # @stderr Log messages indicating the progress of each step. # @returns 1 on failure to merge or close the issue. # [/CONTRACT] function merge_and_complete() { local issue_id="" local pr_id="" local branch_to_delete="" while [[ $# -gt 0 ]]; do case "$1" in --issue-id) issue_id="$2"; shift 2 ;; --pr-id) pr_id="$2"; shift 2 ;; --branch) branch_to_delete="$2"; shift 2 ;; *) echo "Unknown parameter: $1"; return 1 ;; esac done if [[ -z "$issue_id" || -z "$pr_id" || -z "$branch_to_delete" ]]; then echo "Usage: merge-and-complete --issue-id <issue_id> --pr-id <pr_id> --branch <branch_to_delete>" >&2 return 1 fi # 1. Merge the PR echo "Attempting to merge PR #$pr_id..." local merge_data=$(jq -n '{Do: "merge"} ) # Gitea API expects a MergePullRequestOption object local merge_response=$(api_request "POST" "repos/$GITEA_OWNER/$GITEA_REPO/pulls/$pr_id/merge" "$merge_data") if echo "$merge_response" | jq -e '.merged' > /dev/null; then echo "PR #$pr_id merged successfully." else echo "Error merging PR #$pr_id: $merge_response" >&2 return 1 } # 2. Delete the branch echo "Attempting to delete branch $branch_to_delete..." local delete_branch_response=$(api_request "DELETE" "repos/$GITEA_OWNER/$GITEA_REPO/branches/$branch_to_delete") if [[ -z "$delete_branch_response" ]]; then # Gitea API returns empty on successful delete echo "Branch $branch_to_delete deleted successfully." else echo "Error deleting branch $branch_to_delete: $delete_branch_response" >&2 # Do not return 1 here, as PR might be merged even if branch deletion fails } # 3. Close the associated issue echo "Attempting to close issue #$issue_id..." local close_issue_data=$(jq -n '{state: "closed"}') api_request "PATCH" "repos/$GITEA_OWNER/$GITEA_REPO/issues/$issue_id" "$close_issue_data" } # [END_ENTITY: 'Function'('merge_and_complete')] # [ENTITY: 'Function'('return_to_dev')] # [CONTRACT] # Atomically changes the status of a task by removing an old status label and adding a new one. # # @param --issue-id: The ID of the issue to update. # @param --pr-id: The ID of the pull request to update. # @param --report: The defect report text. # # @stdout The JSON representation of the updated issue. # [/CONTRACT] function return_to_dev() { local issue_id="" local pr_id="" local report_text="" while [[ $# -gt 0 ]]; do case "$1" in --issue-id) issue_id="$2"; shift 2 ;; --pr-id) pr_id="$2"; shift 2 ;; --report) report_text="$2"; shift 2 ;; *) echo "Unknown parameter: $1"; return 1 ;; esac done if [[ -z "$issue_id" || -z "$pr_id" || -z "$report_text" ]]; then echo "Usage: return-to-dev --issue-id <issue_id> --pr-id <pr_id> --report <report_text>" >&2 return 1 fi echo "Attempting to return PR #$pr_id and issue #$issue_id to developer with report: $report_text" # 1. Add comment to PR/Issue add_comment --issue-id "$pr_id" --body "Defect Report: $report_text" add_comment --issue-id "$issue_id" --body "Defect Report: $report_text" # 2. Reopen issue and change status to 'in-progress' for developer # First, get existing labels for the issue. local issue_data=$(api_request "GET" "repos/$GITEA_OWNER/$GITEA_REPO/issues/$issue_id") if [[ -z "$issue_data" ]]; then echo "Error: Could not retrieve issue data for issue ID $issue_id. The issue may not exist or there might be a problem with the Gitea API or your token." >&2 return 1 fi local existing_labels=$(echo "$issue_data" | jq -r '.labels | .[].name') local -a new_labels for label in ${=existing_labels}; do if [[ "$label" == "status::completed" || "$label" == "status::in-review" ]]; then continue # Remove completed/in-review status } new_labels+=($label) done new_labels+=("status::in-progress") # Add in-progress status local new_labels_json=$(printf '%s\n' "${new_labels[@]}" | jq -R . | jq -s .) local data=$(jq -n --argjson labels "$new_labels_json" '{state: "open", labels: $labels}') api_request "PATCH" "repos/$GITEA_OWNER/$GITEA_REPO/issues/$issue_id" "$data" # 3. Close PR (or leave open for developer to fix and re-push) - for now, just comment # Gitea API doesn't have a direct "reject PR" or "return to dev" state. # We'll just comment and update the issue. echo "PR #$pr_id commented. Issue #$issue_id status updated to in-progress." } # [END_ENTITY: 'Function'('return_to_dev')] # Test calls for each function echo "--- Testing find_tasks ---" find_tasks --type "type::development" find_tasks echo "--- Testing update_task_status ---" update_task_status --issue-id 123 --old "status::pending" --new "status::in-progress" echo "--- Testing create_pr ---" create_pr --title "Test PR" --head "feature/test-branch" --body "This is a test pull request." echo "--- Testing create_task ---" create_task --title "Test Task" --body "This is a test task body." --assignee "busya" --labels "type::test,status::pending" create_task --title "Another Test Task" echo "--- Testing add_comment ---" add_comment --issue-id 456 --body "This is a test comment." echo "--- Testing merge_and_complete ---" merge_and_complete --issue-id 123 --pr-id 789 --branch "feature/test-branch" echo "--- Testing return_to_dev ---" return_to_dev --issue-id 123 --pr-id 789 --report "Found a bug in feature X." echo "--- All tests completed ---"