summaryrefslogtreecommitdiffstats
path: root/src/ci/scripts/verify-backported-commits.sh
blob: d3da6d1ac915a3a24dd5ca14f0ef582c20042c8b (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
#!/bin/bash
# Ensure commits in beta are in master & commits in stable are in beta + master.
set -euo pipefail
IFS=$'\n\t'

source "$(cd "$(dirname "$0")" && pwd)/../shared.sh"

# We don't care about commits that predate this automation check, so we pass a
# `<limit>` argument to `git cherry`.
BETA_LIMIT="53fd98ca776cb875bc9e5514f56b52eb74f9e7a9"
STABLE_LIMIT="a178d0322ce20e33eac124758e837cbd80a6f633"

verify_backported_commits_main() {
  ci_base_branch=$(ciBaseBranch)

  if [[ "$ci_base_branch" != "beta" && "$ci_base_branch" != "stable" ]]; then
    echo 'Skipping. This is only run when merging to the beta or stable branches.'
    exit 0
  fi

  if [[ $ci_base_branch == "beta" ]]; then
    verify_cherries master "$BETA_LIMIT" \
      || exit 1

  elif [[ $ci_base_branch == "stable" ]]; then
    (verify_cherries master "$STABLE_LIMIT" \
      & verify_cherries beta "$STABLE_LIMIT") \
      || exit 1

  fi
}

# Verify all commits in `HEAD` are backports of a commit in <upstream>. See
# https://git-scm.com/docs/git-cherry for an explanation of the arguments.
#
# $1 = <upstream>
# $2 = <limit>
verify_cherries() {
  # commits that lack a `backport-of` comment.
  local no_backports=()
  # commits with an incorrect `backport-of` comment.
  local bad_backports=()

  commits=$(git cherry "origin/$1" HEAD "$2")

  if [[ -z "$commits" ]]; then
    echo "All commits in \`HEAD\` are present in \`$1\`"
    return 0
  fi

  commits=$(echo "$commits" | grep '^\+' | cut -c 3-)

  while read sha; do
    # Check each commit in <current>..<upstream>
    backport_sha=$(get_backport "$sha")

    if [[ "$backport_sha" == "nothing" ]]; then
      echo "✓ \`$sha\` backports nothing"
      continue
    fi

    if [[ -z "$backport_sha" ]]; then
      no_backports+=("$sha")
      continue
    fi

    if ! is_in_master "$backport_sha"; then
      bad_backports+=("$sha")
      continue
    fi

    echo "✓ \`$sha\` backports \`$backport_sha\`"
  done <<< "$commits"

  failure=0

  if [ ${#no_backports[@]} -ne 0 ]; then
        echo 'Error: Could not find backports for all commits.'
        echo
        echo 'All commits in \`HEAD\` are required to have a corresponding upstream commit.'
        echo 'It looks like the following commits:'
        echo
        for commit in "${no_backports[@]}"; do
          echo "    $commit"
        done
        echo
        echo "do not match any commits in \`$1\`. If this was intended, add the text"
        echo '\`backport-of: <SHA of a commit already in master>\`'
        echo 'somewhere in the message of each of these commits.'
        echo
        failure=1
  fi

  if [ ${#bad_backports[@]} -ne 0 ]; then
        echo 'Error: Found incorrectly marked commits.'
        echo
        echo 'The following commits:'
        echo
        for commit in "${bad_backports[@]}"; do
          echo "    $commit"
        done
        echo
        echo 'have commit messages marked \`backport-of: <SHA>\`, but the SHA is not in'
        echo '\`master\`.'
        echo
        failure=1
  fi

  return $failure
}

# Get the backport of a commit. It echoes one of:
#
# 1. A SHA of the backported commit
# 2. The string "nothing"
# 3. An empty string
#
# $1 = <sha>
get_backport() {
  # This regex is:
  #
  # ^.* - throw away any extra starting characters
  # backport-of: - prefix
  # \s\? - optional space
  # \(\) - capture group
  # [a-f0-9]\+\|nothing - a SHA or the text 'nothing'
  # .* - throw away any extra ending characters
  # \1 - replace it with the first match
  # {s//\1/p;q} - print the first occurrence and quit
  #
  git show -s --format=%B "$1" \
    | sed -n '/^.*backport-of:\s\?\([a-f0-9]\+\|nothing\).*/{s//\1/p;q}'
}

# Check if a commit is in master.
#
# $1 = <sha>
is_in_master() {
  git merge-base --is-ancestor "$1" origin/master 2> /dev/null
}

verify_backported_commits_main