|
# SPDX-License-Identifier: GPL-2.0+ |
|
# |
|
# Copyright 2020 Google LLC |
|
# |
|
"""Handles the main control logic of patman |
|
|
|
This module provides various functions called by the main program to implement |
|
the features of patman. |
|
""" |
|
|
|
import re |
|
import traceback |
|
|
|
try: |
|
from importlib import resources |
|
except ImportError: |
|
# for Python 3.6 |
|
import importlib_resources as resources |
|
|
|
from u_boot_pylib import gitutil |
|
from u_boot_pylib import terminal |
|
from u_boot_pylib import tools |
|
from u_boot_pylib import tout |
|
from patman import cseries |
|
from patman import cser_helper |
|
from patman import patchstream |
|
from patman.patchwork import Patchwork |
|
from patman import send |
|
from patman import settings |
|
|
|
|
|
def setup(): |
|
"""Do required setup before doing anything""" |
|
gitutil.setup() |
|
alias_fname = gitutil.get_alias_file() |
|
if alias_fname: |
|
settings.ReadGitAliases(alias_fname) |
|
|
|
|
|
def do_send(args): |
|
"""Create, check and send patches by email |
|
|
|
Args: |
|
args (argparse.Namespace): Arguments to patman |
|
""" |
|
setup() |
|
send.send(args) |
|
|
|
|
|
def patchwork_status(branch, count, start, end, dest_branch, force, |
|
show_comments, url, single_thread=False): |
|
"""Check the status of patches in patchwork |
|
|
|
This finds the series in patchwork using the Series-link tag, checks for new |
|
comments and review tags, displays then and creates a new branch with the |
|
review tags. |
|
|
|
Args: |
|
branch (str): Branch to create patches from (None = current) |
|
count (int): Number of patches to produce, or -1 to produce patches for |
|
the current branch back to the upstream commit |
|
start (int): Start partch to use (0=first / top of branch) |
|
end (int): End patch to use (0=last one in series, 1=one before that, |
|
etc.) |
|
dest_branch (str): Name of new branch to create with the updated tags |
|
(None to not create a branch) |
|
force (bool): With dest_branch, force overwriting an existing branch |
|
show_comments (bool): True to display snippets from the comments |
|
provided by reviewers |
|
url (str): URL of patchwork server, e.g. 'https://patchwork.ozlabs.org'. |
|
This is ignored if the series provides a Series-patchwork-url tag. |
|
|
|
Raises: |
|
ValueError: if the branch has no Series-link value |
|
""" |
|
if not branch: |
|
branch = gitutil.get_branch() |
|
if count == -1: |
|
# Work out how many patches to send if we can |
|
count = gitutil.count_commits_to_branch(branch) - start |
|
|
|
series = patchstream.get_metadata(branch, start, count - end) |
|
warnings = 0 |
|
for cmt in series.commits: |
|
if cmt.warn: |
|
print('%d warnings for %s:' % (len(cmt.warn), cmt.hash)) |
|
for warn in cmt.warn: |
|
print('\t', warn) |
|
warnings += 1 |
|
print |
|
if warnings: |
|
raise ValueError('Please fix warnings before running status') |
|
links = series.get('links') |
|
if not links: |
|
raise ValueError("Branch has no Series-links value") |
|
|
|
_, version = cser_helper.split_name_version(branch) |
|
link = series.get_link_for_version(version, links) |
|
if not link: |
|
raise ValueError('Series-links has no link for v{version}') |
|
tout.debug(f"Link '{link}") |
|
|
|
# Allow the series to override the URL |
|
if 'patchwork_url' in series: |
|
url = series.patchwork_url |
|
pwork = Patchwork(url, single_thread=single_thread) |
|
|
|
# Import this here to avoid failing on other commands if the dependencies |
|
# are not present |
|
from patman import status |
|
pwork = Patchwork(url) |
|
status.check_and_show_status(series, link, branch, dest_branch, force, |
|
show_comments, False, pwork) |
|
|
|
|
|
def do_series(args, test_db=None, pwork=None, cser=None): |
|
"""Process a series subcommand |
|
|
|
Args: |
|
args (Namespace): Arguments to process |
|
test_db (str or None): Directory containing the test database, None to |
|
use the normal one |
|
pwork (Patchwork): Patchwork object to use, None to create one if |
|
needed |
|
cser (Cseries): Cseries object to use, None to create one |
|
""" |
|
if not cser: |
|
cser = cseries.Cseries(test_db) |
|
needs_patchwork = [ |
|
'autolink', 'autolink-all', 'open', 'send', 'status', 'gather', |
|
'gather-all' |
|
] |
|
try: |
|
cser.open_database() |
|
if args.subcmd in needs_patchwork: |
|
if not pwork: |
|
pwork = Patchwork(args.patchwork_url) |
|
proj = cser.project_get() |
|
if not proj: |
|
raise ValueError( |
|
"Please set project ID with 'patman patchwork set-project'") |
|
_, proj_id, link_name = cser.project_get() |
|
pwork.project_set(proj_id, link_name) |
|
elif pwork and pwork is not True: |
|
raise ValueError( |
|
f"Internal error: command '{args.subcmd}' should not have patchwork") |
|
if args.subcmd == 'add': |
|
cser.add(args.series, args.desc, mark=args.mark, |
|
allow_unmarked=args.allow_unmarked, end=args.upstream, |
|
dry_run=args.dry_run) |
|
elif args.subcmd == 'archive': |
|
cser.archive(args.series) |
|
elif args.subcmd == 'autolink': |
|
cser.link_auto(pwork, args.series, args.version, args.update, |
|
args.autolink_wait) |
|
elif args.subcmd == 'autolink-all': |
|
cser.link_auto_all(pwork, update_commit=args.update, |
|
link_all_versions=args.link_all_versions, |
|
replace_existing=args.replace_existing, |
|
dry_run=args.dry_run, show_summary=True) |
|
elif args.subcmd == 'dec': |
|
cser.decrement(args.series, args.dry_run) |
|
elif args.subcmd == 'gather': |
|
cser.gather(pwork, args.series, args.version, args.show_comments, |
|
args.show_cover_comments, args.gather_tags, |
|
dry_run=args.dry_run) |
|
elif args.subcmd == 'gather-all': |
|
cser.gather_all( |
|
pwork, args.show_comments, args.show_cover_comments, |
|
args.gather_all_versions, args.gather_tags, args.dry_run) |
|
elif args.subcmd == 'get-link': |
|
link = cser.link_get(args.series, args.version) |
|
print(link) |
|
elif args.subcmd == 'inc': |
|
cser.increment(args.series, args.dry_run) |
|
elif args.subcmd == 'ls': |
|
cser.series_list() |
|
elif args.subcmd == 'open': |
|
cser.open(pwork, args.series, args.version) |
|
elif args.subcmd == 'mark': |
|
cser.mark(args.series, args.allow_marked, dry_run=args.dry_run) |
|
elif args.subcmd == 'patches': |
|
cser.list_patches(args.series, args.version, args.commit, |
|
args.patch) |
|
elif args.subcmd == 'progress': |
|
cser.progress(args.series, args.show_all_versions, |
|
args.list_patches) |
|
elif args.subcmd == 'rm': |
|
cser.remove(args.series, dry_run=args.dry_run) |
|
elif args.subcmd == 'rm-version': |
|
cser.version_remove(args.series, args.version, dry_run=args.dry_run) |
|
elif args.subcmd == 'rename': |
|
cser.rename(args.series, args.new_name, dry_run=args.dry_run) |
|
elif args.subcmd == 'scan': |
|
cser.scan(args.series, mark=args.mark, |
|
allow_unmarked=args.allow_unmarked, end=args.upstream, |
|
dry_run=args.dry_run) |
|
elif args.subcmd == 'send': |
|
cser.send(pwork, args.series, args.autolink, args.autolink_wait, |
|
args) |
|
elif args.subcmd == 'set-link': |
|
cser.link_set(args.series, args.version, args.link, args.update) |
|
elif args.subcmd == 'status': |
|
cser.status(pwork, args.series, args.version, args.show_comments, |
|
args.show_cover_comments) |
|
elif args.subcmd == 'summary': |
|
cser.summary(args.series) |
|
elif args.subcmd == 'unarchive': |
|
cser.unarchive(args.series) |
|
elif args.subcmd == 'unmark': |
|
cser.unmark(args.series, args.allow_unmarked, dry_run=args.dry_run) |
|
elif args.subcmd == 'version-change': |
|
cser.version_change(args.series, args.version, args.new_version, |
|
dry_run=args.dry_run) |
|
else: |
|
raise ValueError(f"Unknown series subcommand '{args.subcmd}'") |
|
finally: |
|
cser.close_database() |
|
|
|
|
|
def upstream(args, test_db=None): |
|
"""Process an 'upstream' subcommand |
|
|
|
Args: |
|
args (Namespace): Arguments to process |
|
test_db (str or None): Directory containing the test database, None to |
|
use the normal one |
|
""" |
|
cser = cseries.Cseries(test_db) |
|
try: |
|
cser.open_database() |
|
if args.subcmd == 'add': |
|
cser.upstream_add(args.remote_name, args.url) |
|
elif args.subcmd == 'default': |
|
if args.unset: |
|
cser.upstream_set_default(None) |
|
elif args.remote_name: |
|
cser.upstream_set_default(args.remote_name) |
|
else: |
|
result = cser.upstream_get_default() |
|
print(result if result else 'unset') |
|
elif args.subcmd == 'delete': |
|
cser.upstream_delete(args.remote_name) |
|
elif args.subcmd == 'list': |
|
cser.upstream_list() |
|
else: |
|
raise ValueError(f"Unknown upstream subcommand '{args.subcmd}'") |
|
finally: |
|
cser.close_database() |
|
|
|
|
|
def patchwork(args, test_db=None, pwork=None): |
|
"""Process a 'patchwork' subcommand |
|
Args: |
|
args (Namespace): Arguments to process |
|
test_db (str or None): Directory containing the test database, None to |
|
use the normal one |
|
pwork (Patchwork): Patchwork object to use |
|
""" |
|
cser = cseries.Cseries(test_db) |
|
try: |
|
cser.open_database() |
|
if args.subcmd == 'set-project': |
|
if not pwork: |
|
pwork = Patchwork(args.patchwork_url) |
|
cser.project_set(pwork, args.project_name) |
|
elif args.subcmd == 'get-project': |
|
info = cser.project_get() |
|
if not info: |
|
raise ValueError("Project has not been set; use 'patman patchwork set-project'") |
|
name, pwid, link_name = info |
|
print(f"Project '{name}' patchwork-ID {pwid} link-name {link_name}") |
|
else: |
|
raise ValueError(f"Unknown patchwork subcommand '{args.subcmd}'") |
|
finally: |
|
cser.close_database() |
|
|
|
def do_patman(args, test_db=None, pwork=None, cser=None): |
|
"""Process a patman command |
|
|
|
Args: |
|
args (Namespace): Arguments to process |
|
test_db (str or None): Directory containing the test database, None to |
|
use the normal one |
|
pwork (Patchwork): Patchwork object to use, or None to create one |
|
cser (Cseries): Cseries object to use when executing the command, |
|
or None to create one |
|
""" |
|
if args.full_help: |
|
with resources.path('patman', 'README.rst') as readme: |
|
tools.print_full_help(str(readme)) |
|
return 0 |
|
if args.cmd == 'send': |
|
# Called from git with a patch filename as argument |
|
# Printout a list of additional CC recipients for this patch |
|
if args.cc_cmd: |
|
re_line = re.compile(r'(\S*) (.*)') |
|
with open(args.cc_cmd, 'r', encoding='utf-8') as inf: |
|
for line in inf.readlines(): |
|
match = re_line.match(line) |
|
if match and match.group(1) == args.patchfiles[0]: |
|
for cca in match.group(2).split('\0'): |
|
cca = cca.strip() |
|
if cca: |
|
print(cca) |
|
else: |
|
# If we are not processing tags, no need to warning about bad ones |
|
if not args.process_tags: |
|
args.ignore_bad_tags = True |
|
do_send(args) |
|
return 0 |
|
|
|
ret_code = 0 |
|
try: |
|
# Check status of patches in patchwork |
|
if args.cmd == 'status': |
|
patchwork_status(args.branch, args.count, args.start, args.end, |
|
args.dest_branch, args.force, args.show_comments, |
|
args.patchwork_url) |
|
elif args.cmd == 'series': |
|
do_series(args, test_db, pwork, cser) |
|
elif args.cmd == 'upstream': |
|
upstream(args, test_db) |
|
elif args.cmd == 'patchwork': |
|
patchwork(args, test_db, pwork) |
|
except Exception as exc: |
|
terminal.tprint(f'patman: {type(exc).__name__}: {exc}', |
|
colour=terminal.Color.RED) |
|
if args.debug: |
|
print() |
|
traceback.print_exc() |
|
ret_code = 1 |
|
return ret_code |