Upstream version 9.38.198.0
[platform/framework/web/crosswalk.git] / src / third_party / chromite / cbuildbot / lkgm_manager.py
1 #!/usr/bin/python
2 # Copyright (c) 2012 The Chromium OS Authors. All rights reserved.
3 # Use of this source code is governed by a BSD-style license that can be
4 # found in the LICENSE file.
5
6 """A library to generate and store the manifests for cros builders to use."""
7
8 import logging
9 import os
10 import re
11 import tempfile
12 from xml.dom import minidom
13
14 from chromite.cbuildbot import cbuildbot_config
15 from chromite.cbuildbot import constants
16 from chromite.cbuildbot import manifest_version
17 from chromite.lib import cros_build_lib
18 from chromite.lib import git
19 from chromite.lib import timeout_util
20
21
22 # Paladin constants for manifest names.
23 PALADIN_COMMIT_ELEMENT = 'pending_commit'
24 PALADIN_REMOTE_ATTR = 'remote'
25 PALADIN_GERRIT_NUMBER_ATTR = 'gerrit_number'
26 PALADIN_PROJECT_ATTR = 'project'
27 PALADIN_BRANCH_ATTR = 'branch'
28 PALADIN_PROJECT_URL_ATTR = 'project_url'
29 PALADIN_REF_ATTR = 'ref'
30 PALADIN_CHANGE_ID_ATTR = 'change_id'
31 PALADIN_COMMIT_ATTR = 'commit'
32 PALADIN_PATCH_NUMBER_ATTR = 'patch_number'
33 PALADIN_OWNER_EMAIL_ATTR = 'owner_email'
34 PALADIN_FAIL_COUNT_ATTR = 'fail_count'
35 PALADIN_PASS_COUNT_ATTR = 'pass_count'
36 PALADIN_TOTAL_FAIL_COUNT_ATTR = 'total_fail_count'
37
38 CHROME_ELEMENT = 'chrome'
39 CHROME_VERSION_ATTR = 'version'
40
41 MANIFEST_ELEMENT = 'manifest'
42 DEFAULT_ELEMENT = 'default'
43 PROJECT_ELEMENT = 'project'
44 PROJECT_NAME_ATTR = 'name'
45 PROJECT_REMOTE_ATTR = 'remote'
46
47
48 class PromoteCandidateException(Exception):
49   """Exception thrown for failure to promote manifest candidate."""
50
51
52 class FilterManifestException(Exception):
53   """Exception thrown when failing to filter the internal manifest."""
54
55
56 class _LKGMCandidateInfo(manifest_version.VersionInfo):
57   """Class to encapsualte the chrome os lkgm candidate info
58
59   You can instantiate this class in two ways.
60   1)using a version file, specifically chromeos_version.sh,
61   which contains the version information.
62   2) just passing in the 4 version components (major, minor, sp, patch and
63     revision number),
64   Args:
65       You can instantiate this class in two ways.
66   1)using a version file, specifically chromeos_version.sh,
67   which contains the version information.
68   2) passing in a string with the 3 version components + revision e.g. 41.0.0-r1
69   Args:
70     version_string: Optional 3 component version string to parse.  Contains:
71         build_number: release build number.
72         branch_build_number: current build number on a branch.
73         patch_number: patch number.
74         revision_number: version revision
75     chrome_branch: If version_string specified, specify chrome_branch i.e. 13.
76     version_file: version file location.
77   """
78   LKGM_RE = r'(\d+\.\d+\.\d+)(?:-rc(\d+))?'
79
80   def __init__(self, version_string=None, chrome_branch=None, incr_type=None,
81                version_file=None):
82     self.revision_number = 1
83     if version_string:
84       match = re.search(self.LKGM_RE, version_string)
85       assert match, 'LKGM did not re %s' % self.LKGM_RE
86       super(_LKGMCandidateInfo, self).__init__(match.group(1), chrome_branch,
87                                                incr_type=incr_type)
88       if match.group(2):
89         self.revision_number = int(match.group(2))
90
91     else:
92       super(_LKGMCandidateInfo, self).__init__(version_file=version_file,
93                                                incr_type=incr_type)
94
95   def VersionString(self):
96     """returns the full version string of the lkgm candidate"""
97     return '%s.%s.%s-rc%s' % (self.build_number, self.branch_build_number,
98                               self.patch_number, self.revision_number)
99
100   @classmethod
101   def VersionCompare(cls, version_string):
102     """Useful method to return a comparable version of a LKGM string."""
103     lkgm = cls(version_string)
104     return map(int, [lkgm.build_number, lkgm.branch_build_number,
105                      lkgm.patch_number, lkgm.revision_number])
106
107   def IncrementVersion(self):
108     """Increments the version by incrementing the revision #."""
109     self.revision_number += 1
110     return self.VersionString()
111
112   def UpdateVersionFile(self, *args, **kwargs):
113     """Update the version file on disk.
114
115     For LKGMCandidateInfo there is no version file so this function is a no-op.
116     """
117
118
119 class LKGMManager(manifest_version.BuildSpecsManager):
120   """A Class to manage lkgm candidates and their states.
121
122   Vars:
123     lkgm_subdir:  Subdirectory within manifest repo to store candidates.
124   """
125   # Sub-directories for LKGM and Chrome LKGM's.
126   LKGM_SUBDIR = 'LKGM-candidates'
127   CHROME_PFQ_SUBDIR = 'chrome-LKGM-candidates'
128   COMMIT_QUEUE_SUBDIR = 'paladin'
129
130   # Set path in repository to keep latest approved LKGM manifest.
131   LKGM_PATH = 'LKGM/lkgm.xml'
132
133   def __init__(self, source_repo, manifest_repo, build_names, build_type,
134                incr_type, force, branch, manifest=constants.DEFAULT_MANIFEST,
135                dry_run=True, master=False):
136     """Initialize an LKGM Manager.
137
138     Args:
139       source_repo: Repository object for the source code.
140       manifest_repo: Manifest repository for manifest versions/buildspecs.
141       build_names: Identifiers for the build. Must match cbuildbot_config
142           entries. If multiple identifiers are provided, the first item in the
143           list must be an identifier for the group.
144       build_type: Type of build.  Must be a pfq type.
145       incr_type: How we should increment this version - build|branch|patch
146       force: Create a new manifest even if there are no changes.
147       branch: Branch this builder is running on.
148       manifest: Manifest to use for checkout. E.g. 'full' or 'buildtools'.
149       dry_run: Whether we actually commit changes we make or not.
150       master: Whether we are the master builder.
151     """
152     super(LKGMManager, self).__init__(
153         source_repo=source_repo, manifest_repo=manifest_repo,
154         manifest=manifest, build_names=build_names, incr_type=incr_type,
155         force=force, branch=branch, dry_run=dry_run, master=master)
156
157     self.lkgm_path = os.path.join(self.manifest_dir, self.LKGM_PATH)
158     self.compare_versions_fn = _LKGMCandidateInfo.VersionCompare
159     self.build_type = build_type
160     # Chrome PFQ and PFQ's exist at the same time and version separately so they
161     # must have separate subdirs in the manifest-versions repository.
162     if self.build_type == constants.CHROME_PFQ_TYPE:
163       self.rel_working_dir = self.CHROME_PFQ_SUBDIR
164     elif cbuildbot_config.IsCQType(self.build_type):
165       self.rel_working_dir = self.COMMIT_QUEUE_SUBDIR
166     else:
167       assert cbuildbot_config.IsPFQType(self.build_type)
168       self.rel_working_dir = self.LKGM_SUBDIR
169
170   def GetCurrentVersionInfo(self):
171     """Returns the lkgm version info from the version file."""
172     version_info = super(LKGMManager, self).GetCurrentVersionInfo()
173     return _LKGMCandidateInfo(version_info.VersionString(),
174                               chrome_branch=version_info.chrome_branch,
175                               incr_type=self.incr_type)
176
177   def _AddChromeVersionToManifest(self, manifest, chrome_version):
178     """Adds the chrome element with version |chrome_version| to |manifest|.
179
180     The manifest file should contain the Chrome version to build for
181     PFQ slaves.
182
183     Args:
184       manifest: Path to the manifest
185       chrome_version: A string representing the version of Chrome
186         (e.g. 35.0.1863.0).
187     """
188     manifest_dom = minidom.parse(manifest)
189     chrome = manifest_dom.createElement(CHROME_ELEMENT)
190     chrome.setAttribute(CHROME_VERSION_ATTR, chrome_version)
191     manifest_dom.documentElement.appendChild(chrome)
192     with open(manifest, 'w+') as manifest_file:
193       manifest_dom.writexml(manifest_file)
194
195   def _AddPatchesToManifest(self, manifest, patches):
196     """Adds list of |patches| to given |manifest|.
197
198     The manifest should have sufficient information for the slave
199     builders to fetch the patches from Gerrit and to print the CL link
200     (see cros_patch.GerritFetchOnlyPatch).
201
202     Args:
203       manifest: Path to the manifest.
204       patches: A list of cros_patch.GerritPatch objects.
205     """
206     manifest_dom = minidom.parse(manifest)
207     for patch in patches:
208       pending_commit = manifest_dom.createElement(PALADIN_COMMIT_ELEMENT)
209       pending_commit.setAttribute(PALADIN_REMOTE_ATTR, patch.remote)
210       pending_commit.setAttribute(
211           PALADIN_GERRIT_NUMBER_ATTR, patch.gerrit_number)
212       pending_commit.setAttribute(PALADIN_PROJECT_ATTR, patch.project)
213       pending_commit.setAttribute(PALADIN_PROJECT_URL_ATTR, patch.project_url)
214       pending_commit.setAttribute(PALADIN_REF_ATTR, patch.ref)
215       pending_commit.setAttribute(PALADIN_BRANCH_ATTR, patch.tracking_branch)
216       pending_commit.setAttribute(PALADIN_CHANGE_ID_ATTR, patch.change_id)
217       pending_commit.setAttribute(PALADIN_COMMIT_ATTR, patch.commit)
218       pending_commit.setAttribute(PALADIN_PATCH_NUMBER_ATTR, patch.patch_number)
219       pending_commit.setAttribute(PALADIN_OWNER_EMAIL_ATTR, patch.owner_email)
220       pending_commit.setAttribute(PALADIN_FAIL_COUNT_ATTR,
221                                   str(patch.fail_count))
222       pending_commit.setAttribute(PALADIN_PASS_COUNT_ATTR,
223                                   str(patch.pass_count))
224       pending_commit.setAttribute(PALADIN_TOTAL_FAIL_COUNT_ATTR,
225                                   str(patch.total_fail_count))
226       manifest_dom.documentElement.appendChild(pending_commit)
227
228     with open(manifest, 'w+') as manifest_file:
229       manifest_dom.writexml(manifest_file)
230
231   @staticmethod
232   def _GetDefaultRemote(manifest_dom):
233     """Returns the default remote in a manifest (if any).
234
235     Args:
236       manifest_dom: DOM Document object representing the manifest.
237
238     Returns:
239       Default remote if one exists, None otherwise.
240     """
241     default_nodes = manifest_dom.getElementsByTagName(DEFAULT_ELEMENT)
242     if default_nodes:
243       if len(default_nodes) > 1:
244         raise FilterManifestException(
245             'More than one <default> element found in manifest')
246       return default_nodes[0].getAttribute(PROJECT_REMOTE_ATTR)
247     return None
248
249   @staticmethod
250   def _FilterCrosInternalProjectsFromManifest(
251       manifest, whitelisted_remotes=constants.EXTERNAL_REMOTES):
252     """Returns a path to a new manifest with internal repositories stripped.
253
254     Args:
255       manifest: Path to an existing manifest that may have internal
256         repositories.
257       whitelisted_remotes: Tuple of remotes to allow in the external manifest.
258         Only projects with those remotes will be included in the external
259         manifest.
260
261     Returns:
262       Path to a new manifest that is a copy of the original without internal
263         repositories or pending commits.
264     """
265     temp_fd, new_path = tempfile.mkstemp('external_manifest')
266     manifest_dom = minidom.parse(manifest)
267     manifest_node = manifest_dom.getElementsByTagName(MANIFEST_ELEMENT)[0]
268     projects = manifest_dom.getElementsByTagName(PROJECT_ELEMENT)
269     pending_commits = manifest_dom.getElementsByTagName(PALADIN_COMMIT_ELEMENT)
270
271     default_remote = LKGMManager._GetDefaultRemote(manifest_dom)
272     internal_projects = set()
273     for project_element in projects:
274       project_remote = project_element.getAttribute(PROJECT_REMOTE_ATTR)
275       project = project_element.getAttribute(PROJECT_NAME_ATTR)
276       if not project_remote:
277         if not default_remote:
278           # This should not happen for a valid manifest. Either each
279           # project must have a remote specified or there should
280           # be manifest default we could use.
281           raise FilterManifestException(
282               'Project %s has unspecified remote with no default' % project)
283         project_remote = default_remote
284       if project_remote not in whitelisted_remotes:
285         internal_projects.add(project)
286         manifest_node.removeChild(project_element)
287
288     for commit_element in pending_commits:
289       if commit_element.getAttribute(
290           PALADIN_PROJECT_ATTR) in internal_projects:
291         manifest_node.removeChild(commit_element)
292
293     with os.fdopen(temp_fd, 'w') as manifest_file:
294       # Filter out empty lines.
295       filtered_manifest_noempty = filter(
296           str.strip, manifest_dom.toxml('utf-8').splitlines())
297       manifest_file.write(os.linesep.join(filtered_manifest_noempty))
298
299     return new_path
300
301   def CreateNewCandidate(self, validation_pool=None,
302                          chrome_version=None,
303                          retries=manifest_version.NUM_RETRIES,
304                          build_id=None):
305     """Creates, syncs to, and returns the next candidate manifest.
306
307     Args:
308       validation_pool: Validation pool to apply to the manifest before
309         publishing.
310       chrome_version: The Chrome version to write in the manifest. Defaults
311         to None, in which case no version is written.
312       retries: Number of retries for updating the status. Defaults to
313         manifest_version.NUM_RETRIES.
314       build_id: Optional integer cidb id of the build that is creating
315                 this candidate.
316
317     Raises:
318       GenerateBuildSpecException in case of failure to generate a buildspec
319     """
320     self.CheckoutSourceCode()
321
322     # Refresh manifest logic from manifest_versions repository to grab the
323     # LKGM to generate the blamelist.
324     version_info = self.GetCurrentVersionInfo()
325     self.RefreshManifestCheckout()
326     self.InitializeManifestVariables(version_info)
327
328     self._GenerateBlameListSinceLKGM()
329     new_manifest = self.CreateManifest()
330
331     # For Chrome PFQ, add the version of Chrome to use.
332     if chrome_version:
333       self._AddChromeVersionToManifest(new_manifest, chrome_version)
334
335     # For the Commit Queue, apply the validation pool as part of checkout.
336     if validation_pool:
337       # If we have nothing that could apply from the validation pool and
338       # we're not also a pfq type, we got nothing to do.
339       assert self.cros_source.directory == validation_pool.build_root
340       if (not validation_pool.ApplyPoolIntoRepo() and
341           not cbuildbot_config.IsPFQType(self.build_type)):
342         return None
343
344       self._AddPatchesToManifest(new_manifest, validation_pool.changes)
345
346     last_error = None
347     for attempt in range(0, retries + 1):
348       try:
349         # Refresh manifest logic from manifest_versions repository.
350         # Note we don't need to do this on our first attempt as we needed to
351         # have done it to get the LKGM.
352         if attempt != 0:
353           self.RefreshManifestCheckout()
354           self.InitializeManifestVariables(version_info)
355
356         # If we don't have any valid changes to test, make sure the checkout
357         # is at least different.
358         if ((not validation_pool or not validation_pool.changes) and
359             not self.force and self.HasCheckoutBeenBuilt()):
360           return None
361
362         # Check whether the latest spec available in manifest-versions is
363         # newer than our current version number. If so, use it as the base
364         # version number. Otherwise, we default to 'rc1'.
365         if self.latest:
366           latest = max(self.latest, version_info.VersionString(),
367                        key=self.compare_versions_fn)
368           version_info = _LKGMCandidateInfo(
369               latest, chrome_branch=version_info.chrome_branch,
370               incr_type=self.incr_type)
371
372         git.CreatePushBranch(manifest_version.PUSH_BRANCH, self.manifest_dir,
373                              sync=False)
374         version = self.GetNextVersion(version_info)
375         self.PublishManifest(new_manifest, version, build_id=build_id)
376         self.current_version = version
377         return self.GetLocalManifest(version)
378       except cros_build_lib.RunCommandError as e:
379         err_msg = 'Failed to generate LKGM Candidate. error: %s' % e
380         logging.error(err_msg)
381         last_error = err_msg
382     else:
383       raise manifest_version.GenerateBuildSpecException(last_error)
384
385   def CreateFromManifest(self, manifest, retries=manifest_version.NUM_RETRIES,
386                          dashboard_url=None, build_id=None):
387     """Sets up an lkgm_manager from the given manifest.
388
389     This method sets up an LKGM manager and publishes a new manifest to the
390     manifest versions repo based on the passed in manifest but filtering
391     internal repositories and changes out of it.
392
393     Args:
394       manifest: A manifest that possibly contains private changes/projects. It
395         is named with the given version we want to create a new manifest from
396         i.e R20-1920.0.1-rc7.xml where R20-1920.0.1-rc7 is the version.
397       retries: Number of retries for updating the status.
398       dashboard_url: Optional url linking to builder dashboard for this build.
399       build_id: Optional integer cidb build id of the build publishing the
400                 manifest.
401
402     Raises:
403       GenerateBuildSpecException in case of failure to check-in the new
404         manifest because of a git error or the manifest is already checked-in.
405     """
406     last_error = None
407     new_manifest = self._FilterCrosInternalProjectsFromManifest(manifest)
408     version_info = self.GetCurrentVersionInfo()
409     for _attempt in range(0, retries + 1):
410       try:
411         self.RefreshManifestCheckout()
412         self.InitializeManifestVariables(version_info)
413
414         git.CreatePushBranch(manifest_version.PUSH_BRANCH, self.manifest_dir,
415                              sync=False)
416         version = os.path.splitext(os.path.basename(manifest))[0]
417         logging.info('Publishing filtered build spec')
418         self.PublishManifest(new_manifest, version, build_id=build_id)
419         self.SetInFlight(version, dashboard_url=dashboard_url)
420         self.current_version = version
421         return self.GetLocalManifest(version)
422       except cros_build_lib.RunCommandError as e:
423         err_msg = 'Failed to generate LKGM Candidate. error: %s' % e
424         logging.error(err_msg)
425         last_error = err_msg
426     else:
427       raise manifest_version.GenerateBuildSpecException(last_error)
428
429   def GetLatestCandidate(self, dashboard_url=None, timeout=3 * 60):
430     """Gets and syncs to the next candiate manifest.
431
432     Args:
433       retries: Number of retries for updating the status
434       dashboard_url: Optional url linking to builder dashboard for this build.
435       timeout: The timeout in seconds.
436
437     Returns:
438       Local path to manifest to build or None in case of no need to build.
439
440     Raises:
441       GenerateBuildSpecException in case of failure to generate a buildspec
442     """
443     def _AttemptToGetLatestCandidate():
444       """Attempts to acquire latest candidate using manifest repo."""
445       self.RefreshManifestCheckout()
446       self.InitializeManifestVariables(self.GetCurrentVersionInfo())
447       if self.latest_unprocessed:
448         return self.latest_unprocessed
449       elif self.dry_run and self.latest:
450         return self.latest
451
452     def _PrintRemainingTime(minutes_left):
453       logging.info('Found nothing new to build, will keep trying for %d more'
454                    ' minutes.', minutes_left)
455       logging.info('If this is a PFQ, then you should have forced the master'
456                    ', which runs cbuildbot_master')
457
458     # TODO(sosa):  We only really need the overlay for the version info but we
459     # do a full checkout here because we have no way of refining it currently.
460     self.CheckoutSourceCode()
461     try:
462       version_to_build = timeout_util.WaitForSuccess(
463           lambda x: x is None,
464           _AttemptToGetLatestCandidate,
465           timeout,
466           period=self.SLEEP_TIMEOUT,
467           side_effect_func=_PrintRemainingTime)
468     except timeout_util.TimeoutError:
469       version_to_build = None
470
471     if version_to_build:
472       logging.info('Starting build spec: %s', version_to_build)
473       self.SetInFlight(version_to_build, dashboard_url=dashboard_url)
474       self.current_version = version_to_build
475
476       # Actually perform the sync.
477       manifest = self.GetLocalManifest(version_to_build)
478       self.cros_source.Sync(manifest)
479       self._GenerateBlameListSinceLKGM()
480       return manifest
481     else:
482       return None
483
484   def PromoteCandidate(self, retries=manifest_version.NUM_RETRIES):
485     """Promotes the current LKGM candidate to be a real versioned LKGM."""
486     assert self.current_version, 'No current manifest exists.'
487
488     last_error = None
489     path_to_candidate = self.GetLocalManifest(self.current_version)
490     assert os.path.exists(path_to_candidate), 'Candidate not found locally.'
491
492     # This may potentially fail for not being at TOT while pushing.
493     for attempt in range(0, retries + 1):
494       try:
495         if attempt > 0:
496           self.RefreshManifestCheckout()
497         git.CreatePushBranch(manifest_version.PUSH_BRANCH,
498                              self.manifest_dir, sync=False)
499         manifest_version.CreateSymlink(path_to_candidate, self.lkgm_path)
500         git.RunGit(self.manifest_dir, ['add', self.LKGM_PATH])
501         self.PushSpecChanges(
502             'Automatic: %s promoting %s to LKGM' % (self.build_names[0],
503                                                     self.current_version))
504         return
505       except cros_build_lib.RunCommandError as e:
506         last_error = 'Failed to promote manifest. error: %s' % e
507         logging.error(last_error)
508         logging.error('Retrying to promote manifest:  Retry %d/%d', attempt + 1,
509                       retries)
510
511     else:
512       raise PromoteCandidateException(last_error)
513
514   def _ShouldGenerateBlameListSinceLKGM(self):
515     """Returns True if we should generate the blamelist."""
516     # We want to generate the blamelist only for valid pfq types and if we are
517     # building on the master branch i.e. revving the build number.
518     return (self.incr_type == 'build' and
519             cbuildbot_config.IsPFQType(self.build_type) and
520             self.build_type != constants.CHROME_PFQ_TYPE)
521
522   def _GenerateBlameListSinceLKGM(self):
523     """Prints out links to all CL's that have been committed since LKGM.
524
525     Add buildbot trappings to print <a href='url'>text</a> in the waterfall for
526     each CL committed since we last had a passing build.
527     """
528     if not self._ShouldGenerateBlameListSinceLKGM():
529       logging.info('Not generating blamelist for lkgm as it is not appropriate '
530                    'for this build type.')
531       return
532     # Suppress re-printing changes we tried ourselves on paladin
533     # builders since they are redundant.
534     only_print_chumps = self.build_type == constants.PALADIN_TYPE
535     GenerateBlameList(self.cros_source, self.lkgm_path,
536                       only_print_chumps=only_print_chumps)
537
538   def GetLatestPassingSpec(self):
539     """Get the last spec file that passed in the current branch."""
540     raise NotImplementedError()
541
542
543 def GenerateBlameList(source_repo, lkgm_path, only_print_chumps=False):
544   """Generate the blamelist since the specified manifest.
545
546   Args:
547     source_repo: Repository object for the source code.
548     lkgm_path: Path to LKGM manifest.
549     only_print_chumps: If True, only print changes that were chumped.
550   """
551   handler = git.Manifest(lkgm_path)
552   reviewed_on_re = re.compile(r'\s*Reviewed-on:\s*(\S+)')
553   author_re = re.compile(r'\s*Author:.*<(\S+)@\S+>\s*')
554   committer_re = re.compile(r'\s*Commit:.*<(\S+)@\S+>\s*')
555   for rel_src_path, checkout in handler.checkouts_by_path.iteritems():
556     project = checkout['name']
557
558     # Additional case in case the repo has been removed from the manifest.
559     src_path = source_repo.GetRelativePath(rel_src_path)
560     if not os.path.exists(src_path):
561       cros_build_lib.Info('Detected repo removed from manifest %s' % project)
562       continue
563
564     revision = checkout['revision']
565     cmd = ['log', '--pretty=full', '%s..HEAD' % revision]
566     try:
567       result = git.RunGit(src_path, cmd)
568     except cros_build_lib.RunCommandError as ex:
569       # Git returns 128 when the revision does not exist.
570       if ex.result.returncode != 128:
571         raise
572       cros_build_lib.Warning('Detected branch removed from local checkout.')
573       cros_build_lib.PrintBuildbotStepWarnings()
574       return
575     current_author = None
576     current_committer = None
577     for line in unicode(result.output, 'ascii', 'ignore').splitlines():
578       author_match = author_re.match(line)
579       if author_match:
580         current_author = author_match.group(1)
581
582       committer_match = committer_re.match(line)
583       if committer_match:
584         current_committer = committer_match.group(1)
585
586       review_match = reviewed_on_re.match(line)
587       if review_match:
588         review = review_match.group(1)
589         _, _, change_number = review.rpartition('/')
590         items = [
591             os.path.basename(project),
592             current_author,
593             change_number,
594         ]
595         if current_committer not in ('chrome-bot', 'chrome-internal-fetch',
596                                      'chromeos-commit-bot'):
597           items.insert(0, 'CHUMP')
598         elif only_print_chumps:
599           continue
600         cros_build_lib.PrintBuildbotLink(' | '.join(items), review)