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