Subject: [bug#57646] [PATCH v2 1/4] etc: teams: Add scope support.

From: Mathieu Othacehe <[email-masked]>
Date: Mon, 12 Sep 2022 15:55:42 +0200

Add a scope list to each team.  This list defines all the files and
directories that are mentored by the team.

Also add a cc-members command that takes two Git revision strings as input,
add returns the members that should be CC'ed given the files impacted between
the two revisions.

* etc/teams.scm.in (<team>)[scope]: New field.
(team, list-teams): Adapt those procedures.
(find-team-by-scope, diff-revisions): New procedures.
(main): Add a "cc-members" command.
* doc/contributing.texi ("Teams"): Document it.
("Sending a Patch Series"): Adapt it.
---
 doc/contributing.texi | 41 ++++++++++++++++++++++++
 etc/teams.scm.in      | 74 +++++++++++++++++++++++++++++++++++++------
 2 files changed, 106 insertions(+), 9 deletions(-)

diff --git a/doc/contributing.texi b/doc/contributing.texi
index 17a54f94cc..7712f63d67 100644
--- a/doc/contributing.texi
+++ b/doc/contributing.texi
@@ -1406,6 +1406,47 @@ for more information.  You can install @command{git send-email} with
 @command{guix install git:send-email}.
 @c Debbugs bug: https://debbugs.gnu.org/db/15/15361.html
 
+To maximize the chances that you patch series is reviewed, the preferred
+submission way is to use the @code{etc/teams.scm} script to notify the
+appropriate team members (@pxref{Teams}).
+
+@unnumberedsubsec Teams
+@anchor{Teams}
+@cindex teams
+
+There are several teams mentoring different parts of the Guix source
+code.  To list all those teams, you can run from a Guix checkout:
+
+@example
+$ ./etc/teams.scm list-teams
+id: mentors
+name: Mentors
+description: A group of mentors who chaperone contributions by newcomers.
+members:
++ Christopher Baines <mail@@cbaines.net>
++ Ricardo Wurmus <rekado@@elephly.net>
++ Mathieu Othacehe <othacehe@@gnu.org>
++ jgart <jgart@@dismail.de>
++ Ludovic Courtès <ludo@@gnu.org>
+@dots{}
+@end example
+
+You can run the following command to have the @code{Mentors} team put in
+CC of a patch series:
+
+@example
+$ git send-email --to XXX@@debbugs.gnu.org $(./etc/teams.scm cc mentors) *.patch
+@end example
+
+The appropriate team or teams can also be inferred from the modified
+files.  For instance, if you want to send the two latest commits of the
+current Git repository to review, you can run:
+
+@example
+$ guix shell -D guix
+[env]$ git send-email --to XXX@@debbugs.gnu.org $(./etc/teams.scm cc-members HEAD~2 HEAD) *.patch
+@end example
+
 @node Tracking Bugs and Patches
 @section Tracking Bugs and Patches
 
diff --git a/etc/teams.scm.in b/etc/teams.scm.in
index 9f220cc489..22177422c2 100644
--- a/etc/teams.scm.in
+++ b/etc/teams.scm.in
@@ -4,6 +4,7 @@
 
 ;;; GNU Guix --- Functional package management for GNU
 ;;; Copyright © 2022 Ricardo Wurmus <[email-masked]>
+;;; Copyright © 2022 Mathieu Othacehe <[email-masked]>
 ;;;
 ;;; This file is part of GNU Guix.
 ;;;
@@ -22,23 +23,27 @@
 
 ;;; Commentary:
 
-;; This code defines development teams and team members.
+;; This code defines development teams and team members, as well as their
+;; scope.
 
 ;;; Code:
 
 (use-modules (srfi srfi-1)
              (srfi srfi-9)
+             (srfi srfi-26)
              (ice-9 format)
              (ice-9 match)
-             (guix ui))
+             (guix ui)
+             (git))
 
 (define-record-type <team>
-  (make-team id name description members)
+  (make-team id name description members scope)
   team?
   (id          team-id)
   (name        team-name)
   (description team-description)
-  (members     team-members set-team-members!))
+  (members     team-members set-team-members!)
+  (scope       team-scope))
 
 (define-record-type <person>
   (make-person name email)
@@ -49,11 +54,13 @@ (define-record-type <person>
 (define* (person name #:optional email)
   (make-person name email))
 
-(define* (team id #:key name description (members '()))
+(define* (team id #:key name description (members '())
+               (scope '()))
   (make-team id
              (or name (symbol->string id))
              description
-             members))
+             members
+             scope))
 
 (define %teams
   (make-hash-table))
@@ -272,6 +279,22 @@ (define (find-team name)
       (error (format #false
                            "no such team: ~a~%" name))))
 
+(define (find-team-by-scope files)
+  "Return the team(s) which scope matches at least one of the FILES, as list
+of file names as string."
+  (hash-fold
+   (lambda (key team acc)
+     (if (any (lambda (file)
+                (any (lambda (scope)
+                       ;; XXX: Add regex support?
+                       (string-prefix? scope file))
+                     (team-scope team)))
+              files)
+         (cons team acc)
+         acc))
+   '()
+   %teams))
+
 (define (cc . teams)
   "Return arguments for `git send-email' to notify the members of the given
 TEAMS when a patch is received by Debbugs."
@@ -293,7 +316,7 @@ (define port* (or port (current-output-port)))
    (team-members team)))
 
 (define (list-teams)
-  "Print all teams and their members."
+  "Print all teams, their scope and their members."
   (define port* (current-output-port))
   (define width* (%text-width))
   (hash-for-each
@@ -303,7 +326,7 @@ (define width* (%text-width))
 id: ~a
 name: ~a
 description: ~a
-members:
+~amembers:
 "
              (team-id team)
              (team-name team)
@@ -312,15 +335,48 @@ (define width* (%text-width))
                           (string->recutils
                            (fill-paragraph text width*
                                            (string-length "description: ")))))
-                 "<none>"))
+                 "<none>")
+             (match (team-scope team)
+               (() "")
+               (scope (format #f "scope: ~{~s ~}~%" scope))))
      (list-members team port* "+ ")
      (newline))
    %teams))
 
+
+(define (diff-revisions rev-start rev-end)
+  "Return the list of added, modified or removed files between REV-START
+and REV-END, two git revision strings."
+  (let* ((repository (repository-open (getcwd)))
+         (commit1 (commit-lookup repository
+                                 (object-id
+                                  (revparse-single repository rev-start))))
+         (commit2 (commit-lookup repository
+                                 (object-id
+                                  (revparse-single repository rev-end))))
+         (diff (diff-tree-to-tree repository
+                                  (commit-tree commit1)
+                                  (commit-tree commit2)))
+         (files '()))
+    (diff-foreach
+     diff
+     (lambda (delta progress)
+       (set! files
+             (cons (diff-file-path (diff-delta-old-file delta)) files))
+       0)
+     (const 0)
+     (const 0)
+     (const 0))
+    files))
+
+
 (define (main . args)
   (match args
     (("cc" . team-names)
      (apply cc (map find-team team-names)))
+    (("cc-members" rev-start rev-end)
+     (apply cc (find-team-by-scope
+                (diff-revisions rev-start rev-end))))
     (("list-teams" . args)
      (list-teams))
     (("list-members" . team-names)
-- 
2.37.2