Ian Zerny | bbd36a0 | 2023-06-09 12:37:50 +0200 | [diff] [blame] | 1 | #!/usr/bin/env python3 |
| 2 | # Copyright (c) 2023, the R8 project authors. Please see the AUTHORS file |
| 3 | # for details. All rights reserved. Use of this source code is governed by a |
| 4 | # BSD-style license that can be found in the LICENSE file. |
| 5 | |
| 6 | import subprocess |
| 7 | import sys |
| 8 | import re |
| 9 | import r8_release |
| 10 | |
Christoffer Quist Adamsen | 2434a4d | 2023-10-16 11:29:03 +0200 | [diff] [blame] | 11 | |
Ian Zerny | bbd36a0 | 2023-06-09 12:37:50 +0200 | [diff] [blame] | 12 | class Branch: |
Ian Zerny | bbd36a0 | 2023-06-09 12:37:50 +0200 | [diff] [blame] | 13 | |
Christoffer Quist Adamsen | 2434a4d | 2023-10-16 11:29:03 +0200 | [diff] [blame] | 14 | def __init__(self, name, first, last=None): |
| 15 | self.name = name |
| 16 | self.first = first |
| 17 | self.last = last # optional last for testing purposes. |
Ian Zerny | bbd36a0 | 2023-06-09 12:37:50 +0200 | [diff] [blame] | 18 | |
Christoffer Quist Adamsen | 2434a4d | 2023-10-16 11:29:03 +0200 | [diff] [blame] | 19 | def origin(self): |
| 20 | return "origin/%s" % self.name |
Ian Zerny | bbd36a0 | 2023-06-09 12:37:50 +0200 | [diff] [blame] | 21 | |
Christoffer Quist Adamsen | 2434a4d | 2023-10-16 11:29:03 +0200 | [diff] [blame] | 22 | def last_or_origin(self): |
| 23 | return self.last if self.last else self.origin() |
| 24 | |
| 25 | def __str__(self): |
| 26 | return self.name |
| 27 | |
Ian Zerny | bbd36a0 | 2023-06-09 12:37:50 +0200 | [diff] [blame] | 28 | |
| 29 | # The initial commit is the furthest back we need to search on main. |
| 30 | # Currently, it is the merge point of main onto 4.0.23-dev |
| 31 | MAIN = Branch('main', 'a2e203580aa00a36f85cd68d3d584b97aef34d59') |
| 32 | OLDEST_BRANCH_VERSION = (4, 0) |
| 33 | DEV_BRANCH_VERSION = [int(s) for s in r8_release.R8_DEV_BRANCH.split('.')] |
| 34 | |
| 35 | # List of change ids that should not be reported. |
Christoffer Quist Adamsen | 2434a4d | 2023-10-16 11:29:03 +0200 | [diff] [blame] | 36 | IGNORED = ['I92d7bf3afbf609fdea21683941cfd15c90305cf2'] |
Ian Zerny | bbd36a0 | 2023-06-09 12:37:50 +0200 | [diff] [blame] | 37 | |
| 38 | VERBOSE = False |
| 39 | |
Christoffer Quist Adamsen | 2434a4d | 2023-10-16 11:29:03 +0200 | [diff] [blame] | 40 | |
Ian Zerny | bbd36a0 | 2023-06-09 12:37:50 +0200 | [diff] [blame] | 41 | # Helper to call and decode a shell command. |
| 42 | def run_cmd(cmd): |
Christoffer Quist Adamsen | 2434a4d | 2023-10-16 11:29:03 +0200 | [diff] [blame] | 43 | if VERBOSE: |
| 44 | print(' '.join(cmd)) |
| 45 | return subprocess.check_output(cmd).decode('UTF-8') |
| 46 | |
Ian Zerny | bbd36a0 | 2023-06-09 12:37:50 +0200 | [diff] [blame] | 47 | |
| 48 | # Comparator on major and minor branch versions. |
| 49 | def branch_version_less_than(b1, b2): |
Christoffer Quist Adamsen | 2434a4d | 2023-10-16 11:29:03 +0200 | [diff] [blame] | 50 | if b1[0] < b2[0]: |
| 51 | return True |
| 52 | if b1[0] == b2[0] and b1[1] < b2[1]: |
| 53 | return True |
| 54 | return False |
| 55 | |
Ian Zerny | bbd36a0 | 2023-06-09 12:37:50 +0200 | [diff] [blame] | 56 | # Find all release branches between OLDEST_BRANCH and DEV_BRANCH |
| 57 | def get_release_branches(): |
Christoffer Quist Adamsen | 2434a4d | 2023-10-16 11:29:03 +0200 | [diff] [blame] | 58 | # Release branches are assumed to be of the form 'origin/X.Y' |
| 59 | out = run_cmd(['git', 'branch', '-r', '-l']) |
Rico Wind | 21bbcf8 | 2025-03-10 11:22:07 +0100 | [diff] [blame] | 60 | natsort = lambda s: [int(t) if t.isdigit() else t.lower() |
| 61 | for t in re.split(r'(\d+)', s)] |
| 62 | lines = sorted(out.split('\n'), key=natsort) |
| 63 | pattern = re.compile(r'origin/(\d+).(\d+)') |
Christoffer Quist Adamsen | 2434a4d | 2023-10-16 11:29:03 +0200 | [diff] [blame] | 64 | releases = [] |
Rico Wind | 21bbcf8 | 2025-03-10 11:22:07 +0100 | [diff] [blame] | 65 | |
| 66 | for line in lines: |
Christoffer Quist Adamsen | 2434a4d | 2023-10-16 11:29:03 +0200 | [diff] [blame] | 67 | m = pattern.search(line.strip()) |
| 68 | if m: |
| 69 | major = m.group(1) |
| 70 | minor = m.group(2) |
| 71 | if major and minor: |
| 72 | candidate = (int(major), int(minor)) |
| 73 | if branch_version_less_than(candidate, OLDEST_BRANCH_VERSION): |
| 74 | continue |
| 75 | if branch_version_less_than(candidate, DEV_BRANCH_VERSION): |
| 76 | releases.extend(find_dev_cutoff(candidate)) |
| 77 | return releases |
| 78 | |
Ian Zerny | bbd36a0 | 2023-06-09 12:37:50 +0200 | [diff] [blame] | 79 | |
| 80 | # Find the most recent commit hash that is for a -dev version. |
| 81 | # This is the starting point for the map of commits after cutoff from main. |
| 82 | def find_dev_cutoff(branch_version): |
Christoffer Quist Adamsen | 2434a4d | 2023-10-16 11:29:03 +0200 | [diff] [blame] | 83 | out = run_cmd([ |
| 84 | 'git', |
| 85 | 'log', |
| 86 | 'origin/%d.%d' % branch_version, |
| 87 | '--grep', |
| 88 | 'Version .*-dev', |
| 89 | '--pretty=oneline', |
| 90 | ]) |
| 91 | # Format of output is: <hash> Version <version>-dev |
| 92 | try: |
| 93 | hash = out[0:out.index(' ')] |
| 94 | return [Branch('%d.%d' % branch_version, hash)] |
| 95 | except ValueError: |
| 96 | throw_error("Failed to find dev cutoff for branch %d.%d" % |
| 97 | branch_version) |
| 98 | |
Ian Zerny | bbd36a0 | 2023-06-09 12:37:50 +0200 | [diff] [blame] | 99 | |
| 100 | # Build a map from each "Change-Id" hash to the hash of its commit. |
| 101 | def get_change_id_map(branch): |
Christoffer Quist Adamsen | 2434a4d | 2023-10-16 11:29:03 +0200 | [diff] [blame] | 102 | out = run_cmd( |
| 103 | ['git', 'log', |
| 104 | '%s..%s' % (branch.first, branch.last_or_origin())]) |
| 105 | map = {} |
| 106 | current_commit = None |
| 107 | for line in out.split('\n'): |
| 108 | if line.startswith('commit '): |
| 109 | current_commit = line[len('commit '):] |
| 110 | assert len(current_commit) == 40 |
| 111 | elif line.strip().startswith('Change-Id: '): |
| 112 | change_id = line.strip()[len('Change-Id: '):] |
| 113 | assert len(change_id) == 41 |
| 114 | map[change_id] = current_commit |
| 115 | return map |
| 116 | |
Ian Zerny | bbd36a0 | 2023-06-09 12:37:50 +0200 | [diff] [blame] | 117 | |
| 118 | # Check if a specific commit is present on a specific branch. |
| 119 | def is_commit_in(commit, branch): |
Christoffer Quist Adamsen | 2434a4d | 2023-10-16 11:29:03 +0200 | [diff] [blame] | 120 | out = run_cmd( |
| 121 | ['git', 'branch', '-r', |
| 122 | branch.origin(), '--contains', commit]) |
| 123 | return out.strip() == branch.origin() |
| 124 | |
Ian Zerny | bbd36a0 | 2023-06-09 12:37:50 +0200 | [diff] [blame] | 125 | |
| 126 | def main(): |
Christoffer Quist Adamsen | 2434a4d | 2023-10-16 11:29:03 +0200 | [diff] [blame] | 127 | found_errors = False |
| 128 | # The main map is all commits back to the "init" point. |
| 129 | main_map = get_change_id_map(MAIN) |
| 130 | # Compute the release branches. |
| 131 | release_branches = get_release_branches() |
| 132 | # Populate the release maps with all commits after the last -dev point. |
| 133 | release_maps = {} |
| 134 | for branch in release_branches: |
| 135 | release_maps[branch.name] = get_change_id_map(branch) |
| 136 | # Each branch is then compared forwards with each subsequent branch. |
| 137 | for i in range(len(release_branches)): |
| 138 | branch = release_branches[i] |
| 139 | newer_branches = release_branches[i + 1:] |
| 140 | if (len(newer_branches) == 0): |
| 141 | print('Last non-dev release branch is %s, nothing to check.' % |
| 142 | branch) |
| 143 | continue |
| 144 | print('Checking branch %s.' % branch) |
| 145 | changes = release_maps[branch.name] |
| 146 | cherry_picks_count = 0 |
| 147 | for change in changes.keys(): |
| 148 | is_cherry_pick = False |
| 149 | missing_from = None |
| 150 | commit_on_main = main_map.get(change) |
| 151 | for newer_branch in newer_branches: |
| 152 | if change in release_maps[newer_branch.name]: |
| 153 | is_cherry_pick = True |
| 154 | # If the change is in the release mappings check for holes. |
| 155 | if missing_from: |
| 156 | found_errors |= change_error( |
| 157 | change, 'Error: missing Change-Id %s on branch %s. ' |
| 158 | 'Is present on %s and again on %s.' % ( |
| 159 | change, |
| 160 | missing_from, |
| 161 | branch, |
| 162 | newer_branch, |
| 163 | )) |
| 164 | elif commit_on_main: |
| 165 | is_cherry_pick = True |
| 166 | # The change is not in the non-dev part of the branch, so we need to |
| 167 | # check that the fork from main included the change. |
| 168 | if not is_commit_in(commit_on_main, newer_branch): |
| 169 | found_errors |= change_error( |
| 170 | change, 'Error: missing Change-Id %s on branch %s. ' |
| 171 | 'Is present on %s and on main as commit %s.' % |
| 172 | (change, newer_branch, branch, commit_on_main)) |
| 173 | else: |
| 174 | # The change is not on "main" so we just record for holes on releases. |
| 175 | missing_from = newer_branch |
| 176 | if is_cherry_pick: |
| 177 | cherry_picks_count += 1 |
| 178 | print('Found %d cherry-picks (out of %d commits).' % |
| 179 | (cherry_picks_count, len(changes))) |
Ian Zerny | bbd36a0 | 2023-06-09 12:37:50 +0200 | [diff] [blame] | 180 | |
Christoffer Quist Adamsen | 2434a4d | 2023-10-16 11:29:03 +0200 | [diff] [blame] | 181 | if found_errors: |
| 182 | return 1 |
| 183 | return 0 |
| 184 | |
Ian Zerny | bbd36a0 | 2023-06-09 12:37:50 +0200 | [diff] [blame] | 185 | |
| 186 | def change_error(change, msg): |
Christoffer Quist Adamsen | 2434a4d | 2023-10-16 11:29:03 +0200 | [diff] [blame] | 187 | if change in IGNORED: |
| 188 | return False |
| 189 | error(msg) |
| 190 | return True |
| 191 | |
Ian Zerny | bbd36a0 | 2023-06-09 12:37:50 +0200 | [diff] [blame] | 192 | |
| 193 | def throw_error(msg): |
Christoffer Quist Adamsen | 2434a4d | 2023-10-16 11:29:03 +0200 | [diff] [blame] | 194 | raise ValueError(msg) |
| 195 | |
Ian Zerny | bbd36a0 | 2023-06-09 12:37:50 +0200 | [diff] [blame] | 196 | |
| 197 | def error(msg): |
Christoffer Quist Adamsen | 2434a4d | 2023-10-16 11:29:03 +0200 | [diff] [blame] | 198 | print(msg, file=sys.stderr) |
| 199 | |
Ian Zerny | bbd36a0 | 2023-06-09 12:37:50 +0200 | [diff] [blame] | 200 | |
| 201 | if __name__ == '__main__': |
Christoffer Quist Adamsen | 2434a4d | 2023-10-16 11:29:03 +0200 | [diff] [blame] | 202 | sys.exit(main()) |