|
| 1 | +#!/bin/bash |
| 2 | + |
| 3 | +# Branch regex we consider for rebase targets. |
| 4 | +# For our purposes this is usually 'stable-haskell/feature/*'. |
| 5 | +# 'master' is always considered. |
| 6 | +branch_regex=$1 |
| 7 | +shift 1 |
| 8 | +declare -a input_branches |
| 9 | +input_branches=( "$@" ) |
| 10 | +set -eu |
| 11 | + |
| 12 | +[ ${#input_branches[@]} -eq 0 ] && |
| 13 | + input_branches=( $(gh pr list --label rebase --state open --json headRefName --jq ".[] | select( .headRefName | match(\"${branch_regex}\")) | .headRefName" --template '{{range .}}{{tablerow .headRefName}}{{end}}') ) |
| 14 | + |
| 15 | +branch_list=( ) |
| 16 | +declare -A branch_map |
| 17 | + |
| 18 | +# @FUNCTION: die |
| 19 | +# @USAGE: [msg] |
| 20 | +# @DESCRIPTION: |
| 21 | +# Exits the shell script with status code 2 |
| 22 | +# and prints the given message in red to STDERR, if any. |
| 23 | +die() { |
| 24 | + (>&2 red_message "$1") |
| 25 | + exit 2 |
| 26 | +} |
| 27 | + |
| 28 | +# @FUNCTION: red_message |
| 29 | +# @USAGE: <msg> |
| 30 | +# @DESCRIPTION: |
| 31 | +# Print a red message. |
| 32 | +red_message() { |
| 33 | + printf "\\033[0;31m%s\\033[0m\\n" "$1" |
| 34 | +} |
| 35 | + |
| 36 | +# @FUNCTION: array_contains |
| 37 | +# @USAGE: <arr_ref> <val> |
| 38 | +# @DESCRIPTION: |
| 39 | +# Checks whether the array reference contains the given value. |
| 40 | +# @RETURN: 0 if value exists, 1 otherwise |
| 41 | +array_contains() { |
| 42 | + local -n arr=$1 |
| 43 | + local val=$2 |
| 44 | + shift 2 |
| 45 | + if [[ " ${arr[*]} " =~ [[:space:]]${val}[[:space:]] ]]; then |
| 46 | + return 0 |
| 47 | + else |
| 48 | + return 1 |
| 49 | + fi |
| 50 | +} |
| 51 | + |
| 52 | +max_backtrack=10 |
| 53 | + |
| 54 | +# @FUNCTION: backtrack |
| 55 | +# @USAGE: <map_ref> <start_key> <abort_value> |
| 56 | +# @DESCRIPTION: |
| 57 | +# Backtrack dependencies through an array list. |
| 58 | +# E.g. given an associated array with key value pairs of: |
| 59 | +# B1 -> M |
| 60 | +# B2 -> B1 |
| 61 | +# B3 -> B2 |
| 62 | +# |
| 63 | +# ...if we pass B3 as start_key and M as abort_value, then |
| 64 | +# we receive the flattened ordered list "B1 B2 B3" |
| 65 | +# @STDOUT: space separated list of backtracked values |
| 66 | +backtrack() { |
| 67 | + backtrack_ 0 "$1" "$2" "$3" |
| 68 | +} |
| 69 | + |
| 70 | +# internal to track backtrack depth |
| 71 | +backtrack_() { |
| 72 | + local depth=$1 |
| 73 | + if [[ $depth -gt $max_backtrack ]] ; then |
| 74 | + die "Dependency backtracking too deep... aborting!" |
| 75 | + fi |
| 76 | + shift 1 |
| 77 | + |
| 78 | + if [[ $1 != map ]] ; then |
| 79 | + local -n map=$1 |
| 80 | + fi |
| 81 | + |
| 82 | + local base=$2 |
| 83 | + local abort_value=$3 |
| 84 | + local value |
| 85 | + |
| 86 | + if [ "${base}" = "${abort_value}" ] ; then |
| 87 | + return |
| 88 | + fi |
| 89 | + |
| 90 | + value=${map[$base]} |
| 91 | + |
| 92 | + if [ "${value}" = "${abort_value}" ] ; then |
| 93 | + if ! array_contains branch_list "${base}" ; then |
| 94 | + echo "${base}" |
| 95 | + fi |
| 96 | + else |
| 97 | + if array_contains branch_list "${base}" ; then |
| 98 | + backtrack_ $((depth++)) map "${map[$value]}" "${abort_value}" |
| 99 | + else |
| 100 | + echo "$(backtrack_ $((depth++)) map "${map[$base]}" "${abort_value}")" "${base}" |
| 101 | + fi |
| 102 | + fi |
| 103 | +} |
| 104 | + |
| 105 | +{ |
| 106 | + |
| 107 | +# create branch rebase tree |
| 108 | +# we're doing that on the state of the local tree/master |
| 109 | +while IFS= read -r branch || [[ -n $branch ]]; do |
| 110 | + rebase_target=$(git branch --merged "${branch}" --sort="ahead-behind:${branch}" --format="%(refname:short)" | grep -e "${branch_regex}" -e '^master$' | awk 'NR==2{print;exit}') |
| 111 | + |
| 112 | + # check that rebase target is equal or later than local master |
| 113 | + if [ "${rebase_target}" != "master" ] ; then |
| 114 | + git for-each-ref --contains="master" --format="%(refname)" -- "refs/heads/${branch}" | |
| 115 | + grep --quiet "^refs/heads/${branch}$" || |
| 116 | + die "rebase target ${rebase_target} seems to be older in the history than the master branch ...aborting!" |
| 117 | + fi |
| 118 | + |
| 119 | + branch_map["${branch}"]="${rebase_target}" |
| 120 | +done < <(printf '%s\n' "${input_branches[@]}") |
| 121 | + |
| 122 | +} >&2 |
| 123 | + |
| 124 | +# flatten recursively |
| 125 | +for key in "${!branch_map[@]}"; do |
| 126 | + value=${branch_map[$key]} |
| 127 | + if [ "${value}" = "master" ] ; then |
| 128 | + if ! array_contains branch_list "${key}" ; then |
| 129 | + branch_list+=( "${key}" ) |
| 130 | + fi |
| 131 | + else |
| 132 | + # shellcheck disable=SC2207 |
| 133 | + branch_list+=( $(backtrack branch_map "$key" "master") ) |
| 134 | + fi |
| 135 | +done |
| 136 | +unset key |
| 137 | + |
| 138 | +result=( ) |
| 139 | +for key in "${branch_list[@]}"; do |
| 140 | + result+=( "${key}:${branch_map[$key]}" ) |
| 141 | +done |
| 142 | +echo "${result[@]}" |
| 143 | + |
0 commit comments