|
| 1 | +#!/bin/bash |
| 2 | + |
| 3 | +set -euo pipefail |
| 4 | + |
| 5 | +script_name="get_next_sequence" |
| 6 | + |
| 7 | +namespace="${HWS_NAMESPACE:-}" |
| 8 | +key="${HWS_KEY:-}" |
| 9 | +root_tag="${HWS_ROOT_TAG:-}" |
| 10 | +seed="${HWS_SEED:-1}" |
| 11 | +with_cleanup="${HWS_WITH_CLEANUP:-false}" |
| 12 | +dry_run="${HWS_DRY_RUN:-false}" |
| 13 | + |
| 14 | +log() { |
| 15 | + local timestamp level label value color reset |
| 16 | + timestamp=$(date '+%Y-%m-%d %H:%M:%S') |
| 17 | + |
| 18 | + case "$1" in |
| 19 | + ERROR) color='\033[0;31m'; level=$1; label=$2; value="${3-}" ;; |
| 20 | + WARN) color='\033[0;33m'; level=$1; label=$2; value="${3-}" ;; |
| 21 | + INFO) color=''; level=$1; label=$2; value="${3-}" ;; |
| 22 | + *) color=''; level="INFO"; label=$1; value="${2-}" ;; |
| 23 | + esac |
| 24 | + |
| 25 | + reset='\033[0m' |
| 26 | + |
| 27 | + if [[ -n "$value" ]]; then |
| 28 | + printf "${color}[%s] %-19s %-7s %-30s %s${reset}\n" \ |
| 29 | + "$script_name" "$timestamp" "$level" "$label" "[$value]" >&2 |
| 30 | + else |
| 31 | + printf "${color}[%s] %-19s %-7s %s${reset}\n" \ |
| 32 | + "$script_name" "$timestamp" "$level" "$label" >&2 |
| 33 | + fi |
| 34 | +} |
| 35 | + |
| 36 | + |
| 37 | +while [[ "$#" -gt 0 ]]; do |
| 38 | + case "$1" in |
| 39 | + --namespace=*) namespace="${1#*=}"; shift ;; |
| 40 | + --namespace) namespace="$2"; shift 2 ;; |
| 41 | + --key=*) key="${1#*=}"; shift ;; |
| 42 | + --key) key="$2"; shift 2 ;; |
| 43 | + --root-tag=*) root_tag="${1#*=}"; shift ;; |
| 44 | + --root-tag) root_tag="$2"; shift 2 ;; |
| 45 | + --seed=*) seed="${1#*=}"; shift ;; |
| 46 | + --seed) seed="$2"; shift 2 ;; |
| 47 | + --with-cleanup) with_cleanup=true; shift ;; |
| 48 | + --dry-run) dry_run=true; shift ;; |
| 49 | + *) log "unknown argument" "$1"; exit 1 ;; |
| 50 | + esac |
| 51 | +done |
| 52 | + |
| 53 | +if [[ -z "$namespace" ]]; then |
| 54 | + log ERROR "usage: $0 --namespace <namespace> [--key <key>] [--root-tag <tag>] [--seed <num>] [--with-cleanup] [--dry-run]" |
| 55 | + exit 1 |
| 56 | +fi |
| 57 | + |
| 58 | +if [[ -n "$seed" && ! "$seed" =~ ^[0-9]+$ ]]; then |
| 59 | + log ERROR "seed must be a non-negative integer" "$seed" |
| 60 | + exit 1 |
| 61 | +fi |
| 62 | + |
| 63 | +command -v GIT_TERMINAL_PROMPT=0 git >/dev/null || { |
| 64 | + log ERROR "git not installed" |
| 65 | + exit 1 |
| 66 | +} |
| 67 | + |
| 68 | +if ! GIT_TERMINAL_PROMPT=0 git rev-parse --is-inside-work-tree >/dev/null 2>&1; then |
| 69 | + log ERROR "not inside a git repository" |
| 70 | + exit 1 |
| 71 | +fi |
| 72 | + |
| 73 | +find_max_sequence() { |
| 74 | + local sequence_base=$1 |
| 75 | + shift |
| 76 | + local matches=("$@") |
| 77 | + |
| 78 | + if [[ ${#matches[@]} -eq 0 || ( ${#matches[@]} -eq 1 && -z "${matches[0]}" ) ]]; then |
| 79 | + log "no matching tags found" |
| 80 | + return 0 |
| 81 | + elif [[ "${#matches[@]}" -eq 1 ]]; then |
| 82 | + log "one matching tag found" "${matches[0]}" |
| 83 | + echo "${matches[0]##"$sequence_base"-}" |
| 84 | + return 0 |
| 85 | + else |
| 86 | + log "multiple matching tags found" |
| 87 | + local max_num=-1 |
| 88 | + for tag in "${matches[@]}"; do |
| 89 | + local num=${tag##"$sequence_base"-} |
| 90 | + log "checking" "$tag" |
| 91 | + log "comparing" "$num > $max_num" |
| 92 | + if [[ "$num" =~ ^[0-9]+$ ]]; then |
| 93 | + (( num > max_num )) && max_num=$num |
| 94 | + else |
| 95 | + log ERROR "num not numeric" "$num" |
| 96 | + exit 1 |
| 97 | + fi |
| 98 | + done |
| 99 | + log "determined max sequence" "$max_num" |
| 100 | + echo "$max_num" |
| 101 | + return 0 |
| 102 | + fi |
| 103 | +} |
| 104 | + |
| 105 | +get_ref_commit() { |
| 106 | + local root_tag="$1" |
| 107 | + |
| 108 | + if [[ -n "$root_tag" ]]; then |
| 109 | + if ref_commit=$(GIT_TERMINAL_PROMPT=0 git rev-parse "$root_tag" 2>/dev/null); then |
| 110 | + echo "$ref_commit" |
| 111 | + return 0 |
| 112 | + else |
| 113 | + log ERROR "root tag not found" "$root_tag" |
| 114 | + return 1 |
| 115 | + fi |
| 116 | + else |
| 117 | + # get current branch's latest commit hash |
| 118 | + if ref_commit=$(GIT_TERMINAL_PROMPT=0 git rev-parse HEAD 2>/dev/null); then |
| 119 | + echo "$ref_commit" |
| 120 | + return 0 |
| 121 | + else |
| 122 | + log ERROR "could not get current HEAD commit" |
| 123 | + return 1 |
| 124 | + fi |
| 125 | + fi |
| 126 | +} |
| 127 | + |
| 128 | + |
| 129 | +cleanup() { |
| 130 | + local matches=("${!1}") |
| 131 | + local next_tag=$2 |
| 132 | + |
| 133 | + for tag in "${matches[@]}"; do |
| 134 | + if [[ "$tag" != "$next_tag" ]]; then |
| 135 | + log "deleting tag" "$tag" |
| 136 | + if [[ "$dry_run" == true ]]; then |
| 137 | + log "[DRY RUN] push tag delete" "$tag" |
| 138 | + log "[DRY RUN] local tag delete" "$tag" |
| 139 | + else |
| 140 | + if ! GIT_TERMINAL_PROMPT=0 git push --delete origin "$tag" --quiet >&2; then |
| 141 | + log WARN "failed to push tag delete" "$tag" |
| 142 | + fi |
| 143 | + if ! GIT_TERMINAL_PROMPT=0 git tag -d "$tag" >/dev/null; then |
| 144 | + log WARN "failed to delete local tag" "$tag" |
| 145 | + fi |
| 146 | + fi |
| 147 | + fi |
| 148 | + done |
| 149 | +} |
| 150 | + |
| 151 | +sequence_base="$namespace" |
| 152 | +log "setting sequence base" "$sequence_base" |
| 153 | + |
| 154 | +if [[ -n "$key" ]]; then |
| 155 | + sequence_base+="-$key" |
| 156 | + log "updating sequence base" "$sequence_base" |
| 157 | +fi |
| 158 | + |
| 159 | +ref_commit=$(get_ref_commit "$root_tag") || exit 1 |
| 160 | + |
| 161 | +log "finding tags that start with" "$sequence_base-" |
| 162 | +matches=() |
| 163 | +while IFS= read -r tag; do |
| 164 | + log "found tag" "$tag" |
| 165 | + matches+=("$tag") |
| 166 | +done < <(GIT_TERMINAL_PROMPT=0 git tag -l "$sequence_base-*" | grep -E "^${sequence_base}-[0-9]+$") |
| 167 | + |
| 168 | +current_sequence=$(find_max_sequence "$sequence_base" "${matches[@]-}") |
| 169 | + |
| 170 | +if [[ -n "$current_sequence" ]]; then |
| 171 | + log "existing sequence" "$current_sequence" |
| 172 | + next_sequence=$((current_sequence + 1)) |
| 173 | +else |
| 174 | + log WARN "falling back to seed" "$seed" |
| 175 | + next_sequence=$seed |
| 176 | +fi |
| 177 | + |
| 178 | +next_tag="$sequence_base-$next_sequence" |
| 179 | + |
| 180 | +log "setting this sequence" "$next_sequence" |
| 181 | +log "setting this sequence tag" "$next_tag" |
| 182 | + |
| 183 | +if [[ "$dry_run" == true ]]; then |
| 184 | + log "[DRY RUN] local tag" "$next_tag" |
| 185 | + log "[DRY RUN] push tag" "$next_tag" |
| 186 | +else |
| 187 | + if ! GIT_TERMINAL_PROMPT=0 git tag "$next_tag" "$ref_commit" >&2; then |
| 188 | + log ERROR "failed to tag [$ref_commit] with [$next_tag]" |
| 189 | + exit 1 |
| 190 | + fi |
| 191 | + if ! GIT_TERMINAL_PROMPT=0 git push origin "$next_tag" --quiet >&2; then |
| 192 | + log ERROR "failed to push tag" "$next_tag" |
| 193 | + exit 1 |
| 194 | + fi |
| 195 | +fi |
| 196 | + |
| 197 | +cleanup_log_value="flag=$with_cleanup, matches ${#matches[@]}" |
| 198 | +if [[ "$with_cleanup" == true && ${#matches[@]} -gt 0 ]]; then |
| 199 | + log "starting cleanup" "$cleanup_log_value" |
| 200 | + cleanup "matches[@]" "$next_tag" |
| 201 | +else |
| 202 | + log "skipping cleanup" "$cleanup_log_value" |
| 203 | +fi |
| 204 | + |
| 205 | +log "returning sequence" "$next_sequence" |
| 206 | +log "returning tag" "$next_tag" |
| 207 | + |
| 208 | +cat <<EOF |
| 209 | +sequence=$next_sequence |
| 210 | +tag=$next_tag |
| 211 | +EOF |
| 212 | + |
| 213 | +exit 0 |
0 commit comments