docker release
This commit is contained in:
117
bkp/azdo-tickets-between-branches.sh
Executable file
117
bkp/azdo-tickets-between-branches.sh
Executable file
@@ -0,0 +1,117 @@
|
||||
#!/usr/bin/env bash
|
||||
#
|
||||
# Get all work items (tickets) between two branches in Azure DevOps.
|
||||
#
|
||||
# Reports tickets from TWO sources:
|
||||
# 1. Formally linked work items (git commit <-> work item links)
|
||||
# 2. Ticket numbers scraped from commit messages (this repo references
|
||||
# tickets in text far more than it formally links them)
|
||||
#
|
||||
# Usage:
|
||||
# ./azdo-tickets-between-branches.sh <baseBranch> <targetBranch>
|
||||
#
|
||||
# Example:
|
||||
# ./azdo-tickets-between-branches.sh master release/5.4.0
|
||||
#
|
||||
# Requires: curl, jq
|
||||
# Auth: set a Personal Access Token in AZDO_PAT (scopes: Code Read + Work Items Read).
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
# ---- Config (override via env vars) -----------------------------------------
|
||||
# Project collection base URL (host + collection), e.g. old visualstudio.com style.
|
||||
COLLECTION_URL="${AZDO_COLLECTION_URL:-https://flashmobilevending.visualstudio.com/DefaultCollection}"
|
||||
PROJECT="${AZDO_PROJECT:-GLU}"
|
||||
REPO="${AZDO_REPO:-glu-flash-console}"
|
||||
PAT="${AZDO_PAT:-}"
|
||||
API_VERSION="7.1"
|
||||
# Regex for ticket numbers in commit messages (this project uses 6-digit IDs).
|
||||
TICKET_REGEX="${AZDO_TICKET_REGEX:-[0-9]{6}}"
|
||||
# -----------------------------------------------------------------------------
|
||||
|
||||
BASE="${1:-}"
|
||||
TARGET="${2:-}"
|
||||
|
||||
if [[ -z "$BASE" || -z "$TARGET" ]]; then
|
||||
echo "Usage: $0 <baseBranch> <targetBranch>" >&2
|
||||
echo "Example: $0 master release/5.4.0" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if [[ -z "$PAT" ]]; then
|
||||
echo "Error: set AZDO_PAT to a Personal Access Token (Code Read + Work Items Read)." >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
for cmd in curl jq; do
|
||||
command -v "$cmd" >/dev/null 2>&1 || { echo "Error: '$cmd' is required." >&2; exit 1; }
|
||||
done
|
||||
|
||||
GIT_URL="${COLLECTION_URL}/${PROJECT}/_apis/git/repositories/${REPO}"
|
||||
WIT_URL="${COLLECTION_URL}/${PROJECT}/_apis/wit/workitems"
|
||||
|
||||
# ---- Step 1: commits on TARGET but not on BASE, with linked work items -------
|
||||
commits_json=$(curl -sf -u ":${PAT}" \
|
||||
"${GIT_URL}/commitsbatch?\$top=1000&api-version=${API_VERSION}" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d "{
|
||||
\"itemVersion\": {\"versionType\":\"branch\",\"version\":\"${BASE}\"},
|
||||
\"compareVersion\": {\"versionType\":\"branch\",\"version\":\"${TARGET}\"},
|
||||
\"includeWorkItems\": true
|
||||
}")
|
||||
|
||||
# 1a. Formally linked work item IDs
|
||||
linked_ids=$(echo "$commits_json" | jq -r '[.value[].workItems[]?.id] | unique | .[]' || true)
|
||||
|
||||
# 1b. Ticket numbers scraped from commit messages.
|
||||
# Strip "Merged PR NNNNN" first so PR ids aren't mistaken for tickets.
|
||||
scraped_ids=$(echo "$commits_json" \
|
||||
| jq -r '.value[].comment' \
|
||||
| sed -E 's/[Mm]erged PR [0-9]+//g' \
|
||||
| grep -oE "$TICKET_REGEX" \
|
||||
| sort -u || true)
|
||||
|
||||
# Union of both sources
|
||||
all_ids=$(printf '%s\n%s\n' "$linked_ids" "$scraped_ids" | grep -E '^[0-9]+$' | sort -un || true)
|
||||
|
||||
commit_count=$(echo "$commits_json" | jq '.value | length')
|
||||
echo "Comparing '${TARGET}' against '${BASE}' (${commit_count} commits ahead)"
|
||||
echo " Formally linked: $(echo "$linked_ids" | grep -c . || true)"
|
||||
echo " Scraped from msgs: $(echo "$scraped_ids" | grep -c . || true)"
|
||||
echo
|
||||
|
||||
if [[ -z "$all_ids" ]]; then
|
||||
echo "No tickets found (no links and no ticket numbers in commit messages)."
|
||||
exit 0
|
||||
fi
|
||||
|
||||
# ---- Step 2: hydrate work item details --------------------------------------
|
||||
ids_csv=$(echo "$all_ids" | paste -sd, -)
|
||||
|
||||
http_status=$(curl -s -o /tmp/azdo_wit.json -w "%{http_code}" -u ":${PAT}" \
|
||||
"${WIT_URL}?ids=${ids_csv}&fields=System.Id,System.WorkItemType,System.State,System.Title&errorPolicy=omit&api-version=${API_VERSION}")
|
||||
|
||||
if [[ "$http_status" != "200" ]]; then
|
||||
echo "Warning: could not fetch work item details (HTTP ${http_status})." >&2
|
||||
if [[ "$http_status" == "401" || "$http_status" == "403" ]]; then
|
||||
echo " Your PAT is likely missing the 'Work Items (Read)' scope." >&2
|
||||
fi
|
||||
echo " Falling back to ticket IDs only:" >&2
|
||||
echo
|
||||
echo "$all_ids"
|
||||
echo
|
||||
echo "View any of them at: ${COLLECTION_URL}/${PROJECT}/_workitems/edit/<id>"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
# errorPolicy=omit returns null entries for ids that aren't real work items;
|
||||
# filter those out so scraped false-positives disappear.
|
||||
{
|
||||
printf 'ID\tTYPE\tSTATE\tTITLE\n'
|
||||
jq -r '.value[] | select(. != null)
|
||||
| [ (.fields["System.Id"]|tostring),
|
||||
.fields["System.WorkItemType"],
|
||||
.fields["System.State"],
|
||||
(.fields["System.Title"] // "") ]
|
||||
| @tsv' /tmp/azdo_wit.json
|
||||
} | column -t -s $'\t'
|
||||
Reference in New Issue
Block a user