From 5ba51f4f48a5307ac4bc5435ecabfb4919b47c45 Mon Sep 17 00:00:00 2001 From: Florian Zschocke Date: Thu, 16 Dec 2021 23:46:28 +0100 Subject: [PATCH] build: Allow for updating an existing draft release When creating a release draft and uploading assets, provision for the case that a draft release already exists. In that case, instead of creating a release, the existing release (changelog) is edited and the assets are deleted before the new built ones are uploaded. This commit also introduces the `${gh.repo}` property in build.xml, so that the Github project could be chosen dynamically. Not really needed, to be honest, but, yeah, whatever. --- .github/ok.sh | 348 +++++++++++++++++++++++++++++++++++++++++++++++--- build.xml | 48 +++++-- 2 files changed, 366 insertions(+), 30 deletions(-) diff --git a/.github/ok.sh b/.github/ok.sh index 6ac429d5..6c541c6b 100755 --- a/.github/ok.sh +++ b/.github/ok.sh @@ -52,8 +52,11 @@ # * OK_SH_MARKDOWN=${OK_SH_MARKDOWN} # Output some text in Markdown format. -export NAME=$(basename "$0") -export VERSION='0.5.1' +# shellcheck disable=SC2039,SC2220 + +NAME=$(basename "$0") +export NAME +export VERSION='0.7.0' export OK_SH_URL=${OK_SH_URL:-'https://api.github.com'} export OK_SH_ACCEPT=${OK_SH_ACCEPT:-'application/vnd.github.v3+json'} @@ -205,8 +208,8 @@ __main() { local OPTARG local OPTIND local quiet=0 - local temp_dir="${TMPDIR-/tmp}/${NAME}.${$}.$(awk \ - 'BEGIN {srand(); printf "%d\n", rand() * 10^10}')" + local temp_dir + temp_dir="${TMPDIR-/tmp}/${NAME}.${$}.$(awk 'BEGIN {srand(); printf "%d\n", rand() * 10^10}')" local summary_fifo="${temp_dir}/oksh_summary.fifo" # shellcheck disable=SC2154 @@ -403,8 +406,11 @@ _format_json() { function isnum(x){ return (x == x + 0) } function isnull(x){ return (x == "null" ) } function isbool(x){ if (x == "true" || x == "false") return 1 } - function isnested(x) { if (substr(x, 0, 1) == "[" \ - || substr(x, 0, 1) == "{") return 1 } + function isnested(x) { + if (substr(x, 0, 1) == "[" || substr(x, 0, 1) == "{") { + return 1 + } + } function castOrQuote(val) { if (!isbool(val) && !isnum(val) && !isnull(val) && !isnested(val)) { sub(/^('\''|")/, "", val) # Remove surrounding quotes @@ -509,8 +515,7 @@ _filter_json() { return fi - "${OK_SH_JQ_BIN}" -c -r "${_filter}" - [ $? -eq 0 ] || printf 'jq parse error; invalid JSON.\n' 1>&2 + "${OK_SH_JQ_BIN}" -c -r "${_filter}" || printf 'jq parse error; invalid JSON.\n' 1>&2 } _get_mime_type() { @@ -625,7 +630,8 @@ _opts_qs() { # _opts_qs "$@" # _get "/some/path${qs}" - local querystring=$(_format_urlencode "$@") + local querystring + querystring=$(_format_urlencode "$@") qs="${querystring:+?$querystring}" } @@ -784,36 +790,39 @@ _response() { fi done - headers="http_version: ${http_version} -status_code: ${status_code} -status_text: ${status_text} + headers="HTTP_VERSION: ${http_version} +STATUS_CODE: ${status_code} +STATUS_TEXT: ${status_text} " while IFS=": " read -r hdr val; do # Headers stop at the first blank line. [ "$hdr" = "$crlf" ] && break val="${val%${crlf}}" + # Headers are case insensitive + hdr="$(printf '%s' "$hdr" | awk '{print toupper($0)}')" + # Process each header; reformat some to work better with sh tools. case "$hdr" in # Update the GitHub rate limit trackers. - X-RateLimit-Remaining) + X-RATELIMIT-REMAINING) printf 'GitHub remaining requests: %s\n' "$val" 1>&$LSUMMARY ;; - X-RateLimit-Reset) + X-RATELIMIT-RESET) awk -v gh_reset="$val" 'BEGIN { srand(); curtime = srand() print "GitHub seconds to reset: " gh_reset - curtime }' 1>&$LSUMMARY ;; # Remove quotes from the etag header. - ETag) val="${val#\"}"; val="${val%\"}" ;; + ETAG) val="${val#\"}"; val="${val%\"}" ;; # Split the URLs in the Link header into separate pseudo-headers. - Link) headers="${headers}$(printf '%s' "$val" | awk ' + LINK) headers="${headers}$(printf '%s' "$val" | awk ' BEGIN { RS=", "; FS="; "; OFS=": " } { sub(/^rel="/, "", $2); sub(/"$/, "", $2) sub(/^ *$/, "", $1) - print "Link_" $2, $1 + print "LINK_" toupper($2), $1 }') " # need trailing newline ;; @@ -827,6 +836,7 @@ status_text: ${status_text} # Output requested headers in deterministic order. for arg in "$@"; do _log debug "Outputting requested header '${arg}'." + arg="$(printf '%s' "$arg" | awk '{print toupper($0)}')" output=$(printf '%s' "$headers" | while IFS=": " read -r hdr val; do [ "$hdr" = "$arg" ] && printf '%s' "$val" done) @@ -1125,12 +1135,68 @@ org_members() { local _filter='.[] | "\(.login)\t\(.id)"' # A jq filter to apply to the return data. + local qs + shift 1 _opts_filter "$@" + _opts_qs "$@" - _get "/orgs/${org}/members" \ - | _filter_json "${_filter}" + _get "/orgs/${org}/members${qs}" | _filter_json "${_filter}" +} + +org_collaborators() { + # List organization outside collaborators + # + # Usage: + # + # org_collaborators org + # + # Positional arguments + # + local org="${1:?Org name required.}" + # Organization GitHub login or id. + # + # Keyword arguments + # + local _filter='.[] | "\(.login)\t\(.id)"' + # A jq filter to apply to the return data. + + local qs + + shift 1 + + _opts_filter "$@" + _opts_qs "$@" + + _get "/orgs/${org}/outside_collaborators${qs}" | _filter_json "${_filter}" +} + +org_auditlog() { + # Interact with the Github Audit Log + # + # Usage: + # + # org_auditlog org + # + # Positional arguments + # + local org="${1:?Org name required.}" + # Organization GitHub login or id. + # + # Keyword arguments + # + local _filter='.[] | "\(.actor)\t\(.action)"' + # A jq filter to apply to the return data. + + local qs + + shift 1 + + _opts_filter "$@" + _opts_qs "$@" + + _get "/orgs/${org}/audit-log${qs}" | _filter_json "${_filter}" } team_members() { @@ -1184,8 +1250,10 @@ list_repos() { # * `sort` # * `type` + # User is optional; is this a keyword arg? + case "$user" in *=*) user='' ;; esac + if [ -n "$user" ]; then shift 1; fi - shift 1 local qs _opts_filter "$@" @@ -1239,6 +1307,47 @@ list_branches() { _get "${url}${qs}" | _filter_json "${_filter}" } +list_commits() { + # List commits of a specified repository. + # ( https://developer.github.com/v3/repos/commits/#list-commits-on-a-repository ) + # + # Usage: + # + # list_commits user repo + # + # Positional arguments + # + # GitHub user login or id for which to list branches + # Name of the repo for which to list branches + # + + local user="${1:?User name required.}" + local repo="${2:?Repo name required.}" + shift 2 + + # A jq filter to apply to the return data. + # + + local _filter='.[] | "\(.sha) \(.author.login) \(.commit.author.email) \(.committer.login) \(.commit.committer.email)"' + + # Querystring arguments may also be passed as keyword arguments: + # + # * `sha` + # * `path` + # * `author` + # * `since` Only commits after this date will be returned. This is a timestamp in ISO 8601 format: YYYY-MM-DDTHH:MM:SSZ. + # * `until` + + local qs + + _opts_filter "$@" + _opts_qs "$@" + + url="/repos/${user}/${repo}/commits" + + _get "${url}${qs}" | _filter_json "${_filter}" +} + list_contributors() { # List contributors to the specified repository, sorted by the number of commits per contributor in descending order. # ( https://developer.github.com/v3/repos/#list-contributors ) @@ -1541,6 +1650,7 @@ create_repo() { url='/user/repos' fi + export OK_SH_ACCEPT="application/vnd.github.nebula-preview+json" _format_json "name=${name}" "$@" | _post "$url" | _filter_json "${_filter}" } @@ -1888,6 +1998,54 @@ upload_asset() { | _filter_json "$_filter" } +delete_asset() { + # Delete a release asset + # + # https://docs.github.com/en/rest/reference/releases#delete-a-release-asset + # + # Usage: + # + # delete_asset user repo 51955388 + # + # Example of deleting release assets: + # + # ok.sh release_assets \ + # _filter='.[] | .id' \ + # | xargs -L1 ./ok.sh delete_asset "$myuser" "$myrepo" + # + # Example of the multi-step process for grabbing the release ID for + # a specific version, then grabbing the release asset IDs, and then + # deleting all the release assets (whew!): + # + # username='myuser' + # repo='myrepo' + # release_tag='v1.2.3' + # ok.sh list_releases "$myuser" "$myrepo" \ + # | awk -F'\t' -v tag="$release_tag" '$2 == tag { print $3 }' \ + # | xargs -I{} ./ok.sh release_assets "$myuser" "$myrepo" {} \ + # _filter='.[] | .id' \ + # | xargs -L1 ./ok.sh -y delete_asset "$myuser" "$myrepo" + # + # Positional arguments + # + local owner="${1:?Owner name required.}" + # A GitHub user or organization. + local repo="${2:?Repo name required.}" + # A GitHub repository. + local asset_id="${3:?Release asset ID required.}" + # The unique ID of the release asset; see release_assets. + + shift 3 + + local confirm + + _get_confirm 'This will permanently delete a release asset. Continue?' + [ "$confirm" -eq 1 ] || exit 0 + + _delete "/repos/${owner}/${repo}/releases/assets/${asset_id}" + exit $? +} + # ### Issues # Create, update, edit, delete, list issues and milestones. @@ -1969,6 +2127,47 @@ create_milestone() { | _filter_json "$_filter" } +list_issue_comments() { + # List comments of a specified issue. + # ( https://developer.github.com/v3/issues/comments/#list-issue-comments ) + # + # Usage: + # + # list_issue_comments someuser/somerepo number + # + # Positional arguments + # + # GitHub owner login or id for which to list branches + # Name of the repo for which to list branches + # Issue number + # + local repo="${1:?Repo name required.}" + local number="${2:?Issue number is required.}" + shift 2 + + local _follow_next + # Automatically look for a 'Links' header and follow any 'next' URLs. + local _follow_next_limit + # Maximum number of 'next' URLs to follow before stopping. + local _filter='.[] | "\(.body)"' + # A jq filter to apply to the return data. + + _opts_pagination "$@" + + # A jq filter to apply to the return data. + # + # Querystring arguments may also be passed as keyword arguments: + # + # * `direction` + # * `sort` + # * `since` + local qs + _opts_filter "$@" + _opts_qs "$@" + url="/repos/${repo}/issues/${number}/comments" + _get "${url}${qs}" | _filter_json "${_filter}" +} + add_comment() { # Add a comment to an issue # @@ -1998,6 +2197,48 @@ add_comment() { | _filter_json "${_filter}" } +list_commit_comments() { + # List comments of a specified commit. + # ( https://developer.github.com/v3/repos/comments/#list-commit-comments ) + # + # Usage: + # + # list_commit_comments someuser/somerepo sha + # + # Positional arguments + # + # GitHub owner login or id for which to list branches + # Name of the repo for which to list branches + # Commit SHA + # + local repo="${1:?Repo name required.}" + local sha="${2:?Commit SHA is required.}" + shift 2 + + local _follow_next + # Automatically look for a 'Links' header and follow any 'next' URLs. + local _follow_next_limit + # Maximum number of 'next' URLs to follow before stopping. + local _filter='.[] | "\(.body)"' + # A jq filter to apply to the return data. + + _opts_pagination "$@" + + # A jq filter to apply to the return data. + # + # Querystring arguments may also be passed as keyword arguments: + # + # * `direction` + # * `sort` + # * `since` + local qs + _opts_filter "$@" + _opts_qs "$@" + url="/repos/${repo}/commits/${sha}/comments" + _get "${url}${qs}" | _filter_json "${_filter}" +} + + add_commit_comment() { # Add a comment to a commit # @@ -2227,6 +2468,49 @@ org_issues() { _get "/orgs/${org}/issues${qs}" | _filter_json "$_filter" } +list_starred() { + # List starred repositories + # + # Usage: + # + # list_starred + # list_starred user + # + # Positional arguments + # + local user="$1" + # Optional GitHub user login or id for which to list the starred repos. + # + # Keyword arguments + # + local _filter='.[] | "\(.name)\t\(.html_url)"' + # A jq filter to apply to the return data. + # + # Querystring arguments may also be passed as keyword arguments: + # + # * `direction` + # * `per_page` + # * `sort` + # * `type` + + # User is optional; is this a keyword arg? + case "$user" in *=*) user='' ;; esac + if [ -n "$user" ]; then shift 1; fi + + local qs + + _opts_filter "$@" + _opts_qs "$@" + + if [ -n "$user" ] ; then + url="/users/${user}/starred" + else + url='/user/starred' + fi + + _get "${url}${qs}" | _filter_json "${_filter}" +} + list_my_orgs() { # List your organizations # @@ -2277,6 +2561,30 @@ list_orgs() { _get "/organizations" | _filter_json "$_filter" } +list_users() { + # List all users + # + # Usage: + # + # list_users + # + # Keyword arguments + # + local _follow_next + # Automatically look for a 'Links' header and follow any 'next' URLs. + local _follow_next_limit + # Maximum number of 'next' URLs to follow before stopping. + local _filter='.[] | "\(.login)\t\(.id)"' + # A jq filter to apply to the return data. + + local qs + + _opts_pagination "$@" + _opts_filter "$@" + _opts_qs "$@" + _get "/users" | _filter_json "$_filter" +} + labels() { # List available labels for a repository # diff --git a/build.xml b/build.xml index 6f5de7f1..d15cf305 100644 --- a/build.xml +++ b/build.xml @@ -36,6 +36,8 @@ + +