|
| 1 | +# functions.sh |
| 2 | + |
| 3 | +# _exit should be called from the main script only. If called from |
| 4 | +# another function, it will not cause the main script to exit |
| 5 | +_exit() { |
| 6 | + local exit_code="$1" |
| 7 | + local message="$2" |
| 8 | + if [ "$exit_code" -ne 0 ]; then |
| 9 | + echo -e "\nERROR: $message" |
| 10 | + else |
| 11 | + echo -e "\n$message" |
| 12 | + fi |
| 13 | + echo "Exiting script [exit_code=$exit_code]" |
| 14 | + exit "$exit_code" |
| 15 | +} |
| 16 | + |
| 17 | +getTestDir() { |
| 18 | + echo "/tmp" |
| 19 | +} |
| 20 | + |
| 21 | +getTestRepoName() { |
| 22 | + echo "spring-modulith-temp-copy" |
| 23 | +} |
| 24 | + |
| 25 | +isBlank() { |
| 26 | + [[ "$1" =~ ^[[:space:]]*$ ]] |
| 27 | +} |
| 28 | + |
| 29 | +isValidIssueNumber() { |
| 30 | + local issue="$1" |
| 31 | + # Check if the provided number matches an existing issue number |
| 32 | + # Note: The issue status should be "open" (backporting is done before an issue is closed) |
| 33 | + # But it is not necessary to filter on status - it is preferable not to constrain |
| 34 | + if ! gh issue list --limit 10000 --state "all" --json number --jq '.[].number' | grep -x -q "^$issue$"; then |
| 35 | + echo "The provided issue number [$issue] does not match an existing GitHub issue number" |
| 36 | + # output value |
| 37 | + false |
| 38 | + else |
| 39 | + echo "The provided issue number [$issue] matches an existing GitHub issue number" |
| 40 | + # output value |
| 41 | + true |
| 42 | + fi |
| 43 | +} |
| 44 | + |
| 45 | +isValidVersionNumber() { |
| 46 | + local version="$1" |
| 47 | + # Check input format |
| 48 | + local regex='^[0-9]+\.[0-9]+\.[0-9]+$' |
| 49 | + if [[ "$version" =~ $regex ]]; then |
| 50 | + echo "Version [$version] matches the required format" |
| 51 | + # Check for branch |
| 52 | + local targetBranch=$(getTargetBranch "$version") |
| 53 | + local branch=$(git ls-remote --heads origin "$targetBranch") |
| 54 | + if [[ -n "$branch" ]]; then |
| 55 | + echo "Branch [$targetBranch] exists" |
| 56 | + # Check for milestone |
| 57 | + local targetMilestone=$(getTargetMilestone "$version") |
| 58 | + local milestone=$(gh api repos/:owner/:repo/milestones --jq ".[] | select(.title == \"$targetMilestone\") | .title") |
| 59 | + if [[ -n "$milestone" ]]; then |
| 60 | + echo "Milestone [$targetMilestone] exists" |
| 61 | + true |
| 62 | + else |
| 63 | + echo "Milestone [$targetMilestone] does not exist" |
| 64 | + false |
| 65 | + fi |
| 66 | + else |
| 67 | + echo "Branch [$targetBranch] does not exist" |
| 68 | + false |
| 69 | + fi |
| 70 | + else |
| 71 | + echo "Version [$version] does not match the required format [$regex]" |
| 72 | + false |
| 73 | + fi |
| 74 | +} |
| 75 | + |
| 76 | +getTargetBranch() { |
| 77 | + local version="$1" |
| 78 | + local targetBranch="$(echo $version | grep -oE '^[0-9]+\.[0-9]+').x" |
| 79 | + echo "$targetBranch" |
| 80 | +} |
| 81 | + |
| 82 | +getTargetMilestone() { |
| 83 | + local version="$1" |
| 84 | + local targetMilestone="$version" |
| 85 | + echo "$targetMilestone" |
| 86 | +} |
| 87 | + |
| 88 | +isDefaultBranch() { |
| 89 | + local current_branch=$(git rev-parse --abbrev-ref HEAD) |
| 90 | + local default_branch=$(gh repo view --json defaultBranchRef --jq '.defaultBranchRef.name') |
| 91 | + if [ "$current_branch" != "$default_branch" ]; then |
| 92 | + echo "Current branch [$current_branch] is NOT the default branch [$default_branch]" |
| 93 | + # output value |
| 94 | + false |
| 95 | + else |
| 96 | + echo "Current branch [$current_branch] is the default branch" |
| 97 | + # output value |
| 98 | + true |
| 99 | + fi |
| 100 | +} |
| 101 | + |
| 102 | +isCleanBranch() { |
| 103 | + local branch=$(git symbolic-ref --short HEAD) |
| 104 | + local remote_branch="origin/$branch" |
| 105 | + |
| 106 | + # Check for ongoing cherry-pick |
| 107 | + if [ -d .git/sequencer ]; then |
| 108 | + echo "Error: Ongoing cherry-pick operation detected." |
| 109 | + # return code |
| 110 | + return 1 |
| 111 | + fi |
| 112 | + |
| 113 | + # Check for uncommitted changes |
| 114 | + if ! git diff-index --quiet HEAD --; then |
| 115 | + echo "Error: Uncommitted changes in the working directory." |
| 116 | + # return code |
| 117 | + return 1 |
| 118 | + fi |
| 119 | + |
| 120 | + # Check for untracked files and directories |
| 121 | + if [ -n "$(git clean -fdn)" ]; then |
| 122 | + echo "Error: Untracked files or directories present." |
| 123 | + # return code |
| 124 | + return 1 |
| 125 | + fi |
| 126 | + |
| 127 | + # Fetch latest changes from the remote |
| 128 | + git fetch origin &>/dev/null |
| 129 | + |
| 130 | + # Check if local branch is ahead/behind the remote branch |
| 131 | + local local_status=$(git rev-list --left-right --count ${branch}...${remote_branch}) |
| 132 | + local ahead=$(echo $local_status | awk '{print $1}') |
| 133 | + local behind=$(echo $local_status | awk '{print $2}') |
| 134 | + |
| 135 | + if [ "$ahead" -ne 0 ]; then |
| 136 | + echo "Error: Local branch is ahead of the remote branch by $ahead commit(s)." |
| 137 | + # return code |
| 138 | + return 1 |
| 139 | + elif [ "$behind" -ne 0 ]; then |
| 140 | + echo "Error: Local branch is behind the remote branch by $behind commit(s)." |
| 141 | + # return code |
| 142 | + return 1 |
| 143 | + fi |
| 144 | + |
| 145 | + # If all checks pass |
| 146 | + echo "Local branch matches the remote branch." |
| 147 | + # return code |
| 148 | + return 0 |
| 149 | +} |
| 150 | + |
| 151 | +getGHCode() { |
| 152 | + echo "GH-$1" |
| 153 | +} |
| 154 | + |
| 155 | +getIssueCandidatesForMilestone() { |
| 156 | + local number="$1" |
| 157 | + local milestone="$2" |
| 158 | + |
| 159 | + local json=$(gh issue view "$number" --json=title,labels) |
| 160 | + local title=$(echo "$json" | jq -r '.title') |
| 161 | + local labels=$(echo "$json" | jq -r '.labels[].name' | paste -sd ',' -) |
| 162 | + local body="Back-port of $(getGHCode $number)." |
| 163 | + |
| 164 | + local targetCandidateNumbers=$(gh issue list --limit 10000 --state "open" --assignee "@me" --label "$labels" --milestone "$targetMilestone" --json number,title,body --jq ' |
| 165 | + .[] | select(.title == "'"$title"'" and (.body | contains("'"$body"'")) ) | .number') |
| 166 | + echo "$targetCandidateNumbers" |
| 167 | +} |
| 168 | + |
| 169 | +createIssueForMilestone() { |
| 170 | + local number="$1" |
| 171 | + local milestone="$2" |
| 172 | + |
| 173 | + local json=$(gh issue view "$number" --json=title,labels) |
| 174 | + local title=$(echo "$json" | jq -r '.title') |
| 175 | + local labels=$(echo "$json" | jq -r '.labels[].name' | paste -sd ',' -) |
| 176 | + local body="Back-port of $(getGHCode $number)." |
| 177 | + |
| 178 | + local targetNumber=$(gh issue create --assignee "@me" --label "$labels" --milestone "$targetMilestone" --title "$title" --body "$body" | awk -F '/' '{print $NF}') |
| 179 | + echo "$targetNumber" |
| 180 | +} |
| 181 | + |
| 182 | +isCleanIssue() { |
| 183 | + local targetNumber="$1" |
| 184 | + local targetGh=$(getGHCode "$targetNumber") |
| 185 | + |
| 186 | + # Check for commits mentioning the issue number |
| 187 | + # $test_filter set globally in calling script |
| 188 | + local commits |
| 189 | + if [[ "$test_filter" == "" ]]; then |
| 190 | + commits=$(git log --grep="\b$targetGh\b") |
| 191 | + else |
| 192 | + commits=$(git log --grep="\b$targetGh\b" "$test_filter") |
| 193 | + fi |
| 194 | + if [ -z "$commits" ]; then |
| 195 | + # There are no commits that reference this issue |
| 196 | + # output value |
| 197 | + true |
| 198 | + else |
| 199 | + # output value |
| 200 | + false |
| 201 | + fi |
| 202 | +} |
| 203 | + |
| 204 | +updateCommitMessage() { |
| 205 | + local source="$1" |
| 206 | + local target="$2" |
| 207 | + local message=$(git log -1 --pretty=format:"%B" | sed "s/$source/$target/g") |
| 208 | + if [[ $(echo $message | grep "$target") != "" ]]; then |
| 209 | + # Update commit message to refer to new ticket |
| 210 | + git commit --amend -m "$message" |
| 211 | + [ "$?" -eq 0 ] || return 1 |
| 212 | + return 0 |
| 213 | + else |
| 214 | + return 1 |
| 215 | + fi |
| 216 | +} |
0 commit comments