aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorThomas Gleixner <tglx@linutronix.de>2019-08-26 20:10:59 +0200
committerThomas Gleixner <tglx@linutronix.de>2019-08-26 20:10:59 +0200
commitf17a0c77ca27298641f4c5d2ca31906d61096df5 (patch)
tree8e210a38bedbbb6c507c9d7c4441b621a827c7ca
downloadquilttools-f17a0c77ca27298641f4c5d2ca31906d61096df5.tar.gz
quilttools: Initial import
Signed-off-by: Thomas Gleixner <tglx@linutronix.de>
-rw-r--r--.gitignore24
-rw-r--r--COPYING14
-rw-r--r--Documentation/Makefile19
-rw-r--r--Documentation/conf.py158
-rw-r--r--Documentation/examples/.mb2q.yaml33
-rw-r--r--Documentation/index.rst56
-rw-r--r--Documentation/installation.rst14
-rw-r--r--Documentation/introduction.rst8
-rw-r--r--Documentation/license-rules.rst159
-rw-r--r--Documentation/man1/mb2q.rst166
-rw-r--r--Documentation/sphinx_static/.gitignore0
-rw-r--r--LICENSES/GPL-2.0266
-rw-r--r--README.md11
-rwxr-xr-xmb2q660
14 files changed, 1588 insertions, 0 deletions
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..855ae2b
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,24 @@
+# Normal rules
+*.bz2
+*.diff
+*.gz
+*.patch
+*.orig
+*.rej
+*.tar
+*.xz
+
+# Editor artifacts
+*~
+\#*#
+
+# Quilt's files
+patches
+series
+.pc/
+
+# Python artifacts
+*.pyc
+
+# Sphinx
+Documentation/output/
diff --git a/COPYING b/COPYING
new file mode 100644
index 0000000..9b648d9
--- /dev/null
+++ b/COPYING
@@ -0,0 +1,14 @@
+mb2q is provided under:
+
+ SPDX-License-Identifier: GPL-2.0-only
+
+Being under the terms of the GNU General Public License version 2 only,
+according with:
+
+ LICENSES/GPL-2.0
+
+In addition, other licenses may also apply. Please see:
+
+ Documentation/license-rules.rst
+
+for more details.
diff --git a/Documentation/Makefile b/Documentation/Makefile
new file mode 100644
index 0000000..422a271
--- /dev/null
+++ b/Documentation/Makefile
@@ -0,0 +1,19 @@
+# Minimal makefile for Sphinx documentation generated by sphinx
+
+# You can set these variables from the command line.
+SPHINXOPTS =
+SPHINXBUILD = sphinx-build
+SPHINXPROJ = quilttools
+SOURCEDIR = .
+BUILDDIR = output
+
+# Put it first so that "make" without argument is like "make help".
+help:
+ @$(SPHINXBUILD) -M help "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O)
+
+.PHONY: help Makefile
+
+# Catch-all target: route all unknown targets to Sphinx using the new
+# "make mode" option. $(O) is meant as a shortcut for $(SPHINXOPTS).
+%: Makefile
+ @$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O)
diff --git a/Documentation/conf.py b/Documentation/conf.py
new file mode 100644
index 0000000..962ab51
--- /dev/null
+++ b/Documentation/conf.py
@@ -0,0 +1,158 @@
+# -*- coding: utf-8 -*-
+#
+# quilttools documentation build configuration file, created by
+# sphinx-quickstart on Tue May 15 16:12:59 2018.
+#
+# This file is execfile()d with the current directory set to its
+# containing dir.
+#
+# Note that not all possible configuration values are present in this
+# autogenerated file.
+#
+# All configuration values have a default; values that are commented out
+# serve to show the default.
+
+# If extensions (or modules to document with autodoc) are in another directory,
+# add these directories to sys.path here. If the directory is relative to the
+# documentation root, use os.path.abspath to make it absolute, like shown here.
+#
+# import os
+# import sys
+# sys.path.insert(0, os.path.abspath('.'))
+import glob
+
+
+# -- General configuration ------------------------------------------------
+
+# If your documentation needs a minimal Sphinx version, state it here.
+#
+# needs_sphinx = '1.0'
+
+# Add any Sphinx extension module names here, as strings. They can be
+# extensions coming with Sphinx (named 'sphinx.ext.*') or your custom
+# ones.
+extensions = []
+
+# Add any paths that contain templates here, relative to this directory.
+templates_path = ['sphinx_templates']
+
+# The suffix(es) of source filenames.
+# You can specify multiple suffix as a list of string:
+#
+# source_suffix = ['.rst', '.md']
+source_suffix = '.rst'
+
+# The master toctree document.
+master_doc = 'index'
+
+# General information about the project.
+project = u'quilttools'
+copyright = u'2019, Thomas Gleixner <tglx@linutronix.de>'
+author = u'Thomas Gleixner <tglx@linutronix.de>'
+
+# The version info for the project you're documenting, acts as replacement for
+# |version| and |release|, also used in various other places throughout the
+# built documents.
+#
+# The short X.Y version.
+version = u''
+# The full version, including alpha/beta/rc tags.
+release = u''
+
+# The language for content autogenerated by Sphinx. Refer to documentation
+# for a list of supported languages.
+#
+# This is also used if you do content translation via gettext catalogs.
+# Usually you set "language" from the command line for these cases.
+language = None
+
+# List of patterns, relative to source directory, that match files and
+# directories to ignore when looking for source files.
+# This patterns also effect to html_static_path and html_extra_path
+exclude_patterns = ['_build', 'Thumbs.db', '.DS_Store']
+
+# The name of the Pygments (syntax highlighting) style to use.
+pygments_style = 'sphinx'
+
+# If true, `todo` and `todoList` produce output, else they produce nothing.
+todo_include_todos = False
+
+
+# -- Options for HTML output ----------------------------------------------
+
+# The theme to use for HTML and HTML Help pages. See the documentation for
+# a list of builtin themes.
+#
+html_theme = 'nature'
+
+# Theme options are theme-specific and customize the look and feel of a theme
+# further. For a list of options available for each theme, see the
+# documentation.
+#
+# html_theme_options = {}
+
+# Add any paths that contain custom static files (such as style sheets) here,
+# relative to this directory. They are copied after the builtin static files,
+# so a file named "default.css" will overwrite the builtin "default.css".
+html_static_path = ['sphinx_static']
+
+# -- Options for HTMLHelp output ------------------------------------------
+
+# Output file base name for HTML help builder.
+htmlhelp_basename = 'quilttoolsdoc'
+
+
+# -- Options for LaTeX output ---------------------------------------------
+
+latex_elements = {
+ # The paper size ('letterpaper' or 'a4paper').
+ #
+ # 'papersize': 'letterpaper',
+
+ # The font size ('10pt', '11pt' or '12pt').
+ #
+ # 'pointsize': '10pt',
+
+ # Additional stuff for the LaTeX preamble.
+ #
+ # 'preamble': '',
+
+ # Latex figure (float) alignment
+ #
+ # 'figure_align': 'htbp',
+}
+
+# Grouping the document tree into LaTeX files. List of tuples
+# (source start file, target name, title,
+# author, documentclass [howto, manual, or own class]).
+latex_documents = [
+ (master_doc, 'quilttools.tex', u'quilttools Documentation',
+ u'Thomas Gleixner \\textless{}tglx@linutronix.de\\textgreater{}', 'manual'),
+]
+
+
+# -- Options for manual page output ---------------------------------------
+
+# One entry per manual page. List of tuples
+# (source start file, name, description, authors, manual section).
+man_pages = [
+ # (master_doc, 'quilttools', u'quilttools Documentation',
+ # [author], 1),
+]
+
+for f in glob.glob('man1/*.rst'):
+ src = f.replace('.rst', '')
+ dst = src.split('/')[1]
+ man_pages.append((src, dst, u'%s Documentation' % dst, [author], 1))
+
+
+# -- Options for Texinfo output -------------------------------------------
+
+# Grouping the document tree into Texinfo files. List of tuples
+# (source start file, target name, title, author,
+# dir menu entry, description, category)
+texinfo_documents = [
+ (master_doc, 'quilttools', u'quilttools Documentation',
+ author, 'quilttools', 'Tools for quilt.',
+ 'Miscellaneous'),
+]
diff --git a/Documentation/examples/.mb2q.yaml b/Documentation/examples/.mb2q.yaml
new file mode 100644
index 0000000..abac225
--- /dev/null
+++ b/Documentation/examples/.mb2q.yaml
@@ -0,0 +1,33 @@
+# mb2q configuration
+
+# The committer used for adding the SOB
+# If omitted it's retrieved from GIT or environment
+committer: Thomas Gleixner <tglx@linutronix.de>
+
+# List of mail addresses to drop from the CC list
+nocc_addrs:
+ - tglx@linutronix.de
+ - tglx@tglx.de
+
+# List of mailing list addresses for which the
+# Link: tag is generated
+list_addrs:
+ - linux-kernel@vger.kernel.org
+
+# List of 'From:' addresses which indicate that the message
+# in the mailbox is a mail client internal management mail
+# The example below is the magic mail alpine puts into mboxes
+# Can be abused to filter other things as well
+drop_from:
+ - '<MAILER-DAEMON@'
+
+# The Link: base address to which the message ID is appended
+link_base: https://lkml.kernel.org/r/
+
+# Drop Cc tags from the changelog. They are pointless
+# when a link is available. Default is True. Set to False
+# to keep them.
+dropcc: True
+
+# Emit committer SOB before Cc and Link tags.
+sob_before_cc: True
diff --git a/Documentation/index.rst b/Documentation/index.rst
new file mode 100644
index 0000000..b6fa628
--- /dev/null
+++ b/Documentation/index.rst
@@ -0,0 +1,56 @@
+.. SPDX-License-Identifier: GPL-2.0
+.. quilttools documentation master file.
+
+Welcome to quilttools's documentation!
+======================================
+
+.. toctree::
+ :maxdepth: 2
+ :caption: Contents:
+
+Introduction
+------------
+
+.. toctree::
+ :maxdepth: 2
+
+ introduction.rst
+
+
+Licensing
+---------
+
+Describes the licensing rules for the quilttools source code.
+
+.. toctree::
+ :maxdepth: 2
+
+ license-rules.rst
+
+
+Installation
+------------
+
+Installation guidelines
+
+.. toctree::
+ :maxdepth: 2
+
+ installation.rst
+
+
+Usage
+-----
+
+Usage guidelines
+
+.. toctree::
+ :maxdepth: 2
+
+ man1/mb2q.rst
+
+
+Indices and tables
+==================
+
+* :ref:`genindex`
diff --git a/Documentation/installation.rst b/Documentation/installation.rst
new file mode 100644
index 0000000..8d5746d
--- /dev/null
+++ b/Documentation/installation.rst
@@ -0,0 +1,14 @@
+.. SPDX-License-Identifier: GPL-2.0
+
+quilttools installation guide
+=============================
+
+General
+-------
+
+The recommended installation procedure is via the package management system
+of your distribution.
+
+Manual installation is trivial as the tools are self contained and do not use
+libraries.
+
diff --git a/Documentation/introduction.rst b/Documentation/introduction.rst
new file mode 100644
index 0000000..c4e1a2b
--- /dev/null
+++ b/Documentation/introduction.rst
@@ -0,0 +1,8 @@
+.. SPDX-License-Identifier: GPL-2.0
+
+Introduction
+============
+
+quilttools is a collection of python scripts to help with quilt based
+workflows.
+
diff --git a/Documentation/license-rules.rst b/Documentation/license-rules.rst
new file mode 100644
index 0000000..33735a8
--- /dev/null
+++ b/Documentation/license-rules.rst
@@ -0,0 +1,159 @@
+.. SPDX-License-Identifier: GPL-2.0
+
+quilttools licensing rules
+==========================
+
+The quilttools project is provided under the terms of the GNU General Public
+License version 2 only (GPL-2.0-only), as provided in LICENSES/GPL-2.0.
+
+This documentation file provides a description of how each source file
+should be annotated to make its license clear and unambiguous.
+It doesn't replace the projects license.
+
+The license described in the COPYING file applies to the project source
+as a whole, though individual source files can have a different license
+which is required to be compatible with the GPL-2.0-only::
+
+ GPL-2.0-or-later : GNU General Public License v2.0 or later
+
+Aside from that, individual files can be provided under a dual license,
+e.g. one of the compatible GPL variants and alternatively under a
+permissive license like BSD, MIT etc.
+
+The common way of expressing the license of a source file is to add the
+matching boilerplate text into the top comment of the file. Due to
+formatting, typos etc. these "boilerplates" are hard to validate for
+tools which are used in the context of license compliance.
+
+An alternative to boilerplate text is the use of Software Package Data
+Exchange (SPDX) license identifiers in each source file. SPDX license
+identifiers are machine parsable and precise shorthands for the license
+under which the content of the file is contributed. SPDX license
+identifiers are managed by the SPDX Workgroup at the Linux Foundation and
+have been agreed on by partners throughout the industry, tool vendors, and
+legal teams. For further information see https://spdx.org/
+
+The quilttools prokect requires the precise SPDX identifier in all source
+files. The valid identifiers used in the quilttools project are explained in
+the section `License identifiers`_ and have been retrieved from the
+official SPDX license list at https://spdx.org/licenses/ along with the
+license texts.
+
+License identifier syntax
+-------------------------
+
+1. Placement:
+
+ The SPDX license identifier in source files shall be added at the first
+ possible line in a file which can contain a comment. For the majority
+ or files this is the first line, except for executable scripts which
+ require the '#!PATH_TO_INTERPRETER' in the first line. For those
+ scripts the SPDX identifier goes into the second line.
+
+|
+
+2. Style:
+
+ The SPDX license identifier is added in form of a comment. The comment
+ style depends on the file type::
+
+ scripts: # SPDX-License-Identifier: <SPDX License Expression>
+ .py: # SPDX-License-Identifier: <SPDX License Expression>
+ .rst: .. SPDX-License-Identifier: <SPDX License Expression>
+
+|
+
+3. Syntax:
+
+ A <SPDX License Expression> is either an SPDX short form license
+ identifier found on the SPDX License List. When multiple licenses apply,
+ an expression consists of keywords "AND", "OR" separating
+ sub-expressions and surrounded by "(", ")" .
+
+ License identifiers for licenses like [L]GPL with the 'or later' option
+ are constructed by using a "-or-later" for indicating the 'or later'
+ option.::
+
+ # SPDX-License-Identifier: GPL-2.0-or-later
+
+ OR should be used if the file is dual licensed and only one license is
+ to be selected. For example::
+
+ # SPDX-License-Identifier: GPL-2.0-only OR BSD-3-Clause
+
+ AND should be used if the file has multiple licenses whose terms all
+ apply to use the file. For example, if code is inherited from another
+ project and permission has been given to put it in the quilttools project,
+ but the original license terms need to remain in effect::
+
+ # SPDX-License-Identifier: GPL-2.0-only AND MIT
+
+
+License identifiers
+-------------------
+
+The licenses currently used, as well as the licenses for code added to the
+project can be found in the LICENSES directory.
+
+Whenever possible these licenses should be used as they are known to be
+fully compatible and widely used.
+
+The files in this directory contain the full license text and metatags.
+The file names are identical to the SPDX license identifier which shall be
+used for the license in source files.
+
+Examples::
+
+ LICENSES/GPL-2.0
+
+Contains the GPL version 2 license text and the required metatags:
+
+Metatags:
+
+ The following meta tags must be available in a license file:
+
+ - Valid-License-Identifier:
+
+ One or more lines which declare which License Identifiers are valid
+ inside the project to reference this particular license text. Usually
+ this is a single valid identifier, but e.g. for licenses with the 'or
+ later' options two identifiers are valid.
+
+ - SPDX-URL:
+
+ The URL of the SPDX page which contains additional information related
+ to the license.
+
+ - Usage-Guidance:
+
+ Freeform text for usage advice. The text must include correct examples
+ for the SPDX license identifiers as they should be put into source
+ files according to the `License identifier syntax`_ guidelines.
+
+ - License-Text:
+
+ All text after this tag is treated as the original license text
+
+ File format examples::
+
+ Valid-License-Identifier: GPL-2.0-only
+ Valid-License-Identifier: GPL-2.0-or-later
+ SPDX-URL: https://spdx.org/licenses/GPL-2.0-only.html
+ Usage-Guide:
+ To use this license in source code, put one of the following SPDX
+ tag/value pairs into a comment according to the placement
+ guidelines in the licensing rules documentation.
+ For 'GNU General Public License (GPL) version 2 only' use:
+ SPDX-License-Identifier: GPL-2.0-only
+ For 'GNU General Public License (GPL) version 2 or any later version' use:
+ SPDX-License-Identifier: GPL-2.0-or-later
+ License-Text:
+ Full license text
+
+|
+
+All SPDX license identifiers must have a corresponding file in the LICENSE
+subdirectory. This is required to allow tool verification and to have the
+licenses ready to read and extract right from the source, which is
+recommended by various FOSS organizations, e.g. the `FSFE REUSE initiative
+<https://reuse.software/>`_.
diff --git a/Documentation/man1/mb2q.rst b/Documentation/man1/mb2q.rst
new file mode 100644
index 0000000..5fc5a29
--- /dev/null
+++ b/Documentation/man1/mb2q.rst
@@ -0,0 +1,166 @@
+.. SPDX-License-Identifier: GPL-2.0
+
+mb2q manual page
+================
+
+Synopsis
+--------
+
+**mb2q** [*options*] inbox
+
+Description
+-----------
+
+:program:`mb2q`, A program to create a quilt patch series from a mailbox
+
+mb2q analyses the complete mailbox and collects various tags (Reviewed-by,
+Acked-by, Tested-by) from replies to individual patches and to the cover
+letter. If a tag is in a reply to the cover letter it is applied to all
+patches which reference the cover letter.
+
+Optionally the output can be redirected into a mailbox which can be applied
+with git-am.
+
+Options
+-------
+
+positional arguments:
+ inbox Input mailbox file
+
+optional arguments:
+
+ -h, --help show this help message and exit
+
+ -c CONFIG, --config CONFIG
+ Config file. Default: ~/.mb2q.yaml
+
+ -F, --fromnodup Do not duplicate From into the body
+ By default the From: header is copied
+ into the mail body (changelog) which is
+ is convenient if patches which have been
+ collected from a mailing list are resent.
+
+ A From: tag which is already in the mail
+ body is preserved and not replaced.
+
+ -m MBOXOUT, --mboxout MBOXOUT
+ Do not store the result in a quilt series. Generate
+ a mailbox instead which can be applied with git-am
+
+ -n, --nolink Do not add Link:// tags to patches
+
+
+ -N, --noid Do not require Message ID. Implies --nolink.
+ For testing and in really bad cases.
+
+ -p PATCHESDIR, --patchesdir PATCHESDIR
+ Use given directory and do not automatically create
+ patches-$inbox/
+
+ -a ACKEDBY, --ackedby ACKEDBY
+ Add Acked-by to all patches. Comma separated list.
+ "Joe Devel <joe@dev.el>, Dev Joe <dev@joe.org>"
+
+ -r REVIEWEDBY, --reviewedby REVIEWEDBY
+ Add Reviewed-by to all patches. Comma separated list.
+
+ -t TESTEDBY, --testedby TESTEDBY
+ Add Tested-by to all patches. Comma separated list
+
+ -C, --CollectCC Collect all CCs from mail header
+
+
+Configuration file
+------------------
+
+The program has a default configuration built in which can be reconfigured
+with a configuration file. The default location is ~/.mb2q.yaml. This can
+be overridden by the '-c' command line argument.
+
+Configuration options
+---------------------
+
+ .. code-block:: yaml
+
+ committer: Surname Name <name@email.addr>
+
+ nocc_addrs:
+ - nocc@email.addr
+ - another.nocc@email.addr
+
+ list_addrs:
+ - linux-kernel@vger.kernel.org
+ - another-list@listserver.org
+
+ drop_from:
+ - '<MAILER-DAEMON@'
+
+ link_base: https://lkml.kernel.org/r/
+
+ dropcc: True
+
+ sob_before_cc: True
+
+committer:
+^^^^^^^^^^
+
+ Set the committer name/email for the Signed-off-by tag. If the entry is
+ not in the configuration file mb2q tries to find the committer via git or
+ the relevant git enviroment variables.
+
+nocc_addrs:
+^^^^^^^^^^^
+
+ Mail addresses which are always dropped from the Cc list of a mail
+ independent of the dropcc config option and the '-C' command line
+ parameter
+
+list_addrs:
+^^^^^^^^^^^
+
+ A list of mailing list mail addresses which if matches are:
+
+ - a indicator to append a Link tag based on the link_base configuration
+ entry and the message id of the patch mail
+
+ - removed from the cc list independent of the dropcc config option and
+ the '-C' command line parameter
+
+drop_from:
+^^^^^^^^^^
+
+ A list of email addresses which cause a mail in the mailbox to be ignored.
+ That's useful if the email client which is used to store the mailbox inserts
+ an administrative email at the beginning of the mailbox. The above example
+ catches that mail inserted by alpine.
+
+link_base:
+^^^^^^^^^^
+
+ The base URL for creating Link: tags in the changelog. The Message-ID of
+ the patch mail is appended to the base URL.
+
+dropccs:
+^^^^^^^^
+
+ If True which is also the built-in default all Cc's are stripped from the
+ changelog. If False the Cc's in the changelog of the patch mail are
+ preserved. The default is True because having all that Cc noise in the
+ change log is pointless when the original mail can be retrieved via the
+ Link tag.
+
+sob_before_cc:
+^^^^^^^^^^^^^^
+
+ It true mb2q emits the committer SOB before Cc and Link tags. Otherwise at
+ after all tags.
+
+
+See also
+--------
+
+License
+-------
+Gnu Public License version 2
+
+
diff --git a/Documentation/sphinx_static/.gitignore b/Documentation/sphinx_static/.gitignore
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/Documentation/sphinx_static/.gitignore
diff --git a/LICENSES/GPL-2.0 b/LICENSES/GPL-2.0
new file mode 100644
index 0000000..0a29508
--- /dev/null
+++ b/LICENSES/GPL-2.0
@@ -0,0 +1,266 @@
+Valid-License-Identifier: GPL-2.0-only
+Valid-License-Identifier: GPL-2.0-or-later
+SPDX-URL: https://spdx.org/licenses/GPL-2.0-only.html
+Usage-Guide:
+ To use this license in source code, put one of the following SPDX
+ tag/value pairs into a comment according to the placement
+ guidelines in the licensing rules documentation.
+ For 'GNU General Public License (GPL) version 2 only' use:
+ SPDX-License-Identifier: GPL-2.0-only
+ For 'GNU General Public License (GPL) version 2 or any later version' use:
+ SPDX-License-Identifier: GPL-2.0-or-later
+License-Text:
+
+ GNU GENERAL PUBLIC LICENSE
+ Version 2, June 1991
+
+Copyright (C) 1989, 1991 Free Software Foundation, Inc.
+51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
+
+Everyone is permitted to copy and distribute verbatim copies
+of this license document, but changing it is not allowed.
+
+ Preamble
+
+The licenses for most software are designed to take away your freedom to share
+and change it. By contrast, the GNU General Public License is intended to
+guarantee your freedom to share and change free software--to make sure the
+software is free for all its users. This General Public License applies to most
+of the Free Software Foundation's software and to any other program whose
+authors commit to using it. (Some other Free Software Foundation software is
+covered by the GNU Lesser General Public License instead.) You can apply it to
+your programs, too.
+
+When we speak of free software, we are referring to freedom, not price. Our
+General Public Licenses are designed to make sure that you have the freedom to
+distribute copies of free software (and charge for this service if you wish),
+that you receive source code or can get it if you want it, that you can change
+the software or use pieces of it in new free programs; and that you know you
+can do these things.
+
+To protect your rights, we need to make restrictions that forbid anyone to deny
+you these rights or to ask you to surrender the rights. These restrictions
+translate to certain responsibilities for you if you distribute copies of the
+software, or if you modify it.
+
+For example, if you distribute copies of such a program, whether gratis or for
+a fee, you must give the recipients all the rights that you have. You must make
+sure that they, too, receive or can get the source code. And you must show them
+these terms so they know their rights.
+We protect your rights with two steps: (1) copyright the software, and (2)
+offer you this license which gives you legal permission to copy, distribute
+and/or modify the software.
+
+Also, for each author's protection and ours, we want to make certain that
+everyone understands that there is no warranty for this free software. If the
+software is modified by someone else and passed on, we want its recipients to
+know that what they have is not the original, so that any problems introduced
+by others will not reflect on the original authors' reputations.
+
+Finally, any free program is threatened constantly by software patents. We wish
+to avoid the danger that redistributors of a free program will individually
+obtain patent licenses, in effect making the program proprietary. To prevent
+this, we have made it clear that any patent must be licensed for everyone's
+free use or not licensed at all.
+
+The precise terms and conditions for copying, distribution and modification
+follow.
+
+ TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
+
+0. This License applies to any program or other work which contains a notice
+placed by the copyright holder saying it may be distributed under the terms of
+this General Public License. The "Program", below, refers to any such program
+or work, and a "work based on the Program" means either the Program or any
+derivative work under copyright law: that is to say, a work containing the
+Program or a portion of it, either verbatim or with modifications and/or
+translated into another language. (Hereinafter, translation is included without
+limitation in the term "modification".) Each licensee is addressed as "you".
+Activities other than copying, distribution and modification are not covered by
+this License; they are outside its scope. The act of running the Program is not
+restricted, and the output from the Program is covered only if its contents
+constitute a work based on the Program (independent of having been made by
+running the Program). Whether that is true depends on what the Program does.
+
+1. You may copy and distribute verbatim copies of the Program's source code as
+you receive it, in any medium, provided that you conspicuously and
+appropriately publish on each copy an appropriate copyright notice and
+disclaimer of warranty; keep intact all the notices that refer to this License
+and to the absence of any warranty; and give any other recipients of the
+Program a copy of this License along with the Program.
+You may charge a fee for the physical act of transferring a copy, and you may
+at your option offer warranty protection in exchange for a fee.
+
+2. You may modify your copy or copies of the Program or any portion of it, thus
+forming a work based on the Program, and copy and distribute such modifications
+or work under the terms of Section 1 above, provided that you also meet all of
+these conditions:
+
+ a) You must cause the modified files to carry prominent notices stating
+ that you changed the files and the date of any change.
+
+ b) You must cause any work that you distribute or publish, that in whole
+ or in part contains or is derived from the Program or any part thereof,
+ to be licensed as a whole at no charge to all third parties under the
+ terms of this License.
+
+ c) If the modified program normally reads commands interactively when
+ run, you must cause it, when started running for such interactive use in
+ the most ordinary way, to print or display an announcement including an
+ appropriate copyright notice and a notice that there is no warranty (or
+ else, saying that you provide a warranty) and that users may redistribute
+ the program under these conditions, and telling the user how to view a
+ copy of this License. (Exception: if the Program itself is interactive
+ but does not normally print such an announcement, your work based on the
+ Program is not required to print an announcement.)
+
+These requirements apply to the modified work as a whole. If identifiable
+sections of that work are not derived from the Program, and can be reasonably
+considered independent and separate works in themselves, then this License, and
+its terms, do not apply to those sections when you distribute them as separate
+works. But when you distribute the same sections as part of a whole which is a
+work based on the Program, the distribution of the whole must be on the terms
+of this License, whose permissions for other licensees extend to the entire
+whole, and thus to each and every part regardless of who wrote it.
+Thus, it is not the intent of this section to claim rights or contest your
+rights to work written entirely by you; rather, the intent is to exercise the
+right to control the distribution of derivative or collective works based on
+the Program.
+
+In addition, mere aggregation of another work not based on the Program with the
+Program (or with a work based on the Program) on a volume of a storage or
+distribution medium does not bring the other work under the scope of this
+License.
+
+3. You may copy and distribute the Program (or a work based on it, under
+Section 2) in object code or executable form under the terms of Sections 1 and
+2 above provided that you also do one of the following:
+
+ a) Accompany it with the complete corresponding machine-readable source
+ code, which must be distributed under the terms of Sections 1 and 2 above
+ on a medium customarily used for software interchange; or,
+
+ b) Accompany it with a written offer, valid for at least three years, to
+ give any third party, for a charge no more than your cost of physically
+ performing source distribution, a complete machine-readable copy of the
+ corresponding source code, to be distributed under the terms of Sections
+ 1 and 2 above on a medium customarily used for software interchange; or,
+
+ c) Accompany it with the information you received as to the offer to
+ distribute corresponding source code. (This alternative is allowed only
+ for noncommercial distribution and only if you received the program in
+ object code or executable form with such an offer, in accord with
+ Subsection b above.)
+
+The source code for a work means the preferred form of the work for making
+modifications to it. For an executable work, complete source code means all the
+source code for all modules it contains, plus any associated interface
+definition files, plus the scripts used to control compilation and installation
+of the executable. However, as a special exception, the source code distributed
+need not include anything that is normally distributed (in either source or
+binary form) with the major components (compiler, kernel, and so on) of the
+operating system on which the executable runs, unless that component itself
+accompanies the executable.
+
+If distribution of executable or object code is made by offering access to copy
+from a designated place, then offering equivalent access to copy the source
+code from the same place counts as distribution of the source code, even though
+third parties are not compelled to copy the source along with the object code.
+
+4. You may not copy, modify, sublicense, or distribute the Program except as
+expressly provided under this License. Any attempt otherwise to copy, modify,
+sublicense or distribute the Program is void, and will automatically terminate
+your rights under this License. However, parties who have received copies, or
+rights, from you under this License will not have their licenses terminated so
+long as such parties remain in full compliance.
+
+5. You are not required to accept this License, since you have not signed it.
+However, nothing else grants you permission to modify or distribute the Program
+or its derivative works. These actions are prohibited by law if you do not
+accept this License. Therefore, by modifying or distributing the Program (or
+any work based on the Program), you indicate your acceptance of this License to
+do so, and all its terms and conditions for copying, distributing or modifying
+the Program or works based on it.
+
+6. Each time you redistribute the Program (or any work based on the Program),
+the recipient automatically receives a license from the original licensor to
+copy, distribute or modify the Program subject to these terms and conditions.
+You may not impose any further restrictions on the recipients' exercise of the
+rights granted herein. You are not responsible for enforcing compliance by
+third parties to this License.
+
+7. If, as a consequence of a court judgment or allegation of patent
+infringement or for any other reason (not limited to patent issues), conditions
+are imposed on you (whether by court order, agreement or otherwise) that
+contradict the conditions of this License, they do not excuse you from the
+conditions of this License. If you cannot distribute so as to satisfy
+simultaneously your obligations under this License and any other pertinent
+obligations, then as a consequence you may not distribute the Program at all.
+
+For example, if a patent license would not permit royalty-free redistribution
+of the Program by all those who receive copies directly or indirectly through
+you, then the only way you could satisfy both it and this License would be to
+refrain entirely from distribution of the Program.
+
+If any portion of this section is held invalid or unenforceable under any
+particular circumstance, the balance of the section is intended to apply and
+the section as a whole is intended to apply in other circumstances.
+It is not the purpose of this section to induce you to infringe any patents or
+other property right claims or to contest validity of any such claims; this
+section has the sole purpose of protecting the integrity of the free software
+distribution system, which is implemented by public license practices. Many
+people have made generous contributions to the wide range of software
+distributed through that system in reliance on consistent application of that
+system; it is up to the author/donor to decide if he or she is willing to
+distribute software through any other system and a licensee cannot impose that
+choice.
+
+This section is intended to make thoroughly clear what is believed to be a
+consequence of the rest of this License.
+
+8. If the distribution and/or use of the Program is restricted in certain
+countries either by patents or by copyrighted interfaces, the original
+copyright holder who places the Program under this License may add an explicit
+geographical distribution limitation excluding those countries, so that
+distribution is permitted only in or among countries not thus excluded. In such
+case, this License incorporates the limitation as if written in the body of
+this License.
+
+9. The Free Software Foundation may publish revised and/or new versions of the
+General Public License from time to time. Such new versions will be similar in
+spirit to the present version, but may differ in detail to address new problems
+or concerns.
+Each version is given a distinguishing version number. If the Program specifies
+a version number of this License which applies to it and "any later version",
+you have the option of following the terms and conditions either of that
+version or of any later version published by the Free Software Foundation. If
+the Program does not specify a version number of this License, you may choose
+any version ever published by the Free Software Foundation.
+10. If you wish to incorporate parts of the Program into other free programs
+whose distribution conditions are different, write to the author to ask for
+permission. For software which is copyrighted by the Free Software Foundation,
+write to the Free Software Foundation; we sometimes make exceptions for this.
+Our decision will be guided by the two goals of preserving the free status of
+all derivatives of our free software and of promoting the sharing and reuse of
+software generally.
+
+NO WARRANTY
+
+11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY FOR
+THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN OTHERWISE
+STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES PROVIDE THE
+PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED,
+INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
+FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND
+PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU
+ASSUME THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
+12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING WILL
+ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR REDISTRIBUTE THE
+PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY
+GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR
+INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA
+BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A
+FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), EVEN IF SUCH HOLDER
+OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES.
+
+ END OF TERMS AND CONDITIONS
diff --git a/README.md b/README.md
new file mode 100644
index 0000000..7fa3a9b
--- /dev/null
+++ b/README.md
@@ -0,0 +1,11 @@
+# mb2q - Mailbox to quilt series converter
+
+## Install
+
+It's recommended to generate the appropriate package for your system and
+install it via the package manager.
+
+## Usage
+
+See Documentation and man pages
+
diff --git a/mb2q b/mb2q
new file mode 100755
index 0000000..44bc1ef
--- /dev/null
+++ b/mb2q
@@ -0,0 +1,660 @@
+#!/usr/bin/env python3
+# SPDX-License-Identifier: GPL2.0
+# Copyright Thomas Gleixner <tglx@linutronix.de>
+
+from email.utils import make_msgid, formatdate
+from argparse import ArgumentParser
+import subprocess
+import mailbox
+import email.policy
+import email
+import yaml
+import time
+import sys
+import os
+import re
+
+def committer_from_git():
+ '''
+ Retrieve the commiter name/mail from git or environment
+ '''
+ cmd = "echo $(git config --get user.name)' <'$(git config --get user.email)'>'"
+ committer = subprocess.getoutput(cmd)
+
+ if committer == " <>":
+ committer = os.environ.get("GIT_COMMITTER_NAME") + " <" + \
+ os.environ.get("GIT_COMMITTER_EMAIL") + ">"
+ return committer
+
+'''
+Regexes to split out the patch from a mail
+'''
+re_split_at_patch = [
+ re.compile('\ndiff --git a/.* b/.*\n'),
+ re.compile('\n--- a/.*\n'),
+ re.compile('\n--- dev/null.*\n'),
+]
+
+def split_at_patch(text):
+ '''
+ Check whether the text has a patch embedded.
+ If so, split the text and return the parts
+ If not, return the text as a single entry list
+ '''
+ for r in re_split_at_patch:
+ m = r.search(text)
+ if m:
+ s = m.start()
+ return [text[:s], text[s+1:]]
+ return [text, None]
+
+def get_raw_mailaddr(addr):
+ '''
+ Extract the raw email address from a string
+ '''
+ try:
+ return addr.split('<')[1].split('>')[0].strip()
+ except:
+ return addr
+
+re_compress_space = re.compile('\s+')
+
+def decode_hdr(hdr):
+ '''
+ Decode a mail header with encoding
+ '''
+ elm = email.header.decode_header(hdr.strip())
+ res = ''
+ for txt, enc in elm:
+ # Groan ....
+ if enc:
+ res += ' ' + txt.decode(enc)
+ elif isinstance(txt, str):
+ res += ' ' + txt
+ else:
+ res += ' ' + txt.decode('ascii')
+ return re_compress_space.sub(' ', res).strip()
+
+def should_drop(addr, drop):
+ raw = get_raw_mailaddr(addr)
+ for d in drop:
+ if raw == get_raw_mailaddr(d):
+ return True
+ return False
+
+def decode_addrs(hdr, drop=[]):
+ '''
+ Decode mail addresses from a header and handle encondings
+ '''
+ addrs = mailaddrs()
+ if not hdr:
+ return addrs
+ parts = re_compress_space.sub(' ', hdr).split(',')
+ for p in parts:
+ addr = decode_hdr(p)
+ if not should_drop(addr, drop):
+ addrs.add(addr)
+ return addrs
+
+def decode_from(msg):
+ '''
+ Decode the From header and return it as the topmost element
+ '''
+ addrs = decode_addrs(str(msg['From']))
+ return addrs.get_first()
+
+re_noquote = re.compile('[a-zA-Z0-9_\- ]+')
+
+def encode_hdr(txt):
+ try:
+ return txt.encode('ascii').decode()
+ except:
+ txt = txt.encode('UTF-8').decode()
+
+def encode_from(fulladdr):
+ try:
+ name, addr = fulladdr.split('<', 1)
+ name = name.strip()
+ except:
+ return fulladdr
+
+ try:
+ name = txt.encode('ascii').decode()
+ if not re_noquote.fullmatch(name):
+ name = '"%s"' %name.replace('"', '')
+ except:
+ name = email.header.Header(name).encode()
+
+ return name + ' <' + addr
+
+class mailaddrs(object):
+ '''
+ Storage for mail addresses with no duplicates allowed
+ '''
+ def __init__(self, addrlist=[]):
+ self.addrs = {}
+ self.order = []
+ self.append_list(addrlist)
+
+ def add(self, addr_new):
+ raw = get_raw_mailaddr(addr_new)
+ if not raw in self.order:
+ self.order.append(raw)
+ # Prevent duplicates
+ addr = self.addrs.get(raw, addr_new)
+ # Prefer the full Name <addr> variant
+ if len(addr) < len(addr_new):
+ addr = addr_new
+ self.addrs[raw] = addr
+
+ def values(self):
+ res = []
+ for r in self.order:
+ res.append(self.addrs[r])
+ return res
+
+ def extend(self, addrs):
+ for addr in addrs.values():
+ self.add(addr)
+
+ def append_list(self, addrlist):
+ for addr in addrlist:
+ if len(addr):
+ self.add(addr)
+
+ def get_first(self):
+ return self.addrs[self.order[0]]
+
+ def get_last(self):
+ return self.addrs[self.order[-1]]
+
+ def remove(self, addr):
+ raw = get_raw_mailaddr(addr)
+ if raw in self.order:
+ del self.addrs[raw]
+ self.order.remove(raw)
+
+ def contains(self, addrlist):
+ for addr in addrlist:
+ raw = get_raw_mailaddr(addr)
+ if raw in self.order:
+ return True
+ return False
+
+class tagstore(object):
+ '''
+ Storage for links and other things with no duplicates
+ '''
+ def __init__(self):
+ self.links = []
+
+ def values(self):
+ return self.links
+
+ def add(self, link):
+ if not link in self.links:
+ self.links.append(link)
+
+tag_order = [
+ 'Fixes',
+ 'Reported-and-tested-by',
+ 'Reported-by',
+ 'Suggested-by',
+ 'Originally-from',
+ 'Originally-by',
+ 'Signed-off-by',
+ 'Tested-by',
+ 'Reviewed-by',
+ 'Acked-by',
+ 'Cc',
+ 'Link',
+]
+
+tag_nomail = [
+ 'Fixes',
+ 'Link',
+]
+
+tags_reply = [
+ 'Tested-by',
+ 'Reviewed-by',
+ 'Acked-by',
+]
+
+re_tags = []
+
+def compile_tags():
+ for tag in tag_order:
+ re_tags.append(re.compile('^%s:' %tag, re.IGNORECASE))
+
+class patchmsg(object):
+ '''
+ Internal representation of a patch message.
+ '''
+
+ re_rmsquare = re.compile('^\[.*?\]')
+ re_tofname = re.compile('[^0-9a-zA-Z]+')
+
+ def __init__(self, msg, args):
+ self.args = args
+ self.tags = {}
+
+ self.date = msg.get('Date', formatdate())
+ self.decode_subject(msg)
+ self.get_references(msg)
+ self.get_addrs(msg)
+ self.split_at_patch(msg)
+ self.scan_tags()
+
+ def decode_subject(self, msg):
+ subj = msg.get('Subject')
+ if not subj:
+ return
+ # Remove starting brackets stuff
+ while subj.startswith('['):
+ subj = self.re_rmsquare.sub('', subj, count=1)
+ subj = subj.replace('\n', ' ')
+ re_compress_space.sub(' ', subj)
+
+ # Try to automatically uppercase the start of the sentence
+ # This assumes that the prefix(es) end with a colon and the
+ # sentence afterwards does not contain one.
+ spos = subj.rfind(':') + 1
+ if spos > 0 and spos < len(subj):
+ prefix = subj[:spos]
+ tail = subj[spos:].strip()
+ subj = prefix + ' ' + tail[0].capitalize() + tail[1:]
+
+ self.subject = subj.strip()
+ # Create a filename just in case
+ self.fname = self.re_tofname.sub('_', self.subject)
+
+ def get_references(self, msg):
+ self.references = msg.get('References', '').split()
+ self.inreplyto = msg.get('In-Reply-To', '').split()
+ self.msgid = msg.get('Message-ID', None)
+
+ def get_addrs(self, msg):
+ self.author = decode_from(msg)
+ # Get To and Cc
+ ccs = decode_addrs(str(msg['To']), drop = self.args.nocc_addrs)
+ ccs.extend(decode_addrs(str(msg['Cc']), drop = self.args.nocc_addrs))
+
+ # Check whether the cc list contains a list address match
+ self.listmatch = ccs.contains(self.args.list_addrs)
+ # Remove the matching lists from Cc
+ for addr in self.args.list_addrs:
+ ccs.remove(addr)
+
+ if self.args.collectcc:
+ self.tags['Cc'] = ccs
+
+ def split_at_patch(self, msg):
+ self.text = ''
+ self.patch = None
+
+ # Handle multipart messages gracefully
+ # i.e. extract attached patches as well
+ for part in msg.walk():
+ if part.is_multipart():
+ continue
+ # Refuse to handle anything else than plain text
+ if part.get_content_type() != 'text/plain':
+ continue
+ # Get the text with decoding (could be base64 encoded)
+ text = part.get_payload(decode=True)
+ if not text:
+ continue
+
+ text, patch = split_at_patch(text.decode())
+ self.text += text
+ if patch:
+ self.patch = patch
+
+ def add_tag(self, key, addr, force=False):
+
+ # Drop old links based on linkbase
+ if key == 'Link' and not force and self.args.droplinks:
+ if addr.startswith(self.args.linkbase):
+ return
+
+ # Drop all ccs if requested
+ if key == 'Cc' and self.args.dropccs:
+ return
+
+ if key not in tag_nomail:
+ ts = self.tags.get(key, mailaddrs())
+ else:
+ ts = self.tags.get(key, tagstore())
+ ts.add(addr)
+ self.tags[key] = ts
+ self.has_tags = True
+
+ def scan_tag(self, txt):
+ i = 0
+ txt = txt.strip()
+ for r in re_tags:
+ m = r.search(txt)
+ if m:
+ pos = m.end()
+ tag = tag_order[i]
+ addr = txt[pos + 1:].strip()
+ if tag == 'Reported-and-tested-by':
+ self.add_tag('Reported-by', addr)
+ tag = 'Tested-by'
+ self.add_tag(tag, addr)
+ return True
+ i += 1
+ return False
+
+ def scan_tags(self):
+ lines = self.text.splitlines()
+ self.text = ''
+ self.posttags = ''
+ self.has_tags = False
+ self.has_from_in_text = False
+ discard_seen = False
+ for l in lines:
+ if self.scan_tag(l):
+ continue
+
+ # Drop quoted text, except when it's '>From'
+ if l.startswith('>') and l.find('From') < 0:
+ continue
+ # Respect a From: in the body and update author
+ if l.startswith('From:'):
+ self.author = l.split(':')[1]
+ self.has_from_in_text = True
+ if l.strip() == '---' :
+ discard_seen = True
+ if discard_seen:
+ self.posttags += l + '\n'
+ else:
+ self.text += l + '\n'
+
+ def update_tags(self, pmsg):
+ for tag in tags_reply:
+ rtags = pmsg.tags.get(tag, mailaddrs())
+ for addr in rtags.values():
+ self.add_tag(tag, addr)
+
+ def sanitize_ccs(self):
+ # If a CC address is in any other tag and remove it
+ ccs = self.tags.get('Cc', mailaddrs())
+ for cc in ccs.values():
+ for tag in tag_order:
+ if tag == 'Cc' or tag in tag_nomail:
+ continue
+ if self.tags.get(tag, mailaddrs()).contains([cc]):
+ ccs.remove(cc)
+ break
+
+class quilter(object):
+ '''
+ The worker which turns mbox into quilt
+ '''
+ def __init__(self, args):
+ # Store args for further reference
+ self.args = args
+
+ # Extract the acked, tested, reviewedby tags which came from
+ # the command line
+ self.tags = {
+ 'Acked-by' : mailaddrs(args.ackedby.split(',')),
+ 'Tested-by' : mailaddrs(args.testedby.split(',')),
+ 'Reviewed-by' : mailaddrs(args.reviewedby.split(',')),
+ }
+
+ def should_drop(self, msg):
+ try:
+ mfrom = str(msg['From'])
+ except:
+ mfrom = msg['From']
+ for drop in self.args.drop_from:
+ if mfrom.find(drop) >= 0:
+ return True
+ return False
+
+ def check_replies(self, pmsg, ref):
+ for r in self.replies:
+ if ref:
+ which = r.references
+ else:
+ which = r.inreplyto
+
+ if pmsg.msgid in which:
+ pmsg.update_tags(r)
+ self.replies.remove(r)
+
+ def scan_mbox(self, mbox):
+ self.patches = []
+ self.replies = []
+ self.others = []
+
+ # Initial scan to gather information
+ for k, msg in mbox.iteritems():
+
+ if self.should_drop(msg):
+ continue
+ if not msg.get('Message-ID') and not self.args.noid:
+ sys.stderr.write('Drop Message w/o Message-ID: %s\n'
+ %msg.get('Subject','No subject'))
+ continue
+ pmsg = patchmsg(msg, self.args)
+
+ # Found a patch
+ if pmsg.patch:
+ # Add the command line supplied tags
+ pmsg.update_tags(self)
+ if pmsg.msgid and pmsg.listmatch and not self.args.nolink:
+ mid = pmsg.msgid.lstrip('<').rstrip('>')
+ pmsg.add_tag('Link', self.args.linkbase + mid, force = True)
+ self.patches.append(pmsg)
+ elif pmsg.has_tags:
+ self.replies.append(pmsg)
+ else:
+ self.others.append(pmsg)
+
+ # Find all direct replies to patches
+ for pmsg in self.patches:
+ self.check_replies(pmsg, False)
+
+ # Find all replies which reference a patch
+ for pmsg in self.patches:
+ self.check_replies(pmsg, True)
+
+ # Find all direct replies to others (cover letter etc)
+ for pmsg in self.others:
+ self.check_replies(pmsg, False)
+
+ # Find all replies which reference others (cover letter etc)
+ for pmsg in self.others:
+ self.check_replies(pmsg, True)
+
+ # Sort out others which are not referenced by patches
+ cover = []
+ for o in self.others:
+ for p in self.patches:
+ if o.msgid == p.inreplyto or o.msgid in p.references:
+ cover.append(o)
+ break
+
+ # Update patches which reference a cover letter
+ for c in cover:
+ for p in self.patches:
+ if o.msgid == p.inreplyto or o.msgid in p.references:
+ p.update_tags(c)
+
+ # Sanitize Ccs:
+ for p in self.patches:
+ p.sanitize_ccs()
+
+ def write_series(self, pdir):
+ # Write the series file
+ with open(os.path.join(pdir, 'series'), 'a') as fd:
+ for pmsg in self.patches:
+ fname = pmsg.fname + '.patch'
+ i = 0
+ # If the patch file exists, create a new one
+ # Duplicate subjects happen ....
+ while os.path.isfile(os.path.join(pdir, fname)):
+ i += 1
+ fname = pmsg.fname + '-%d.patch' % i
+ pmsg.fname = fname
+ fd.write(fname + '\n')
+
+ def needs_sob(self, pmsg):
+ last = pmsg.tags.get('Signed-off-by', mailaddrs()).get_last()
+ if not last:
+ return True
+ return get_raw_mailaddr(self.args.committer) != get_raw_mailaddr(last)
+
+ def create_patch(self, pmsg):
+ res = ''
+ if not self.args.fromnodup and not pmsg.has_from_in_text:
+ res += 'From: %s\n\n' %pmsg.author
+
+ # Drop extra newlines before tags
+ res += pmsg.text.rstrip() + '\n\n'
+
+ for tag in tag_order:
+ # If SOB is empty warn
+ if tag == 'Signed-off-by' and not tag in pmsg.tags:
+ sys.stderr.write('Patch %s has no SOB\n' %pmsg.subject)
+
+ # Write out the committer SOB before Cc, but not if the
+ # committer is the author and no other SOBs are behind his
+ # SOB
+ if self.args.sob_before_cc and tag == 'Cc' and self.needs_sob(pmsg):
+ res += 'Signed-off-by: %s\n' %self.args.committer
+
+ if not tag in pmsg.tags:
+ continue
+
+ for val in pmsg.tags[tag].values():
+ res += '%s: %s\n' %(tag, val)
+
+ if not self.args.sob_before_cc and self.needs_sob(pmsg):
+ res += 'Signed-off-by: %s\n' %self.args.committer
+
+ res += '\n'
+ res += pmsg.posttags
+ res += '\n'
+ res += pmsg.patch
+ return res
+
+ def write_patch(self, pmsg, pdir):
+ with open(os.path.join(pdir, pmsg.fname), 'w') as fd:
+ res = 'From: %s\n' %pmsg.author
+ res += 'Subject: %s\n' %pmsg.subject
+ res += 'Date: %s\n\n' %pmsg.date
+ res += self.create_patch(pmsg)
+ fd.write(res)
+
+ def write_patches(self, pdir):
+ for pmsg in self.patches:
+ self.write_patch(pmsg, pdir)
+
+ def create_mbox(self, fpath):
+ mbox = mailbox.mbox(fpath, create = True)
+ for pmsg in self.patches:
+ pol = email.policy.EmailPolicy(utf8=True)
+ msg = email.message.EmailMessage(pol)
+ msg['From'] = email.header.Header(pmsg.author)
+ msg['Date'] = pmsg.date
+ msg['Subject'] = encode_hdr(pmsg.subject)
+ msg['MIME-Version'] = '1.0'
+ msg['Content-Type'] = 'text/plain'
+ msg.set_param('charset', 'utf-8', header='Content-Type')
+ msg['Content-Transfer-Encoding'] = '8bit'
+ msg['Content-Disposition'] = 'inline'
+ msg.set_unixfrom('From mb2q ' + time.ctime(time.time()))
+ msg.set_content(self.create_patch(pmsg))
+ mbox.add(mailbox.mboxMessage(msg))
+ mbox.close()
+
+if __name__ == '__main__':
+
+ parser = ArgumentParser(description='Mailbox 2 quilt converter')
+ parser.add_argument('inbox', metavar='inbox', help='Input mbox file')
+ parser.add_argument('-c', '--config', dest='config', default='~/.mb2q.yaml',
+ help='Config file. Default: ~/.mb2q.yaml')
+ parser.add_argument('-d', '--droplinks', dest='droplinks',
+ action='store_true',
+ help='Drop existing Link:// tags in patches')
+ parser.add_argument('-F', '--fromnodup', dest='fromnodup',
+ action='store_true',
+ help='Do not duplicate From into the body')
+ parser.add_argument('-m', '--mboxout', dest='mboxout',
+ help='Store result in mailbox and do not quilt')
+ parser.add_argument('-n', '--nolink', dest='nolink',
+ action='store_true',
+ help='Do not add Link:// tags to patches')
+ parser.add_argument('-N', '--noid', dest='noid',
+ action='store_true',
+ help='Do not require Message ID. Implies --nolink.')
+ parser.add_argument('-p', '--patchesdir', dest='patchesdir',
+ default=None,
+ help='''
+ Use given directory and do not automatically create
+ patches-$inbox/''')
+ parser.add_argument('-a', '--ackedby', dest='ackedby', type=str,
+ default='',
+ help='''
+ Add Acked-by to all patches. Comma separated list.
+ "Joe Devel <joe@dev.el>, Dev Joe <dev@joe.org>"
+ ''')
+ parser.add_argument('-r', '--reviewedby', dest='reviewedby', type=str,
+ default='',
+ help='Add Reviewed-by to all patches')
+ parser.add_argument('-t', '--testedby', dest='testedby', type=str,
+ default='',
+ help='Add Tested-by to all patches')
+ parser.add_argument('-C', '--CollectCC', dest='collectcc',
+ action='store_true',
+ help='Collect all CCs from mail header')
+ args = parser.parse_args()
+
+ try:
+ cfg = yaml.load(open(os.path.expanduser(args.config)))
+ except:
+ cfg = {}
+
+ # Get committer from config or GIT
+ args.committer = cfg.get('committer', committer_from_git())
+ # Cc's which should be dropped
+ args.nocc_addrs = cfg.get('nocc_addrs', [])
+ # Mailing list addresses
+ args.list_addrs = cfg.get('list_addrs', ['linux-kernel.@vger.kernel.org'])
+ # Drop mails which originate from a mail clients management mail
+ # usually the first mail in a mbox
+ args.drop_from = cfg.get('drop_from', ['<MAILER-DAEMON@'])
+ # The base address for generating Link://
+ args.linkbase = cfg.get('link_base', 'https://lkml.kernel.org/r/')
+ # Drop all ccs?
+ args.dropccs = cfg.get('dropccs', True)
+ # Committer SOB before Cc and Link
+ args.sob_before_cc = cfg.get('sob_before_cc', True)
+
+ compile_tags()
+
+ q = quilter(args)
+ mbox = mailbox.mbox(args.inbox, create=False)
+ q.scan_mbox(mbox)
+
+ if not len(q.patches):
+ sys.stderr.write("No patches found in mbox\n")
+ sys.exit(1)
+
+ if args.mboxout:
+ q.create_mbox(args.mboxout)
+ else:
+ # Create the patch directory
+ if args.patchesdir:
+ pdir = os.path.expanduser(args.patchesdir)
+ else:
+ pdir = 'patches-%s' % os.path.basename(args.inbox)
+ if not os.path.isdir(pdir):
+ os.makedirs(pdir)
+ q.write_series(pdir)
+ q.write_patches(pdir)