1*bb4ee6a4SAndroid Build Coastguard Worker#!/usr/bin/env python3 2*bb4ee6a4SAndroid Build Coastguard Worker# Copyright 2022 The ChromiumOS Authors 3*bb4ee6a4SAndroid Build Coastguard Worker# Use of this source code is governed by a BSD-style license that can be 4*bb4ee6a4SAndroid Build Coastguard Worker# found in the LICENSE file. 5*bb4ee6a4SAndroid Build Coastguard Worker 6*bb4ee6a4SAndroid Build Coastguard Workerimport argparse 7*bb4ee6a4SAndroid Build Coastguard Workerimport functools 8*bb4ee6a4SAndroid Build Coastguard Workerimport re 9*bb4ee6a4SAndroid Build Coastguard Workerimport sys 10*bb4ee6a4SAndroid Build Coastguard Workerfrom argh import arg # type: ignore 11*bb4ee6a4SAndroid Build Coastguard Workerfrom os import chdir 12*bb4ee6a4SAndroid Build Coastguard Workerfrom pathlib import Path 13*bb4ee6a4SAndroid Build Coastguard Workerfrom typing import List, Optional, Tuple 14*bb4ee6a4SAndroid Build Coastguard Worker 15*bb4ee6a4SAndroid Build Coastguard Workerfrom impl.common import CROSVM_ROOT, GerritChange, cmd, confirm, run_commands 16*bb4ee6a4SAndroid Build Coastguard Worker 17*bb4ee6a4SAndroid Build Coastguard WorkerUSAGE = """\ 18*bb4ee6a4SAndroid Build Coastguard Worker./tools/cl [upload|rebase|status|prune] 19*bb4ee6a4SAndroid Build Coastguard Worker 20*bb4ee6a4SAndroid Build Coastguard WorkerUpload changes to the upstream crosvm gerrit. 21*bb4ee6a4SAndroid Build Coastguard Worker 22*bb4ee6a4SAndroid Build Coastguard WorkerMultiple projects have their own downstream repository of crosvm and tooling 23*bb4ee6a4SAndroid Build Coastguard Workerto upload to those. 24*bb4ee6a4SAndroid Build Coastguard Worker 25*bb4ee6a4SAndroid Build Coastguard WorkerThis tool allows developers to send commits to the upstream gerrit review site 26*bb4ee6a4SAndroid Build Coastguard Workerof crosvm and helps rebase changes if needed. 27*bb4ee6a4SAndroid Build Coastguard Worker 28*bb4ee6a4SAndroid Build Coastguard WorkerYou need to be on a local branch tracking a remote one. `repo start` does this 29*bb4ee6a4SAndroid Build Coastguard Workerfor AOSP and chromiumos, or you can do this yourself: 30*bb4ee6a4SAndroid Build Coastguard Worker 31*bb4ee6a4SAndroid Build Coastguard Worker $ git checkout -b mybranch --track origin/main 32*bb4ee6a4SAndroid Build Coastguard Worker 33*bb4ee6a4SAndroid Build Coastguard WorkerThen to upload commits you have made: 34*bb4ee6a4SAndroid Build Coastguard Worker 35*bb4ee6a4SAndroid Build Coastguard Worker [mybranch] $ ./tools/cl upload 36*bb4ee6a4SAndroid Build Coastguard Worker 37*bb4ee6a4SAndroid Build Coastguard WorkerIf you are tracking a different branch (e.g. aosp/main or cros/chromeos), the upload may 38*bb4ee6a4SAndroid Build Coastguard Workerfail if your commits do not apply cleanly. This tool can help rebase the changes, it will 39*bb4ee6a4SAndroid Build Coastguard Workercreate a new branch tracking origin/main and cherry-picks your commits. 40*bb4ee6a4SAndroid Build Coastguard Worker 41*bb4ee6a4SAndroid Build Coastguard Worker [mybranch] $ ./tools/cl rebase 42*bb4ee6a4SAndroid Build Coastguard Worker [mybranch-upstream] ... resolve conflicts 43*bb4ee6a4SAndroid Build Coastguard Worker [mybranch-upstream] $ git add . 44*bb4ee6a4SAndroid Build Coastguard Worker [mybranch-upstream] $ git cherry-pick --continue 45*bb4ee6a4SAndroid Build Coastguard Worker [mybranch-upstream] $ ./tools/cl upload 46*bb4ee6a4SAndroid Build Coastguard Worker 47*bb4ee6a4SAndroid Build Coastguard Worker""" 48*bb4ee6a4SAndroid Build Coastguard Worker 49*bb4ee6a4SAndroid Build Coastguard WorkerGERRIT_URL = "https://chromium-review.googlesource.com" 50*bb4ee6a4SAndroid Build Coastguard WorkerCROSVM_URL = "https://chromium.googlesource.com/crosvm/crosvm" 51*bb4ee6a4SAndroid Build Coastguard WorkerCROSVM_SSO = "sso://chromium/crosvm/crosvm" 52*bb4ee6a4SAndroid Build Coastguard Worker 53*bb4ee6a4SAndroid Build Coastguard Workergit = cmd("git") 54*bb4ee6a4SAndroid Build Coastguard Workercurl = cmd("curl --silent --fail") 55*bb4ee6a4SAndroid Build Coastguard Workerchmod = cmd("chmod") 56*bb4ee6a4SAndroid Build Coastguard Worker 57*bb4ee6a4SAndroid Build Coastguard Worker 58*bb4ee6a4SAndroid Build Coastguard Workerclass LocalChange(object): 59*bb4ee6a4SAndroid Build Coastguard Worker sha: str 60*bb4ee6a4SAndroid Build Coastguard Worker title: str 61*bb4ee6a4SAndroid Build Coastguard Worker branch: str 62*bb4ee6a4SAndroid Build Coastguard Worker 63*bb4ee6a4SAndroid Build Coastguard Worker def __init__(self, sha: str, title: str): 64*bb4ee6a4SAndroid Build Coastguard Worker self.sha = sha 65*bb4ee6a4SAndroid Build Coastguard Worker self.title = title 66*bb4ee6a4SAndroid Build Coastguard Worker 67*bb4ee6a4SAndroid Build Coastguard Worker @classmethod 68*bb4ee6a4SAndroid Build Coastguard Worker def list_changes(cls, branch: str): 69*bb4ee6a4SAndroid Build Coastguard Worker upstream = get_upstream(branch) 70*bb4ee6a4SAndroid Build Coastguard Worker for line in git(f'log "--format=%H %s" --first-parent {upstream}..{branch}').lines(): 71*bb4ee6a4SAndroid Build Coastguard Worker sha_title = line.split(" ", 1) 72*bb4ee6a4SAndroid Build Coastguard Worker yield cls(sha_title[0], sha_title[1]) 73*bb4ee6a4SAndroid Build Coastguard Worker 74*bb4ee6a4SAndroid Build Coastguard Worker @functools.cached_property 75*bb4ee6a4SAndroid Build Coastguard Worker def change_id(self): 76*bb4ee6a4SAndroid Build Coastguard Worker msg = git("log -1 --format=email", self.sha).stdout() 77*bb4ee6a4SAndroid Build Coastguard Worker match = re.search("^Change-Id: (I[a-f0-9]+)", msg, re.MULTILINE) 78*bb4ee6a4SAndroid Build Coastguard Worker if not match: 79*bb4ee6a4SAndroid Build Coastguard Worker return None 80*bb4ee6a4SAndroid Build Coastguard Worker return match.group(1) 81*bb4ee6a4SAndroid Build Coastguard Worker 82*bb4ee6a4SAndroid Build Coastguard Worker @functools.cached_property 83*bb4ee6a4SAndroid Build Coastguard Worker def gerrit(self): 84*bb4ee6a4SAndroid Build Coastguard Worker if not self.change_id: 85*bb4ee6a4SAndroid Build Coastguard Worker return None 86*bb4ee6a4SAndroid Build Coastguard Worker results = GerritChange.query("project:crosvm/crosvm", self.change_id) 87*bb4ee6a4SAndroid Build Coastguard Worker if len(results) > 1: 88*bb4ee6a4SAndroid Build Coastguard Worker raise Exception(f"Multiple gerrit changes found for commit {self.sha}: {self.title}.") 89*bb4ee6a4SAndroid Build Coastguard Worker return results[0] if results else None 90*bb4ee6a4SAndroid Build Coastguard Worker 91*bb4ee6a4SAndroid Build Coastguard Worker @property 92*bb4ee6a4SAndroid Build Coastguard Worker def status(self): 93*bb4ee6a4SAndroid Build Coastguard Worker if not self.gerrit: 94*bb4ee6a4SAndroid Build Coastguard Worker return "NOT_UPLOADED" 95*bb4ee6a4SAndroid Build Coastguard Worker else: 96*bb4ee6a4SAndroid Build Coastguard Worker return self.gerrit.status 97*bb4ee6a4SAndroid Build Coastguard Worker 98*bb4ee6a4SAndroid Build Coastguard Worker 99*bb4ee6a4SAndroid Build Coastguard Workerdef get_upstream(branch: str = ""): 100*bb4ee6a4SAndroid Build Coastguard Worker try: 101*bb4ee6a4SAndroid Build Coastguard Worker return git(f"rev-parse --abbrev-ref --symbolic-full-name {branch}@{{u}}").stdout() 102*bb4ee6a4SAndroid Build Coastguard Worker except: 103*bb4ee6a4SAndroid Build Coastguard Worker return None 104*bb4ee6a4SAndroid Build Coastguard Worker 105*bb4ee6a4SAndroid Build Coastguard Worker 106*bb4ee6a4SAndroid Build Coastguard Workerdef list_local_branches(): 107*bb4ee6a4SAndroid Build Coastguard Worker return git("for-each-ref --format=%(refname:short) refs/heads").lines() 108*bb4ee6a4SAndroid Build Coastguard Worker 109*bb4ee6a4SAndroid Build Coastguard Worker 110*bb4ee6a4SAndroid Build Coastguard Workerdef get_active_upstream(): 111*bb4ee6a4SAndroid Build Coastguard Worker upstream = get_upstream() 112*bb4ee6a4SAndroid Build Coastguard Worker if not upstream: 113*bb4ee6a4SAndroid Build Coastguard Worker default_upstream = "origin/main" 114*bb4ee6a4SAndroid Build Coastguard Worker if confirm(f"You are not tracking an upstream branch. Set upstream to {default_upstream}?"): 115*bb4ee6a4SAndroid Build Coastguard Worker git(f"branch --set-upstream-to {default_upstream}").fg() 116*bb4ee6a4SAndroid Build Coastguard Worker upstream = get_upstream() 117*bb4ee6a4SAndroid Build Coastguard Worker if not upstream: 118*bb4ee6a4SAndroid Build Coastguard Worker raise Exception("You are not tracking an upstream branch.") 119*bb4ee6a4SAndroid Build Coastguard Worker parts = upstream.split("/") 120*bb4ee6a4SAndroid Build Coastguard Worker if len(parts) != 2: 121*bb4ee6a4SAndroid Build Coastguard Worker raise Exception(f"Your upstream branch '{upstream}' is not remote.") 122*bb4ee6a4SAndroid Build Coastguard Worker return (parts[0], parts[1]) 123*bb4ee6a4SAndroid Build Coastguard Worker 124*bb4ee6a4SAndroid Build Coastguard Worker 125*bb4ee6a4SAndroid Build Coastguard Workerdef prerequisites(): 126*bb4ee6a4SAndroid Build Coastguard Worker if not git("remote get-url origin").success(): 127*bb4ee6a4SAndroid Build Coastguard Worker print("Setting up origin") 128*bb4ee6a4SAndroid Build Coastguard Worker git("remote add origin", CROSVM_URL).fg() 129*bb4ee6a4SAndroid Build Coastguard Worker if git("remote get-url origin").stdout() not in [CROSVM_URL, CROSVM_SSO]: 130*bb4ee6a4SAndroid Build Coastguard Worker print("Your remote 'origin' does not point to the main crosvm repository.") 131*bb4ee6a4SAndroid Build Coastguard Worker if confirm(f"Do you want to fix it?"): 132*bb4ee6a4SAndroid Build Coastguard Worker git("remote set-url origin", CROSVM_URL).fg() 133*bb4ee6a4SAndroid Build Coastguard Worker else: 134*bb4ee6a4SAndroid Build Coastguard Worker sys.exit(1) 135*bb4ee6a4SAndroid Build Coastguard Worker 136*bb4ee6a4SAndroid Build Coastguard Worker # Install gerrit commit hook 137*bb4ee6a4SAndroid Build Coastguard Worker hooks_dir = Path(git("rev-parse --git-path hooks").stdout()) 138*bb4ee6a4SAndroid Build Coastguard Worker hook_path = hooks_dir / "commit-msg" 139*bb4ee6a4SAndroid Build Coastguard Worker if not hook_path.exists(): 140*bb4ee6a4SAndroid Build Coastguard Worker hook_path.parent.mkdir(exist_ok=True) 141*bb4ee6a4SAndroid Build Coastguard Worker curl(f"{GERRIT_URL}/tools/hooks/commit-msg").write_to(hook_path) 142*bb4ee6a4SAndroid Build Coastguard Worker chmod("+x", hook_path).fg() 143*bb4ee6a4SAndroid Build Coastguard Worker 144*bb4ee6a4SAndroid Build Coastguard Worker 145*bb4ee6a4SAndroid Build Coastguard Workerdef print_branch_summary(branch: str): 146*bb4ee6a4SAndroid Build Coastguard Worker upstream = get_upstream(branch) 147*bb4ee6a4SAndroid Build Coastguard Worker if not upstream: 148*bb4ee6a4SAndroid Build Coastguard Worker print("Branch", branch, "is not tracking an upstream branch") 149*bb4ee6a4SAndroid Build Coastguard Worker print() 150*bb4ee6a4SAndroid Build Coastguard Worker return 151*bb4ee6a4SAndroid Build Coastguard Worker print("Branch", branch, "tracking", upstream) 152*bb4ee6a4SAndroid Build Coastguard Worker changes = [*LocalChange.list_changes(branch)] 153*bb4ee6a4SAndroid Build Coastguard Worker for change in changes: 154*bb4ee6a4SAndroid Build Coastguard Worker if change.gerrit: 155*bb4ee6a4SAndroid Build Coastguard Worker print(" ", change.status, change.title, f"({change.gerrit.short_url()})") 156*bb4ee6a4SAndroid Build Coastguard Worker else: 157*bb4ee6a4SAndroid Build Coastguard Worker print(" ", change.status, change.title) 158*bb4ee6a4SAndroid Build Coastguard Worker 159*bb4ee6a4SAndroid Build Coastguard Worker if not changes: 160*bb4ee6a4SAndroid Build Coastguard Worker print(" No changes") 161*bb4ee6a4SAndroid Build Coastguard Worker print() 162*bb4ee6a4SAndroid Build Coastguard Worker 163*bb4ee6a4SAndroid Build Coastguard Worker 164*bb4ee6a4SAndroid Build Coastguard Workerdef status(): 165*bb4ee6a4SAndroid Build Coastguard Worker """ 166*bb4ee6a4SAndroid Build Coastguard Worker Lists all branches and their local commits. 167*bb4ee6a4SAndroid Build Coastguard Worker """ 168*bb4ee6a4SAndroid Build Coastguard Worker for branch in list_local_branches(): 169*bb4ee6a4SAndroid Build Coastguard Worker print_branch_summary(branch) 170*bb4ee6a4SAndroid Build Coastguard Worker 171*bb4ee6a4SAndroid Build Coastguard Worker 172*bb4ee6a4SAndroid Build Coastguard Workerdef prune(force: bool = False): 173*bb4ee6a4SAndroid Build Coastguard Worker """ 174*bb4ee6a4SAndroid Build Coastguard Worker Deletes branches with changes that have been submitted or abandoned 175*bb4ee6a4SAndroid Build Coastguard Worker """ 176*bb4ee6a4SAndroid Build Coastguard Worker current_branch = git("branch --show-current").stdout() 177*bb4ee6a4SAndroid Build Coastguard Worker branches_to_delete = [ 178*bb4ee6a4SAndroid Build Coastguard Worker branch 179*bb4ee6a4SAndroid Build Coastguard Worker for branch in list_local_branches() 180*bb4ee6a4SAndroid Build Coastguard Worker if branch != current_branch 181*bb4ee6a4SAndroid Build Coastguard Worker and get_upstream(branch) is not None 182*bb4ee6a4SAndroid Build Coastguard Worker and all( 183*bb4ee6a4SAndroid Build Coastguard Worker change.status in ["ABANDONED", "MERGED"] for change in LocalChange.list_changes(branch) 184*bb4ee6a4SAndroid Build Coastguard Worker ) 185*bb4ee6a4SAndroid Build Coastguard Worker ] 186*bb4ee6a4SAndroid Build Coastguard Worker if not branches_to_delete: 187*bb4ee6a4SAndroid Build Coastguard Worker print("No obsolete branches to delete.") 188*bb4ee6a4SAndroid Build Coastguard Worker return 189*bb4ee6a4SAndroid Build Coastguard Worker 190*bb4ee6a4SAndroid Build Coastguard Worker print("Obsolete branches:") 191*bb4ee6a4SAndroid Build Coastguard Worker print() 192*bb4ee6a4SAndroid Build Coastguard Worker for branch in branches_to_delete: 193*bb4ee6a4SAndroid Build Coastguard Worker print_branch_summary(branch) 194*bb4ee6a4SAndroid Build Coastguard Worker 195*bb4ee6a4SAndroid Build Coastguard Worker if force or confirm("Do you want to delete the above branches?"): 196*bb4ee6a4SAndroid Build Coastguard Worker git("branch", "-D", *branches_to_delete).fg() 197*bb4ee6a4SAndroid Build Coastguard Worker 198*bb4ee6a4SAndroid Build Coastguard Worker 199*bb4ee6a4SAndroid Build Coastguard Workerdef rebase(): 200*bb4ee6a4SAndroid Build Coastguard Worker """ 201*bb4ee6a4SAndroid Build Coastguard Worker Rebases changes from the current branch onto origin/main. 202*bb4ee6a4SAndroid Build Coastguard Worker 203*bb4ee6a4SAndroid Build Coastguard Worker Will create a new branch called 'current-branch'-upstream tracking origin/main. Changes from 204*bb4ee6a4SAndroid Build Coastguard Worker the current branch will then be rebased into the -upstream branch. 205*bb4ee6a4SAndroid Build Coastguard Worker """ 206*bb4ee6a4SAndroid Build Coastguard Worker branch_name = git("branch --show-current").stdout() 207*bb4ee6a4SAndroid Build Coastguard Worker upstream_branch_name = branch_name + "-upstream" 208*bb4ee6a4SAndroid Build Coastguard Worker 209*bb4ee6a4SAndroid Build Coastguard Worker if git("rev-parse", upstream_branch_name).success(): 210*bb4ee6a4SAndroid Build Coastguard Worker print(f"Overwriting existing branch {upstream_branch_name}") 211*bb4ee6a4SAndroid Build Coastguard Worker git("log -n1", upstream_branch_name).fg() 212*bb4ee6a4SAndroid Build Coastguard Worker 213*bb4ee6a4SAndroid Build Coastguard Worker git("fetch -q origin main").fg() 214*bb4ee6a4SAndroid Build Coastguard Worker git("checkout -B", upstream_branch_name, "origin/main").fg() 215*bb4ee6a4SAndroid Build Coastguard Worker 216*bb4ee6a4SAndroid Build Coastguard Worker print(f"Cherry-picking changes from {branch_name}") 217*bb4ee6a4SAndroid Build Coastguard Worker git(f"cherry-pick {branch_name}@{{u}}..{branch_name}").fg() 218*bb4ee6a4SAndroid Build Coastguard Worker 219*bb4ee6a4SAndroid Build Coastguard Worker 220*bb4ee6a4SAndroid Build Coastguard Workerdef upload( 221*bb4ee6a4SAndroid Build Coastguard Worker dry_run: bool = False, 222*bb4ee6a4SAndroid Build Coastguard Worker reviewer: Optional[str] = None, 223*bb4ee6a4SAndroid Build Coastguard Worker auto_submit: bool = False, 224*bb4ee6a4SAndroid Build Coastguard Worker submit: bool = False, 225*bb4ee6a4SAndroid Build Coastguard Worker try_: bool = False, 226*bb4ee6a4SAndroid Build Coastguard Worker): 227*bb4ee6a4SAndroid Build Coastguard Worker """ 228*bb4ee6a4SAndroid Build Coastguard Worker Uploads changes to the crosvm main branch. 229*bb4ee6a4SAndroid Build Coastguard Worker """ 230*bb4ee6a4SAndroid Build Coastguard Worker remote, branch = get_active_upstream() 231*bb4ee6a4SAndroid Build Coastguard Worker changes = [*LocalChange.list_changes("HEAD")] 232*bb4ee6a4SAndroid Build Coastguard Worker if not changes: 233*bb4ee6a4SAndroid Build Coastguard Worker print("No changes to upload") 234*bb4ee6a4SAndroid Build Coastguard Worker return 235*bb4ee6a4SAndroid Build Coastguard Worker 236*bb4ee6a4SAndroid Build Coastguard Worker print("Uploading to origin/main:") 237*bb4ee6a4SAndroid Build Coastguard Worker for change in changes: 238*bb4ee6a4SAndroid Build Coastguard Worker print(" ", change.sha, change.title) 239*bb4ee6a4SAndroid Build Coastguard Worker print() 240*bb4ee6a4SAndroid Build Coastguard Worker 241*bb4ee6a4SAndroid Build Coastguard Worker if len(changes) > 1: 242*bb4ee6a4SAndroid Build Coastguard Worker if not confirm("Uploading {} changes, continue?".format(len(changes))): 243*bb4ee6a4SAndroid Build Coastguard Worker return 244*bb4ee6a4SAndroid Build Coastguard Worker 245*bb4ee6a4SAndroid Build Coastguard Worker if (remote, branch) != ("origin", "main"): 246*bb4ee6a4SAndroid Build Coastguard Worker print(f"WARNING! Your changes are based on {remote}/{branch}, not origin/main.") 247*bb4ee6a4SAndroid Build Coastguard Worker print("If gerrit rejects your changes, try `./tools/cl rebase -h`.") 248*bb4ee6a4SAndroid Build Coastguard Worker print() 249*bb4ee6a4SAndroid Build Coastguard Worker if not confirm("Upload anyway?"): 250*bb4ee6a4SAndroid Build Coastguard Worker return 251*bb4ee6a4SAndroid Build Coastguard Worker print() 252*bb4ee6a4SAndroid Build Coastguard Worker 253*bb4ee6a4SAndroid Build Coastguard Worker extra_args: List[str] = [] 254*bb4ee6a4SAndroid Build Coastguard Worker if auto_submit: 255*bb4ee6a4SAndroid Build Coastguard Worker extra_args.append("l=Auto-Submit+1") 256*bb4ee6a4SAndroid Build Coastguard Worker try_ = True 257*bb4ee6a4SAndroid Build Coastguard Worker if try_: 258*bb4ee6a4SAndroid Build Coastguard Worker extra_args.append("l=Commit-Queue+1") 259*bb4ee6a4SAndroid Build Coastguard Worker if submit: 260*bb4ee6a4SAndroid Build Coastguard Worker extra_args.append(f"l=Commit-Queue+2") 261*bb4ee6a4SAndroid Build Coastguard Worker if reviewer: 262*bb4ee6a4SAndroid Build Coastguard Worker extra_args.append(f"r={reviewer}") 263*bb4ee6a4SAndroid Build Coastguard Worker 264*bb4ee6a4SAndroid Build Coastguard Worker git(f"push origin HEAD:refs/for/main%{','.join(extra_args)}").fg(dry_run=dry_run) 265*bb4ee6a4SAndroid Build Coastguard Worker 266*bb4ee6a4SAndroid Build Coastguard Worker 267*bb4ee6a4SAndroid Build Coastguard Workerif __name__ == "__main__": 268*bb4ee6a4SAndroid Build Coastguard Worker chdir(CROSVM_ROOT) 269*bb4ee6a4SAndroid Build Coastguard Worker prerequisites() 270*bb4ee6a4SAndroid Build Coastguard Worker run_commands(upload, rebase, status, prune, usage=USAGE) 271