From: Arun Isaac <arunisaac@systemreboot.net>
To: 61645@debbugs.gnu.org
Cc: Ricardo Wurmus <rekado@elephly.net>,
Arun Isaac <arunisaac@systemreboot.net>
Subject: [bug#61645] [PATCH v2 3/4] client: Support sending email to issues.
Date: Tue, 21 Feb 2023 00:33:36 +0000 [thread overview]
Message-ID: <20230221003336.5374-1-arunisaac@systemreboot.net> (raw)
In-Reply-To: <20230220013821.27440-1-arunisaac@systemreboot.net>
* mumi/client.scm: Import (rnrs io ports), (srfi srfi-71), (srfi
srfi-171), (ice-9 match), (ice-9 popen), (web client), (web response)
and (email email).
(issue-number-of-message, call-with-input-pipe, git-send-email): New
functions.
(send-email): New public function.
* scripts/mumi.in (show-mumi-usage): Document send-email subcommand.
(main): Add send-email subcommand.
* tests/client.scm: New file.
* Makefile.am (SCM_TESTS): Add tests/client.scm.
---
Makefile.am | 1 +
mumi/client.scm | 105 ++++++++++++++++++++++++++++++++++++++++++++++-
scripts/mumi.in | 5 +++
tests/client.scm | 93 +++++++++++++++++++++++++++++++++++++++++
4 files changed, 203 insertions(+), 1 deletion(-)
create mode 100644 tests/client.scm
diff --git a/Makefile.am b/Makefile.am
index a8c11a1..86ba4f0 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -58,6 +58,7 @@ SOURCES = \
TEST_EXTENSIONS = .scm
SCM_TESTS = \
+ tests/client.scm \
tests/debbugs.scm \
tests/xapian.scm
diff --git a/mumi/client.scm b/mumi/client.scm
index ae3a0a9..09f83ee 100644
--- a/mumi/client.scm
+++ b/mumi/client.scm
@@ -17,18 +17,27 @@
;;; along with mumi. If not, see <http://www.gnu.org/licenses/>.
(define-module (mumi client)
+ #:use-module (rnrs io ports)
#:use-module (srfi srfi-19)
#:use-module (srfi srfi-26)
#:use-module (srfi srfi-43)
+ #:use-module (srfi srfi-71)
+ #:use-module (srfi srfi-171)
+ #:use-module (ice-9 match)
+ #:use-module (ice-9 popen)
#:use-module (term ansi-color)
+ #:use-module (web client)
+ #:use-module (web response)
#:use-module (web uri)
+ #:use-module (email email)
#:use-module (kolam http)
#:use-module (mumi config)
#:use-module (mumi web view utils)
#:export (search
print-current-issue
set-current-issue!
- clear-current-issue!))
+ clear-current-issue!
+ send-email))
(define (git-top-level)
"Return the top-level directory of the current git repository."
@@ -152,3 +161,97 @@
(let ((issue-file (current-issue-file)))
(when (file-exists? issue-file)
(delete-file issue-file))))
+
+(define* (issue-number-of-message message-id #:optional (retries 15))
+ "Return issue number that MESSAGE-ID belongs to. Retry RETRIES number
+of times with an interval of 60 seconds between retries."
+ ;; TODO: Re-implement this using our GraphQL endpoint once it
+ ;; supports retrieving the issue from a message ID. Later,
+ ;; re-implement this using a GraphQL subscription when kolam
+ ;; supports it.
+ (define (poll-issue-number-of-message message-id)
+ (let ((response _ (http-get (build-uri (client-config 'mumi-scheme)
+ #:host (client-config 'mumi-host)
+ #:path (string-append "/msgid/" message-id)))))
+ (and (>= (response-code response) 300)
+ (< (response-code response) 400)
+ (match (split-and-decode-uri-path
+ (uri-path (response-location response)))
+ (("issue" issue-number)
+ (string->number issue-number))))))
+
+ (let loop ((i retries))
+ (if (zero? i)
+ (begin
+ (format (current-error-port)
+ "Mail not acknowledged by issue tracker. Giving up.~%")
+ (exit #f))
+ (or (poll-issue-number-of-message message-id)
+ (begin
+ (let ((retry-interval 60))
+ (format (current-error-port)
+ "Trial ~a/~a: Server has not yet received our email. Will retry in ~a seconds.~%"
+ (1+ i) retries retry-interval)
+ (sleep retry-interval))
+ (loop (1- retries)))))))
+
+(define (call-with-input-pipe command proc)
+ "Call PROC with input pipe to COMMAND. COMMAND is a list of program
+arguments."
+ (match command
+ ((prog args ...)
+ (let ((port #f))
+ (dynamic-wind
+ (lambda ()
+ (set! port (apply open-pipe* OPEN_READ prog args)))
+ (cut proc port)
+ (cut close-pipe port))))))
+
+(define (git-send-email to patch)
+ "Send email using git send-email and return the message ID of the sent
+email."
+ (let ((command (list "git" "send-email"
+ (string-append "--to=" to)
+ patch)))
+ (display (string-join command))
+ (newline)
+ (call-with-input-pipe command
+ (lambda (port)
+ ;; FIXME: This messes up the order of stdout and stderr.
+ (let ((message-id
+ ;; Read till you get the Message ID.
+ (port-transduce (tlog (lambda (_ line)
+ (display line)
+ (newline)))
+ (rany (lambda (line)
+ (and (string-prefix-ci? "Message-ID:" line)
+ (assq-ref
+ (parse-email-headers
+ (string-append line "\n"))
+ 'message-id))))
+ get-line
+ port)))
+ ;; Pass through the rest.
+ (display (get-string-all port))
+ message-id)))))
+
+(define (send-email patches)
+ "Send PATCHES via email."
+ (match patches
+ ((first-patch other-patches ...)
+ ;; If an issue is current, send patches to that issue's email
+ ;; address. Else, send first patch to the patch email address and
+ ;; get an issue number. Then, send the remaining patches to that
+ ;; issue's email address.
+ (for-each (cute git-send-email
+ (string-append (number->string
+ (or (current-issue-number)
+ (issue-number-of-message
+ (git-send-email (client-config 'patch-email-address)
+ first-patch))))
+ "@"
+ (client-config 'debbugs-host))
+ <>)
+ (if (current-issue-number)
+ patches
+ other-patches)))))
diff --git a/scripts/mumi.in b/scripts/mumi.in
index dfd082d..2295328 100644
--- a/scripts/mumi.in
+++ b/scripts/mumi.in
@@ -126,6 +126,9 @@
`mumi new':
clear current issue presumably to open a new one.
+ `mumi send-email':
+ send patches to debbugs.
+
`mumi web [--address=address] [--port=port] [--listen-repl[=port]] [--disable-mailer]':
start the application web server.
@@ -158,6 +161,8 @@
(client:print-current-issue))
(("new")
(client:clear-current-issue!))
+ (("send-email" . patches)
+ (client:send-email patches))
(("mailer" . rest)
(let* ((opts (parse-options rest))
(sender (assoc-ref opts 'sender))
diff --git a/tests/client.scm b/tests/client.scm
new file mode 100644
index 0000000..2948aed
--- /dev/null
+++ b/tests/client.scm
@@ -0,0 +1,93 @@
+;;; mumi -- Mediocre, uh, mail interface
+;;; Copyright © 2023 Arun Isaac <arunisaac@systemreboot.net>
+;;;
+;;; This program is free software: you can redistribute it and/or
+;;; modify it under the terms of the GNU Affero General Public License
+;;; as published by the Free Software Foundation, either version 3 of
+;;; the License, or (at your option) any later version.
+;;;
+;;; This program is distributed in the hope that it will be useful,
+;;; but WITHOUT ANY WARRANTY; without even the implied warranty of
+;;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+;;; Affero General Public License for more details.
+;;;
+;;; You should have received a copy of the GNU Affero General Public
+;;; License along with this program. If not, see
+;;; <http://www.gnu.org/licenses/>.
+
+(use-modules (srfi srfi-26)
+ (srfi srfi-64)
+ (ice-9 match))
+
+(define (with-variable variable value thunk)
+ "Set VARIABLE to VALUE, run THUNK and restore the old value of
+VARIABLE. Return the value returned by THUNK."
+ (let ((old-value (variable-ref variable)))
+ (dynamic-wind
+ (cut variable-set! variable value)
+ thunk
+ (cut variable-set! variable old-value))))
+
+(define (with-variables variable-bindings thunk)
+ "Set VARIABLE-BINDINGS, run THUNK and restore the old values of the
+variables. Return the value returned by THUNK. VARIABLE-BINDINGS is a
+list of pairs mapping variables to their values."
+ (match variable-bindings
+ (((variable . value) tail ...)
+ (with-variable variable value
+ (cut with-variables tail thunk)))
+ (() (thunk))))
+
+(define-syntax-rule (var@@ module-name variable-name)
+ (module-variable (resolve-module 'module-name)
+ 'variable-name))
+
+(define (trace-calls function-variable thunk)
+ "Run THUNK and return a list of argument lists FUNCTION-VARIABLE is
+called with."
+ (let ((args-list (list)))
+ (with-variable function-variable (lambda args
+ (set! args-list
+ (cons args args-list)))
+ thunk)
+ (reverse args-list)))
+
+(define client-config-stub
+ (cons (var@@ (mumi client) client-config)
+ (lambda (key)
+ (case key
+ ((debbugs-host) "example.com")
+ ((patch-email-address) "foo@patches.com")
+ (else (error "Key unimplemented in stub" key))))))
+
+(test-begin "client")
+
+(test-equal "send patches to new issue"
+ '(("git" "send-email" "--to=foo@patches.com" "foo.patch")
+ ("git" "send-email" "--to=12345@example.com" "bar.patch")
+ ("git" "send-email" "--to=12345@example.com" "foobar.patch"))
+ (map (match-lambda
+ ((command _) command))
+ (trace-calls (var@@ (mumi client) call-with-input-pipe)
+ (lambda ()
+ (with-variables (list (cons (var@@ (mumi client) issue-number-of-message)
+ (const 12345))
+ client-config-stub)
+ (cut (@@ (mumi client) send-email)
+ (list "foo.patch" "bar.patch" "foobar.patch")))))))
+
+(test-equal "send patches to existing issue"
+ '(("git" "send-email" "--to=12345@example.com" "foo.patch")
+ ("git" "send-email" "--to=12345@example.com" "bar.patch")
+ ("git" "send-email" "--to=12345@example.com" "foobar.patch"))
+ (map (match-lambda
+ ((command _) command))
+ (trace-calls (var@@ (mumi client) call-with-input-pipe)
+ (lambda ()
+ (with-variables (list (cons (var@@ (mumi client) current-issue-number)
+ (const 12345))
+ client-config-stub)
+ (cut (@@ (mumi client) send-email)
+ (list "foo.patch" "bar.patch" "foobar.patch")))))))
+
+(test-end "client")
--
2.38.1
next prev parent reply other threads:[~2023-02-21 0:35 UTC|newest]
Thread overview: 25+ messages / expand[flat|nested] mbox.gz Atom feed top
2023-02-20 1:38 [bug#61645] [PATCH mumi 0/1] Add CLI client to search for issues Arun Isaac
2023-02-20 1:41 ` [bug#61645] [PATCH mumi 1/1] client: " Arun Isaac
2023-02-21 0:32 ` [bug#61645] [PATCH v2 0/4] Add mumi CLI client Arun Isaac
2023-02-21 0:33 ` [bug#61645] [PATCH v2 1/4] client: Add CLI client to search for issues Arun Isaac
2023-02-21 0:33 ` [bug#61645] [PATCH v2 2/4] client: Support checking in to a specific issue Arun Isaac
2023-02-21 0:33 ` Arun Isaac [this message]
2023-02-21 0:33 ` [bug#61645] [PATCH v2 4/4] Set only GUILE_LOAD_PATH when running tests Arun Isaac
2023-03-08 12:05 ` [bug#61645] [PATCH mumi 0/1] Add CLI client to search for issues Ludovic Courtès
2023-03-08 13:28 ` Arun Isaac
2023-03-08 15:36 ` [bug#61645] [mumi v3 0/4] Add mumi CLI client Arun Isaac
2023-03-08 15:36 ` [bug#61645] [mumi v3 1/4] client: Add CLI client to search for issues Arun Isaac
2023-03-08 15:36 ` [bug#61645] [mumi v3 2/4] client: Support checking in to a specific issue Arun Isaac
2023-03-08 15:36 ` [bug#61645] [mumi v3 3/4] client: Support sending email to issues Arun Isaac
2023-03-08 15:36 ` [bug#61645] [mumi v3 4/4] Set only GUILE_LOAD_PATH when running tests Arun Isaac
2023-03-30 20:47 ` [bug#61645] [PATCH mumi 0/1] Add CLI client to search for issues Ludovic Courtès
2023-03-30 20:57 ` Ricardo Wurmus
2023-03-30 21:57 ` bug#61645: " Ricardo Wurmus
2023-03-31 12:15 ` [bug#61645] " Ludovic Courtès
2023-03-31 20:32 ` Arun Isaac
2023-03-31 22:15 ` Ludovic Courtès
2023-03-31 22:51 ` Arun Isaac
2023-04-01 17:32 ` Arun Isaac
2023-04-24 14:41 ` Arun Isaac
2023-04-24 20:01 ` Ludovic Courtès
2023-04-25 12:28 ` Arun Isaac
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=20230221003336.5374-1-arunisaac@systemreboot.net \
--to=arunisaac@systemreboot.net \
--cc=61645@debbugs.gnu.org \
--cc=rekado@elephly.net \
/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.
Code repositories for project(s) associated with this external index
https://git.savannah.gnu.org/cgit/guix.git
This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.