mirror of
https://chromium.googlesource.com/crosvm/crosvm
synced 2025-02-05 18:20:34 +00:00
5a4ed32368
When running tools/cl on a branch without an upstream set, ask if the user would like the script to fix it automatically. BUG=None TEST=tools/cl upload # on a new branch with no upstream Change-Id: Id5f4ee30058a209ac352013159d09278cd92a23b Reviewed-on: https://chromium-review.googlesource.com/c/crosvm/crosvm/+/3780873 Commit-Queue: Daniel Verkamp <dverkamp@chromium.org> Reviewed-by: Dennis Kempin <denniskempin@google.com> Commit-Queue: Dennis Kempin <denniskempin@google.com> Auto-Submit: Daniel Verkamp <dverkamp@chromium.org> Tested-by: Daniel Verkamp <dverkamp@chromium.org>
225 lines
7 KiB
Python
Executable file
225 lines
7 KiB
Python
Executable file
#!/usr/bin/env python3
|
|
# Copyright 2022 The Chromium OS Authors. All rights reserved.
|
|
# Use of this source code is governed by a BSD-style license that can be
|
|
# found in the LICENSE file.
|
|
|
|
import functools
|
|
from pathlib import Path
|
|
from impl.common import GerritChange, confirm, run_commands, cmd
|
|
import sys
|
|
|
|
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"
|
|
|
|
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 gerrit(self):
|
|
results = GerritChange.query("project:crosvm/crosvm", self.sha)
|
|
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() != CROSVM_URL:
|
|
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):
|
|
print("Branch", branch, "tracking", get_upstream(branch))
|
|
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"
|
|
|
|
print(f"Checking out '{upstream_branch_name}'")
|
|
rev = git("rev-parse", upstream_branch_name).stdout(check=False)
|
|
if rev:
|
|
print(f"Leaving behind previous revision of {upstream_branch_name}: {rev}")
|
|
git("checkout -B", upstream_branch_name, "origin/main").fg(quiet=True)
|
|
|
|
print(f"Cherry-picking changes from {branch_name}")
|
|
git(f"cherry-pick {branch_name}@{{u}}..{branch_name}").fg()
|
|
|
|
|
|
def upload():
|
|
"""
|
|
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 (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()
|
|
|
|
git("push", remote, f"HEAD:refs/for/{branch}").fg()
|
|
|
|
|
|
if __name__ == "__main__":
|
|
prerequisites()
|
|
run_commands(upload, rebase, status, prune, usage=USAGE)
|