From patchwork Thu Oct 22 17:33:02 2026 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Ray Strode X-Patchwork-Id: 28565 Return-Path: X-Original-To: parsemail@patchwork.libcamera.org Delivered-To: parsemail@patchwork.libcamera.org Received: from lancelot.ideasonboard.com (lancelot.ideasonboard.com [92.243.16.209]) by patchwork.libcamera.org (Postfix) with ESMTPS id 9C894C324C for ; Thu, 22 Oct 2026 17:33:09 +0000 (UTC) Received: from lancelot.ideasonboard.com (localhost [IPv6:::1]) by lancelot.ideasonboard.com (Postfix) with ESMTP id B604868F8B; Thu, 22 Oct 2026 19:33:07 +0200 (CEST) Authentication-Results: lancelot.ideasonboard.com; dkim=pass (2048-bit key; unprotected) header.d=gmail.com header.i=@gmail.com header.b="guVjB8u4"; dkim-atps=neutral Received: from mail-yx1-xb12d.google.com (mail-yx1-xb12d.google.com [IPv6:2607:f8b0:4864:20::b12d]) by lancelot.ideasonboard.com (Postfix) with ESMTPS id 4917068F89 for ; Thu, 22 Oct 2026 19:33:04 +0200 (CEST) Received: by mail-yx1-xb12d.google.com with SMTP id 956f58d0204a3-67583c777abso1455542d50.1 for ; Thu, 22 Oct 2026 10:33:04 -0700 (PDT) ARC-Seal: i=1; a=rsa-sha256; t=1792690383; cv=none; d=google.com; s=arc-20260327; b=Y7ZiYA5fy0Jfq0EOequdbg3OWZhXyHgL/VAXO32hHg4CeRDFzQtQZ1mhjWA/2cMJzt tX1NAayuxbhGh9uRDoB7DEIlqkAc+Ph6WvHwdiK1FrYlZ2dicGol5gqsfI4hhMZSbPPb /+DWzwLLWL4U0fZUK9FMFQkpgsI9D+2glLH3YC37h6htqiA1mwdbM27pWIqrfqjqehJV zQHZ37fTuU8Kbzw5TVmb8rDdoKI0PUDGUyiIysiKv8zjuIR/P8A5vc5Xl8B01d2gIIx9 mKRpESkGy1c5TfMCIbpghryXj98Z/Aqbz0RXBh5ssMkvIWM3QOaYrbCftjfdRjiuBs0+ LYGA== ARC-Message-Signature: i=1; a=rsa-sha256; c=relaxed/relaxed; d=google.com; s=arc-20260327; h=cc:to:subject:message-id:date:references:in-reply-to:from :mime-version:dkim-signature; bh=PDzg1XnLGoASy8ht4cEwD532wRXzv3V+zbh1BTOqLQk=; fh=KRWpOUVUb6PgSdBVDB+V+zdegqhVAongcWJguCVTXWM=; b=J+rQP617fDVsHiVUY6ucKkjUpT+Bym9fwQ3pENrwnjWTS32XzWCtL+Biyl0KNcdEx/ PXjrbZK+o/OZG2Y6LKPv1jBXEHp0SauzNYeOZyLdvpd2+zRCX0wyINMujqrrroGAvbbC 2gGWEF10lLIDfvNEPT8BG/jbVYPU63e0ufs3KO7lHHxCVB8tvSwynT1pTXdE3Jzja4p0 BJh3HBwvB57Gxk/9Hp9Gg34Pi55Lx71F8piL4RfqyOqvphsReHWLhbQwwjjN1u8TTKhn 3cGDFhupm31YEOv163pPT8wuWPrVgerEoe6HGbWQvjS90CgxqqTTeRGSaQmntrXMXLca NxYA==; darn=lists.libcamera.org ARC-Authentication-Results: i=1; mx.google.com; arc=none DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20251104; t=1792690383; x=1793295183; darn=lists.libcamera.org; h=content-type:cc:to:subject:message-id:date:references:in-reply-to :from:mime-version:from:to:cc:subject:date:message-id:reply-to :content-type; bh=PDzg1XnLGoASy8ht4cEwD532wRXzv3V+zbh1BTOqLQk=; b=guVjB8u4Pc1PKBLFb7RLJNN048gDfRqEXgJX9cQyxUIoEdJHtyYrFfDeRW2OeDEBZG fUeuaqJpbhl96MKqXluUfkFS35pZqs93+lwLYVf1mkKkAO/Pr6R7zmTZNf8dehmCX9MC PnoYt1YMyusH7OYGDNQvmQt5wOc3hAqvA3qIPNww8FMxZ/5TdNhNcK9jK3prlvH2y2zt jUyfmTwE1eaNlgoTVKH51a05TMPxxI6lKLMkEgXCjgtjFEgTQ1t661Nt9WARUlamlaRU FTbLZE/AGExtahkQFFtTo0KTPySaw9szxLX8gdy96Uw6CilGjtSv5hRCYUsCtuImvZ1P AM8w== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20260707; t=1792690383; x=1793295183; h=content-type:cc:to:subject:message-id:date:references:in-reply-to :from:mime-version:x-gm-gg:x-gm-message-state:from:to:cc:subject :date:message-id:reply-to:content-type; bh=PDzg1XnLGoASy8ht4cEwD532wRXzv3V+zbh1BTOqLQk=; b=IoKqQMmrD6BXMFrTYGJUL9mZaHRW2mIHh6Sx+f7yihL6+pZec3Rwvj1aDfzGX44hZ/ kk/ZcPvLJ7wRFVuJelxJolTmLJnfZd7/qgZuErtXv0wLrxu7kmDxBD5OYlMdfLJ7GGQ4 H6IjqBS83+Tze2EJVkGDDvby9+fjjDMW030GgbRz1G6G/oO4piSo/yUF/bILlb+lWKb9 KT0tG2HFykvRoYmX5vfWABBZ/K57yACLx9nN8YrzCSh+U66q4EjrmMI27Jh7KxRcYBKb iiUs9wk7jkivaidE+fqlF+g70CpoewuWU/jY1fAejabVe0kisKzc6BlLeIfaA6FpQAT0 0Vuw== X-Gm-Message-State: AFq9FYJOMF+8RKCrdGtL/e5TrAxVN8j8Aw87qoDUSB8xNr/BgGFxUbbi jFu8zXhdOfzkJi/jp8reelWoIjdtEGN3mnOn6jnbSTmPPl/fKX7XH3TCeX2eQE+MGX0kBSl6FuV SACVwkz0AYr7+c8lx6U4G/nnxWOBpVH+BLg== X-Gm-Gg: AYBFou0ciB35Ljr33OEKTL5iJpH2WHaFqxHh+5ML5U1K7JQ100L/s4zd1cfbiQonpVx EY5Wu/AWs56Im13zuUAHlkejmbF786iY8UA4uiKppPv68Ali1cljbPf4cR0M6RF903vMuWVBrCG tgrAs7esYE9/JOdnqGAhcLFsNn+1p01yW8ytQbbdnFRHKjm3PCCKa31K3isfQyjyZzDDGkgA2QQ tNqvra8fa6Lmadf04bZzNzL3nPKx8vY8Ffxufyji9xLGZNsVWdBXWk8xXouVnqY1CVTgiiOwCw0 MT/mTOAJpbQSWyfOaL/Q43jMLnWtIPnkRr7/zY/ORWxnbsL7KgkbGRfwjD7ZJSXDObs3by32+Wp 3WfFrU3EBIZ/h6RD/JMFC3NyFqTwbcROiN4xl3BFVxPKCWkXtBN9F55mkEVSYWZuTqJXpcZKgqu y0FWtZjlb14MbJ X-Received: by 2002:a05:690e:43d7:b0:679:bfcf:a0fe with SMTP id 956f58d0204a3-67a21d7262dmr1411037d50.18.1792690383043; Thu, 22 Oct 2026 10:33:03 -0700 (PDT) Received: from 77377267392 named unknown by gmailapi.google.com with HTTPREST; Thu, 22 Oct 2026 13:33:02 -0400 Received: from 77377267392 named unknown by gmailapi.google.com with HTTPREST; Thu, 22 Oct 2026 13:33:02 -0400 MIME-Version: 1.0 From: Ray Strode In-Reply-To: References: Date: Thu, 22 Oct 2026 13:33:02 -0400 X-Gm-Features: AclHuK9s_gn60pwJ46DKANKiwdu_EiGr1zhccPsp59dSHBVwUTmwFjieBXA3kcY Message-ID: Subject: [PATCH v2 1/2] utils: Find maintainers from Git history To: libcamera-devel@lists.libcamera.org Cc: =?utf-8?q?Barnab=C3=A1s_P=C5=91cze?= , Kieran Bingham , Jacopo Mondi , Laurent Pinchart , Milan Zamazal , Umang Jain X-BeenThere: libcamera-devel@lists.libcamera.org X-Mailman-Version: 2.1.29 Precedence: list List-Id: List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Errors-To: libcamera-devel-bounces@lists.libcamera.org Sender: "libcamera-devel" Patch 1/2 is attached to preserve long lines. On Thu, 22 Oct 2026 13:33:00 -0400, Ray Strode wrote: > Hi, > > Resending with patch attachments to preserve long lines. > > This series adds utils/get_maintainer.py to help contributors find > maintainers and reviewers for their patches, and documents its use in > the contribution guide. > > The script accepts patches or repository paths and uses Git history to > rank review and acknowledgment credits ahead of authorship. It follows > the kernel script's input conventions and supports plain recipient > output for git send-email --cc-cmd. > > --Ray > > Ray Strode (2): > utils: Find maintainers from Git history > Documentation: contributing: Document the maintainer lookup script From 8fd62207ed7404e5f1231392ed33624f42451c7f Mon Sep 17 00:00:00 2001 From: Ray Strode Date: Mon, 19 Oct 2026 13:30:12 -0400 Subject: [PATCH v2 1/2] utils: Find maintainers from Git history From: Ray Strode This commit adds get_maintainer.py to find maintainers and reviewers from local Git history. Contributors can supply patches or repository paths instead of searching commit logs manually for people familiar with an area. The utility ranks review and acknowledgment credits ahead of authorship, normalizes identities through mailmap, and limits contacts per path so busy areas do not crowd out quiet ones. Each result includes roles and commit counts to explain the match. History ends at the configured upstream by default to exclude local unsubmitted work. Kernel-style options control the history window, selection thresholds, and annotations for use with git send-email. Signed-off-by: Ray Strode --- utils/get_maintainer.py | 316 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 316 insertions(+) create mode 100755 utils/get_maintainer.py diff --git a/utils/get_maintainer.py b/utils/get_maintainer.py new file mode 100755 index 00000000..3b6af514 --- /dev/null +++ b/utils/get_maintainer.py @@ -0,0 +1,316 @@ +#!/usr/bin/env python3 +# SPDX-License-Identifier: GPL-2.0-or-later +# Copyright (C) 2026, Red Hat Inc. +# +# Author: Ray Strode +# +# Find libcamera maintainers and reviewers from local Git history. + +import argparse +import subprocess +import sys +from dataclasses import dataclass, field +from datetime import datetime, timezone +from email.headerregistry import Address +from email.utils import parseaddr +from pathlib import Path, PurePosixPath + + +MAILING_LIST = 'libcamera-devel@lists.libcamera.org' + + +def git(directory, *args, input=None): + """Run a read-only Git command, keeping diagnostics off standard output.""" + result = subprocess.run(['git', '--no-pager', '--literal-pathspecs', + '-C', str(directory), *args], + input=input, stdout=subprocess.PIPE, + stderr=subprocess.PIPE) + if result.returncode: + raise ValueError(result.stderr.decode(errors='replace').strip()) + return result.stdout.decode('utf-8', errors='surrogateescape') + + +def repository(): + try: + root = git(Path.cwd(), 'rev-parse', '--show-toplevel') + except ValueError: + root = git(Path(__file__).resolve().parent, 'rev-parse', + '--show-toplevel') + return Path(root.strip()).resolve() + + +def default_revision(root): + try: + return git(root, 'rev-parse', '--symbolic-full-name', '@{upstream}').strip() + except ValueError: + return 'HEAD' + + +def file_path(root, name, revision): + # Resolve the parent so that a tracked symlink is treated as a file, even + # when its target is outside the repository. + path = Path(name).absolute() + path = path.parent.resolve() / path.name + try: + relative = path.relative_to(root).as_posix() + except ValueError: + raise ValueError(f'{name}: path is outside the repository') from None + + if not path.exists() and not git(root, 'ls-tree', revision, '--', relative): + raise ValueError(f'{name}: no such file in the worktree or history tip') + return relative or '.' + + +def patch_paths(root, data): + """Let Git parse filenames, including quoted names, renames and deletions.""" + paths = set() + # Forward statistics name a rename's destination; reverse statistics name + # its source. Neither command applies the patch or changes the worktree. + for direction in ((), ('--reverse',)): + output = git(root, 'apply', '--numstat', '-z', *direction, input=data) + for record in output.split('\0'): + if not record: + continue + name = record.split('\t', 2)[2] + path = PurePosixPath(name) + if path.is_absolute() or '..' in path.parts: + raise ValueError(f'{name}: patch path is outside the repository') + paths.add(path.as_posix()) + if not paths: + raise ValueError('patch contains no changed paths') + return paths + + +@dataclass +class Commit: + oid: str + timestamp: int + author: str + reviewers: set + ackers: set + + +def history(root, revision, since, path): + # Ask Git to parse trailers; prose containing "Reviewed-by" is not a credit. + # NUL separators keep the variable-length fields unambiguous. + fmt = ('%H%x00%ct%x00%an <%ae>%x00' + '%(trailers:key=Reviewed-by,valueonly,unfold)%x00' + '%(trailers:key=Acked-by,valueonly,unfold)') + output = git(root, 'log', '--no-merges', '--no-notes', + '--no-show-signature', '-z', f'--format={fmt}', + f'--since-as-filter={since}', revision, '--', path) + fields = output.split('\0')[:-1] + commits = [] + for offset in range(0, len(fields), 5): + oid, timestamp, author, reviews, acks = fields[offset:offset + 5] + commits.append(Commit(oid, int(timestamp), author, + set(reviews.splitlines()), set(acks.splitlines()))) + return commits + + +def identities(root, commits, use_mailmap): + """Canonicalize authors and trailer identities together, once per address.""" + names = set() + for commit in commits: + names.update({commit.author} | commit.reviewers | commit.ackers) + names = sorted(names) + if use_mailmap and names: + data = ('\n'.join(names) + '\n').encode('utf-8', errors='surrogateescape') + canonical = git(root, 'check-mailmap', '--stdin', input=data).splitlines() + else: + canonical = names + + result = {} + for original, mapped in zip(names, canonical): + name, address = parseaddr(mapped) + if '@' in address: + result[original] = (name, address.lower()) + return result + + +@dataclass +class Contact: + name: str + email: str + reviews: set = field(default_factory=set) + acks: set = field(default_factory=set) + authorship: set = field(default_factory=set) + latest: int = 0 + + def rank(self): + # Review experience comes first. Authorship supplies additional contacts + # and breaks ties; the date is the matching commit's date, not a review + # timestamp. Each commit contributes at most once to the review count. + return (-len(self.reviews | self.acks), -len(self.reviews), + -len(self.authorship), -self.latest, self.email) + + def description(self, total, stats): + roles = [] + for role, commits in (('reviewer', self.reviews), ('acker', self.acks), + ('author', self.authorship)): + if not commits: + continue + if stats: + roles.append(f'{role}:{len(commits)}/{total}' + f'={len(commits) * 100 / total:.0f}%') + else: + roles.append(role) + if stats: + date = datetime.fromtimestamp(self.latest, timezone.utc).date() + roles.append(f'last-commit:{date}') + return ', '.join(roles) + + +def contacts(commits, names): + people = {} + for commit in commits: + for members, role in (({commit.author}, 'authorship'), + (commit.reviewers, 'reviews'), + (commit.ackers, 'acks')): + for identity in members: + if identity not in names: + continue + name, email = names[identity] + person = people.setdefault(email, Contact(name, email)) + if commit.timestamp > person.latest: + person.name = name + person.latest = commit.timestamp + getattr(person, role).add(commit.oid) + return people + + +def select_contacts(people, total, args): + selected = [] + for person in sorted(people.values(), key=Contact.rank): + count = len(person.reviews | person.acks | person.authorship) + if count < args.git_min_signatures: + continue + if count * 100 < args.git_min_percent * total: + continue + selected.append(person.email) + if len(selected) == args.git_max_maintainers: + break + return selected + + +def add_boolean_argument(parser, name, default, help_text): + group = parser.add_mutually_exclusive_group() + group.add_argument(f'--{name}', dest=name, action='store_true', help=help_text) + group.add_argument(f'--no{name}', f'--no-{name}', dest=name, + action='store_false', help=f'disable --{name}') + parser.set_defaults(**{name: default}) + + +def parse_arguments(argv): + parser = argparse.ArgumentParser( + description='Find maintainers and reviewers from local Git history.', + usage='%(prog)s [options] patchfile ...\n' + ' %(prog)s [options] -f file-or-directory ...', + epilog='Reads a patch from stdin when no patchfile is given; use - for ' + 'stdin explicitly. History is enabled by default. Contacts are ' + 'selected using Reviewed-by, Acked-by and authorship.') + parser.add_argument('inputs', nargs='*', help='patch files, or paths with -f') + parser.add_argument('-f', '--file', action='store_true', + help='interpret positional arguments as repository paths') + parser.add_argument('--git', action='store_true', + help='use Git history (enabled by default)') + parser.add_argument('--email', action='store_true', + help='print email addresses (enabled by default)') + parser.add_argument('--git-since', default='1-year-ago', metavar='DATE', + help='Git date limiting history (default: 1-year-ago)') + parser.add_argument('--git-revision', metavar='REF', + help='history tip (default: branch upstream, or HEAD)') + parser.add_argument('--git-min-signatures', type=int, default=1, metavar='N', + help='minimum distinct matching commits (default: 1)') + parser.add_argument('--git-min-percent', type=float, default=5, metavar='PCT', + help='minimum share of matching commits (default: 5)') + parser.add_argument('--git-max-maintainers', type=int, default=5, metavar='N', + help='maximum suggestions per input path (default: 5)') + add_boolean_argument(parser, 'mailmap', True, 'normalize identities with Git mailmap') + add_boolean_argument(parser, 'roles', True, 'annotate each address with its roles') + add_boolean_argument(parser, 'rolestats', True, 'include commit counts and last commit date') + add_boolean_argument(parser, 'n', True, 'include names with email addresses') + add_boolean_argument(parser, 'l', True, 'include the libcamera-devel mailing list') + args = parser.parse_intermixed_args(argv[1:]) + if args.file and not args.inputs: + parser.error('-f requires at least one file or directory') + if args.git_min_signatures < 1 or args.git_max_maintainers < 1: + parser.error('commit and contact limits must be positive') + if not 0 <= args.git_min_percent <= 100: + parser.error('--git-min-percent must be between 0 and 100') + if not args.inputs and sys.stdin.isatty(): + parser.error('provide a patchfile, stdin, or -f file-or-directory') + return args + + +def main(argv): + args = parse_arguments(argv) + try: + root = repository() + ref = args.git_revision or default_revision(root) + revision = git(root, 'rev-parse', '--verify', '--end-of-options', + f'{ref}^{{commit}}').strip() + if args.file: + paths = {file_path(root, name, revision) for name in args.inputs} + else: + paths = set() + for name in args.inputs or ['-']: + if name == '-': + data = sys.stdin.buffer.read() + else: + data = Path(name).read_bytes() + paths.update(patch_paths(root, data)) + + if git(root, 'rev-parse', '--is-shallow-repository').strip() == 'true': + print('warning: shallow repository; reviewer history is incomplete', + file=sys.stderr) + + scopes = [] + for path in sorted(paths): + commits = history(root, revision, args.git_since, path) + if not commits: + parent = PurePosixPath(path).parent.as_posix() + # An immediate parent can supply contacts for a new file. Never + # widen a query to the entire repository implicitly. + if parent != '.': + print(f'No recent history for {path}; checking {parent}/', + file=sys.stderr) + commits = history(root, revision, args.git_since, parent) + if not commits: + print(f'No recent history for {path}', file=sys.stderr) + scopes.append(commits) + + all_commits = {commit.oid: commit for scope in scopes for commit in scope} + names = identities(root, all_commits.values(), args.mailmap) + selected = set() + for commits in scopes: + selected.update(select_contacts(contacts(commits, names), + len(commits), args)) + + # Select per path so a busy subsystem cannot crowd out a quiet one. + # Merge evidence by commit ID before printing, so overlapping paths + # cannot inflate a person's counts. + people = contacts(all_commits.values(), names) + for person in sorted(people.values(), key=Contact.rank): + if person.email not in selected or (args.l and person.email == MAILING_LIST): + continue + if args.n: + address = str(Address(person.name, addr_spec=person.email)) + else: + address = person.email + if args.roles or args.rolestats: + address += f' ({person.description(len(all_commits), args.rolestats)})' + print(address) + if args.l: + address = MAILING_LIST + if args.roles or args.rolestats: + address += ' (open list:libcamera-devel)' + print(address) + return 0 + except (OSError, ValueError) as error: + print(f'{Path(argv[0]).name}: {error}', file=sys.stderr) + return 1 + + +if __name__ == '__main__': + sys.exit(main(sys.argv)) base-commit: 7278a59eba64358bbf3d51f557506dd5b0bce5c4 -- 2.56.0 From patchwork Thu Oct 22 17:33:35 2026 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Ray Strode X-Patchwork-Id: 28566 Return-Path: X-Original-To: parsemail@patchwork.libcamera.org Delivered-To: parsemail@patchwork.libcamera.org Received: from lancelot.ideasonboard.com (lancelot.ideasonboard.com [92.243.16.209]) by patchwork.libcamera.org (Postfix) with ESMTPS id 9FB77C324C for ; Thu, 22 Oct 2026 17:33:39 +0000 (UTC) Received: from lancelot.ideasonboard.com (localhost [IPv6:::1]) by lancelot.ideasonboard.com (Postfix) with ESMTP id 5856E68F93; Thu, 22 Oct 2026 19:33:39 +0200 (CEST) Authentication-Results: lancelot.ideasonboard.com; dkim=pass (2048-bit key; unprotected) header.d=gmail.com header.i=@gmail.com header.b="UhF0cuiJ"; dkim-atps=neutral Received: from mail-yx1-xb12f.google.com (mail-yx1-xb12f.google.com [IPv6:2607:f8b0:4864:20::b12f]) by lancelot.ideasonboard.com (Postfix) with ESMTPS id 900CF68F8B for ; Thu, 22 Oct 2026 19:33:37 +0200 (CEST) Received: by mail-yx1-xb12f.google.com with SMTP id 956f58d0204a3-67489c8202cso133937d50.3 for ; Thu, 22 Oct 2026 10:33:37 -0700 (PDT) ARC-Seal: i=1; a=rsa-sha256; t=1792690416; cv=none; d=google.com; s=arc-20260327; b=qMDzp1GbEMdN8Sm4SBDaELKgyQ8NkkMLzO9dtzlprhiOiI1aB/C+/6AWMlrJ5sAN4j yG49OYJ/Y+8/x/qUYlus7E1+XYwPoKrM7fZUA6jec6WGi1+X3Yjp5eIXVx4dCbMxBy+M eN0sAWfyv3NCqtzTVd92VxFAKCkHgbVun5YDHhAvKbdwd/ubr5AcGmr5i2ACB/ZcMmQR PdadGst/AoVYwqFvnrOJ237z0ddDEMK/mJIAm4pVs9gLAXyl4H2/QIKeemimSq+pW6dr jc9danfeW9/iCIBmYimEvx84qqapjGjLDOMxHM+Bp0vuuxa8n+Tl7Xes9XrQuQlm2MTd +gtA== ARC-Message-Signature: i=1; a=rsa-sha256; c=relaxed/relaxed; d=google.com; s=arc-20260327; h=cc:to:subject:message-id:date:references:in-reply-to:from :mime-version:dkim-signature; bh=UCiPL27jfqfx9vO0rM9h9ReVL/Re20eh770GUGqsx+w=; fh=KRWpOUVUb6PgSdBVDB+V+zdegqhVAongcWJguCVTXWM=; b=B/0bpL0hkKRi29nlX0X7qY8R/Rv6DFxTiSDtnX6c59QsgCVuTW9+uCY/yX1RmeIDFJ lyGq/kPpHf2es9/vZ2xoU8sMRNiZUz7IF844tbRWbM4/bJvdtvwFTSfe9AnB5gy5LexW M7DPQ1pIHv7YZwvpB8tnSuXdf/MmXQUohhXRqLV/Ziwpi89nXEeA7vONDlCK9pp6Nscx o4GwClw1n8JSuJlUx9tTrGCO70LxwsVtjjK5J2HBjQvytD+pjKnyQV3pcrBsARvu8tMr aOX+s9yDLpQKtWkgcO3zyT1H24pvpHDf+4XAGD61VX9ojZII8o0zp83XWi/ebJ8rzRvB fOaQ==; darn=lists.libcamera.org ARC-Authentication-Results: i=1; mx.google.com; arc=none DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20251104; t=1792690416; x=1793295216; darn=lists.libcamera.org; h=content-type:cc:to:subject:message-id:date:references:in-reply-to :from:mime-version:from:to:cc:subject:date:message-id:reply-to :content-type; bh=UCiPL27jfqfx9vO0rM9h9ReVL/Re20eh770GUGqsx+w=; b=UhF0cuiJ/a9PM3hVNWDrRux9yjXgndydF1YqWyAizeOdnsUifRLrpFV+vE95FQTaRz 2I3VyYjGqoEn1mGksDHRtXVoQFAPgtj01HPa4SljWz+EtKk5XphZ8mm03xEw9Ikr5OyI nQjIRtLimdIRhSHsM1I78JZgMh6mhPLPNcjQ715J0DRvjEDCu4/I2zKXErMxNcKNg18H unXFeDdNRDltfQeyiGZKBXd5hKfiQRDs7X8IofhSCnms3JUWbrlHO/5N/RuXgeHj7fYO 0DB1Q1e8T5CJGrY3xA063To5ZeUlqM+Hu6szW5FzKroKkzcJJCvYqbtgr/+QffYU3H88 I2NQ== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20260707; t=1792690416; x=1793295216; h=content-type:cc:to:subject:message-id:date:references:in-reply-to :from:mime-version:x-gm-gg:x-gm-message-state:from:to:cc:subject :date:message-id:reply-to:content-type; bh=UCiPL27jfqfx9vO0rM9h9ReVL/Re20eh770GUGqsx+w=; b=gcuBV6vh9C9lytUWC4TgqZ6C+a3i7BVgpfaL56NnR4Kkpx0EPQOkJgJZybcPgUBiWG Wq20F2BdsJz4SDwhojUw1QmWjdjym5AcLztsJbN5ATVpgCWsapyC/JUf5FNzgXXZygJM iduVrSXxO2xP/iBaYOIbd8/zjRluM8TgHTJzoCWxrEnoT7wR9PqcO3h+sbosTznm+1Dz ac+6/59QNdKK/zkrOpnn1zuRT9S4Kiq/PLbGwRQTAak7sGWQEM1wEAnaHr15t1YJCDiI m3y9nLrsP+elX+KNk7oWynCu7TjduD+wtVVQcOvRY9nxFULACYHsFpElUsOZ/c+HOQOP OkBQ== X-Gm-Message-State: AFq9FYKSFNZt/B/zsaKEo6dhHr72Zq43MpDWmTPkfLoIHPFgdZN1fO19 OE0sYd8gK4hDdmn8pjRE1lGZWV4satMdKorErdMVPY/bnpaJBr5KyrEhy02MzDY2nYmR6ZPqnCd p/U96O8YpjcdUYh0bsOySOXtsn2ZD6bMVOg== X-Gm-Gg: AYBFou3zCxn0uERJ/WE5eouyc+0HaJn05N1FhthnMdxsN0kPva8E6JgHEmDe8MY1E3E +a15ejGOw/ZM2uliDdP9CpT0/186nGxaWS+0M5dtSfuet9coPqzukNe/VTXe3+y1N23hqelFkG4 d+Cf7fhhSCGYz3xxbUpW1dlVKFicJT4druIvsF0YCeQC3WPaCC6wPLTKcuEapjncJBAGXJG8sft jH/gdBgeiXQ/ZjexCujzqRt7fCFHgJXyx9bl1Di/X757gzeefAsnAUhZdZvcDzdtLO6VDgGjP8e 6CyqnchDIqiMeBxcY6QW6CLD3qE9Vdwab6kVoPKQpAUL6HVzonmdUbhU7oUo+naUOMkG4iB58+p C8PSmSHyN56sUHXO81PznPAWVEgqBbLrk0V+UNXPge1NfM4EVGtqBxOi3XMOVZfWeyJ4dYyv8MX An9N02IMTBuosP X-Received: by 2002:a05:690e:e8c:b0:676:8974:9788 with SMTP id 956f58d0204a3-67a0e5a2905mr3054585d50.96.1792690416380; Thu, 22 Oct 2026 10:33:36 -0700 (PDT) Received: from 77377267392 named unknown by gmailapi.google.com with HTTPREST; Thu, 22 Oct 2026 13:33:35 -0400 Received: from 77377267392 named unknown by gmailapi.google.com with HTTPREST; Thu, 22 Oct 2026 13:33:35 -0400 MIME-Version: 1.0 From: Ray Strode In-Reply-To: References: Date: Thu, 22 Oct 2026 13:33:35 -0400 X-Gm-Features: AclHuK9ZZH0O-1UwNP1DGqnj04v11tezgDUTmnirXR9l-R5W6HJUc0u6WKNcGf4 Message-ID: Subject: [PATCH v2 2/2] Documentation: contributing: Document the maintainer lookup script To: libcamera-devel@lists.libcamera.org Cc: =?utf-8?q?Barnab=C3=A1s_P=C5=91cze?= , Kieran Bingham , Jacopo Mondi , Laurent Pinchart , Milan Zamazal , Umang Jain X-BeenThere: libcamera-devel@lists.libcamera.org X-Mailman-Version: 2.1.29 Precedence: list List-Id: List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Errors-To: libcamera-devel-bounces@lists.libcamera.org Sender: "libcamera-devel" Patch 2/2 is attached to preserve long lines. On Thu, 22 Oct 2026 13:33:00 -0400, Ray Strode wrote: > Hi, > > Resending with patch attachments to preserve long lines. > > This series adds utils/get_maintainer.py to help contributors find > maintainers and reviewers for their patches, and documents its use in > the contribution guide. > > The script accepts patches or repository paths and uses Git history to > rank review and acknowledgment credits ahead of authorship. It follows > the kernel script's input conventions and supports plain recipient > output for git send-email --cc-cmd. > > --Ray > > Ray Strode (2): > utils: Find maintainers from Git history > Documentation: contributing: Document the maintainer lookup script From 44ebaa5a29799eee71c7bb2a00a4ca90d11bb8cd Mon Sep 17 00:00:00 2001 From: Ray Strode Date: Mon, 19 Oct 2026 13:30:27 -0400 Subject: [PATCH v2 2/2] Documentation: contributing: Document the maintainer lookup script From: Ray Strode This commit documents get_maintainer.py in the patch submission guide. The guide explains mailing list submission but does not show contributors how to find maintainers and reviewers for the affected code. The examples cover path and patch inputs plus recipient output suitable for git send-email. The text explains the history boundary, time window, parent-directory fallback, and how review credits determine the results. Signed-off-by: Ray Strode --- Documentation/contributing.rst | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/Documentation/contributing.rst b/Documentation/contributing.rst index 20682d56..b762bf11 100644 --- a/Documentation/contributing.rst +++ b/Documentation/contributing.rst @@ -83,60 +83,85 @@ Documentation ------------- Project documentation is created using `Sphinx`_. Source level documentation uses `Doxygen`_. Please make sure to document all code during development. .. _Sphinx: https://www.sphinx-doc.org .. _Doxygen: https://www.doxygen.nl Submitting Patches ------------------ The libcamera project has high standards of stability, efficiency and reliability. To achieve those, the project goes to great length to produce code that is as easy to read, understand and maintain as possible. This is made possible by a set of :ref:`coding-style-guidelines` that all submissions are expected to follow. We also care about the quality of commit messages. A good commit message not only describes what a commit does, but why it does so. By conveying clear information about the purpose of the commit, it helps speeding up reviews. Regardless of whether you're new to git or have years of experience, https://cbea.ms/git-commit/ is always a good guide to read to improve your commit message writing skills. The patch submission process for libcamera is similar to the Linux kernel, and goes through the `libcamera-devel`_ mailing list. If you have no previous experience with ``git-send-email``, or just experience trouble configuring it for your e-mail provider, the sourcehut developers have put together a detailed guide available at https://git-send-email.io/. +To find maintainers and reviewers for the code changed by your patch, use +``utils/get_maintainer.py``. It reads local Git history and accepts patches, +standard input, or paths with ``-f``, following the Linux kernel utility's +input conventions:: + + utils/get_maintainer.py --git-since=2-years-ago -f src/gstreamer/ + utils/get_maintainer.py 0001-camera-fix.patch + +The output includes roles and review, acknowledgment and authorship counts for +each contact. +History ends at the branch's upstream when configured, or ``HEAD`` otherwise; +``--git-revision`` overrides that choice. The default history window is one +year, so quiet areas may need a longer window. A path with no recent history +uses its immediate parent directory, except the repository root. Statistics +count whole commits that touch the selected paths, including broader refactors. + +For use with ``git send-email``, disable the annotations:: + + git send-email --cc-cmd='utils/get_maintainer.py --noroles --norolestats' \ + 0001-camera-fix.patch + +The utility includes ``libcamera-devel`` by default. Use ``--nol`` if you +already provide the list with ``--to``. See ``--help`` for the supported subset +of kernel utility options, including history limits and mailmap support. + Patches submitted to the libcamera project must be certified as suitable for integration into an open source project. As such libcamera follows the same model as utilised by the Linux kernel, and requires the use of 'Signed-off-by:' tags in all patches. By signing your contributions you are certifying your work in accordance with the following: `Developer's Certificate of Origin`_ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Version 1.1 Copyright (C) 2004, 2006 The Linux Foundation and its contributors. 1 Letterman Drive Suite D4700 San Francisco, CA, 94129 Everyone is permitted to copy and distribute verbatim copies of this license document, but changing it is not allowed. Developer's Certificate of Origin 1.1 By making a contribution to this project, I certify that: (a) The contribution was created in whole or in part by me and I have the right to submit it under the open source license indicated in the file; or (b) The contribution is based upon previous work that, to the best -- 2.56.0