-;;; debbugs-gnu.el --- interface for the GNU bug tracker
+;;; debbugs-gnu.el --- interface for the GNU bug tracker -*- lexical-binding:t -*-
;; Copyright (C) 2011-2016 Free Software Foundation, Inc.
(require 'debbugs)
(require 'tabulated-list)
(require 'add-log)
-(require 'subr-x)
-(eval-when-compile (require 'cl))
+(eval-when-compile (require 'subr-x))
+(eval-when-compile (require 'cl-lib))
(autoload 'article-decode-charset "gnus-art")
(autoload 'diff-goto-source "diff-mode")
(autoload 'diff-hunk-file-names "diff-mode")
(autoload 'gnus-article-mime-handles "gnus-art")
+(autoload 'gnus-fetch-field "gnus-util")
(autoload 'gnus-read-ephemeral-emacs-bug-group "gnus-group")
(autoload 'gnus-summary-article-header "gnus-sum")
(autoload 'gnus-summary-select-article "gnus-sum")
(const "tagged"))
:version "24.1")
+(defcustom debbugs-gnu-suppress-closed t
+ "If non-nil, don't show closed bugs."
+ :group 'debbugs-gnu
+ :type 'boolean
+ :version "25.2")
+
(defconst debbugs-gnu-all-severities
(mapcar 'cadr (cdr (get 'debbugs-gnu-default-severities 'custom-type)))
"*List of all possible severities.")
"Face for reports that are pending.")
(defface debbugs-gnu-stale '((t (:foreground "orange")))
- "Face for reports that have not been touched for a week.")
+ "Face for reports that have not been touched for two weeks.")
(defface debbugs-gnu-done '((t (:foreground "DarkGrey")))
"Face for closed bug reports.")
be empty, in this case only the following attributes are used for
search."))
+;;;###autoload
+(defun debbugs-gnu-patches ()
+ "List the bug reports that have been marked as containing a patch."
+ (interactive)
+ (debbugs-gnu nil '("emacs") nil nil "patch"))
+
;;;###autoload
(defun debbugs-gnu-search ()
"Search for Emacs bugs interactively.
(setq phrase nil)
(add-to-list 'debbugs-gnu-current-query (cons 'phrase phrase)))
;; We suppress closed bugs if there is no phrase.
- (setq debbugs-gnu-current-suppress (null phrase))
+ (setq debbugs-gnu-current-suppress
+ (if (not debbugs-gnu-suppress-closed)
+ nil
+ (null phrase)))
;; The other queries.
(catch :finished
(insert-file-contents debbugs-gnu-persistency-file)
(eval (read (current-buffer)))))
;; Per default, we suppress retrieved unwanted bugs.
- (when (called-interactively-p 'any)
+ (when (and (called-interactively-p 'any)
+ debbugs-gnu-suppress-closed)
(setq debbugs-gnu-current-suppress t))
;; Add queries.
(mapcar
(lambda (x) (cdr (assoc "id" x)))
(apply 'debbugs-search-est args)))
- ;; User tags.
- (tags
- (setq args (mapcar (lambda (x) (if (eq x :package) :user x)) args))
- (apply 'debbugs-get-usertag args))
;; Otherwise, we retrieve the bugs from the server.
(t (apply 'debbugs-get-bugs args)))))
-(defun debbugs-gnu-show-reports ()
- "Show bug reports."
+(defun debbugs-gnu-show-reports (&optional offline)
+ "Show bug reports.
+If OFFLINE is non-nil, the query is not sent to the server. Bugs
+are taken from the cache instead."
(let ((inhibit-read-only t)
(buffer-name "*Emacs Bugs*"))
;; The tabulated mode sets several local variables. We must get
;; Print bug reports.
(dolist (status
- (apply 'debbugs-get-status
- (debbugs-gnu-get-bugs debbugs-gnu-local-query)))
+ (let ((debbugs-cache-expiry (if offline nil debbugs-cache-expiry))
+ ids)
+ (apply 'debbugs-get-status
+ (if offline
+ (progn
+ (maphash (lambda (key _elem)
+ (push key ids))
+ debbugs-cache-data)
+ (sort ids '<))
+ (debbugs-gnu-get-bugs debbugs-gnu-local-query)))))
(let* ((id (cdr (assq 'id status)))
(words
(mapconcat
(submitter (aref cols 2))
(submitter-length (nth 1 (aref tabulated-list-format 2)))
(title (aref cols 3))
- (title-length (nth 1 (aref tabulated-list-format 3))))
+ ;; (title-length (nth 1 (aref tabulated-list-format 3)))
+ )
(when (and
;; We may have a narrowing in effect.
(or (not debbugs-gnu-limit)
t)
(t nil))))
-(defun debbugs-gnu-sort-title (s1 s2)
+(defun debbugs-gnu-sort-title (s1 _s2)
(let ((owner (if (cdr (assq 'owner (car s1)))
(car (mail-header-parse-address
(decode-coding-string (cdr (assq 'owner (car s1)))
(re-search-forward "#\\([0-9]+\\)" nil t)))
(string-to-number (match-string 1)))))
+(defvar debbugs-gnu-send-mail-function nil
+ "A function to send control messages from debbugs.")
+
(defun debbugs-gnu-send-control-message (message &optional reverse)
"Send a control message for the current bug report.
You can set the severity or add a tag, or close the report. If
"usertag")
nil t)
current-prefix-arg))
- (let* ((id (or debbugs-gnu-bug-number ; Set on group entry.
- (debbugs-gnu-guess-current-id)
- (debbugs-gnu-current-id)))
+ (let* ((id (or (debbugs-gnu-current-id t)
+ debbugs-gnu-bug-number ; Set on group entry.
+ (debbugs-gnu-guess-current-id)))
(version
(when (member message '("close" "done"))
(read-string
(format "tags %d%s %s\n"
id (if reverse " -" "")
message))))
- (funcall send-mail-function)
+ (funcall (or debbugs-gnu-send-mail-function send-mail-function))
(remhash id debbugs-cache-data)
(message-goto-body)
(message "Control message sent:\n%s"
;; buffer. Determine which.
(gnus-with-article-buffer
(dolist (handle (mapcar 'cdr (gnus-article-mime-handles)))
- (when (string-match "diff\\|patch" (mm-handle-media-type handle))
+ (when (string-match "diff\\|patch\\|plain" (mm-handle-media-type handle))
(push (cons (mm-handle-encoding handle)
(mm-handle-buffer handle))
patch-buffers))))
(article-decode-charset)
(push (cons nil gnus-article-buffer) patch-buffers))
(dolist (elem patch-buffers)
- (with-temp-buffer
+ (with-current-buffer (generate-new-buffer "*debbugs input patch*")
(insert-buffer-substring (cdr elem))
(cond ((eq (car elem) 'base64)
(base64-decode-region (point-min) (point-max)))
(message "%s is a contributor %d times" string found)
found))
+(defvar debbugs-gnu-patch-subject nil)
+
(defun debbugs-gnu-insert-changelog ()
"Add a ChangeLog from a recently applied patch from a third party."
(interactive)
- (let (from subject)
+ (let (from subject patch-subject changelog)
(gnus-with-article-buffer
(widen)
(goto-char (point-min))
(setq from (mail-extract-address-components (gnus-fetch-field "from"))
- subject (gnus-fetch-field "subject")))
+ subject (gnus-fetch-field "subject"))
+ ;; If it's a patch formatted the right way, extract that data.
+ (dolist (handle (mapcar 'cdr (gnus-article-mime-handles)))
+ (when (string-match "diff\\|patch\\|plain"
+ (mm-handle-media-type handle))
+ (with-temp-buffer
+ (insert-buffer-substring (mm-handle-buffer handle))
+ (cond ((eq (mm-handle-encoding handle) 'base64)
+ (base64-decode-region (point-min) (point-max)))
+ ((eq (mm-handle-encoding handle) 'quoted-printable)
+ (quoted-printable-decode-region (point-min) (point-max))))
+ (setq patch-subject
+ (or (gnus-fetch-field "subject") patch-subject))
+ (goto-char (point-min))
+ (when (re-search-forward "^[*] " nil t)
+ (let ((start (match-beginning 0)))
+ (while (and (not (eobp))
+ (not (looking-at "---")))
+ (forward-line 1))
+ (setq changelog (buffer-substring
+ start (line-end-position 0)))))))))
(let ((add-log-full-name (car from))
(add-log-mailing-address (cadr from)))
(add-change-log-entry-other-window)
+ (when patch-subject
+ (setq-local debbugs-gnu-patch-subject patch-subject))
+ (when changelog
+ (delete-region (line-beginning-position) (point-max))
+ (save-restriction
+ (narrow-to-region (point) (point))
+ (insert changelog)
+ (indent-region (point-min) (point-max))))
(let ((point (point)))
(when (string-match "\\(bug#[0-9]+\\)" subject)
(insert " (" (match-string 1 subject) ")."))
(cadr from))))))
(goto-char (point-max))
(end-of-line)
- (insert " (tiny change"))
+ (when changelog
+ (insert "\n\n"))
+ (insert " Copyright-paperwork-exempt: yes"))
(goto-char point)))))
(defvar debbugs-gnu-lisp-mode-map
"Prepare checking in the current changes."
(interactive)
(save-some-buffers t)
- (when (get-buffer "*vc-dir*")
- (kill-buffer (get-buffer "*vc-dir*")))
- (let ((trunk (expand-file-name debbugs-gnu-trunk-directory)))
- (if (equal (cl-subseq default-directory 0 (length trunk))
- trunk)
- (vc-dir debbugs-gnu-trunk-directory)
- (vc-dir debbugs-gnu-branch-directory)))
- (goto-char (point-min))
- (while (not (search-forward "edited" nil t))
- (sit-for 0.01))
- (beginning-of-line)
- (while (search-forward "edited" nil t)
- (vc-dir-mark)
- (beginning-of-line))
- (vc-diff nil)
- (vc-next-action nil)
- (log-edit-insert-changelog t)
- (delete-other-windows)
- (split-window)
- (other-window 1)
- (switch-to-buffer "*vc-diff*")
- (other-window 1))
+ (when (get-buffer "*vc-dir*")
+ (kill-buffer (get-buffer "*vc-dir*")))
+ (let ((patch-subject debbugs-gnu-patch-subject))
+ (let ((trunk (expand-file-name debbugs-gnu-trunk-directory)))
+ (if (equal (cl-subseq default-directory 0 (length trunk))
+ trunk)
+ (vc-dir debbugs-gnu-trunk-directory)
+ (vc-dir debbugs-gnu-branch-directory)))
+ (goto-char (point-min))
+ (while (not (search-forward "edited" nil t))
+ (sit-for 0.01))
+ (beginning-of-line)
+ (while (search-forward "edited" nil t)
+ (vc-dir-mark)
+ (beginning-of-line))
+ (vc-diff nil)
+ (vc-next-action nil)
+ (delete-region (point-min) (point-max))
+ (log-edit-insert-changelog t)
+ (delete-other-windows)
+ (split-window)
+ (other-window 1)
+ (switch-to-buffer "*vc-diff*")
+ (other-window 1)
+ (when patch-subject
+ (insert "Summary: "
+ (replace-regexp-in-string "^ *\\[PATCH\\] *" "" patch-subject)
+ "\n"))))
+
+(defun debbugs-gnu-save-cache ()
+ "Save the bugs cache to a file."
+ (interactive)
+ (unless debbugs-cache-data
+ (error "No data to cache"))
+ (unless (file-exists-p "~/.emacs.d/debbugs-cache")
+ (make-directory "~/.emacs.d/debbugs-cache" t))
+ (let ((coding-system-for-write 'utf-8))
+ (with-temp-file "~/.emacs.d/debbugs-cache/list"
+ (prin1 debbugs-cache-data (current-buffer)))))
(provide 'debbugs-gnu)