patman enhancements and fixes
-----BEGIN PGP SIGNATURE----- iQFFBAABCgAvFiEEslwAIq+Gp8wWVbYnfxc6PpAIreYFAmO3l6kRHHNqZ0BjaHJv bWl1bS5vcmcACgkQfxc6PpAIreaUpQf+O3dJCPhOdr8Uxu4GjX5kcG2dGw7m5D4g WgQXxWbXQVU0Uvm2wXeJDcLgA7A0VrnegBdKxeugh1Rp/YOP+Wa55NgeJ9cY+jA2 uovFB2FCope3T4JR1HyG1zStx9jZJgb/T0GOOUbHF0dmXytOUi7kwZ99CHo7nBv3 OFWaVjOt4MdbEMyaNvkI9wEpHiWXO49TfPQU0FKvNkRRVbWQWYHUBmbWpEL/8VSw Y60YNmg6ZQIFrFneS6orl4oUNe1sTjK/UEZAL0SH5a7S5YkTsLBjlxO/1WdBOfLV AiCyaAFdkviBPs2UPggrTn34tXCwAqAaKSQoNM+pBwX/jJsxLusZ1g== =6oDn -----END PGP SIGNATURE----- Merge tag 'dm-next-5jan23' of https://source.denx.de/u-boot/custodians/u-boot-dm into next patman enhancements and fixes
This commit is contained in:
commit
b63905cfc6
@ -127,7 +127,7 @@ static int dm_test_blk_devnum(struct unit_test_state *uts)
|
||||
|
||||
/*
|
||||
* Probe the devices, with the first one being probed last. This is the
|
||||
* one with no alias / sequence numnber.
|
||||
* one with no alias / sequence number.
|
||||
*/
|
||||
ut_assertok(uclass_get_device(UCLASS_MMC, 1, &dev));
|
||||
ut_assertok(uclass_get_device(UCLASS_MMC, 2, &dev));
|
||||
|
@ -46,7 +46,7 @@ static int dm_test_button_gpio(struct unit_test_state *uts)
|
||||
struct udevice *dev, *gpio;
|
||||
|
||||
/*
|
||||
* Check that we can manipulate an BUTTON. BUTTON 1 is connected to GPIO
|
||||
* Check that we can manipulate a BUTTON. BUTTON 1 is connected to GPIO
|
||||
* bank gpio_a, offset 3.
|
||||
*/
|
||||
ut_assertok(uclass_get_device(UCLASS_BUTTON, 1, &dev));
|
||||
@ -64,7 +64,7 @@ static int dm_test_button_gpio(struct unit_test_state *uts)
|
||||
}
|
||||
DM_TEST(dm_test_button_gpio, UT_TESTF_SCAN_PDATA | UT_TESTF_SCAN_FDT);
|
||||
|
||||
/* Test obtaining an BUTTON by label */
|
||||
/* Test obtaining a BUTTON by label */
|
||||
static int dm_test_button_label(struct unit_test_state *uts)
|
||||
{
|
||||
struct udevice *dev, *cmp;
|
||||
|
@ -348,7 +348,7 @@ static int dm_test_gpio_phandles(struct unit_test_state *uts)
|
||||
ut_asserteq(-ENOENT, gpio_request_by_name(dev, "test-gpios", 5, &desc,
|
||||
0));
|
||||
|
||||
/* Last GPIO is ignord as it comes after <0> */
|
||||
/* Last GPIO is ignored as it comes after <0> */
|
||||
ut_asserteq(3, gpio_request_list_by_name(dev, "test-gpios", desc_list,
|
||||
ARRAY_SIZE(desc_list), 0));
|
||||
ut_asserteq(-EBUSY, gpio_request_list_by_name(dev, "test-gpios",
|
||||
@ -377,7 +377,7 @@ static int dm_test_gpio_phandles(struct unit_test_state *uts)
|
||||
ut_asserteq(6, gpio_request_list_by_name(dev, "test2-gpios", desc_list,
|
||||
ARRAY_SIZE(desc_list), 0));
|
||||
|
||||
/* This was set to output previously but flags resetted to 0 = INPUT */
|
||||
/* This was set to output previously but flags reset to 0 = INPUT */
|
||||
ut_asserteq(0, sandbox_gpio_get_flags(gpio_a, 1));
|
||||
ut_asserteq(GPIOF_INPUT, gpio_get_function(gpio_a, 1, NULL));
|
||||
|
||||
|
@ -132,7 +132,7 @@ static int dm_test_cmd_host(struct unit_test_state *uts)
|
||||
|
||||
ut_assertok(run_commandf("host bind fat %s", filename2));
|
||||
|
||||
/* Check it is not removeable (no '-r') */
|
||||
/* Check it is not removable (no '-r') */
|
||||
ut_assertok(uclass_next_device_err(&dev));
|
||||
ut_assertok(blk_get_from_parent(dev, &blk));
|
||||
desc = dev_get_uclass_plat(blk);
|
||||
|
@ -139,7 +139,7 @@ static int dm_test_remoteproc_elf(struct unit_test_state *uts)
|
||||
0x20, 0x00, 0x00, 0x00,
|
||||
/* memsz = filesz */
|
||||
0x20, 0x00, 0x00, 0x00,
|
||||
/* flags : readable and exectuable */
|
||||
/* flags : readable and executable */
|
||||
0x05, 0x00, 0x00, 0x00,
|
||||
/* padding */
|
||||
0x00, 0x00, 0x00, 0x00,
|
||||
|
@ -6,7 +6,7 @@
|
||||
* uclass devices probe when a SCMI server exposes resources.
|
||||
*
|
||||
* Note in test.dts the protocol@10 node in scmi node. Protocol 0x10 is not
|
||||
* implemented in U-Boot SCMI components but the implementation is exepected
|
||||
* implemented in U-Boot SCMI components but the implementation is expected
|
||||
* to not complain on unknown protocol IDs, as long as it is not used. Note
|
||||
* in test.dts tests that SCMI drivers probing does not fail for such an
|
||||
* unknown SCMI protocol ID.
|
||||
|
@ -17,7 +17,7 @@
|
||||
#include <test/test.h>
|
||||
#include <test/ut.h>
|
||||
|
||||
/* Test if bus childs got probed propperly*/
|
||||
/* Test if bus children got probed properly*/
|
||||
static int dm_test_spmi_probe(struct unit_test_state *uts)
|
||||
{
|
||||
const char *name = "spmi@0";
|
||||
|
@ -592,7 +592,7 @@ static int dm_test_fdt_translation(struct unit_test_state *uts)
|
||||
ut_asserteq_str("dev@2,200", dev->name);
|
||||
ut_asserteq(0xA000, dev_read_addr(dev));
|
||||
|
||||
/* No translation for busses with #size-cells == 0 */
|
||||
/* No translation for buses with #size-cells == 0 */
|
||||
ut_assertok(uclass_find_device_by_seq(UCLASS_TEST_DUMMY, 3, &dev));
|
||||
ut_asserteq_str("dev@42", dev->name);
|
||||
ut_asserteq(0x42, dev_read_addr(dev));
|
||||
|
@ -1,6 +1,6 @@
|
||||
# SPDX-License-Identifier: GPL-2.0+
|
||||
|
||||
__all__ = ['checkpatch', 'command', 'commit', 'control', 'cros_subprocess',
|
||||
'func_test', 'get_maintainer', 'gitutil', 'main', 'patchstream',
|
||||
'func_test', 'get_maintainer', 'gitutil', '__main__', 'patchstream',
|
||||
'project', 'series', 'setup', 'settings', 'terminal',
|
||||
'test_checkpatch', 'test_util', 'tools', 'tout']
|
||||
|
@ -7,9 +7,9 @@
|
||||
"""See README for more information"""
|
||||
|
||||
from argparse import ArgumentParser
|
||||
import importlib.resources
|
||||
import os
|
||||
import re
|
||||
import shutil
|
||||
import sys
|
||||
import traceback
|
||||
|
||||
@ -19,8 +19,8 @@ if __name__ == "__main__":
|
||||
sys.path.append(os.path.join(our_path, '..'))
|
||||
|
||||
# Our modules
|
||||
from patman import command
|
||||
from patman import control
|
||||
from patman import func_test
|
||||
from patman import gitutil
|
||||
from patman import project
|
||||
from patman import settings
|
||||
@ -55,7 +55,8 @@ parser.add_argument('-H', '--full-help', action='store_true', dest='full_help',
|
||||
default=False, help='Display the README file')
|
||||
|
||||
subparsers = parser.add_subparsers(dest='cmd')
|
||||
send = subparsers.add_parser('send')
|
||||
send = subparsers.add_parser(
|
||||
'send', help='Format, check and email patches (default command)')
|
||||
send.add_argument('-i', '--ignore-errors', action='store_true',
|
||||
dest='ignore_errors', default=False,
|
||||
help='Send patches email even if patch errors are found')
|
||||
@ -64,6 +65,12 @@ send.add_argument('-l', '--limit-cc', dest='limit', type=int, default=None,
|
||||
send.add_argument('-m', '--no-maintainers', action='store_false',
|
||||
dest='add_maintainers', default=True,
|
||||
help="Don't cc the file maintainers automatically")
|
||||
send.add_argument(
|
||||
'--get-maintainer-script', dest='get_maintainer_script', type=str,
|
||||
action='store',
|
||||
default=os.path.join(gitutil.get_top_level(), 'scripts',
|
||||
'get_maintainer.pl') + ' --norolestats',
|
||||
help='File name of the get_maintainer.pl (or compatible) script.')
|
||||
send.add_argument('-n', '--dry-run', action='store_true', dest='dry_run',
|
||||
default=False, help="Do a dry run (create but don't email patches)")
|
||||
send.add_argument('-r', '--in-reply-to', type=str, action='store',
|
||||
@ -96,9 +103,11 @@ send.add_argument('--smtp-server', type=str,
|
||||
|
||||
send.add_argument('patchfiles', nargs='*')
|
||||
|
||||
test_parser = subparsers.add_parser('test', help='Run tests')
|
||||
test_parser.add_argument('testname', type=str, default=None, nargs='?',
|
||||
help="Specify the test to run")
|
||||
# Only add the 'test' action if the test data files are available.
|
||||
if os.path.exists(func_test.TEST_DATA_DIR):
|
||||
test_parser = subparsers.add_parser('test', help='Run tests')
|
||||
test_parser.add_argument('testname', type=str, default=None, nargs='?',
|
||||
help="Specify the test to run")
|
||||
|
||||
status = subparsers.add_parser('status',
|
||||
help='Check status of patches in patchwork')
|
||||
@ -115,7 +124,7 @@ status.add_argument('-f', '--force', action='store_true',
|
||||
argv = sys.argv[1:]
|
||||
args, rest = parser.parse_known_args(argv)
|
||||
if hasattr(args, 'project'):
|
||||
settings.Setup(gitutil, parser, args.project, '')
|
||||
settings.Setup(parser, args.project)
|
||||
args, rest = parser.parse_known_args(argv)
|
||||
|
||||
# If we have a command, it is safe to parse all arguments
|
||||
@ -136,7 +145,6 @@ if not args.debug:
|
||||
|
||||
# Run our meagre tests
|
||||
if args.cmd == 'test':
|
||||
import doctest
|
||||
from patman import func_test
|
||||
|
||||
result = test_util.run_test_suites(
|
||||
@ -163,11 +171,8 @@ elif args.cmd == 'send':
|
||||
fd.close()
|
||||
|
||||
elif args.full_help:
|
||||
tools.print_full_help(
|
||||
os.path.join(os.path.dirname(os.path.realpath(sys.argv[0])),
|
||||
'README.rst')
|
||||
)
|
||||
|
||||
with importlib.resources.path('patman', 'README.rst') as readme:
|
||||
tools.print_full_help(str(readme))
|
||||
else:
|
||||
# If we are not processing tags, no need to warning about bad ones
|
||||
if not args.process_tags:
|
||||
@ -183,7 +188,7 @@ elif args.cmd == 'status':
|
||||
args.show_comments, args.patchwork_url)
|
||||
except Exception as e:
|
||||
terminal.tprint('patman: %s: %s' % (type(e).__name__, e),
|
||||
colour=terminal.Color.RED)
|
||||
colour=terminal.Color.RED)
|
||||
if args.debug:
|
||||
print()
|
||||
traceback.print_exc()
|
@ -211,7 +211,7 @@ def check_patch(fname, verbose=False, show_types=False, use_tree=False):
|
||||
stdout: Full output of checkpatch
|
||||
"""
|
||||
chk = find_check_patch()
|
||||
args = [chk]
|
||||
args = [chk, '--u-boot', '--strict']
|
||||
if not use_tree:
|
||||
args.append('--no-tree')
|
||||
if show_types:
|
||||
|
@ -94,8 +94,8 @@ def check_patches(series, patch_files, run_checkpatch, verbose, use_tree):
|
||||
|
||||
|
||||
def email_patches(col, series, cover_fname, patch_files, process_tags, its_a_go,
|
||||
ignore_bad_tags, add_maintainers, limit, dry_run, in_reply_to,
|
||||
thread, smtp_server):
|
||||
ignore_bad_tags, add_maintainers, get_maintainer_script, limit,
|
||||
dry_run, in_reply_to, thread, smtp_server):
|
||||
"""Email patches to the recipients
|
||||
|
||||
This emails out the patches and cover letter using 'git send-email'. Each
|
||||
@ -123,6 +123,8 @@ def email_patches(col, series, cover_fname, patch_files, process_tags, its_a_go,
|
||||
ignore_bad_tags (bool): True to just print a warning for unknown tags,
|
||||
False to halt with an error
|
||||
add_maintainers (bool): Run the get_maintainer.pl script for each patch
|
||||
get_maintainer_script (str): The script used to retrieve which
|
||||
maintainers to cc
|
||||
limit (int): Limit on the number of people that can be cc'd on a single
|
||||
patch or the cover letter (None if no limit)
|
||||
dry_run (bool): Don't actually email the patches, just print out what
|
||||
@ -134,7 +136,7 @@ def email_patches(col, series, cover_fname, patch_files, process_tags, its_a_go,
|
||||
smtp_server (str): SMTP server to use to send patches (None for default)
|
||||
"""
|
||||
cc_file = series.MakeCcFile(process_tags, cover_fname, not ignore_bad_tags,
|
||||
add_maintainers, limit)
|
||||
add_maintainers, limit, get_maintainer_script)
|
||||
|
||||
# Email the patches out (giving the user time to check / cancel)
|
||||
cmd = ''
|
||||
@ -174,8 +176,8 @@ def send(args):
|
||||
email_patches(
|
||||
col, series, cover_fname, patch_files, args.process_tags,
|
||||
its_a_go, args.ignore_bad_tags, args.add_maintainers,
|
||||
args.limit, args.dry_run, args.in_reply_to, args.thread,
|
||||
args.smtp_server)
|
||||
args.get_maintainer_script, args.limit, args.dry_run,
|
||||
args.in_reply_to, args.thread, args.smtp_server)
|
||||
|
||||
def patchwork_status(branch, count, start, end, dest_branch, force,
|
||||
show_comments, url):
|
||||
|
@ -6,7 +6,9 @@
|
||||
|
||||
"""Functional tests for checking that patman behaves correctly"""
|
||||
|
||||
import contextlib
|
||||
import os
|
||||
import pathlib
|
||||
import re
|
||||
import shutil
|
||||
import sys
|
||||
@ -28,6 +30,21 @@ from patman.test_util import capture_sys_output
|
||||
import pygit2
|
||||
from patman import status
|
||||
|
||||
PATMAN_DIR = pathlib.Path(__file__).parent
|
||||
TEST_DATA_DIR = PATMAN_DIR / 'test/'
|
||||
|
||||
|
||||
@contextlib.contextmanager
|
||||
def directory_excursion(directory):
|
||||
"""Change directory to `directory` for a limited to the context block."""
|
||||
current = os.getcwd()
|
||||
try:
|
||||
os.chdir(directory)
|
||||
yield
|
||||
finally:
|
||||
os.chdir(current)
|
||||
|
||||
|
||||
class TestFunctional(unittest.TestCase):
|
||||
"""Functional tests for checking that patman behaves correctly"""
|
||||
leb = (b'Lord Edmund Blackadd\xc3\xabr <weasel@blackadder.org>'.
|
||||
@ -57,8 +74,7 @@ class TestFunctional(unittest.TestCase):
|
||||
Returns:
|
||||
str: Full path to file in the test directory
|
||||
"""
|
||||
return os.path.join(os.path.dirname(os.path.realpath(sys.argv[0])),
|
||||
'test', fname)
|
||||
return TEST_DATA_DIR / fname
|
||||
|
||||
@classmethod
|
||||
def _get_text(cls, fname):
|
||||
@ -200,6 +216,8 @@ class TestFunctional(unittest.TestCase):
|
||||
text = self._get_text('test01.txt')
|
||||
series = patchstream.get_metadata_for_test(text)
|
||||
cover_fname, args = self._create_patches_for_test(series)
|
||||
get_maintainer_script = str(pathlib.Path(__file__).parent.parent.parent
|
||||
/ 'get_maintainer.pl') + ' --norolestats'
|
||||
with capture_sys_output() as out:
|
||||
patchstream.fix_patches(series, args)
|
||||
if cover_fname and series.get('cover'):
|
||||
@ -207,7 +225,7 @@ class TestFunctional(unittest.TestCase):
|
||||
series.DoChecks()
|
||||
cc_file = series.MakeCcFile(process_tags, cover_fname,
|
||||
not ignore_bad_tags, add_maintainers,
|
||||
None)
|
||||
None, get_maintainer_script)
|
||||
cmd = gitutil.email_patches(
|
||||
series, cover_fname, args, dry_run, not ignore_bad_tags,
|
||||
cc_file, in_reply_to=in_reply_to, thread=None)
|
||||
@ -502,6 +520,37 @@ complicated as possible''')
|
||||
finally:
|
||||
os.chdir(orig_dir)
|
||||
|
||||
def test_custom_get_maintainer_script(self):
|
||||
"""Validate that a custom get_maintainer script gets used."""
|
||||
self.make_git_tree()
|
||||
with directory_excursion(self.gitdir):
|
||||
# Setup git.
|
||||
os.environ['GIT_CONFIG_GLOBAL'] = '/dev/null'
|
||||
os.environ['GIT_CONFIG_SYSTEM'] = '/dev/null'
|
||||
tools.run('git', 'config', 'user.name', 'Dummy')
|
||||
tools.run('git', 'config', 'user.email', 'dumdum@dummy.com')
|
||||
tools.run('git', 'branch', 'upstream')
|
||||
tools.run('git', 'branch', '--set-upstream-to=upstream')
|
||||
tools.run('git', 'add', '.')
|
||||
tools.run('git', 'commit', '-m', 'new commit')
|
||||
|
||||
# Setup patman configuration.
|
||||
with open('.patman', 'w', buffering=1) as f:
|
||||
f.write('[settings]\n'
|
||||
'get_maintainer_script: dummy-script.sh\n'
|
||||
'check_patch: False\n')
|
||||
with open('dummy-script.sh', 'w', buffering=1) as f:
|
||||
f.write('#!/usr/bin/env python\n'
|
||||
'print("hello@there.com")\n')
|
||||
os.chmod('dummy-script.sh', 0x555)
|
||||
|
||||
# Finally, do the test
|
||||
with capture_sys_output():
|
||||
output = tools.run(PATMAN_DIR / 'patman', '--dry-run')
|
||||
# Assert the email address is part of the dry-run
|
||||
# output.
|
||||
self.assertIn('hello@there.com', output)
|
||||
|
||||
def test_tags(self):
|
||||
"""Test collection of tags in a patchstream"""
|
||||
text = '''This is a patch
|
||||
|
@ -1,48 +1,61 @@
|
||||
# SPDX-License-Identifier: GPL-2.0+
|
||||
# Copyright (c) 2012 The Chromium OS Authors.
|
||||
# Copyright (c) 2022 Maxim Cournoyer <maxim.cournoyer@savoirfairelinux.com>
|
||||
#
|
||||
|
||||
import os
|
||||
import shlex
|
||||
import shutil
|
||||
|
||||
from patman import command
|
||||
from patman import gitutil
|
||||
|
||||
def find_get_maintainer(try_list):
|
||||
"""Look for the get_maintainer.pl script.
|
||||
|
||||
Args:
|
||||
try_list: List of directories to try for the get_maintainer.pl script
|
||||
def find_get_maintainer(script_file_name):
|
||||
"""Try to find where `script_file_name` is.
|
||||
|
||||
Returns:
|
||||
If the script is found we'll return a path to it; else None.
|
||||
It searches in PATH and falls back to a path relative to the top
|
||||
of the current git repository.
|
||||
"""
|
||||
# Look in the list
|
||||
for path in try_list:
|
||||
fname = os.path.join(path, 'get_maintainer.pl')
|
||||
if os.path.isfile(fname):
|
||||
return fname
|
||||
get_maintainer = shutil.which(script_file_name)
|
||||
if get_maintainer:
|
||||
return get_maintainer
|
||||
|
||||
return None
|
||||
git_relative_script = os.path.join(gitutil.get_top_level(),
|
||||
script_file_name)
|
||||
if os.path.exists(git_relative_script):
|
||||
return git_relative_script
|
||||
|
||||
def get_maintainer(dir_list, fname, verbose=False):
|
||||
"""Run get_maintainer.pl on a file if we find it.
|
||||
|
||||
We look for get_maintainer.pl in the 'scripts' directory at the top of
|
||||
git. If we find it we'll run it. If we don't find get_maintainer.pl
|
||||
then we fail silently.
|
||||
def get_maintainer(script_file_name, fname, verbose=False):
|
||||
"""Run `script_file_name` on a file.
|
||||
|
||||
`script_file_name` should be a get_maintainer.pl-like script that
|
||||
takes a patch file name as an input and return the email addresses
|
||||
of the associated maintainers to standard output, one per line.
|
||||
|
||||
If `script_file_name` does not exist we fail silently.
|
||||
|
||||
Args:
|
||||
dir_list: List of directories to try for the get_maintainer.pl script
|
||||
fname: Path to the patch file to run get_maintainer.pl on.
|
||||
script_file_name: The file name of the get_maintainer.pl script
|
||||
(or compatible).
|
||||
fname: File name of the patch to process with get_maintainer.pl.
|
||||
|
||||
Returns:
|
||||
A list of email addresses to CC to.
|
||||
"""
|
||||
get_maintainer = find_get_maintainer(dir_list)
|
||||
# Expand `script_file_name` into a file name and its arguments, if
|
||||
# any.
|
||||
cmd_args = shlex.split(script_file_name)
|
||||
file_name = cmd_args[0]
|
||||
arguments = cmd_args[1:]
|
||||
|
||||
get_maintainer = find_get_maintainer(file_name)
|
||||
if not get_maintainer:
|
||||
if verbose:
|
||||
print("WARNING: Couldn't find get_maintainer.pl")
|
||||
return []
|
||||
|
||||
stdout = command.output(get_maintainer, '--norolestats', fname)
|
||||
stdout = command.output(get_maintainer, *arguments, fname)
|
||||
lines = stdout.splitlines()
|
||||
return [ x.replace('"', '') for x in lines ]
|
||||
return [x.replace('"', '') for x in lines]
|
||||
|
@ -2,21 +2,19 @@
|
||||
# Copyright (c) 2011 The Chromium OS Authors.
|
||||
#
|
||||
|
||||
import re
|
||||
import os
|
||||
import subprocess
|
||||
import sys
|
||||
|
||||
from patman import command
|
||||
from patman import settings
|
||||
from patman import terminal
|
||||
from patman import tools
|
||||
|
||||
# True to use --no-decorate - we check this in setup()
|
||||
use_no_decorate = True
|
||||
|
||||
|
||||
def log_cmd(commit_range, git_dir=None, oneline=False, reverse=False,
|
||||
count=None):
|
||||
count=None):
|
||||
"""Create a command to perform a 'git log'
|
||||
|
||||
Args:
|
||||
@ -49,6 +47,7 @@ def log_cmd(commit_range, git_dir=None, oneline=False, reverse=False,
|
||||
cmd.append('--')
|
||||
return cmd
|
||||
|
||||
|
||||
def count_commits_to_branch(branch):
|
||||
"""Returns number of commits between HEAD and the tracking branch.
|
||||
|
||||
@ -68,13 +67,14 @@ def count_commits_to_branch(branch):
|
||||
rev_range = '@{upstream}..'
|
||||
pipe = [log_cmd(rev_range, oneline=True)]
|
||||
result = command.run_pipe(pipe, capture=True, capture_stderr=True,
|
||||
oneline=True, raise_on_error=False)
|
||||
oneline=True, raise_on_error=False)
|
||||
if result.return_code:
|
||||
raise ValueError('Failed to determine upstream: %s' %
|
||||
result.stderr.strip())
|
||||
patch_count = len(result.stdout.splitlines())
|
||||
return patch_count
|
||||
|
||||
|
||||
def name_revision(commit_hash):
|
||||
"""Gets the revision name for a commit
|
||||
|
||||
@ -91,6 +91,7 @@ def name_revision(commit_hash):
|
||||
name = stdout.split(' ')[1].strip()
|
||||
return name
|
||||
|
||||
|
||||
def guess_upstream(git_dir, branch):
|
||||
"""Tries to guess the upstream for a branch
|
||||
|
||||
@ -109,7 +110,7 @@ def guess_upstream(git_dir, branch):
|
||||
"""
|
||||
pipe = [log_cmd(branch, git_dir=git_dir, oneline=True, count=100)]
|
||||
result = command.run_pipe(pipe, capture=True, capture_stderr=True,
|
||||
raise_on_error=False)
|
||||
raise_on_error=False)
|
||||
if result.return_code:
|
||||
return None, "Branch '%s' not found" % branch
|
||||
for line in result.stdout.splitlines()[1:]:
|
||||
@ -121,6 +122,7 @@ def guess_upstream(git_dir, branch):
|
||||
return name, "Guessing upstream as '%s'" % name
|
||||
return None, "Cannot find a suitable upstream for branch '%s'" % branch
|
||||
|
||||
|
||||
def get_upstream(git_dir, branch):
|
||||
"""Returns the name of the upstream for a branch
|
||||
|
||||
@ -135,10 +137,10 @@ def get_upstream(git_dir, branch):
|
||||
"""
|
||||
try:
|
||||
remote = command.output_one_line('git', '--git-dir', git_dir, 'config',
|
||||
'branch.%s.remote' % branch)
|
||||
'branch.%s.remote' % branch)
|
||||
merge = command.output_one_line('git', '--git-dir', git_dir, 'config',
|
||||
'branch.%s.merge' % branch)
|
||||
except:
|
||||
'branch.%s.merge' % branch)
|
||||
except Exception:
|
||||
upstream, msg = guess_upstream(git_dir, branch)
|
||||
return upstream, msg
|
||||
|
||||
@ -149,7 +151,8 @@ def get_upstream(git_dir, branch):
|
||||
return '%s/%s' % (remote, leaf), None
|
||||
else:
|
||||
raise ValueError("Cannot determine upstream branch for branch "
|
||||
"'%s' remote='%s', merge='%s'" % (branch, remote, merge))
|
||||
"'%s' remote='%s', merge='%s'"
|
||||
% (branch, remote, merge))
|
||||
|
||||
|
||||
def get_range_in_branch(git_dir, branch, include_upstream=False):
|
||||
@ -168,6 +171,7 @@ def get_range_in_branch(git_dir, branch, include_upstream=False):
|
||||
rstr = '%s%s..%s' % (upstream, '~' if include_upstream else '', branch)
|
||||
return rstr, msg
|
||||
|
||||
|
||||
def count_commits_in_range(git_dir, range_expr):
|
||||
"""Returns the number of commits in the given range.
|
||||
|
||||
@ -180,12 +184,13 @@ def count_commits_in_range(git_dir, range_expr):
|
||||
"""
|
||||
pipe = [log_cmd(range_expr, git_dir=git_dir, oneline=True)]
|
||||
result = command.run_pipe(pipe, capture=True, capture_stderr=True,
|
||||
raise_on_error=False)
|
||||
raise_on_error=False)
|
||||
if result.return_code:
|
||||
return None, "Range '%s' not found or is invalid" % range_expr
|
||||
patch_count = len(result.stdout.splitlines())
|
||||
return patch_count, None
|
||||
|
||||
|
||||
def count_commits_in_branch(git_dir, branch, include_upstream=False):
|
||||
"""Returns the number of commits in the given branch.
|
||||
|
||||
@ -201,6 +206,7 @@ def count_commits_in_branch(git_dir, branch, include_upstream=False):
|
||||
return None, msg
|
||||
return count_commits_in_range(git_dir, range_expr)
|
||||
|
||||
|
||||
def count_commits(commit_range):
|
||||
"""Returns the number of commits in the given range.
|
||||
|
||||
@ -215,6 +221,7 @@ def count_commits(commit_range):
|
||||
patch_count = int(stdout)
|
||||
return patch_count
|
||||
|
||||
|
||||
def checkout(commit_hash, git_dir=None, work_tree=None, force=False):
|
||||
"""Checkout the selected commit for this build
|
||||
|
||||
@ -231,10 +238,11 @@ def checkout(commit_hash, git_dir=None, work_tree=None, force=False):
|
||||
pipe.append('-f')
|
||||
pipe.append(commit_hash)
|
||||
result = command.run_pipe([pipe], capture=True, raise_on_error=False,
|
||||
capture_stderr=True)
|
||||
capture_stderr=True)
|
||||
if result.return_code != 0:
|
||||
raise OSError('git checkout (%s): %s' % (pipe, result.stderr))
|
||||
|
||||
|
||||
def clone(git_dir, output_dir):
|
||||
"""Checkout the selected commit for this build
|
||||
|
||||
@ -243,10 +251,11 @@ def clone(git_dir, output_dir):
|
||||
"""
|
||||
pipe = ['git', 'clone', git_dir, '.']
|
||||
result = command.run_pipe([pipe], capture=True, cwd=output_dir,
|
||||
capture_stderr=True)
|
||||
capture_stderr=True)
|
||||
if result.return_code != 0:
|
||||
raise OSError('git clone: %s' % result.stderr)
|
||||
|
||||
|
||||
def fetch(git_dir=None, work_tree=None):
|
||||
"""Fetch from the origin repo
|
||||
|
||||
@ -263,6 +272,7 @@ def fetch(git_dir=None, work_tree=None):
|
||||
if result.return_code != 0:
|
||||
raise OSError('git fetch: %s' % result.stderr)
|
||||
|
||||
|
||||
def check_worktree_is_available(git_dir):
|
||||
"""Check if git-worktree functionality is available
|
||||
|
||||
@ -274,9 +284,10 @@ def check_worktree_is_available(git_dir):
|
||||
"""
|
||||
pipe = ['git', '--git-dir', git_dir, 'worktree', 'list']
|
||||
result = command.run_pipe([pipe], capture=True, capture_stderr=True,
|
||||
raise_on_error=False)
|
||||
raise_on_error=False)
|
||||
return result.return_code == 0
|
||||
|
||||
|
||||
def add_worktree(git_dir, output_dir, commit_hash=None):
|
||||
"""Create and checkout a new git worktree for this build
|
||||
|
||||
@ -290,10 +301,11 @@ def add_worktree(git_dir, output_dir, commit_hash=None):
|
||||
if commit_hash:
|
||||
pipe.append(commit_hash)
|
||||
result = command.run_pipe([pipe], capture=True, cwd=output_dir,
|
||||
capture_stderr=True)
|
||||
capture_stderr=True)
|
||||
if result.return_code != 0:
|
||||
raise OSError('git worktree add: %s' % result.stderr)
|
||||
|
||||
|
||||
def prune_worktrees(git_dir):
|
||||
"""Remove administrative files for deleted worktrees
|
||||
|
||||
@ -305,7 +317,8 @@ def prune_worktrees(git_dir):
|
||||
if result.return_code != 0:
|
||||
raise OSError('git worktree prune: %s' % result.stderr)
|
||||
|
||||
def create_patches(branch, start, count, ignore_binary, series, signoff = True):
|
||||
|
||||
def create_patches(branch, start, count, ignore_binary, series, signoff=True):
|
||||
"""Create a series of patches from the top of the current branch.
|
||||
|
||||
The patch files are written to the current directory using
|
||||
@ -321,9 +334,7 @@ def create_patches(branch, start, count, ignore_binary, series, signoff = True):
|
||||
Filename of cover letter (None if none)
|
||||
List of filenames of patch files
|
||||
"""
|
||||
if series.get('version'):
|
||||
version = '%s ' % series['version']
|
||||
cmd = ['git', 'format-patch', '-M' ]
|
||||
cmd = ['git', 'format-patch', '-M']
|
||||
if signoff:
|
||||
cmd.append('--signoff')
|
||||
if ignore_binary:
|
||||
@ -341,9 +352,10 @@ def create_patches(branch, start, count, ignore_binary, series, signoff = True):
|
||||
|
||||
# We have an extra file if there is a cover letter
|
||||
if series.get('cover'):
|
||||
return files[0], files[1:]
|
||||
return files[0], files[1:]
|
||||
else:
|
||||
return None, files
|
||||
return None, files
|
||||
|
||||
|
||||
def build_email_list(in_list, tag=None, alias=None, warn_on_error=True):
|
||||
"""Build a list of email addresses based on an input list.
|
||||
@ -385,40 +397,43 @@ def build_email_list(in_list, tag=None, alias=None, warn_on_error=True):
|
||||
raw += lookup_email(item, alias, warn_on_error=warn_on_error)
|
||||
result = []
|
||||
for item in raw:
|
||||
if not item in result:
|
||||
if item not in result:
|
||||
result.append(item)
|
||||
if tag:
|
||||
return ['%s %s%s%s' % (tag, quote, email, quote) for email in result]
|
||||
return result
|
||||
|
||||
|
||||
def check_suppress_cc_config():
|
||||
"""Check if sendemail.suppresscc is configured correctly.
|
||||
|
||||
Returns:
|
||||
True if the option is configured correctly, False otherwise.
|
||||
"""
|
||||
suppresscc = command.output_one_line('git', 'config', 'sendemail.suppresscc',
|
||||
raise_on_error=False)
|
||||
suppresscc = command.output_one_line(
|
||||
'git', 'config', 'sendemail.suppresscc', raise_on_error=False)
|
||||
|
||||
# Other settings should be fine.
|
||||
if suppresscc == 'all' or suppresscc == 'cccmd':
|
||||
col = terminal.Color()
|
||||
|
||||
print((col.build(col.RED, "error") +
|
||||
": git config sendemail.suppresscc set to %s\n" % (suppresscc)) +
|
||||
" patman needs --cc-cmd to be run to set the cc list.\n" +
|
||||
" Please run:\n" +
|
||||
" git config --unset sendemail.suppresscc\n" +
|
||||
" Or read the man page:\n" +
|
||||
" git send-email --help\n" +
|
||||
" and set an option that runs --cc-cmd\n")
|
||||
": git config sendemail.suppresscc set to %s\n"
|
||||
% (suppresscc)) +
|
||||
" patman needs --cc-cmd to be run to set the cc list.\n" +
|
||||
" Please run:\n" +
|
||||
" git config --unset sendemail.suppresscc\n" +
|
||||
" Or read the man page:\n" +
|
||||
" git send-email --help\n" +
|
||||
" and set an option that runs --cc-cmd\n")
|
||||
return False
|
||||
|
||||
return True
|
||||
|
||||
|
||||
def email_patches(series, cover_fname, args, dry_run, warn_on_error, cc_fname,
|
||||
self_only=False, alias=None, in_reply_to=None, thread=False,
|
||||
smtp_server=None):
|
||||
self_only=False, alias=None, in_reply_to=None, thread=False,
|
||||
smtp_server=None, get_maintainer_script=None):
|
||||
"""Email a patch series.
|
||||
|
||||
Args:
|
||||
@ -435,6 +450,7 @@ def email_patches(series, cover_fname, args, dry_run, warn_on_error, cc_fname,
|
||||
thread: True to add --thread to git send-email (make
|
||||
all patches reply to cover-letter or first patch in series)
|
||||
smtp_server: SMTP server to use to send patches
|
||||
get_maintainer_script: File name of script to get maintainers emails
|
||||
|
||||
Returns:
|
||||
Git command that was/would be run
|
||||
@ -487,9 +503,10 @@ send --cc-cmd cc-fname" cover p1 p2'
|
||||
"git config sendemail.to u-boot@lists.denx.de")
|
||||
return
|
||||
cc = build_email_list(list(set(series.get('cc')) - set(series.get('to'))),
|
||||
'--cc', alias, warn_on_error)
|
||||
'--cc', alias, warn_on_error)
|
||||
if self_only:
|
||||
to = build_email_list([os.getenv('USER')], '--to', alias, warn_on_error)
|
||||
to = build_email_list([os.getenv('USER')], '--to',
|
||||
alias, warn_on_error)
|
||||
cc = []
|
||||
cmd = ['git', 'send-email', '--annotate']
|
||||
if smtp_server:
|
||||
@ -565,7 +582,7 @@ def lookup_email(lookup_name, alias=None, warn_on_error=True, level=0):
|
||||
if not alias:
|
||||
alias = settings.alias
|
||||
lookup_name = lookup_name.strip()
|
||||
if '@' in lookup_name: # Perhaps a real email address
|
||||
if '@' in lookup_name: # Perhaps a real email address
|
||||
return [lookup_name]
|
||||
|
||||
lookup_name = lookup_name.lower()
|
||||
@ -581,7 +598,7 @@ def lookup_email(lookup_name, alias=None, warn_on_error=True, level=0):
|
||||
return out_list
|
||||
|
||||
if lookup_name:
|
||||
if not lookup_name in alias:
|
||||
if lookup_name not in alias:
|
||||
msg = "Alias '%s' not found" % lookup_name
|
||||
if warn_on_error:
|
||||
print(col.build(col.RED, msg))
|
||||
@ -589,11 +606,12 @@ def lookup_email(lookup_name, alias=None, warn_on_error=True, level=0):
|
||||
for item in alias[lookup_name]:
|
||||
todo = lookup_email(item, alias, warn_on_error, level + 1)
|
||||
for new_item in todo:
|
||||
if not new_item in out_list:
|
||||
if new_item not in out_list:
|
||||
out_list.append(new_item)
|
||||
|
||||
return out_list
|
||||
|
||||
|
||||
def get_top_level():
|
||||
"""Return name of top-level directory for this git repo.
|
||||
|
||||
@ -608,6 +626,7 @@ def get_top_level():
|
||||
"""
|
||||
return command.output_one_line('git', 'rev-parse', '--show-toplevel')
|
||||
|
||||
|
||||
def get_alias_file():
|
||||
"""Gets the name of the git alias file.
|
||||
|
||||
@ -615,7 +634,7 @@ def get_alias_file():
|
||||
Filename of git alias file, or None if none
|
||||
"""
|
||||
fname = command.output_one_line('git', 'config', 'sendemail.aliasesfile',
|
||||
raise_on_error=False)
|
||||
raise_on_error=False)
|
||||
if not fname:
|
||||
return None
|
||||
|
||||
@ -625,6 +644,7 @@ def get_alias_file():
|
||||
|
||||
return os.path.join(get_top_level(), fname)
|
||||
|
||||
|
||||
def get_default_user_name():
|
||||
"""Gets the user.name from .gitconfig file.
|
||||
|
||||
@ -634,6 +654,7 @@ def get_default_user_name():
|
||||
uname = command.output_one_line('git', 'config', '--global', 'user.name')
|
||||
return uname
|
||||
|
||||
|
||||
def get_default_user_email():
|
||||
"""Gets the user.email from the global .gitconfig file.
|
||||
|
||||
@ -643,17 +664,19 @@ def get_default_user_email():
|
||||
uemail = command.output_one_line('git', 'config', '--global', 'user.email')
|
||||
return uemail
|
||||
|
||||
|
||||
def get_default_subject_prefix():
|
||||
"""Gets the format.subjectprefix from local .git/config file.
|
||||
|
||||
Returns:
|
||||
Subject prefix found in local .git/config file, or None if none
|
||||
"""
|
||||
sub_prefix = command.output_one_line('git', 'config', 'format.subjectprefix',
|
||||
raise_on_error=False)
|
||||
sub_prefix = command.output_one_line(
|
||||
'git', 'config', 'format.subjectprefix', raise_on_error=False)
|
||||
|
||||
return sub_prefix
|
||||
|
||||
|
||||
def setup():
|
||||
"""Set up git utils, by reading the alias files."""
|
||||
# Check for a git alias file also
|
||||
@ -666,6 +689,7 @@ def setup():
|
||||
use_no_decorate = (command.run_pipe([cmd], raise_on_error=False)
|
||||
.return_code == 0)
|
||||
|
||||
|
||||
def get_head():
|
||||
"""Get the hash of the current HEAD
|
||||
|
||||
@ -674,6 +698,7 @@ def get_head():
|
||||
"""
|
||||
return command.output_one_line('git', 'show', '-s', '--pretty=format:%H')
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
import doctest
|
||||
|
||||
|
@ -1 +1 @@
|
||||
main.py
|
||||
__main__.py
|
@ -1,6 +1,7 @@
|
||||
.. SPDX-License-Identifier: GPL-2.0+
|
||||
.. Copyright (c) 2011 The Chromium OS Authors
|
||||
.. Simon Glass <sjg@chromium.org>
|
||||
.. Maxim Cournoyer <maxim.cournoyer@savoirfairelinux.com>
|
||||
.. v1, v2, 19-Oct-11
|
||||
.. revised v3 24-Nov-11
|
||||
.. revised v4 Independence Day 2020, with Patchwork integration
|
||||
@ -68,13 +69,28 @@ this once::
|
||||
|
||||
git config sendemail.aliasesfile doc/git-mailrc
|
||||
|
||||
For both Linux and U-Boot the 'scripts/get_maintainer.pl' handles figuring
|
||||
out where to send patches pretty well.
|
||||
For both Linux and U-Boot the 'scripts/get_maintainer.pl' handles
|
||||
figuring out where to send patches pretty well. For other projects,
|
||||
you may want to specify a different script to be run, for example via
|
||||
a project-specific `.patman` file::
|
||||
|
||||
# .patman configuration file at the root of some project
|
||||
|
||||
[settings]
|
||||
get_maintainer_script: etc/teams.scm get-maintainer
|
||||
|
||||
The `get_maintainer_script` option corresponds to the
|
||||
`--get-maintainer-script` argument of the `send` command. It is
|
||||
looked relatively to the root of the current git repository, as well
|
||||
as on PATH. It can also be provided arguments, as shown above. The
|
||||
contract is that the script should accept a patch file name and return
|
||||
a list of email addresses, one per line, like `get_maintainer.pl`
|
||||
does.
|
||||
|
||||
During the first run patman creates a config file for you by taking the default
|
||||
user name and email address from the global .gitconfig file.
|
||||
|
||||
To add your own, create a file ~/.patman like this::
|
||||
To add your own, create a file `~/.patman` like this::
|
||||
|
||||
# patman alias file
|
||||
|
||||
@ -85,6 +101,12 @@ To add your own, create a file ~/.patman like this::
|
||||
wolfgang: Wolfgang Denk <wd@denx.de>
|
||||
others: Mike Frysinger <vapier@gentoo.org>, Fred Bloggs <f.bloggs@napier.net>
|
||||
|
||||
As hinted above, Patman will also look for a `.patman` configuration
|
||||
file at the root of the current project git repository, which makes it
|
||||
possible to override the `project` settings variable or anything else
|
||||
in a project-specific way. The values of this "local" configuration
|
||||
file take precedence over those of the "global" one.
|
||||
|
||||
Aliases are recursive.
|
||||
|
||||
The checkpatch.pl in the U-Boot tools/ subdirectory will be located and
|
||||
@ -680,6 +702,16 @@ them:
|
||||
|
||||
$ tools/patman/patman test
|
||||
|
||||
Note that since the test suite depends on data files only available in
|
||||
the git checkout, the `test` command is hidden unless `patman` is
|
||||
invoked from the U-Boot git repository.
|
||||
|
||||
Alternatively, you can run the test suite via Pytest:
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
$ cd tools/patman && pytest
|
||||
|
||||
Error handling doesn't always produce friendly error messages - e.g.
|
||||
putting an incorrect tag in a commit may provide a confusing message.
|
||||
|
||||
|
2
tools/patman/pytest.ini
Normal file
2
tools/patman/pytest.ini
Normal file
@ -0,0 +1,2 @@
|
||||
[pytest]
|
||||
addopts = --doctest-modules
|
@ -235,7 +235,7 @@ class Series(dict):
|
||||
print(col.build(col.RED, str))
|
||||
|
||||
def MakeCcFile(self, process_tags, cover_fname, warn_on_error,
|
||||
add_maintainers, limit):
|
||||
add_maintainers, limit, get_maintainer_script):
|
||||
"""Make a cc file for us to use for per-commit Cc automation
|
||||
|
||||
Also stores in self._generated_cc to make ShowActions() faster.
|
||||
@ -249,6 +249,8 @@ class Series(dict):
|
||||
True/False to call the get_maintainers to CC maintainers
|
||||
List of maintainers to include (for testing)
|
||||
limit: Limit the length of the Cc list (None if no limit)
|
||||
get_maintainer_script: The file name of the get_maintainer.pl
|
||||
script (or compatible).
|
||||
Return:
|
||||
Filename of temp file created
|
||||
"""
|
||||
@ -267,8 +269,9 @@ class Series(dict):
|
||||
if type(add_maintainers) == type(cc):
|
||||
cc += add_maintainers
|
||||
elif add_maintainers:
|
||||
dir_list = [os.path.join(gitutil.get_top_level(), 'scripts')]
|
||||
cc += get_maintainer.get_maintainer(dir_list, commit.patch)
|
||||
|
||||
cc += get_maintainer.get_maintainer(get_maintainer_script,
|
||||
commit.patch)
|
||||
for x in set(cc) & set(settings.bounces):
|
||||
print(col.build(col.YELLOW, 'Skipping "%s"' % x))
|
||||
cc = list(set(cc) - set(settings.bounces))
|
||||
|
@ -1,18 +1,18 @@
|
||||
# SPDX-License-Identifier: GPL-2.0+
|
||||
# Copyright (c) 2011 The Chromium OS Authors.
|
||||
# Copyright (c) 2022 Maxim Cournoyer <maxim.cournoyer@savoirfairelinux.com>
|
||||
#
|
||||
|
||||
try:
|
||||
import configparser as ConfigParser
|
||||
except:
|
||||
except Exception:
|
||||
import ConfigParser
|
||||
|
||||
import argparse
|
||||
import os
|
||||
import re
|
||||
|
||||
from patman import command
|
||||
from patman import tools
|
||||
from patman import gitutil
|
||||
|
||||
"""Default settings per-project.
|
||||
|
||||
@ -32,7 +32,8 @@ _default_settings = {
|
||||
},
|
||||
}
|
||||
|
||||
class _ProjectConfigParser(ConfigParser.SafeConfigParser):
|
||||
|
||||
class _ProjectConfigParser(ConfigParser.ConfigParser):
|
||||
"""ConfigParser that handles projects.
|
||||
|
||||
There are two main goals of this class:
|
||||
@ -83,14 +84,14 @@ class _ProjectConfigParser(ConfigParser.SafeConfigParser):
|
||||
def __init__(self, project_name):
|
||||
"""Construct _ProjectConfigParser.
|
||||
|
||||
In addition to standard SafeConfigParser initialization, this also loads
|
||||
project defaults.
|
||||
In addition to standard ConfigParser initialization, this also
|
||||
loads project defaults.
|
||||
|
||||
Args:
|
||||
project_name: The name of the project.
|
||||
"""
|
||||
self._project_name = project_name
|
||||
ConfigParser.SafeConfigParser.__init__(self)
|
||||
ConfigParser.ConfigParser.__init__(self)
|
||||
|
||||
# Update the project settings in the config based on
|
||||
# the _default_settings global.
|
||||
@ -102,31 +103,31 @@ class _ProjectConfigParser(ConfigParser.SafeConfigParser):
|
||||
self.set(project_settings, setting_name, setting_value)
|
||||
|
||||
def get(self, section, option, *args, **kwargs):
|
||||
"""Extend SafeConfigParser to try project_section before section.
|
||||
"""Extend ConfigParser to try project_section before section.
|
||||
|
||||
Args:
|
||||
See SafeConfigParser.
|
||||
See ConfigParser.
|
||||
Returns:
|
||||
See SafeConfigParser.
|
||||
See ConfigParser.
|
||||
"""
|
||||
try:
|
||||
val = ConfigParser.SafeConfigParser.get(
|
||||
val = ConfigParser.ConfigParser.get(
|
||||
self, "%s_%s" % (self._project_name, section), option,
|
||||
*args, **kwargs
|
||||
)
|
||||
except (ConfigParser.NoSectionError, ConfigParser.NoOptionError):
|
||||
val = ConfigParser.SafeConfigParser.get(
|
||||
val = ConfigParser.ConfigParser.get(
|
||||
self, section, option, *args, **kwargs
|
||||
)
|
||||
return val
|
||||
|
||||
def items(self, section, *args, **kwargs):
|
||||
"""Extend SafeConfigParser to add project_section to section.
|
||||
"""Extend ConfigParser to add project_section to section.
|
||||
|
||||
Args:
|
||||
See SafeConfigParser.
|
||||
See ConfigParser.
|
||||
Returns:
|
||||
See SafeConfigParser.
|
||||
See ConfigParser.
|
||||
"""
|
||||
project_items = []
|
||||
has_project_section = False
|
||||
@ -134,7 +135,7 @@ class _ProjectConfigParser(ConfigParser.SafeConfigParser):
|
||||
|
||||
# Get items from the project section
|
||||
try:
|
||||
project_items = ConfigParser.SafeConfigParser.items(
|
||||
project_items = ConfigParser.ConfigParser.items(
|
||||
self, "%s_%s" % (self._project_name, section), *args, **kwargs
|
||||
)
|
||||
has_project_section = True
|
||||
@ -143,7 +144,7 @@ class _ProjectConfigParser(ConfigParser.SafeConfigParser):
|
||||
|
||||
# Get top-level items
|
||||
try:
|
||||
top_items = ConfigParser.SafeConfigParser.items(
|
||||
top_items = ConfigParser.ConfigParser.items(
|
||||
self, section, *args, **kwargs
|
||||
)
|
||||
except ConfigParser.NoSectionError:
|
||||
@ -155,6 +156,7 @@ class _ProjectConfigParser(ConfigParser.SafeConfigParser):
|
||||
item_dict.update(project_items)
|
||||
return {(item, val) for item, val in item_dict.items()}
|
||||
|
||||
|
||||
def ReadGitAliases(fname):
|
||||
"""Read a git alias file. This is in the form used by git:
|
||||
|
||||
@ -170,7 +172,7 @@ def ReadGitAliases(fname):
|
||||
print("Warning: Cannot find alias file '%s'" % fname)
|
||||
return
|
||||
|
||||
re_line = re.compile('alias\s+(\S+)\s+(.*)')
|
||||
re_line = re.compile(r'alias\s+(\S+)\s+(.*)')
|
||||
for line in fd.readlines():
|
||||
line = line.strip()
|
||||
if not line or line[0] == '#':
|
||||
@ -190,7 +192,8 @@ def ReadGitAliases(fname):
|
||||
|
||||
fd.close()
|
||||
|
||||
def CreatePatmanConfigFile(gitutil, config_fname):
|
||||
|
||||
def CreatePatmanConfigFile(config_fname):
|
||||
"""Creates a config file under $(HOME)/.patman if it can't find one.
|
||||
|
||||
Args:
|
||||
@ -200,12 +203,12 @@ def CreatePatmanConfigFile(gitutil, config_fname):
|
||||
None
|
||||
"""
|
||||
name = gitutil.get_default_user_name()
|
||||
if name == None:
|
||||
if name is None:
|
||||
name = input("Enter name: ")
|
||||
|
||||
email = gitutil.get_default_user_email()
|
||||
|
||||
if email == None:
|
||||
if email is None:
|
||||
email = input("Enter email: ")
|
||||
|
||||
try:
|
||||
@ -220,7 +223,8 @@ me: %s <%s>
|
||||
[bounces]
|
||||
nxp = Zhikang Zhang <zhikang.zhang@nxp.com>
|
||||
''' % (name, email), file=f)
|
||||
f.close();
|
||||
f.close()
|
||||
|
||||
|
||||
def _UpdateDefaults(main_parser, config):
|
||||
"""Update the given OptionParser defaults based on config.
|
||||
@ -242,8 +246,8 @@ def _UpdateDefaults(main_parser, config):
|
||||
# Find all the parsers and subparsers
|
||||
parsers = [main_parser]
|
||||
parsers += [subparser for action in main_parser._actions
|
||||
if isinstance(action, argparse._SubParsersAction)
|
||||
for _, subparser in action.choices.items()]
|
||||
if isinstance(action, argparse._SubParsersAction)
|
||||
for _, subparser in action.choices.items()]
|
||||
|
||||
# Collect the defaults from each parser
|
||||
defaults = {}
|
||||
@ -270,8 +274,9 @@ def _UpdateDefaults(main_parser, config):
|
||||
# Set all the defaults and manually propagate them to subparsers
|
||||
main_parser.set_defaults(**defaults)
|
||||
for parser, pdefs in zip(parsers, parser_defaults):
|
||||
parser.set_defaults(**{ k: v for k, v in defaults.items()
|
||||
if k in pdefs })
|
||||
parser.set_defaults(**{k: v for k, v in defaults.items()
|
||||
if k in pdefs})
|
||||
|
||||
|
||||
def _ReadAliasFile(fname):
|
||||
"""Read in the U-Boot git alias file if it exists.
|
||||
@ -298,6 +303,7 @@ def _ReadAliasFile(fname):
|
||||
if bad_line:
|
||||
print(bad_line)
|
||||
|
||||
|
||||
def _ReadBouncesFile(fname):
|
||||
"""Read in the bounces file if it exists
|
||||
|
||||
@ -311,6 +317,7 @@ def _ReadBouncesFile(fname):
|
||||
continue
|
||||
bounces.add(line.strip())
|
||||
|
||||
|
||||
def GetItems(config, section):
|
||||
"""Get the items from a section of the config.
|
||||
|
||||
@ -323,31 +330,50 @@ def GetItems(config, section):
|
||||
"""
|
||||
try:
|
||||
return config.items(section)
|
||||
except ConfigParser.NoSectionError as e:
|
||||
except ConfigParser.NoSectionError:
|
||||
return []
|
||||
except:
|
||||
raise
|
||||
|
||||
def Setup(gitutil, parser, project_name, config_fname=''):
|
||||
|
||||
def Setup(parser, project_name, config_fname=None):
|
||||
"""Set up the settings module by reading config files.
|
||||
|
||||
Unless `config_fname` is specified, a `.patman` config file local
|
||||
to the git repository is consulted, followed by the global
|
||||
`$HOME/.patman`. If none exists, the later is created. Values
|
||||
defined in the local config file take precedence over those
|
||||
defined in the global one.
|
||||
|
||||
Args:
|
||||
parser: The parser to update
|
||||
parser: The parser to update.
|
||||
project_name: Name of project that we're working on; we'll look
|
||||
for sections named "project_section" as well.
|
||||
config_fname: Config filename to read ('' for default)
|
||||
config_fname: Config filename to read. An error is raised if it
|
||||
does not exist.
|
||||
"""
|
||||
# First read the git alias file if available
|
||||
_ReadAliasFile('doc/git-mailrc')
|
||||
config = _ProjectConfigParser(project_name)
|
||||
if config_fname == '':
|
||||
|
||||
if config_fname and not os.path.exists(config_fname):
|
||||
raise Exception(f'provided {config_fname} does not exist')
|
||||
|
||||
if not config_fname:
|
||||
config_fname = '%s/.patman' % os.getenv('HOME')
|
||||
has_config = os.path.exists(config_fname)
|
||||
|
||||
if not os.path.exists(config_fname):
|
||||
print("No config file found ~/.patman\nCreating one...\n")
|
||||
CreatePatmanConfigFile(gitutil, config_fname)
|
||||
git_local_config_fname = os.path.join(gitutil.get_top_level(), '.patman')
|
||||
has_git_local_config = os.path.exists(git_local_config_fname)
|
||||
|
||||
config.read(config_fname)
|
||||
# Read the git local config last, so that its values override
|
||||
# those of the global config, if any.
|
||||
if has_config:
|
||||
config.read(config_fname)
|
||||
if has_git_local_config:
|
||||
config.read(git_local_config_fname)
|
||||
|
||||
if not (has_config or has_git_local_config):
|
||||
print("No config file found.\nCreating ~/.patman...\n")
|
||||
CreatePatmanConfigFile(config_fname)
|
||||
|
||||
for name, value in GetItems(config, 'alias'):
|
||||
alias[name] = value.split(',')
|
||||
@ -358,6 +384,7 @@ def Setup(gitutil, parser, project_name, config_fname=''):
|
||||
|
||||
_UpdateDefaults(parser, config)
|
||||
|
||||
|
||||
# These are the aliases we understand, indexed by alias. Each member is a list.
|
||||
alias = {}
|
||||
bounces = set()
|
||||
|
@ -1,12 +1,12 @@
|
||||
# SPDX-License-Identifier: GPL-2.0+
|
||||
|
||||
from distutils.core import setup
|
||||
from setuptools import setup
|
||||
setup(name='patman',
|
||||
version='1.0',
|
||||
license='GPL-2.0+',
|
||||
scripts=['patman'],
|
||||
packages=['patman'],
|
||||
package_dir={'patman': ''},
|
||||
package_data={'patman': ['README']},
|
||||
package_data={'patman': ['README.rst']},
|
||||
classifiers=['Environment :: Console',
|
||||
'Topic :: Software Development'])
|
||||
|
67
tools/patman/test_settings.py
Normal file
67
tools/patman/test_settings.py
Normal file
@ -0,0 +1,67 @@
|
||||
# SPDX-License-Identifier: GPL-2.0+
|
||||
#
|
||||
# Copyright (c) 2022 Maxim Cournoyer <maxim.cournoyer@savoirfairelinux.com>
|
||||
#
|
||||
|
||||
import argparse
|
||||
import contextlib
|
||||
import os
|
||||
import sys
|
||||
import tempfile
|
||||
|
||||
from patman import settings
|
||||
from patman import tools
|
||||
|
||||
|
||||
@contextlib.contextmanager
|
||||
def empty_git_repository():
|
||||
with tempfile.TemporaryDirectory() as tmpdir:
|
||||
os.chdir(tmpdir)
|
||||
tools.run('git', 'init', raise_on_error=True)
|
||||
yield tmpdir
|
||||
|
||||
|
||||
@contextlib.contextmanager
|
||||
def cleared_command_line_args():
|
||||
old_value = sys.argv[:]
|
||||
sys.argv = [sys.argv[0]]
|
||||
try:
|
||||
yield
|
||||
finally:
|
||||
sys.argv = old_value
|
||||
|
||||
|
||||
def test_git_local_config():
|
||||
# Clearing the command line arguments is required, otherwise
|
||||
# arguments passed to the test running such as in 'pytest -k
|
||||
# filter' would be processed by _UpdateDefaults and fail.
|
||||
with cleared_command_line_args():
|
||||
with empty_git_repository():
|
||||
with tempfile.NamedTemporaryFile() as global_config:
|
||||
global_config.write(b'[settings]\n'
|
||||
b'project=u-boot\n')
|
||||
global_config.flush()
|
||||
parser = argparse.ArgumentParser()
|
||||
parser.add_argument('-p', '--project', default='unknown')
|
||||
subparsers = parser.add_subparsers(dest='cmd')
|
||||
send = subparsers.add_parser('send')
|
||||
send.add_argument('--no-check', action='store_false',
|
||||
dest='check_patch', default=True)
|
||||
|
||||
# Test "global" config is used.
|
||||
settings.Setup(parser, 'unknown', global_config.name)
|
||||
args, _ = parser.parse_known_args([])
|
||||
assert args.project == 'u-boot'
|
||||
send_args, _ = send.parse_known_args([])
|
||||
assert send_args.check_patch
|
||||
|
||||
# Test local config can shadow it.
|
||||
with open('.patman', 'w', buffering=1) as f:
|
||||
f.write('[settings]\n'
|
||||
'project: guix-patches\n'
|
||||
'check_patch: False\n')
|
||||
settings.Setup(parser, 'unknown', global_config.name)
|
||||
args, _ = parser.parse_known_args([])
|
||||
assert args.project == 'guix-patches'
|
||||
send_args, _ = send.parse_known_args([])
|
||||
assert not send_args.check_patch
|
Loading…
Reference in New Issue
Block a user