summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorSean Whitton <spwhitton@spwhitton.name>2022-07-04 10:05:09 -0700
committerSean Whitton <spwhitton@spwhitton.name>2022-07-11 18:37:32 -0700
commit2f8ce791b1f91193867ef90411da6fd88ff588be (patch)
tree6dfb2b7e6db87c9bd42d381c87eb370f2708107a
parent1bca0a557e970c88bacb582285b198365ddfb47b (diff)
downloaddotfiles-2f8ce791b1f91193867ef90411da6fd88ff588be.tar.gz
archive notmuch.el config to git history
-rw-r--r--.emacs.d/init.el772
-rwxr-xr-x.fmail/.notmuch/hooks/post-new5
-rwxr-xr-x.fmail/.notmuch/hooks/pre-new5
-rw-r--r--.mbsyncrc4
-rw-r--r--.mrconfig.in22
-rw-r--r--.notmuch-config14
-rwxr-xr-xbin/movemymail30
-rwxr-xr-xbin/nmbug852
-rwxr-xr-xbin/xdg-open3
9 files changed, 136 insertions, 1571 deletions
diff --git a/.emacs.d/init.el b/.emacs.d/init.el
index 73f2efca..a35979f8 100644
--- a/.emacs.d/init.el
+++ b/.emacs.d/init.el
@@ -230,14 +230,22 @@ windows side-by-side in the frame."
'(magit-define-global-key-bindings nil)
'(magit-diff-refine-hunk 'all)
'(magit-save-repository-buffers nil)
- '(mail-user-agent 'notmuch-user-agent)
+ '(mail-envelope-from 'header nil nil "Bypass MTA rewriting user@localhost.")
+ '(mail-specify-envelope-from t nil nil "Bypass MTA rewriting user@localhost.")
+ '(mail-user-agent 'gnus-user-agent)
'(mailscripts-detach-head-from-existing-branch 'ask)
'(mailscripts-extract-patches-branch-prefix "mail/")
'(mailscripts-project-library 'project)
'(make-pointer-invisible t nil nil "Works only for self-insert chars and undone by changes in window manager focus, but less annoying than `mouse-avoidance-mode'.")
- '(message-auto-save-directory nil nil nil "Don't have Message, Gnus and notmuch all saving drafts.")
+ '(message-auto-save-directory "~/tmp/" nil nil "So locmaint will catch them.")
'(message-citation-line-format "On %a %d %b %Y at %I:%M%p %Z, %N wrote:\12")
'(message-citation-line-function 'message-insert-formatted-citation-line)
+ '(message-forward-as-mime nil nil nil "For compatibility.")
+ '(message-forward-before-signature nil nil nil "For compatibility.")
+ '(message-forward-included-headers
+ '("^From:" "^Subject:" "^Date:" "^To:" "^Cc:" "^Message-ID:") nil nil "For compatibility.")
+ '(message-make-forward-subject-function '(message-forward-subject-fwd) nil nil "For compatibility.")
+ '(message-sendmail-envelope-from 'header nil nil "Bypass MTA rewriting user@localhost.")
'(message-templ-alist
'(("default"
("From" . "Sean Whitton <spwhitton@spwhitton.name>"))
@@ -254,15 +262,6 @@ windows side-by-side in the frame."
'(native-comp-async-report-warnings-errors 'silent)
'(network-security-level 'high)
'(notmuch-address-use-company nil)
- '(notmuch-archive-tags '("-unread"))
- '(notmuch-fcc-dirs "sent -unread")
- '(notmuch-mua-cite-function 'message-cite-original-without-signature)
- '(notmuch-mua-user-agent-function
- '(lambda nil
- (format "Notmuch/%s Emacs/%s (%s)" notmuch-emacs-version emacs-version system-configuration)) nil nil "Drop notmuch homepage URI to reduce length.")
- '(notmuch-show-all-tags-list t)
- '(notmuch-show-insert-text/plain-hook
- '(notmuch-wash-convert-inline-patch-to-part notmuch-wash-wrap-long-lines notmuch-wash-tidy-citations notmuch-wash-elide-blank-lines notmuch-wash-excerpt-citations))
'(nov-text-width 78)
'(org-adapt-indentation t nil nil "Sometimes set to nil in .dir-locals.el, e.g. in ~/doc/newpapers.")
'(org-agenda-entry-text-maxlines 3)
@@ -1149,6 +1148,8 @@ to open them using `spw/try-external-open'")
(define-key icomplete-fido-mode-map [?\M-.] #'icomplete-forward-completions)
(define-key icomplete-fido-mode-map [?\M-,] #'icomplete-backward-completions)
+(setq skeleton-end-newline nil)
+
;;;; Buffers and windows
@@ -2114,31 +2115,29 @@ Used in my `message-mode' yasnippets."
full-name))
""))
-;;; With these skeletons, and taking advantage of how `notmuch-message-mode'
-;;; leaves point and mark around the quoted text, can immediately use C-x C-d
-;;; to kill it all if no need to quote, or C-x C-x to hop back to the top for
-;;; using M-RET to interleave responses.
-
(spw/define-skeleton spw/message-dear
- (notmuch-message-mode :abbrev "dear" :file 'notmuch-mua)
+ (message-mode :abbrev "dear" :file "message")
""
(completing-read "Dear " (ignore-errors (list (spw/recipient-first-name))))
- "Dear " str "," \n ?\n
- (when (> (mark) (point)) (exchange-point-and-mark) '\n) - "\n")
+ '(when (setq v1 (looking-at ">")) (forward-line -2))
+ "Dear " str "," \n \n
+ '(when v1 (forward-line 2)))
(spw/define-skeleton spw/message-hello
- (notmuch-message-mode :abbrev "hl" :file 'notmuch-mua)
+ (message-mode :abbrev "hl" :file "message")
""
(completing-read "Hello " (ignore-errors (list (spw/recipient-first-name))))
- "Hello " str "," \n ?\n
- (when (> (mark) (point)) (exchange-point-and-mark) '\n) - "\n")
+ '(when (setq v1 (looking-at ">")) (forward-line -2))
+ "Hello " str '(when (zerop (length str)) (delete-backward-char 1)) "," \n \n
+ '(when v1 (forward-line 2)))
(spw/define-skeleton spw/message-thanks
- (notmuch-message-mode :abbrev "ty" :file 'notmuch-mua)
+ (message-mode :abbrev "ty" :file "message")
""
(completing-read "Dear " (ignore-errors (list (spw/recipient-first-name))))
+ '(when (setq v1 (looking-at ">")) (forward-line -2))
"Dear " str "," \n \n "Thank you for your e-mail." \n \n
- '(exchange-point-and-mark) \n - "\n")
+ '(when v1 (forward-line 2)))
(defun spw/copy-to-annotated ()
(interactive)
@@ -2170,12 +2169,6 @@ Used in my `message-mode' yasnippets."
(message-goto-body)
(insert type ": " package "\n" "Version: " version "\n")))
-;; this one doesn't need a binding as it doesn't come up enough
-(defun spw/notmuch-decrypt-inline ()
- (interactive)
- (call-interactively #'mark-whole-buffer)
- (call-interactively #'epa-decrypt-armor-in-region))
-
;; if we're going to be using multiple frames, make `frame-title-format' not
;; depend on whether there are multiple frames right now
(add-function :after after-focus-change-function #'spw/set-frame-title-format)
@@ -2539,6 +2532,12 @@ mutt's review view, after exiting EDITOR."
(message-kill-to-signature)
(spw/normalise-message))
+(defun spw/message-send-and-exit ()
+ (interactive)
+ (when (or spw/message-normalised
+ (y-or-n-p "Send message which has not been auto-formatted?"))
+ (call-interactively #'message-send-and-exit)))
+
(defun spw/message-maybe-sign ()
;; no PGP signing on athena
(unless (spw/on-host-p "athena.silentflame.com")
@@ -2589,19 +2588,6 @@ mutt's review view, after exiting EDITOR."
(while (looking-at re)
(delete-region (point) (1+ (line-end-position))))))))
-;; Use this to mark sent mail as containing unresolved comments., e.g. when
-;; responding to a patch posting. Remove the flag from the message when the
-;; next version of the patch series is seen to resolve the review comments.
-;;
-;; Don't use this for review comments where I'll notice, without effort,
-;; that the revised series does not address the comments. E.g. don't flag a
-;; review comment only objecting to a clone-and-hack of a function: I'll
-;; notice the clone-and-hack if it still remains in the revised series, so
-;; no need to go back and look at that review comment on the previous series
-(defun spw/message-fcc-flag ()
- (interactive)
- (save-excursion (message-goto-fcc) (insert " +spw::unresolved")))
-
(with-eval-after-load 'message
(spw/when-library-available message-templ
(define-key message-mode-map [f7] #'spw/unfinalise-message)
@@ -2621,6 +2607,10 @@ mutt's review view, after exiting EDITOR."
(add-to-list 'auto-mode-alist '("/mutt-.+$" . message-mode))
(add-hook 'message-mode-hook #'spw/mutt-mail-header-separator)
+ ;; This relies on user.primary_email, user.other_email notmuch config keys.
+ (spw/when-library-available notmuch-address
+ (require 'notmuch-address) (notmuch-address-setup))
+
(add-hook 'message-mode-hook #'footnote-mode)
;; this is for the benefit of mutt
@@ -2629,7 +2619,8 @@ mutt's review view, after exiting EDITOR."
(define-key message-mode-map
[remap message-newline-and-reformat] #'spw/message-newline-and-reformat)
- (define-key message-mode-map "\C-ciu" #'spw/message-fcc-flag))
+ (define-key message-mode-map
+ [remap message-send-and-exit] #'spw/message-send-and-exit))
;;;; Dired
@@ -2687,555 +2678,6 @@ mutt's review view, after exiting EDITOR."
#'spw/bookmark-eww-bookmark-make-record)))
-;;;; The Notmuch e-mail system's Emacs interface
-
-(setq notmuch-tagging-keys '(("u" ("+unread") "Mark as unread")
- ("s" ("-unread" "+spam") "Mark as spam")
-
- ;; 'm' for 'mute'
- ("m" ("-unread" "+spw::killed") "Kill thread")
-
- ;; for work mail sent to a personal
- ;; address, or similar
- ("w" ("+spw::work") "Mark as work-related")
-
- ("b" ("+spw::browse") "Mark for browsing")
- ("d" ("-unread" "+deleted") "Send to trash")
- ("f" ("-unread" "+flagged") "Unread->flagged")
- ("F" ("-flagged") "Unflag message"))
-
- ;; default is t, but given that notmuch searches run to the
- ;; beginning of time, and we are likely to want recent mail, we want
- ;; newer e-mails at the top
- notmuch-search-oldest-first nil
-
- ;; Don't collapse cited text. We ought to be able to just remove
- ;; `notmuch-wash-excerpt-citations' from
- ;; `notmuch-show-insert-text/plain-hook', but that function is also
- ;; responsible for colouring cited text (this is an upstream bug:
- ;; that function does the colouring for performance reasons but the
- ;; right answer is to use fontlocking, not overlays, for the
- ;; colouring)
- notmuch-wash-citation-lines-prefix 10000
- notmuch-wash-citation-lines-suffix 10000
-
- ;; have Emacs set envelope-from to bypass my MTA rewriting of
- ;; user@localhost
- mail-specify-envelope-from t
- mail-envelope-from 'header
- message-sendmail-envelope-from 'header
-
- ;; when 'unread' is being used as an inbox, want manual resolution
- ;; of messages
- notmuch-show-mark-read-function 'ignore
- notmuch-show-mark-read-tags nil
- ;; but always resolve when I write a reply
- notmuch-message-replied-tags '("-unread" "+replied")
-
- ;; for compatibility
- message-forward-before-signature nil
- message-forward-as-mime nil
- message-forward-included-headers
- '("^From:" "^Subject:" "^Date:" "^To:" "^Cc:" "^Message-ID:")
- message-make-forward-subject-function #'message-forward-subject-fwd)
-
-(spw/when-library-available notmuch
- ;; Ensure `notmuch-user-agent' is loaded, `notmuch-saved-searches' is
- ;; populated etc. when I invoke certain commands soon after starting Emacs.
- (defun spw/require-notmuch (&rest ignore) (require 'notmuch))
- (dolist (cmd '(compose-mail notmuch-jump-search notmuch-hello
- compose-mail-other-window compose-mail-other-frame))
- (advice-add cmd :before #'spw/require-notmuch)))
-
-(advice-add #'notmuch-bury-or-kill-this-buffer :override #'bury-buffer)
-
-;; An alternative would be just to bind `notmuch-hello' to C-c m, as s, j
-;; and <f9> have appropriate bindings in `notmuch-hello-mode' such that the
-;; following complete sequences would still call their associated commands.
-(global-set-key "\C-cms" #'notmuch-search)
-(global-set-key "\C-cmj" #'notmuch-jump-search)
-(global-set-key "\C-cmm" #'notmuch-hello)
-(global-set-key [?\C-c ?m f9] #'spw/next-unread-group)
-
-(with-eval-after-load 'notmuch
- (require 'notmuch) (require 'notmuch-hello) (require 'notmuch-message)
-
- (advice-add 'notmuch-tree-archive-thread :after #'notmuch-tree-next-thread)
-
- (define-key notmuch-message-mode-map "\C-c\C-s" #'message-goto-subject)
-
- (define-key notmuch-show-mode-map "\C-cg.g" #'spw/notmuch-import-gpg)
- (define-key notmuch-show-mode-map "\C-cg.a"
- #'spw/notmuch-show-apply-part-to-project)
-
- ;; we want these not to be adjacent keys
- (define-key notmuch-search-mode-map [f5] #'spw/spam-message)
- (define-key notmuch-search-mode-map "S" #'spw/spam-message)
- (define-key notmuch-search-mode-map [f7] #'spw/kill-thread)
- (define-key notmuch-search-mode-map "\M-k" #'spw/kill-thread)
- (define-key notmuch-search-mode-map "," #'spw/maybe-kill-thread)
- (define-key notmuch-search-mode-map [f9] #'spw/next-unread-group)
-
- ;; ditto
- (define-key notmuch-show-mode-map [f5] #'spw/spam-message)
- (define-key notmuch-show-mode-map "S" #'spw/spam-message)
- (define-key notmuch-show-mode-map [f7] #'spw/kill-thread)
- (define-key notmuch-show-mode-map "\M-k" #'spw/kill-thread)
- (define-key notmuch-show-mode-map "," #'spw/maybe-kill-thread)
-
- ;; ditto
- (define-key notmuch-tree-mode-map [f5] #'spw/spam-message)
- (define-key notmuch-tree-mode-map "S" #'spw/spam-message)
- (define-key notmuch-tree-mode-map [f7] #'spw/kill-thread)
- (define-key notmuch-tree-mode-map "\M-k" #'spw/kill-thread)
- (define-key notmuch-tree-mode-map [f9] #'spw/next-unread-group)
-
- (define-key notmuch-hello-mode-map [f9] #'spw/next-unread-group)
-
- (define-key notmuch-tree-mode-map "\C-cgo" #'spw/notmuch-reader)
- (define-key notmuch-tree-mode-map "\C-z\C-c" #'spw/notmuch-catchup)
-
- (define-key notmuch-show-mode-map "\C-cgo" #'spw/notmuch-reader)
- (define-key notmuch-search-mode-map "\C-z\C-c" #'spw/notmuch-catchup)
-
- (define-key notmuch-show-mode-map " "
- #'spw/notmuch-show-advance-and-archive)
-
- (define-key notmuch-message-mode-map [remap message-send-and-exit]
- #'spw/notmuch-mua-send-and-exit)
-
- (define-key notmuch-show-mode-map "\C-cgf"
- #'spw/notmuch-show-filter-thread-patches)
- (define-key notmuch-show-mode-map "\C-cgi"
- #'spw/notmuch-show-with-remote-images)
-
- (add-hook 'notmuch-show-mode-hook #'variable-pitch-mode)
-
- (unless spw/lists-browse-searches (spw/standard-notmuch-saved-searches)))
-
-(defun spw/notmuch-import-gpg ()
- (interactive)
- (when (get-buffer "*notmuch-pipe*")
- (with-current-buffer "*notmuch-pipe*"
- (let ((buffer-read-only nil))
- (erase-buffer))))
- (notmuch-show-pipe-message t "gpg --decrypt | gpg --import")
- (display-buffer "*notmuch-pipe*"))
-
-;; unlike `notmuch-extract-thread-patches' and
-;; `notmuch-extract-message-patches', it does not make sense to
-;; check out a branch when performing an action which will not make
-;; a commit. If that's wanted, the code which calls
-;; `spw/notmuch-show-apply-part-to-project' should perform the checkout
-(defun spw/notmuch-show-apply-part-to-project ()
- (interactive)
- (let ((default-directory (expand-file-name (project-prompt-project-dir))))
- (notmuch-show-apply-to-current-part-handle
- (lambda (handle)
- (mm-pipe-part handle "git apply")))))
-
-(defun spw/kill-thread ()
- (interactive)
- (cl-case major-mode
- (notmuch-show-mode
- (notmuch-show-tag '("+spw::killed"))
- (notmuch-show-archive-thread-then-next))
- (notmuch-tree-mode
- (notmuch-tree-close-message-window)
- (notmuch-tree-tag '("+spw::killed"))
- (notmuch-tree-archive-thread)
- (unless (notmuch-tree-get-match)
- (notmuch-tree-next-matching-message))
- (notmuch-tree-show-message nil))
- (notmuch-search-mode
- ;; here we want to avoid tagging every message in the thread to reduce
- ;; pressure on nmbug-spw.git -- so we just pick the first of the matched
- ;; messages
- (notmuch-tag
- (car (split-string (car (plist-get (notmuch-search-get-result) :query))))
- '("+spw::killed"))
- (notmuch-search-archive-thread)))
- (message "Thread killed"))
-
-(defun spw/spam-message ()
- (interactive)
- (cl-case major-mode
- (notmuch-show-mode
- (notmuch-show-tag '("-unread" "+spam"))
- (notmuch-show-archive-message-then-next-or-next-thread))
- (notmuch-tree-mode
- (notmuch-tree-tag '("-unread" "+spam"))
- (notmuch-tree-next-matching-message)))
- (message "Message marked as spam"))
-
-(defun spw/notmuch-reader ()
- (interactive)
- (with-current-buffer (or notmuch-tree-message-buffer (current-buffer))
- (save-excursion
- (cond
- ((re-search-forward
- "https://www.wsj.com/.*-WSJNewsPaper-[0-9-]+\\.pdf" nil t)
- (call-process-shell-command
- (format "evince %s"
- (shell-quote-argument
- (buffer-substring-no-properties (match-beginning 0) (point))))
- nil 0))
- (t
- (re-search-forward "^URL:\\( \\|\n\\)")
- (let ((url (buffer-substring-no-properties (point) (line-end-position))))
- ;; alternative to eww readable view:
- ;; (start-process "firefox" nil "firefox"
- ;; "-new-window"
- ;; (concat "about:reader?url=" url))
- (spw/add-once-hook 'eww-after-render-hook #'eww-readable)
- (eww url)))))))
-
-(defun spw/notmuch-show-stable-matching-query ()
- (let (ids)
- (notmuch-show-mapc
- (lambda ()
- (let ((props (notmuch-show-get-message-properties)))
- (when (plist-get props :match)
- (push (concat "id:" (plist-get props :id)) ids)))))
- (string-join ids " ")))
-
-(defun spw/notmuch-connective (word)
- (let ((sep (format " %s " word))
- (f (apply-partially #'format "(%s)")))
- (lambda (&rest queries)
- (mapconcat f (flatten-tree queries) sep))))
-
-(defalias 'spw/nm| (spw/notmuch-connective "or"))
-(defalias 'spw/nm& (spw/notmuch-connective "and"))
-(defalias 'spw/nm~ (apply-partially #'format "not (%s)"))
-(defalias 'spw/th{ (apply-partially #'format "thread:{%s}"))
-
-(defvar spw/non-readall nil
- "Mail addressed directly to me that is to be processed as though
-it were instead addressed to a mailing list.")
-
-(defvar spw/lists-readall nil
- "Lists where I want to read all posts as though they're addressed
-directly to me. These get inserted into my main inbox view.")
-
-(defvar spw/lists-browse nil
- "Lists I want to read like newsgroups, though with no expiry
-and manual catchup. Use `spw/next-unread-group' to read new postings.")
-
-(defvar spw/lists-archiveonly nil
- "Lists to which I'm subscribed only because I want to archive all
-postings. notmuch post-new hook should mark as read.")
-
-(defvar spw/weekday-only-mail
- (spw/nm| "to:spwhitton@email.arizona.edu" "from:arizona.edu"
- (spw/th{ "tag:spw::work"))
- "Mail to be filtered out of processing views at the weekend.")
-
-(defvar spw/readall nil)
-(defvar spw/lists-browse-searches nil)
-(defun spw/standard-notmuch-saved-searches ()
- (interactive)
- (setq notmuch-saved-searches nil spw/lists-browse-searches nil)
- (when (file-exists-p (locate-user-emacs-file "notmuch-private.el"))
- (load (locate-user-emacs-file "notmuch-private"))
- (cl-loop for group in spw/lists-browse
- for name = (if (atom group)
- ;; Assume we got a List: search and extract the
- ;; first component of the List-Id to use as the
- ;; name of the search.
- (if (string-match ":\\([^.]+\\)\\." group)
- (match-string 1 group)
- (error "Could not extract a list name"))
- (plist-get group :name))
- for query
- = (if (atom group) group (spw/nm| (plist-get group :queries)))
- for usearch = `(:name ,(concat name " unread")
- :search-type nil :sort-order newest-first
- :query ,(spw/nm& "tag:unread" query))
-
- ;; We used to add the search without tag:unread with the idea of
- ;; accessing from `notmuch-hello' and then using
- ;; `notmuch-search-filter' to find something in particular. But
- ;; I just do toplevel searches.
- ;; collect `(:name ,name :search-type nil :sort-order newest-first
- ;; :query ,query :key ,(plist-get group :key))
- ;; into searches
-
- ;; Add tag:unread search as a saved search so buffers created by
- ;; `spw/next-unread-group' get a reasonable name.
- collect usearch into searches
- collect (list :search usearch
- :catchup-method (plist-get group :catchup-method))
- into browse-searches
- finally (setq notmuch-saved-searches searches
- spw/lists-browse-searches browse-searches)))
- (setq spw/readall (spw/nm& (spw/nm~ (spw/nm| spw/non-readall))
- (spw/nm~ (spw/th{ "tag:spw::browse"))
- (spw/nm| "query:inbox" spw/lists-readall)))
- (let* ((to-process (spw/nm& "tag:unread" spw/readall))
- (feeds (spw/nm| "from:rss2email@athena.silentflame.com"
- "from:gmi2email@athena.silentflame.com"))
- (categorised
- (spw/nm| spw/readall spw/lists-archiveonly
- (cl-loop for search in spw/lists-browse
- if (atom search) collect search
- else collect (plist-get search :queries))))
-
- ;; Content not from mailing lists and not otherwise categorised --
- ;; previously such items would fall into "uncategorised unread" but
- ;; that's wrong because I've explicitly subscribed to each of these.
- (uncategorised-feeds
- (spw/nm& "tag:unread" feeds (spw/nm~ categorised)))
-
- ;; Groups/lists where I don't know how or whether I want to follow
- ;; them; I may have subscribed just to post something.
- (uncategorised-other
- (spw/nm& "tag:unread" (spw/nm~ feeds) (spw/nm~ categorised))))
-
- (push `(:search (:name "Uncategorised feeds" :query ,uncategorised-feeds)
- :catchup-method :archive)
- spw/lists-browse-searches)
- (rplacd (last spw/lists-browse-searches)
- `((:search
- (:name "uncategorised unread" :query ,uncategorised-other))))
-
- ;; Prepend inbox views for processing the day's mail addressed to me.
- (setq notmuch-saved-searches
- (cl-list*
- `(:name "Weekday unread" :key "u" :search-type nil
- :sort-order oldest-first :query ,to-process)
- `(:name "Weekend unread" :key "w" :search-type nil
- :sort-order oldest-first
- :query ,(spw/nm& to-process
- (spw/nm~ spw/weekday-only-mail)))
- `(:name "Uncategorised feeds" :key "r" :search-type nil
- :sort-order newest-first :query ,uncategorised-feeds)
- notmuch-saved-searches))
-
- ;; Append some miscellaneous views.
- (rplacd (last notmuch-saved-searches)
- `((:name "Flagged" :key "f" :search-type tree :query "tag:flagged")
- (:name "Sent" :key "s" :search-type nil
- :sort-order newest-first
- :query ,(spw/nm|
- (mapcar (apply-partially #'concat "from:")
- (notmuch-user-emails))))
- (:name "Drafts" :key "d" :search-type nil
- :sort-order newest-first :query "tag:draft")
- (:name "Imported series" :key "p" :search-type nil
- :sort-order newest-first
- :query "subject:\"/PATCH .+ imported/\"")
- (:name "Phone notes" :key "n" :search-type nil
- :sort-order newest-first :query "folder:notes")
- (:name "Uncategorised unread" :key "U" :search-type nil
- :sort-order newest-first :query ,uncategorised-other)))))
-
-(defun spw/notmuch-catchup-by-archive ()
- (interactive)
- (when (and (memq major-mode '(notmuch-tree-mode notmuch-search-mode))
- (y-or-n-p "Are you sure you want to mark all as read?")
- spw/readall)
- (let ((query (if (eq major-mode 'notmuch-tree-mode)
- (notmuch-tree-get-query)
- (notmuch-search-get-query))))
- (notmuch-tag (spw/nm& query (spw/nm~ spw/readall)) '("-unread")))
- (spw/next-unread-group)))
-
-(defun spw/maybe-kill-thread (&optional resolve)
- (interactive "p")
- (unless (bound-and-true-p spw/readall)
- (error "`spw/readall' not defined; unsafe to proceed"))
- (let* ((thread-id
- (cl-ecase major-mode
- (notmuch-search-mode
- (concat "thread:"
- (plist-get (notmuch-search-get-result) :thread)))
- (notmuch-show-mode notmuch-show-thread-id)))
- (message-ids
- (cl-ecase major-mode
- (notmuch-search-mode
- (car (notmuch-search-find-stable-query)))
- (notmuch-show-mode (spw/notmuch-show-stable-matching-query))))
- (method-buffer (or notmuch-show-parent-buffer (current-buffer)))
- (catchup-method (and (buffer-local-boundp
- 'spw/notmuch-catchup-method method-buffer)
- (buffer-local-value
- 'spw/notmuch-catchup-method method-buffer)))
- (killp (not (eq :archive catchup-method))))
- ;; If any messages match `spw/readall' then for safety user must call
- ;; `spw/kill-thread', which has a harder-to-press binding.
- (unless (zerop
- (string-to-number
- (notmuch-saved-search-count (spw/nm& thread-id spw/readall))))
- (user-error "Some messages in thread match `spw/readall'"))
- ;; Catchup only the messages that were matched by the saved search.
- (notmuch-tag message-ids '("-unread"))
- ;; Kill unless we are in / came from a search in which we catchup by
- ;; marking all as read. This means we can call this function to work
- ;; through groups with either catchup method.
- ;;
- ;; As in `spw/kill-thread' for `notmuch-search-mode', want to tag only a
- ;; single message with spw::killed.
- (when killp
- (notmuch-tag (car (split-string message-ids)) '("+spw::killed")))
- (when resolve
- (cl-case major-mode
- (notmuch-search-mode
- (let* ((result (notmuch-search-get-result))
- (tags (remove "unread" (plist-get result :tags))))
- (notmuch-search-update-result
- (plist-put result
- :tags (if killp (cons "spw::killed" tags) tags))))
- (notmuch-search-next-thread))
- (notmuch-show-mode (notmuch-show-next-thread t))))))
-
-(defun spw/notmuch-catchup-by-killing ()
- (interactive)
- (when (and (eq major-mode 'notmuch-search-mode)
- (y-or-n-p "Are you sure you want to kill all threads?"))
- (goto-char (point-min))
- (while (notmuch-search-get-result)
- ;; Don't touch unless there are unread messages, so that we skip over
- ;; threads which have been manually processed -- this is in case I
- ;; just archived the thread without killing it, and want any new
- ;; messages to show up as unread.
- ;;
- ;; We can't rely on (plist-get (notmuch-show-get-result) :tags) here
- ;; because that might be out-of-date if the thread was archived from
- ;; `notmuch-show-mode' rather than this buffer, and we can't refresh
- ;; the buffer as we don't want to kill any newly-arrived threads
- (unless
- (zerop
- (string-to-number
- (notmuch-saved-search-count
- (spw/nm& "tag:unread" (car (notmuch-search-find-stable-query))))))
- (ignore-error user-error (spw/maybe-kill-thread)))
- (notmuch-search-next-thread))
- (spw/next-unread-group)))
-
-(defun spw/notmuch-show-advance-and-archive ()
- "Like `notmuch-show-advance-and-archive' but confirm thread archive.
-
-Note that this does not archive individual messages are you
-scroll through them."
- (interactive)
- (when (or ;; since we have a confirmation, it's fine to archive when point
- ;; it not yet at the bottom of the window
- (pos-visible-in-window-p (point-max))
- (notmuch-show-advance))
- (if (or (pos-visible-in-window-p (point-min))
- (let ((map (make-sparse-keymap)))
- (set-keymap-parent map query-replace-map)
- (define-key map " " 'ignore)
- ;; override usual map so SPC cannot confirm the archive, to
- ;; avoid accidental archives
- (let ((query-replace-map map))
- (y-or-n-p "Mark all as read before moving on?"))))
- (when (and notmuch-show-thread-id notmuch-archive-tags)
- ;; only tag messages which would have been expanded when we opened
- ;; the thread
- (notmuch-tag (spw/notmuch-show-stable-matching-query)
- (notmuch-tag-change-list notmuch-archive-tags nil))
- (notmuch-show-next-thread t))
- (notmuch-show-next-thread-show))))
-
-;; use on views produced by `spw/next-unread-group'
-(defun spw/notmuch-catchup (arg)
- (interactive "P")
- (if (or arg (and (boundp 'spw/notmuch-catchup-method)
- (eq :archive spw/notmuch-catchup-method)))
- (spw/notmuch-catchup-by-archive)
- (spw/notmuch-catchup-by-killing))
- (message "Group caught up"))
-
-(defun spw/next-unread-group ()
- (interactive)
- (let ((already-looking (boundp 'spw/more-unread-groups))
- (queries (bound-and-true-p spw/more-unread-groups))
- (remaining))
- (when already-looking
- (when (eq major-mode 'notmuch-tree-mode)
- (notmuch-tree-close-message-window))
- (bury-buffer))
- (if (or (and already-looking (not queries))
- (not (setq remaining
- (cl-loop with queries
- = (or queries spw/lists-browse-searches)
- if (and queries
- (zerop
- (string-to-number
- (notmuch-saved-search-count
- (plist-get
- (plist-get (car queries) :search)
- :query)))))
- do (pop queries) else return queries))))
- (set-transient-map
- (let ((map (make-sparse-keymap)))
- (define-key map [f9] #'spw/next-unread-group) map))
- (let* ((search (plist-get (car remaining) :search))
- (name (plist-get search :name)))
- ;; I think that a tree-style view is probably best for browsing
- ;; groups, but atm notmuch-tree's use of windows is a bit inflexible,
- ;; so use notmuch-search
- ;; (notmuch-tree search nil nil
- ;; (concat "*notmuch-tree-saved-search-" name "*"))
- (notmuch-search (plist-get search :query) t)
- ;; renaming the buffer seems to break refreshing it & reversing the
- ;; sort order
- ;; (rename-buffer
- ;; (concat "*notmuch-saved-search-"
- ;; (plist-get (plist-get (car remaining) :search) :name) "*")
- ;; t)
- (set (make-local-variable 'spw/more-unread-groups) (cdr remaining))
- (set (make-local-variable 'spw/notmuch-catchup-method)
- (plist-get (car remaining) :catchup-method)))
- (put 'spw/more-unread-groups 'permanent-local t)
- (put 'spw/notmuch-catchup-method 'permanent-local t))))
-
-(defun spw/notmuch-mua-send-and-exit ()
- (interactive)
- (when (or spw/message-normalised
- (y-or-n-p "Send message which has not been auto-formatted?"))
- (call-interactively #'notmuch-mua-send-and-exit)))
-
-;; In a thread with patches, try to collapse messages not relevant for
-;; reviewing those patches. Optional numeric prefix argument specifies the
-;; version of the series to review, in case there is more than one series in
-;; the thread. Include spw::unresolved mail, as these may contain unresolved
-;; review comments on older versions of the series.
-;;
-;; In the case where you want to compare the new series against unresolved
-;; review comments on the old series, and the series are in different threads,
-;; open each thread in a separate buffer (probably in separate frames). Run
-;; this command in the new series' buffer and hit `l tag:spw::unresolved RET'
-;; in the old series' buffer
-(defun spw/notmuch-show-filter-thread-patches (&optional reroll-count)
- (interactive "P")
- (let ((subject-filter
- (if reroll-count
- (let ((n (prefix-numeric-value reroll-count)))
- (if (= n 1)
- (concat "("
- "subject:/\\[.*PATCH[^v]*\\]/"
- "or"
- "subject:/\\[.*PATCH.*v1.*\\]/"
- ")")
- (concat "subject:/\\[.*PATCH.*v"
- (number-to-string n)
- ".*\\]/")))
- "subject:/\\[.*PATCH.*\\]/ ")))
- (notmuch-show-filter-thread
- (concat "tag:unread or tag:spw::unresolved or ("
- subject-filter
- " and not subject:'Re:' and not subject:'Info received')"))))
-
-(defun spw/notmuch-show-with-remote-images ()
- (interactive)
- (setq-local notmuch-show-text/html-blocked-images nil
- notmuch-multipart/alternative-discouraged '("text/plain"))
- (notmuch-show-refresh-view))
-
-
;;;; Gnus
;; If NNTPSERVER has been configured by the local administrator, accept Gnus'
@@ -3304,6 +2746,16 @@ scroll through them."
`((nnselect-specs . ,specs)
(nnselect-artlist . nil))))))))
+(defun spw/notmuch-connective (word)
+ (let ((sep (format " %s " word))
+ (f (apply-partially #'format "(%s)")))
+ (lambda (&rest queries)
+ (mapconcat f (flatten-tree queries) sep))))
+
+(defalias 'spw/nm| (spw/notmuch-connective "or"))
+(defalias 'spw/nm& (spw/notmuch-connective "and"))
+(defalias 'spw/nm~ (apply-partially #'format "not (%s)"))
+
(defun spw/gnus-group-nnselect-query (group)
(when-let ((specs (gnus-group-get-parameter group 'nnselect-specs t)))
(cdr (assq 'query
@@ -3398,6 +2850,86 @@ scroll through them."
(define-key gnus-summary-mode-map [remap gnus-summary-mark-as-read-forward]
#'spw/gnus-summary-mark-as-read-forward))
+;; Unlike `notmuch-extract-thread-patches' and
+;; `notmuch-extract-message-patches', it does not make sense to check out a
+;; branch when performing an action which will not make a commit. If that's
+;; wanted, the code which calls `spw/gnus-mime-apply-part' should perform the
+;; checkout.
+;;
+;; Probably also want `spw/gnus-article-apply-part' for summary buffers.
+(defun spw/gnus-mime-apply-part ()
+ (interactive)
+ (let ((default-directory (expand-file-name (project-prompt-project-dir))))
+ (gnus-mime-pipe-part "git apply")))
+(with-eval-after-load 'gnus-art
+ (define-key gnus-mime-button-map "a" #'spw/gnus-mime-apply-part))
+
+;; There's an alternative to having a dedicated command for this described in
+;; (info "(gnus) Security"), "Snarfing OpenPGP keys".
+(defun spw/gnus-import-gpg ()
+ (interactive)
+ (gnus-summary-save-in-pipe "gpg --decrypt | gpg --import" t)
+ (display-buffer "*Shell Command Output*"))
+(with-eval-after-load 'gnus-sum
+ (define-key gnus-summary-mode-map "\C-zg" #'spw/gnus-import-gpg))
+
+(defun spw/gnus-reader ()
+ (interactive)
+ ;; Can't use `gnus-eval-in-buffer-window' because we want eww buffer to be
+ ;; left selected, if that's what we use.
+ (gnus-summary-select-article-buffer)
+ (save-excursion
+ (cond
+ ((re-search-forward
+ "https://www.wsj.com/.*-WSJNewsPaper-[0-9-]+\\.pdf" nil t)
+ (call-process-shell-command
+ (format "evince %s"
+ (shell-quote-argument
+ (buffer-substring-no-properties (match-beginning 0) (point))))
+ nil 0)
+ ;; We used an external program, so switch back.
+ (gnus-article-show-summary))
+ (t
+ (re-search-forward "^URL:\\( \\|\n\\)")
+ (let ((url (buffer-substring-no-properties (point) (line-end-position))))
+ ;; alternative to eww readable view:
+ ;; (start-process "firefox" nil "firefox"
+ ;; "-new-window"
+ ;; (concat "about:reader?url=" url))
+ ;;
+ ;; There is also Gnus's `A w' binding.
+ (spw/add-once-hook 'eww-after-render-hook #'eww-readable)
+ (eww url))))))
+(with-eval-after-load 'gnus-sum
+ (define-key gnus-summary-mode-map "\C-cgo" #'spw/gnus-reader))
+
+;; In a group with patches, try to expunge messages not relevant for reviewing
+;; those patches. Optional numeric prefix argument specifies the version of
+;; the series to review, in case there is more than one series in the summary
+;; buffer. Include ticked messages, as these may contain unresolved review
+;; comments on older versions of the series.
+;;
+;; In the case where you want to compare the new series against review
+;; comments on the old series, and the series are in different threads, use
+;; C-c g m to open a distinct summary buffer for each thread, in two frames,
+;; use this command in the buffer with the new series, and possibly use / m to
+;; see only ticked articles in the old series' summary buffer.
+(defun spw/gnus-summary-limit-to-patches (&optional reroll-count)
+ (interactive "P")
+ (gnus-summary-limit-to-subject
+ (if reroll-count
+ (cl-case (prefix-numeric-value reroll-count)
+ (1 "\\[.*PATCH\\(?:[^v]*\\|.*v1.*\\)\\]")
+ (t (format "\\[.*PATCH.*v%s.*\\]"
+ (prefix-numeric-value reroll-count))))
+ "\\[.*PATCH.*\\]"))
+ (gnus-summary-limit-to-subject (regexp-opt '("Re:" "Info received")) nil t)
+ ;; Would be good also to reinsert all unread messages.
+ (gnus-summary-insert-ticked-articles))
+(with-eval-after-load 'gnus-sum
+ (define-key gnus-summary-mode-map
+ "\C-cgf" #'spw/gnus-summary-limit-to-patches))
+
(defun spw/org-gnus-follow-link (orig-fun &optional group article)
(if (not article)
(apply orig-fun group nil)
@@ -3504,11 +3036,6 @@ scroll through them."
(spw/when-library-available nov
(add-to-list 'auto-mode-alist '("\\.epub\\'" . nov-mode)))
-(global-set-key "\C-cvt" #'notmuch-extract-thread-patches-to-project)
-(global-set-key "\C-cvw" #'notmuch-extract-message-patches-to-project)
-(global-set-key "\C-cgb" #'notmuch-slurp-debbug)
-(global-set-key "\C-cgB" #'notmuch-slurp-this-debbug)
-
(setq ggtags-mode-line-project-name nil)
(spw/when-library-available ggtags
@@ -3915,11 +3442,10 @@ before uploading to NEW again." \n \n
("NOAGENDA" . ?A))
org-capture-templates-contexts
- '(("t" "m" ((in-mode . "notmuch-show-mode")))
- ("t" ((not-in-mode . "notmuch-show-mode")))
- ("T" ((in-mode . "notmuch-show-mode")))
- ("m" ((in-mode . "notmuch-show-mode")))
- ("f" ((in-mode . "notmuch-show-mode"))))
+ '(("t" "m" ((in-mode . "gnus-summary-mode")))
+ ("t" ((not-in-mode . "gnus-summary-mode")))
+ ("T" ((in-mode . "gnus-summary-mode")))
+ ("m" ((in-mode . "gnus-summary-mode"))))
org-capture-templates
'(("t" "Task to be refiled" entry (file org-default-notes-file)
"* TODO %^{Title}\n%?")
@@ -3932,20 +3458,7 @@ before uploading to NEW again." \n \n
;; the Org-mode link does not properly form. In Org 9.3, the escaping
;; syntax for links has changed, so might be able to do something smarter
;; than this
- "* TODO [[notmuch:id:%:message-id][%^{Title|\"%(replace-regexp-in-string \"\\\\\\[\\\\\\|\\\\\\]\" \"\" \"%:subject\")\" from %:fromname}]]\n%?")
-
- ;; This will show a thread with only flagged messages expanded.
- ;;
- ;; The purpose of this is for cases where there are multiple actionable
- ;; messages in a single thread, such that I want to view them all in a
- ;; single buffer. I flag those, and create a link to the thread using
- ;; this snippet. Creating Org links to individual messages would not
- ;; achieve this. And having an 'inbox' tag which represents actionable
- ;; but read messages would add overhead as I'd have to get used to
- ;; removing that tag from messages, and sort out syncing the tag. The
- ;; case comes up too rarely for it to be worth doing that.
- ("f" "All flagged messages in current thread" entry (file org-default-notes-file)
- "* TODO [[notmuch:thread:{id:%:message-id} and tag:flagged][%^{Title|Flagged messages in thread \"%(replace-regexp-in-string \"\\\\\\[\\\\\\|\\\\\\]\" \"\" \"%:subject\")\"}]]\n%?")
+ "* TODO [[gnus:%:group#%:message-id][%^{Title|\"%(replace-regexp-in-string \"\\\\\\[\\\\\\|\\\\\\]\" \"\" \"%:subject\")\" from %:fromname}]]\n%?")
;; ("a" "Appointment" entry (file+datetree "~/doc/org/diary.org")
;; "* %^{Time} %^{Title & location}
@@ -3978,8 +3491,9 @@ before uploading to NEW again." \n \n
(spw/remap-mark-commands org org-mode-map org-mark-subtree org-mark-element)
(with-eval-after-load 'org
- (require 'org-agenda) (require 'org-inlinetask)
- (require 'ol-notmuch nil t) (require 'org-checklist nil t)
+ (require 'org-agenda)
+ (require 'org-inlinetask)
+ (require 'org-checklist nil t)
;; With the new exporter in Org version 8, must explicitly require the
;; exporters I want to use.
@@ -4043,26 +3557,6 @@ Called by doccheckin script."
(when (string-prefix-p root (buffer-file-name buffer))
(with-current-buffer buffer (basic-save-buffer))))))
-;; the default value for `org-notmuch-open-function' is
-;; `org-notmuch-follow-link', but that function is broken: it calls
-;; `notmuch-show' with a search query rather than a thread ID. This
-;; causes `notmuch-show-thread-id' to be populated with a value
-;; which is not a thread ID, which breaks various other things
-;;
-;; so use a custom function instead
-(defun spw/org-notmuch-follow-link (search)
- (let ((thread-id
- (car (process-lines notmuch-command
- "search"
- "--output=threads"
- "--limit=1"
- "--format=text"
- "--format-version=4"
- search))))
- (notmuch-show thread-id nil nil search
- (concat "*notmuch-" search "*"))))
-(setq org-notmuch-open-function #'spw/org-notmuch-follow-link)
-
;;; Org-mode export
;;;
;;; Org-mode's export engine is great for producing versions of arbitrary Org
diff --git a/.fmail/.notmuch/hooks/post-new b/.fmail/.notmuch/hooks/post-new
index bd716980..78a68631 100755
--- a/.fmail/.notmuch/hooks/post-new
+++ b/.fmail/.notmuch/hooks/post-new
@@ -15,8 +15,3 @@ notmuch tag +spam -- folder:spam
# mark all trash as trash
notmuch tag +deleted -- folder:trash
-
-[ -r "$HOME/doc/notmuch-killfile" ] && perl \
- -we'length and system "notmuch", "tag", "-unread", "--", "($_)"
- for join ") or (", grep length, map s/^\s+|\s*(#.*)?$//gr, <>' \
- "$HOME/doc/notmuch-killfile"
diff --git a/.fmail/.notmuch/hooks/pre-new b/.fmail/.notmuch/hooks/pre-new
index a445a51e..302651d8 100755
--- a/.fmail/.notmuch/hooks/pre-new
+++ b/.fmail/.notmuch/hooks/pre-new
@@ -2,11 +2,6 @@
. $HOME/.shenv
-# In this script (actually within movemymail), we move mail according to
-# tags that may have been added since the last 'notmuch new' run.
-# Then we sync with my IMAP server. Then in the post-new script we
-# add tags to new messages in particular folders
-
offline || movemymail
# ensure that notmuch is able to detect renames by archive-fmail-to-annex
diff --git a/.mbsyncrc b/.mbsyncrc
index 6c3c5118..30fca4e7 100644
--- a/.mbsyncrc
+++ b/.mbsyncrc
@@ -71,8 +71,7 @@ Slave :fmmaildir:sent
# it doesn't matter much. At the present time the script is *not* installed.
# If I find myself having to use webmail a lot more, could create the
# INBOX.Lists folder and install it again. Cf. ~/doc/archive/fastmail.sieve
-# for the most recent more involved script I had installed, now replaced by
-# query:inbox and query:bulk.
+# for the most recent more involved script I had installed.
# if header :regex ["List-Id","List-Post"] ".+"
# {
@@ -88,6 +87,7 @@ Slave :fmmaildir:sent
# Master :fmimap:Lists
# Slave :fmmaildir:lists
+# drafts written outside of Emacs, in other clients
Channel fmaild
Master :fmimap:Drafts
Slave :fmmaildir:drafts
diff --git a/.mrconfig.in b/.mrconfig.in
index 40c39fed..491631e8 100644
--- a/.mrconfig.in
+++ b/.mrconfig.in
@@ -762,28 +762,6 @@ isclean =
checkout = git clone demeterp:realloc realloc
skip = ! mine
-[lib/nmbug-spw]
-checkout = notmuch git clone demeterp:nmbug-spw
-update = notmuch git pull
-push = notmuch git push
-sync = mr autoci; notmuch git pull && notmuch git push
-status =
- notmuch git status | grep -v "^U\s" || true
- # `nmbug status` does not catch committed but unpushed changes
- git --no-pager log --branches \
- --not --remotes \
- --simplify-by-decoration --decorate --oneline
-log = notmuch git log
-# 'notmuch git' has safety checks to avoid wiping out git because the db
-# contains no spw:: tags (`notmuch git checkout` needed), so this autocommit
-# should be safe.
-#
-# TODO (Script to) periodically drop old spw::killed tags from repo. This
-# should speed up 'notmuch git' runs on athena.
-autoci = notmuch git commit
-commit = notmuch git commit --force
-skip = ! [ -e "$HOME/local/auth/fmailsyncpass" ]
-
# --- my personal documents. Override my global update command back
# --- to the myrepos default so that git automatically pulls and
# --- merges. Skipped on non-local hosts
diff --git a/.notmuch-config b/.notmuch-config
index f49cc4d9..597482a7 100644
--- a/.notmuch-config
+++ b/.notmuch-config
@@ -95,17 +95,3 @@ synchronize_flags=true
[index]
header.List=List-Id
header.Team=X-Distro-Tracker-Team
-
-[query]
-# These are the base search queries for inboxes vs. non-inboxes. We need the
-# new sexp: modifier in notmuch 0.36 to be able to express "(not (List *))".
-# The other "List:" terms catch some bogus List-Id headers on some automated
-# mail that is in fact addressed directly to me.
-#
-# If this has to become any more complex I might move this file ->athpriv.git.
-bulk=sexp:"(List *)" or from:rss2email@athena.silentflame.com or from:gmi2email@athena.silentflame.com or to:ftpmaster@debian.org or to:ftpmaster@ftp-master.debian.org or to:cron@ftp-master.debian.org
-inbox=(to:spwhitton@spwhitton.name or to:spwhitton@email.arizona.edu or to:"Sean Whitton" or not query:bulk or List:".xt.local>" or List:".list-id.mcsv.net") and not (folder:sent or folder:/^annex/sent-/ or folder:drafts or folder:notes)
-
-[git]
-tag_prefix=spw::
-path=/home/spwhitton/lib/nmbug-spw/
diff --git a/bin/movemymail b/bin/movemymail
index 39f30eb6..16ce96ab 100755
--- a/bin/movemymail
+++ b/bin/movemymail
@@ -14,25 +14,12 @@ open our $us, "<", $0 or die $!;
exit 0 unless flock $us, LOCK_EX|LOCK_NB;
our $root = "$ENV{HOME}/.fmail";
-our $on_athena = hostfqdn eq "athena.silentflame.com";
die "no movemymail\n" if -e "$ENV{HOME}/.nomovemymail";
open my $df, "-|", "df", "-kP", $root;
<$df>, my @df_fields = split " ", <$df>;
$df_fields[3] > 1048576 or die "free space low; no movemymail\n";
-# Sync notmuch's database to maildir and to git.
-if (-d "$root/.notmuch/xapian") {
- # Skip on athena because it's v. slow atm.
- system "mr", "-d", "$ENV{HOME}/lib/nmbug-spw", "autoci" unless $on_athena;
-
- # Ensure messages tagged in Emacs are in the appropriate folder. In the
- # case of spam, this should train FastMail's spam filter.
- search2folder("drafts", "tag:draft", "and", "not", "tag:deleted");
- search2folder("spam", "tag:spam");
- search2folder("trash", "tag:deleted");
-}
-
system [0, 1], "offline";
$? >> 8 == 0 and die "we're offline; cannot further sync mail\n";
@@ -48,7 +35,7 @@ if (my $exception = $@) {
# athena's 'notmuch new' cronjob is responsible for imap-dl(1) runs. We have
# this here rather than separate cronjob entries so that hitting 'G' in
# athena's Emacs inbox displays new mail from the other accounts.
-if ($on_athena) {
+if (hostfqdn eq "athena.silentflame.com") {
system "imap-dl", "$ENV{HOME}/.config/mailscripts/imap-dl.selene";
system "imap-dl", "$ENV{HOME}/.config/mailscripts/imap-dl.catmail";
}
@@ -64,18 +51,3 @@ for (<$root/*/new/*>) {
# Useful to see if any mail has got stuck before closing laptop lid.
`mailq` =~ "Mail queue is empty" or warn "WARNING: Outbox not empty.\n";
-
-sub search2folder {
- my ($folder, @terms) = @_;
- open my $search, "-|", "notmuch", "search", "--output=files", "--",
- "(", @terms, ")", "and", "not", "folder:$folder";
- my @matches;
- for (<$search>) {
- next if m{^\Q$root\E/annex/};
- chomp;
- # If notmuch's database is out-of-date the file may no longer exist
- # because it's already been moved by a previous run of this script.
- -f and push @matches, $_
- }
- @matches and system "mdmv", @matches, "$root/$folder";
-}
diff --git a/bin/nmbug b/bin/nmbug
deleted file mode 100755
index 6580c31a..00000000
--- a/bin/nmbug
+++ /dev/null
@@ -1,852 +0,0 @@
-#!/usr/bin/env python3
-#
-# Copyright (c) 2011-2014 David Bremner <david@tethera.net>
-# W. Trevor King <wking@tremily.us>
-#
-# 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/ .
-
-"""
-Manage notmuch tags with Git
-
-Environment variables:
-
-* NMBGIT specifies the location of the git repository used by nmbug.
- If not specified $HOME/.nmbug is used.
-* NMBPREFIX specifies the prefix in the notmuch database for tags of
- interest to nmbug. If not specified 'notmuch::' is used.
-"""
-
-from __future__ import print_function
-from __future__ import unicode_literals
-
-import codecs as _codecs
-import collections as _collections
-import functools as _functools
-import inspect as _inspect
-import locale as _locale
-import logging as _logging
-import os as _os
-import re as _re
-import shutil as _shutil
-import subprocess as _subprocess
-import sys as _sys
-import tempfile as _tempfile
-import textwrap as _textwrap
-try: # Python 3
- from urllib.parse import quote as _quote
- from urllib.parse import unquote as _unquote
-except ImportError: # Python 2
- from urllib import quote as _quote
- from urllib import unquote as _unquote
-
-
-__version__ = '0.3'
-
-_LOG = _logging.getLogger('nmbug')
-_LOG.setLevel(_logging.WARNING)
-_LOG.addHandler(_logging.StreamHandler())
-
-NMBGIT = _os.path.expanduser(
- _os.getenv('NMBGIT', _os.path.join('~', '.nmbug')))
-_NMBGIT = _os.path.join(NMBGIT, '.git')
-if _os.path.isdir(_NMBGIT):
- NMBGIT = _NMBGIT
-
-TAG_PREFIX = _os.getenv('NMBPREFIX', 'notmuch::')
-_HEX_ESCAPE_REGEX = _re.compile('%[0-9A-F]{2}')
-_TAG_DIRECTORY = 'tags/'
-_TAG_FILE_REGEX = _re.compile(_TAG_DIRECTORY + '(?P<id>[^/]*)/(?P<tag>[^/]*)')
-
-# magic hash for Git (git hash-object -t blob /dev/null)
-_EMPTYBLOB = 'e69de29bb2d1d6434b8b29ae775ad8c2e48c5391'
-
-
-try:
- getattr(_tempfile, 'TemporaryDirectory')
-except AttributeError: # Python < 3.2
- class _TemporaryDirectory(object):
- """
- Fallback context manager for Python < 3.2
-
- See PEP 343 for details on context managers [1].
-
- [1]: https://www.python.org/dev/peps/pep-0343/
- """
- def __init__(self, **kwargs):
- self.name = _tempfile.mkdtemp(**kwargs)
-
- def __enter__(self):
- return self.name
-
- def __exit__(self, type, value, traceback):
- _shutil.rmtree(self.name)
-
-
- _tempfile.TemporaryDirectory = _TemporaryDirectory
-
-
-def _hex_quote(string, safe='+@=:,'):
- """
- quote('abc def') -> 'abc%20def'.
-
- Wrap urllib.parse.quote with additional safe characters (in
- addition to letters, digits, and '_.-') and lowercase hex digits
- (e.g. '%3a' instead of '%3A').
- """
- uppercase_escapes = _quote(string, safe)
- return _HEX_ESCAPE_REGEX.sub(
- lambda match: match.group(0).lower(),
- uppercase_escapes)
-
-
-_ENCODED_TAG_PREFIX = _hex_quote(TAG_PREFIX, safe='+@=,') # quote ':'
-
-
-def _xapian_quote(string):
- """
- Quote a string for Xapian's QueryParser.
-
- Xapian uses double-quotes for quoting strings. You can escape
- internal quotes by repeating them [1,2,3].
-
- [1]: https://trac.xapian.org/ticket/128#comment:2
- [2]: https://trac.xapian.org/ticket/128#comment:17
- [3]: https://trac.xapian.org/changeset/13823/svn
- """
- return '"{0}"'.format(string.replace('"', '""'))
-
-
-def _xapian_unquote(string):
- """
- Unquote a Xapian-quoted string.
- """
- if string.startswith('"') and string.endswith('"'):
- return string[1:-1].replace('""', '"')
- return string
-
-
-class SubprocessError(RuntimeError):
- "A subprocess exited with a nonzero status"
- def __init__(self, args, status, stdout=None, stderr=None):
- self.status = status
- self.stdout = stdout
- self.stderr = stderr
- msg = '{args} exited with {status}'.format(args=args, status=status)
- if stderr:
- msg = '{msg}: {stderr}'.format(msg=msg, stderr=stderr)
- super(SubprocessError, self).__init__(msg)
-
-
-class _SubprocessContextManager(object):
- """
- PEP 343 context manager for subprocesses.
-
- 'expect' holds a tuple of acceptable exit codes, otherwise we'll
- raise a SubprocessError in __exit__.
- """
- def __init__(self, process, args, expect=(0,)):
- self._process = process
- self._args = args
- self._expect = expect
-
- def __enter__(self):
- return self._process
-
- def __exit__(self, type, value, traceback):
- for name in ['stdin', 'stdout', 'stderr']:
- stream = getattr(self._process, name)
- if stream:
- stream.close()
- setattr(self._process, name, None)
- status = self._process.wait()
- _LOG.debug(
- 'collect {args} with status {status} (expected {expect})'.format(
- args=self._args, status=status, expect=self._expect))
- if status not in self._expect:
- raise SubprocessError(args=self._args, status=status)
-
- def wait(self):
- return self._process.wait()
-
-
-def _spawn(args, input=None, additional_env=None, wait=False, stdin=None,
- stdout=None, stderr=None, encoding=_locale.getpreferredencoding(),
- expect=(0,), **kwargs):
- """Spawn a subprocess, and optionally wait for it to finish.
-
- This wrapper around subprocess.Popen has two modes, depending on
- the truthiness of 'wait'. If 'wait' is true, we use p.communicate
- internally to write 'input' to the subprocess's stdin and read
- from it's stdout/stderr. If 'wait' is False, we return a
- _SubprocessContextManager instance for fancier handling
- (e.g. piping between processes).
-
- For 'wait' calls when you want to write to the subprocess's stdin,
- you only need to set 'input' to your content. When 'input' is not
- None but 'stdin' is, we'll automatically set 'stdin' to PIPE
- before calling Popen. This avoids having the subprocess
- accidentally inherit the launching process's stdin.
- """
- _LOG.debug('spawn {args} (additional env. var.: {env})'.format(
- args=args, env=additional_env))
- if not stdin and input is not None:
- stdin = _subprocess.PIPE
- if additional_env:
- if not kwargs.get('env'):
- kwargs['env'] = dict(_os.environ)
- kwargs['env'].update(additional_env)
- p = _subprocess.Popen(
- args, stdin=stdin, stdout=stdout, stderr=stderr, **kwargs)
- if wait:
- if hasattr(input, 'encode'):
- input = input.encode(encoding)
- (stdout, stderr) = p.communicate(input=input)
- status = p.wait()
- _LOG.debug(
- 'collect {args} with status {status} (expected {expect})'.format(
- args=args, status=status, expect=expect))
- if stdout is not None:
- stdout = stdout.decode(encoding)
- if stderr is not None:
- stderr = stderr.decode(encoding)
- if status not in expect:
- raise SubprocessError(
- args=args, status=status, stdout=stdout, stderr=stderr)
- return (status, stdout, stderr)
- if p.stdin and not stdin:
- p.stdin.close()
- p.stdin = None
- if p.stdin:
- p.stdin = _codecs.getwriter(encoding=encoding)(stream=p.stdin)
- stream_reader = _codecs.getreader(encoding=encoding)
- if p.stdout:
- p.stdout = stream_reader(stream=p.stdout)
- if p.stderr:
- p.stderr = stream_reader(stream=p.stderr)
- return _SubprocessContextManager(args=args, process=p, expect=expect)
-
-
-def _git(args, **kwargs):
- args = ['git', '--git-dir', NMBGIT] + list(args)
- return _spawn(args=args, **kwargs)
-
-
-def _get_current_branch():
- """Get the name of the current branch.
-
- Return 'None' if we're not on a branch.
- """
- try:
- (status, branch, stderr) = _git(
- args=['symbolic-ref', '--short', 'HEAD'],
- stdout=_subprocess.PIPE, stderr=_subprocess.PIPE, wait=True)
- except SubprocessError as e:
- if 'not a symbolic ref' in e:
- return None
- raise
- return branch.strip()
-
-
-def _get_remote():
- "Get the default remote for the current branch."
- local_branch = _get_current_branch()
- (status, remote, stderr) = _git(
- args=['config', 'branch.{0}.remote'.format(local_branch)],
- stdout=_subprocess.PIPE, wait=True)
- return remote.strip()
-
-
-def get_tags(prefix=None):
- "Get a list of tags with a given prefix."
- if prefix is None:
- prefix = TAG_PREFIX
- (status, stdout, stderr) = _spawn(
- args=['notmuch', 'search', '--output=tags', '*'],
- stdout=_subprocess.PIPE, wait=True)
- return [tag for tag in stdout.splitlines() if tag.startswith(prefix)]
-
-
-def archive(treeish='HEAD', args=()):
- """
- Dump a tar archive of the current nmbug tag set.
-
- Using 'git archive'.
-
- Each tag $tag for message with Message-Id $id is written to
- an empty file
-
- tags/encode($id)/encode($tag)
-
- The encoding preserves alphanumerics, and the characters
- "+-_@=.:," (not the quotes). All other octets are replaced with
- '%' followed by a two digit hex number.
- """
- _git(args=['archive', treeish] + list(args), wait=True)
-
-
-def clone(repository):
- """
- Create a local nmbug repository from a remote source.
-
- This wraps 'git clone', adding some options to avoid creating a
- working tree while preserving remote-tracking branches and
- upstreams.
- """
- with _tempfile.TemporaryDirectory(prefix='nmbug-clone.') as workdir:
- _spawn(
- args=[
- 'git', 'clone', '--no-checkout', '--separate-git-dir', NMBGIT,
- repository, workdir],
- wait=True)
- _git(args=['config', '--unset', 'core.worktree'], wait=True, expect=(0, 5))
- _git(args=['config', 'core.bare', 'true'], wait=True)
- _git(args=['branch', 'config', 'origin/config'], wait=True)
- existing_tags = get_tags()
- if existing_tags:
- _LOG.warning(
- 'Not checking out to avoid clobbering existing tags: {}'.format(
- ', '.join(existing_tags)))
- else:
- checkout()
-
-
-def _is_committed(status):
- return len(status['added']) + len(status['deleted']) == 0
-
-
-def commit(treeish='HEAD', message=None):
- """
- Commit prefix-matching tags from the notmuch database to Git.
- """
- status = get_status()
-
- if _is_committed(status=status):
- _LOG.warning('Nothing to commit')
- return
-
- _git(args=['read-tree', '--empty'], wait=True)
- _git(args=['read-tree', treeish], wait=True)
- try:
- _update_index(status=status)
- (_, tree, _) = _git(
- args=['write-tree'],
- stdout=_subprocess.PIPE,
- wait=True)
- (_, parent, _) = _git(
- args=['rev-parse', treeish],
- stdout=_subprocess.PIPE,
- wait=True)
- (_, commit, _) = _git(
- args=['commit-tree', tree.strip(), '-p', parent.strip()],
- input=message,
- stdout=_subprocess.PIPE,
- wait=True)
- _git(
- args=['update-ref', treeish, commit.strip()],
- stdout=_subprocess.PIPE,
- wait=True)
- except Exception as e:
- _git(args=['read-tree', '--empty'], wait=True)
- _git(args=['read-tree', treeish], wait=True)
- raise
-
-def _update_index(status):
- with _git(
- args=['update-index', '--index-info'],
- stdin=_subprocess.PIPE) as p:
- for id, tags in status['deleted'].items():
- for line in _index_tags_for_message(id=id, status='D', tags=tags):
- p.stdin.write(line)
- for id, tags in status['added'].items():
- for line in _index_tags_for_message(id=id, status='A', tags=tags):
- p.stdin.write(line)
-
-
-def fetch(remote=None):
- """
- Fetch changes from the remote repository.
-
- See 'merge' to bring those changes into notmuch.
- """
- args = ['fetch']
- if remote:
- args.append(remote)
- _git(args=args, wait=True)
-
-
-def checkout():
- """
- Update the notmuch database from Git.
-
- This is mainly useful to discard your changes in notmuch relative
- to Git.
- """
- status = get_status()
- with _spawn(
- args=['notmuch', 'tag', '--batch'], stdin=_subprocess.PIPE) as p:
- for id, tags in status['added'].items():
- p.stdin.write(_batch_line(action='-', id=id, tags=tags))
- for id, tags in status['deleted'].items():
- p.stdin.write(_batch_line(action='+', id=id, tags=tags))
-
-
-def _batch_line(action, id, tags):
- """
- 'notmuch tag --batch' line for adding/removing tags.
-
- Set 'action' to '-' to remove a tag or '+' to add the tags to a
- given message id.
- """
- tag_string = ' '.join(
- '{action}{prefix}{tag}'.format(
- action=action, prefix=_ENCODED_TAG_PREFIX, tag=_hex_quote(tag))
- for tag in tags)
- line = '{tags} -- id:{id}\n'.format(
- tags=tag_string, id=_xapian_quote(string=id))
- return line
-
-
-def _insist_committed():
- "Die if the the notmuch tags don't match the current HEAD."
- status = get_status()
- if not _is_committed(status=status):
- _LOG.error('\n'.join([
- 'Uncommitted changes to {prefix}* tags in notmuch',
- '',
- "For a summary of changes, run 'nmbug status'",
- "To save your changes, run 'nmbug commit' before merging/pull",
- "To discard your changes, run 'nmbug checkout'",
- ]).format(prefix=TAG_PREFIX))
- _sys.exit(1)
-
-
-def pull(repository=None, refspecs=None):
- """
- Pull (merge) remote repository changes to notmuch.
-
- 'pull' is equivalent to 'fetch' followed by 'merge'. We use the
- Git-configured repository for your current branch
- (branch.<name>.repository, likely 'origin', and
- branch.<name>.merge, likely 'master').
- """
- _insist_committed()
- if refspecs and not repository:
- repository = _get_remote()
- args = ['pull']
- if repository:
- args.append(repository)
- if refspecs:
- args.extend(refspecs)
- with _tempfile.TemporaryDirectory(prefix='nmbug-pull.') as workdir:
- for command in [
- ['reset', '--hard'],
- args]:
- _git(
- args=command,
- additional_env={'GIT_WORK_TREE': workdir},
- wait=True)
- checkout()
-
-
-def merge(reference='@{upstream}'):
- """
- Merge changes from 'reference' into HEAD and load the result into notmuch.
-
- The default reference is '@{upstream}'.
- """
- _insist_committed()
- with _tempfile.TemporaryDirectory(prefix='nmbug-merge.') as workdir:
- for command in [
- ['reset', '--hard'],
- ['merge', reference]]:
- _git(
- args=command,
- additional_env={'GIT_WORK_TREE': workdir},
- wait=True)
- checkout()
-
-
-def log(args=()):
- """
- A simple wrapper for 'git log'.
-
- After running 'nmbug fetch', you can inspect the changes with
- 'nmbug log HEAD..@{upstream}'.
- """
- # we don't want output trapping here, because we want the pager.
- args = ['log', '--name-status', '--no-renames'] + list(args)
- with _git(args=args, expect=(0, 1, -13)) as p:
- p.wait()
-
-
-def push(repository=None, refspecs=None):
- "Push the local nmbug Git state to a remote repository."
- if refspecs and not repository:
- repository = _get_remote()
- args = ['push']
- if repository:
- args.append(repository)
- if refspecs:
- args.extend(refspecs)
- _git(args=args, wait=True)
-
-
-def status():
- """
- Show pending updates in notmuch or git repo.
-
- Prints lines of the form
-
- ng Message-Id tag
-
- where n is a single character representing notmuch database status
-
- * A
-
- Tag is present in notmuch database, but not committed to nmbug
- (equivalently, tag has been deleted in nmbug repo, e.g. by a
- pull, but not restored to notmuch database).
-
- * D
-
- Tag is present in nmbug repo, but not restored to notmuch
- database (equivalently, tag has been deleted in notmuch).
-
- * U
-
- Message is unknown (missing from local notmuch database).
-
- The second character (if present) represents a difference between
- local and upstream branches. Typically 'nmbug fetch' needs to be
- run to update this.
-
- * a
-
- Tag is present in upstream, but not in the local Git branch.
-
- * d
-
- Tag is present in local Git branch, but not upstream.
- """
- status = get_status()
- # 'output' is a nested defaultdict for message status:
- # * The outer dict is keyed by message id.
- # * The inner dict is keyed by tag name.
- # * The inner dict values are status strings (' a', 'Dd', ...).
- output = _collections.defaultdict(
- lambda : _collections.defaultdict(lambda : ' '))
- for id, tags in status['added'].items():
- for tag in tags:
- output[id][tag] = 'A'
- for id, tags in status['deleted'].items():
- for tag in tags:
- output[id][tag] = 'D'
- for id, tags in status['missing'].items():
- for tag in tags:
- output[id][tag] = 'U'
- if _is_unmerged():
- for id, tag in _diff_refs(filter='A'):
- output[id][tag] += 'a'
- for id, tag in _diff_refs(filter='D'):
- output[id][tag] += 'd'
- for id, tag_status in sorted(output.items()):
- for tag, status in sorted(tag_status.items()):
- print('{status}\t{id}\t{tag}'.format(
- status=status, id=id, tag=tag))
-
-
-def _is_unmerged(ref='@{upstream}'):
- try:
- (status, fetch_head, stderr) = _git(
- args=['rev-parse', ref],
- stdout=_subprocess.PIPE, stderr=_subprocess.PIPE, wait=True)
- except SubprocessError as e:
- if 'No upstream configured' in e.stderr:
- return
- raise
- (status, base, stderr) = _git(
- args=['merge-base', 'HEAD', ref],
- stdout=_subprocess.PIPE, wait=True)
- return base != fetch_head
-
-
-def get_status():
- status = {
- 'deleted': {},
- 'missing': {},
- }
- index = _index_tags()
- maybe_deleted = _diff_index(index=index, filter='D')
- for id, tags in maybe_deleted.items():
- (_, stdout, stderr) = _spawn(
- args=['notmuch', 'search', '--output=files', 'id:{0}'.format(id)],
- stdout=_subprocess.PIPE,
- wait=True)
- if stdout:
- status['deleted'][id] = tags
- else:
- status['missing'][id] = tags
- status['added'] = _diff_index(index=index, filter='A')
- _os.remove(index)
- return status
-
-
-def _index_tags():
- "Write notmuch tags to the nmbug.index."
- (_, path) = _tempfile.mkstemp(dir=NMBGIT, prefix="nmbug", suffix=".index")
- query = ' '.join('tag:"{tag}"'.format(tag=tag) for tag in get_tags())
- prefix = '+{0}'.format(_ENCODED_TAG_PREFIX)
- _git(
- args=['read-tree', '--empty'],
- additional_env={'GIT_INDEX_FILE': path}, wait=True)
- with _spawn(
- args=['notmuch', 'dump', '--format=batch-tag', '--', query],
- stdout=_subprocess.PIPE) as notmuch:
- with _git(
- args=['update-index', '--index-info'],
- stdin=_subprocess.PIPE,
- additional_env={'GIT_INDEX_FILE': path}) as git:
- for line in notmuch.stdout:
- if line.strip().startswith('#'):
- continue
- (tags_string, id) = [_.strip() for _ in line.split(' -- id:')]
- tags = [
- _unquote(tag[len(prefix):])
- for tag in tags_string.split()
- if tag.startswith(prefix)]
- id = _xapian_unquote(string=id)
- for line in _index_tags_for_message(
- id=id, status='A', tags=tags):
- git.stdin.write(line)
- return path
-
-
-def _index_tags_for_message(id, status, tags):
- """
- Update the Git index to either create or delete an empty file.
-
- Neither 'id' nor the tags in 'tags' should be encoded/escaped.
- """
- mode = '100644'
- hash = _EMPTYBLOB
-
- if status == 'D':
- mode = '0'
- hash = '0000000000000000000000000000000000000000'
-
- for tag in tags:
- path = 'tags/{id}/{tag}'.format(
- id=_hex_quote(string=id), tag=_hex_quote(string=tag))
- yield '{mode} {hash}\t{path}\n'.format(mode=mode, hash=hash, path=path)
-
-
-def _diff_index(index, filter):
- """
- Get an {id: {tag, ...}} dict for a given filter.
-
- For example, use 'A' to find added tags, and 'D' to find deleted tags.
- """
- s = _collections.defaultdict(set)
- with _git(
- args=[
- 'diff-index', '--cached', '--diff-filter', filter,
- '--name-only', 'HEAD'],
- additional_env={'GIT_INDEX_FILE': index},
- stdout=_subprocess.PIPE) as p:
- # Once we drop Python < 3.3, we can use 'yield from' here
- for id, tag in _unpack_diff_lines(stream=p.stdout):
- s[id].add(tag)
- return s
-
-
-def _diff_refs(filter, a='HEAD', b='@{upstream}'):
- with _git(
- args=['diff', '--diff-filter', filter, '--name-only', a, b],
- stdout=_subprocess.PIPE) as p:
- # Once we drop Python < 3.3, we can use 'yield from' here
- for id, tag in _unpack_diff_lines(stream=p.stdout):
- yield id, tag
-
-
-def _unpack_diff_lines(stream):
- "Iterate through (id, tag) tuples in a diff stream."
- for line in stream:
- match = _TAG_FILE_REGEX.match(line.strip())
- if not match:
- message = 'non-tag line in diff: {!r}'.format(line.strip())
- if line.startswith(_TAG_DIRECTORY):
- raise ValueError(message)
- _LOG.info(message)
- continue
- id = _unquote(match.group('id'))
- tag = _unquote(match.group('tag'))
- yield (id, tag)
-
-
-def _help(parser, command=None):
- """
- Show help for an nmbug command.
-
- Because some folks prefer:
-
- $ nmbug help COMMAND
-
- to
-
- $ nmbug COMMAND --help
- """
- if command:
- parser.parse_args([command, '--help'])
- else:
- parser.parse_args(['--help'])
-
-
-if __name__ == '__main__':
- import argparse
-
- parser = argparse.ArgumentParser(
- description=__doc__.strip(),
- formatter_class=argparse.RawDescriptionHelpFormatter)
- parser.add_argument(
- '-v', '--version', action='version',
- version='%(prog)s {}'.format(__version__))
- parser.add_argument(
- '-l', '--log-level',
- choices=['critical', 'error', 'warning', 'info', 'debug'],
- help='Log verbosity. Defaults to {!r}.'.format(
- _logging.getLevelName(_LOG.level).lower()))
-
- help = _functools.partial(_help, parser=parser)
- help.__doc__ = _help.__doc__
- subparsers = parser.add_subparsers(
- title='commands',
- description=(
- 'For help on a particular command, run: '
- "'%(prog)s ... <command> --help'."))
- for command in [
- 'archive',
- 'checkout',
- 'clone',
- 'commit',
- 'fetch',
- 'help',
- 'log',
- 'merge',
- 'pull',
- 'push',
- 'status',
- ]:
- func = locals()[command]
- doc = _textwrap.dedent(func.__doc__).strip().replace('%', '%%')
- subparser = subparsers.add_parser(
- command,
- help=doc.splitlines()[0],
- description=doc,
- formatter_class=argparse.RawDescriptionHelpFormatter)
- subparser.set_defaults(func=func)
- if command == 'archive':
- subparser.add_argument(
- 'treeish', metavar='TREE-ISH', nargs='?', default='HEAD',
- help=(
- 'The tree or commit to produce an archive for. Defaults '
- "to 'HEAD'."))
- subparser.add_argument(
- 'args', metavar='ARG', nargs='*',
- help=(
- "Argument passed through to 'git archive'. Set anything "
- 'before <tree-ish>, see git-archive(1) for details.'))
- elif command == 'clone':
- subparser.add_argument(
- 'repository',
- help=(
- 'The (possibly remote) repository to clone from. See the '
- 'URLS section of git-clone(1) for more information on '
- 'specifying repositories.'))
- elif command == 'commit':
- subparser.add_argument(
- 'message', metavar='MESSAGE', default='', nargs='?',
- help='Text for the commit message.')
- elif command == 'fetch':
- subparser.add_argument(
- 'remote', metavar='REMOTE', nargs='?',
- help=(
- 'Override the default configured in branch.<name>.remote '
- 'to fetch from a particular remote repository (e.g. '
- "'origin')."))
- elif command == 'help':
- subparser.add_argument(
- 'command', metavar='COMMAND', nargs='?',
- help='The command to show help for.')
- elif command == 'log':
- subparser.add_argument(
- 'args', metavar='ARG', nargs='*',
- help="Additional argument passed through to 'git log'.")
- elif command == 'merge':
- subparser.add_argument(
- 'reference', metavar='REFERENCE', default='@{upstream}',
- nargs='?',
- help=(
- 'Reference, usually other branch heads, to merge into '
- "our branch. Defaults to '@{upstream}'."))
- elif command == 'pull':
- subparser.add_argument(
- 'repository', metavar='REPOSITORY', default=None, nargs='?',
- help=(
- 'The "remote" repository that is the source of the pull. '
- 'This parameter can be either a URL (see the section GIT '
- 'URLS in git-pull(1)) or the name of a remote (see the '
- 'section REMOTES in git-pull(1)).'))
- subparser.add_argument(
- 'refspecs', metavar='REFSPEC', default=None, nargs='*',
- help=(
- 'Refspec (usually a branch name) to fetch and merge. See '
- 'the <refspec> entry in the OPTIONS section of '
- 'git-pull(1) for other possibilities.'))
- elif command == 'push':
- subparser.add_argument(
- 'repository', metavar='REPOSITORY', default=None, nargs='?',
- help=(
- 'The "remote" repository that is the destination of the '
- 'push. This parameter can be either a URL (see the '
- 'section GIT URLS in git-push(1)) or the name of a remote '
- '(see the section REMOTES in git-push(1)).'))
- subparser.add_argument(
- 'refspecs', metavar='REFSPEC', default=None, nargs='*',
- help=(
- 'Refspec (usually a branch name) to push. See '
- 'the <refspec> entry in the OPTIONS section of '
- 'git-push(1) for other possibilities.'))
-
- args = parser.parse_args()
-
- if args.log_level:
- level = getattr(_logging, args.log_level.upper())
- _LOG.setLevel(level)
-
- if not getattr(args, 'func', None):
- parser.print_usage()
- _sys.exit(1)
-
- if args.func == help:
- arg_names = ['command']
- else:
- (arg_names, varargs, varkw) = _inspect.getargs(args.func.__code__)
- kwargs = {key: getattr(args, key) for key in arg_names if key in args}
- try:
- args.func(**kwargs)
- except SubprocessError as e:
- if _LOG.level == _logging.DEBUG:
- raise # don't mask the traceback
- _LOG.error(str(e))
- _sys.exit(1)
diff --git a/bin/xdg-open b/bin/xdg-open
index f080b31c..f6d2ad99 100755
--- a/bin/xdg-open
+++ b/bin/xdg-open
@@ -14,9 +14,6 @@ case "${1%%:*}" in
*.xlsx)
exec soffice "$1"
;;
- mailto)
- exec notmuch emacs-mua --client --create-frame "$1"
- ;;
*)
exec /usr/bin/xdg-open "$@"
;;