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