# Our modules
from patman import control
from patman import func_test
+from patman import gitutil
from patman import project
from patman import settings
from patman import terminal
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',
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
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
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 = ''
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):
"""Functional tests for checking that patman behaves correctly"""
+import contextlib
import os
import pathlib
import re
import pygit2
from patman import status
+PATMAN_DIR = pathlib.Path(__file__).parent
+TEST_DATA_DIR = PATMAN_DIR / 'test/'
-TEST_DATA_DIR = pathlib.Path(__file__).parent / '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):
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'):
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)
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
# 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
+
+ git_relative_script = os.path.join(gitutil.get_top_level(),
+ script_file_name)
+ if os.path.exists(git_relative_script):
+ return git_relative_script
- return None
-def get_maintainer(dir_list, fname, verbose=False):
- """Run get_maintainer.pl on a file if we find it.
+def get_maintainer(script_file_name, fname, verbose=False):
+ """Run `script_file_name` on a file.
- 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.
+ `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]
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):
+ smtp_server=None, get_maintainer_script=None):
"""Email a patch series.
Args:
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
.. 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
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.
wolfgang: Wolfgang Denk <wd@denx.de>
others: Mike Frysinger <vapier@gentoo.org>, Fred Bloggs <f.bloggs@napier.net>
-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.
+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.
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.
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
"""
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))