Subject: [PATCH v6 2/2] gnu: Add patman.

From: Maxim Cournoyer <[email-masked]>
Date: Tue, 20 Dec 2022 08:21:07 -0500

* gnu/packages/bootloaders.scm (patman): New variable.
* gnu/packages/patches/u-boot-patman-fix-help.patch: New patch.
* gnu/packages/patches/u-boot-patman-get-maintainer.patch: Likewise.
* gnu/packages/patches/u-boot-patman-local-conf.patch: Likewise.
* gnu/local.mk (dist_patch_DATA): Register them.

---

Changes in v6:
- Update u-boot-patman-get-maintainer.patch.

Changes in v5:
- Add new u-boot-patman-get-maintainer.patch.

Changes in v4:
- Use the series URL for the patches upstream submission status
- Add new u-boot-patman-local-conf.patch

 gnu/local.mk                                  |   3 +
 gnu/packages/bootloaders.scm                  |  36 +++-
 .../patches/u-boot-patman-fix-help.patch      |  40 ++++
 .../u-boot-patman-get-maintainer.patch        | 104 +++++++++++
 .../patches/u-boot-patman-local-conf.patch    | 176 ++++++++++++++++++
 5 files changed, 358 insertions(+), 1 deletion(-)
 create mode 100644 gnu/packages/patches/u-boot-patman-fix-help.patch
 create mode 100644 gnu/packages/patches/u-boot-patman-get-maintainer.patch
 create mode 100644 gnu/packages/patches/u-boot-patman-local-conf.patch

diff --git a/gnu/local.mk b/gnu/local.mk
index 01fcc9d7d3..a704cf6002 100644
--- a/gnu/local.mk
+++ b/gnu/local.mk
@@ -1925,6 +1925,9 @@ dist_patch_DATA =						\
   %D%/packages/patches/twinkle-bcg729.patch			\
   %D%/packages/patches/u-boot-allow-disabling-openssl.patch	\
   %D%/packages/patches/u-boot-infodocs-target.patch		\
+  %D%/packages/patches/u-boot-patman-fix-help.patch		\
+  %D%/packages/patches/u-boot-patman-get-maintainer.patch	\
+  %D%/packages/patches/u-boot-patman-local-conf.patch		\
   %D%/packages/patches/u-boot-nintendo-nes-serial.patch		\
   %D%/packages/patches/u-boot-rockchip-inno-usb.patch		\
   %D%/packages/patches/u-boot-sifive-prevent-reloc-initrd-fdt.patch	\
diff --git a/gnu/packages/bootloaders.scm b/gnu/packages/bootloaders.scm
index 32970f86f1..4cfb16860e 100644
--- a/gnu/packages/bootloaders.scm
+++ b/gnu/packages/bootloaders.scm
@@ -69,7 +69,10 @@ (define-module (gnu packages bootloaders)
   #:use-module (gnu packages valgrind)
   #:use-module (gnu packages virtualization)
   #:use-module (gnu packages xorg)
+  #:use-module (gnu packages python-web)
+  #:use-module (gnu packages python-xyz)
   #:use-module (guix build-system gnu)
+  #:use-module (guix build-system pyproject)
   #:use-module (guix build-system trivial)
   #:use-module (guix download)
   #:use-module (guix gexp)
@@ -640,7 +643,10 @@ (define u-boot
                      %u-boot-allow-disabling-openssl-patch
                      %u-boot-sifive-prevent-relocating-initrd-fdt
                      %u-boot-rk3399-enable-emmc-phy-patch
-                     (search-patch "u-boot-infodocs-target.patch")))
+                     (search-patch "u-boot-infodocs-target.patch")
+                     (search-patch "u-boot-patman-fix-help.patch")
+                     (search-patch "u-boot-patman-local-conf.patch")
+                     (search-patch "u-boot-patman-get-maintainer.patch")))
               (method url-fetch)
               (uri (string-append
                     "https://ftp.denx.de/pub/u-boot/"
@@ -816,6 +822,34 @@ (define-public u-boot-tools
                   "  This package provides board-independent tools "
                   "of U-Boot."))))
 
+;;; This is packaged separately, as it can be used in other contexts than for
+;;; U-Boot development.
+(define-public patman
+  (package
+    (inherit u-boot)
+    (name "patman")
+    (build-system pyproject-build-system)
+    (arguments
+     ;; The test suite strongly relies on the git metadata being available (23
+     ;; failed, 14 passed).
+     (list
+      #:tests? #f
+      #:phases
+      #~(modify-phases %standard-phases
+          (add-after 'unpack 'chdir
+            (lambda _
+              (chdir "tools/patman"))))))
+    (inputs (list python-pygit2 python-requests))
+    (synopsis "Patch automation tool")
+    (description "Patman is a patch automation script which:
+@itemize
+@item Creates patches directly from your branch
+@item Cleans them up by removing unwanted tags
+@item Inserts a cover letter with change lists
+@item Runs the patches through automated checks
+@item Optionally emails them out to selected people.
+@end itemize")))
+
 (define*-public (make-u-boot-package board triplet
                                      #:key
                                      defconfig
diff --git a/gnu/packages/patches/u-boot-patman-fix-help.patch b/gnu/packages/patches/u-boot-patman-fix-help.patch
new file mode 100644
index 0000000000..89bac06c2f
--- /dev/null
+++ b/gnu/packages/patches/u-boot-patman-fix-help.patch
@@ -0,0 +1,40 @@
+Upstream status: https://patchwork.ozlabs.org/project/uboot/list/?series=333156
+
+diff --git a/tools/patman/main.py b/tools/patman/main.py
+index 5a7756a221..bf300c6e64 100755
+--- a/tools/patman/main.py
++++ b/tools/patman/main.py
+@@ -7,6 +7,7 @@
+ """See README for more information"""
+ 
+ from argparse import ArgumentParser
++import importlib.resources
+ import os
+ import re
+ import shutil
+@@ -163,11 +164,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:
+diff --git a/tools/patman/setup.py b/tools/patman/setup.py
+index 43fdc00ce6..ce9bb4aa63 100644
+--- a/tools/patman/setup.py
++++ b/tools/patman/setup.py
+@@ -7,6 +7,6 @@ setup(name='patman',
+       scripts=['patman'],
+       packages=['patman'],
+       package_dir={'patman': ''},
+-      package_data={'patman': ['README']},
++      package_data={'patman': ['README.rst']},
+       classifiers=['Environment :: Console',
+                    'Topic :: Software Development'])
diff --git a/gnu/packages/patches/u-boot-patman-get-maintainer.patch b/gnu/packages/patches/u-boot-patman-get-maintainer.patch
new file mode 100644
index 0000000000..4377f8394e
--- /dev/null
+++ b/gnu/packages/patches/u-boot-patman-get-maintainer.patch
@@ -0,0 +1,104 @@
+Upstream status: https://patchwork.ozlabs.org/project/uboot/list/?series=333427
+
+diff --git a/tools/patman/patman.rst b/tools/patman/patman.rst
+index 7828899879..95b6c9c3f0 100644
+--- a/tools/patman/patman.rst
++++ b/tools/patman/patman.rst
+@@ -88,7 +88,7 @@ To add your own, create a file `~/.patman` like this::
+ 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
++project-specific way. The values of this "local" configuration file
+ take precedence over those of the "global" one.
+ 
+ Aliases are recursive.
+diff --git a/tools/patman/test_settings.py b/tools/patman/test_settings.py
+index 9c14b4aaa3..c768a2fc64 100644
+--- a/tools/patman/test_settings.py
++++ b/tools/patman/test_settings.py
+@@ -6,38 +6,62 @@
+ import argparse
+ import contextlib
+ import os
+-import subprocess
++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)
+-        subprocess.check_call(['git', 'init'])
++        tools.run('git', 'init', raise_on_error=True)
+         yield tmpdir
+ 
+ 
[email-masked]
++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():
+-    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')
+-
+-            # Test "global" config is used.
+-            settings.Setup(parser, 'unknown', global_config.name)
+-            args, _ = parser.parse_known_args()
+-            assert args.project == 'u-boot'
+-
+-            # Test local config can shadow it.
+-            with open('.patman', 'w', buffering=1) as f:
+-                f.write('[settings]\n'
+-                        'project=guix-patches\n')
+-            settings.Setup(parser, 'unknown', global_config.name)
+-            args, _ = parser.parse_known_args([])
+-            assert args.project == 'guix-patches'
++    # 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
diff --git a/gnu/packages/patches/u-boot-patman-local-conf.patch b/gnu/packages/patches/u-boot-patman-local-conf.patch
new file mode 100644
index 0000000000..3400982356
--- /dev/null
+++ b/gnu/packages/patches/u-boot-patman-local-conf.patch
@@ -0,0 +1,176 @@
+Upstream status: https://patchwork.ozlabs.org/project/uboot/list/?series=333354
+
+diff --git a/tools/patman/main.py b/tools/patman/main.py
+index bf300c6e64..3616b28f27 100755
+--- a/tools/patman/main.py
++++ b/tools/patman/main.py
+@@ -116,7 +116,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
+diff --git a/tools/patman/patman.rst b/tools/patman/patman.rst
+index 8c5c9cc2cc..7828899879 100644
+--- a/tools/patman/patman.rst
++++ b/tools/patman/patman.rst
+@@ -74,7 +74,7 @@ out where to send patches pretty well.
+ 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 +85,12 @@ To add your own, create a file ~/.patman like this::
+     wolfgang: Wolfgang Denk <[email-masked]>
+     others: Mike Frysinger <[email-masked]>, Fred Bloggs <[email-masked]>
+ 
++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
+diff --git a/tools/patman/settings.py b/tools/patman/settings.py
+index 903d6fcb0b..e8e2908f1f 100644
+--- a/tools/patman/settings.py
++++ b/tools/patman/settings.py
+@@ -1,5 +1,6 @@
+ # SPDX-License-Identifier: GPL-2.0+
+ # Copyright (c) 2011 The Chromium OS Authors.
++# Copyright (c) 2022 Maxim Cournoyer <[email-masked]>
+ #
+ 
+ try:
+@@ -11,8 +12,7 @@ import argparse
+ import os
+ import re
+ 
+-from patman import command
+-from patman import tools
++from patman import gitutil
+ 
+ """Default settings per-project.
+ 
+@@ -190,7 +190,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:
+@@ -328,26 +329,46 @@ def GetItems(config, section):
+     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)
++
++    git_local_config_fname = os.path.join(gitutil.get_top_level(), '.patman')
++    has_git_local_config = os.path.exists(git_local_config_fname)
+ 
+-    if not os.path.exists(config_fname):
+-        print("No config file found ~/.patman\nCreating one...\n")
+-        CreatePatmanConfigFile(gitutil, 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)
+ 
+-    config.read(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(',')
+diff --git a/tools/patman/test_settings.py b/tools/patman/test_settings.py
+new file mode 100644
+index 0000000000..9c14b4aaa3
+--- /dev/null
++++ b/tools/patman/test_settings.py
+@@ -0,0 +1,43 @@
++# SPDX-License-Identifier: GPL-2.0+
++#
++# Copyright (c) 2022 Maxim Cournoyer <[email-masked]>
++#
++
++import argparse
++import contextlib
++import os
++import subprocess
++import tempfile
++
++from patman import settings
++
++
[email-masked]
++def empty_git_repository():
++    with tempfile.TemporaryDirectory() as tmpdir:
++        os.chdir(tmpdir)
++        subprocess.check_call(['git', 'init'])
++        yield tmpdir
++
++
++def test_git_local_config():
++    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')
++
++            # Test "global" config is used.
++            settings.Setup(parser, 'unknown', global_config.name)
++            args, _ = parser.parse_known_args()
++            assert args.project == 'u-boot'
++
++            # Test local config can shadow it.
++            with open('.patman', 'w', buffering=1) as f:
++                f.write('[settings]\n'
++                        'project=guix-patches\n')
++            settings.Setup(parser, 'unknown', global_config.name)
++            args, _ = parser.parse_known_args([])
++            assert args.project == 'guix-patches'
-- 
2.38.1