From 394a0cd4ffbe292fe48a8f714d1c382531733bcb Mon Sep 17 00:00:00 2001 From: Florian Zschocke Date: Sat, 11 Jan 2020 17:36:00 +0100 Subject: Run build and tests on Java 7. --- .github/workflows/ci-build.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ci-build.yml b/.github/workflows/ci-build.yml index e9961f2f..04a25ca1 100644 --- a/.github/workflows/ci-build.yml +++ b/.github/workflows/ci-build.yml @@ -62,4 +62,4 @@ jobs: moxie-0.9.4/bin/moxie -version - name: Build with Moxie - run: moxie-0.9.4/bin/moxie + run: moxie-0.9.4/bin/moxie test -- cgit v1.2.3 From 8463244e78c9d6fdfed8ebb1227b9a9494219567 Mon Sep 17 00:00:00 2001 From: Florian Zschocke Date: Sun, 26 Jan 2020 16:31:49 +0100 Subject: Change single quotes to back ticks for formatting --- releases.moxie | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/releases.moxie b/releases.moxie index db6c2dea..9776f0a9 100644 --- a/releases.moxie +++ b/releases.moxie @@ -63,8 +63,8 @@ r30: { - Encode email sender's name with UTF-8 (pr-1206) - Made Gitblit run on Java 9+ (issue-1262, issue-1294, pr-1266) - The JRE version is reported upon starting - - Add the 'ext' directory to the classpath on the command-line to start Gitblit and related programs. - - Report back that git command 'clone.bundle' is unsupported instead of simply failing + - Add the `ext` directory to the classpath on the command-line to start Gitblit and related programs. + - Report back that git command `clone.bundle` is unsupported instead of simply failing additions: - Added option to merge a ticket branch to the integration branch fast-forward or with a merge commit (pr-1142) - Added SSH key manager that retrieves keys from LDAP directory (pr-1160) -- cgit v1.2.3 From e0e23d872c625c8b5494c37b0e5b90475a3cee78 Mon Sep 17 00:00:00 2001 From: Florian Zschocke Date: Sun, 26 Jan 2020 16:47:44 +0100 Subject: Add deployment of a release to GitHub Add Ant tasks and macros to deploy binaries to GitHub, using GitHub's releases. Adds an Awk script to extract GH flavoured markdown release notes from the release.moxie file. Adds `ok.sh` to the repository so that it is readily available. This is a Bourne shell GitHub API client, used to create a release on GitHub and upload the binaries. --- .github/ok.sh | 2560 +++++++++++++++++++++++++++++++++ build.xml | 168 ++- src/site/templates/ghreleasenotes.awk | 66 + 3 files changed, 2785 insertions(+), 9 deletions(-) create mode 100755 .github/ok.sh create mode 100755 src/site/templates/ghreleasenotes.awk diff --git a/.github/ok.sh b/.github/ok.sh new file mode 100755 index 00000000..6ac429d5 --- /dev/null +++ b/.github/ok.sh @@ -0,0 +1,2560 @@ +#!/usr/bin/env sh +# # A GitHub API client library written in POSIX sh +# +# https://github.com/whiteinge/ok.sh +# BSD licensed. +# +# ## Requirements +# +# * A POSIX environment (tested against Busybox v1.19.4) +# * curl (tested against 7.32.0) +# +# ## Optional requirements +# +# * jq (tested against 1.3) +# If jq is not installed commands will output raw JSON; if jq is installed +# the output will be formatted and filtered for use with other shell tools. +# +# ## Setup +# +# Authentication credentials are read from a `$HOME/.netrc` file on UNIX +# machines or a `_netrc` file in `%HOME%` for UNIX environments under Windows. +# [Generate the token on GitHub](https://github.com/settings/tokens) under +# "Account Settings -> Applications". +# Restrict permissions on that file with `chmod 600 ~/.netrc`! +# +# machine api.github.com +# login +# password +# +# machine uploads.github.com +# login +# password +# +# Or set an environment `GITHUB_TOKEN=token` +# +# ## Configuration +# +# The following environment variables may be set to customize ${NAME}. +# +# * OK_SH_URL=${OK_SH_URL} +# Base URL for GitHub or GitHub Enterprise. +# * OK_SH_ACCEPT=${OK_SH_ACCEPT} +# The 'Accept' header to send with each request. +# * OK_SH_JQ_BIN=${OK_SH_JQ_BIN} +# The name of the jq binary, if installed. +# * OK_SH_VERBOSE=${OK_SH_VERBOSE} +# The debug logging verbosity level. Same as the verbose flag. +# * OK_SH_RATE_LIMIT=${OK_SH_RATE_LIMIT} +# Output current GitHub rate limit information to stderr. +# * OK_SH_DESTRUCTIVE=${OK_SH_DESTRUCTIVE} +# Allow destructive operations without prompting for confirmation. +# * OK_SH_MARKDOWN=${OK_SH_MARKDOWN} +# Output some text in Markdown format. + +export NAME=$(basename "$0") +export VERSION='0.5.1' + +export OK_SH_URL=${OK_SH_URL:-'https://api.github.com'} +export OK_SH_ACCEPT=${OK_SH_ACCEPT:-'application/vnd.github.v3+json'} +export OK_SH_JQ_BIN="${OK_SH_JQ_BIN:-jq}" +export OK_SH_VERBOSE="${OK_SH_VERBOSE:-0}" +export OK_SH_RATE_LIMIT="${OK_SH_RATE_LIMIT:-0}" +export OK_SH_DESTRUCTIVE="${OK_SH_DESTRUCTIVE:-0}" +export OK_SH_MARKDOWN="${OK_SH_MARKDOWN:-0}" + +# Detect if jq is installed. +command -v "$OK_SH_JQ_BIN" 1>/dev/null 2>/dev/null +NO_JQ=$? + +# Customizable logging output. +exec 4>/dev/null +exec 5>/dev/null +exec 6>/dev/null +export LINFO=4 # Info-level log messages. +export LDEBUG=5 # Debug-level log messages. +export LSUMMARY=6 # Summary output. + +# Generate a carriage return so we can match on it. +# Using a variable because these are tough to specify in a portable way. +crlf=$(printf '\r\n') + +# ## Main +# Generic functions not necessarily specific to working with GitHub. + +# ### Help +# Functions for fetching and formatting help text. + + _cols() { + sort | awk ' + { w[NR] = $0 } + END { + cols = 3 + per_col = sprintf("%.f", NR / cols + 0.5) # Round up if decimal. + + for (i = 1; i < per_col + 1; i += 1) { + for (j = 0; j < cols; j += 1) { + printf("%-24s", w[i + per_col * j]) + } + printf("\n") + } + } + ' + } + _links() { awk '{ print "* [" $0 "](#" $0 ")" }'; } + _funcsfmt() { if [ "$OK_SH_MARKDOWN" -eq 0 ]; then _cols; else _links; fi; } + +help() { + # Output the help text for a command + # + # Usage: + # + # help commandname + # + # Positional arguments + # + local fname="$1" + # Function name to search for; if omitted searches whole file. + + # Short-circuit if only producing help for a single function. + if [ $# -gt 0 ]; then + awk -v fname="^$fname\\\(\\\) \\\{$" '$0 ~ fname, /^}/ { print }' "$0" \ + | _helptext + return + fi + + _helptext < "$0" + printf '\n' + help __main + printf '\n' + + printf '## Table of Contents\n' + printf '\n### Utility and request/response commands\n\n' + _all_funcs public=0 | _funcsfmt + printf '\n### GitHub commands\n\n' + _all_funcs private=0 | _funcsfmt + printf '\n## Commands\n\n' + + for cmd in $(_all_funcs public=0); do + printf '### %s\n\n' "$cmd" + help "$cmd" + printf '\n' + done + + for cmd in $(_all_funcs private=0); do + printf '### %s\n\n' "$cmd" + help "$cmd" + printf '\n' + done +} + +_all_funcs() { + # List all functions found in the current file in the order they appear + # + # Keyword arguments + # + local public=1 + # `0` do not output public functions. + local private=1 + # `0` do not output private functions. + + for arg in "$@"; do + case $arg in + (public=*) public="${arg#*=}";; + (private=*) private="${arg#*=}";; + esac + done + + awk -v public="$public" -v private="$private" ' + $1 !~ /^__/ && /^[a-zA-Z0-9_]+\s*\(\)/ { + sub(/\(\)$/, "", $1) + if (!public && substr($1, 1, 1) != "_") next + if (!private && substr($1, 1, 1) == "_") next + print $1 + } + ' "$0" +} + +__main() { + # ## Usage + # + # `${NAME} [] (command [, ...])` + # + # ${NAME} -h # Short, usage help text. + # ${NAME} help # All help text. Warning: long! + # ${NAME} help command # Command-specific help text. + # ${NAME} command # Run a command with and without args. + # ${NAME} command foo bar baz=Baz qux='Qux arg here' + # + # Flag | Description + # ---- | ----------- + # -V | Show version. + # -h | Show this screen. + # -j | Output raw JSON; don't process with jq. + # -q | Quiet; don't print to stdout. + # -r | Print current GitHub API rate limit to stderr. + # -v | Logging output; specify multiple times: info, debug, trace. + # -x | Enable xtrace debug logging. + # -y | Answer 'yes' to any prompts. + # + # Flags _must_ be the first argument to `${NAME}`, before `command`. + + local cmd + local ret + local opt + local OPTARG + local OPTIND + local quiet=0 + local 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 + trap ' + excode=$?; trap - EXIT; + exec 4>&- + exec 5>&- + exec 6>&- + rm -rf '"$temp_dir"' + exit $excode + ' INT TERM EXIT + + while getopts Vhjqrvxy opt; do + case $opt in + V) printf 'Version: %s\n' $VERSION + exit;; + h) help __main + printf '\nAvailable commands:\n\n' + _all_funcs private=0 | _cols + printf '\n' + exit;; + j) NO_JQ=1;; + q) quiet=1;; + r) OK_SH_RATE_LIMIT=1;; + v) OK_SH_VERBOSE=$(( OK_SH_VERBOSE + 1 ));; + x) set -x;; + y) OK_SH_DESTRUCTIVE=1;; + esac + done + shift $(( OPTIND - 1 )) + + if [ -z "$1" ] ; then + printf 'No command given. Available commands:\n\n%s\n' \ + "$(_all_funcs private=0 | _cols)" 1>&2 + exit 1 + fi + + [ $OK_SH_VERBOSE -gt 0 ] && exec 4>&2 + [ $OK_SH_VERBOSE -gt 1 ] && exec 5>&2 + if [ $quiet -eq 1 ]; then + exec 1>/dev/null 2>/dev/null + fi + + if [ "$OK_SH_RATE_LIMIT" -eq 1 ] ; then + mkdir -m 700 "$temp_dir" || { + printf 'failed to create temp_dir\n' >&2; exit 1; + } + mkfifo "$summary_fifo" + # Hold the fifo open so it will buffer input until emptied. + exec 6<>"$summary_fifo" + fi + + # Run the command. + cmd="$1" && shift + _log debug "Running command ${cmd}." + "$cmd" "$@" + ret=$? + _log debug "Command ${cmd} exited with ${?}." + + # Output any summary messages. + if [ "$OK_SH_RATE_LIMIT" -eq 1 ] ; then + cat "$summary_fifo" 1>&2 & + exec 6>&- + fi + + exit $ret +} + +_log() { + # A lightweight logging system based on file descriptors + # + # Usage: + # + # _log debug 'Starting the combobulator!' + # + # Positional arguments + # + local level="${1:?Level is required.}" + # The level for a given log message. (info or debug) + local message="${2:?Message is required.}" + # The log message. + + shift 2 + + local lname + + case "$level" in + info) lname='INFO'; level=$LINFO ;; + debug) lname='DEBUG'; level=$LDEBUG ;; + *) printf 'Invalid logging level: %s\n' "$level" ;; + esac + + printf '%s %s: %s\n' "$NAME" "$lname" "$message" 1>&$level +} + +_helptext() { + # Extract contiguous lines of comments and function params as help text + # + # Indentation will be ignored. She-bangs will be ignored. Local variable + # declarations and their default values can also be pulled in as + # documentation. Exits upon encountering the first blank line. + # + # Exported environment variables can be used for string interpolation in + # the extracted commented text. + # + # Input + # + # * (stdin) + # The text of a function body to parse. + + awk ' + NR != 1 && /^\s*#/ { + line=$0 + while(match(line, "[$]{[^}]*}")) { + var=substr(line, RSTART+2, RLENGTH -3) + gsub("[$]{"var"}", ENVIRON[var], line) + } + gsub(/^\s*#\s?/, "", line) + print line + } + /^\s*local/ { + sub(/^\s*local /, "") + sub(/\$\{/, "$", $0) + sub(/:.*}/, "", $0) + print "* `" $0 "`\n" + } + !NF { exit }' +} + +# ### Request-response +# Functions for making HTTP requests and processing HTTP responses. + +_format_json() { + # Create formatted JSON from name=value pairs + # + # Usage: + # ``` + # ok.sh _format_json foo=Foo bar=123 baz=true qux=Qux=Qux quux='Multi-line + # string' quuz=\'5.20170918\' \ + # corge="$(ok.sh _format_json grault=Grault)" \ + # garply="$(ok.sh _format_json -a waldo true 3)" + # ``` + # + # Return: + # ``` + # { + # "garply": [ + # "waldo", + # true, + # 3 + # ], + # "foo": "Foo", + # "corge": { + # "grault": "Grault" + # }, + # "baz": true, + # "qux": "Qux=Qux", + # "quux": "Multi-line\nstring", + # "quuz": "5.20170918", + # "bar": 123 + # } + # ``` + # + # Tries not to quote numbers, booleans, nulls, or nested structures. + # Note, nested structures must be quoted since the output contains spaces. + # + # The `-a` option will create an array instead of an object. This option + # must come directly after the _format_json command and before any + # operands. E.g., `_format_json -a foo bar baz`. + # + # If jq is installed it will also validate the output. + # + # Positional arguments + # + # * $1 - $9 + # + # Each positional arg must be in the format of `name=value` which will be + # added to a single, flat JSON object. + + local opt + local OPTIND + local is_array=0 + local use_env=1 + while getopts a opt; do + case $opt in + a) is_array=1; unset use_env;; + esac + done + shift $(( OPTIND - 1 )) + + _log debug "Formatting ${#} parameters as JSON." + + env -i -- ${use_env+"$@"} awk -v is_array="$is_array" ' + 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 castOrQuote(val) { + if (!isbool(val) && !isnum(val) && !isnull(val) && !isnested(val)) { + sub(/^('\''|")/, "", val) # Remove surrounding quotes + sub(/('\''|")$/, "", val) + + gsub(/"/, "\\\"", val) # Escape double-quotes. + gsub(/\n/, "\\n", val) # Replace newlines with \n text. + val = "\"" val "\"" + return val + } else { + return val + } + } + + BEGIN { + printf("%s", is_array ? "[" : "{") + + for (i = 1; i < length(ARGV); i += 1) { + arg = ARGV[i] + + if (is_array == 1) { + val = castOrQuote(arg) + printf("%s%s", sep, val) + } else { + name = substr(arg, 0, index(arg, "=") - 1) + val = castOrQuote(ENVIRON[name]) + printf("%s\"%s\": %s", sep, name, val) + } + + sep = ", " + ARGV[i] = "" + } + printf("%s", is_array ? "]" : "}") + }' "$@" +} + +_format_urlencode() { + # URL encode and join name=value pairs + # + # Usage: + # ``` + # _format_urlencode foo='Foo Foo' bar='&/Bar/' + # ``` + # + # Return: + # ``` + # foo=Foo%20Foo&bar=%3CBar%3E%26%2FBar%2F + # ``` + # + # Ignores pairs if the value begins with an underscore. + + _log debug "Formatting ${#} parameters as urlencoded" + + env -i -- "$@" awk ' + function escape(str, c, i, len, res) { + len = length(str) + res = "" + for (i = 1; i <= len; i += 1) { + c = substr(str, i, 1); + if (c ~ /[0-9A-Za-z]/) + res = res c + else + res = res "%" sprintf("%02X", ord[c]) + } + return res + } + + BEGIN { + for (i = 0; i <= 255; i += 1) ord[sprintf("%c", i)] = i; + + for (j = 1; j < length(ARGV); j += 1) { + arg = ARGV[j] + name = substr(arg, 0, index(arg, "=") - 1) + if (substr(name, 1, 1) == "_") continue + val = ENVIRON[name] + + printf("%s%s=%s", sep, name, escape(val)) + sep = "&" + ARGV[j] = "" + } + }' "$@" +} + +_filter_json() { + # Filter JSON input using jq; outputs raw JSON if jq is not installed + # + # Usage: + # + # printf '[{"foo": "One"}, {"foo": "Two"}]' | \ + # ok.sh _filter_json '.[] | "\(.foo)"' + # + # * (stdin) + # JSON input. + local _filter="$1" + # A string of jq filters to apply to the input stream. + + _log debug 'Filtering JSON.' + + if [ $NO_JQ -ne 0 ] ; then + _log debug 'Bypassing jq processing.' + cat + return + fi + + "${OK_SH_JQ_BIN}" -c -r "${_filter}" + [ $? -eq 0 ] || printf 'jq parse error; invalid JSON.\n' 1>&2 +} + +_get_mime_type() { + # Guess the mime type for a file based on the file extension + # + # Usage: + # + # local mime_type + # _get_mime_type "foo.tar"; printf 'mime is: %s' "$mime_type" + # + # Sets the global variable `mime_type` with the result. (If this function + # is called from within a function that has declared a local variable of + # that name it will update the local copy and not set a global.) + # + # Positional arguments + # + local filename="${1:?Filename is required.}" + # The full name of the file, with extension. + + # Taken from Apache's mime.types file (public domain). + case "$filename" in + *.bz2) mime_type=application/x-bzip2 ;; + *.exe) mime_type=application/x-msdownload ;; + *.tar.gz | *.gz | *.tgz) mime_type=application/x-gzip ;; + *.jpg | *.jpeg | *.jpe | *.jfif) mime_type=image/jpeg ;; + *.json) mime_type=application/json ;; + *.pdf) mime_type=application/pdf ;; + *.png) mime_type=image/png ;; + *.rpm) mime_type=application/x-rpm ;; + *.svg | *.svgz) mime_type=image/svg+xml ;; + *.tar) mime_type=application/x-tar ;; + *.txt) mime_type=text/plain ;; + *.yaml) mime_type=application/x-yaml ;; + *.apk) mime_type=application/vnd.android.package-archive ;; + *.zip) mime_type=application/zip ;; + *.jar) mime_type=application/java-archive ;; + *.war) mime_type=application/zip ;; + esac + + _log debug "Guessed mime type of '${mime_type}' for '${filename}'." +} + +_get_confirm() { + # Prompt the user for confirmation + # + # Usage: + # + # local confirm; _get_confirm + # [ "$confirm" -eq 1 ] && printf 'Good to go!\n' + # + # If global confirmation is set via `$OK_SH_DESTRUCTIVE` then the user + # is not prompted. Assigns the user's confirmation to the `confirm` global + # variable. (If this function is called within a function that has a local + # variable of that name, the local variable will be updated instead.) + # + # Positional arguments + # + local message="${1:-Are you sure?}" + # The message to prompt the user with. + + local answer + + if [ "$OK_SH_DESTRUCTIVE" -eq 1 ] ; then + confirm=$OK_SH_DESTRUCTIVE + return + fi + + printf '%s ' "$message" + read -r answer + + ! printf '%s\n' "$answer" | grep -Eq "$(locale yesexpr)" + confirm=$? +} + +_opts_filter() { + # Extract common jq filter keyword options and assign to vars + # + # Usage: + # + # local filter + # _opts_filter "$@" + + for arg in "$@"; do + case $arg in + (_filter=*) _filter="${arg#*=}";; + esac + done +} + +_opts_pagination() { + # Extract common pagination keyword options and assign to vars + # + # Usage: + # + # local _follow_next + # _opts_pagination "$@" + + for arg in "$@"; do + case $arg in + (_follow_next=*) _follow_next="${arg#*=}";; + (_follow_next_limit=*) _follow_next_limit="${arg#*=}";; + esac + done +} + +_opts_qs() { + # Extract common query string keyword options and assign to vars + # + # Usage: + # + # local qs + # _opts_qs "$@" + # _get "/some/path${qs}" + + local querystring=$(_format_urlencode "$@") + qs="${querystring:+?$querystring}" +} + +_request() { + # A wrapper around making HTTP requests with curl + # + # Usage: + # ``` + # # Get JSON for all issues: + # _request /repos/saltstack/salt/issues + # + # # Send a POST request; parse response using jq: + # printf '{"title": "%s", "body": "%s"}\n' "Stuff" "Things" \ + # | _request /some/path | jq -r '.[url]' + # + # # Send a PUT request; parse response using jq: + # printf '{"title": "%s", "body": "%s"}\n' "Stuff" "Things" \ + # | _request /repos/:owner/:repo/issues method=PUT | jq -r '.[url]' + # + # # Send a conditional-GET request: + # _request /users etag=edd3a0d38d8c329d3ccc6575f17a76bb + # ``` + # + # Input + # + # * (stdin) + # Data that will be used as the request body. + # + # Positional arguments + # + local path="${1:?Path is required.}" + # The URL path for the HTTP request. + # Must be an absolute path that starts with a `/` or a full URL that + # starts with http(s). Absolute paths will be append to the value in + # `$OK_SH_URL`. + # + # Keyword arguments + # + local method='GET' + # The method to use for the HTTP request. + local content_type='application/json' + # The value of the Content-Type header to use for the request. + local etag + # An optional Etag to send as the If-None-Match header. + + shift 1 + + local cmd + local arg + local has_stdin + local trace_curl + + case $path in + (http*) : ;; + *) path="${OK_SH_URL}${path}" ;; + esac + + for arg in "$@"; do + case $arg in + (method=*) method="${arg#*=}";; + (content_type=*) content_type="${arg#*=}";; + (etag=*) etag="${arg#*=}";; + esac + done + + case "$method" in + POST | PUT | PATCH) has_stdin=1;; + esac + + [ $OK_SH_VERBOSE -eq 3 ] && trace_curl=1 + + [ "$OK_SH_VERBOSE" -eq 1 ] && set -x + # shellcheck disable=SC2086 + curl -nsSig \ + -H "Accept: ${OK_SH_ACCEPT}" \ + -H "Content-Type: ${content_type}" \ + ${GITHUB_TOKEN:+-H "Authorization: token ${GITHUB_TOKEN}"} \ + ${etag:+-H "If-None-Match: \"${etag}\""} \ + ${has_stdin:+--data-binary @-} \ + ${trace_curl:+--trace-ascii /dev/stderr} \ + -X "${method}" \ + "${path}" + set +x +} + +_response() { + # Process an HTTP response from curl + # + # Output only headers of interest followed by the response body. Additional + # processing is performed on select headers to make them easier to parse + # using shell tools. + # + # Usage: + # ``` + # # Send a request; output the response and only select response headers: + # _request /some/path | _response status_code ETag Link_next + # + # # Make request using curl; output response with select response headers; + # # assign response headers to local variables: + # curl -isS example.com/some/path | _response status_code status_text | { + # local status_code status_text + # read -r status_code + # read -r status_text + # } + # ``` + # + # Header reformatting + # + # * HTTP Status + # + # The HTTP line is split into separate `http_version`, `status_code`, and + # `status_text` variables. + # + # * ETag + # + # The surrounding quotes are removed. + # + # * Link + # + # Each URL in the Link header is expanded with the URL type appended to + # the name. E.g., `Link_first`, `Link_last`, `Link_next`. + # + # Positional arguments + # + # * $1 - $9 + # + # Each positional arg is the name of an HTTP header. Each header value is + # output in the same order as each argument; each on a single line. A + # blank line is output for headers that cannot be found. + + local hdr + local val + local http_version + local status_code=100 + local status_text + local headers output + + _log debug 'Processing response.' + + while [ "${status_code}" = "100" ]; do + read -r http_version status_code status_text + status_text="${status_text%${crlf}}" + http_version="${http_version#HTTP/}" + + _log debug "Response status is: ${status_code} ${status_text}" + + if [ "${status_code}" = "100" ]; then + _log debug "Ignoring response '${status_code} ${status_text}', skipping to real response." + while IFS=": " read -r hdr val; do + # Headers stop at the first blank line. + [ "$hdr" = "$crlf" ] && break + val="${val%${crlf}}" + _log debug "Unexpected additional header: ${hdr}: ${val}" + done + + fi + done + + 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}}" + + # Process each header; reformat some to work better with sh tools. + case "$hdr" in + # Update the GitHub rate limit trackers. + X-RateLimit-Remaining) + printf 'GitHub remaining requests: %s\n' "$val" 1>&$LSUMMARY ;; + 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%\"}" ;; + + # Split the URLs in the Link header into separate pseudo-headers. + Link) headers="${headers}$(printf '%s' "$val" | awk ' + BEGIN { RS=", "; FS="; "; OFS=": " } + { + sub(/^rel="/, "", $2); sub(/"$/, "", $2) + sub(/^ *$/, "", $1) + print "Link_" $2, $1 + }') +" # need trailing newline + ;; + esac + + headers="${headers}${hdr}: ${val} +" # need trailing newline + + done + + # Output requested headers in deterministic order. + for arg in "$@"; do + _log debug "Outputting requested header '${arg}'." + output=$(printf '%s' "$headers" | while IFS=": " read -r hdr val; do + [ "$hdr" = "$arg" ] && printf '%s' "$val" + done) + printf '%s\n' "$output" + done + + # Output the response body. + cat +} + +_get() { + # A wrapper around _request() for common GET patterns + # + # Will automatically follow 'next' pagination URLs in the Link header. + # + # Usage: + # + # _get /some/path + # _get /some/path _follow_next=0 + # _get /some/path _follow_next_limit=200 | jq -c . + # + # Positional arguments + # + local path="${1:?Path is required.}" + # The HTTP path or URL to pass to _request(). + # + # Keyword arguments + # + # * _follow_next=1 + # + # Automatically look for a 'Links' header and follow any 'next' URLs. + # + # * _follow_next_limit=50 + # + # Maximum number of 'next' URLs to follow before stopping. + + shift 1 + local status_code + local status_text + local next_url + + # If the variable is unset or empty set it to a default value. Functions + # that call this function can pass these parameters in one of two ways: + # explicitly as a keyword arg or implicitly by setting variables of the same + # names within the local scope. + # shellcheck disable=SC2086 + if [ -z ${_follow_next+x} ] || [ -z "${_follow_next}" ]; then + local _follow_next=1 + fi + # shellcheck disable=SC2086 + if [ -z ${_follow_next_limit+x} ] || [ -z "${_follow_next_limit}" ]; then + local _follow_next_limit=50 + fi + + _opts_pagination "$@" + + _request "$path" | _response status_code status_text Link_next | { + read -r status_code + read -r status_text + read -r next_url + + case "$status_code" in + 20*) : ;; + 4*) printf 'Client Error: %s %s\n' \ + "$status_code" "$status_text" 1>&2; exit 1 ;; + 5*) printf 'Server Error: %s %s\n' \ + "$status_code" "$status_text" 1>&2; exit 1 ;; + esac + + # Output response body. + cat + + [ "$_follow_next" -eq 1 ] || return + + _log info "Remaining next link follows: ${_follow_next_limit}" + if [ -n "$next_url" ] && [ $_follow_next_limit -gt 0 ] ; then + _follow_next_limit=$(( _follow_next_limit - 1 )) + + _get "$next_url" "_follow_next_limit=${_follow_next_limit}" + fi + } +} + +_post() { + # A wrapper around _request() for common POST / PUT patterns + # + # Usage: + # + # _format_json foo=Foo bar=Bar | _post /some/path + # _format_json foo=Foo bar=Bar | _post /some/path method='PUT' + # _post /some/path filename=somearchive.tar + # _post /some/path filename=somearchive.tar mime_type=application/x-tar + # _post /some/path filename=somearchive.tar \ + # mime_type=$(file -b --mime-type somearchive.tar) + # + # Input + # + # * (stdin) + # Optional. See the `filename` argument also. + # Data that will be used as the request body. + # + # Positional arguments + # + local path="${1:?Path is required.}" + # The HTTP path or URL to pass to _request(). + # + # Keyword arguments + # + local method='POST' + # The method to use for the HTTP request. + local filename + # Optional. See the `stdin` option above also. + # Takes precedence over any data passed as stdin and loads a file off the + # file system to serve as the request body. + local mime_type + # The value of the Content-Type header to use for the request. + # If the `filename` argument is given this value will be guessed from the + # file extension. If the `filename` argument is not given (i.e., using + # stdin) this value defaults to `application/json`. Specifying this + # argument overrides all other defaults or guesses. + + shift 1 + + for arg in "$@"; do + case $arg in + (method=*) method="${arg#*=}";; + (filename=*) filename="${arg#*=}";; + (mime_type=*) mime_type="${arg#*=}";; + esac + done + + # Make either the file or stdin available as fd7. + if [ -n "$filename" ] ; then + if [ -r "$filename" ] ; then + _log debug "Using '${filename}' as POST data." + [ -n "$mime_type" ] || _get_mime_type "$filename" + : ${mime_type:?The MIME type could not be guessed.} + exec 7<"$filename" + else + printf 'File could not be found or read.\n' 1>&2 + exit 1 + fi + else + _log debug "Using stdin as POST data." + mime_type='application/json' + exec 7<&0 + fi + + _request "$path" method="$method" content_type="$mime_type" 0<&7 \ + | _response status_code status_text \ + | { + read -r status_code + read -r status_text + + case "$status_code" in + 20*) : ;; + 4*) printf 'Client Error: %s %s\n' \ + "$status_code" "$status_text" 1>&2; exit 1 ;; + 5*) printf 'Server Error: %s %s\n' \ + "$status_code" "$status_text" 1>&2; exit 1 ;; + esac + + # Output response body. + cat + } +} + +_delete() { + # A wrapper around _request() for common DELETE patterns + # + # Usage: + # + # _delete '/some/url' + # + # Return: 0 for success; 1 for failure. + # + # Positional arguments + # + local url="${1:?URL is required.}" + # The URL to send the DELETE request to. + + local status_code + + _request "${url}" method='DELETE' | _response status_code | { + read -r status_code + [ "$status_code" = "204" ] + exit $? + } +} + +# ## GitHub +# Friendly functions for common GitHub tasks. + +# ### Authorization +# Perform authentication and authorization. + +show_scopes() { + # Show the permission scopes for the currently authenticated user + # + # Usage: + # + # show_scopes + + local oauth_scopes + + _request '/' | _response X-OAuth-Scopes | { + read -r oauth_scopes + + printf '%s\n' "$oauth_scopes" + + # Dump any remaining response body. + cat >/dev/null + } +} + +# ### Repository +# Create, update, delete, list repositories. + +org_repos() { + # List organization repositories + # + # Usage: + # + # org_repos myorg + # org_repos myorg type=private per_page=10 + # org_repos myorg _filter='.[] | "\(.name)\t\(.owner.login)"' + # + # Positional arguments + # + local org="${1:?Org name required.}" + # Organization GitHub login or id for which to list repos. + # + # 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='.[] | "\(.name)\t\(.ssh_url)"' + # A jq filter to apply to the return data. + # + # Querystring arguments may also be passed as keyword arguments: + # + # * `per_page` + # * `type` + + shift 1 + local qs + + _opts_pagination "$@" + _opts_filter "$@" + _opts_qs "$@" + + _get "/orgs/${org}/repos${qs}" | _filter_json "${_filter}" +} + +org_teams() { + # List teams + # + # Usage: + # + # org_teams org + # + # Positional arguments + # + local org="${1:?Org name required.}" + # Organization GitHub login or id. + # + # Keyword arguments + # + local _filter='.[] | "\(.name)\t\(.id)\t\(.permission)"' + # A jq filter to apply to the return data. + + shift 1 + + _opts_filter "$@" + + _get "/orgs/${org}/teams" \ + | _filter_json "${_filter}" +} + +org_members() { + # List organization members + # + # Usage: + # + # org_members 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. + + shift 1 + + _opts_filter "$@" + + _get "/orgs/${org}/members" \ + | _filter_json "${_filter}" +} + +team_members() { + # List team members + # + # Usage: + # + # team_members team_id + # + # Positional arguments + # + local team_id="${1:?Team id required.}" + # Team id. + # + # Keyword arguments + # + local _filter='.[] | "\(.login)\t\(.id)"' + # A jq filter to apply to the return data. + + shift 1 + + _opts_filter "$@" + + _get "/teams/${team_id}/members" \ + | _filter_json "${_filter}" + +} + +list_repos() { + # List user repositories + # + # Usage: + # + # list_repos + # list_repos user + # + # Positional arguments + # + local user="$1" + # Optional GitHub user login or id for which to list 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` + + + shift 1 + local qs + + _opts_filter "$@" + _opts_qs "$@" + + if [ -n "$user" ] ; then + url="/users/${user}/repos" + else + url='/user/repos' + fi + + _get "${url}${qs}" | _filter_json "${_filter}" +} + +list_branches() { + # List branches of a specified repository. + # ( https://developer.github.com/v3/repos/#list_branches ) + # + # Usage: + # + # list_branches 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 + # + # Keyword arguments + # + local _filter='.[] | "\(.name)"' + # A jq filter to apply to the return data. + # + # Querystring arguments may also be passed as keyword arguments: + # + # * `direction` + # * `per_page` + # * `sort` + # * `type` + + local qs + + _opts_filter "$@" + _opts_qs "$@" + + url="/repos/${user}/${repo}/branches" + + _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 ) + # + # Usage: + # + # list_contributors user repo + # + # Positional arguments + # + local user="${1:?User name required.}" + # GitHub user login or id for which to list contributors + local repo="${2:?Repo name required.}" + # Name of the repo for which to list contributors + # + # Keyword arguments + # + local _filter='.[] | "\(.login)\t\(.type)\tType:\(.type)\tContributions:\(.contributions)"' + # A jq filter to apply to the return data. + # + # Querystring arguments may also be passed as keyword arguments: + # + # * `direction` + # * `per_page` + # * `sort` + # * `type` + + shift 2 + + local qs + + _opts_filter "$@" + _opts_qs "$@" + + url="/repos/${user}/${repo}/contributors" + + _get "${url}${qs}" | _filter_json "${_filter}" +} + +list_collaborators() { + # List collaborators to the specified repository, sorted by the number of commits per collaborator in descending order. + # ( https://developer.github.com/v3/repos/#list-collaborators ) + # + # Usage: + # + # list_collaborators someuser/somerepo + # + # Positional arguments + # GitHub user login or id for which to list collaborators + # Name of the repo for which to list collaborators + # + local repo="${1:?Repo name required.}" + # + # Keyword arguments + # + local _filter='.[] | "\(.login)\t\(.type)\tType:\(.type)\tPermissions:\(.permissions)"' + # A jq filter to apply to the return data. + # + # Querystring arguments may also be passed as keyword arguments: + # + # * `direction` + # * `per_page` + # * `sort` + # * `type` + + shift 1 + + local qs + + _opts_filter "$@" + _opts_qs "$@" + + url="/repos/${repo}/collaborators" + + _get "${url}${qs}" | _filter_json "${_filter}" +} + +list_hooks() { + # List webhooks from the specified repository. + # ( https://developer.github.com/v3/repos/hooks/#list-hooks ) + # + # Usage: + # + # list_hooks owner/repo + # + # Positional arguments + # + local repo="${1:?Repo name required.}" + # Name of the repo for which to list contributors + # Owner is mandatory, like 'owner/repo' + # + local _filter='.[] | "\(.name)\t\(.config.url)"' + # A jq filter to apply to the return data. + # + + shift 1 + + _opts_filter "$@" + + url="/repos/${repo}/hooks" + + _get "${url}" | _filter_json "${_filter}" +} + +list_gists() { + # List gists for the current authenticated user or a specific user + # + # https://developer.github.com/v3/gists/#list-a-users-gists + # + # Usage: + # + # list_gists + # list_gists + # + # Positional arguments + # + local username="$1" + # An optional user to filter listing + # + # 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='.[] | "\(.id)\t\(.description)"' + # A jq filter to apply to the return data. + + local url + case "$username" in + ('') url='/gists';; + (*=*) url='/gists';; + (*) url="/users/${username}/gists"; shift 1;; + esac + + _opts_pagination "$@" + _opts_filter "$@" + + _get "${url}" | _filter_json "${_filter}" +} + +public_gists() { + # List public gists + # + # https://developer.github.com/v3/gists/#list-all-public-gists + # + # Usage: + # + # public_gists + # + # 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='.[] | "\(.id)\t\(.description)"' + # A jq filter to apply to the return data. + + _opts_pagination "$@" + _opts_filter "$@" + + _get '/gists/public' | _filter_json "${_filter}" +} + +gist() { + # Get a single gist + # + # https://developer.github.com/v3/gists/#get-a-single-gist + # + # Usage: + # + # get_gist + # + # Positional arguments + # + local gist_id="${1:?Gist ID required.}" + # ID of gist to fetch. + # + # Keyword arguments + # + local _filter='.files | keys | join(", ")' + # A jq filter to apply to the return data. + + shift 1 + + _opts_filter "$@" + + _get "/gists/${gist_id}" | _filter_json "${_filter}" +} + +add_collaborator() { + # Add a collaborator to a repository + # + # Usage: + # + # add_collaborator someuser/somerepo collaboratoruser permission + # + # Positional arguments + # + local repo="${1:?Repo name required.}" + # A GitHub repository. + local collaborator="${2:?Collaborator name required.}" + # A new collaborator. + local permission="${3:?Permission required. One of: push pull admin}" + # The permission level for this collaborator. One of `push`, `pull`, + # `admin`. The `pull` and `admin` permissions are valid for organization + # repos only. + case $permission in + push|pull|admin) :;; + *) printf 'Permission invalid: %s\nMust be one of: push pull admin\n' \ + "$permission" 1>&2; exit 1 ;; + esac + # + # Keyword arguments + # + local _filter='"\(.name)\t\(.color)"' + # A jq filter to apply to the return data. + + _opts_filter "$@" + + _format_json permission="$permission" \ + | _post "/repos/${repo}/collaborators/${collaborator}" method='PUT' \ + | _filter_json "$_filter" +} + +delete_collaborator() { + # Delete a collaborator to a repository + # + # Usage: + # + # delete_collaborator someuser/somerepo collaboratoruser permission + # + # Positional arguments + # + local repo="${1:?Repo name required.}" + # A GitHub repository. + local collaborator="${2:?Collaborator name required.}" + # A new collaborator. + + shift 2 + + local confirm + + _get_confirm 'This will permanently delete the collaborator from this repo. Continue?' + [ "$confirm" -eq 1 ] || exit 0 + + _delete "/repos/${repo}/collaborators/${collaborator}" + exit $? +} + +create_repo() { + # Create a repository for a user or organization + # + # Usage: + # + # create_repo foo + # create_repo bar description='Stuff and things' homepage='example.com' + # create_repo baz organization=myorg + # + # Positional arguments + # + local name="${1:?Repo name required.}" + # Name of the new repo + # + # Keyword arguments + # + local _filter='"\(.name)\t\(.html_url)"' + # A jq filter to apply to the return data. + # + # POST data may also be passed as keyword arguments: + # + # * `auto_init`, + # * `description` + # * `gitignore_template` + # * `has_downloads` + # * `has_issues` + # * `has_wiki`, + # * `homepage` + # * `organization` + # * `private` + # * `team_id` + + shift 1 + + _opts_filter "$@" + + local url + local organization + + for arg in "$@"; do + case $arg in + (organization=*) organization="${arg#*=}";; + esac + done + + if [ -n "$organization" ] ; then + url="/orgs/${organization}/repos" + else + url='/user/repos' + fi + + _format_json "name=${name}" "$@" | _post "$url" | _filter_json "${_filter}" +} + +delete_repo() { + # Delete a repository for a user or organization + # + # Usage: + # + # delete_repo owner repo + # + # The currently authenticated user must have the `delete_repo` scope. View + # current scopes with the `show_scopes()` function. + # + # Positional arguments + # + local owner="${1:?Owner name required.}" + # Name of the new repo + local repo="${2:?Repo name required.}" + # Name of the new repo + + shift 2 + + local confirm + + _get_confirm 'This will permanently delete a repository! Continue?' + [ "$confirm" -eq 1 ] || exit 0 + + _delete "/repos/${owner}/${repo}" + exit $? +} + +fork_repo() { + # Fork a repository from a user or organization to own account or organization + # + # Usage: + # + # fork_repo owner repo + # + # Positional arguments + # + local owner="${1:?Owner name required.}" + # Name of existing user or organization + local repo="${2:?Repo name required.}" + # Name of the existing repo + # + # + # Keyword arguments + # + local _filter='"\(.clone_url)\t\(.ssh_url)"' + # A jq filter to apply to the return data. + # + # POST data may also be passed as keyword arguments: + # + # * `organization` (The organization to clone into; default: your personal account) + + shift 2 + + _opts_filter "$@" + + _format_json "$@" | _post "/repos/${owner}/${repo}/forks" \ + | _filter_json "${_filter}" + exit $? # might take a bit time... +} + +# ### Releases +# Create, update, delete, list releases. + +list_releases() { + # List releases for a repository + # + # https://developer.github.com/v3/repos/releases/#list-releases-for-a-repository + # + # Usage: + # + # list_releases org repo '\(.assets[0].name)\t\(.name.id)' + # + # Positional arguments + # + local owner="${1:?Owner name required.}" + # A GitHub user or organization. + local repo="${2:?Repo name required.}" + # A GitHub repository. + # + # Keyword arguments + # + local _filter='.[] | "\(.name)\t\(.tag_name)\t\(.id)\t\(.html_url)"' + # A jq filter to apply to the return data. + + shift 2 + + _opts_filter "$@" + + _get "/repos/${owner}/${repo}/releases" \ + | _filter_json "${_filter}" +} + +release() { + # Get a release + # + # https://developer.github.com/v3/repos/releases/#get-a-single-release + # + # Usage: + # + # release user repo 1087855 + # + # Positional arguments + # + local owner="${1:?Owner name required.}" + # A GitHub user or organization. + local repo="${2:?Repo name required.}" + # A GitHub repository. + local release_id="${3:?Release ID required.}" + # The unique ID of the release; see list_releases. + # + # Keyword arguments + # + local _filter='"\(.author.login)\t\(.published_at)"' + # A jq filter to apply to the return data. + + shift 3 + + _opts_filter "$@" + + _get "/repos/${owner}/${repo}/releases/${release_id}" \ + | _filter_json "${_filter}" +} + +create_release() { + # Create a release + # + # https://developer.github.com/v3/repos/releases/#create-a-release + # + # Usage: + # + # create_release org repo v1.2.3 + # create_release user repo v3.2.1 draft=true + # + # Positional arguments + # + local owner="${1:?Owner name required.}" + # A GitHub user or organization. + local repo="${2:?Repo name required.}" + # A GitHub repository. + local tag_name="${3:?Tag name required.}" + # Git tag from which to create release. + # + # Keyword arguments + # + local _filter='"\(.name)\t\(.id)\t\(.html_url)"' + # A jq filter to apply to the return data. + # + # POST data may also be passed as keyword arguments: + # + # * `body` + # * `draft` + # * `name` + # * `prerelease` + # * `target_commitish` + + shift 3 + + _opts_filter "$@" + + _format_json "tag_name=${tag_name}" "$@" \ + | _post "/repos/${owner}/${repo}/releases" \ + | _filter_json "${_filter}" +} + +edit_release() { + # Edit a release + # + # https://developer.github.com/v3/repos/releases/#edit-a-release + # + # Usage: + # + # edit_release org repo 1087855 name='Foo Bar 1.4.6' + # edit_release user repo 1087855 draft=false + # + # Positional arguments + # + local owner="${1:?Owner name required.}" + # A GitHub user or organization. + local repo="${2:?Repo name required.}" + # A GitHub repository. + local release_id="${3:?Release ID required.}" + # The unique ID of the release; see list_releases. + # + # Keyword arguments + # + local _filter='"\(.tag_name)\t\(.name)\t\(.html_url)"' + # A jq filter to apply to the return data. + # + # POST data may also be passed as keyword arguments: + # + # * `tag_name` + # * `body` + # * `draft` + # * `name` + # * `prerelease` + # * `target_commitish` + + shift 3 + + _opts_filter "$@" + + _format_json "$@" \ + | _post "/repos/${owner}/${repo}/releases/${release_id}" method="PATCH" \ + | _filter_json "${_filter}" +} + +delete_release() { + # Delete a release + # + # https://developer.github.com/v3/repos/releases/#delete-a-release + # + # Usage: + # + # delete_release org repo 1087855 + # + # Return: 0 for success; 1 for failure. + # + # Positional arguments + # + local owner="${1:?Owner name required.}" + # A GitHub user or organization. + local repo="${2:?Repo name required.}" + # A GitHub repository. + local release_id="${3:?Release ID required.}" + # The unique ID of the release; see list_releases. + + shift 3 + + local confirm + + _get_confirm 'This will permanently delete a release. Continue?' + [ "$confirm" -eq 1 ] || exit 0 + + _delete "/repos/${owner}/${repo}/releases/${release_id}" + exit $? +} + +release_assets() { + # List release assets + # + # https://developer.github.com/v3/repos/releases/#list-assets-for-a-release + # + # Usage: + # + # release_assets user repo 1087855 + # + # Example of downloading release assets: + # + # ok.sh release_assets \ + # _filter='.[] | .browser_download_url' \ + # | xargs -L1 curl -L -O + # + # Example of the multi-step process for grabbing the release ID for + # a specific version, then grabbing the release asset IDs, and then + # downloading 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='.[] | .browser_download_url' \ + # | xargs -L1 curl -n -L -O + # + # Positional arguments + # + local owner="${1:?Owner name required.}" + # A GitHub user or organization. + local repo="${2:?Repo name required.}" + # A GitHub repository. + local release_id="${3:?Release ID required.}" + # The unique ID of the release; see list_releases. + # + # Keyword arguments + # + local _filter='.[] | "\(.id)\t\(.name)\t\(.updated_at)"' + # A jq filter to apply to the return data. + + shift 3 + + _opts_filter "$@" + + _get "/repos/${owner}/${repo}/releases/${release_id}/assets" \ + | _filter_json "$_filter" +} + +upload_asset() { + # Upload a release asset + # + # https://developer.github.com/v3/repos/releases/#upload-a-release-asset + # + # Usage: + # + # upload_asset https:// /path/to/file.zip + # + # The upload URL can be gotten from `release()`. There are multiple steps + # required to upload a file: get the release ID, get the upload URL, parse + # the upload URL, then finally upload the file. For example: + # + # ```sh + # USER="someuser" + # REPO="somerepo" + # TAG="1.2.3" + # FILE_NAME="foo.zip" + # FILE_PATH="/path/to/foo.zip" + # + # # Create a release then upload a file: + # ok.sh create_release "$USER" "$REPO" "$TAG" _filter='.upload_url' \ + # | sed 's/{.*$/?name='"$FILE_NAME"'/' \ + # | xargs -I@ ok.sh upload_asset @ "$FILE_PATH" + # + # # Find a release by tag then upload a file: + # ok.sh list_releases "$USER" "$REPO" \ + # | awk -v "tag=$TAG" -F'\t' '$2 == tag { print $3 }' \ + # | xargs -I@ ok.sh release "$USER" "$REPO" @ _filter='.upload_url' \ + # | sed 's/{.*$/?name='"$FILE_NAME"'/' \ + # | xargs -I@ ok.sh upload_asset @ "$FILE_PATH" + # ``` + # + # Positional arguments + # + local upload_url="${1:?upload_url is required.}" + # The _parsed_ upload_url returned from GitHub. + # + local file_path="${2:?file_path is required.}" + # A path to the file that should be uploaded. + # + # Keyword arguments + # + local _filter='"\(.state)\t\(.browser_download_url)"' + # A jq filter to apply to the return data. + # + # Also any other keyword arguments accepted by `_post()`. + + shift 2 + + _opts_filter "$@" + + _post "$upload_url" filename="$file_path" "$@" \ + | _filter_json "$_filter" +} + +# ### Issues +# Create, update, edit, delete, list issues and milestones. + +list_milestones() { + # List milestones for a repository + # + # Usage: + # + # list_milestones someuser/somerepo + # list_milestones someuser/somerepo state=closed + # + # Positional arguments + # + local repository="${1:?Repo name required.}" + # A GitHub repository. + # + # 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='.[] | "\(.number)\t\(.open_issues)/\(.closed_issues)\t\(.title)"' + # A jq filter to apply to the return data. + # + # GitHub querystring arguments may also be passed as keyword arguments: + # + # * `direction` + # * `per_page` + # * `sort` + # * `state` + + shift 1 + local qs + + _opts_pagination "$@" + _opts_filter "$@" + _opts_qs "$@" + + _get "/repos/${repository}/milestones${qs}" | _filter_json "$_filter" +} + +create_milestone() { + # Create a milestone for a repository + # + # Usage: + # + # create_milestone someuser/somerepo MyMilestone + # + # create_milestone someuser/somerepo MyMilestone \ + # due_on=2015-06-16T16:54:00Z \ + # description='Long description here + # that spans multiple lines.' + # + # Positional arguments + # + local repo="${1:?Repo name required.}" + # A GitHub repository. + local title="${2:?Milestone name required.}" + # A unique title. + # + # Keyword arguments + # + local _filter='"\(.number)\t\(.html_url)"' + # A jq filter to apply to the return data. + # + # Milestone options may also be passed as keyword arguments: + # + # * `description` + # * `due_on` + # * `state` + + shift 2 + + _opts_filter "$@" + + _format_json title="$title" "$@" \ + | _post "/repos/${repo}/milestones" \ + | _filter_json "$_filter" +} + +add_comment() { + # Add a comment to an issue + # + # Usage: + # + # add_comment someuser/somerepo 123 'This is a comment' + # + # Positional arguments + # + local repository="${1:?Repo name required}" + # A GitHub repository + local number="${2:?Issue number required}" + # Issue Number + local comment="${3:?Comment required}" + # Comment to be added + # + # Keyword arguments + # + local _filter='"\(.id)\t\(.html_url)"' + # A jq filter to apply to the return data. + + shift 3 + _opts_filter "$@" + + _format_json body="$comment" \ + | _post "/repos/${repository}/issues/${number}/comments" \ + | _filter_json "${_filter}" +} + +add_commit_comment() { + # Add a comment to a commit + # + # Usage: + # + # add_commit_comment someuser/somerepo 123 'This is a comment' + # + # Positional arguments + # + local repository="${1:?Repo name required}" + # A GitHub repository + local hash="${2:?Commit hash required}" + # Commit hash + local comment="${3:?Comment required}" + # Comment to be added + # + # Keyword arguments + # + local _filter='"\(.id)\t\(.html_url)"' + # A jq filter to apply to the return data. + + shift 3 + _opts_filter "$@" + + _format_json body="$comment" \ + | _post "/repos/${repository}/commits/${hash}/comments" \ + | _filter_json "${_filter}" +} + +close_issue() { + # Close an issue + # + # Usage: + # + # close_issue someuser/somerepo 123 + # + # Positional arguments + # + local repository="${1:?Repo name required}" + # A GitHub repository + local number="${2:?Issue number required}" + # Issue Number + # + # Keyword arguments + # + local _filter='"\(.id)\t\(.state)\t\(.html_url)"' + # A jq filter to apply to the return data. + # + # POST data may also be passed as keyword arguments: + # + # * `assignee` + # * `labels` + # * `milestone` + + shift 2 + _opts_filter "$@" + + _format_json state="closed" "$@" \ + | _post "/repos/${repository}/issues/${number}" method='PATCH' \ + | _filter_json "${_filter}" +} + +list_issues() { + # List issues for the authenticated user or repository + # + # Usage: + # + # list_issues + # list_issues someuser/somerepo + # list_issues state=closed labels=foo,bar + # + # Positional arguments + # + # user or user/repository + # + # 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='.[] | "\(.number)\t\(.title)"' + # A jq filter to apply to the return data. + # + # GitHub querystring arguments may also be passed as keyword arguments: + # + # * `assignee` + # * `creator` + # * `direction` + # * `labels` + # * `mentioned` + # * `milestone` + # * `per_page` + # * `since` + # * `sort` + # * `state` + + local url + local qs + + case $1 in + ('') url='/user/issues' ;; + (*=*) url='/user/issues' ;; + (*/*) url="/repos/${1}/issues"; shift 1 ;; + esac + + _opts_pagination "$@" + _opts_filter "$@" + _opts_qs "$@" + + _get "${url}${qs}" | _filter_json "$_filter" +} + +user_issues() { + # List all issues across owned and member repositories for the authenticated user + # + # Usage: + # + # user_issues + # user_issues since=2015-60-11T00:09:00Z + # + # 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='.[] | "\(.repository.full_name)\t\(.number)\t\(.title)"' + # A jq filter to apply to the return data. + # + # GitHub querystring arguments may also be passed as keyword arguments: + # + # * `direction` + # * `filter` + # * `labels` + # * `per_page` + # * `since` + # * `sort` + # * `state` + + local qs + + _opts_pagination "$@" + _opts_filter "$@" + _opts_qs "$@" + + _get "/issues${qs}" | _filter_json "$_filter" +} + +create_issue() { + # Create an issue + # + # Usage: + # + # create_issue owner repo 'Issue title' body='Add multiline body + # content here' labels="$(./ok.sh _format_json -a foo bar)" + # + # Positional arguments + # + local owner="${1:?Owner name required.}" + # A GitHub repository. + local repo="${2:?Repo name required.}" + # A GitHub repository. + local title="${3:?Issue title required.}" + # A GitHub repository. + # + # Keyword arguments + # + local _filter='"\(.id)\t\(.number)\t\(.html_url)"' + # A jq filter to apply to the return data. + # + # Additional issue fields may be passed as keyword arguments: + # + # * `body` (string) + # * `assignee` (string) + # * `milestone` (integer) + # * `labels` (array of strings) + # * `assignees` (array of strings) + + shift 3 + + _opts_filter "$@" + + _format_json title="$title" "$@" \ + | _post "/repos/${owner}/${repo}/issues" \ + | _filter_json "$_filter" +} + +org_issues() { + # List all issues for a given organization for the authenticated user + # + # Usage: + # + # org_issues someorg + # + # Positional arguments + # + local org="${1:?Organization name required.}" + # Organization GitHub login or id. + # + # 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='.[] | "\(.number)\t\(.title)"' + # A jq filter to apply to the return data. + # + # GitHub querystring arguments may also be passed as keyword arguments: + # + # * `direction` + # * `filter` + # * `labels` + # * `per_page` + # * `since` + # * `sort` + # * `state` + + shift 1 + local qs + + _opts_pagination "$@" + _opts_filter "$@" + _opts_qs "$@" + + _get "/orgs/${org}/issues${qs}" | _filter_json "$_filter" +} + +list_my_orgs() { + # List your organizations + # + # Usage: + # + # list_my_orgs + # + # 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 "/user/orgs" | _filter_json "$_filter" +} + +list_orgs() { + # List all organizations + # + # Usage: + # + # list_orgs + # + # 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 "/organizations" | _filter_json "$_filter" +} + +labels() { + # List available labels for a repository + # + # Usage: + # + # labels someuser/somerepo + # + # Positional arguments + # + local repo="$1" + # A GitHub repository. + # + # 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='.[] | "\(.name)\t\(.color)"' + # A jq filter to apply to the return data. + + _opts_pagination "$@" + _opts_filter "$@" + + _get "/repos/${repo}/labels" | _filter_json "$_filter" +} + +add_label() { + # Add a label to a repository + # + # Usage: + # + # add_label someuser/somerepo LabelName color + # + # Positional arguments + # + local repo="${1:?Repo name required.}" + # A GitHub repository. + local label="${2:?Label name required.}" + # A new label. + local color="${3:?Hex color required.}" + # A color, in hex, without the leading `#`. + # + # Keyword arguments + # + local _filter='"\(.name)\t\(.color)"' + # A jq filter to apply to the return data. + + _opts_filter "$@" + + _format_json name="$label" color="$color" \ + | _post "/repos/${repo}/labels" \ + | _filter_json "$_filter" +} + +update_label() { + # Update a label + # + # Usage: + # + # update_label someuser/somerepo OldLabelName \ + # label=NewLabel color=newcolor + # + # Positional arguments + # + local repo="${1:?Repo name required.}" + # A GitHub repository. + local label="${2:?Label name required.}" + # The name of the label which will be updated + # + # Keyword arguments + # + local _filter='"\(.name)\t\(.color)"' + # A jq filter to apply to the return data. + # + # Label options may also be passed as keyword arguments, these will update + # the existing values: + # + # * `color` + # * `name` + + shift 2 + + _opts_filter "$@" + + _format_json "$@" \ + | _post "/repos/${repo}/labels/${label}" method='PATCH' \ + | _filter_json "$_filter" +} + +add_team_repo() { + # Add a team repository + # + # Usage: + # + # add_team_repo team_id organization repository_name permission + # + # Positional arguments + # + local team_id="${1:?Team id required.}" + # Team id to add repository to + local organization="${2:?Organization required.}" + # Organization to add repository to + local repository_name="${3:?Repository name required.}" + # Repository name to add + local permission="${4:?Permission required.}" + # Permission to grant: pull, push, admin + # + local url="/teams/${team_id}/repos/${organization}/${repository_name}" + + export OK_SH_ACCEPT="application/vnd.github.ironman-preview+json" + + _format_json "name=${name}" "permission=${permission}" | _post "$url" method='PUT' | _filter_json "${_filter}" +} + +list_pulls() { + # Lists the pull requests for a repository + # + # Usage: + # + # list_pulls user repo + # + # Positional arguments + # + local owner="${1:?Owner required.}" + # A GitHub owner. + local repo="${2:?Repo name required.}" + # A GitHub repository. + # + # 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='.[] | "\(.number)\t\(.user.login)\t\(.head.repo.clone_url)\t\(.head.ref)"' + # A jq filter to apply to the return data. + + _opts_pagination "$@" + _opts_filter "$@" + + _get "/repos/${owner}/${repo}/pulls" | _filter_json "$_filter" +} + +create_pull_request() { + # Create a pull request for a repository + # + # Usage: + # + # create_pull_request someuser/somerepo title head base + # + # create_pull_request someuser/somerepo title head base body='Description here.' + # + # Positional arguments + # + local repo="${1:?Repo name required.}" + # A GitHub repository. + local title="${2:?Pull request title required.}" + # A title. + local head="${3:?Pull request head required.}" + # A head. + local base="${4:?Pull request base required.}" + # A base. + # + # Keyword arguments + # + local _filter='"\(.number)\t\(.html_url)"' + # A jq filter to apply to the return data. + # + # Pull request options may also be passed as keyword arguments: + # + # * `body` + # * `maintainer_can_modify` + + shift 4 + + _opts_filter "$@" + + _format_json title="$title" head="$head" base="$base" "$@" \ + | _post "/repos/${repo}/pulls" \ + | _filter_json "$_filter" +} + +update_pull_request() { + # Update a pull request for a repository + # + # Usage: + # + # update_pull_request someuser/somerepo number title='New title' body='New body' + # + # Positional arguments + # + local repo="${1:?Repo name required.}" + # A GitHub repository. + local number="${2:?Pull request number required.}" + # A pull request number. + # + # Keyword arguments + # + local _filter='"\(.number)\t\(.html_url)"' + # A jq filter to apply to the return data. + # + # Pull request options may also be passed as keyword arguments: + # + # * `base` + # * `body` + # * `maintainer_can_modify` + # * `state` (either open or closed) + # * `title` + + shift 2 + + _opts_filter "$@" + + _format_json "$@" \ + | _post "/repos/${repo}/pulls/${number}" method='PATCH' \ + | _filter_json "$_filter" +} + +transfer_repo() { + # Transfer a repository to a user or organization + # + # Usage: + # + # transfer_repo owner repo new_owner + # transfer_repo owner repo new_owner team_ids='[ 12, 345 ]' + # + # Positional arguments + # + local owner="${1:?Owner name required.}" + # Name of the current owner + # + local repo="${2:?Repo name required.}" + # Name of the current repo + # + local new_owner="${3:?New owner name required.}" + # Name of the new owner + # + # Keyword arguments + # + local _filter='"\(.name)"' + # A jq filter to apply to the return data. + # + # POST data may also be passed as keyword arguments: + # + # * `team_ids` + + shift 3 + + _opts_filter "$@" + + export OK_SH_ACCEPT='application/vnd.github.nightshade-preview+json' + _format_json "new_owner=${new_owner}" "$@" | _post "/repos/${owner}/${repo}/transfer" | _filter_json "${_filter}" +} + +archive_repo() { + # Archive a repo + # + # Usage: + # + # archive_repo owner/repo + # + # Positional arguments + # + local repo="${1:?Repo name required.}" + # A GitHub repository. + # + local _filter='"\(.name)\t\(.html_url)"' + # A jq filter to apply to the return data. + # + + shift 1 + + _opts_filter "$@" + + _format_json "archived=true" \ + | _post "/repos/${repo}" method='PATCH' \ + | _filter_json "$_filter" +} + +__main "$@" diff --git a/build.xml b/build.xml index 68b31422..144aab08 100644 --- a/build.xml +++ b/build.xml @@ -27,7 +27,12 @@ - + + + + + + @@ -480,9 +487,9 @@ - - - @@ -688,7 +693,7 @@ - + + + + + + + Uploading Gitblit ${project.version} binaries + + + + + + + + + + + + + + + + + + + + + + + + + + + + Publishing Gitblit ${project.version} release draft on GitHub + + + + + + + + + + + + + + + Creating release ${project.tag} draft on GitHub + + + + + + + + + + + + + + + + + + + + + + + + + uploading @{source} to GitHub release ${ghrelease.id} + + + + + + + + + + + + publishing GitHub release draft @{releaseid} + + + + + + + + + + + + + + + + + + + + + + + @@ -743,10 +744,10 @@ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ --> - + releaselog="${releaselog}" + releasetag="${release.tag}"/> Uploading Gitblit ${project.version} binaries @@ -790,7 +791,6 @@ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ --> - Publishing Gitblit ${project.version} release draft on GitHub @@ -798,7 +798,7 @@ releaseVersion="${project.version}"/> - + @@ -1090,11 +1090,12 @@ --> + Creating release ${project.tag} draft on GitHub - + -- cgit v1.2.3 From 8c8c427949843cfad896b1f6f5fb4078affa26e0 Mon Sep 17 00:00:00 2001 From: Florian Zschocke Date: Mon, 27 Jan 2020 20:31:21 +0100 Subject: Site: Remove Google Plus id Google Plus is gone, so linking to it makes no sense anymore. --- build.xml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/build.xml b/build.xml index 26c5724a..73ca2201 100644 --- a/build.xml +++ b/build.xml @@ -500,8 +500,8 @@ Building Gitblit Website ${project.version} - + -- cgit v1.2.3 From 677fc999e65532fd7b98454472aa44328863ee58 Mon Sep 17 00:00:00 2001 From: Florian Zschocke Date: Mon, 27 Jan 2020 20:34:20 +0100 Subject: Site: Change the download URL to GitHub --- build.xml | 22 +++++++++++++--------- 1 file changed, 13 insertions(+), 9 deletions(-) diff --git a/build.xml b/build.xml index 73ca2201..e8c3e160 100644 --- a/build.xml +++ b/build.xml @@ -47,6 +47,7 @@ + @@ -58,9 +59,11 @@ + + - + JDK version: ${ant.java.version} @@ -573,20 +576,21 @@ - - - + + + - - + + - + - + + @@ -612,7 +616,7 @@ - + -- cgit v1.2.3 From 3d2955c2271387149a8bf293db6efc5635de4b60 Mon Sep 17 00:00:00 2001 From: Florian Zschocke Date: Mon, 27 Jan 2020 21:42:17 +0100 Subject: Site: Remove link to Bintray update notifications --- src/site/siteindex.mkd | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/site/siteindex.mkd b/src/site/siteindex.mkd index aec5c42a..34c93bf0 100644 --- a/src/site/siteindex.mkd +++ b/src/site/siteindex.mkd @@ -15,8 +15,7 @@ - - +
-- cgit v1.2.3 From 243ee989ef13fde63faed451c938e4ccdd5f8922 Mon Sep 17 00:00:00 2001 From: Florian Zschocke Date: Mon, 27 Jan 2020 21:43:01 +0100 Subject: Add Twitter link to site overview and built-in docs. --- build.xml | 1 + src/site/siteindex.mkd | 1 + 2 files changed, 2 insertions(+) diff --git a/build.xml b/build.xml index e8c3e160..f9ec975f 100644 --- a/build.xml +++ b/build.xml @@ -992,6 +992,7 @@ + diff --git a/src/site/siteindex.mkd b/src/site/siteindex.mkd index 34c93bf0..40460e60 100644 --- a/src/site/siteindex.mkd +++ b/src/site/siteindex.mkd @@ -24,6 +24,7 @@ +
SourcesGitHub
IssuesGitHub
DiscussionGitblit Group
Twitter@gitblit
OhlohOhloh project report for Gitblit
-- cgit v1.2.3 From c28e21611538f1977e89d847bdaf181b659f89ed Mon Sep 17 00:00:00 2001 From: Florian Zschocke Date: Mon, 27 Jan 2020 21:46:23 +0100 Subject: Use GitHub org property in octokit calls --- build.xml | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/build.xml b/build.xml index f9ec975f..ca356d34 100644 --- a/build.xml +++ b/build.xml @@ -802,7 +802,7 @@ releaseVersion="${project.version}"/> - + @@ -1100,11 +1100,11 @@ Creating release ${project.tag} draft on GitHub - + - + @@ -1112,7 +1112,7 @@ - + @@ -1145,7 +1145,7 @@ publishing GitHub release draft @{releaseid} - + @@ -1160,7 +1160,7 @@ - + -- cgit v1.2.3 From b4b0cfa6065ca4b83577503b607e5db1286b876a Mon Sep 17 00:00:00 2001 From: Florian Zschocke Date: Mon, 27 Jan 2020 21:53:15 +0100 Subject: Doc: Update built-in docs with missing pages and links Add pages for `fail2ban` and GFS to the `generateDocs` target, so that they also show up in the documentation on GO etc. Also adjust the links to issues and commits. Add the replacement of the download base URL. --- build.xml | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/build.xml b/build.xml index ca356d34..768d6c0b 100644 --- a/build.xml +++ b/build.xml @@ -949,6 +949,8 @@ + +
@@ -996,10 +998,13 @@
- + + + - - + + + -- cgit v1.2.3 From ae6e8a5095af27769d50269923ef1758b3cec799 Mon Sep 17 00:00:00 2001 From: Florian Zschocke Date: Tue, 28 Jan 2020 11:44:55 +0100 Subject: Doc: Remove Google+ from Readme Since Google Plus doesn't exist any more... --- README.markdown | 1 - 1 file changed, 1 deletion(-) diff --git a/README.markdown b/README.markdown index 53776f8a..02c1fd0c 100644 --- a/README.markdown +++ b/README.markdown @@ -26,7 +26,6 @@ Getting help | Documentation | [Gitblit website](http://gitblit.com) | | Forums | [Google Groups](https://groups.google.com/forum/#!forum/gitblit) | | Twitter | @gitblit or @jamesmoger | -| Google+ | +gitblit or +jamesmoger | Contributing ------------ -- cgit v1.2.3 From 0cdc0a00d3a99050a4ac2acdfd7d1e7732f2581c Mon Sep 17 00:00:00 2001 From: Florian Zschocke Date: Tue, 28 Jan 2020 13:40:37 +0100 Subject: Doc: Change download button on Readme to point to GitHub --- README.markdown | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.markdown b/README.markdown index 02c1fd0c..a40ccdf6 100644 --- a/README.markdown +++ b/README.markdown @@ -5,7 +5,7 @@ Gitblit is an open source, pure Java Git solution for managing, viewing, and ser More information about Gitblit can be found [here](http://gitblit.com). - + License ------- -- cgit v1.2.3 From 89ef6880b39a3f3ba521984c0630b26fc6850358 Mon Sep 17 00:00:00 2001 From: Florian Zschocke Date: Tue, 28 Jan 2020 13:53:39 +0100 Subject: Add Maven Central to registeredRepositories Moxie does have these defined by default, but with transport HTTP. Since January 2020, Maven Central only accepts HTTPS, no HTTP anymore. So we add them here explicitly, with `https://`. We add it to the `build.moxie` file, instead of keeping it in a local `settings.moxie`, so that a CI server can immediately use it. --- build.moxie | 2 ++ 1 file changed, 2 insertions(+) diff --git a/build.moxie b/build.moxie index f4297923..d95db89a 100644 --- a/build.moxie +++ b/build.moxie @@ -94,6 +94,8 @@ dependencyDirectory: ext # Register the Eclipse JGit Maven repositories registeredRepositories: +- { id: central, url: 'https://repo1.maven.org/maven2' } +- { id: mavencentral, url: 'https://repo1.maven.org/maven2' } - { id: eclipse, url: 'http://repo.eclipse.org/content/groups/releases' } - { id: eclipse-snapshots, url: 'http://repo.eclipse.org/content/groups/snapshots' } - { id: gitblit, url: 'http://gitblit.github.io/gitblit-maven' } -- cgit v1.2.3 From 684e554f0ef81bc10d84823827c80f9d6b92ccb8 Mon Sep 17 00:00:00 2001 From: Florian Zschocke Date: Tue, 28 Jan 2020 21:23:25 +0100 Subject: Build: Fix output prints --- build.xml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/build.xml b/build.xml index 768d6c0b..3d47080f 100644 --- a/build.xml +++ b/build.xml @@ -796,7 +796,7 @@ --> - Publishing Gitblit ${project.version} release draft on GitHub + Publishing Gitblit ${project.version} release draft on GitHub for tag ${release.tag} @@ -1102,7 +1102,7 @@ - Creating release ${project.tag} draft on GitHub + creating release ${release.tag} draft on GitHub -- cgit v1.2.3 From 7180b22b2b693c71d53fa1c32a106a91133404ae Mon Sep 17 00:00:00 2001 From: Florian Zschocke Date: Tue, 28 Jan 2020 21:26:12 +0100 Subject: Doc: Fix change log in built-in docs Use the same templates as in `buildSite` to generate the change log in the built-in documentation. --- build.xml | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/build.xml b/build.xml index 3d47080f..85d06e6b 100644 --- a/build.xml +++ b/build.xml @@ -982,10 +982,16 @@ + - - + +