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."""
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
79 self.set_projects(project_names)
82 def pretty_print_gerrits(project, gerrits):
86 print("i grantedOn lastUpdatd chang subject")
87 print("-- ---------- ---------- ----- -----------------------------------------")
88 for i, gerrit in enumerate(gerrits):
89 print("%02d %d %d %s %s" % (i, gerrit["grantedOn"], gerrit["lastUpdated"],
90 gerrit["number"], gerrit["subject"]))
92 def pretty_print_includes(self, includes):
93 for project, gerrits in includes.items():
94 self.pretty_print_gerrits(project, gerrits)
96 def pretty_print_projects(self, projects):
97 for project_name, values in projects.items():
98 if values["includes"]:
99 self.pretty_print_gerrits(project_name, values["includes"])
101 def set_projects(self, project_names=PROJECT_NAMES):
102 for project in project_names:
103 self.projects[project] = {"commit": [], "includes": []}
105 def download_distro(self):
107 Download the distribution from self.distro_url and extract it to self.distro_path
109 if self.verbose >= 2:
110 print("attempting to download distribution from %s and extract to %s " %
111 (self.distro_url, self.distro_path))
113 tmp_distro_zip = '/tmp/distro.zip'
114 tmp_unzipped_location = '/tmp/distro_unzipped'
115 downloader = urllib3.PoolManager(cert_reqs='CERT_NONE')
117 # disabling warnings to prevent scaring the user with InsecureRequestWarning
118 urllib3.disable_warnings()
120 downloaded_distro = downloader.request('GET', self.distro_url)
121 with open(tmp_distro_zip, 'wb') as f:
122 f.write(downloaded_distro.data)
124 downloaded_distro.release_conn()
126 # after the .zip is extracted we want to rename it to be the distro_path which may have
127 # been given by the user
128 distro_zip = zipfile.ZipFile(tmp_distro_zip, 'r')
129 distro_zip.extractall(tmp_unzipped_location)
130 unzipped_distro_folder = os.listdir(tmp_unzipped_location)
132 # if the distro_path already exists, we wont overwrite it and just continue hoping what's
133 # there is relevant (and maybe already put there by this tool earlier)
135 os.rename(tmp_unzipped_location + "/" + unzipped_distro_folder[0], self.distro_path)
138 print("Unable to move extracted files from %s to %s. Using whatever bits are already there" %
139 (tmp_unzipped_location, self.distro_path))
141 def get_includes(self, project, changeid=None, msg=None):
143 Get the gerrits that would be included before the change merge time.
145 :param str project: The project to search
146 :param str changeid: The Change-Id of the gerrit to use for the merge time
147 :param str msg: The commit message of the gerrit to use for the merge time
148 :return list: includes[0] is the gerrit requested, [1 to limit] are the gerrits found.
150 includes = self.gerritquery.get_gerrits(project, changeid, 1, msg)
152 print("Review %s in %s:%s was not found" % (changeid, project, self.gerritquery.branch))
155 gerrits = self.gerritquery.get_gerrits(project, changeid=None, limit=self.qlimit, msg=msg)
156 for gerrit in gerrits:
157 # don"t include the same change in the list
158 if gerrit["id"] == changeid:
161 # TODO: should the check be < or <=?
162 if gerrit["grantedOn"] <= includes[0]["grantedOn"]:
163 includes.append(gerrit)
165 # break out if we have the number requested
166 if len(includes) == self.limit + 1:
169 if len(includes) != self.limit + 1:
170 print("%s query limit was not large enough to capture %d gerrits" % (project, self.limit))
175 def extract_gitproperties_file(fullpath):
177 Extract a git.properties from a jar archive.
179 :param str fullpath: Path to the jar
180 :return str: Containing git.properties or None if not found
182 if zipfile.is_zipfile(fullpath):
183 zf = zipfile.ZipFile(fullpath, "r")
185 pfile = zf.open("META-INF/git.properties")
191 def get_changeid_from_properties(self, project, pfile):
193 Parse the git.properties file to find a Change-Id.
195 There are a few different forms that we know of so far:
196 - I0123456789012345678901234567890123456789
198 - no Change-Id at all. There is a commit message and commit hash.
199 In this example the commit hash cannot be found because it was a merge
200 so you must use the message. Note spaces need to be replaced with +"s
202 :param str project: The project to search
203 :param str pfile: String containing the content of the git.properties file
204 :return str: The Change-Id or None if not found
206 # match a 40 or 8 char Change-Id hash. both start with I
207 regex = re.compile(r'\bI([a-f0-9]{40})\b|\bI([a-f0-9]{8})\b')
208 changeid = regex.search(pfile)
210 return changeid.group()
212 # Didn"t find a Change-Id so try to get a commit message
213 # match on "blah" but only keep the blah
214 regex_msg = re.compile(r'"([^"]*)"|^git.commit.message.short=(.*)$')
215 msg = regex_msg.search(pfile)
216 if self.verbose >= 2:
217 print("did not find Change-Id in %s, trying with commit-msg: %s" % (project, msg.group()))
220 # TODO: add new query using this msg
221 gerrits = self.gerritquery.get_gerrits(project, None, 1, msg.group())
223 return gerrits[0]["id"]
226 def find_distro_changeid(self, project):
228 Find a distribution Change-Id by finding a project jar in
229 the distribution and parsing it's git.properties.
231 :param str project: The project to search
232 :return str: The Change-Id or None if not found
234 project_dir = os.path.join(self.distro_path, "system", "org", "opendaylight", project)
236 for root, dirs, files in os.walk(project_dir):
238 if file_.endswith(".jar"):
239 fullpath = os.path.join(root, file_)
240 pfile = self.extract_gitproperties_file(fullpath)
242 changeid = self.get_changeid_from_properties(project, pfile)
246 print("Could not find %s Change-Id in git.properties" % project)
247 break # all jars will have the same git.properties
248 if pfile is not None:
249 break # all jars will have the same git.properties
253 self.gerritquery = gerritquery.GerritQuery(self.remote_url, self.branch, self.qlimit, self.verbose)
254 self.set_projects(self.project_names)
256 def print_options(self):
257 print("Using these options: branch: %s, limit: %d, qlimit: %d"
258 % (self.branch, self.limit, self.qlimit))
259 print("remote_url: %s" % self.remote_url)
260 print("distro_path: %s" % self.distro_path)
261 print("projects: %s" % (", ".join(map(str, self.projects))))
262 print("gerrit 00 is the most recent patch from which the project was built followed by the next most"
263 " recently merged patches up to %s." % self.limit)
267 Internal wrapper between main, options parser and internal code.
269 Get the gerrit for the given Change-Id and parse it.
270 Loop over all projects:
271 get qlimit gerrits and parse them
272 copy up to limit gerrits with a SUBM time (grantedOn) <= to the given change-id
274 # TODO: need method to validate the branch matches the distribution
279 if self.distro_url is not None:
280 self.download_distro()
282 for project in self.projects:
283 changeid = self.find_distro_changeid(project)
285 self.projects[project]["includes"] = self.get_includes(project, changeid)
289 parser = argparse.ArgumentParser(description=COPYRIGHT)
291 parser.add_argument("-b", "--branch", default=self.BRANCH,
292 help="git branch for patch under test")
293 parser.add_argument("-d", "--distro-path", dest="distro_path", default=self.DISTRO_PATH,
294 help="path to the expanded distribution, i.e. " + self.DISTRO_PATH)
295 parser.add_argument("-u", "--distro-url", dest="distro_url", default=self.DISTRO_URL,
296 help="optional url to download a distribution " + str(self.DISTRO_URL))
297 parser.add_argument("-l", "--limit", dest="limit", type=int, default=self.LIMIT,
298 help="number of gerrits to return")
299 parser.add_argument("-p", "--projects", dest="projects", default=self.PROJECT_NAMES,
300 help="list of projects to include in output")
301 parser.add_argument("-q", "--query-limit", dest="qlimit", type=int, default=self.QUERY_LIMIT,
302 help="number of gerrits to search")
303 parser.add_argument("-r", "--remote", dest="remote_url", default=self.REMOTE_URL,
304 help="git remote url to use for gerrit")
305 parser.add_argument("-v", "--verbose", dest="verbose", action="count", default=self.VERBOSE,
306 help="Output more information about what's going on")
307 parser.add_argument("--license", dest="license", action="store_true",
308 help="Print the license and exit")
309 parser.add_argument("-V", "--version", action="version",
310 version="%s version %s" %
311 (os.path.split(sys.argv[0])[-1], 0.1))
313 options = parser.parse_args()
319 self.branch = options.branch
320 self.distro_path = options.distro_path
321 self.distro_url = options.distro_url
322 self.limit = options.limit
323 self.project_names = options.projects
324 self.qlimit = options.qlimit
325 self.remote_url = options.remote_url
326 self.verbose = options.verbose
328 # TODO: add check to verify that the remote can be reached,
329 # though the first gerrit query will fail anyways
331 projects = self.run_cmd()
332 self.pretty_print_projects(projects)
340 except Exception as e:
341 # If one does unguarded print(e) here, in certain locales the implicit
342 # str(e) blows up with familiar "UnicodeEncodeError ... ordinal not in
343 # range(128)". See rhbz#1058167.
347 # Python 3, we"re home free.
350 print(u.encode("utf-8"))
352 sys.exit(getattr(e, "EXIT_CODE", -1))
355 if __name__ == "__main__":