Klimi's new dotfiles with stow.
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

813 lines
30 KiB

4 years ago
  1. ;;; git-rebase.el --- Edit Git rebase files -*- lexical-binding: t -*-
  2. ;; Copyright (C) 2010-2019 The Magit Project Contributors
  3. ;;
  4. ;; You should have received a copy of the AUTHORS.md file which
  5. ;; lists all contributors. If not, see http://magit.vc/authors.
  6. ;; Author: Phil Jackson <phil@shellarchive.co.uk>
  7. ;; Maintainer: Jonas Bernoulli <jonas@bernoul.li>
  8. ;; This file is not part of GNU Emacs.
  9. ;; This file is free software; you can redistribute it and/or modify
  10. ;; it under the terms of the GNU General Public License as published by
  11. ;; the Free Software Foundation; either version 3, or (at your option)
  12. ;; any later version.
  13. ;; This file is distributed in the hope that it will be useful,
  14. ;; but WITHOUT ANY WARRANTY; without even the implied warranty of
  15. ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  16. ;; GNU General Public License for more details.
  17. ;; You should have received a copy of the GNU General Public License
  18. ;; along with this file. If not, see <http://www.gnu.org/licenses/>.
  19. ;;; Commentary:
  20. ;; This package assists the user in editing the list of commits to be
  21. ;; rewritten during an interactive rebase.
  22. ;; When the user initiates an interactive rebase, e.g. using "r e" in
  23. ;; a Magit buffer or on the command line using "git rebase -i REV",
  24. ;; Git invokes the `$GIT_SEQUENCE_EDITOR' (or if that is undefined
  25. ;; `$GIT_EDITOR' or even `$EDITOR') letting the user rearrange, drop,
  26. ;; reword, edit, and squash commits.
  27. ;; This package provides the major-mode `git-rebase-mode' which makes
  28. ;; doing so much more fun, by making the buffer more colorful and
  29. ;; providing the following commands:
  30. ;;
  31. ;; C-c C-c Tell Git to make it happen.
  32. ;; C-c C-k Tell Git that you changed your mind, i.e. abort.
  33. ;;
  34. ;; p Move point to previous line.
  35. ;; n Move point to next line.
  36. ;;
  37. ;; M-p Move the commit at point up.
  38. ;; M-n Move the commit at point down.
  39. ;;
  40. ;; k Drop the commit at point.
  41. ;; c Don't drop the commit at point.
  42. ;; r Change the message of the commit at point.
  43. ;; e Edit the commit at point.
  44. ;; s Squash the commit at point, into the one above.
  45. ;; f Like "s" but don't also edit the commit message.
  46. ;; b Break for editing at this point in the sequence.
  47. ;; x Add a script to be run with the commit at point
  48. ;; being checked out.
  49. ;; z Add noop action at point.
  50. ;;
  51. ;; SPC Show the commit at point in another buffer.
  52. ;; RET Show the commit at point in another buffer and
  53. ;; select its window.
  54. ;; C-/ Undo last change.
  55. ;;
  56. ;; Commands for --rebase-merges:
  57. ;; l Associate label with current HEAD in sequence.
  58. ;; MM Merge specified revisions into HEAD.
  59. ;; Mt Toggle whether the merge will invoke an editor
  60. ;; before committing.
  61. ;; t Reset HEAD to the specified label.
  62. ;; You should probably also read the `git-rebase' manpage.
  63. ;;; Code:
  64. (require 'dash)
  65. (require 'easymenu)
  66. (require 'server)
  67. (require 'with-editor)
  68. (require 'magit)
  69. (and (require 'async-bytecomp nil t)
  70. (memq 'magit (bound-and-true-p async-bytecomp-allowed-packages))
  71. (fboundp 'async-bytecomp-package-mode)
  72. (async-bytecomp-package-mode 1))
  73. (eval-when-compile (require 'recentf))
  74. ;;; Options
  75. ;;;; Variables
  76. (defgroup git-rebase nil
  77. "Edit Git rebase sequences."
  78. :link '(info-link "(magit)Editing Rebase Sequences")
  79. :group 'tools)
  80. (defcustom git-rebase-auto-advance t
  81. "Whether to move to next line after changing a line."
  82. :group 'git-rebase
  83. :type 'boolean)
  84. (defcustom git-rebase-show-instructions t
  85. "Whether to show usage instructions inside the rebase buffer."
  86. :group 'git-rebase
  87. :type 'boolean)
  88. (defcustom git-rebase-confirm-cancel t
  89. "Whether confirmation is required to cancel."
  90. :group 'git-rebase
  91. :type 'boolean)
  92. ;;;; Faces
  93. (defgroup git-rebase-faces nil
  94. "Faces used by Git-Rebase mode."
  95. :group 'faces
  96. :group 'git-rebase)
  97. (defface git-rebase-hash '((t (:inherit magit-hash)))
  98. "Face for commit hashes."
  99. :group 'git-rebase-faces)
  100. (defface git-rebase-label '((t (:inherit magit-refname)))
  101. "Face for labels in label, merge, and reset lines."
  102. :group 'git-rebase-faces)
  103. (defface git-rebase-description nil
  104. "Face for commit descriptions."
  105. :group 'git-rebase-faces)
  106. (defface git-rebase-killed-action
  107. '((t (:inherit font-lock-comment-face :strike-through t)))
  108. "Face for commented commit action lines."
  109. :group 'git-rebase-faces)
  110. (defface git-rebase-comment-hash
  111. '((t (:inherit git-rebase-hash :weight bold)))
  112. "Face for commit hashes in commit message comments."
  113. :group 'git-rebase-faces)
  114. (defface git-rebase-comment-heading
  115. '((t :inherit font-lock-keyword-face))
  116. "Face for headings in rebase message comments."
  117. :group 'git-commit-faces)
  118. ;;; Keymaps
  119. (defvar git-rebase-mode-map
  120. (let ((map (make-sparse-keymap)))
  121. (set-keymap-parent map special-mode-map)
  122. (cond ((featurep 'jkl)
  123. (define-key map [return] 'git-rebase-show-commit)
  124. (define-key map (kbd "i") 'git-rebase-backward-line)
  125. (define-key map (kbd "k") 'forward-line)
  126. (define-key map (kbd "M-i") 'git-rebase-move-line-up)
  127. (define-key map (kbd "M-k") 'git-rebase-move-line-down)
  128. (define-key map (kbd "p") 'git-rebase-pick)
  129. (define-key map (kbd ",") 'git-rebase-kill-line))
  130. (t
  131. (define-key map (kbd "C-m") 'git-rebase-show-commit)
  132. (define-key map (kbd "p") 'git-rebase-backward-line)
  133. (define-key map (kbd "n") 'forward-line)
  134. (define-key map (kbd "M-p") 'git-rebase-move-line-up)
  135. (define-key map (kbd "M-n") 'git-rebase-move-line-down)
  136. (define-key map (kbd "c") 'git-rebase-pick)
  137. (define-key map (kbd "k") 'git-rebase-kill-line)
  138. (define-key map (kbd "C-k") 'git-rebase-kill-line)))
  139. (define-key map (kbd "b") 'git-rebase-break)
  140. (define-key map (kbd "e") 'git-rebase-edit)
  141. (define-key map (kbd "l") 'git-rebase-label)
  142. (define-key map (kbd "MM") 'git-rebase-merge)
  143. (define-key map (kbd "Mt") 'git-rebase-merge-toggle-editmsg)
  144. (define-key map (kbd "m") 'git-rebase-edit)
  145. (define-key map (kbd "f") 'git-rebase-fixup)
  146. (define-key map (kbd "q") 'undefined)
  147. (define-key map (kbd "r") 'git-rebase-reword)
  148. (define-key map (kbd "w") 'git-rebase-reword)
  149. (define-key map (kbd "s") 'git-rebase-squash)
  150. (define-key map (kbd "t") 'git-rebase-reset)
  151. (define-key map (kbd "x") 'git-rebase-exec)
  152. (define-key map (kbd "y") 'git-rebase-insert)
  153. (define-key map (kbd "z") 'git-rebase-noop)
  154. (define-key map (kbd "SPC") 'git-rebase-show-or-scroll-up)
  155. (define-key map (kbd "DEL") 'git-rebase-show-or-scroll-down)
  156. (define-key map (kbd "C-x C-t") 'git-rebase-move-line-up)
  157. (define-key map [M-up] 'git-rebase-move-line-up)
  158. (define-key map [M-down] 'git-rebase-move-line-down)
  159. (define-key map [remap undo] 'git-rebase-undo)
  160. map)
  161. "Keymap for Git-Rebase mode.")
  162. (cond ((featurep 'jkl)
  163. (put 'git-rebase-reword :advertised-binding "r")
  164. (put 'git-rebase-move-line-up :advertised-binding (kbd "M-i"))
  165. (put 'git-rebase-kill-line :advertised-binding ","))
  166. (t
  167. (put 'git-rebase-reword :advertised-binding "r")
  168. (put 'git-rebase-move-line-up :advertised-binding (kbd "M-p"))
  169. (put 'git-rebase-kill-line :advertised-binding "k")))
  170. (easy-menu-define git-rebase-mode-menu git-rebase-mode-map
  171. "Git-Rebase mode menu"
  172. '("Rebase"
  173. ["Pick" git-rebase-pick t]
  174. ["Reword" git-rebase-reword t]
  175. ["Edit" git-rebase-edit t]
  176. ["Squash" git-rebase-squash t]
  177. ["Fixup" git-rebase-fixup t]
  178. ["Kill" git-rebase-kill-line t]
  179. ["Noop" git-rebase-noop t]
  180. ["Execute" git-rebase-exec t]
  181. ["Move Down" git-rebase-move-line-down t]
  182. ["Move Up" git-rebase-move-line-up t]
  183. "---"
  184. ["Cancel" with-editor-cancel t]
  185. ["Finish" with-editor-finish t]))
  186. (defvar git-rebase-command-descriptions
  187. '((with-editor-finish . "tell Git to make it happen")
  188. (with-editor-cancel . "tell Git that you changed your mind, i.e. abort")
  189. (git-rebase-backward-line . "move point to previous line")
  190. (forward-line . "move point to next line")
  191. (git-rebase-move-line-up . "move the commit at point up")
  192. (git-rebase-move-line-down . "move the commit at point down")
  193. (git-rebase-show-or-scroll-up . "show the commit at point in another buffer")
  194. (git-rebase-show-commit
  195. . "show the commit at point in another buffer and select its window")
  196. (undo . "undo last change")
  197. (git-rebase-kill-line . "drop the commit at point")
  198. (git-rebase-insert . "insert a line for an arbitrary commit")
  199. (git-rebase-noop . "add noop action at point")))
  200. ;;; Commands
  201. (defun git-rebase-pick ()
  202. "Use commit on current line."
  203. (interactive)
  204. (git-rebase-set-action "pick"))
  205. (defun git-rebase-reword ()
  206. "Edit message of commit on current line."
  207. (interactive)
  208. (git-rebase-set-action "reword"))
  209. (defun git-rebase-edit ()
  210. "Stop at the commit on the current line."
  211. (interactive)
  212. (git-rebase-set-action "edit"))
  213. (defun git-rebase-squash ()
  214. "Meld commit on current line into previous commit, edit message."
  215. (interactive)
  216. (git-rebase-set-action "squash"))
  217. (defun git-rebase-fixup ()
  218. "Meld commit on current line into previous commit, discard its message."
  219. (interactive)
  220. (git-rebase-set-action "fixup"))
  221. (defvar-local git-rebase-comment-re nil)
  222. (defvar git-rebase-short-options
  223. '((?b . "break")
  224. (?e . "edit")
  225. (?f . "fixup")
  226. (?l . "label")
  227. (?m . "merge")
  228. (?p . "pick")
  229. (?r . "reword")
  230. (?s . "squash")
  231. (?t . "reset")
  232. (?x . "exec"))
  233. "Alist mapping single key of an action to the full name.")
  234. (defclass git-rebase-action ()
  235. (;; action-type: commit, exec, bare, label, merge
  236. (action-type :initarg :action-type :initform nil)
  237. ;; Examples for each action type:
  238. ;; | action | action options | target | trailer |
  239. ;; |--------+----------------+---------+---------|
  240. ;; | pick | | hash | subject |
  241. ;; | exec | | command | |
  242. ;; | noop | | | |
  243. ;; | reset | | name | subject |
  244. ;; | merge | -C hash | name | subject |
  245. (action :initarg :action :initform nil)
  246. (action-options :initarg :action-options :initform nil)
  247. (target :initarg :target :initform nil)
  248. (trailer :initarg :trailer :initform nil)
  249. (comment-p :initarg :comment-p :initform nil)))
  250. (defvar git-rebase-line-regexps
  251. `((commit . ,(concat
  252. (regexp-opt '("e" "edit"
  253. "f" "fixup"
  254. "p" "pick"
  255. "r" "reword"
  256. "s" "squash")
  257. "\\(?1:")
  258. " \\(?3:[^ \n]+\\) \\(?4:.*\\)"))
  259. (exec . "\\(?1:x\\|exec\\) \\(?3:.*\\)")
  260. (bare . ,(concat (regexp-opt '("b" "break" "noop") "\\(?1:")
  261. " *$"))
  262. (label . ,(concat (regexp-opt '("l" "label"
  263. "t" "reset")
  264. "\\(?1:")
  265. " \\(?3:[^ \n]+\\) ?\\(?4:.*\\)"))
  266. (merge . ,(concat "\\(?1:m\\|merge\\) "
  267. "\\(?:\\(?2:-[cC] [^ \n]+\\) \\)?"
  268. "\\(?3:[^ \n]+\\)"
  269. " ?\\(?4:.*\\)"))))
  270. ;;;###autoload
  271. (defun git-rebase-current-line ()
  272. "Parse current line into a `git-rebase-action' instance.
  273. If the current line isn't recognized as a rebase line, an
  274. instance with all nil values is returned."
  275. (save-excursion
  276. (goto-char (line-beginning-position))
  277. (if-let ((re-start (concat "^\\(?5:" (regexp-quote comment-start)
  278. "\\)? *"))
  279. (type (-some (lambda (arg)
  280. (let ((case-fold-search nil))
  281. (and (looking-at (concat re-start (cdr arg)))
  282. (car arg))))
  283. git-rebase-line-regexps)))
  284. (git-rebase-action
  285. :action-type type
  286. :action (when-let ((action (match-string-no-properties 1)))
  287. (or (cdr (assoc action git-rebase-short-options))
  288. action))
  289. :action-options (match-string-no-properties 2)
  290. :target (match-string-no-properties 3)
  291. :trailer (match-string-no-properties 4)
  292. :comment-p (and (match-string 5) t))
  293. ;; Use default empty class rather than nil to ease handling.
  294. (git-rebase-action))))
  295. (defun git-rebase-set-action (action)
  296. (goto-char (line-beginning-position))
  297. (with-slots (action-type target trailer)
  298. (git-rebase-current-line)
  299. (if (eq action-type 'commit)
  300. (let ((inhibit-read-only t))
  301. (magit-delete-line)
  302. (insert (concat action " " target " " trailer "\n"))
  303. (unless git-rebase-auto-advance
  304. (forward-line -1)))
  305. (ding))))
  306. (defun git-rebase-line-p (&optional pos)
  307. (save-excursion
  308. (when pos (goto-char pos))
  309. (and (oref (git-rebase-current-line) action-type)
  310. t)))
  311. (defun git-rebase-region-bounds ()
  312. (when (use-region-p)
  313. (let ((beg (save-excursion (goto-char (region-beginning))
  314. (line-beginning-position)))
  315. (end (save-excursion (goto-char (region-end))
  316. (line-end-position))))
  317. (when (and (git-rebase-line-p beg)
  318. (git-rebase-line-p end))
  319. (list beg (1+ end))))))
  320. (defun git-rebase-move-line-down (n)
  321. "Move the current commit (or command) N lines down.
  322. If N is negative, move the commit up instead. With an active
  323. region, move all the lines that the region touches, not just the
  324. current line."
  325. (interactive "p")
  326. (pcase-let* ((`(,beg ,end)
  327. (or (git-rebase-region-bounds)
  328. (list (line-beginning-position)
  329. (1+ (line-end-position)))))
  330. (pt-offset (- (point) beg))
  331. (mark-offset (and mark-active (- (mark) beg))))
  332. (save-restriction
  333. (narrow-to-region
  334. (point-min)
  335. (1-
  336. (if git-rebase-show-instructions
  337. (save-excursion
  338. (goto-char (point-min))
  339. (while (or (git-rebase-line-p)
  340. ;; The output for --rebase-merges has empty
  341. ;; lines and "Branch" comments interspersed.
  342. (looking-at-p "^$")
  343. (looking-at-p (concat git-rebase-comment-re
  344. " Branch")))
  345. (forward-line))
  346. (line-beginning-position))
  347. (point-max))))
  348. (if (or (and (< n 0) (= beg (point-min)))
  349. (and (> n 0) (= end (point-max)))
  350. (> end (point-max)))
  351. (ding)
  352. (goto-char (if (< n 0) beg end))
  353. (forward-line n)
  354. (atomic-change-group
  355. (let ((inhibit-read-only t))
  356. (insert (delete-and-extract-region beg end)))
  357. (let ((new-beg (- (point) (- end beg))))
  358. (when (use-region-p)
  359. (setq deactivate-mark nil)
  360. (set-mark (+ new-beg mark-offset)))
  361. (goto-char (+ new-beg pt-offset))))))))
  362. (defun git-rebase-move-line-up (n)
  363. "Move the current commit (or command) N lines up.
  364. If N is negative, move the commit down instead. With an active
  365. region, move all the lines that the region touches, not just the
  366. current line."
  367. (interactive "p")
  368. (git-rebase-move-line-down (- n)))
  369. (defun git-rebase-highlight-region (start end window rol)
  370. (let ((inhibit-read-only t)
  371. (deactivate-mark nil)
  372. (bounds (git-rebase-region-bounds)))
  373. (mapc #'delete-overlay magit-section-highlight-overlays)
  374. (when bounds
  375. (magit-section-make-overlay (car bounds) (cadr bounds)
  376. 'magit-section-heading-selection))
  377. (if (and bounds (not magit-keep-region-overlay))
  378. (funcall (default-value 'redisplay-unhighlight-region-function) rol)
  379. (funcall (default-value 'redisplay-highlight-region-function)
  380. start end window rol))))
  381. (defun git-rebase-unhighlight-region (rol)
  382. (mapc #'delete-overlay magit-section-highlight-overlays)
  383. (funcall (default-value 'redisplay-unhighlight-region-function) rol))
  384. (defun git-rebase-kill-line ()
  385. "Kill the current action line."
  386. (interactive)
  387. (goto-char (line-beginning-position))
  388. (unless (oref (git-rebase-current-line) comment-p)
  389. (let ((inhibit-read-only t))
  390. (insert comment-start)
  391. (insert " "))
  392. (goto-char (line-beginning-position))
  393. (when git-rebase-auto-advance
  394. (forward-line))))
  395. (defun git-rebase-insert (rev)
  396. "Read an arbitrary commit and insert it below current line."
  397. (interactive (list (magit-read-branch-or-commit "Insert revision")))
  398. (forward-line)
  399. (--if-let (magit-rev-format "%h %s" rev)
  400. (let ((inhibit-read-only t))
  401. (insert "pick " it ?\n))
  402. (user-error "Unknown revision")))
  403. (defun git-rebase-set-noncommit-action (action value-fn arg)
  404. (goto-char (line-beginning-position))
  405. (pcase-let* ((inhibit-read-only t)
  406. (`(,initial ,trailer ,comment-p)
  407. (and (not arg)
  408. (with-slots ((ln-action action)
  409. target trailer comment-p)
  410. (git-rebase-current-line)
  411. (and (equal ln-action action)
  412. (list target trailer comment-p)))))
  413. (value (funcall value-fn initial)))
  414. (pcase (list value initial comment-p)
  415. (`("" nil ,_)
  416. (ding))
  417. (`("" ,_ ,_)
  418. (magit-delete-line))
  419. (_
  420. (if initial
  421. (magit-delete-line)
  422. (forward-line))
  423. (insert (concat action " " value
  424. (and (equal value initial)
  425. trailer
  426. (concat " " trailer))
  427. "\n"))
  428. (unless git-rebase-auto-advance
  429. (forward-line -1))))))
  430. (defun git-rebase-exec (arg)
  431. "Insert a shell command to be run after the current commit.
  432. If there already is such a command on the current line, then edit
  433. that instead. With a prefix argument insert a new command even
  434. when there already is one on the current line. With empty input
  435. remove the command on the current line, if any."
  436. (interactive "P")
  437. (git-rebase-set-noncommit-action
  438. "exec"
  439. (lambda (initial) (read-shell-command "Execute: " initial))
  440. arg))
  441. (defun git-rebase-label (arg)
  442. "Add a label after the current commit.
  443. If there already is a label on the current line, then edit that
  444. instead. With a prefix argument, insert a new label even when
  445. there is already a label on the current line. With empty input,
  446. remove the label on the current line, if any."
  447. (interactive "P")
  448. (git-rebase-set-noncommit-action
  449. "label"
  450. (lambda (initial)
  451. (read-from-minibuffer
  452. "Label: " initial magit-minibuffer-local-ns-map))
  453. arg))
  454. (defun git-rebase-buffer-labels ()
  455. (let (labels)
  456. (save-excursion
  457. (goto-char (point-min))
  458. (while (re-search-forward "^\\(?:l\\|label\\) \\([^ \n]+\\)" nil t)
  459. (push (match-string-no-properties 1) labels)))
  460. (nreverse labels)))
  461. (defun git-rebase-reset (arg)
  462. "Reset the current HEAD to a label.
  463. If there already is a reset command on the current line, then
  464. edit that instead. With a prefix argument, insert a new reset
  465. line even when point is already on a reset line. With empty
  466. input, remove the reset command on the current line, if any."
  467. (interactive "P")
  468. (git-rebase-set-noncommit-action
  469. "reset"
  470. (lambda (initial)
  471. (or (magit-completing-read "Label" (git-rebase-buffer-labels)
  472. nil t initial)
  473. ""))
  474. arg))
  475. (defun git-rebase-merge (arg)
  476. "Add a merge command after the current commit.
  477. If there is already a merge command on the current line, then
  478. replace that command instead. With a prefix argument, insert a
  479. new merge command even when there is already one on the current
  480. line. With empty input, remove the merge command on the current
  481. line, if any."
  482. (interactive "P")
  483. (git-rebase-set-noncommit-action
  484. "merge"
  485. (lambda (_)
  486. (or (magit-completing-read "Merge" (git-rebase-buffer-labels))
  487. ""))
  488. arg))
  489. (defun git-rebase-merge-toggle-editmsg ()
  490. "Toggle whether an editor is invoked when performing the merge at point.
  491. When a merge command uses a lower-case -c, the message for the
  492. specified commit will be opened in an editor before creating the
  493. commit. For an upper-case -C, the message will be used as is."
  494. (interactive)
  495. (with-slots (action-type target action-options trailer)
  496. (git-rebase-current-line)
  497. (if (eq action-type 'merge)
  498. (let ((inhibit-read-only t))
  499. (magit-delete-line)
  500. (insert
  501. (format "merge %s %s %s\n"
  502. (replace-regexp-in-string
  503. "-[cC]" (lambda (c)
  504. (if (equal c "-c") "-C" "-c"))
  505. action-options t t)
  506. target
  507. trailer)))
  508. (ding))))
  509. (defun git-rebase-set-bare-action (action arg)
  510. (goto-char (line-beginning-position))
  511. (with-slots ((ln-action action) comment-p)
  512. (git-rebase-current-line)
  513. (let ((same-action-p (equal action ln-action))
  514. (inhibit-read-only t))
  515. (when (or arg
  516. (not ln-action)
  517. (not same-action-p)
  518. (and same-action-p comment-p))
  519. (unless (or arg (not same-action-p))
  520. (magit-delete-line))
  521. (insert action ?\n)
  522. (unless git-rebase-auto-advance
  523. (forward-line -1))))))
  524. (defun git-rebase-noop (&optional arg)
  525. "Add noop action at point.
  526. If the current line already contains a noop action, leave it
  527. unchanged. If there is a commented noop action present, remove
  528. the comment. Otherwise add a new noop action. With a prefix
  529. argument insert a new noop action regardless of what is already
  530. present on the current line.
  531. A noop action can be used to make git perform a rebase even if
  532. no commits are selected. Without the noop action present, git
  533. would see an empty file and therefore do nothing."
  534. (interactive "P")
  535. (git-rebase-set-bare-action "noop" arg))
  536. (defun git-rebase-break (&optional arg)
  537. "Add break action at point.
  538. If there is a commented break action present, remove the comment.
  539. If the current line already contains a break action, add another
  540. break action only if a prefix argument is given.
  541. A break action can be used to interrupt the rebase at the
  542. specified point. It is particularly useful for pausing before
  543. the first commit in the sequence. For other cases, the
  544. equivalent behavior can be achieved with `git-rebase-edit'."
  545. (interactive "P")
  546. (git-rebase-set-bare-action "break" arg))
  547. (defun git-rebase-undo (&optional arg)
  548. "Undo some previous changes.
  549. Like `undo' but works in read-only buffers."
  550. (interactive "P")
  551. (let ((inhibit-read-only t))
  552. (undo arg)))
  553. (defun git-rebase--show-commit (&optional scroll)
  554. (let ((disable-magit-save-buffers t))
  555. (save-excursion
  556. (goto-char (line-beginning-position))
  557. (--if-let (with-slots (action-type target) (git-rebase-current-line)
  558. (and (eq action-type 'commit)
  559. target))
  560. (pcase scroll
  561. (`up (magit-diff-show-or-scroll-up))
  562. (`down (magit-diff-show-or-scroll-down))
  563. (_ (apply #'magit-show-commit it
  564. (magit-diff-arguments 'magit-revision-mode))))
  565. (ding)))))
  566. (defun git-rebase-show-commit ()
  567. "Show the commit on the current line if any."
  568. (interactive)
  569. (git-rebase--show-commit))
  570. (defun git-rebase-show-or-scroll-up ()
  571. "Update the commit buffer for commit on current line.
  572. Either show the commit at point in the appropriate buffer, or if
  573. that buffer is already being displayed in the current frame and
  574. contains information about that commit, then instead scroll the
  575. buffer up."
  576. (interactive)
  577. (git-rebase--show-commit 'up))
  578. (defun git-rebase-show-or-scroll-down ()
  579. "Update the commit buffer for commit on current line.
  580. Either show the commit at point in the appropriate buffer, or if
  581. that buffer is already being displayed in the current frame and
  582. contains information about that commit, then instead scroll the
  583. buffer down."
  584. (interactive)
  585. (git-rebase--show-commit 'down))
  586. (defun git-rebase-backward-line (&optional n)
  587. "Move N lines backward (forward if N is negative).
  588. Like `forward-line' but go into the opposite direction."
  589. (interactive "p")
  590. (forward-line (- (or n 1))))
  591. ;;; Mode
  592. ;;;###autoload
  593. (define-derived-mode git-rebase-mode special-mode "Git Rebase"
  594. "Major mode for editing of a Git rebase file.
  595. Rebase files are generated when you run 'git rebase -i' or run
  596. `magit-interactive-rebase'. They describe how Git should perform
  597. the rebase. See the documentation for git-rebase (e.g., by
  598. running 'man git-rebase' at the command line) for details."
  599. :group 'git-rebase
  600. (setq comment-start (or (magit-get "core.commentChar") "#"))
  601. (setq git-rebase-comment-re (concat "^" (regexp-quote comment-start)))
  602. (setq font-lock-defaults (list (git-rebase-mode-font-lock-keywords) t t))
  603. (unless git-rebase-show-instructions
  604. (let ((inhibit-read-only t))
  605. (flush-lines git-rebase-comment-re)))
  606. (unless with-editor-mode
  607. ;; Maybe already enabled when using `shell-command' or an Emacs shell.
  608. (with-editor-mode 1))
  609. (when git-rebase-confirm-cancel
  610. (add-hook 'with-editor-cancel-query-functions
  611. 'git-rebase-cancel-confirm nil t))
  612. (setq-local redisplay-highlight-region-function 'git-rebase-highlight-region)
  613. (setq-local redisplay-unhighlight-region-function 'git-rebase-unhighlight-region)
  614. (add-hook 'with-editor-pre-cancel-hook 'git-rebase-autostash-save nil t)
  615. (add-hook 'with-editor-post-cancel-hook 'git-rebase-autostash-apply nil t)
  616. (setq imenu-prev-index-position-function
  617. #'magit-imenu--rebase-prev-index-position-function)
  618. (setq imenu-extract-index-name-function
  619. #'magit-imenu--rebase-extract-index-name-function)
  620. (when (boundp 'save-place)
  621. (setq save-place nil)))
  622. (defun git-rebase-cancel-confirm (force)
  623. (or (not (buffer-modified-p))
  624. force
  625. (magit-confirm 'abort-rebase "Abort this rebase" nil 'noabort)))
  626. (defun git-rebase-autostash-save ()
  627. (--when-let (magit-file-line (magit-git-dir "rebase-merge/autostash"))
  628. (push (cons 'stash it) with-editor-cancel-alist)))
  629. (defun git-rebase-autostash-apply ()
  630. (--when-let (cdr (assq 'stash with-editor-cancel-alist))
  631. (magit-stash-apply it)))
  632. (defun git-rebase-match-comment-line (limit)
  633. (re-search-forward (concat git-rebase-comment-re ".*") limit t))
  634. (defun git-rebase-mode-font-lock-keywords ()
  635. "Font lock keywords for Git-Rebase mode."
  636. `((,(concat "^" (cdr (assq 'commit git-rebase-line-regexps)))
  637. (1 'font-lock-keyword-face)
  638. (3 'git-rebase-hash)
  639. (4 'git-rebase-description))
  640. (,(concat "^" (cdr (assq 'exec git-rebase-line-regexps)))
  641. (1 'font-lock-keyword-face)
  642. (3 'git-rebase-description))
  643. (,(concat "^" (cdr (assq 'bare git-rebase-line-regexps)))
  644. (1 'font-lock-keyword-face))
  645. (,(concat "^" (cdr (assq 'label git-rebase-line-regexps)))
  646. (1 'font-lock-keyword-face)
  647. (3 'git-rebase-label)
  648. (4 'font-lock-comment-face))
  649. ("^\\(m\\(?:erge\\)?\\) -[Cc] \\([^ \n]+\\) \\([^ \n]+\\)\\( #.*\\)?"
  650. (1 'font-lock-keyword-face)
  651. (2 'git-rebase-hash)
  652. (3 'git-rebase-label)
  653. (4 'font-lock-comment-face))
  654. ("^\\(m\\(?:erge\\)?\\) \\([^ \n]+\\)"
  655. (1 'font-lock-keyword-face)
  656. (2 'git-rebase-label))
  657. (,(concat git-rebase-comment-re " *"
  658. (cdr (assq 'commit git-rebase-line-regexps)))
  659. 0 'git-rebase-killed-action t)
  660. (git-rebase-match-comment-line 0 'font-lock-comment-face)
  661. ("\\[[^[]*\\]"
  662. 0 'magit-keyword t)
  663. (,(format "^%s Rebase \\([^ ]*\\) onto \\([^ ]*\\)" comment-start)
  664. (1 'git-rebase-comment-hash t)
  665. (2 'git-rebase-comment-hash t))
  666. (,(format "^%s \\(Commands:\\)" comment-start)
  667. (1 'git-rebase-comment-heading t))
  668. (,(format "^%s Branch \\(.*\\)" comment-start)
  669. (1 'git-rebase-label t))))
  670. (defun git-rebase-mode-show-keybindings ()
  671. "Modify the \"Commands:\" section of the comment Git generates
  672. at the bottom of the file so that in place of the one-letter
  673. abbreviation for the command, it shows the command's keybinding.
  674. By default, this is the same except for the \"pick\" command."
  675. (let ((inhibit-read-only t))
  676. (save-excursion
  677. (goto-char (point-min))
  678. (when (and git-rebase-show-instructions
  679. (re-search-forward
  680. (concat git-rebase-comment-re "\\s-+p, pick")
  681. nil t))
  682. (goto-char (line-beginning-position))
  683. (pcase-dolist (`(,cmd . ,desc) git-rebase-command-descriptions)
  684. (insert (format "%s %-8s %s\n"
  685. comment-start
  686. (substitute-command-keys (format "\\[%s]" cmd))
  687. desc)))
  688. (while (re-search-forward (concat git-rebase-comment-re
  689. "\\( ?\\)\\([^\n,],\\) "
  690. "\\([^\n ]+\\) ")
  691. nil t)
  692. (let ((cmd (intern (concat "git-rebase-" (match-string 3)))))
  693. (if (not (fboundp cmd))
  694. (delete-region (line-beginning-position) (1+ (line-end-position)))
  695. (replace-match " " t t nil 1)
  696. (replace-match
  697. (format "%-8s"
  698. (mapconcat #'key-description
  699. (--remove (eq (elt it 0) 'menu-bar)
  700. (reverse (where-is-internal
  701. cmd git-rebase-mode-map)))
  702. ", "))
  703. t t nil 2))))))))
  704. (add-hook 'git-rebase-mode-hook 'git-rebase-mode-show-keybindings t)
  705. (defun git-rebase-mode-disable-before-save-hook ()
  706. (set (make-local-variable 'before-save-hook) nil))
  707. (add-hook 'git-rebase-mode-hook 'git-rebase-mode-disable-before-save-hook)
  708. ;;;###autoload
  709. (defconst git-rebase-filename-regexp "/git-rebase-todo\\'")
  710. ;;;###autoload
  711. (add-to-list 'auto-mode-alist
  712. (cons git-rebase-filename-regexp 'git-rebase-mode))
  713. (add-to-list 'with-editor-server-window-alist
  714. (cons git-rebase-filename-regexp 'switch-to-buffer))
  715. (eval-after-load 'recentf
  716. '(add-to-list 'recentf-exclude git-rebase-filename-regexp))
  717. (add-to-list 'with-editor-file-name-history-exclude git-rebase-filename-regexp)
  718. ;;; _
  719. (provide 'git-rebase)
  720. ;;; git-rebase.el ends here