From: Marat Khalili <marat.khalili@huawei.com>
To: Bruce Richardson <bruce.richardson@intel.com>,
"dev@dpdk.org" <dev@dpdk.org>
Subject: RE: [PATCH v2 1/2] devtools/mailmap_ctl: script to work with mailmap
Date: Mon, 11 Aug 2025 11:28:05 +0000 [thread overview]
Message-ID: <993dc8b5fb3f42cf8ce5433c9aadceb6@huawei.com> (raw)
In-Reply-To: <20250808210837.518507-2-bruce.richardson@intel.com>
LGTM
> -----Original Message-----
> From: Bruce Richardson <bruce.richardson@intel.com>
> Sent: Friday 8 August 2025 22:09
> To: dev@dpdk.org
> Cc: Marat Khalili <marat.khalili@huawei.com>; Bruce Richardson <bruce.richardson@intel.com>
> Subject: [PATCH v2 1/2] devtools/mailmap_ctl: script to work with mailmap
>
> Add a script to easily add entries to, check and sort the mailmap file.
>
> Signed-off-by: Bruce Richardson <bruce.richardson@intel.com>
> ---
> devtools/mailmap_ctl.py | 212 ++++++++++++++++++++++++++++++++++++++++
> 1 file changed, 212 insertions(+)
> create mode 100755 devtools/mailmap_ctl.py
>
> diff --git a/devtools/mailmap_ctl.py b/devtools/mailmap_ctl.py
> new file mode 100755
> index 0000000000..15548c54cd
> --- /dev/null
> +++ b/devtools/mailmap_ctl.py
> @@ -0,0 +1,212 @@
> +#!/usr/bin/env python3
> +# SPDX-License-Identifier: BSD-3-Clause
> +# Copyright(c) 2025 Intel Corporation
> +
> +"""
> +A tool for manipulating the .mailmap file in DPDK repository.
> +
> +This script supports three operations:
> +- add: adds a new entry to the mailmap file in the correct position
> +- check: validates mailmap entries are sorted and correctly formatted
> +- sort: sorts the mailmap entries alphabetically by name
> +"""
> +
> +import sys
> +import re
> +import argparse
> +import itertools
> +import unicodedata
> +from pathlib import Path
> +from dataclasses import dataclass
> +from typing import List, Optional
> +
> +
> +@dataclass
> +class MailmapEntry:
> + """Represents a single mailmap entry."""
> +
> + name: str
> + name_for_sorting: str
> + email1: str
> + email2: Optional[str]
> + line_number: int
> +
> + def __str__(self) -> str:
> + """Format the entry back to mailmap string format."""
> + return f"{self.name} <{self.email1}>" + (f" <{self.email2}>" if self.email2 else "")
> +
> + @staticmethod
> + def _get_name_for_sorting(name) -> str:
> + """Normalize a name for sorting purposes."""
> + # Remove accents/diacritics. Separate accented chars into two - so accent is separate,
> + # then remove the accent.
> + normalized = unicodedata.normalize("NFD", name)
> + normalized = "".join(c for c in normalized if unicodedata.category(c) != "Mn")
> +
> + return normalized.lower()
> +
> + @classmethod
> + def parse(cls, line: str, line_number: int = 0):
> + """
> + Parse a mailmap line and create a MailmapEntry instance.
> +
> + Valid formats:
> + - Name <email>
> + - Name <primary_email> <secondary_email>
> + """
> + # Pattern to match mailmap entries
> + # Group 1: Name, Group 2: first email, Group 3: optional second email
> + pattern = r"^([^<]+?)\s*<([^>]+)>(?:\s*<([^>]+)>)?$"
> + match = re.match(pattern, line.strip())
> + if not match:
> + raise argparse.ArgumentTypeError(f"Invalid entry format: '{line}'")
> +
> + name = match.group(1).strip()
> + primary_email = match.group(2).strip()
> + secondary_email = match.group(3).strip() if match.group(3) else None
> +
> + return cls(
> + name=name,
> + name_for_sorting=cls._get_name_for_sorting(name),
> + email1=primary_email,
> + email2=secondary_email,
> + line_number=line_number,
> + )
> +
> +
> +def read_and_parse_mailmap(mailmap_path: Path, fail_on_err: bool) -> List[MailmapEntry]:
> + """Read and parse a mailmap file, returning entries."""
> + try:
> + with open(mailmap_path, "r", encoding="utf-8") as f:
> + lines = f.readlines()
> + except IOError as e:
> + print(f"Error reading {mailmap_path}: {e}", file=sys.stderr)
> + sys.exit(1)
> +
> + entries = []
> + for line_num, line in enumerate(lines, 1):
> + stripped_line = line.strip()
> +
> + # Skip empty lines and comments
> + if not stripped_line or stripped_line.startswith("#"):
> + continue
> +
> + try:
> + entry = MailmapEntry.parse(stripped_line, line_num)
> + except argparse.ArgumentTypeError as e:
> + print(f"Line {line_num}: {e}", file=sys.stderr)
> + if fail_on_err:
> + sys.exit(1)
> + continue
> +
> + entries.append(entry)
> + return entries
> +
> +
> +def write_entries_to_file(mailmap_path: Path, entries: List[MailmapEntry]):
> + """Write entries to mailmap file."""
> + try:
> + with open(mailmap_path, "w", encoding="utf-8") as f:
> + for entry in entries:
> + f.write(str(entry) + "\n")
> + except IOError as e:
> + print(f"Error writing {mailmap_path}: {e}", file=sys.stderr)
> + sys.exit(1)
> +
> +
> +def check_mailmap(mailmap_path, _):
> + """Check that mailmap entries are correctly sorted and formatted."""
> + entries = read_and_parse_mailmap(mailmap_path, False)
> +
> + errors = 0
> + for e1, e2 in itertools.pairwise(entries):
> + if e1.name_for_sorting > e2.name_for_sorting:
> + print(
> + f"Line {e2.line_number}: '{e2.name}' should come before '{e1.name}'",
> + file=sys.stderr,
> + )
> + errors += 1
> +
> + if errors:
> + sys.exit(1)
> +
> +
> +def sort_mailmap(mailmap_path, _):
> + """Sort the mailmap entries alphabetically by name."""
> + entries = read_and_parse_mailmap(mailmap_path, True)
> +
> + entries.sort(key=lambda x: x.name_for_sorting)
> + write_entries_to_file(mailmap_path, entries)
> +
> +
> +def add_entry(mailmap_path, args):
> + """Add a new entry to the mailmap file in the correct alphabetical position."""
> + if not args.entry:
> + print("Error: 'add' operation requires an entry argument", file=sys.stderr)
> + sys.exit(1)
> +
> + new_entry = args.entry
> + entries = read_and_parse_mailmap(mailmap_path, True)
> +
> + # Check if entry already exists, checking email2 only if it's specified
> + if (
> + not new_entry.email2
> + and any(e.name == new_entry.name and e.email1 == new_entry.email1 for e in entries)
> + ) or any(
> + e.name == new_entry.name and e.email1 == new_entry.email1 and e.email2 == new_entry.email2
> + for e in entries
> + ):
> + print(
> + f"Error: Duplicate entry - '{new_entry.name} <{new_entry.email1}>' already exists",
> + file=sys.stderr,
> + )
> + sys.exit(1)
> +
> + for n, entry in enumerate(entries):
> + if entry.name_for_sorting > new_entry.name_for_sorting:
> + entries.insert(n, new_entry)
> + break
> + else:
> + entries.append(new_entry)
> + write_entries_to_file(mailmap_path, entries)
> +
> +
> +def main():
> + """Main function."""
> + # ops and functions implementing them
> + operations = {"add": add_entry, "check": check_mailmap, "sort": sort_mailmap}
> +
> + parser = argparse.ArgumentParser(
> + description=__doc__,
> + formatter_class=argparse.RawDescriptionHelpFormatter,
> + epilog="NOTE:\n for operations which write .mailmap, any comments or blank lines in the file will be
> removed",
> + )
> + parser.add_argument("operation", choices=operations.keys(), help="Operation to perform")
> + parser.add_argument("--mailmap", help="Path to .mailmap file (default: search up tree)")
> + parser.add_argument(
> + "entry",
> + nargs="?",
> + type=MailmapEntry.parse,
> + help='Entry to add. Format: "Name <email@domain.com>"',
> + )
> +
> + args = parser.parse_args()
> +
> + if args.mailmap:
> + mailmap_path = Path(args.mailmap)
> + else:
> + # Find mailmap file
> + mailmap_path = Path(".").resolve()
> + while not (mailmap_path / ".mailmap").exists():
> + if mailmap_path == mailmap_path.parent:
> + print("Error: No .mailmap file found", file=sys.stderr)
> + sys.exit(1)
> + mailmap_path = mailmap_path.parent
> + mailmap_path = mailmap_path / ".mailmap"
> +
> + # call appropriate operation
> + operations[args.operation](mailmap_path, args)
> +
> +
> +if __name__ == "__main__":
> + main()
> --
> 2.48.1
>
Acked-by: Marat Khalili <marat.khalili@huawei.com>
next prev parent reply other threads:[~2025-08-11 11:28 UTC|newest]
Thread overview: 12+ messages / expand[flat|nested] mbox.gz Atom feed top
2025-08-08 14:27 [PATCH 0/2] Improve mailmap file Bruce Richardson
2025-08-08 14:27 ` [PATCH 1/2] devtools/mailmap_ctl: script to work with mailmap Bruce Richardson
2025-08-08 16:44 ` Marat Khalili
2025-08-08 16:47 ` Bruce Richardson
2025-08-08 19:58 ` Bruce Richardson
2025-08-11 11:27 ` Marat Khalili
2025-08-08 14:27 ` [PATCH 2/2] mailmap: sort mailmap Bruce Richardson
2025-08-08 21:08 ` [PATCH v2 0/2] Improve mailmap file Bruce Richardson
2025-08-08 21:08 ` [PATCH v2 1/2] devtools/mailmap_ctl: script to work with mailmap Bruce Richardson
2025-08-11 11:28 ` Marat Khalili [this message]
2025-08-08 21:08 ` [PATCH v2 2/2] mailmap: sort mailmap Bruce Richardson
2025-08-11 12:18 ` Marat Khalili
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=993dc8b5fb3f42cf8ce5433c9aadceb6@huawei.com \
--to=marat.khalili@huawei.com \
--cc=bruce.richardson@intel.com \
--cc=dev@dpdk.org \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for NNTP newsgroup(s).