blob: 3b27b082f5497bad5f929ffd743f2d2497a51ec8 [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
57# Find all release branches between OLDEST_BRANCH and DEV_BRANCH
58def get_release_branches():
Christoffer Quist Adamsen2434a4d2023-10-16 11:29:03 +020059 # Release branches are assumed to be of the form 'origin/X.Y'
60 out = run_cmd(['git', 'branch', '-r', '-l'])
61 pattern = re.compile('origin/(\d+).(\d+)')
62 releases = []
63 for line in out.split('\n'):
64 m = pattern.search(line.strip())
65 if m:
66 major = m.group(1)
67 minor = m.group(2)
68 if major and minor:
69 candidate = (int(major), int(minor))
70 if branch_version_less_than(candidate, OLDEST_BRANCH_VERSION):
71 continue
72 if branch_version_less_than(candidate, DEV_BRANCH_VERSION):
73 releases.extend(find_dev_cutoff(candidate))
74 return releases
75
Ian Zernybbd36a02023-06-09 12:37:50 +020076
77# Find the most recent commit hash that is for a -dev version.
78# This is the starting point for the map of commits after cutoff from main.
79def find_dev_cutoff(branch_version):
Christoffer Quist Adamsen2434a4d2023-10-16 11:29:03 +020080 out = run_cmd([
81 'git',
82 'log',
83 'origin/%d.%d' % branch_version,
84 '--grep',
85 'Version .*-dev',
86 '--pretty=oneline',
87 ])
88 # Format of output is: <hash> Version <version>-dev
89 try:
90 hash = out[0:out.index(' ')]
91 return [Branch('%d.%d' % branch_version, hash)]
92 except ValueError:
93 throw_error("Failed to find dev cutoff for branch %d.%d" %
94 branch_version)
95
Ian Zernybbd36a02023-06-09 12:37:50 +020096
97# Build a map from each "Change-Id" hash to the hash of its commit.
98def get_change_id_map(branch):
Christoffer Quist Adamsen2434a4d2023-10-16 11:29:03 +020099 out = run_cmd(
100 ['git', 'log',
101 '%s..%s' % (branch.first, branch.last_or_origin())])
102 map = {}
103 current_commit = None
104 for line in out.split('\n'):
105 if line.startswith('commit '):
106 current_commit = line[len('commit '):]
107 assert len(current_commit) == 40
108 elif line.strip().startswith('Change-Id: '):
109 change_id = line.strip()[len('Change-Id: '):]
110 assert len(change_id) == 41
111 map[change_id] = current_commit
112 return map
113
Ian Zernybbd36a02023-06-09 12:37:50 +0200114
115# Check if a specific commit is present on a specific branch.
116def is_commit_in(commit, branch):
Christoffer Quist Adamsen2434a4d2023-10-16 11:29:03 +0200117 out = run_cmd(
118 ['git', 'branch', '-r',
119 branch.origin(), '--contains', commit])
120 return out.strip() == branch.origin()
121
Ian Zernybbd36a02023-06-09 12:37:50 +0200122
123def main():
Christoffer Quist Adamsen2434a4d2023-10-16 11:29:03 +0200124 found_errors = False
125 # The main map is all commits back to the "init" point.
126 main_map = get_change_id_map(MAIN)
127 # Compute the release branches.
128 release_branches = get_release_branches()
129 # Populate the release maps with all commits after the last -dev point.
130 release_maps = {}
131 for branch in release_branches:
132 release_maps[branch.name] = get_change_id_map(branch)
133 # Each branch is then compared forwards with each subsequent branch.
134 for i in range(len(release_branches)):
135 branch = release_branches[i]
136 newer_branches = release_branches[i + 1:]
137 if (len(newer_branches) == 0):
138 print('Last non-dev release branch is %s, nothing to check.' %
139 branch)
140 continue
141 print('Checking branch %s.' % branch)
142 changes = release_maps[branch.name]
143 cherry_picks_count = 0
144 for change in changes.keys():
145 is_cherry_pick = False
146 missing_from = None
147 commit_on_main = main_map.get(change)
148 for newer_branch in newer_branches:
149 if change in release_maps[newer_branch.name]:
150 is_cherry_pick = True
151 # If the change is in the release mappings check for holes.
152 if missing_from:
153 found_errors |= change_error(
154 change, 'Error: missing Change-Id %s on branch %s. '
155 'Is present on %s and again on %s.' % (
156 change,
157 missing_from,
158 branch,
159 newer_branch,
160 ))
161 elif commit_on_main:
162 is_cherry_pick = True
163 # The change is not in the non-dev part of the branch, so we need to
164 # check that the fork from main included the change.
165 if not is_commit_in(commit_on_main, newer_branch):
166 found_errors |= change_error(
167 change, 'Error: missing Change-Id %s on branch %s. '
168 'Is present on %s and on main as commit %s.' %
169 (change, newer_branch, branch, commit_on_main))
170 else:
171 # The change is not on "main" so we just record for holes on releases.
172 missing_from = newer_branch
173 if is_cherry_pick:
174 cherry_picks_count += 1
175 print('Found %d cherry-picks (out of %d commits).' %
176 (cherry_picks_count, len(changes)))
Ian Zernybbd36a02023-06-09 12:37:50 +0200177
Christoffer Quist Adamsen2434a4d2023-10-16 11:29:03 +0200178 if found_errors:
179 return 1
180 return 0
181
Ian Zernybbd36a02023-06-09 12:37:50 +0200182
183def change_error(change, msg):
Christoffer Quist Adamsen2434a4d2023-10-16 11:29:03 +0200184 if change in IGNORED:
185 return False
186 error(msg)
187 return True
188
Ian Zernybbd36a02023-06-09 12:37:50 +0200189
190def throw_error(msg):
Christoffer Quist Adamsen2434a4d2023-10-16 11:29:03 +0200191 raise ValueError(msg)
192
Ian Zernybbd36a02023-06-09 12:37:50 +0200193
194def error(msg):
Christoffer Quist Adamsen2434a4d2023-10-16 11:29:03 +0200195 print(msg, file=sys.stderr)
196
Ian Zernybbd36a02023-06-09 12:37:50 +0200197
198if __name__ == '__main__':
Christoffer Quist Adamsen2434a4d2023-10-16 11:29:03 +0200199 sys.exit(main())