1 ;;; debbugs-gnu.el --- interface for the GNU bug tracker
3 ;; Copyright (C) 2011 Free Software Foundation, Inc.
5 ;; Author: Lars Magne Ingebrigtsen <larsi@gnus.org>
6 ;; Keywords: comm, hypermedia, maint
10 ;; This file is part of GNU Emacs.
12 ;; GNU Emacs is free software: you can redistribute it and/or modify
13 ;; it under the terms of the GNU General Public License as published by
14 ;; the Free Software Foundation, either version 3 of the License, or
15 ;; (at your option) any later version.
17 ;; GNU Emacs is distributed in the hope that it will be useful,
18 ;; but WITHOUT ANY WARRANTY; without even the implied warranty of
19 ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
20 ;; GNU General Public License for more details.
22 ;; You should have received a copy of the GNU General Public License
23 ;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>.
31 (eval-when-compile (require 'cl))
33 (autoload 'gnus-read-ephemeral-emacs-bug-group "gnus-group")
34 (autoload 'mail-header-subject "nnheader")
35 (autoload 'gnus-summary-article-header "gnus-sum")
36 (autoload 'message-make-from "message")
38 (defface debbugs-new '((t (:foreground "red")))
39 "Face for new reports that nobody has answered.")
41 (defface debbugs-handled '((t (:foreground "ForestGreen")))
42 "Face for new reports that nobody has answered.")
44 (defface debbugs-stale '((t (:foreground "orange")))
45 "Face for new reports that nobody has answered.")
47 (defface debbugs-done '((t (:foreground "DarkGrey")))
48 "Face for closed bug reports.")
50 (defun debbugs-emacs (severities &optional package suppress-done archivedp)
51 "List all outstanding Emacs bugs."
54 (completing-read "Severity: "
55 '("important" "normal" "minor" "wishlist")
57 (unless (consp severities)
58 (setq severities (list severities)))
59 (let ((debbugs-port "gnu.org")
62 (dolist (severity severities)
64 (debbugs-get-bugs :package (or package "emacs")
66 :archive (if archivedp
68 (setq ids (sort ids '<))
70 (if (> (length ids) default)
71 (let ((cursor-in-echo-area nil))
76 "How many reports (available %d, default %d): "
80 (number-to-string default))))))
82 (if (> (length ids) default)
87 curr-ids (butlast ids (- (length ids) default))
93 :follow-link 'mouse-face
94 :notify (lambda (widget &rest ignore)
97 (widget-get widget :debbugs-widgets)))
98 :debbugs-suppress-done suppress-done
99 :debbugs-buffer-name (format "*Emacs Bugs*<%d>" i)
100 :debbugs-ids curr-ids
103 (car ids) (car (last curr-ids)))
105 (number-to-string i))))
106 ids (last ids (- (length ids) default))))
107 (debbugs-show-reports (car widgets) widgets))
109 (debbugs-show-reports (widget-convert
111 :debbugs-suppress-done suppress-done
112 :debbugs-buffer-name "*Emacs Bugs*"
116 (defun debbugs-widget-format-handler (widget escape)
118 ;; That's the only format we support.
120 (let ((size (widget-get widget :debbugs-size))
121 (string (format (widget-get widget :debbugs-format)
122 (widget-value widget))))
125 ((and (numberp size) (> (length string) size))
126 (propertize (substring string 0 size) 'help-echo string))
127 ((numberp size) string)
128 (t (propertize string 'help-echo string))))))
131 (widget-default-format-handler widget escape))))
133 (defun debbugs-show-reports (widget widgets)
134 "Show bug reports as given in WIDGET property :debbugs-ids."
135 (pop-to-buffer (get-buffer-create (widget-get widget :debbugs-buffer-name)))
137 (let ((suppress-done (widget-get widget :debbugs-suppress-done)))
141 (widget-insert "Page:")
144 (widget-put obj :debbugs-widgets widgets)
145 (widget-put obj :button-face
147 'widget-button-pressed
148 'widget-button-face))
149 (widget-apply obj :create))
151 (widget-insert "\n\n"))
153 (dolist (status (sort (apply 'debbugs-get-status
154 (widget-get widget :debbugs-ids))
156 (< (cdr (assq 'id s1))
157 (cdr (assq 'id s2))))))
158 (when (or (not suppress-done)
159 (not (equal (cdr (assq 'pending status)) "done")))
160 (let ((id (cdr (assq 'id status)))
163 ((equal (cdr (assq 'pending status)) "done")
165 ((= (cdr (assq 'date status))
166 (cdr (assq 'log_modified status)))
169 (cdr (assq 'log_modified status)))
177 (cons (cdr (assq 'severity status))
178 (cdr (assq 'keywords status)))
180 (address (mail-header-parse-address
181 (decode-coding-string (cdr (assq 'originator status))
183 (subject (decode-coding-string (cdr (assq 'subject status))
186 (unless (equal (cdr (assq 'pending status)) "pending")
188 (concat words "," (cdr (assq 'pending status)))))
189 (when (setq merged (cdr (assq 'mergedwith status)))
190 (setq words (format "%s,%s"
193 (mapconcat 'number-to-string merged ","))
196 ;; Prefer the name over the address.
200 (widget-create 'const
202 :debbugs-format "%5d"
204 :debbugs-status status
205 :format-handler 'debbugs-widget-format-handler
208 (widget-create 'const
210 :debbugs-format "%-20s"
212 :format-handler 'debbugs-widget-format-handler
216 (widget-create 'const
218 :debbugs-format "%-23s"
220 :format-handler 'debbugs-widget-format-handler
223 (let ((widget-link-prefix "")
224 (widget-link-suffix ""))
228 :follow-link 'mouse-face
229 :notify (lambda (widget &rest ignore)
230 (debbugs-select-report
231 (widget-get widget :debbugs-id)))
236 (widget-insert "\nPage:")
237 (mapc (lambda (obj) (widget-apply obj :create)) widgets))
240 (set-buffer-modified-p nil)
241 (goto-char (point-min))))
243 (defvar debbugs-mode-map
244 (let ((map (copy-keymap special-mode-map)))
245 (define-key map "q" 'kill-buffer)
246 (define-key map "s" 'debbugs-toggle-sort)
247 (set-keymap-parent map widget-keymap)
250 (defvar debbugs-sort-state 'number)
252 (defun debbugs-mode ()
253 "Major mode for listing bug reports.
255 All normal editing commands are switched off.
258 The following commands are available:
260 \\{debbugs-mode-map}"
262 (kill-all-local-variables)
263 (setq major-mode 'debbugs-mode)
264 (setq mode-name "Debbugs")
265 (use-local-map debbugs-mode-map)
266 (set (make-local-variable 'debbugs-sort-state)
268 (buffer-disable-undo)
269 (setq truncate-lines t)
270 (setq buffer-read-only t))
272 (defvar debbugs-state-preference
275 (debbugs-handled . 3)
278 (defun debbugs-toggle-sort ()
279 "Toggle sorting by age and by state."
282 (let ((buffer-read-only nil)
283 (current-bug (and (not (eobp))
284 (buffer-substring (point) (+ (point) 5)))))
285 (goto-char (point-min))
286 (setq debbugs-sort-state
287 (if (eq debbugs-sort-state 'number)
291 nil (lambda () (forward-line 1)) 'end-of-line
293 (if (eq debbugs-sort-state 'number)
294 (string-to-number (buffer-substring (point) (+ (point) 5)))
295 (or (cdr (assq (get-text-property (+ (point) 7) 'face)
296 debbugs-state-preference))
298 (if (not current-bug)
299 (goto-char (point-max))
300 (goto-char (point-min))
301 (re-search-forward (concat "^" current-bug) nil t))))
303 (defvar debbugs-bug-number nil)
305 (defun debbugs-select-report (id)
306 "Select the report for ID."
308 (gnus-read-ephemeral-emacs-bug-group
309 id (cons (current-buffer)
310 (current-window-configuration)))
311 (with-current-buffer (window-buffer (selected-window))
312 (debbugs-summary-mode 1)
313 (set (make-local-variable 'debbugs-bug-number) id)))
315 (defvar debbugs-summary-mode-map
316 (let ((map (make-sparse-keymap)))
317 (define-key map "C" 'debbugs-send-control-message)
320 (define-minor-mode debbugs-summary-mode
321 "Minor mode for providing a debbugs interface in Gnus summary buffers.
323 \\{debbugs-summary-mode-map}"
324 :lighter " Debbugs" :keymap debbugs-summary-mode-map
325 (set (make-local-variable 'gnus-posting-styles)
328 (with-current-buffer gnus-article-copy
329 (set (make-local-variable 'message-prune-recipient-rules)
330 '((".*@debbugs.*" "emacs-pretest-bug")
331 (".*@debbugs.*" "bug-gnu-emacs")))
332 (set (make-local-variable 'message-alter-recipients-function)
334 (if (string-match "\\([0-9]+\\)@donarmstrong" (car address))
335 (let ((new (format "%s@debbugs.gnu.org"
336 (match-string 1 (car address)))))
340 (defun debbugs-send-control-message (message)
341 "Send a control message for the current bug report.
342 You can set the severity or add a tag, or close the report. If
343 you use the special \"done\" MESSAGE, the report will be marked as
344 fixed, and then closed."
346 (list (completing-read
348 '("important" "normal" "minor" "wishlist"
350 "unarchive" "reopen" "close"
352 "patch" "wontfix" "moreinfo" "unreproducible" "fixed" "notabug")
354 (let* ((id debbugs-bug-number) ; Set on group entry.
356 (when (member message '("close" "done"))
360 ;; Emacs development versions.
362 "^\\([0-9]+\\)\\.\\([0-9]+\\)\\.\\([0-9]+\\)\\." emacs-version)
364 (match-string 1 emacs-version)
365 (1+ (string-to-number (match-string 2 emacs-version)))))
366 ;; Emacs release versions.
368 "^\\([0-9]+\\)\\.\\([0-9]+\\)\\.\\([0-9]+\\)$" emacs-version)
370 (match-string 1 emacs-version)
371 (match-string 2 emacs-version)))
372 (t emacs-version))))))
374 (insert "To: control@debbugs.gnu.org\n"
375 "From: " (message-make-from) "\n"
376 (format "Subject: control message for bug #%d\n" id)
379 ((member message '("unarchive" "reopen"))
380 (format "%s %d\n" message id))
381 ((member message '("merge" "forcemerge"))
382 (format "%s %d %s\n" message id
383 (read-string "Merge with bug #: ")))
384 ((equal message "close")
385 (format "close %d %s\n" id version))
386 ((equal message "done")
387 (format "tags %d fixed\nclose %d %s\n" id id version))
388 ((member message '("important" "normal" "minor" "wishlist"))
389 (format "severity %d %s\n" id message))
391 (format "tags %d %s\n" id message))))
392 (funcall send-mail-function))))
394 (provide 'debbugs-gnu)
398 ;; * Widget-oriented bug overview like webDDTs.
399 ;; * Actions on bugs.
400 ;; * Integration into gnus (nnir).
402 ;;; debbugs-gnu.el ends here