1#!/usr/bin/env python3 2# -*- coding: utf-8 -*- 3# Copyright 2020 The ChromiumOS Authors 4# Use of this source code is governed by a BSD-style license that can be 5# found in the LICENSE file. 6 7"""Git helper functions.""" 8 9 10import collections 11import os 12import re 13import subprocess 14import tempfile 15 16 17CommitContents = collections.namedtuple("CommitContents", ["url", "cl_number"]) 18 19 20def InChroot(): 21 """Returns True if currently in the chroot.""" 22 return "CROS_WORKON_SRCROOT" in os.environ 23 24 25def VerifyOutsideChroot(): 26 """Checks whether the script invoked was executed in the chroot. 27 28 Raises: 29 AssertionError: The script was run inside the chroot. 30 """ 31 32 assert not InChroot(), "Script should be run outside the chroot." 33 34 35def CreateBranch(repo, branch): 36 """Creates a branch in the given repo. 37 38 Args: 39 repo: The absolute path to the repo. 40 branch: The name of the branch to create. 41 42 Raises: 43 ValueError: Failed to create a repo in that directory. 44 """ 45 46 if not os.path.isdir(repo): 47 raise ValueError("Invalid directory path provided: %s" % repo) 48 49 subprocess.check_output(["git", "-C", repo, "reset", "HEAD", "--hard"]) 50 51 subprocess.check_output(["repo", "start", branch], cwd=repo) 52 53 54def DeleteBranch(repo, branch): 55 """Deletes a branch in the given repo. 56 57 Args: 58 repo: The absolute path of the repo. 59 branch: The name of the branch to delete. 60 61 Raises: 62 ValueError: Failed to delete the repo in that directory. 63 """ 64 65 if not os.path.isdir(repo): 66 raise ValueError("Invalid directory path provided: %s" % repo) 67 68 subprocess.check_output(["git", "-C", repo, "checkout", "cros/main"]) 69 70 subprocess.check_output(["git", "-C", repo, "reset", "HEAD", "--hard"]) 71 72 subprocess.check_output(["git", "-C", repo, "branch", "-D", branch]) 73 74 75def UploadChanges(repo, branch, commit_messages, reviewers=None, cc=None): 76 """Uploads the changes in the specifed branch of the given repo for review. 77 78 Args: 79 repo: The absolute path to the repo where changes were made. 80 branch: The name of the branch to upload. 81 commit_messages: A string of commit message(s) (i.e. '[message]' 82 of the changes made. 83 reviewers: A list of reviewers to add to the CL. 84 cc: A list of contributors to CC about the CL. 85 86 Returns: 87 A nametuple that has two (key, value) pairs, where the first pair is the 88 Gerrit commit URL and the second pair is the change list number. 89 90 Raises: 91 ValueError: Failed to create a commit or failed to upload the 92 changes for review. 93 """ 94 95 if not os.path.isdir(repo): 96 raise ValueError("Invalid path provided: %s" % repo) 97 98 # Create a git commit. 99 with tempfile.NamedTemporaryFile(mode="w+t") as f: 100 f.write("\n".join(commit_messages)) 101 f.flush() 102 103 subprocess.check_output(["git", "commit", "-F", f.name], cwd=repo) 104 105 # Upload the changes for review. 106 git_args = [ 107 "repo", 108 "upload", 109 "--yes", 110 f'--reviewers={",".join(reviewers)}' if reviewers else "--ne", 111 "--no-verify", 112 f"--br={branch}", 113 ] 114 115 if cc: 116 git_args.append(f'--cc={",".join(cc)}') 117 118 out = subprocess.check_output( 119 git_args, 120 stderr=subprocess.STDOUT, 121 cwd=repo, 122 encoding="utf-8", 123 ) 124 125 print(out) 126 127 found_url = re.search( 128 r"https://chromium-review.googlesource.com/c/" 129 r"chromiumos/overlays/chromiumos-overlay/\+/([0-9]+)", 130 out.rstrip(), 131 ) 132 133 if not found_url: 134 raise ValueError("Failed to find change list URL.") 135 136 return CommitContents( 137 url=found_url.group(0), cl_number=int(found_url.group(1)) 138 ) 139