#!/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] # [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:="gitea-client-tests"} # <-- Убедитесь, что здесь тестовый репозиторий # [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, DELETE). # @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] # ЗАМЕНИТЕ ВСЮ ФУНКЦИЮ api_request НА ЭТУ ВЕРСИЮ function api_request() { local method="$1" local endpoint="$2" local data="$3" local url="$GITEA_URL/api/v1/$endpoint" local http_code local response_body # Создаем временный файл для хранения тела ответа local body_file=$(mktemp) local -a curl_opts # -s: silent # -w '%{http_code}': записать http-код в stdout ПОСЛЕ ответа # -o "$body_file": записать тело ответа в файл curl_opts=("-s" "-w" "%{http_code}" "-o" "$body_file" \ "-H" "Authorization: token $GITEA_TOKEN" \ "-H" "Content-Type: application/json") case "$method" in GET|DELETE) http_code=$(curl "${curl_opts[@]}" -X "$method" "$url") ;; POST|PATCH) http_code=$(curl "${curl_opts[@]}" -X "$method" -d @- "$url" <<< "$data") ;; *) echo "Unsupported HTTP method: $method" >&2 rm -f "$body_file" # Очистка перед выходом return 1 ;; esac response_body=$(<"$body_file") rm -f "$body_file" # Очистка после использования echo "DEBUG: HTTP Code: $http_code" >&2 echo "DEBUG: Response Body: $response_body" >&2 if [[ "$http_code" -ge 200 && "$http_code" -lt 300 ]]; then if [[ -z "$response_body" ]]; then echo "{""http_status"": $http_code, ""body"": ""empty""}" else echo "$response_body" fi return 0 else echo "API Error: Received HTTP status $http_code. Body: $response_body" >&2 return 1 fi } # [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 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="" 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 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." >&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" 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 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 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"}' ) # Запускаем в подоболочке, чтобы обработать возможную ошибку, если api_request вернет 1 local merge_response if ! merge_response=$(api_request "POST" "repos/$GITEA_OWNER/$GITEA_REPO/pulls/$pr_id/merge" "$merge_data"); then echo "Error merging PR #$pr_id: API request failed." >&2 echo "Response: $merge_response" >&2 return 1 fi # API на успешный мерж возвращает ПУСТОЕ тело и код 200/204. # Наша новая api_request вернет JSON-маркер. Проверяем это. if echo "$merge_response" | jq -e '.body == "empty"' > /dev/null; then echo "PR #$pr_id merged successfully." else # Если тело не пустое, это может быть тоже успех (старые версии Gitea) или ошибка if echo "$merge_response" | jq -e '.merged' > /dev/null; then echo "PR #$pr_id merged successfully (with response body)." else echo "Error merging PR #$pr_id: Unexpected API response: $merge_response" >&2 return 1 fi fi # 2. Delete the branch echo "Attempting to delete branch $branch_to_delete..." if api_request "DELETE" "repos/$GITEA_OWNER/$GITEA_REPO/branches/$branch_to_delete" > /dev/null; then echo "Branch $branch_to_delete deleted successfully." else echo "Warning: Failed to delete branch $branch_to_delete. It might have already been deleted or protected." >&2 fi # 3. Close the associated issue echo "Attempting to close issue #$issue_id..." local close_issue_data=$(jq -n '{state: "closed"}') local close_response if ! close_response=$(api_request "PATCH" "repos/$GITEA_OWNER/$GITEA_REPO/issues/$issue_id" "$close_issue_data"); then echo "Error closing issue #$issue_id: API request failed." >&2 return 1 fi if echo "$close_response" | jq -e '.state == "closed"' > /dev/null; then echo "Issue #$issue_id closed successfully." else echo "Error closing issue #$issue_id: Unexpected API response: $close_response" >&2 return 1 fi } # [END_ENTITY: 'Function'('merge_and_complete')] # [ENTITY: 'Function'('return_to_dev')] # [CONTRACT] # Returns an issue to development by adding a comment and changing its status. # It specifically changes the status from 'status::in-review' to 'status::in-progress'. # # @param --issue-id: The ID of the issue to update. # @param --comment: The comment explaining why the issue is being returned. # # @stderr Log messages indicating the progress of each step. # @returns 1 on failure to add comment or update status. # [/CONTRACT] function return_to_dev() { local issue_id="" local comment_body="" while [[ $# -gt 0 ]]; do case "$1" in --issue-id) issue_id="$2"; shift 2 ;; --comment) comment_body="$2"; shift 2 ;; *) echo "Unknown parameter: $1"; return 1 ;; esac done if [[ -z "$issue_id" || -z "$comment_body" ]]; then echo "Usage: return-to-dev --issue-id <id> --comment <comment_body>" >&2 return 1 fi # 1. Add the comment echo "Adding comment to issue #$issue_id..." local add_comment_response add_comment_response=$(add_comment --issue-id "$issue_id" --body "$comment_body") if ! echo "$add_comment_response" | jq -e '.id' > /dev/null; then echo "Error: Failed to add comment to issue #$issue_id. Response: $add_comment_response" >&2 return 1 fi # 2. Update the status echo "Updating status for issue #$issue_id..." local update_status_response update_status_response=$(update_task_status --issue-id "$issue_id" --old "status::in-review" --new "status::in-progress") if ! echo "$update_status_response" | jq -e '.id' > /dev/null; then echo "Error: Failed to update status for issue #$issue_id. Response: $update_status_response" >&2 return 1 fi echo "Issue #$issue_id returned to development." } # [END_ENTITY: 'Function'('return_to_dev')] # Здесь может быть функция main_dispatch, если она вам нужна