13 1. What about the time between when a merge is submitted and
14 the patch is in the distribution? Should we look at the other
15 events and see when the merge job finished?
16 2. Use the git query option to request records in multiple queries
17 rather than grabbing all 50 in one shot. Keep going until the requested
18 number is found. Verify if this is better than just doing all 50 in one
19 shot since multiple requests are ssh round trips per request.
22 # This file started as an exact copy of git-review so including it"s copyright
25 Copyright (C) 2011-2017 OpenStack LLC.
27 Licensed under the Apache License, Version 2.0 (the "License");
28 you may not use this file except in compliance with the License.
29 You may obtain a copy of the License at
31 http://www.apache.org/licenses/LICENSE-2.0
33 Unless required by applicable law or agreed to in writing, software
34 distributed under the License is distributed on an "AS IS" BASIS,
35 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
38 See the License for the specific language governing permissions and
39 limitations under the License."""
42 class Changes(object):
43 # NETVIRT_PROJECTS, as taken from autorelease dependency info [0]
44 # TODO: it would be nice to fetch the dependency info on the fly in case it changes down the road
45 # [0] https://logs.opendaylight.org/releng/jenkins092/autorelease-release-carbon/127/archives/dependencies.log.gz
46 NETVIRT_PROJECTS = ["netvirt", "controller", "dlux", "dluxapps", "genius", "infrautils", "mdsal", "netconf",
47 "neutron", "odlparent", "openflowplugin", "ovsdb", "sfc", "yangtools"]
48 PROJECT_NAMES = NETVIRT_PROJECTS
50 DISTRO_PATH = "/tmp/distribution-karaf"
52 REMOTE_URL = gerritquery.GerritQuery.REMOTE_URL
58 distro_path = DISTRO_PATH
59 distro_url = DISTRO_URL
60 project_names = PROJECT_NAMES
64 remote_url = REMOTE_URL
68 def __init__(self, branch=BRANCH, distro_path=DISTRO_PATH,
69 limit=LIMIT, qlimit=QUERY_LIMIT,
70 project_names=PROJECT_NAMES, remote_url=REMOTE_URL,
73 self.distro_path = distro_path
76 self.project_names = project_names
77 self.remote_url = remote_url
78 self.verbose = verbose
81 def epoch_to_utc(self, epoch):
82 utc = time.gmtime(epoch)
84 return time.strftime("%Y-%m-%d %H:%M:%S", utc)
86 def pretty_print_gerrits(self, project, gerrits):
90 print("i grantedOn lastUpdatd chang subject")
91 print("-- ------------------- ------------------- ----- -----------------------------------------")
93 print("gerrit is under review")
95 for i, gerrit in enumerate(gerrits):
96 if isinstance(gerrit, dict):
97 print("%02d %19s %19s %5s %s"
99 self.epoch_to_utc(gerrit["grantedOn"]) if "grantedOn" in gerrit else 0,
100 self.epoch_to_utc(gerrit["lastUpdated"]) if "lastUpdated" in gerrit else 0,
101 gerrit["number"] if "number" in gerrit else "00000",
102 gerrit["subject"] if "subject" in gerrit else "none"))
104 def pretty_print_projects(self, projects):
105 if isinstance(projects, dict):
106 for project_name, values in projects.items():
107 if "includes" in values:
108 self.pretty_print_gerrits(project_name, values["includes"])
110 def set_projects(self, project_names=PROJECT_NAMES):
111 for project in project_names:
112 self.projects[project] = {"commit": [], "includes": []}
114 def download_distro(self):
116 Download the distribution from self.distro_url and extract it to self.distro_path
118 if self.verbose >= 2:
119 print("attempting to download distribution from %s and extract to %s " %
120 (self.distro_url, self.distro_path))
122 tmp_distro_zip = '/tmp/distro.zip'
123 tmp_unzipped_location = '/tmp/distro_unzipped'
124 downloader = urllib3.PoolManager(cert_reqs='CERT_NONE')
126 # disabling warnings to prevent scaring the user with InsecureRequestWarning
127 urllib3.disable_warnings()
129 downloaded_distro = downloader.request('GET', self.distro_url)
130 with open(tmp_distro_zip, 'wb') as f:
131 f.write(downloaded_distro.data)
133 downloaded_distro.release_conn()
135 # after the .zip is extracted we want to rename it to be the distro_path which may have
136 # been given by the user
137 distro_zip = zipfile.ZipFile(tmp_distro_zip, 'r')
138 distro_zip.extractall(tmp_unzipped_location)
139 unzipped_distro_folder = os.listdir(tmp_unzipped_location)
141 # if the distro_path already exists, we wont overwrite it and just continue hoping what's
142 # there is relevant (and maybe already put there by this tool earlier)
144 os.rename(tmp_unzipped_location + "/" + unzipped_distro_folder[0], self.distro_path)
147 print("Unable to move extracted files from %s to %s. Using whatever bits are already there" %
148 (tmp_unzipped_location, self.distro_path))
150 def get_includes(self, project, changeid=None, msg=None):
152 Get the gerrits that would be included before the change merge time.
154 :param str project: The project to search
155 :param str changeid: The Change-Id of the gerrit to use for the merge time
156 :param str msg: The commit message of the gerrit to use for the merge time
157 :return list: includes[0] is the gerrit requested, [1 to limit] are the gerrits found.
159 includes = self.gerritquery.get_gerrits(project, changeid, 1, msg)
161 print("Review %s in %s:%s was not found" % (changeid, project, self.gerritquery.branch))
164 gerrits = self.gerritquery.get_gerrits(project, changeid=None, limit=self.qlimit, msg=msg)
165 for gerrit in gerrits:
166 # don"t include the same change in the list
167 if gerrit["id"] == changeid:
170 # TODO: should the check be < or <=?
171 if gerrit["grantedOn"] <= includes[0]["grantedOn"]:
172 includes.append(gerrit)
174 # break out if we have the number requested
175 if len(includes) == self.limit + 1:
178 if len(includes) != self.limit + 1:
179 print("%s query limit was not large enough to capture %d gerrits" % (project, self.limit))
184 def extract_gitproperties_file(fullpath):
186 Extract a git.properties from a jar archive.
188 :param str fullpath: Path to the jar
189 :return str: Containing git.properties or None if not found
191 if zipfile.is_zipfile(fullpath):
192 zf = zipfile.ZipFile(fullpath, "r")
194 pfile = zf.open("META-INF/git.properties")
195 return str(pfile.read())
200 def get_changeid_from_properties(self, project, pfile):
202 Parse the git.properties file to find a Change-Id.
204 There are a few different forms that we know of so far:
205 - I0123456789012345678901234567890123456789
207 - no Change-Id at all. There is a commit message and commit hash.
208 In this example the commit hash cannot be found because it was a merge
209 so you must use the message. Note spaces need to be replaced with +"s
211 :param str project: The project to search
212 :param str pfile: String containing the content of the git.properties file
213 :return str: The Change-Id or None if not found
215 # match a 40 or 8 char Change-Id hash. both start with I
216 regex = re.compile(r'\bI([a-f0-9]{40})\b|\bI([a-f0-9]{8})\b')
217 changeid = regex.search(pfile)
219 return changeid.group()
221 # Didn't find a Change-Id so try to get a commit message
222 # match on "blah" but only keep the blah
223 if self.verbose >= 2:
224 print("did not find Change-Id in %s, trying with commit-msg" % (project))
225 regex_msg = re.compile(r'"([^"]*)"|^git.commit.message.short=(.*)$')
226 msg = regex_msg.search(pfile)
228 if self.verbose >= 2:
229 print("did not find Change-Id in %s, trying with commit-msg: %s" % (project, msg.group()))
231 # TODO: add new query using this msg
232 gerrits = self.gerritquery.get_gerrits(project, None, 1, msg.group())
234 return gerrits[0]["id"]
236 # Maybe one of the monster 'merge the world' gerrits
237 regex_msg = re.compile(r'git.commit.message.full=(.*)')
238 msg = regex_msg.search(pfile)
241 lines = str(msg.group()).split("\\n")
242 cli = next((i for i, line in enumerate(lines[:-1]) if '* changes\\:' in line), None)
243 first_msg = lines[cli+1] if cli else None
245 gerrits = self.gerritquery.get_gerrits(project, None, 1, first_msg)
247 return gerrits[0]["id"]
249 print("did not find Change-Id for %s" % project)
253 def find_distro_changeid(self, project):
255 Find a distribution Change-Id by finding a project jar in
256 the distribution and parsing it's git.properties.
258 :param str project: The project to search
259 :return str: The Change-Id or None if not found
261 project_dir = os.path.join(self.distro_path, "system", "org", "opendaylight", project)
263 for root, dirs, files in os.walk(project_dir):
265 if file_.endswith(".jar"):
266 fullpath = os.path.join(root, file_)
267 pfile = self.extract_gitproperties_file(fullpath)
269 changeid = self.get_changeid_from_properties(project, pfile)
273 print("Could not find %s Change-Id in git.properties" % project)
274 break # all jars will have the same git.properties
275 if pfile is not None:
276 break # all jars will have the same git.properties
280 self.gerritquery = gerritquery.GerritQuery(self.remote_url, self.branch, self.qlimit, self.verbose)
281 self.set_projects(self.project_names)
283 def print_options(self):
284 print("Using these options: branch: %s, limit: %d, qlimit: %d"
285 % (self.branch, self.limit, self.qlimit))
286 print("remote_url: %s" % self.remote_url)
287 print("distro_path: %s" % self.distro_path)
288 print("projects: %s" % (", ".join(map(str, self.projects))))
289 print("gerrit 00 is the most recent patch from which the project was built followed by the next most"
290 " recently merged patches up to %s." % self.limit)
294 Internal wrapper between main, options parser and internal code.
296 Get the gerrit for the given Change-Id and parse it.
297 Loop over all projects:
298 get qlimit gerrits and parse them
299 copy up to limit gerrits with a SUBM time (grantedOn) <= to the given change-id
301 # TODO: need method to validate the branch matches the distribution
306 if self.distro_url is not None:
307 self.download_distro()
309 for project in self.projects:
310 changeid = self.find_distro_changeid(project)
312 self.projects[project]['commit'] = changeid
313 self.projects[project]["includes"] = self.get_includes(project, changeid)
317 parser = argparse.ArgumentParser(description=COPYRIGHT)
319 parser.add_argument("-b", "--branch", default=self.BRANCH,
320 help="git branch for patch under test")
321 parser.add_argument("-d", "--distro-path", dest="distro_path", default=self.DISTRO_PATH,
322 help="path to the expanded distribution, i.e. " + self.DISTRO_PATH)
323 parser.add_argument("-u", "--distro-url", dest="distro_url", default=self.DISTRO_URL,
324 help="optional url to download a distribution " + str(self.DISTRO_URL))
325 parser.add_argument("-l", "--limit", dest="limit", type=int, default=self.LIMIT,
326 help="number of gerrits to return")
327 parser.add_argument("-p", "--projects", dest="projects", default=self.PROJECT_NAMES,
328 help="list of projects to include in output")
329 parser.add_argument("-q", "--query-limit", dest="qlimit", type=int, default=self.QUERY_LIMIT,
330 help="number of gerrits to search")
331 parser.add_argument("-r", "--remote", dest="remote_url", default=self.REMOTE_URL,
332 help="git remote url to use for gerrit")
333 parser.add_argument("-v", "--verbose", dest="verbose", action="count", default=self.VERBOSE,
334 help="Output more information about what's going on")
335 parser.add_argument("--license", dest="license", action="store_true",
336 help="Print the license and exit")
337 parser.add_argument("-V", "--version", action="version",
338 version="%s version %s" %
339 (os.path.split(sys.argv[0])[-1], 0.1))
341 options = parser.parse_args()
347 self.branch = options.branch
348 self.distro_path = options.distro_path
349 self.distro_url = options.distro_url
350 self.limit = options.limit
351 self.qlimit = options.qlimit
352 self.remote_url = options.remote_url
353 self.verbose = options.verbose
354 if options.projects != self.PROJECT_NAMES:
355 self.project_names = options.projects.split(',')
357 # TODO: add check to verify that the remote can be reached,
358 # though the first gerrit query will fail anyways
360 projects = self.run_cmd()
361 self.pretty_print_projects(projects)
369 except Exception as e:
370 # If one does unguarded print(e) here, in certain locales the implicit
371 # str(e) blows up with familiar "UnicodeEncodeError ... ordinal not in
372 # range(128)". See rhbz#1058167.
376 # Python 3, we"re home free.
379 print(u.encode("utf-8"))
381 sys.exit(getattr(e, "EXIT_CODE", -1))
384 if __name__ == "__main__":