aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.gitignore1
-rw-r--r--Makefile9
-rw-r--r--piem-b4.el205
-rw-r--r--piem.texi4
4 files changed, 217 insertions, 2 deletions
diff --git a/.gitignore b/.gitignore
index f02607a..8171c92 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,2 +1,3 @@
*.elc
*.info
+config.mk
diff --git a/Makefile b/Makefile
index 8b04a53..50c87d2 100644
--- a/Makefile
+++ b/Makefile
@@ -1,7 +1,12 @@
-BATCH = emacs -Q --batch
+-include config.mk
-all: piem.elc piem.info
+TRANSIENT_DIR ?= /dev/null
+
+LOAD_PATH = -L . -L $(TRANSIENT_DIR)
+BATCH = emacs -Q --batch $(LOAD_PATH)
+
+all: piem.elc piem-b4.elc piem.info
.PHONY: clean
clean:
diff --git a/piem-b4.el b/piem-b4.el
new file mode 100644
index 0000000..957994e
--- /dev/null
+++ b/piem-b4.el
@@ -0,0 +1,205 @@
+;;; piem-b4.el --- Emacs interface to the b4 tool -*- lexical-binding: t; -*-
+
+;; Copyright (C) 2020 Kyle Meyer
+
+;; Author: Kyle Meyer <kyle@kyleam.com>
+;; Keywords: vc, tools
+;; Version: 0.0.0
+;; Package-Requires: ((emacs "26.3") (transient "0.2.0"))
+
+;; This program is free software; you can redistribute it and/or modify
+;; it under the terms of the GNU 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 General Public License for more details.
+
+;; You should have received a copy of the GNU General Public License
+;; along with this program. If not, see <https://www.gnu.org/licenses/>.
+
+;;; Commentary:
+
+;;
+
+;;; Code:
+
+(require 'cl-lib)
+(require 'piem)
+(require 'subr-x)
+(require 'transient)
+
+
+;;; Options
+
+(defgroup piem-b4 nil
+ "Control the b4 tool from Emacs."
+ :link '(info-link "(piem)b4 integration")
+ :group 'piem)
+
+(defcustom piem-b4-b4-executable "b4"
+ "Which b4 executable to use."
+ :type 'string)
+
+(defcustom piem-b4-git-executable
+ (or (and (boundp 'magit-git-executable) magit-git-executable)
+ "git")
+ "Which git executable to use."
+ :type 'string)
+
+(defcustom piem-b4-use-magit (featurep 'magit)
+ "Whether to use Magit where possible."
+ :type 'boolean)
+
+
+;;; Internals
+
+(define-error 'piem-b4-error "b4 error")
+
+(defconst piem-b4-output-buffer "*piem-b4-output*")
+
+;; TODO: Use an asynchronous process.
+(defun piem-b4--call-b4 (&rest args)
+ (unless piem-b4-b4-executable
+ (user-error "piem-b4-b4-executable must be non-nil"))
+ (let ((temp-buffer-show-function (lambda (_))))
+ (with-output-to-temp-buffer piem-b4-output-buffer
+ (unless (= 0 (apply #'call-process piem-b4-b4-executable
+ nil standard-output nil
+ (remq nil args)))
+ (display-buffer piem-b4-output-buffer)
+ (signal 'piem-b4-error
+ (list (format "b4 call in %s failed" default-directory)))))))
+
+;; In many cases, we don't really need b4 to download the mbox for us,
+;; as we already have our own mbox to URL mapping. Perhaps we should
+;; default to using that, but it should still be an option to use b4
+;; so that we honor its customization/URL resolution.
+(defun piem-b4--get-mbox-file (mid coderepo args)
+ (let* ((mbox-name (format "%s.mbx" mid))
+ (outdir (file-name-as-directory
+ (make-temp-file "piem-b4-" t)))
+ (mbox-am (concat outdir mbox-name))
+ (mbox-thread (concat mbox-am "-thread"))
+ (custom-p nil))
+ (when-let ((fn (run-hook-with-args-until-success
+ 'piem-mid-to-thread-functions mid)))
+ (with-temp-file mbox-thread
+ (funcall fn)
+ (unless (= (point-max) 1)
+ (setq custom-p t))))
+ ;; Move to the coderepo so that we pick up any b4 configuration
+ ;; from there.
+ (let ((default-directory coderepo))
+ (apply #'piem-b4--call-b4 "am"
+ (and custom-p
+ (concat "--use-local-mbox=" mbox-thread))
+ (concat "--outdir=" outdir)
+ (concat "--mbox-name=" mbox-name)
+ (append args (list mid))))
+ (unless (file-exists-p mbox-am)
+ (error "Expected mbox file does not exist: %s" mbox-am))
+ mbox-am))
+
+
+;;; Commands
+
+;;;###autoload
+(defun piem-b4-am-ready-from-mbox (mbox &optional args)
+ (interactive (list (read-file-name "mbox: ")
+ (transient-args 'piem-b4-am)))
+ (apply #'piem-b4--call-b4 "am"
+ (cons (concat "--use-local-mbox=" mbox) args))
+ (display-buffer piem-b4-output-buffer))
+
+;;;###autoload
+(defun piem-b4-am-ready-from-mid (mid &optional args)
+ (interactive (list (read-string "Message ID: " nil nil (piem-mid))
+ (transient-args 'piem-b4-am)))
+ (apply #'piem-b4--call-b4 "am" (append args (list mid)))
+ (display-buffer piem-b4-output-buffer))
+
+;;;###autoload
+(defun piem-b4-am-from-mid (mid &optional args)
+ (interactive (list (or (piem-mid)
+ (read-string "Message ID: "))
+ (transient-args 'piem-b4-am)))
+ (when-let ((badopt (cl-some
+ (lambda (arg)
+ (and (string-match
+ (rx string-start
+ (group (or "--outdir" "--mbox-name")) "=")
+ arg)
+ (match-string 1 arg)))
+ args)))
+ (user-error "%s is incompatible with this command" badopt))
+ (let* ((coderepo (or (piem-inbox-coderepo)
+ (and (fboundp 'projectile-relevant-known-projects)
+ (completing-read
+ "Project: "
+ (projectile-relevant-known-projects)))
+ (and piem-b4-use-magit
+ (fboundp 'magit-read-repository)
+ (magit-read-repository))
+ (read-directory-name "Git repository: ")))
+ (mbox-file (piem-b4--get-mbox-file mid coderepo args))
+ (default-directory coderepo))
+ ;; TODO: From the mbox file (1) search for base commit and (2)
+ ;; gather information to suggest default branch name.
+
+ ;; TODO: Add branch call. Without base, will need to ask branch
+ ;; name and starting point. Detached head could be signaled with
+ ;; empty string.
+
+ ;; TODO: Optionally do more through Magit.
+ (call-process piem-b4-git-executable
+ mbox-file nil nil "am" "--scissors")
+ (if (and piem-b4-use-magit
+ (fboundp 'magit-status-setup-buffer))
+ (magit-status-setup-buffer)
+ (dired "."))))
+
+(define-infix-argument piem-b4-am:--outdir ()
+ :description "Output directory"
+ :class 'transient-option
+ :shortarg "-o"
+ :argument "--outdir="
+ :reader #'transient-read-existing-directory)
+
+(define-infix-argument piem-b4-am:--mbox-name ()
+ :description "Base file name for mbox"
+ :class 'transient-option
+ :shortarg "-n"
+ :argument "--mbox-name="
+ :reader #'read-string)
+
+(define-infix-argument piem-b4-am:--use-version ()
+ :description "Desired version of patch series"
+ :class 'transient-option
+ :shortarg "-v"
+ :argument "--use-version="
+ :reader #'transient-read-number-N+)
+
+;;;###autoload (autoload 'piem-b4-am "b4" nil t)
+(define-transient-command piem-b4-am ()
+ "Filter mbox to patches and feed to git-am"
+ ["General options"
+ ("-c" "Check newer versions" "--check-newer-revisions")
+ ("-C" "Don't use local cache" "--no-cache")
+ ("-s" "Add my signed-off-by" "--add-my-sob")
+ ("-S" "Apply trailers without checking email addresses" "--sloppy-trailers")
+ ("-t" "Apply cover letter trailers" "--apply-cover-trailers")
+ ("-T" "Do not add trailers" "--no-add-trailers")
+ (piem-b4-am:--use-version)]
+ ["Options for creating am-ready mboxes"
+ (piem-b4-am:--outdir)
+ (piem-b4-am:--mbox-name)]
+ ["Actions"
+ [("a" "Message ID -> mbox -> git-am" piem-b4-am-from-mid)]
+ [("b" "Local mbox -> am-ready mbox" piem-b4-am-ready-from-mbox)
+ ("i" "Message ID -> am-ready mbox" piem-b4-am-ready-from-mid)]])
+
+;;; piem-b4.el ends here
+(provide 'piem-b4)
diff --git a/piem.texi b/piem.texi
index 15b0e2e..137a7cb 100644
--- a/piem.texi
+++ b/piem.texi
@@ -45,6 +45,7 @@ This manual is for piem version @value{VERSION}.
@menu
* Overview::
+* b4 interface::
* Related tools::
* Contributing::
@@ -64,6 +65,9 @@ Indices
@node public-inbox
@section public-inbox
+@node b4 interface
+@chapter b4 interface
+
@node Related tools
@chapter Related tools