1*3c875a21SAndroid Build Coastguard Worker# Copyright (C) 2018 The Android Open Source Project 2*3c875a21SAndroid Build Coastguard Worker# 3*3c875a21SAndroid Build Coastguard Worker# Licensed under the Apache License, Version 2.0 (the 'License'); 4*3c875a21SAndroid Build Coastguard Worker# you may not use this file except in compliance with the License. 5*3c875a21SAndroid Build Coastguard Worker# You may obtain a copy of the License at 6*3c875a21SAndroid Build Coastguard Worker# 7*3c875a21SAndroid Build Coastguard Worker# http://www.apache.org/licenses/LICENSE-2.0 8*3c875a21SAndroid Build Coastguard Worker# 9*3c875a21SAndroid Build Coastguard Worker# Unless required by applicable law or agreed to in writing, software 10*3c875a21SAndroid Build Coastguard Worker# distributed under the License is distributed on an 'AS IS' BASIS, 11*3c875a21SAndroid Build Coastguard Worker# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12*3c875a21SAndroid Build Coastguard Worker# See the License for the specific language governing permissions and 13*3c875a21SAndroid Build Coastguard Worker# limitations under the License. 14*3c875a21SAndroid Build Coastguard Worker"""Helper functions to communicate with Git.""" 15*3c875a21SAndroid Build Coastguard Worker 16*3c875a21SAndroid Build Coastguard Workerimport datetime 17*3c875a21SAndroid Build Coastguard Workerimport re 18*3c875a21SAndroid Build Coastguard Workerimport subprocess 19*3c875a21SAndroid Build Coastguard Workerfrom pathlib import Path 20*3c875a21SAndroid Build Coastguard Worker 21*3c875a21SAndroid Build Coastguard Workerimport hashtags 22*3c875a21SAndroid Build Coastguard Workerimport reviewers 23*3c875a21SAndroid Build Coastguard Worker 24*3c875a21SAndroid Build Coastguard WorkerUNWANTED_TAGS = ["*alpha*", "*Alpha*", "*beta*", "*Beta*", "*rc*", "*RC*", "*test*"] 25*3c875a21SAndroid Build Coastguard Worker 26*3c875a21SAndroid Build Coastguard Worker 27*3c875a21SAndroid Build Coastguard Workerdef fetch(proj_path: Path, remote_name: str, branch: str | None = None) -> None: 28*3c875a21SAndroid Build Coastguard Worker """Runs git fetch. 29*3c875a21SAndroid Build Coastguard Worker 30*3c875a21SAndroid Build Coastguard Worker Args: 31*3c875a21SAndroid Build Coastguard Worker proj_path: Path to Git repository. 32*3c875a21SAndroid Build Coastguard Worker remote_name: A string to specify remote names. 33*3c875a21SAndroid Build Coastguard Worker """ 34*3c875a21SAndroid Build Coastguard Worker cmd = ['git', 'fetch', '--tags', remote_name] + ([branch] if branch is not None else []) 35*3c875a21SAndroid Build Coastguard Worker subprocess.run(cmd, capture_output=True, cwd=proj_path, check=True) 36*3c875a21SAndroid Build Coastguard Worker 37*3c875a21SAndroid Build Coastguard Worker 38*3c875a21SAndroid Build Coastguard Workerdef add_remote(proj_path: Path, name: str, url: str) -> None: 39*3c875a21SAndroid Build Coastguard Worker """Adds a git remote. 40*3c875a21SAndroid Build Coastguard Worker 41*3c875a21SAndroid Build Coastguard Worker Args: 42*3c875a21SAndroid Build Coastguard Worker proj_path: Path to Git repository. 43*3c875a21SAndroid Build Coastguard Worker name: Name of the new remote. 44*3c875a21SAndroid Build Coastguard Worker url: Url of the new remote. 45*3c875a21SAndroid Build Coastguard Worker """ 46*3c875a21SAndroid Build Coastguard Worker cmd = ['git', 'remote', 'add', name, url] 47*3c875a21SAndroid Build Coastguard Worker subprocess.run(cmd, cwd=proj_path, check=True) 48*3c875a21SAndroid Build Coastguard Worker 49*3c875a21SAndroid Build Coastguard Worker 50*3c875a21SAndroid Build Coastguard Workerdef remove_remote(proj_path: Path, name: str) -> None: 51*3c875a21SAndroid Build Coastguard Worker """Removes a git remote.""" 52*3c875a21SAndroid Build Coastguard Worker cmd = ['git', 'remote', 'remove', name] 53*3c875a21SAndroid Build Coastguard Worker subprocess.run(cmd, cwd=proj_path, check=True) 54*3c875a21SAndroid Build Coastguard Worker 55*3c875a21SAndroid Build Coastguard Worker 56*3c875a21SAndroid Build Coastguard Workerdef list_remotes(proj_path: Path) -> dict[str, str]: 57*3c875a21SAndroid Build Coastguard Worker """Lists all Git remotes. 58*3c875a21SAndroid Build Coastguard Worker 59*3c875a21SAndroid Build Coastguard Worker Args: 60*3c875a21SAndroid Build Coastguard Worker proj_path: Path to Git repository. 61*3c875a21SAndroid Build Coastguard Worker 62*3c875a21SAndroid Build Coastguard Worker Returns: 63*3c875a21SAndroid Build Coastguard Worker A dict from remote name to remote url. 64*3c875a21SAndroid Build Coastguard Worker """ 65*3c875a21SAndroid Build Coastguard Worker def parse_remote(line: str) -> tuple[str, str]: 66*3c875a21SAndroid Build Coastguard Worker split = line.split() 67*3c875a21SAndroid Build Coastguard Worker return split[0], split[1] 68*3c875a21SAndroid Build Coastguard Worker 69*3c875a21SAndroid Build Coastguard Worker cmd = ['git', 'remote', '-v'] 70*3c875a21SAndroid Build Coastguard Worker out = subprocess.run(cmd, capture_output=True, cwd=proj_path, check=True, 71*3c875a21SAndroid Build Coastguard Worker text=True).stdout 72*3c875a21SAndroid Build Coastguard Worker lines = out.splitlines() 73*3c875a21SAndroid Build Coastguard Worker return dict([parse_remote(line) for line in lines]) 74*3c875a21SAndroid Build Coastguard Worker 75*3c875a21SAndroid Build Coastguard Worker 76*3c875a21SAndroid Build Coastguard Workerdef detect_default_branch(proj_path: Path, remote_name: str) -> str: 77*3c875a21SAndroid Build Coastguard Worker """Gets the name of the upstream's default branch to use.""" 78*3c875a21SAndroid Build Coastguard Worker cmd = ['git', 'remote', 'show', remote_name] 79*3c875a21SAndroid Build Coastguard Worker out = subprocess.run(cmd, capture_output=True, cwd=proj_path, check=True, 80*3c875a21SAndroid Build Coastguard Worker text=True).stdout 81*3c875a21SAndroid Build Coastguard Worker lines = out.splitlines() 82*3c875a21SAndroid Build Coastguard Worker for line in lines: 83*3c875a21SAndroid Build Coastguard Worker if "HEAD branch" in line: 84*3c875a21SAndroid Build Coastguard Worker return line.split()[-1] 85*3c875a21SAndroid Build Coastguard Worker raise RuntimeError( 86*3c875a21SAndroid Build Coastguard Worker f"Could not find HEAD branch in 'git remote show {remote_name}'" 87*3c875a21SAndroid Build Coastguard Worker ) 88*3c875a21SAndroid Build Coastguard Worker 89*3c875a21SAndroid Build Coastguard Worker 90*3c875a21SAndroid Build Coastguard Workerdef get_sha_for_branch(proj_path: Path, branch: str): 91*3c875a21SAndroid Build Coastguard Worker """Gets the hash SHA for a branch.""" 92*3c875a21SAndroid Build Coastguard Worker cmd = ['git', 'rev-parse', branch] 93*3c875a21SAndroid Build Coastguard Worker return subprocess.run(cmd, capture_output=True, cwd=proj_path, check=True, 94*3c875a21SAndroid Build Coastguard Worker text=True).stdout.strip() 95*3c875a21SAndroid Build Coastguard Worker 96*3c875a21SAndroid Build Coastguard Worker 97*3c875a21SAndroid Build Coastguard Workerdef get_most_recent_tag(proj_path: Path, branch: str) -> str | None: 98*3c875a21SAndroid Build Coastguard Worker """Finds the most recent tag that is reachable from HEAD.""" 99*3c875a21SAndroid Build Coastguard Worker cmd = ['git', 'describe', '--tags', branch, '--abbrev=0'] + \ 100*3c875a21SAndroid Build Coastguard Worker [f'--exclude={unwanted_tag}' for unwanted_tag in UNWANTED_TAGS] 101*3c875a21SAndroid Build Coastguard Worker try: 102*3c875a21SAndroid Build Coastguard Worker out = subprocess.run(cmd, capture_output=True, cwd=proj_path, check=True, 103*3c875a21SAndroid Build Coastguard Worker text=True).stdout.strip() 104*3c875a21SAndroid Build Coastguard Worker return out 105*3c875a21SAndroid Build Coastguard Worker except subprocess.CalledProcessError as ex: 106*3c875a21SAndroid Build Coastguard Worker if "fatal: No names found" in ex.stderr: 107*3c875a21SAndroid Build Coastguard Worker return None 108*3c875a21SAndroid Build Coastguard Worker if "fatal: No tags can describe" in ex.stderr: 109*3c875a21SAndroid Build Coastguard Worker return None 110*3c875a21SAndroid Build Coastguard Worker raise 111*3c875a21SAndroid Build Coastguard Worker 112*3c875a21SAndroid Build Coastguard Worker 113*3c875a21SAndroid Build Coastguard Worker# pylint: disable=redefined-outer-name 114*3c875a21SAndroid Build Coastguard Workerdef get_commit_time(proj_path: Path, commit: str) -> datetime.datetime: 115*3c875a21SAndroid Build Coastguard Worker """Gets commit time of one commit.""" 116*3c875a21SAndroid Build Coastguard Worker cmd = ['git', 'show', '-s', '--format=%ct', commit] 117*3c875a21SAndroid Build Coastguard Worker out = subprocess.run(cmd, capture_output=True, cwd=proj_path, check=True, 118*3c875a21SAndroid Build Coastguard Worker text=True).stdout 119*3c875a21SAndroid Build Coastguard Worker return datetime.datetime.fromtimestamp(int(out.strip())) 120*3c875a21SAndroid Build Coastguard Worker 121*3c875a21SAndroid Build Coastguard Worker 122*3c875a21SAndroid Build Coastguard Workerdef list_remote_branches(proj_path: Path, remote_name: str) -> list[str]: 123*3c875a21SAndroid Build Coastguard Worker """Lists all branches for a remote.""" 124*3c875a21SAndroid Build Coastguard Worker cmd = ['git', 'branch', '-r'] 125*3c875a21SAndroid Build Coastguard Worker lines = subprocess.run(cmd, capture_output=True, cwd=proj_path, check=True, 126*3c875a21SAndroid Build Coastguard Worker text=True).stdout.splitlines() 127*3c875a21SAndroid Build Coastguard Worker stripped = [line.strip() for line in lines] 128*3c875a21SAndroid Build Coastguard Worker remote_path = remote_name + '/' 129*3c875a21SAndroid Build Coastguard Worker return [ 130*3c875a21SAndroid Build Coastguard Worker line[len(remote_path):] for line in stripped 131*3c875a21SAndroid Build Coastguard Worker if line.startswith(remote_path) 132*3c875a21SAndroid Build Coastguard Worker ] 133*3c875a21SAndroid Build Coastguard Worker 134*3c875a21SAndroid Build Coastguard Worker 135*3c875a21SAndroid Build Coastguard Workerdef list_local_branches(proj_path: Path) -> list[str]: 136*3c875a21SAndroid Build Coastguard Worker """Lists all local branches.""" 137*3c875a21SAndroid Build Coastguard Worker cmd = ['git', 'branch', '--format=%(refname:short)'] 138*3c875a21SAndroid Build Coastguard Worker lines = subprocess.run(cmd, capture_output=True, cwd=proj_path, check=True, 139*3c875a21SAndroid Build Coastguard Worker text=True).stdout.splitlines() 140*3c875a21SAndroid Build Coastguard Worker return lines 141*3c875a21SAndroid Build Coastguard Worker 142*3c875a21SAndroid Build Coastguard Worker 143*3c875a21SAndroid Build Coastguard WorkerCOMMIT_PATTERN = r'^[a-f0-9]{40}$' 144*3c875a21SAndroid Build Coastguard WorkerCOMMIT_RE = re.compile(COMMIT_PATTERN) 145*3c875a21SAndroid Build Coastguard Worker 146*3c875a21SAndroid Build Coastguard Worker 147*3c875a21SAndroid Build Coastguard Worker# pylint: disable=redefined-outer-name 148*3c875a21SAndroid Build Coastguard Workerdef is_commit(commit: str) -> bool: 149*3c875a21SAndroid Build Coastguard Worker """Whether a string looks like a SHA1 hash.""" 150*3c875a21SAndroid Build Coastguard Worker return bool(COMMIT_RE.match(commit)) 151*3c875a21SAndroid Build Coastguard Worker 152*3c875a21SAndroid Build Coastguard Worker 153*3c875a21SAndroid Build Coastguard Workerdef merge(proj_path: Path, branch: str) -> None: 154*3c875a21SAndroid Build Coastguard Worker """Merges a branch.""" 155*3c875a21SAndroid Build Coastguard Worker try: 156*3c875a21SAndroid Build Coastguard Worker cmd = ['git', 'merge', branch, '--no-commit'] 157*3c875a21SAndroid Build Coastguard Worker subprocess.run(cmd, cwd=proj_path, check=True) 158*3c875a21SAndroid Build Coastguard Worker except subprocess.CalledProcessError as err: 159*3c875a21SAndroid Build Coastguard Worker if hasattr(err, "output"): 160*3c875a21SAndroid Build Coastguard Worker print(err.output) 161*3c875a21SAndroid Build Coastguard Worker if not merge_conflict(proj_path): 162*3c875a21SAndroid Build Coastguard Worker raise 163*3c875a21SAndroid Build Coastguard Worker 164*3c875a21SAndroid Build Coastguard Worker 165*3c875a21SAndroid Build Coastguard Workerdef merge_conflict(proj_path: Path) -> bool: 166*3c875a21SAndroid Build Coastguard Worker """Checks if there was a merge conflict.""" 167*3c875a21SAndroid Build Coastguard Worker cmd = ['git', 'ls-files', '--unmerged'] 168*3c875a21SAndroid Build Coastguard Worker out = subprocess.run(cmd, capture_output=True, cwd=proj_path, check=True, 169*3c875a21SAndroid Build Coastguard Worker text=True).stdout 170*3c875a21SAndroid Build Coastguard Worker return bool(out) 171*3c875a21SAndroid Build Coastguard Worker 172*3c875a21SAndroid Build Coastguard Worker 173*3c875a21SAndroid Build Coastguard Workerdef add_file(proj_path: Path, file_name: str) -> None: 174*3c875a21SAndroid Build Coastguard Worker """Stages a file.""" 175*3c875a21SAndroid Build Coastguard Worker cmd = ['git', 'add', file_name] 176*3c875a21SAndroid Build Coastguard Worker subprocess.run(cmd, cwd=proj_path, check=True) 177*3c875a21SAndroid Build Coastguard Worker 178*3c875a21SAndroid Build Coastguard Worker 179*3c875a21SAndroid Build Coastguard Workerdef remove_gitmodules(proj_path: Path) -> None: 180*3c875a21SAndroid Build Coastguard Worker """Deletes .gitmodules files.""" 181*3c875a21SAndroid Build Coastguard Worker cmd = ['find', '.', '-name', '.gitmodules', '-delete'] 182*3c875a21SAndroid Build Coastguard Worker subprocess.run(cmd, cwd=proj_path, check=True) 183*3c875a21SAndroid Build Coastguard Worker 184*3c875a21SAndroid Build Coastguard Worker 185*3c875a21SAndroid Build Coastguard Workerdef delete_branch(proj_path: Path, branch_name: str) -> None: 186*3c875a21SAndroid Build Coastguard Worker """Force delete a branch.""" 187*3c875a21SAndroid Build Coastguard Worker cmd = ['git', 'branch', '-D', branch_name] 188*3c875a21SAndroid Build Coastguard Worker subprocess.run(cmd, cwd=proj_path, check=True) 189*3c875a21SAndroid Build Coastguard Worker 190*3c875a21SAndroid Build Coastguard Worker 191*3c875a21SAndroid Build Coastguard Workerdef start_branch(proj_path: Path, branch_name: str) -> None: 192*3c875a21SAndroid Build Coastguard Worker """Starts a new repo branch.""" 193*3c875a21SAndroid Build Coastguard Worker subprocess.run(['repo', 'start', branch_name], cwd=proj_path, check=True) 194*3c875a21SAndroid Build Coastguard Worker 195*3c875a21SAndroid Build Coastguard Worker 196*3c875a21SAndroid Build Coastguard Workerdef commit(proj_path: Path, message: str, no_verify: bool) -> None: 197*3c875a21SAndroid Build Coastguard Worker """Commits changes.""" 198*3c875a21SAndroid Build Coastguard Worker cmd = ['git', 'commit', '-m', message] + (['--no-verify'] if no_verify is True else []) 199*3c875a21SAndroid Build Coastguard Worker subprocess.run(cmd, cwd=proj_path, check=True) 200*3c875a21SAndroid Build Coastguard Worker 201*3c875a21SAndroid Build Coastguard Worker 202*3c875a21SAndroid Build Coastguard Workerdef commit_amend(proj_path: Path) -> None: 203*3c875a21SAndroid Build Coastguard Worker """Commits changes.""" 204*3c875a21SAndroid Build Coastguard Worker cmd = ['git', 'commit', '--amend', '--no-edit'] 205*3c875a21SAndroid Build Coastguard Worker subprocess.run(cmd, cwd=proj_path, check=True) 206*3c875a21SAndroid Build Coastguard Worker 207*3c875a21SAndroid Build Coastguard Worker 208*3c875a21SAndroid Build Coastguard Workerdef checkout(proj_path: Path, branch_name: str) -> None: 209*3c875a21SAndroid Build Coastguard Worker """Checkouts a branch.""" 210*3c875a21SAndroid Build Coastguard Worker cmd = ['git', 'checkout', branch_name] 211*3c875a21SAndroid Build Coastguard Worker subprocess.run(cmd, cwd=proj_path, check=True) 212*3c875a21SAndroid Build Coastguard Worker 213*3c875a21SAndroid Build Coastguard Worker 214*3c875a21SAndroid Build Coastguard Workerdef detach_to_android_head(proj_path: Path) -> None: 215*3c875a21SAndroid Build Coastguard Worker """Detaches the project HEAD back to the manifest revision.""" 216*3c875a21SAndroid Build Coastguard Worker # -d detaches the project back to the manifest revision without updating. 217*3c875a21SAndroid Build Coastguard Worker # -l avoids fetching new revisions from the remote. This might be superfluous with 218*3c875a21SAndroid Build Coastguard Worker # -d, but I'm not sure, and it certainly doesn't harm anything. 219*3c875a21SAndroid Build Coastguard Worker subprocess.run(['repo', 'sync', '-l', '-d', proj_path], cwd=proj_path, check=True) 220*3c875a21SAndroid Build Coastguard Worker 221*3c875a21SAndroid Build Coastguard Worker 222*3c875a21SAndroid Build Coastguard Workerdef push(proj_path: Path, remote_name: str, has_errors: bool) -> None: 223*3c875a21SAndroid Build Coastguard Worker """Pushes change to remote.""" 224*3c875a21SAndroid Build Coastguard Worker cmd = ['git', 'push', remote_name, 'HEAD:refs/for/main', '-o', 'banned-words~skip'] 225*3c875a21SAndroid Build Coastguard Worker if revs := reviewers.find_reviewers(str(proj_path)): 226*3c875a21SAndroid Build Coastguard Worker cmd.extend(['-o', revs]) 227*3c875a21SAndroid Build Coastguard Worker if tag := hashtags.find_hashtag(proj_path): 228*3c875a21SAndroid Build Coastguard Worker cmd.extend(['-o', 't=' + tag]) 229*3c875a21SAndroid Build Coastguard Worker if has_errors: 230*3c875a21SAndroid Build Coastguard Worker cmd.extend(['-o', 'l=Verified-1']) 231*3c875a21SAndroid Build Coastguard Worker subprocess.run(cmd, cwd=proj_path, check=True) 232*3c875a21SAndroid Build Coastguard Worker 233*3c875a21SAndroid Build Coastguard Worker 234*3c875a21SAndroid Build Coastguard Workerdef reset_hard(proj_path: Path) -> None: 235*3c875a21SAndroid Build Coastguard Worker """Resets current HEAD and discards changes to tracked files.""" 236*3c875a21SAndroid Build Coastguard Worker cmd = ['git', 'reset', '--hard'] 237*3c875a21SAndroid Build Coastguard Worker subprocess.run(cmd, cwd=proj_path, check=True) 238*3c875a21SAndroid Build Coastguard Worker 239*3c875a21SAndroid Build Coastguard Worker 240*3c875a21SAndroid Build Coastguard Workerdef clean(proj_path: Path) -> None: 241*3c875a21SAndroid Build Coastguard Worker """Removes untracked files and directories.""" 242*3c875a21SAndroid Build Coastguard Worker cmd = ['git', 'clean', '-fdx'] 243*3c875a21SAndroid Build Coastguard Worker subprocess.run(cmd, cwd=proj_path, check=True) 244*3c875a21SAndroid Build Coastguard Worker 245*3c875a21SAndroid Build Coastguard Worker 246*3c875a21SAndroid Build Coastguard Workerdef is_valid_url(proj_path: Path, url: str) -> bool: 247*3c875a21SAndroid Build Coastguard Worker cmd = ['git', "ls-remote", url] 248*3c875a21SAndroid Build Coastguard Worker return subprocess.run(cmd, cwd=proj_path, check=False, stdin=subprocess.DEVNULL, 249*3c875a21SAndroid Build Coastguard Worker stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL, 250*3c875a21SAndroid Build Coastguard Worker start_new_session=True).returncode == 0 251*3c875a21SAndroid Build Coastguard Worker 252*3c875a21SAndroid Build Coastguard Worker 253*3c875a21SAndroid Build Coastguard Workerdef list_remote_tags(proj_path: Path, remote_name: str) -> list[str]: 254*3c875a21SAndroid Build Coastguard Worker """Lists tags in a remote repository.""" 255*3c875a21SAndroid Build Coastguard Worker cmd = ['git', "ls-remote", "--tags", remote_name] 256*3c875a21SAndroid Build Coastguard Worker out = subprocess.run(cmd, capture_output=True, cwd=proj_path, check=True, 257*3c875a21SAndroid Build Coastguard Worker text=True).stdout 258*3c875a21SAndroid Build Coastguard Worker lines = out.splitlines() 259*3c875a21SAndroid Build Coastguard Worker return lines 260*3c875a21SAndroid Build Coastguard Worker 261*3c875a21SAndroid Build Coastguard Worker 262*3c875a21SAndroid Build Coastguard Workerdef diff(proj_path: Path, diff_filter: str, revision: str) -> str: 263*3c875a21SAndroid Build Coastguard Worker try: 264*3c875a21SAndroid Build Coastguard Worker cmd = ['git', 'diff', revision, '--stat', f'--diff-filter={diff_filter}'] 265*3c875a21SAndroid Build Coastguard Worker out = subprocess.run(cmd, capture_output=True, cwd=proj_path, 266*3c875a21SAndroid Build Coastguard Worker check=True, text=True).stdout 267*3c875a21SAndroid Build Coastguard Worker return out 268*3c875a21SAndroid Build Coastguard Worker except subprocess.CalledProcessError as err: 269*3c875a21SAndroid Build Coastguard Worker return f"Could not calculate the diff: {err}" 270*3c875a21SAndroid Build Coastguard Worker 271*3c875a21SAndroid Build Coastguard Worker 272*3c875a21SAndroid Build Coastguard Workerdef is_ancestor(proj_path: Path, ancestor: str, child: str) -> bool: 273*3c875a21SAndroid Build Coastguard Worker cmd = ['git', 'merge-base', '--is-ancestor', ancestor, child] 274*3c875a21SAndroid Build Coastguard Worker # https://git-scm.com/docs/git-merge-base#Documentation/git-merge-base.txt---is-ancestor 275*3c875a21SAndroid Build Coastguard Worker # Exit status of 0 means yes, 1 means no, and all others mean an error occurred. 276*3c875a21SAndroid Build Coastguard Worker # Although a commit is an ancestor of itself, we don't want to return True 277*3c875a21SAndroid Build Coastguard Worker # if ancestor points to the same commit as child. 278*3c875a21SAndroid Build Coastguard Worker if get_sha_for_branch(proj_path, ancestor) == child: 279*3c875a21SAndroid Build Coastguard Worker return False 280*3c875a21SAndroid Build Coastguard Worker try: 281*3c875a21SAndroid Build Coastguard Worker subprocess.run( 282*3c875a21SAndroid Build Coastguard Worker cmd, 283*3c875a21SAndroid Build Coastguard Worker cwd=proj_path, 284*3c875a21SAndroid Build Coastguard Worker text=True, 285*3c875a21SAndroid Build Coastguard Worker stderr=subprocess.STDOUT, 286*3c875a21SAndroid Build Coastguard Worker check=True, 287*3c875a21SAndroid Build Coastguard Worker stdout=subprocess.PIPE 288*3c875a21SAndroid Build Coastguard Worker ) 289*3c875a21SAndroid Build Coastguard Worker return True 290*3c875a21SAndroid Build Coastguard Worker except subprocess.CalledProcessError as ex: 291*3c875a21SAndroid Build Coastguard Worker if ex.returncode == 1: 292*3c875a21SAndroid Build Coastguard Worker return False 293*3c875a21SAndroid Build Coastguard Worker raise 294*3c875a21SAndroid Build Coastguard Worker 295*3c875a21SAndroid Build Coastguard Worker 296*3c875a21SAndroid Build Coastguard Workerdef list_branches_with_commit(proj_path: Path, commit: str, remote_name: str) -> list[str]: 297*3c875a21SAndroid Build Coastguard Worker """Lists upstream branches which contain the specified commit""" 298*3c875a21SAndroid Build Coastguard Worker cmd = ['git', 'branch', '-r', '--contains', commit] 299*3c875a21SAndroid Build Coastguard Worker out = subprocess.run(cmd, capture_output=True, cwd=proj_path, check=True, 300*3c875a21SAndroid Build Coastguard Worker text=True).stdout 301*3c875a21SAndroid Build Coastguard Worker lines = out.splitlines() 302*3c875a21SAndroid Build Coastguard Worker remote_branches = [line for line in lines if remote_name in line] 303*3c875a21SAndroid Build Coastguard Worker return remote_branches 304