1*c8dee2aaSAndroid Build Coastguard Worker#!/usr/bin/env python 2*c8dee2aaSAndroid Build Coastguard Worker# Copyright (c) 2014 The Chromium Authors. All rights reserved. 3*c8dee2aaSAndroid Build Coastguard Worker# Use of this source code is governed by a BSD-style license that can be 4*c8dee2aaSAndroid Build Coastguard Worker# found in the LICENSE file. 5*c8dee2aaSAndroid Build Coastguard Worker 6*c8dee2aaSAndroid Build Coastguard Worker"""This module contains functions for using git.""" 7*c8dee2aaSAndroid Build Coastguard Worker 8*c8dee2aaSAndroid Build Coastguard Workerimport os 9*c8dee2aaSAndroid Build Coastguard Workerimport re 10*c8dee2aaSAndroid Build Coastguard Workerimport shutil 11*c8dee2aaSAndroid Build Coastguard Workerimport subprocess 12*c8dee2aaSAndroid Build Coastguard Workerimport tempfile 13*c8dee2aaSAndroid Build Coastguard Worker 14*c8dee2aaSAndroid Build Coastguard Workerimport utils 15*c8dee2aaSAndroid Build Coastguard Worker 16*c8dee2aaSAndroid Build Coastguard Worker 17*c8dee2aaSAndroid Build Coastguard Workerclass GitLocalConfig(object): 18*c8dee2aaSAndroid Build Coastguard Worker """Class to manage local git configs.""" 19*c8dee2aaSAndroid Build Coastguard Worker def __init__(self, config_dict): 20*c8dee2aaSAndroid Build Coastguard Worker self._config_dict = config_dict 21*c8dee2aaSAndroid Build Coastguard Worker self._previous_values = {} 22*c8dee2aaSAndroid Build Coastguard Worker 23*c8dee2aaSAndroid Build Coastguard Worker def __enter__(self): 24*c8dee2aaSAndroid Build Coastguard Worker for k, v in self._config_dict.items(): 25*c8dee2aaSAndroid Build Coastguard Worker try: 26*c8dee2aaSAndroid Build Coastguard Worker prev = subprocess.check_output([ 27*c8dee2aaSAndroid Build Coastguard Worker 'git', 'config', '--local', k]).decode('utf-8').rstrip() 28*c8dee2aaSAndroid Build Coastguard Worker if prev: 29*c8dee2aaSAndroid Build Coastguard Worker self._previous_values[k] = prev 30*c8dee2aaSAndroid Build Coastguard Worker except subprocess.CalledProcessError: 31*c8dee2aaSAndroid Build Coastguard Worker # We are probably here because the key did not exist in the config. 32*c8dee2aaSAndroid Build Coastguard Worker pass 33*c8dee2aaSAndroid Build Coastguard Worker subprocess.check_call(['git', 'config', '--local', k, v]) 34*c8dee2aaSAndroid Build Coastguard Worker 35*c8dee2aaSAndroid Build Coastguard Worker def __exit__(self, exc_type, _value, _traceback): 36*c8dee2aaSAndroid Build Coastguard Worker for k in self._config_dict: 37*c8dee2aaSAndroid Build Coastguard Worker if self._previous_values.get(k): 38*c8dee2aaSAndroid Build Coastguard Worker subprocess.check_call( 39*c8dee2aaSAndroid Build Coastguard Worker ['git', 'config', '--local', k, self._previous_values[k]]) 40*c8dee2aaSAndroid Build Coastguard Worker else: 41*c8dee2aaSAndroid Build Coastguard Worker subprocess.check_call(['git', 'config', '--local', '--unset', k]) 42*c8dee2aaSAndroid Build Coastguard Worker 43*c8dee2aaSAndroid Build Coastguard Worker 44*c8dee2aaSAndroid Build Coastguard Workerclass GitBranch(object): 45*c8dee2aaSAndroid Build Coastguard Worker """Class to manage git branches. 46*c8dee2aaSAndroid Build Coastguard Worker 47*c8dee2aaSAndroid Build Coastguard Worker This class allows one to create a new branch in a repository to make changes, 48*c8dee2aaSAndroid Build Coastguard Worker then it commits the changes, switches to main branch, and deletes the 49*c8dee2aaSAndroid Build Coastguard Worker created temporary branch upon exit. 50*c8dee2aaSAndroid Build Coastguard Worker """ 51*c8dee2aaSAndroid Build Coastguard Worker def __init__(self, branch_name, commit_msg, upload=True, commit_queue=False, 52*c8dee2aaSAndroid Build Coastguard Worker delete_when_finished=True, cc_list=None): 53*c8dee2aaSAndroid Build Coastguard Worker self._branch_name = branch_name 54*c8dee2aaSAndroid Build Coastguard Worker self._commit_msg = commit_msg 55*c8dee2aaSAndroid Build Coastguard Worker self._upload = upload 56*c8dee2aaSAndroid Build Coastguard Worker self._commit_queue = commit_queue 57*c8dee2aaSAndroid Build Coastguard Worker self._patch_set = 0 58*c8dee2aaSAndroid Build Coastguard Worker self._delete_when_finished = delete_when_finished 59*c8dee2aaSAndroid Build Coastguard Worker self._cc_list = cc_list 60*c8dee2aaSAndroid Build Coastguard Worker 61*c8dee2aaSAndroid Build Coastguard Worker def __enter__(self): 62*c8dee2aaSAndroid Build Coastguard Worker subprocess.check_call(['git', 'reset', '--hard', 'HEAD']) 63*c8dee2aaSAndroid Build Coastguard Worker subprocess.check_call(['git', 'checkout', 'main']) 64*c8dee2aaSAndroid Build Coastguard Worker if self._branch_name in subprocess.check_output([ 65*c8dee2aaSAndroid Build Coastguard Worker 'git', 'branch']).decode('utf-8').split(): 66*c8dee2aaSAndroid Build Coastguard Worker subprocess.check_call(['git', 'branch', '-D', self._branch_name]) 67*c8dee2aaSAndroid Build Coastguard Worker subprocess.check_call(['git', 'checkout', '-b', self._branch_name, 68*c8dee2aaSAndroid Build Coastguard Worker '-t', 'origin/main']) 69*c8dee2aaSAndroid Build Coastguard Worker return self 70*c8dee2aaSAndroid Build Coastguard Worker 71*c8dee2aaSAndroid Build Coastguard Worker def commit_and_upload(self, use_commit_queue=False): 72*c8dee2aaSAndroid Build Coastguard Worker """Commit all changes and upload a CL, returning the issue URL.""" 73*c8dee2aaSAndroid Build Coastguard Worker subprocess.check_call(['git', 'commit', '-a', '-m', self._commit_msg]) 74*c8dee2aaSAndroid Build Coastguard Worker upload_cmd = ['git', 'cl', 'upload', '-f', '--bypass-hooks', 75*c8dee2aaSAndroid Build Coastguard Worker '--bypass-watchlists'] 76*c8dee2aaSAndroid Build Coastguard Worker self._patch_set += 1 77*c8dee2aaSAndroid Build Coastguard Worker if self._patch_set > 1: 78*c8dee2aaSAndroid Build Coastguard Worker upload_cmd.extend(['-t', 'Patch set %d' % self._patch_set]) 79*c8dee2aaSAndroid Build Coastguard Worker if use_commit_queue: 80*c8dee2aaSAndroid Build Coastguard Worker upload_cmd.append('--use-commit-queue') 81*c8dee2aaSAndroid Build Coastguard Worker # Need the --send-mail flag to publish the CL and remove WIP bit. 82*c8dee2aaSAndroid Build Coastguard Worker upload_cmd.append('--send-mail') 83*c8dee2aaSAndroid Build Coastguard Worker if self._cc_list: 84*c8dee2aaSAndroid Build Coastguard Worker upload_cmd.extend(['--cc=%s' % ','.join(self._cc_list)]) 85*c8dee2aaSAndroid Build Coastguard Worker subprocess.check_call(upload_cmd) 86*c8dee2aaSAndroid Build Coastguard Worker output = subprocess.check_output([ 87*c8dee2aaSAndroid Build Coastguard Worker 'git', 'cl', 'issue']).decode('utf-8').rstrip() 88*c8dee2aaSAndroid Build Coastguard Worker return re.match('^Issue number: (?P<issue>\d+) \((?P<issue_url>.+)\)$', 89*c8dee2aaSAndroid Build Coastguard Worker output).group('issue_url') 90*c8dee2aaSAndroid Build Coastguard Worker 91*c8dee2aaSAndroid Build Coastguard Worker def __exit__(self, exc_type, _value, _traceback): 92*c8dee2aaSAndroid Build Coastguard Worker if self._upload: 93*c8dee2aaSAndroid Build Coastguard Worker # Only upload if no error occurred. 94*c8dee2aaSAndroid Build Coastguard Worker try: 95*c8dee2aaSAndroid Build Coastguard Worker if exc_type is None: 96*c8dee2aaSAndroid Build Coastguard Worker self.commit_and_upload(use_commit_queue=self._commit_queue) 97*c8dee2aaSAndroid Build Coastguard Worker finally: 98*c8dee2aaSAndroid Build Coastguard Worker subprocess.check_call(['git', 'checkout', 'main']) 99*c8dee2aaSAndroid Build Coastguard Worker if self._delete_when_finished: 100*c8dee2aaSAndroid Build Coastguard Worker subprocess.check_call(['git', 'branch', '-D', self._branch_name]) 101*c8dee2aaSAndroid Build Coastguard Worker 102*c8dee2aaSAndroid Build Coastguard Worker 103*c8dee2aaSAndroid Build Coastguard Workerclass NewGitCheckout(utils.tmp_dir): 104*c8dee2aaSAndroid Build Coastguard Worker """Creates a new local checkout of a Git repository.""" 105*c8dee2aaSAndroid Build Coastguard Worker 106*c8dee2aaSAndroid Build Coastguard Worker def __init__(self, repository, local=None): 107*c8dee2aaSAndroid Build Coastguard Worker """Set parameters for this local copy of a Git repository. 108*c8dee2aaSAndroid Build Coastguard Worker 109*c8dee2aaSAndroid Build Coastguard Worker Because this is a new checkout, rather than a reference to an existing 110*c8dee2aaSAndroid Build Coastguard Worker checkout on disk, it is safe to assume that the calling thread is the 111*c8dee2aaSAndroid Build Coastguard Worker only thread manipulating the checkout. 112*c8dee2aaSAndroid Build Coastguard Worker 113*c8dee2aaSAndroid Build Coastguard Worker You must use the 'with' statement to create this object: 114*c8dee2aaSAndroid Build Coastguard Worker 115*c8dee2aaSAndroid Build Coastguard Worker with NewGitCheckout(*args) as checkout: 116*c8dee2aaSAndroid Build Coastguard Worker # use checkout instance 117*c8dee2aaSAndroid Build Coastguard Worker # the checkout is automatically cleaned up here 118*c8dee2aaSAndroid Build Coastguard Worker 119*c8dee2aaSAndroid Build Coastguard Worker Args: 120*c8dee2aaSAndroid Build Coastguard Worker repository: URL of the remote repository (e.g., 121*c8dee2aaSAndroid Build Coastguard Worker 'https://skia.googlesource.com/common') or path to a local repository 122*c8dee2aaSAndroid Build Coastguard Worker (e.g., '/path/to/repo/.git') to check out a copy of 123*c8dee2aaSAndroid Build Coastguard Worker local: optional path to an existing copy of the remote repo on local disk. 124*c8dee2aaSAndroid Build Coastguard Worker If provided, the initial clone is performed with the local copy as the 125*c8dee2aaSAndroid Build Coastguard Worker upstream, then the upstream is switched to the remote repo and the 126*c8dee2aaSAndroid Build Coastguard Worker new copy is updated from there. 127*c8dee2aaSAndroid Build Coastguard Worker """ 128*c8dee2aaSAndroid Build Coastguard Worker super(NewGitCheckout, self).__init__() 129*c8dee2aaSAndroid Build Coastguard Worker self._checkout_root = '' 130*c8dee2aaSAndroid Build Coastguard Worker self._repository = repository 131*c8dee2aaSAndroid Build Coastguard Worker self._local = local 132*c8dee2aaSAndroid Build Coastguard Worker 133*c8dee2aaSAndroid Build Coastguard Worker @property 134*c8dee2aaSAndroid Build Coastguard Worker def name(self): 135*c8dee2aaSAndroid Build Coastguard Worker return self._checkout_root 136*c8dee2aaSAndroid Build Coastguard Worker 137*c8dee2aaSAndroid Build Coastguard Worker @property 138*c8dee2aaSAndroid Build Coastguard Worker def root(self): 139*c8dee2aaSAndroid Build Coastguard Worker """Returns the root directory containing the checked-out files.""" 140*c8dee2aaSAndroid Build Coastguard Worker return self.name 141*c8dee2aaSAndroid Build Coastguard Worker 142*c8dee2aaSAndroid Build Coastguard Worker def __enter__(self): 143*c8dee2aaSAndroid Build Coastguard Worker """Check out a new local copy of the repository. 144*c8dee2aaSAndroid Build Coastguard Worker 145*c8dee2aaSAndroid Build Coastguard Worker Uses the parameters that were passed into the constructor. 146*c8dee2aaSAndroid Build Coastguard Worker """ 147*c8dee2aaSAndroid Build Coastguard Worker super(NewGitCheckout, self).__enter__() 148*c8dee2aaSAndroid Build Coastguard Worker remote = self._repository 149*c8dee2aaSAndroid Build Coastguard Worker if self._local: 150*c8dee2aaSAndroid Build Coastguard Worker remote = self._local 151*c8dee2aaSAndroid Build Coastguard Worker subprocess.check_call(['git', 'clone', remote]) 152*c8dee2aaSAndroid Build Coastguard Worker repo_name = remote.split('/')[-1] 153*c8dee2aaSAndroid Build Coastguard Worker if repo_name.endswith('.git'): 154*c8dee2aaSAndroid Build Coastguard Worker repo_name = repo_name[:-len('.git')] 155*c8dee2aaSAndroid Build Coastguard Worker self._checkout_root = os.path.join(os.getcwd(), repo_name) 156*c8dee2aaSAndroid Build Coastguard Worker os.chdir(repo_name) 157*c8dee2aaSAndroid Build Coastguard Worker if self._local: 158*c8dee2aaSAndroid Build Coastguard Worker subprocess.check_call([ 159*c8dee2aaSAndroid Build Coastguard Worker 'git', 'remote', 'set-url', 'origin', self._repository]) 160*c8dee2aaSAndroid Build Coastguard Worker subprocess.check_call(['git', 'remote', 'update']) 161*c8dee2aaSAndroid Build Coastguard Worker subprocess.check_call(['git', 'checkout', 'main']) 162*c8dee2aaSAndroid Build Coastguard Worker subprocess.check_call(['git', 'reset', '--hard', 'origin/main']) 163*c8dee2aaSAndroid Build Coastguard Worker return self 164