mirror of
https://chromium.googlesource.com/crosvm/crosvm
synced 2024-11-28 09:33:01 +00:00
797de4a579
Some users of *.googlesource.com access the platform through the internal Single Sign-On authentication mechanism so consider the sso:// remote as valid. TEST=tools/cl upload Change-Id: Ie7ebe4c9e165241645fddcdf1884b7e9e7a7d83d Reviewed-on: https://chromium-review.googlesource.com/c/crosvm/crosvm/+/5009550 Auto-Submit: Pierre-Clément Tosi <ptosi@google.com> Commit-Queue: Daniel Verkamp <dverkamp@chromium.org> Reviewed-by: Daniel Verkamp <dverkamp@chromium.org>
270 lines
8.2 KiB
Python
Executable file
270 lines
8.2 KiB
Python
Executable file
#!/usr/bin/env python3
|
|
# Copyright 2022 The ChromiumOS Authors
|
|
# Use of this source code is governed by a BSD-style license that can be
|
|
# found in the LICENSE file.
|
|
|
|
import argparse
|
|
import functools
|
|
import re
|
|
import sys
|
|
from argh import arg # type: ignore
|
|
from os import chdir
|
|
from pathlib import Path
|
|
from typing import List, Optional, Tuple
|
|
|
|
from impl.common import CROSVM_ROOT, GerritChange, cmd, confirm, run_commands
|
|
|
|
USAGE = """\
|
|
./tools/cl [upload|rebase|status|prune]
|
|
|
|
Upload changes to the upstream crosvm gerrit.
|
|
|
|
Multiple projects have their own downstream repository of crosvm and tooling
|
|
to upload to those.
|
|
|
|
This tool allows developers to send commits to the upstream gerrit review site
|
|
of crosvm and helps rebase changes if needed.
|
|
|
|
You need to be on a local branch tracking a remote one. `repo start` does this
|
|
for AOSP and chromiumos, or you can do this yourself:
|
|
|
|
$ git checkout -b mybranch --track origin/main
|
|
|
|
Then to upload commits you have made:
|
|
|
|
[mybranch] $ ./tools/cl upload
|
|
|
|
If you are tracking a different branch (e.g. aosp/main or cros/chromeos), the upload may
|
|
fail if your commits do not apply cleanly. This tool can help rebase the changes, it will
|
|
create a new branch tracking origin/main and cherry-picks your commits.
|
|
|
|
[mybranch] $ ./tools/cl rebase
|
|
[mybranch-upstream] ... resolve conflicts
|
|
[mybranch-upstream] $ git add .
|
|
[mybranch-upstream] $ git cherry-pick --continue
|
|
[mybranch-upstream] $ ./tools/cl upload
|
|
|
|
"""
|
|
|
|
GERRIT_URL = "https://chromium-review.googlesource.com"
|
|
CROSVM_URL = "https://chromium.googlesource.com/crosvm/crosvm"
|
|
CROSVM_SSO = "sso://chromium/crosvm/crosvm"
|
|
|
|
git = cmd("git")
|
|
curl = cmd("curl --silent --fail")
|
|
chmod = cmd("chmod")
|
|
|
|
|
|
class LocalChange(object):
|
|
sha: str
|
|
title: str
|
|
branch: str
|
|
|
|
def __init__(self, sha: str, title: str):
|
|
self.sha = sha
|
|
self.title = title
|
|
|
|
@classmethod
|
|
def list_changes(cls, branch: str):
|
|
upstream = get_upstream(branch)
|
|
for line in git(f'log "--format=%H %s" --first-parent {upstream}..{branch}').lines():
|
|
sha_title = line.split(" ", 1)
|
|
yield cls(sha_title[0], sha_title[1])
|
|
|
|
@functools.cached_property
|
|
def change_id(self):
|
|
msg = git("log -1 --format=email", self.sha).stdout()
|
|
match = re.search("^Change-Id: (I[a-f0-9]+)", msg, re.MULTILINE)
|
|
if not match:
|
|
return None
|
|
return match.group(1)
|
|
|
|
@functools.cached_property
|
|
def gerrit(self):
|
|
if not self.change_id:
|
|
return None
|
|
results = GerritChange.query("project:crosvm/crosvm", self.change_id)
|
|
if len(results) > 1:
|
|
raise Exception(f"Multiple gerrit changes found for commit {self.sha}: {self.title}.")
|
|
return results[0] if results else None
|
|
|
|
@property
|
|
def status(self):
|
|
if not self.gerrit:
|
|
return "NOT_UPLOADED"
|
|
else:
|
|
return self.gerrit.status
|
|
|
|
|
|
def get_upstream(branch: str = ""):
|
|
try:
|
|
return git(f"rev-parse --abbrev-ref --symbolic-full-name {branch}@{{u}}").stdout()
|
|
except:
|
|
return None
|
|
|
|
|
|
def list_local_branches():
|
|
return git("for-each-ref --format=%(refname:short) refs/heads").lines()
|
|
|
|
|
|
def get_active_upstream():
|
|
upstream = get_upstream()
|
|
if not upstream:
|
|
default_upstream = "origin/main"
|
|
if confirm(f"You are not tracking an upstream branch. Set upstream to {default_upstream}?"):
|
|
git(f"branch --set-upstream-to {default_upstream}").fg()
|
|
upstream = get_upstream()
|
|
if not upstream:
|
|
raise Exception("You are not tracking an upstream branch.")
|
|
parts = upstream.split("/")
|
|
if len(parts) != 2:
|
|
raise Exception(f"Your upstream branch '{upstream}' is not remote.")
|
|
return (parts[0], parts[1])
|
|
|
|
|
|
def prerequisites():
|
|
if not git("remote get-url origin").success():
|
|
print("Setting up origin")
|
|
git("remote add origin", CROSVM_URL).fg()
|
|
if git("remote get-url origin").stdout() not in [CROSVM_URL, CROSVM_SSO]:
|
|
print("Your remote 'origin' does not point to the main crosvm repository.")
|
|
if confirm(f"Do you want to fix it?"):
|
|
git("remote set-url origin", CROSVM_URL).fg()
|
|
else:
|
|
sys.exit(1)
|
|
|
|
# Install gerrit commit hook
|
|
hooks_dir = Path(git("rev-parse --git-path hooks").stdout())
|
|
hook_path = hooks_dir / "commit-msg"
|
|
if not hook_path.exists():
|
|
hook_path.parent.mkdir(exist_ok=True)
|
|
curl(f"{GERRIT_URL}/tools/hooks/commit-msg").write_to(hook_path)
|
|
chmod("+x", hook_path).fg()
|
|
|
|
|
|
def print_branch_summary(branch: str):
|
|
upstream = get_upstream(branch)
|
|
if not upstream:
|
|
print("Branch", branch, "is not tracking an upstream branch")
|
|
print()
|
|
return
|
|
print("Branch", branch, "tracking", upstream)
|
|
changes = [*LocalChange.list_changes(branch)]
|
|
for change in changes:
|
|
if change.gerrit:
|
|
print(" ", change.status, change.title, f"({change.gerrit.short_url()})")
|
|
else:
|
|
print(" ", change.status, change.title)
|
|
|
|
if not changes:
|
|
print(" No changes")
|
|
print()
|
|
|
|
|
|
def status():
|
|
"""
|
|
Lists all branches and their local commits.
|
|
"""
|
|
for branch in list_local_branches():
|
|
print_branch_summary(branch)
|
|
|
|
|
|
def prune(force: bool = False):
|
|
"""
|
|
Deletes branches with changes that have been submitted or abandoned
|
|
"""
|
|
current_branch = git("branch --show-current").stdout()
|
|
branches_to_delete = [
|
|
branch
|
|
for branch in list_local_branches()
|
|
if branch != current_branch
|
|
and get_upstream(branch) is not None
|
|
and all(
|
|
change.status in ["ABANDONED", "MERGED"] for change in LocalChange.list_changes(branch)
|
|
)
|
|
]
|
|
if not branches_to_delete:
|
|
print("No obsolete branches to delete.")
|
|
return
|
|
|
|
print("Obsolete branches:")
|
|
print()
|
|
for branch in branches_to_delete:
|
|
print_branch_summary(branch)
|
|
|
|
if force or confirm("Do you want to delete the above branches?"):
|
|
git("branch", "-D", *branches_to_delete).fg()
|
|
|
|
|
|
def rebase():
|
|
"""
|
|
Rebases changes from the current branch onto origin/main.
|
|
|
|
Will create a new branch called 'current-branch'-upstream tracking origin/main. Changes from
|
|
the current branch will then be rebased into the -upstream branch.
|
|
"""
|
|
branch_name = git("branch --show-current").stdout()
|
|
upstream_branch_name = branch_name + "-upstream"
|
|
|
|
if git("rev-parse", upstream_branch_name).success():
|
|
print(f"Overwriting existing branch {upstream_branch_name}")
|
|
git("log -n1", upstream_branch_name).fg()
|
|
|
|
git("fetch -q origin main").fg()
|
|
git("checkout -B", upstream_branch_name, "origin/main").fg()
|
|
|
|
print(f"Cherry-picking changes from {branch_name}")
|
|
git(f"cherry-pick {branch_name}@{{u}}..{branch_name}").fg()
|
|
|
|
|
|
def upload(
|
|
dry_run: bool = False,
|
|
reviewer: Optional[str] = None,
|
|
auto_submit: bool = False,
|
|
submit: bool = False,
|
|
try_: bool = False,
|
|
):
|
|
"""
|
|
Uploads changes to the crosvm main branch.
|
|
"""
|
|
remote, branch = get_active_upstream()
|
|
changes = [*LocalChange.list_changes("HEAD")]
|
|
if not changes:
|
|
print("No changes to upload")
|
|
return
|
|
|
|
print("Uploading to origin/main:")
|
|
for change in changes:
|
|
print(" ", change.sha, change.title)
|
|
print()
|
|
|
|
if len(changes) > 1:
|
|
if not confirm("Uploading {} changes, continue?".format(len(changes))):
|
|
return
|
|
|
|
if (remote, branch) != ("origin", "main"):
|
|
print(f"WARNING! Your changes are based on {remote}/{branch}, not origin/main.")
|
|
print("If gerrit rejects your changes, try `./tools/cl rebase -h`.")
|
|
print()
|
|
if not confirm("Upload anyway?"):
|
|
return
|
|
print()
|
|
|
|
extra_args: List[str] = []
|
|
if auto_submit:
|
|
extra_args.append("l=Auto-Submit+1")
|
|
try_ = True
|
|
if try_:
|
|
extra_args.append("l=Commit-Queue+1")
|
|
if submit:
|
|
extra_args.append(f"l=Commit-Queue+2")
|
|
if reviewer:
|
|
extra_args.append(f"r={reviewer}")
|
|
|
|
git(f"push origin HEAD:refs/for/main%{','.join(extra_args)}").fg(dry_run=dry_run)
|
|
|
|
|
|
if __name__ == "__main__":
|
|
chdir(CROSVM_ROOT)
|
|
prerequisites()
|
|
run_commands(upload, rebase, status, prune, usage=USAGE)
|