| # -*- coding: utf-8 -*- |
| # Copyright 2018 The Chromium OS Authors. All rights reserved. |
| # Use of this source code is governed by a BSD-style license that can be |
| # found in the LICENSE file. |
| |
| """Commits files to the chromium git repository.""" |
| |
| from __future__ import print_function |
| |
| import os |
| |
| from chromite.lib import constants |
| from chromite.lib import commandline |
| from chromite.lib import cros_build_lib |
| from chromite.lib import cros_logging as logging |
| from chromite.lib import git |
| from chromite.lib import osutils |
| from chromite.lib import tree_status |
| |
| |
| class CommitError(Exception): |
| """Raised if the commit failed.""" |
| |
| |
| class ChromeCommitter(object): |
| """Committer object responsible for committing a git change.""" |
| |
| def __init__(self, args): |
| self._checkout_dir = args.workdir |
| self._dryrun = args.dryrun |
| self._git_committer_args = ['-c', 'user.email=%s' % args.user_email, |
| '-c', 'user.name=%s' % args.user_email] |
| self._commit_msg = '' |
| |
| logging.info('user_email=%s', args.user_email) |
| logging.info('checkout_dir=%s', args.workdir) |
| |
| def __del__(self): |
| self.Cleanup() |
| |
| def FullPath(self, file_path): |
| """Returns the full path in the source tree given a relative path. |
| |
| Args: |
| file_path: Path of file. |
| |
| Returns: |
| Full path rooted in source checkout. |
| """ |
| if os.path.isabs(file_path): |
| return file_path |
| return os.path.join(self._checkout_dir, file_path) |
| |
| def Checkout(self, sparse_checkout): |
| """Checks out chrome into tmp checkout dir. |
| |
| Args: |
| sparse_checkout: List of file paths to fetch. |
| """ |
| assert isinstance(sparse_checkout, list) |
| sparse_checkout += ['codereview.settings', 'WATCHLISTS'] |
| git.ShallowFetch(self._checkout_dir, constants.CHROMIUM_GOB_URL, |
| sparse_checkout=sparse_checkout) |
| git.CreateBranch(self._checkout_dir, 'auto-commit-branch', |
| branch_point='origin/master') |
| |
| def Commit(self, file_paths, commit_msg): |
| """Commits files listed in |file_paths|. |
| |
| Args: |
| file_paths: List of files to commit. |
| commit_msg: Message to use in commit. |
| """ |
| assert file_paths and isinstance(file_paths, list) |
| # Make paths absolute and ensure they exist. |
| for i, file_path in enumerate(file_paths): |
| if not os.path.isabs(file_path): |
| file_paths[i] = self.FullPath(file_path) |
| if not os.path.exists(file_paths[i]): |
| raise CommitError('Invalid path: %s' % file_paths[i]) |
| |
| self._commit_msg = 'Automated Commit: ' + commit_msg |
| try: |
| for file_path in file_paths: |
| git.AddPath(file_path) |
| commit_args = ['commit', '-m', self._commit_msg] |
| git.RunGit(self._checkout_dir, self._git_committer_args + commit_args, |
| print_cmd=True, redirect_stderr=True, capture_output=False) |
| except cros_build_lib.RunCommandError as e: |
| raise CommitError('Could not create git commit: %r' % e) |
| |
| def Upload(self): |
| """Uploads the change to gerrit.""" |
| logging.info('Uploading commit.') |
| |
| try: |
| # Run 'git cl upload' with --bypass-hooks to skip running scripts that are |
| # not part of the shallow checkout, -f to skip editing the CL message, |
| upload_args = ['cl', 'upload', '-v', '-m', self._commit_msg, |
| '--bypass-hooks', '-f'] |
| if not self._dryrun: |
| # Add the gardener(s) as TBR; fall-back to tbr-owners. |
| gardeners = tree_status.GetSheriffEmailAddresses('chrome') |
| if gardeners: |
| for tbr in gardeners: |
| upload_args += ['--tbrs', tbr] |
| else: |
| upload_args += ['--tbr-owners'] |
| # Marks CL as ready. |
| upload_args += ['--send-mail'] |
| git.RunGit(self._checkout_dir, self._git_committer_args + upload_args, |
| print_cmd=True, redirect_stderr=True, capture_output=False) |
| |
| # Flip the CQ commit bit. |
| submit_args = ['cl', 'set-commit', '-v'] |
| if self._dryrun: |
| submit_args += ['--dry-run'] |
| git.RunGit(self._checkout_dir, submit_args, |
| print_cmd=True, redirect_stderr=True, capture_output=False) |
| except cros_build_lib.RunCommandError as e: |
| # Log the change for debugging. |
| git.RunGit(self._checkout_dir, ['--no-pager', 'log', '--pretty=full'], |
| capture_output=False) |
| raise CommitError('Could not submit: %r' % e) |
| |
| logging.info('Submitted to CQ.') |
| |
| def Cleanup(self): |
| """Remove chrome checkout.""" |
| osutils.RmDir(self._checkout_dir, ignore_missing=True) |
| |
| @staticmethod |
| def GetParser(): |
| """Returns parser for ChromeCommitter. |
| |
| Returns: |
| Dictionary of parsed command line args. |
| """ |
| # We need to use the account used by the builder to upload git CLs when |
| # generating CLs. |
| default_git_account = None |
| if cros_build_lib.HostIsCIBuilder(golo_only=True): |
| default_git_account = 'chromeos-commit-bot@chromium.org' |
| elif cros_build_lib.HostIsCIBuilder(gce_only=True): |
| default_git_account = '3su6n15k.default@developer.gserviceaccount.com' |
| |
| parser = commandline.ArgumentParser(usage=__doc__, add_help=False) |
| parser.add_argument('--dryrun', action='store_true', default=False, |
| help='Don\'t commit changes or send out emails.') |
| parser.add_argument('--user_email', required=False, |
| default=default_git_account, |
| help='Email address to use when comitting changes.') |
| parser.add_argument('--workdir', |
| default=os.path.join(os.getcwd(), 'chrome_src'), |
| help=('Path to a checkout of the chrome src. ' |
| 'Defaults to PWD/chrome_src')) |
| return parser |