PageRenderTime 45ms CodeModel.GetById 17ms RepoModel.GetById 0ms app.codeStats 0ms

/contrib/mq.el

https://bitbucket.org/mirror/mercurial/
Emacs Lisp | 417 lines | 346 code | 51 blank | 20 comment | 5 complexity | 4241dee8dae7f194b38061d0c91aa468 MD5 | raw file
Possible License(s): GPL-2.0
  1. ;;; mq.el --- Emacs support for Mercurial Queues
  2. ;; Copyright (C) 2006 Bryan O'Sullivan
  3. ;; Author: Bryan O'Sullivan <bos@serpentine.com>
  4. ;; mq.el is free software; you can redistribute it and/or modify it
  5. ;; under the terms of the GNU General Public License version 2 or any
  6. ;; later version.
  7. ;; mq.el is distributed in the hope that it will be useful, but
  8. ;; WITHOUT ANY WARRANTY; without even the implied warranty of
  9. ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
  10. ;; General Public License for more details.
  11. ;; You should have received a copy of the GNU General Public License
  12. ;; along with mq.el, GNU Emacs, or XEmacs; see the file COPYING (`C-h
  13. ;; C-l'). If not, see <http://www.gnu.org/licenses/>.
  14. (eval-when-compile (require 'cl))
  15. (require 'mercurial)
  16. (defcustom mq-mode-hook nil
  17. "Hook run when a buffer enters mq-mode."
  18. :type 'sexp
  19. :group 'mercurial)
  20. (defcustom mq-global-prefix "\C-cq"
  21. "The global prefix for Mercurial Queues keymap bindings."
  22. :type 'sexp
  23. :group 'mercurial)
  24. (defcustom mq-edit-mode-hook nil
  25. "Hook run after a buffer is populated to edit a patch description."
  26. :type 'sexp
  27. :group 'mercurial)
  28. (defcustom mq-edit-finish-hook nil
  29. "Hook run before a patch description is finished up with."
  30. :type 'sexp
  31. :group 'mercurial)
  32. (defcustom mq-signoff-address nil
  33. "Address with which to sign off on a patch."
  34. :type 'string
  35. :group 'mercurial)
  36. ;;; Internal variables.
  37. (defvar mq-mode nil
  38. "Is this file managed by MQ?")
  39. (make-variable-buffer-local 'mq-mode)
  40. (put 'mq-mode 'permanent-local t)
  41. (defvar mq-patch-history nil)
  42. (defvar mq-top-patch '(nil))
  43. (defvar mq-prev-buffer nil)
  44. (make-variable-buffer-local 'mq-prev-buffer)
  45. (put 'mq-prev-buffer 'permanent-local t)
  46. (defvar mq-top nil)
  47. (make-variable-buffer-local 'mq-top)
  48. (put 'mq-top 'permanent-local t)
  49. ;;; Global keymap.
  50. (defvar mq-global-map
  51. (let ((map (make-sparse-keymap)))
  52. (define-key map "." 'mq-push)
  53. (define-key map ">" 'mq-push-all)
  54. (define-key map "," 'mq-pop)
  55. (define-key map "<" 'mq-pop-all)
  56. (define-key map "=" 'mq-diff)
  57. (define-key map "r" 'mq-refresh)
  58. (define-key map "e" 'mq-refresh-edit)
  59. (define-key map "i" 'mq-new)
  60. (define-key map "n" 'mq-next)
  61. (define-key map "o" 'mq-signoff)
  62. (define-key map "p" 'mq-previous)
  63. (define-key map "s" 'mq-edit-series)
  64. (define-key map "t" 'mq-top)
  65. map))
  66. (global-set-key mq-global-prefix mq-global-map)
  67. (add-minor-mode 'mq-mode 'mq-mode)
  68. ;;; Refresh edit mode keymap.
  69. (defvar mq-edit-mode-map
  70. (let ((map (make-sparse-keymap)))
  71. (define-key map "\C-c\C-c" 'mq-edit-finish)
  72. (define-key map "\C-c\C-k" 'mq-edit-kill)
  73. (define-key map "\C-c\C-s" 'mq-signoff)
  74. map))
  75. ;;; Helper functions.
  76. (defun mq-read-patch-name (&optional source prompt force)
  77. "Read a patch name to use with a command.
  78. May return nil, meaning \"use the default\"."
  79. (let ((patches (split-string
  80. (hg-chomp (hg-run0 (or source "qseries"))) "\n")))
  81. (when force
  82. (completing-read (format "Patch%s: " (or prompt ""))
  83. (mapcar (lambda (x) (cons x x)) patches)
  84. nil
  85. nil
  86. nil
  87. 'mq-patch-history))))
  88. (defun mq-refresh-buffers (root)
  89. (save-excursion
  90. (dolist (buf (hg-buffers-visiting-repo root))
  91. (when (not (verify-visited-file-modtime buf))
  92. (set-buffer buf)
  93. (let ((ctx (hg-buffer-context)))
  94. (message "Refreshing %s..." (buffer-name))
  95. (revert-buffer t t t)
  96. (hg-restore-context ctx)
  97. (message "Refreshing %s...done" (buffer-name))))))
  98. (hg-update-mode-lines root)
  99. (mq-update-mode-lines root))
  100. (defun mq-last-line ()
  101. (goto-char (point-max))
  102. (beginning-of-line)
  103. (when (looking-at "^$")
  104. (forward-line -1))
  105. (let ((bol (point)))
  106. (end-of-line)
  107. (let ((line (buffer-substring bol (point))))
  108. (when (> (length line) 0)
  109. line))))
  110. (defun mq-push (&optional patch)
  111. "Push patches until PATCH is reached.
  112. If PATCH is nil, push at most one patch."
  113. (interactive (list (mq-read-patch-name "qunapplied" " to push"
  114. current-prefix-arg)))
  115. (let ((root (hg-root))
  116. (prev-buf (current-buffer))
  117. last-line ok)
  118. (unless root
  119. (error "Cannot push outside a repository!"))
  120. (hg-sync-buffers root)
  121. (let ((buf-name (format "MQ: Push %s" (or patch "next patch"))))
  122. (kill-buffer (get-buffer-create buf-name))
  123. (split-window-vertically)
  124. (other-window 1)
  125. (switch-to-buffer (get-buffer-create buf-name))
  126. (cd root)
  127. (message "Pushing...")
  128. (setq ok (= 0 (apply 'call-process (hg-binary) nil t t "qpush"
  129. (if patch (list patch))))
  130. last-line (mq-last-line))
  131. (let ((lines (count-lines (point-min) (point-max))))
  132. (if (or (<= lines 1)
  133. (and (equal lines 2) (string-match "Now at:" last-line)))
  134. (progn
  135. (kill-buffer (current-buffer))
  136. (delete-window))
  137. (hg-view-mode prev-buf))))
  138. (mq-refresh-buffers root)
  139. (sit-for 0)
  140. (when last-line
  141. (if ok
  142. (message "Pushing... %s" last-line)
  143. (error "Pushing... %s" last-line)))))
  144. (defun mq-push-all ()
  145. "Push patches until all are applied."
  146. (interactive)
  147. (mq-push "-a"))
  148. (defun mq-pop (&optional patch)
  149. "Pop patches until PATCH is reached.
  150. If PATCH is nil, pop at most one patch."
  151. (interactive (list (mq-read-patch-name "qapplied" " to pop to"
  152. current-prefix-arg)))
  153. (let ((root (hg-root))
  154. last-line ok)
  155. (unless root
  156. (error "Cannot pop outside a repository!"))
  157. (hg-sync-buffers root)
  158. (set-buffer (generate-new-buffer "qpop"))
  159. (cd root)
  160. (message "Popping...")
  161. (setq ok (= 0 (apply 'call-process (hg-binary) nil t t "qpop"
  162. (if patch (list patch))))
  163. last-line (mq-last-line))
  164. (kill-buffer (current-buffer))
  165. (mq-refresh-buffers root)
  166. (sit-for 0)
  167. (when last-line
  168. (if ok
  169. (message "Popping... %s" last-line)
  170. (error "Popping... %s" last-line)))))
  171. (defun mq-pop-all ()
  172. "Push patches until none are applied."
  173. (interactive)
  174. (mq-pop "-a"))
  175. (defun mq-refresh-internal (root &rest args)
  176. (hg-sync-buffers root)
  177. (let ((patch (mq-patch-info "qtop")))
  178. (message "Refreshing %s..." patch)
  179. (let ((ret (apply 'hg-run "qrefresh" args)))
  180. (if (equal (car ret) 0)
  181. (message "Refreshing %s... done." patch)
  182. (error "Refreshing %s... %s" patch (hg-chomp (cdr ret)))))))
  183. (defun mq-refresh (&optional git)
  184. "Refresh the topmost applied patch.
  185. With a prefix argument, generate a git-compatible patch."
  186. (interactive "P")
  187. (let ((root (hg-root)))
  188. (unless root
  189. (error "Cannot refresh outside of a repository!"))
  190. (apply 'mq-refresh-internal root (if git '("--git")))))
  191. (defun mq-patch-info (cmd &optional msg)
  192. (let* ((ret (hg-run cmd))
  193. (info (hg-chomp (cdr ret))))
  194. (if (equal (car ret) 0)
  195. (if msg
  196. (message "%s patch: %s" msg info)
  197. info)
  198. (error "%s" info))))
  199. (defun mq-top ()
  200. "Print the name of the topmost applied patch."
  201. (interactive)
  202. (mq-patch-info "qtop" "Top"))
  203. (defun mq-next ()
  204. "Print the name of the next patch to be pushed."
  205. (interactive)
  206. (mq-patch-info "qnext" "Next"))
  207. (defun mq-previous ()
  208. "Print the name of the first patch below the topmost applied patch.
  209. This would become the active patch if popped to."
  210. (interactive)
  211. (mq-patch-info "qprev" "Previous"))
  212. (defun mq-edit-finish ()
  213. "Finish editing the description of this patch, and refresh the patch."
  214. (interactive)
  215. (unless (equal (mq-patch-info "qtop") mq-top)
  216. (error "Topmost patch has changed!"))
  217. (hg-sync-buffers hg-root)
  218. (run-hooks 'mq-edit-finish-hook)
  219. (mq-refresh-internal hg-root "-m" (buffer-substring (point-min) (point-max)))
  220. (let ((buf mq-prev-buffer))
  221. (kill-buffer nil)
  222. (switch-to-buffer buf)))
  223. (defun mq-edit-kill ()
  224. "Kill the edit currently being prepared."
  225. (interactive)
  226. (when (or (not (buffer-modified-p)) (y-or-n-p "Really kill this edit? "))
  227. (let ((buf mq-prev-buffer))
  228. (kill-buffer nil)
  229. (switch-to-buffer buf))))
  230. (defun mq-get-top (root)
  231. (let ((entry (assoc root mq-top-patch)))
  232. (if entry
  233. (cdr entry))))
  234. (defun mq-set-top (root patch)
  235. (let ((entry (assoc root mq-top-patch)))
  236. (if entry
  237. (if patch
  238. (setcdr entry patch)
  239. (setq mq-top-patch (delq entry mq-top-patch)))
  240. (setq mq-top-patch (cons (cons root patch) mq-top-patch)))))
  241. (defun mq-update-mode-lines (root)
  242. (let ((cwd default-directory))
  243. (cd root)
  244. (condition-case nil
  245. (mq-set-top root (mq-patch-info "qtop"))
  246. (error (mq-set-top root nil)))
  247. (cd cwd))
  248. (let ((patch (mq-get-top root)))
  249. (save-excursion
  250. (dolist (buf (hg-buffers-visiting-repo root))
  251. (set-buffer buf)
  252. (if mq-mode
  253. (setq mq-mode (or (and patch (concat " MQ:" patch)) " MQ")))))))
  254. (defun mq-mode (&optional arg)
  255. "Minor mode for Mercurial repositories with an MQ patch queue"
  256. (interactive "i")
  257. (cond ((hg-root)
  258. (setq mq-mode (if (null arg) (not mq-mode)
  259. arg))
  260. (mq-update-mode-lines (hg-root))))
  261. (run-hooks 'mq-mode-hook))
  262. (defun mq-edit-mode ()
  263. "Mode for editing the description of a patch.
  264. Key bindings
  265. ------------
  266. \\[mq-edit-finish] use this description
  267. \\[mq-edit-kill] abandon this description"
  268. (interactive)
  269. (use-local-map mq-edit-mode-map)
  270. (set-syntax-table text-mode-syntax-table)
  271. (setq local-abbrev-table text-mode-abbrev-table
  272. major-mode 'mq-edit-mode
  273. mode-name "MQ-Edit")
  274. (set-buffer-modified-p nil)
  275. (setq buffer-undo-list nil)
  276. (run-hooks 'text-mode-hook 'mq-edit-mode-hook))
  277. (defun mq-refresh-edit ()
  278. "Refresh the topmost applied patch, editing the patch description."
  279. (interactive)
  280. (while mq-prev-buffer
  281. (set-buffer mq-prev-buffer))
  282. (let ((root (hg-root))
  283. (prev-buffer (current-buffer))
  284. (patch (mq-patch-info "qtop")))
  285. (hg-sync-buffers root)
  286. (let ((buf-name (format "*MQ: Edit description of %s*" patch)))
  287. (switch-to-buffer (get-buffer-create buf-name))
  288. (when (= (point-min) (point-max))
  289. (set (make-local-variable 'hg-root) root)
  290. (set (make-local-variable 'mq-top) patch)
  291. (setq mq-prev-buffer prev-buffer)
  292. (insert (hg-run0 "qheader"))
  293. (goto-char (point-min)))
  294. (mq-edit-mode)
  295. (cd root)))
  296. (message "Type `C-c C-c' to finish editing and refresh the patch."))
  297. (defun mq-new (name)
  298. "Create a new empty patch named NAME.
  299. The patch is applied on top of the current topmost patch.
  300. With a prefix argument, forcibly create the patch even if the working
  301. directory is modified."
  302. (interactive (list (mq-read-patch-name "qseries" " to create" t)))
  303. (message "Creating patch...")
  304. (let ((ret (if current-prefix-arg
  305. (hg-run "qnew" "-f" name)
  306. (hg-run "qnew" name))))
  307. (if (equal (car ret) 0)
  308. (progn
  309. (hg-update-mode-lines (buffer-file-name))
  310. (message "Creating patch... done."))
  311. (error "Creating patch... %s" (hg-chomp (cdr ret))))))
  312. (defun mq-edit-series ()
  313. "Edit the MQ series file directly."
  314. (interactive)
  315. (let ((root (hg-root)))
  316. (unless root
  317. (error "Not in an MQ repository!"))
  318. (find-file (concat root ".hg/patches/series"))))
  319. (defun mq-diff (&optional git)
  320. "Display a diff of the topmost applied patch.
  321. With a prefix argument, display a git-compatible diff."
  322. (interactive "P")
  323. (hg-view-output ((format "MQ: Diff of %s" (mq-patch-info "qtop")))
  324. (if git
  325. (call-process (hg-binary) nil t nil "qdiff" "--git")
  326. (call-process (hg-binary) nil t nil "qdiff"))
  327. (diff-mode)
  328. (font-lock-fontify-buffer)))
  329. (defun mq-signoff ()
  330. "Sign off on the current patch, in the style used by the Linux kernel.
  331. If the variable mq-signoff-address is non-nil, it will be used, otherwise
  332. the value of the ui.username item from your hgrc will be used."
  333. (interactive)
  334. (let ((was-editing (eq major-mode 'mq-edit-mode))
  335. signed)
  336. (unless was-editing
  337. (mq-refresh-edit))
  338. (save-excursion
  339. (let* ((user (or mq-signoff-address
  340. (hg-run0 "debugconfig" "ui.username")))
  341. (signoff (concat "Signed-off-by: " user)))
  342. (if (search-forward signoff nil t)
  343. (message "You have already signed off on this patch.")
  344. (goto-char (point-max))
  345. (let ((case-fold-search t))
  346. (if (re-search-backward "^Signed-off-by: " nil t)
  347. (forward-line 1)
  348. (insert "\n")))
  349. (insert signoff)
  350. (message "%s" signoff)
  351. (setq signed t))))
  352. (unless was-editing
  353. (if signed
  354. (mq-edit-finish)
  355. (mq-edit-kill)))))
  356. (provide 'mq)
  357. ;;; Local Variables:
  358. ;;; prompt-to-byte-compile: nil
  359. ;;; end: