blob: 85d1acb1f2f7b2212830e9924fa99923dc3f68f1 [file] [log] [blame]
Ian Zernybbd36a02023-06-09 12:37:50 +02001#!/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
6import subprocess
7import sys
8import re
9import r8_release
10
Christoffer Quist Adamsen2434a4d2023-10-16 11:29:03 +020011
Ian Zernybbd36a02023-06-09 12:37:50 +020012class Branch:
Ian Zernybbd36a02023-06-09 12:37:50 +020013
Christoffer Quist Adamsen2434a4d2023-10-16 11:29:03 +020014 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 Zernybbd36a02023-06-09 12:37:50 +020018
Christoffer Quist Adamsen2434a4d2023-10-16 11:29:03 +020019 def origin(self):
20 return "origin/%s" % self.name
Ian Zernybbd36a02023-06-09 12:37:50 +020021
Christoffer Quist Adamsen2434a4d2023-10-16 11:29:03 +020022 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 Zernybbd36a02023-06-09 12:37:50 +020028
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
31MAIN = Branch('main', 'a2e203580aa00a36f85cd68d3d584b97aef34d59')
32OLDEST_BRANCH_VERSION = (4, 0)
33DEV_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 Adamsen2434a4d2023-10-16 11:29:03 +020036IGNORED = ['I92d7bf3afbf609fdea21683941cfd15c90305cf2']
Ian Zernybbd36a02023-06-09 12:37:50 +020037
38VERBOSE = False
39
Christoffer Quist Adamsen2434a4d2023-10-16 11:29:03 +020040
Ian Zernybbd36a02023-06-09 12:37:50 +020041# Helper to call and decode a shell command.
42def run_cmd(cmd):
Christoffer Quist Adamsen2434a4d2023-10-16 11:29:03 +020043 if VERBOSE:
44 print(' '.join(cmd))
45 return subprocess.check_output(cmd).decode('UTF-8')
46
Ian Zernybbd36a02023-06-09 12:37:50 +020047
48# Comparator on major and minor branch versions.
49def branch_version_less_than(b1, b2):
Christoffer Quist Adamsen2434a4d2023-10-16 11:29:03 +020050 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 Zernybbd36a02023-06-09 12:37:50 +020056# Find all release branches between OLDEST_BRANCH and DEV_BRANCH
57def get_release_branches():
Christoffer Quist Adamsen2434a4d2023-10-16 11:29:03 +020058 # Release branches are assumed to be of the form 'origin/X.Y'
59 out = run_cmd(['git', 'branch', '-r', '-l'])
Rico Wind21bbcf82025-03-10 11:22:07 +010060 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 Adamsen2434a4d2023-10-16 11:29:03 +020064 releases = []
Rico Wind21bbcf82025-03-10 11:22:07 +010065
66 for line in lines:
Christoffer Quist Adamsen2434a4d2023-10-16 11:29:03 +020067 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 Zernybbd36a02023-06-09 12:37:50 +020079
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.
82def find_dev_cutoff(branch_version):
Christoffer Quist Adamsen2434a4d2023-10-16 11:29:03 +020083 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 Zernybbd36a02023-06-09 12:37:50 +020099
100# Build a map from each "Change-Id" hash to the hash of its commit.
101def get_change_id_map(branch):
Christoffer Quist Adamsen2434a4d2023-10-16 11:29:03 +0200102 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 Zernybbd36a02023-06-09 12:37:50 +0200117
118# Check if a specific commit is present on a specific branch.
119def is_commit_in(commit, branch):
Christoffer Quist Adamsen2434a4d2023-10-16 11:29:03 +0200120 out = run_cmd(
121 ['git', 'branch', '-r',
122 branch.origin(), '--contains', commit])
123 return out.strip() == branch.origin()
124
Ian Zernybbd36a02023-06-09 12:37:50 +0200125
126def main():
Christoffer Quist Adamsen2434a4d2023-10-16 11:29:03 +0200127 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 Zernybbd36a02023-06-09 12:37:50 +0200180
Christoffer Quist Adamsen2434a4d2023-10-16 11:29:03 +0200181 if found_errors:
182 return 1
183 return 0
184
Ian Zernybbd36a02023-06-09 12:37:50 +0200185
186def change_error(change, msg):
Christoffer Quist Adamsen2434a4d2023-10-16 11:29:03 +0200187 if change in IGNORED:
188 return False
189 error(msg)
190 return True
191
Ian Zernybbd36a02023-06-09 12:37:50 +0200192
193def throw_error(msg):
Christoffer Quist Adamsen2434a4d2023-10-16 11:29:03 +0200194 raise ValueError(msg)
195
Ian Zernybbd36a02023-06-09 12:37:50 +0200196
197def error(msg):
Christoffer Quist Adamsen2434a4d2023-10-16 11:29:03 +0200198 print(msg, file=sys.stderr)
199
Ian Zernybbd36a02023-06-09 12:37:50 +0200200
201if __name__ == '__main__':
Christoffer Quist Adamsen2434a4d2023-10-16 11:29:03 +0200202 sys.exit(main())