commit | author | age
|
5cb5f7
|
1 |
;;; magit-diff.el --- inspect Git diffs -*- 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: Jonas Bernoulli <jonas@bernoul.li> |
|
9 |
;; Maintainer: Jonas Bernoulli <jonas@bernoul.li> |
|
10 |
|
|
11 |
;; Magit is free software; you can redistribute it and/or modify it |
|
12 |
;; under the terms of the GNU General Public License as published by |
|
13 |
;; the Free Software Foundation; either version 3, or (at your option) |
|
14 |
;; any later version. |
|
15 |
;; |
|
16 |
;; Magit is distributed in the hope that it will be useful, but WITHOUT |
|
17 |
;; ANY WARRANTY; without even the implied warranty of MERCHANTABILITY |
|
18 |
;; or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public |
|
19 |
;; License for more details. |
|
20 |
;; |
|
21 |
;; You should have received a copy of the GNU General Public License |
|
22 |
;; along with Magit. If not, see http://www.gnu.org/licenses. |
|
23 |
|
|
24 |
;;; Commentary: |
|
25 |
|
|
26 |
;; This library implements support for looking at Git diffs and |
|
27 |
;; commits. |
|
28 |
|
|
29 |
;;; Code: |
|
30 |
|
|
31 |
(eval-when-compile |
|
32 |
(require 'subr-x)) |
|
33 |
|
|
34 |
(require 'git-commit) |
|
35 |
(require 'magit-core) |
|
36 |
|
|
37 |
;; For `magit-diff-popup' |
|
38 |
(declare-function magit-stash-show "magit-stash" (stash &optional args files)) |
|
39 |
;; For `magit-diff-visit-file' |
|
40 |
(declare-function dired-jump "dired-x" (&optional other-window file-name)) |
|
41 |
(declare-function magit-find-file-noselect "magit-files" (rev file)) |
|
42 |
(declare-function magit-status-internal "magit-status" (directory)) |
|
43 |
;; For `magit-diff-while-committing' |
|
44 |
(declare-function magit-commit-message-buffer "magit-commit" ()) |
|
45 |
;; For `magit-insert-revision-gravatar' |
|
46 |
(defvar gravatar-size) |
|
47 |
;; For `magit-show-commit' and `magit-diff-show-or-scroll' |
|
48 |
(declare-function magit-current-blame-chunk "magit-blame" ()) |
|
49 |
(eval-when-compile |
|
50 |
(cl-pushnew 'orig-rev eieio--known-slot-names)) |
|
51 |
(declare-function magit-blame-mode "magit-blame" (&optional arg)) |
|
52 |
(defvar magit-blame-mode) |
|
53 |
(defvar git-rebase-line) |
|
54 |
;; For `magit-diff-unmerged' |
|
55 |
(declare-function magit-merge-in-progress-p "magit-merge" ()) |
|
56 |
(declare-function magit--merge-range "magit-merge" (&optional head)) |
|
57 |
|
|
58 |
(require 'diff-mode) |
|
59 |
(require 'smerge-mode) |
|
60 |
|
|
61 |
(defvar bookmark-make-record-function) |
|
62 |
|
|
63 |
;;; Options |
|
64 |
;;;; Diff Mode |
|
65 |
|
|
66 |
(defgroup magit-diff nil |
|
67 |
"Inspect and manipulate Git diffs." |
|
68 |
:link '(info-link "(magit)Diffing") |
|
69 |
:group 'magit-modes) |
|
70 |
|
|
71 |
(defcustom magit-diff-mode-hook nil |
|
72 |
"Hook run after entering Magit-Diff mode." |
|
73 |
:group 'magit-diff |
|
74 |
:type 'hook) |
|
75 |
|
|
76 |
(defcustom magit-diff-arguments '("--stat" "--no-ext-diff") |
|
77 |
"The diff arguments used in buffers whose mode derives from `magit-diff-mode'." |
|
78 |
:group 'magit-git-arguments |
|
79 |
:group 'magit-diff |
|
80 |
:type '(repeat (string :tag "Argument"))) |
|
81 |
|
|
82 |
(defcustom magit-diff-sections-hook |
|
83 |
'(magit-insert-diff |
|
84 |
magit-insert-xref-buttons) |
|
85 |
"Hook run to insert sections into a `magit-diff-mode' buffer." |
|
86 |
:package-version '(magit . "2.3.0") |
|
87 |
:group 'magit-diff |
|
88 |
:type 'hook) |
|
89 |
|
|
90 |
(defcustom magit-diff-expansion-threshold 60 |
|
91 |
"After how many seconds not to expand anymore diffs. |
|
92 |
|
|
93 |
Except in status buffers, diffs are usually start out fully |
|
94 |
expanded. Because that can take a long time, all diffs that |
|
95 |
haven't been fontified during a refresh before the threshold |
|
96 |
defined here are instead displayed with their bodies collapsed. |
|
97 |
|
|
98 |
Note that this can cause sections that were previously expanded |
|
99 |
to be collapsed. So you should not pick a very low value here. |
|
100 |
|
|
101 |
The hook function `magit-diff-expansion-threshold' has to be a |
|
102 |
member of `magit-section-set-visibility-hook' for this option |
|
103 |
to have any effect." |
|
104 |
:package-version '(magit . "2.9.0") |
|
105 |
:group 'magit-diff |
|
106 |
:type 'float) |
|
107 |
|
|
108 |
(defcustom magit-diff-highlight-hunk-body t |
|
109 |
"Whether to highlight bodies of selected hunk sections. |
|
110 |
This only has an effect if `magit-diff-highlight' is a |
|
111 |
member of `magit-section-highlight-hook', which see." |
|
112 |
:package-version '(magit . "2.1.0") |
|
113 |
:group 'magit-diff |
|
114 |
:type 'boolean) |
|
115 |
|
|
116 |
(defcustom magit-diff-highlight-hunk-region-functions |
|
117 |
'(magit-diff-highlight-hunk-region-dim-outside |
|
118 |
magit-diff-highlight-hunk-region-using-overlays) |
|
119 |
"The functions used to highlight the hunk-internal region. |
|
120 |
|
|
121 |
`magit-diff-highlight-hunk-region-dim-outside' overlays the outside |
|
122 |
of the hunk internal selection with a face that causes the added and |
|
123 |
removed lines to have the same background color as context lines. |
|
124 |
This function should not be removed from the value of this option. |
|
125 |
|
|
126 |
`magit-diff-highlight-hunk-region-using-overlays' and |
|
127 |
`magit-diff-highlight-hunk-region-using-underline' emphasize the |
|
128 |
region by placing delimiting horizonal lines before and after it. |
|
129 |
The underline variant was implemented because Eli said that is |
|
130 |
how we should do it. However the overlay variant actually works |
|
131 |
better. Also see https://github.com/magit/magit/issues/2758. |
|
132 |
|
|
133 |
Instead of, or in addition to, using delimiting horizontal lines, |
|
134 |
to emphasize the boundaries, you may which to emphasize the text |
|
135 |
itself, using `magit-diff-highlight-hunk-region-using-face'. |
|
136 |
|
|
137 |
In terminal frames it's not possible to draw lines as the overlay |
|
138 |
and underline variants normally do, so there they fall back to |
|
139 |
calling the face function instead." |
|
140 |
:package-version '(magit . "2.9.0") |
|
141 |
:set-after '(magit-diff-show-lines-boundaries) |
|
142 |
:group 'magit-diff |
|
143 |
:type 'hook |
|
144 |
:options '(magit-diff-highlight-hunk-region-dim-outside |
|
145 |
magit-diff-highlight-hunk-region-using-underline |
|
146 |
magit-diff-highlight-hunk-region-using-overlays |
|
147 |
magit-diff-highlight-hunk-region-using-face)) |
|
148 |
|
|
149 |
(defcustom magit-diff-unmarked-lines-keep-foreground t |
|
150 |
"Whether `magit-diff-highlight-hunk-region-dim-outside' preserves foreground. |
|
151 |
When this is set to nil, then that function only adjusts the |
|
152 |
foreground color but added and removed lines outside the region |
|
153 |
keep their distinct foreground colors." |
|
154 |
:package-version '(magit . "2.9.0") |
|
155 |
:group 'magit-diff |
|
156 |
:type 'boolean) |
|
157 |
|
|
158 |
(defcustom magit-diff-refine-hunk nil |
|
159 |
"Whether to show word-granularity differences within diff hunks. |
|
160 |
|
|
161 |
nil never show fine differences. |
|
162 |
t show fine differences for the current diff hunk only. |
|
163 |
`all' show fine differences for all displayed diff hunks." |
|
164 |
:group 'magit-diff |
|
165 |
:safe (lambda (val) (memq val '(nil t all))) |
|
166 |
:type '(choice (const :tag "Never" nil) |
|
167 |
(const :tag "Current" t) |
|
168 |
(const :tag "All" all))) |
|
169 |
|
|
170 |
(put 'magit-diff-refine-hunk 'permanent-local t) |
|
171 |
|
|
172 |
(defcustom magit-diff-adjust-tab-width nil |
|
173 |
"Whether to adjust the width of tabs in diffs. |
|
174 |
|
|
175 |
Determining the correct width can be expensive if it requires |
|
176 |
opening large and/or many files, so the widths are cached in |
|
177 |
the variable `magit-diff--tab-width-cache'. Set that to nil |
|
178 |
to invalidate the cache. |
|
179 |
|
|
180 |
nil Never ajust tab width. Use `tab-width's value from |
|
181 |
the Magit buffer itself instead. |
|
182 |
|
|
183 |
t If the corresponding file-visiting buffer exits, then |
|
184 |
use `tab-width's value from that buffer. Doing this is |
|
185 |
cheap, so this value is used even if a corresponding |
|
186 |
cache entry exists. |
|
187 |
|
|
188 |
`always' If there is no such buffer, then temporarily visit the |
|
189 |
file to determine the value. |
|
190 |
|
|
191 |
NUMBER Like `always', but don't visit files larger than NUMBER |
|
192 |
bytes." |
|
193 |
:package-version '(magit . "2.12.0") |
|
194 |
:group 'magit-diff |
|
195 |
:type '(choice (const :tag "Never" nil) |
|
196 |
(const :tag "If file-visiting buffer exists" t) |
|
197 |
(const :tag "... or file isn't larger than bytes" all) |
|
198 |
(const :tag "Always" always))) |
|
199 |
|
|
200 |
(defcustom magit-diff-paint-whitespace t |
|
201 |
"Specify where to highlight whitespace errors. |
|
202 |
See `magit-diff-highlight-trailing', |
|
203 |
`magit-diff-highlight-indentation'. The symbol t means in all |
|
204 |
diffs, `status' means only in the status buffer, and nil means |
|
205 |
nowhere." |
|
206 |
:group 'magit-diff |
|
207 |
:safe (lambda (val) (memq val '(t nil status))) |
|
208 |
:type '(choice (const :tag "Always" t) |
|
209 |
(const :tag "Never" nil) |
|
210 |
(const :tag "In status buffer" status))) |
|
211 |
|
|
212 |
(defcustom magit-diff-highlight-trailing t |
|
213 |
"Whether to highlight whitespace at the end of a line in diffs. |
|
214 |
Used only when `magit-diff-paint-whitespace' is non-nil." |
|
215 |
:group 'magit-diff |
|
216 |
:safe 'booleanp |
|
217 |
:type 'boolean) |
|
218 |
|
|
219 |
(defcustom magit-diff-highlight-indentation nil |
|
220 |
"Highlight the \"wrong\" indentation style. |
|
221 |
Used only when `magit-diff-paint-whitespace' is non-nil. |
|
222 |
|
|
223 |
The value is a list of cons cells. The car is a regular |
|
224 |
expression, and the cdr is the value that applies to repositories |
|
225 |
whose directory matches the regular expression. If more than one |
|
226 |
element matches, then the *last* element in the list applies. |
|
227 |
The default value should therefore come first in the list. |
|
228 |
|
|
229 |
If the value is `tabs', highlight indentation with tabs. If the |
|
230 |
value is an integer, highlight indentation with at least that |
|
231 |
many spaces. Otherwise, highlight neither." |
|
232 |
:group 'magit-diff |
|
233 |
:type `(repeat (cons (string :tag "Directory regexp") |
|
234 |
(choice (const :tag "Tabs" tabs) |
|
235 |
(integer :tag "Spaces" :value ,tab-width) |
|
236 |
(const :tag "Neither" nil))))) |
|
237 |
|
|
238 |
(defcustom magit-diff-hide-trailing-cr-characters |
|
239 |
(and (memq system-type '(ms-dos windows-nt)) t) |
|
240 |
"Whether to hide ^M characters at the end of a line in diffs." |
|
241 |
:package-version '(magit . "2.6.0") |
|
242 |
:group 'magit-diff |
|
243 |
:type 'boolean) |
|
244 |
|
|
245 |
(defcustom magit-diff-visit-previous-blob t |
|
246 |
"Whether `magit-diff-visit-file' may visit the previous blob. |
|
247 |
|
|
248 |
When this is t and point is on a removed line in a diff for a |
|
249 |
committed change, then `magit-diff-visit-file' visits the blob |
|
250 |
from the last revision which still had that line. |
|
251 |
|
|
252 |
Currently this is only supported for committed changes, for |
|
253 |
staged and unstaged changes `magit-diff-visit-file' always |
|
254 |
visits the file in the working tree." |
|
255 |
:package-version '(magit . "2.9.0") |
|
256 |
:group 'magit-diff |
|
257 |
:type 'boolean) |
|
258 |
|
|
259 |
(defcustom magit-diff-highlight-keywords t |
|
260 |
"Whether to highlight bracketed keywords in commit messages." |
|
261 |
:package-version '(magit . "2.12.0") |
|
262 |
:group 'magit-diff |
|
263 |
:type 'boolean) |
|
264 |
|
|
265 |
;;;; File Diff |
|
266 |
|
|
267 |
(defcustom magit-diff-buffer-file-locked t |
|
268 |
"Whether `magit-diff-buffer-file' uses a dedicated buffer." |
|
269 |
:package-version '(magit . "2.7.0") |
|
270 |
:group 'magit-commands |
|
271 |
:group 'magit-diff |
|
272 |
:type 'boolean) |
|
273 |
|
|
274 |
;;;; Revision Mode |
|
275 |
|
|
276 |
(defgroup magit-revision nil |
|
277 |
"Inspect and manipulate Git commits." |
|
278 |
:link '(info-link "(magit)Revision Buffer") |
|
279 |
:group 'magit-modes) |
|
280 |
|
|
281 |
(defcustom magit-revision-mode-hook '(bug-reference-mode) |
|
282 |
"Hook run after entering Magit-Revision mode." |
|
283 |
:group 'magit-revision |
|
284 |
:type 'hook |
|
285 |
:options '(bug-reference-mode)) |
|
286 |
|
|
287 |
(defcustom magit-revision-sections-hook |
|
288 |
'(magit-insert-revision-tag |
|
289 |
magit-insert-revision-headers |
|
290 |
magit-insert-revision-message |
|
291 |
magit-insert-revision-notes |
|
292 |
magit-insert-revision-diff |
|
293 |
magit-insert-xref-buttons) |
|
294 |
"Hook run to insert sections into a `magit-revision-mode' buffer." |
|
295 |
:package-version '(magit . "2.3.0") |
|
296 |
:group 'magit-revision |
|
297 |
:type 'hook) |
|
298 |
|
|
299 |
(defcustom magit-revision-headers-format "\ |
|
300 |
Author: %aN <%aE> |
|
301 |
AuthorDate: %ad |
|
302 |
Commit: %cN <%cE> |
|
303 |
CommitDate: %cd |
|
304 |
" |
|
305 |
"Format string used to insert headers in revision buffers. |
|
306 |
|
|
307 |
All headers in revision buffers are inserted by the section |
|
308 |
inserter `magit-insert-revision-headers'. Some of the headers |
|
309 |
are created by calling `git show --format=FORMAT' where FORMAT |
|
310 |
is the format specified here. Other headers are hard coded or |
|
311 |
subject to option `magit-revision-insert-related-refs'." |
|
312 |
:package-version '(magit . "2.3.0") |
|
313 |
:group 'magit-revision |
|
314 |
:type 'string) |
|
315 |
|
|
316 |
(defcustom magit-revision-insert-related-refs t |
|
317 |
"Whether to show related branches in revision buffers |
|
318 |
|
|
319 |
`nil' Don't show any related branches. |
|
320 |
`t' Show related local branches. |
|
321 |
`all' Show related local and remote branches. |
|
322 |
`mixed' Show all containing branches and local merged branches." |
|
323 |
:package-version '(magit . "2.1.0") |
|
324 |
:group 'magit-revision |
|
325 |
:type '(choice (const :tag "don't" nil) |
|
326 |
(const :tag "local only" t) |
|
327 |
(const :tag "all related" all) |
|
328 |
(const :tag "all containing, local merged" mixed))) |
|
329 |
|
|
330 |
(defcustom magit-revision-use-hash-sections 'quicker |
|
331 |
"Whether to turn hashes inside the commit message into sections. |
|
332 |
|
|
333 |
If non-nil, then hashes inside the commit message are turned into |
|
334 |
`commit' sections. There is a trade off to be made between |
|
335 |
performance and reliability: |
|
336 |
|
|
337 |
- `slow' calls git for every word to be absolutely sure. |
|
338 |
- `quick' skips words less than seven characters long. |
|
339 |
- `quicker' additionally skips words that don't contain a number. |
|
340 |
- `quickest' uses all words that are at least seven characters |
|
341 |
long and which contain at least one number as well as at least |
|
342 |
one letter. |
|
343 |
|
|
344 |
If nil, then no hashes are turned into sections, but you can |
|
345 |
still visit the commit at point using \"RET\"." |
|
346 |
:package-version '(magit . "2.12.0") |
|
347 |
:group 'magit-revision |
|
348 |
:type '(choice (const :tag "Use sections, quickest" quickest) |
|
349 |
(const :tag "Use sections, quicker" quicker) |
|
350 |
(const :tag "Use sections, quick" quick) |
|
351 |
(const :tag "Use sections, slow" slow) |
|
352 |
(const :tag "Don't use sections" nil))) |
|
353 |
|
|
354 |
(defcustom magit-revision-show-gravatars nil |
|
355 |
"Whether to show gravatar images in revision buffers. |
|
356 |
|
|
357 |
If nil, then don't insert any gravatar images. If t, then insert |
|
358 |
both images. If `author' or `committer', then insert only the |
|
359 |
respective image. |
|
360 |
|
|
361 |
If you have customized the option `magit-revision-header-format' |
|
362 |
and want to insert the images then you might also have to specify |
|
363 |
where to do so. In that case the value has to be a cons-cell of |
|
364 |
two regular expressions. The car specifies where to insert the |
|
365 |
author's image. The top half of the image is inserted right |
|
366 |
after the matched text, the bottom half on the next line in the |
|
367 |
same column. The cdr specifies where to insert the committer's |
|
368 |
image, accordingly. Either the car or the cdr may be nil." |
|
369 |
:package-version '(magit . "2.3.0") |
|
370 |
:group 'magit-revision |
|
371 |
:type '(choice (const :tag "Don't show gravatars" nil) |
|
372 |
(const :tag "Show gravatars" t) |
|
373 |
(const :tag "Show author gravatar" author) |
|
374 |
(const :tag "Show committer gravatar" committer) |
|
375 |
(cons :tag "Show gravatars using custom pattern." |
|
376 |
(regexp :tag "Author regexp" "^Author: ") |
|
377 |
(regexp :tag "Committer regexp" "^Commit: ")))) |
|
378 |
|
|
379 |
(defcustom magit-revision-use-gravatar-kludge nil |
|
380 |
"Whether to work around a bug which affects display of gravatars. |
|
381 |
|
|
382 |
Gravatar images are spliced into two halves which are then |
|
383 |
displayed on separate lines. On OS X the splicing has a bug in |
|
384 |
some Emacs builds, which causes the top and bottom halves to be |
|
385 |
interchanged. Enabling this option works around this issue by |
|
386 |
interchanging the halves once more, which cancels out the effect |
|
387 |
of the bug. |
|
388 |
|
|
389 |
See https://github.com/magit/magit/issues/2265 |
|
390 |
and https://debbugs.gnu.org/cgi/bugreport.cgi?bug=7847. |
|
391 |
|
|
392 |
Starting with Emacs 26.1 this kludge should not be required for |
|
393 |
any build." |
|
394 |
:package-version '(magit . "2.3.0") |
|
395 |
:group 'magit-revision |
|
396 |
:type 'boolean) |
|
397 |
|
|
398 |
(defcustom magit-revision-fill-summary-line nil |
|
399 |
"Whether to fill excessively long summary lines. |
|
400 |
|
|
401 |
If this is an integer, then the summary line is filled if it is |
|
402 |
longer than either the limit specified here or `window-width'. |
|
403 |
|
|
404 |
You may want to only set this locally in \".dir-locals-2.el\" for |
|
405 |
repositories known to contain bad commit messages. |
|
406 |
|
|
407 |
The body of the message is left alone because (a) most people who |
|
408 |
write excessively long summary lines usually don't add a body and |
|
409 |
(b) even people who have the decency to wrap their lines may have |
|
410 |
a good reason to include a long line in the body sometimes." |
|
411 |
:package-version '(magit . "2.90.0") |
|
412 |
:group 'magit-revision |
|
413 |
:type '(choice (const :tag "Don't fill" nil) |
|
414 |
(integer :tag "Fill if longer than"))) |
|
415 |
|
|
416 |
;;;; Diff Sections |
|
417 |
|
|
418 |
(defcustom magit-diff-section-arguments '("--no-ext-diff") |
|
419 |
"The diff arguments used in buffers that show other things besides diffs." |
|
420 |
:group 'magit-git-arguments |
|
421 |
:group 'magit-diff |
|
422 |
:group 'magit-status |
|
423 |
:type '(repeat (string :tag "Argument"))) |
|
424 |
|
|
425 |
(put 'magit-diff-section-arguments 'permanent-local t) |
|
426 |
|
|
427 |
;;; Faces |
|
428 |
|
|
429 |
(defface magit-diff-file-heading |
|
430 |
'((t :weight bold)) |
|
431 |
"Face for diff file headings." |
|
432 |
:group 'magit-faces) |
|
433 |
|
|
434 |
(defface magit-diff-file-heading-highlight |
|
435 |
'((t :inherit (magit-section-highlight))) |
|
436 |
"Face for current diff file headings." |
|
437 |
:group 'magit-faces) |
|
438 |
|
|
439 |
(defface magit-diff-file-heading-selection |
|
440 |
'((((class color) (background light)) |
|
441 |
:inherit magit-diff-file-heading-highlight |
|
442 |
:foreground "salmon4") |
|
443 |
(((class color) (background dark)) |
|
444 |
:inherit magit-diff-file-heading-highlight |
|
445 |
:foreground "LightSalmon3")) |
|
446 |
"Face for selected diff file headings." |
|
447 |
:group 'magit-faces) |
|
448 |
|
|
449 |
(defface magit-diff-hunk-heading |
|
450 |
'((((class color) (background light)) |
|
451 |
:background "grey80" |
|
452 |
:foreground "grey30") |
|
453 |
(((class color) (background dark)) |
|
454 |
:background "grey25" |
|
455 |
:foreground "grey70")) |
|
456 |
"Face for diff hunk headings." |
|
457 |
:group 'magit-faces) |
|
458 |
|
|
459 |
(defface magit-diff-hunk-heading-highlight |
|
460 |
'((((class color) (background light)) |
|
461 |
:background "grey75" |
|
462 |
:foreground "grey30") |
|
463 |
(((class color) (background dark)) |
|
464 |
:background "grey35" |
|
465 |
:foreground "grey70")) |
|
466 |
"Face for current diff hunk headings." |
|
467 |
:group 'magit-faces) |
|
468 |
|
|
469 |
(defface magit-diff-hunk-heading-selection |
|
470 |
'((((class color) (background light)) |
|
471 |
:inherit magit-diff-hunk-heading-highlight |
|
472 |
:foreground "salmon4") |
|
473 |
(((class color) (background dark)) |
|
474 |
:inherit magit-diff-hunk-heading-highlight |
|
475 |
:foreground "LightSalmon3")) |
|
476 |
"Face for selected diff hunk headings." |
|
477 |
:group 'magit-faces) |
|
478 |
|
|
479 |
(defface magit-diff-hunk-region |
|
480 |
'((t :inherit bold)) |
|
481 |
"Face used by `magit-diff-highlight-hunk-region-using-face'. |
|
482 |
|
|
483 |
This face is overlayed over text that uses other hunk faces, |
|
484 |
and those normally set the foreground and background colors. |
|
485 |
The `:foreground' and especially the `:background' properties |
|
486 |
should be avoided here. Setting the latter would cause the |
|
487 |
lose of information. Good properties to set here are `:weight' |
|
488 |
and `:slant'." |
|
489 |
:group 'magit-faces) |
|
490 |
|
|
491 |
(defface magit-diff-lines-heading |
|
492 |
'((((class color) (background light)) |
|
493 |
:inherit magit-diff-hunk-heading-highlight |
|
494 |
:background "LightSalmon3") |
|
495 |
(((class color) (background dark)) |
|
496 |
:inherit magit-diff-hunk-heading-highlight |
|
497 |
:foreground "grey80" |
|
498 |
:background "salmon4")) |
|
499 |
"Face for diff hunk heading when lines are marked." |
|
500 |
:group 'magit-faces) |
|
501 |
|
|
502 |
(defface magit-diff-lines-boundary |
|
503 |
'((t :inherit magit-diff-lines-heading)) |
|
504 |
"Face for boundary of marked lines in diff hunk." |
|
505 |
:group 'magit-faces) |
|
506 |
|
|
507 |
(defface magit-diff-conflict-heading |
|
508 |
'((t :inherit magit-diff-hunk-heading)) |
|
509 |
"Face for conflict markers." |
|
510 |
:group 'magit-faces) |
|
511 |
|
|
512 |
(defface magit-diff-added |
|
513 |
'((((class color) (background light)) |
|
514 |
:background "#ddffdd" |
|
515 |
:foreground "#22aa22") |
|
516 |
(((class color) (background dark)) |
|
517 |
:background "#335533" |
|
518 |
:foreground "#ddffdd")) |
|
519 |
"Face for lines in a diff that have been added." |
|
520 |
:group 'magit-faces) |
|
521 |
|
|
522 |
(defface magit-diff-removed |
|
523 |
'((((class color) (background light)) |
|
524 |
:background "#ffdddd" |
|
525 |
:foreground "#aa2222") |
|
526 |
(((class color) (background dark)) |
|
527 |
:background "#553333" |
|
528 |
:foreground "#ffdddd")) |
|
529 |
"Face for lines in a diff that have been removed." |
|
530 |
:group 'magit-faces) |
|
531 |
|
|
532 |
(defface magit-diff-our |
|
533 |
'((t :inherit magit-diff-removed)) |
|
534 |
"Face for lines in a diff for our side in a conflict." |
|
535 |
:group 'magit-faces) |
|
536 |
|
|
537 |
(defface magit-diff-base |
|
538 |
'((((class color) (background light)) |
|
539 |
:background "#ffffcc" |
|
540 |
:foreground "#aaaa11") |
|
541 |
(((class color) (background dark)) |
|
542 |
:background "#555522" |
|
543 |
:foreground "#ffffcc")) |
|
544 |
"Face for lines in a diff for the base side in a conflict." |
|
545 |
:group 'magit-faces) |
|
546 |
|
|
547 |
(defface magit-diff-their |
|
548 |
'((t :inherit magit-diff-added)) |
|
549 |
"Face for lines in a diff for their side in a conflict." |
|
550 |
:group 'magit-faces) |
|
551 |
|
|
552 |
(defface magit-diff-context |
|
553 |
'((((class color) (background light)) :foreground "grey50") |
|
554 |
(((class color) (background dark)) :foreground "grey70")) |
|
555 |
"Face for lines in a diff that are unchanged." |
|
556 |
:group 'magit-faces) |
|
557 |
|
|
558 |
(defface magit-diff-added-highlight |
|
559 |
'((((class color) (background light)) |
|
560 |
:background "#cceecc" |
|
561 |
:foreground "#22aa22") |
|
562 |
(((class color) (background dark)) |
|
563 |
:background "#336633" |
|
564 |
:foreground "#cceecc")) |
|
565 |
"Face for lines in a diff that have been added." |
|
566 |
:group 'magit-faces) |
|
567 |
|
|
568 |
(defface magit-diff-removed-highlight |
|
569 |
'((((class color) (background light)) |
|
570 |
:background "#eecccc" |
|
571 |
:foreground "#aa2222") |
|
572 |
(((class color) (background dark)) |
|
573 |
:background "#663333" |
|
574 |
:foreground "#eecccc")) |
|
575 |
"Face for lines in a diff that have been removed." |
|
576 |
:group 'magit-faces) |
|
577 |
|
|
578 |
(defface magit-diff-our-highlight |
|
579 |
'((t :inherit magit-diff-removed-highlight)) |
|
580 |
"Face for lines in a diff for our side in a conflict." |
|
581 |
:group 'magit-faces) |
|
582 |
|
|
583 |
(defface magit-diff-base-highlight |
|
584 |
'((((class color) (background light)) |
|
585 |
:background "#eeeebb" |
|
586 |
:foreground "#aaaa11") |
|
587 |
(((class color) (background dark)) |
|
588 |
:background "#666622" |
|
589 |
:foreground "#eeeebb")) |
|
590 |
"Face for lines in a diff for the base side in a conflict." |
|
591 |
:group 'magit-faces) |
|
592 |
|
|
593 |
(defface magit-diff-their-highlight |
|
594 |
'((t :inherit magit-diff-added-highlight)) |
|
595 |
"Face for lines in a diff for their side in a conflict." |
|
596 |
:group 'magit-faces) |
|
597 |
|
|
598 |
(defface magit-diff-context-highlight |
|
599 |
'((((class color) (background light)) |
|
600 |
:background "grey95" |
|
601 |
:foreground "grey50") |
|
602 |
(((class color) (background dark)) |
|
603 |
:background "grey20" |
|
604 |
:foreground "grey70")) |
|
605 |
"Face for lines in the current context in a diff." |
|
606 |
:group 'magit-faces) |
|
607 |
|
|
608 |
(defface magit-diff-whitespace-warning |
|
609 |
'((t :inherit trailing-whitespace)) |
|
610 |
"Face for highlighting whitespace errors added lines." |
|
611 |
:group 'magit-faces) |
|
612 |
|
|
613 |
(defface magit-diffstat-added |
|
614 |
'((((class color) (background light)) :foreground "#22aa22") |
|
615 |
(((class color) (background dark)) :foreground "#448844")) |
|
616 |
"Face for plus sign in diffstat." |
|
617 |
:group 'magit-faces) |
|
618 |
|
|
619 |
(defface magit-diffstat-removed |
|
620 |
'((((class color) (background light)) :foreground "#aa2222") |
|
621 |
(((class color) (background dark)) :foreground "#aa4444")) |
|
622 |
"Face for minus sign in diffstat." |
|
623 |
:group 'magit-faces) |
|
624 |
|
|
625 |
;;; Commands |
|
626 |
;;;; Diff popups |
|
627 |
|
|
628 |
(defconst magit-diff-popup-common-keywords |
|
629 |
'(:variable magit-diff-arguments |
|
630 |
:man-page "git-diff")) |
|
631 |
|
|
632 |
(defconst magit-diff-popup-common-options |
|
633 |
'((?f "Limit to files" "-- " magit-read-files) |
|
634 |
(?u "Context lines" "-U") |
|
635 |
(?m "Detect renames" "-M") |
|
636 |
(?c "Detect copies" "-C") |
|
637 |
(?a "Diff algorithm" "--diff-algorithm=" magit-diff-select-algorithm) |
|
638 |
(?i "Ignore submodules" "--ignore-submodules=" |
|
639 |
magit-diff-select-ignore-submodules))) |
|
640 |
|
|
641 |
(defconst magit-diff-popup-common-switches |
|
642 |
'((?f "Show surrounding functions" "--function-context") |
|
643 |
(?b "Ignore whitespace changes" "--ignore-space-change") |
|
644 |
(?w "Ignore all whitespace" "--ignore-all-space") |
|
645 |
(?x "Disallow external diff drivers" "--no-ext-diff"))) |
|
646 |
|
|
647 |
(defvar magit-diff-popup |
|
648 |
`(,@magit-diff-popup-common-keywords |
|
649 |
:options ,magit-diff-popup-common-options |
|
650 |
:switches (,@magit-diff-popup-common-switches |
|
651 |
(?s "Show stats" "--stat")) |
|
652 |
:actions ((?d "Dwim" magit-diff-dwim) |
|
653 |
(?u "Diff unstaged" magit-diff-unstaged) |
|
654 |
(?c "Show commit" magit-show-commit) |
|
655 |
(?r "Diff range" magit-diff-range) |
|
656 |
(?s "Diff staged" magit-diff-staged) |
|
657 |
(?t "Show stash" magit-stash-show) |
|
658 |
(?p "Diff paths" magit-diff-paths) |
|
659 |
(?w "Diff worktree" magit-diff-working-tree)) |
|
660 |
:default-action magit-diff-dwim |
|
661 |
:max-action-columns 3)) |
|
662 |
|
|
663 |
(defvar magit-diff-refresh-popup |
|
664 |
`(,@magit-diff-popup-common-keywords |
|
665 |
:options ,magit-diff-popup-common-options |
|
666 |
:switches ,magit-diff-popup-common-switches |
|
667 |
:actions ((?g "Refresh" magit-diff-refresh) |
|
668 |
(?t "Toggle hunk refinement" magit-diff-toggle-refine-hunk) |
|
669 |
(?s "Set defaults" magit-diff-set-default-arguments) |
|
670 |
(?F "Toggle file filter" magit-diff-toggle-file-filter) |
|
671 |
(?w "Save defaults" magit-diff-save-default-arguments)) |
|
672 |
:max-action-columns 2)) |
|
673 |
|
|
674 |
(defvar magit-diff-mode-refresh-popup |
|
675 |
`(,@magit-diff-popup-common-keywords |
|
676 |
:options ,magit-diff-popup-common-options |
|
677 |
:switches (,@magit-diff-popup-common-switches |
|
678 |
(?s "Show stats" "--stat")) |
|
679 |
:actions ((?g "Refresh" magit-diff-refresh) |
|
680 |
(?t "Toggle hunk refinement" magit-diff-toggle-refine-hunk) |
|
681 |
(?s "Set defaults" magit-diff-set-default-arguments) |
|
682 |
(?r "Switch range type" magit-diff-switch-range-type) |
|
683 |
(?w "Save defaults" magit-diff-save-default-arguments) |
|
684 |
(?f "Flip revisions" magit-diff-flip-revs) nil |
|
685 |
(?F "Toggle file filter" magit-diff-toggle-file-filter)) |
|
686 |
:max-action-columns 2)) |
|
687 |
|
|
688 |
(defvar magit-revision-mode-refresh-popup |
|
689 |
`(,@magit-diff-popup-common-keywords |
|
690 |
:options ,magit-diff-popup-common-options |
|
691 |
:switches (,@magit-diff-popup-common-switches |
|
692 |
(?s "Show stats" "--stat")) |
|
693 |
:actions ((?g "Refresh" magit-diff-refresh) |
|
694 |
(?t "Toggle hunk refinement" magit-diff-toggle-refine-hunk) |
|
695 |
(?s "Set defaults" magit-diff-set-default-arguments) |
|
696 |
(?F "Toggle file filter" magit-diff-toggle-file-filter) |
|
697 |
(?w "Save defaults" magit-diff-save-default-arguments)) |
|
698 |
:max-action-columns 2)) |
|
699 |
|
|
700 |
(magit-define-popup-keys-deferred 'magit-diff-popup) |
|
701 |
(magit-define-popup-keys-deferred 'magit-diff-refresh-popup) |
|
702 |
(magit-define-popup-keys-deferred 'magit-diff-mode-refresh-popup) |
|
703 |
(magit-define-popup-keys-deferred 'magit-revision-mode-refresh-popup) |
|
704 |
|
|
705 |
(defvar magit-diff-section-file-args nil) |
|
706 |
(put 'magit-diff-section-file-args 'permanent-local t) |
|
707 |
(put 'magit-diff-section-file-args 'safe-local-variable |
|
708 |
(lambda (val) |
|
709 |
(and (listp val) |
|
710 |
(-all-p #'stringp val)))) |
|
711 |
|
|
712 |
(defun magit-diff-get-buffer-args () |
|
713 |
(cond ((and magit-use-sticky-arguments |
|
714 |
(derived-mode-p 'magit-diff-mode)) |
|
715 |
(list (nth 2 magit-refresh-args) |
|
716 |
(nth 3 magit-refresh-args))) |
|
717 |
((and (eq magit-use-sticky-arguments t) |
|
718 |
(--when-let (magit-mode-get-buffer 'magit-diff-mode) |
|
719 |
(with-current-buffer it |
|
720 |
(list (nth 2 magit-refresh-args) |
|
721 |
(nth 3 magit-refresh-args)))))) |
|
722 |
(t |
|
723 |
(list (default-value 'magit-diff-arguments) nil)))) |
|
724 |
|
|
725 |
(defun magit-diff-arguments (&optional refresh) |
|
726 |
(cond ((memq magit-current-popup '(magit-diff-popup magit-diff-refresh-popup)) |
|
727 |
(magit-popup-export-file-args magit-current-popup-args)) |
|
728 |
((and refresh (not (derived-mode-p 'magit-diff-mode))) |
|
729 |
(list magit-diff-section-arguments |
|
730 |
magit-diff-section-file-args)) |
|
731 |
(t |
|
732 |
(magit-diff-get-buffer-args)))) |
|
733 |
|
|
734 |
;;;###autoload |
|
735 |
(defun magit-diff-popup (arg) |
|
736 |
"Popup console for diff commands." |
|
737 |
(interactive "P") |
|
738 |
(let ((magit-diff-arguments |
|
739 |
;; We cannot possibly know what suffix command the user is |
|
740 |
;; about to invoke, so we also don't know from which buffer |
|
741 |
;; we should get the current values. However it is much |
|
742 |
;; more likely that we will end up updating the diff buffer, |
|
743 |
;; and we therefore use the value from that buffer. |
|
744 |
(apply #'magit-popup-import-file-args (magit-diff-get-buffer-args)))) |
|
745 |
(magit-invoke-popup 'magit-diff-popup nil arg))) |
|
746 |
|
|
747 |
;;;###autoload |
|
748 |
(defun magit-diff-buffer-file-popup () |
|
749 |
"Popup console for diff commands. |
|
750 |
|
|
751 |
This is a variant of `magit-diff-popup' which shows the same popup |
|
752 |
but which limits the diff to the file being visited in the current |
|
753 |
buffer." |
|
754 |
(interactive) |
|
755 |
(if-let ((file (magit-file-relative-name))) |
|
756 |
(let ((magit-diff-arguments |
|
757 |
(magit-popup-import-file-args |
|
758 |
(if-let ((buffer (magit-mode-get-buffer 'magit-diff-mode))) |
|
759 |
(with-current-buffer buffer |
|
760 |
(nth 3 magit-refresh-args)) |
|
761 |
(default-value 'magit-diff-arguments)) |
|
762 |
(list file)))) |
|
763 |
(magit-invoke-popup 'magit-diff-popup nil nil)) |
|
764 |
(user-error "Buffer isn't visiting a file"))) |
|
765 |
|
|
766 |
(defun magit-diff-refresh-popup (arg) |
|
767 |
"Popup console for changing diff arguments in the current buffer." |
|
768 |
(interactive "P") |
|
769 |
(let ((magit-diff-refresh-popup |
|
770 |
(pcase major-mode |
|
771 |
(`magit-revision-mode magit-revision-mode-refresh-popup) |
|
772 |
(`magit-diff-mode magit-diff-mode-refresh-popup) |
|
773 |
(_ magit-diff-refresh-popup))) |
|
774 |
(magit-diff-arguments |
|
775 |
(if (derived-mode-p 'magit-diff-mode) |
|
776 |
(magit-popup-import-file-args (nth 2 magit-refresh-args) |
|
777 |
(nth 3 magit-refresh-args)) |
|
778 |
(magit-popup-import-file-args magit-diff-section-arguments |
|
779 |
magit-diff-section-file-args)))) |
|
780 |
(magit-invoke-popup 'magit-diff-refresh-popup nil arg))) |
|
781 |
|
|
782 |
(defun magit-diff-select-algorithm (&rest _ignore) |
|
783 |
(magit-read-char-case nil t |
|
784 |
(?d "[d]efault" "default") |
|
785 |
(?m "[m]inimal" "minimal") |
|
786 |
(?p "[p]atience" "patience") |
|
787 |
(?h "[h]istogram" "histogram"))) |
|
788 |
|
|
789 |
(defun magit-diff-select-ignore-submodules (&rest _ignored) |
|
790 |
(magit-read-char-case "Ignore submodules " t |
|
791 |
(?u "[u]ntracked" "untracked") |
|
792 |
(?d "[d]irty" "dirty") |
|
793 |
(?a "[a]ll" "all"))) |
|
794 |
|
|
795 |
;;;; Diff commands |
|
796 |
|
|
797 |
;;;###autoload |
|
798 |
(defun magit-diff-dwim (&optional args files) |
|
799 |
"Show changes for the thing at point." |
|
800 |
(interactive (magit-diff-arguments)) |
|
801 |
(pcase (magit-diff--dwim) |
|
802 |
(`unmerged (magit-diff-unmerged args files)) |
|
803 |
(`unstaged (magit-diff-unstaged args files)) |
|
804 |
(`staged |
|
805 |
(let ((file (magit-file-at-point))) |
|
806 |
(if (and file (equal (cddr (car (magit-file-status file))) '(?D ?U))) |
|
807 |
;; File was deleted by us and modified by them. Show the latter. |
|
808 |
(magit-diff-unmerged args (list file)) |
|
809 |
(magit-diff-staged nil args files)))) |
|
810 |
(`(commit . ,value) |
|
811 |
(magit-diff-range (format "%s^..%s" value value) args files)) |
|
812 |
(`(stash . ,value) (magit-stash-show value args)) |
|
813 |
((and range (pred stringp)) |
|
814 |
(magit-diff-range range args files)) |
|
815 |
(_ |
|
816 |
(call-interactively #'magit-diff-range)))) |
|
817 |
|
|
818 |
(defun magit-diff--dwim () |
|
819 |
"Return information for performing DWIM diff. |
|
820 |
|
|
821 |
The information can be in three forms: |
|
822 |
1. TYPE |
|
823 |
A symbol describing a type of diff where no additional information |
|
824 |
is needed to generate the diff. Currently, this includes `staged', |
|
825 |
`unstaged' and `unmerged'. |
|
826 |
2. (TYPE . VALUE) |
|
827 |
Like #1 but the diff requires additional information, which is |
|
828 |
given by VALUE. Currently, this includes `commit' and `stash', |
|
829 |
where VALUE is the given commit or stash, respectively. |
|
830 |
3. RANGE |
|
831 |
A string indicating a diff range. |
|
832 |
|
|
833 |
If no DWIM context is found, nil is returned." |
|
834 |
(cond |
|
835 |
((--when-let (magit-region-values '(commit branch) t) |
|
836 |
(deactivate-mark) |
|
837 |
(concat (car (last it)) ".." (car it)))) |
|
838 |
(magit-buffer-refname |
|
839 |
(cons 'commit magit-buffer-refname)) |
|
840 |
((derived-mode-p 'magit-stash-mode) |
|
841 |
(cons 'commit |
|
842 |
(magit-section-case |
|
843 |
(commit (oref it value)) |
|
844 |
(file (-> it |
|
845 |
(oref parent) |
|
846 |
(oref value))) |
|
847 |
(hunk (-> it |
|
848 |
(oref parent) |
|
849 |
(oref parent) |
|
850 |
(oref value)))))) |
|
851 |
((derived-mode-p 'magit-revision-mode) |
|
852 |
(cons 'commit (car magit-refresh-args))) |
|
853 |
((derived-mode-p 'magit-diff-mode) |
|
854 |
(nth 0 magit-refresh-args)) |
|
855 |
(t |
|
856 |
(magit-section-case |
|
857 |
([* unstaged] 'unstaged) |
|
858 |
([* staged] 'staged) |
|
859 |
(unmerged 'unmerged) |
|
860 |
(unpushed (oref it value)) |
|
861 |
(unpulled (oref it value)) |
|
862 |
(branch (let ((current (magit-get-current-branch)) |
|
863 |
(atpoint (oref it value))) |
|
864 |
(if (equal atpoint current) |
|
865 |
(--if-let (magit-get-upstream-branch) |
|
866 |
(format "%s...%s" it current) |
|
867 |
(if (magit-anything-modified-p) |
|
868 |
current |
|
869 |
(cons 'commit current))) |
|
870 |
(format "%s...%s" |
|
871 |
(or current "HEAD") |
|
872 |
atpoint)))) |
|
873 |
(commit (cons 'commit (oref it value))) |
|
874 |
(stash (cons 'stash (oref it value))))))) |
|
875 |
|
|
876 |
(defun magit-diff-read-range-or-commit (prompt &optional secondary-default mbase) |
|
877 |
"Read range or revision with special diff range treatment. |
|
878 |
If MBASE is non-nil, prompt for which rev to place at the end of |
|
879 |
a \"revA...revB\" range. Otherwise, always construct |
|
880 |
\"revA..revB\" range." |
|
881 |
(--if-let (magit-region-values '(commit branch) t) |
|
882 |
(let ((revA (car (last it))) |
|
883 |
(revB (car it))) |
|
884 |
(deactivate-mark) |
|
885 |
(if mbase |
|
886 |
(let ((base (magit-git-string "merge-base" revA revB))) |
|
887 |
(cond |
|
888 |
((string= (magit-rev-parse revA) base) |
|
889 |
(format "%s..%s" revA revB)) |
|
890 |
((string= (magit-rev-parse revB) base) |
|
891 |
(format "%s..%s" revB revA)) |
|
892 |
(t |
|
893 |
(let ((main (magit-completing-read "View changes along" |
|
894 |
(list revA revB) |
|
895 |
nil t nil nil revB))) |
|
896 |
(format "%s...%s" |
|
897 |
(if (string= main revB) revA revB) main))))) |
|
898 |
(format "%s..%s" revA revB))) |
|
899 |
(magit-read-range prompt |
|
900 |
(or (pcase (magit-diff--dwim) |
|
901 |
(`(commit . ,value) |
|
902 |
(format "%s^..%s" value value)) |
|
903 |
((and range (pred stringp)) |
|
904 |
range)) |
|
905 |
secondary-default |
|
906 |
(magit-get-current-branch))))) |
|
907 |
|
|
908 |
(defun magit-diff-setup (rev-or-range const args files) |
|
909 |
(require 'magit) |
|
910 |
(magit-mode-setup #'magit-diff-mode rev-or-range const args files)) |
|
911 |
|
|
912 |
;;;###autoload |
|
913 |
(defun magit-diff-range (rev-or-range &optional args files) |
|
914 |
"Show differences between two commits. |
|
915 |
|
|
916 |
REV-OR-RANGE should be a range or a single revision. If it is a |
|
917 |
revision, then show changes in the working tree relative to that |
|
918 |
revision. If it is a range, but one side is omitted, then show |
|
919 |
changes relative to `HEAD'. |
|
920 |
|
|
921 |
If the region is active, use the revisions on the first and last |
|
922 |
line of the region as the two sides of the range. With a prefix |
|
923 |
argument, instead of diffing the revisions, choose a revision to |
|
924 |
view changes along, starting at the common ancestor of both |
|
925 |
revisions (i.e., use a \"...\" range)." |
|
926 |
(interactive (cons (magit-diff-read-range-or-commit "Diff for range" |
|
927 |
nil current-prefix-arg) |
|
928 |
(magit-diff-arguments))) |
|
929 |
(magit-diff-setup rev-or-range nil args files)) |
|
930 |
|
|
931 |
;;;###autoload |
|
932 |
(defun magit-diff-working-tree (&optional rev args files) |
|
933 |
"Show changes between the current working tree and the `HEAD' commit. |
|
934 |
With a prefix argument show changes between the working tree and |
|
935 |
a commit read from the minibuffer." |
|
936 |
(interactive |
|
937 |
(cons (and current-prefix-arg |
|
938 |
(magit-read-branch-or-commit "Diff working tree and commit")) |
|
939 |
(magit-diff-arguments))) |
|
940 |
(magit-diff-setup (or rev "HEAD") nil args files)) |
|
941 |
|
|
942 |
;;;###autoload |
|
943 |
(defun magit-diff-staged (&optional rev args files) |
|
944 |
"Show changes between the index and the `HEAD' commit. |
|
945 |
With a prefix argument show changes between the index and |
|
946 |
a commit read from the minibuffer." |
|
947 |
(interactive |
|
948 |
(cons (and current-prefix-arg |
|
949 |
(magit-read-branch-or-commit "Diff index and commit")) |
|
950 |
(magit-diff-arguments))) |
|
951 |
(magit-diff-setup rev (list "--cached") args files)) |
|
952 |
|
|
953 |
;;;###autoload |
|
954 |
(defun magit-diff-unstaged (&optional args files) |
|
955 |
"Show changes between the working tree and the index." |
|
956 |
(interactive (magit-diff-arguments)) |
|
957 |
(magit-diff-setup nil nil args files)) |
|
958 |
|
|
959 |
;;;###autoload |
|
960 |
(defun magit-diff-unmerged (&optional args files) |
|
961 |
"Show changes that are being merged." |
|
962 |
(interactive (magit-diff-arguments)) |
|
963 |
(unless (magit-merge-in-progress-p) |
|
964 |
(user-error "No merge is in progress")) |
|
965 |
(magit-diff-setup (magit--merge-range) nil args files)) |
|
966 |
|
|
967 |
;;;###autoload |
|
968 |
(defun magit-diff-while-committing (&optional args) |
|
969 |
"While committing, show the changes that are about to be committed. |
|
970 |
While amending, invoking the command again toggles between |
|
971 |
showing just the new changes or all the changes that will |
|
972 |
be committed." |
|
973 |
(interactive (list (car (magit-diff-arguments)))) |
|
974 |
(unless (magit-commit-message-buffer) |
|
975 |
(user-error "No commit in progress")) |
|
976 |
(let ((magit-display-buffer-noselect t) |
|
977 |
(diff-buf (magit-mode-get-buffer 'magit-diff-mode))) |
|
978 |
(if (and diff-buf |
|
979 |
(get-buffer-window diff-buf)) |
|
980 |
(with-current-buffer diff-buf |
|
981 |
(pcase-let ((`(,rev ,arg . ,_) magit-refresh-args)) |
|
982 |
(cond ((and (equal rev "HEAD^") |
|
983 |
(equal arg '("--cached"))) |
|
984 |
(magit-diff-staged nil args)) |
|
985 |
((and (equal rev nil) |
|
986 |
(equal arg '("--cached"))) |
|
987 |
(magit-diff-while-amending args)) |
|
988 |
((magit-anything-staged-p) |
|
989 |
(magit-diff-staged nil args)) |
|
990 |
(t |
|
991 |
(magit-diff-while-amending args))))) |
|
992 |
(if (magit-anything-staged-p) |
|
993 |
(magit-diff-staged nil args) |
|
994 |
(magit-diff-while-amending args))))) |
|
995 |
|
|
996 |
(define-key git-commit-mode-map |
|
997 |
(kbd "C-c C-d") 'magit-diff-while-committing) |
|
998 |
|
|
999 |
(defun magit-diff-while-amending (&optional args) |
|
1000 |
(magit-diff-setup "HEAD^" (list "--cached") args nil)) |
|
1001 |
|
|
1002 |
;;;###autoload |
|
1003 |
(defun magit-diff-buffer-file () |
|
1004 |
"Show diff for the blob or file visited in the current buffer." |
|
1005 |
(interactive) |
|
1006 |
(require 'magit) |
|
1007 |
(if-let ((file (magit-file-relative-name))) |
|
1008 |
(magit-mode-setup-internal #'magit-diff-mode |
|
1009 |
(list (or magit-buffer-refname |
|
1010 |
(magit-get-current-branch) |
|
1011 |
"HEAD") |
|
1012 |
nil |
|
1013 |
(cadr (magit-diff-arguments)) |
|
1014 |
(list file)) |
|
1015 |
magit-diff-buffer-file-locked) |
|
1016 |
(user-error "Buffer isn't visiting a file"))) |
|
1017 |
|
|
1018 |
;;;###autoload |
|
1019 |
(defun magit-diff-paths (a b) |
|
1020 |
"Show changes between any two files on disk." |
|
1021 |
(interactive (list (read-file-name "First file: " nil nil t) |
|
1022 |
(read-file-name "Second file: " nil nil t))) |
|
1023 |
(magit-diff-setup nil (list "--no-index") |
|
1024 |
nil (list (magit-convert-filename-for-git |
|
1025 |
(expand-file-name a)) |
|
1026 |
(magit-convert-filename-for-git |
|
1027 |
(expand-file-name b))))) |
|
1028 |
|
|
1029 |
(defvar-local magit-buffer-revision-hash nil) |
|
1030 |
|
|
1031 |
(defun magit-show-commit--arguments () |
|
1032 |
(pcase-let ((`(,args ,diff-files) (magit-diff-arguments))) |
|
1033 |
(list args (if (derived-mode-p 'magit-log-mode) |
|
1034 |
(and (not (member "--follow" (nth 1 magit-refresh-args))) |
|
1035 |
(nth 2 magit-refresh-args)) |
|
1036 |
diff-files)))) |
|
1037 |
|
|
1038 |
;;;###autoload |
|
1039 |
(defun magit-show-commit (rev &optional args files module) |
|
1040 |
"Visit the revision at point in another buffer. |
|
1041 |
If there is no revision at point or with a prefix argument prompt |
|
1042 |
for a revision." |
|
1043 |
(interactive |
|
1044 |
(pcase-let* ((mcommit (magit-section-value-if 'module-commit)) |
|
1045 |
(atpoint (or (and (bound-and-true-p magit-blame-mode) |
|
1046 |
(oref (magit-current-blame-chunk) orig-rev)) |
|
1047 |
mcommit |
|
1048 |
(magit-branch-or-commit-at-point))) |
|
1049 |
(`(,args ,files) (magit-show-commit--arguments))) |
|
1050 |
(list (or (and (not current-prefix-arg) atpoint) |
|
1051 |
(magit-read-branch-or-commit "Show commit" atpoint)) |
|
1052 |
args |
|
1053 |
files |
|
1054 |
(and mcommit |
|
1055 |
(magit-section-parent-value (magit-current-section)))))) |
|
1056 |
(require 'magit) |
|
1057 |
(magit-with-toplevel |
|
1058 |
(when module |
|
1059 |
(setq default-directory |
|
1060 |
(expand-file-name (file-name-as-directory module)))) |
|
1061 |
(unless (magit-rev-verify-commit rev) |
|
1062 |
(user-error "%s is not a commit" rev)) |
|
1063 |
(magit-mode-setup #'magit-revision-mode rev nil args files))) |
|
1064 |
|
|
1065 |
;;;; Setting commands |
|
1066 |
|
|
1067 |
(defun magit-diff-refresh (args files) |
|
1068 |
"Set the local diff arguments for the current buffer." |
|
1069 |
(interactive (magit-diff-arguments t)) |
|
1070 |
(cond ((derived-mode-p 'magit-diff-mode) |
|
1071 |
(setcdr (cdr magit-refresh-args) (list args files))) |
|
1072 |
(t |
|
1073 |
(setq-local magit-diff-section-arguments args) |
|
1074 |
(setq-local magit-diff-section-file-args files))) |
|
1075 |
(magit-refresh)) |
|
1076 |
|
|
1077 |
(defun magit-diff-set-default-arguments (args files) |
|
1078 |
"Set the global diff arguments for the current buffer." |
|
1079 |
(interactive (magit-diff-arguments t)) |
|
1080 |
(cond ((derived-mode-p 'magit-diff-mode) |
|
1081 |
(customize-set-variable 'magit-diff-arguments args) |
|
1082 |
(setcdr (cdr magit-refresh-args) (list args files))) |
|
1083 |
(t |
|
1084 |
(customize-set-variable 'magit-diff-section-arguments args) |
|
1085 |
(kill-local-variable 'magit-diff-section-arguments) |
|
1086 |
(kill-local-variable 'magit-diff-section-file-args))) |
|
1087 |
(magit-refresh)) |
|
1088 |
|
|
1089 |
(defun magit-diff-save-default-arguments (args files) |
|
1090 |
"Set and save the global diff arguments for the current buffer." |
|
1091 |
(interactive (magit-diff-arguments t)) |
|
1092 |
(cond ((derived-mode-p 'magit-diff-mode) |
|
1093 |
(customize-save-variable 'magit-diff-arguments args) |
|
1094 |
(setcdr (cdr magit-refresh-args) (list args files))) |
|
1095 |
(t |
|
1096 |
(customize-save-variable 'magit-diff-section-arguments args) |
|
1097 |
(kill-local-variable 'magit-diff-section-arguments) |
|
1098 |
(kill-local-variable 'magit-diff-section-file-args))) |
|
1099 |
(magit-refresh)) |
|
1100 |
|
|
1101 |
(defun magit-diff-switch-range-type () |
|
1102 |
"Convert diff range type. |
|
1103 |
Change \"revA..revB\" to \"revB...revA\", or vice versa." |
|
1104 |
(interactive) |
|
1105 |
(let ((range (car magit-refresh-args))) |
|
1106 |
(if (and range |
|
1107 |
(derived-mode-p 'magit-diff-mode) |
|
1108 |
(string-match magit-range-re range)) |
|
1109 |
(progn |
|
1110 |
(setcar magit-refresh-args |
|
1111 |
(concat (match-string 1 range) |
|
1112 |
(if (string= (match-string 2 range) "..") |
|
1113 |
"..." |
|
1114 |
"..") |
|
1115 |
(match-string 3 range))) |
|
1116 |
(magit-refresh)) |
|
1117 |
(user-error "No range to change")))) |
|
1118 |
|
|
1119 |
(defun magit-diff-flip-revs () |
|
1120 |
"Swap revisions in diff range. |
|
1121 |
Change \"revA..revB\" to \"revB..revA\"." |
|
1122 |
(interactive) |
|
1123 |
(let ((range (car magit-refresh-args))) |
|
1124 |
(if (and range |
|
1125 |
(derived-mode-p 'magit-diff-mode) |
|
1126 |
(string-match magit-range-re range)) |
|
1127 |
(progn |
|
1128 |
(setcar magit-refresh-args |
|
1129 |
(concat (match-string 3 range) |
|
1130 |
(match-string 2 range) |
|
1131 |
(match-string 1 range))) |
|
1132 |
(magit-refresh)) |
|
1133 |
(user-error "No range to swap")))) |
|
1134 |
|
|
1135 |
(defvar-local magit-diff--last-file-args nil) |
|
1136 |
(defun magit-diff--toggle-file-args (files) |
|
1137 |
(cond (files |
|
1138 |
(setq magit-diff--last-file-args files) |
|
1139 |
nil) |
|
1140 |
(magit-diff--last-file-args) |
|
1141 |
(t |
|
1142 |
(user-error "No diff file filter to toggle")))) |
|
1143 |
|
|
1144 |
(defun magit-diff-toggle-file-filter () |
|
1145 |
"Toggle the file restriction of the current buffer's diffs. |
|
1146 |
If the current buffer's mode is derived from `magit-log-mode', |
|
1147 |
toggle the file restriction in the repository's revision buffer |
|
1148 |
instead." |
|
1149 |
(interactive) |
|
1150 |
(--if-let (and (derived-mode-p 'magit-log-mode) |
|
1151 |
(magit-mode-get-buffer 'magit-revision-mode)) |
|
1152 |
(with-current-buffer it |
|
1153 |
(setf (nth 3 magit-refresh-args) |
|
1154 |
(magit-diff--toggle-file-args (nth 3 magit-refresh-args))) |
|
1155 |
(magit-refresh)) |
|
1156 |
(if (derived-mode-p 'magit-diff-mode) |
|
1157 |
(setf (nth 3 magit-refresh-args) |
|
1158 |
(magit-diff--toggle-file-args (nth 3 magit-refresh-args))) |
|
1159 |
(setq-local magit-diff-section-file-args |
|
1160 |
(magit-diff--toggle-file-args magit-diff-section-file-args))) |
|
1161 |
(magit-refresh))) |
|
1162 |
|
|
1163 |
(defun magit-diff-less-context (&optional count) |
|
1164 |
"Decrease the context for diff hunks by COUNT lines." |
|
1165 |
(interactive "p") |
|
1166 |
(magit-diff-set-context `(lambda (cur) (max 0 (- (or cur 0) ,count))))) |
|
1167 |
|
|
1168 |
(defun magit-diff-more-context (&optional count) |
|
1169 |
"Increase the context for diff hunks by COUNT lines." |
|
1170 |
(interactive "p") |
|
1171 |
(magit-diff-set-context `(lambda (cur) (+ (or cur 0) ,count)))) |
|
1172 |
|
|
1173 |
(defun magit-diff-default-context () |
|
1174 |
"Reset context for diff hunks to the default height." |
|
1175 |
(interactive) |
|
1176 |
(magit-diff-set-context #'ignore)) |
|
1177 |
|
|
1178 |
(defun magit-diff-set-context (fn) |
|
1179 |
(let* ((def (--if-let (magit-get "diff.context") (string-to-number it) 3)) |
|
1180 |
(val (car (magit-diff-arguments t))) |
|
1181 |
(arg (--first (string-match "^-U\\([0-9]+\\)?$" it) val)) |
|
1182 |
(num (--if-let (and arg (match-string 1 arg)) (string-to-number it) def)) |
|
1183 |
(val (delete arg val)) |
|
1184 |
(num (funcall fn num)) |
|
1185 |
(arg (and num (not (= num def)) (format "-U%i" num))) |
|
1186 |
(val (if arg (cons arg val) val))) |
|
1187 |
(if (derived-mode-p 'magit-diff-mode) |
|
1188 |
(setcar (cddr magit-refresh-args) val) |
|
1189 |
(setq magit-diff-section-arguments val))) |
|
1190 |
(magit-refresh)) |
|
1191 |
|
|
1192 |
(defun magit-diff-context-p () |
|
1193 |
(--if-let (--first (string-match "^-U\\([0-9]+\\)$" it) |
|
1194 |
(car (magit-diff-arguments t))) |
|
1195 |
(not (equal "-U0" it)) |
|
1196 |
t)) |
|
1197 |
|
|
1198 |
(defun magit-diff-ignore-any-space-p () |
|
1199 |
(let ((args (car (magit-diff-arguments t)))) |
|
1200 |
(--any-p (member it args) |
|
1201 |
'("--ignore-cr-at-eol" |
|
1202 |
"--ignore-space-at-eol" |
|
1203 |
"--ignore-space-change" "-b" |
|
1204 |
"--ignore-all-space" "-w" |
|
1205 |
"--ignore-blank-space")))) |
|
1206 |
|
|
1207 |
(defun magit-diff-toggle-refine-hunk (&optional style) |
|
1208 |
"Turn diff-hunk refining on or off. |
|
1209 |
|
|
1210 |
If hunk refining is currently on, then hunk refining is turned off. |
|
1211 |
If hunk refining is off, then hunk refining is turned on, in |
|
1212 |
`selected' mode (only the currently selected hunk is refined). |
|
1213 |
|
|
1214 |
With a prefix argument, the \"third choice\" is used instead: |
|
1215 |
If hunk refining is currently on, then refining is kept on, but |
|
1216 |
the refining mode (`selected' or `all') is switched. |
|
1217 |
If hunk refining is off, then hunk refining is turned on, in |
|
1218 |
`all' mode (all hunks refined). |
|
1219 |
|
|
1220 |
Customize variable `magit-diff-refine-hunk' to change the default mode." |
|
1221 |
(interactive "P") |
|
1222 |
(setq-local magit-diff-refine-hunk |
|
1223 |
(if style |
|
1224 |
(if (eq magit-diff-refine-hunk 'all) t 'all) |
|
1225 |
(not magit-diff-refine-hunk))) |
|
1226 |
(magit-diff-update-hunk-refinement)) |
|
1227 |
|
|
1228 |
;;;; Visit commands |
|
1229 |
|
|
1230 |
(defun magit-diff-visit-file |
|
1231 |
(file &optional other-window force-worktree display-fn) |
|
1232 |
"From a diff, visit the corresponding file at the appropriate position. |
|
1233 |
|
|
1234 |
If the diff shows changes in the worktree, the index, or `HEAD', |
|
1235 |
then visit the actual file. Otherwise, when the diff is about an |
|
1236 |
older commit or a range, then visit the appropriate blob. |
|
1237 |
|
|
1238 |
If point is on a removed line, then visit the blob for the first |
|
1239 |
parent of the commit which removed that line, i.e. the last |
|
1240 |
commit where that line still existed. Otherwise visit the blob |
|
1241 |
for the commit whose changes are being shown. |
|
1242 |
|
|
1243 |
Interactively, when the file or blob to be displayed is already |
|
1244 |
being displayed in another window of the same frame, then just |
|
1245 |
select that window and adjust point. Otherwise, or with a prefix |
|
1246 |
argument, display the buffer in another window. The meaning of |
|
1247 |
the prefix argument can be inverted or further modified using the |
|
1248 |
option `magit-display-file-buffer-function'. |
|
1249 |
|
|
1250 |
Non-interactively the optional OTHER-WINDOW argument is taken |
|
1251 |
literally. DISPLAY-FN can be used to specify the display |
|
1252 |
function explicitly, in which case OTHER-WINDOW is ignored. |
|
1253 |
|
|
1254 |
The optional FORCE-WORKTREE means to force visiting the worktree |
|
1255 |
version of the file. To do this interactively use the command |
|
1256 |
`magit-diff-visit-file-worktree' instead." |
|
1257 |
(interactive (list (--if-let (magit-file-at-point) |
|
1258 |
(expand-file-name it) |
|
1259 |
(user-error "No file at point")) |
|
1260 |
current-prefix-arg)) |
|
1261 |
(if (magit-file-accessible-directory-p file) |
|
1262 |
(magit-diff-visit-directory file other-window) |
|
1263 |
(let* ((hunk (magit-diff-visit--hunk)) |
|
1264 |
(last (and magit-diff-visit-previous-blob |
|
1265 |
(not force-worktree) |
|
1266 |
(magit-section-match 'hunk) |
|
1267 |
(save-excursion |
|
1268 |
(goto-char (line-beginning-position)) |
|
1269 |
(looking-at "-")))) |
|
1270 |
(line (and hunk (magit-diff-hunk-line hunk))) |
|
1271 |
(col (and hunk (magit-diff-hunk-column hunk last))) |
|
1272 |
(rev (if last |
|
1273 |
(magit-diff-visit--range-beginning) |
|
1274 |
(magit-diff-visit--range-end))) |
|
1275 |
(buf (if (and (not force-worktree) |
|
1276 |
(stringp rev)) |
|
1277 |
(magit-find-file-noselect rev file) |
|
1278 |
(or (get-file-buffer file) |
|
1279 |
(find-file-noselect file))))) |
|
1280 |
(cond ((called-interactively-p 'any) |
|
1281 |
(magit-display-file-buffer buf)) |
|
1282 |
(display-fn |
|
1283 |
(funcall display-fn buf)) |
|
1284 |
((or other-window (get-buffer-window buf)) |
|
1285 |
(switch-to-buffer-other-window buf)) |
|
1286 |
(t |
|
1287 |
(pop-to-buffer buf))) |
|
1288 |
(with-selected-window |
|
1289 |
(or (get-buffer-window buf 'visible) |
|
1290 |
(error "File buffer is not visible")) |
|
1291 |
(when line |
|
1292 |
(setq line |
|
1293 |
(cond ((eq rev 'staged) |
|
1294 |
(apply 'magit-diff-visit--offset file nil line)) |
|
1295 |
((and force-worktree |
|
1296 |
(stringp rev)) |
|
1297 |
(apply 'magit-diff-visit--offset file rev line)) |
|
1298 |
(t |
|
1299 |
(apply '+ line)))) |
|
1300 |
(let ((pos (save-restriction |
|
1301 |
(widen) |
|
1302 |
(goto-char (point-min)) |
|
1303 |
(forward-line (1- line)) |
|
1304 |
(move-to-column col) |
|
1305 |
(point)))) |
|
1306 |
(unless (<= (point-min) pos (point-max)) |
|
1307 |
(widen) |
|
1308 |
(goto-char pos)))) |
|
1309 |
(when (magit-anything-unmerged-p file) |
|
1310 |
(smerge-start-session)) |
|
1311 |
(run-hooks 'magit-diff-visit-file-hook))))) |
|
1312 |
|
|
1313 |
(defun magit-diff-visit-file-other-window (file) |
|
1314 |
"From a diff, visit the corresponding file at the appropriate position. |
|
1315 |
The file is shown in another window. |
|
1316 |
|
|
1317 |
If the diff shows changes in the worktree, the index, or `HEAD', |
|
1318 |
then visit the actual file. Otherwise, when the diff is about an |
|
1319 |
older commit or a range, then visit the appropriate blob. |
|
1320 |
|
|
1321 |
If point is on a removed line, then visit the blob for the first |
|
1322 |
parent of the commit which removed that line, i.e. the last |
|
1323 |
commit where that line still existed. Otherwise visit the blob |
|
1324 |
for the commit whose changes are being shown." |
|
1325 |
(interactive (list (--if-let (magit-file-at-point) |
|
1326 |
(expand-file-name it) |
|
1327 |
(user-error "No file at point")))) |
|
1328 |
(magit-diff-visit-file file t)) |
|
1329 |
|
|
1330 |
(defvar magit-display-file-buffer-function |
|
1331 |
'magit-display-file-buffer-traditional |
|
1332 |
"The function used by `magit-diff-visit-file' to display blob buffers. |
|
1333 |
|
|
1334 |
Other commands such as `magit-find-file' do not use this |
|
1335 |
function. Instead they use high-level functions to select the |
|
1336 |
window to be used to display the buffer. This variable and the |
|
1337 |
related functions are an experimental feature and should be |
|
1338 |
treated as such.") |
|
1339 |
|
|
1340 |
(defun magit-display-file-buffer (buffer) |
|
1341 |
(funcall magit-display-file-buffer-function buffer)) |
|
1342 |
|
|
1343 |
(defun magit-display-file-buffer-traditional (buffer) |
|
1344 |
"Display BUFFER in the current window. |
|
1345 |
With a prefix argument display it in another window. |
|
1346 |
Option `magit-display-file-buffer-function' controls |
|
1347 |
whether `magit-diff-visit-file' uses this function." |
|
1348 |
(if (or current-prefix-arg (get-buffer-window buffer)) |
|
1349 |
(pop-to-buffer buffer) |
|
1350 |
(switch-to-buffer buffer))) |
|
1351 |
|
|
1352 |
(defun magit-display-file-buffer-other-window (buffer) |
|
1353 |
"Display BUFFER in another window. |
|
1354 |
With a prefix argument display it in the current window. |
|
1355 |
Option `magit-display-file-buffer-function' controls |
|
1356 |
whether `magit-diff-visit-file' uses this function." |
|
1357 |
(if current-prefix-arg |
|
1358 |
(switch-to-buffer buffer) |
|
1359 |
(pop-to-buffer buffer))) |
|
1360 |
|
|
1361 |
(defun magit-diff-visit-file-worktree (file &optional other-window) |
|
1362 |
"From a diff, visit the corresponding file at the appropriate position. |
|
1363 |
|
|
1364 |
When the file is already being displayed in another window of the |
|
1365 |
same frame, then just select that window and adjust point. With |
|
1366 |
a prefix argument also display in another window. |
|
1367 |
|
|
1368 |
The actual file in the worktree is visited. The positions in the |
|
1369 |
hunk headers get less useful the \"older\" the changes are, and |
|
1370 |
as a result, jumping to the appropriate position gets less |
|
1371 |
reliable. |
|
1372 |
|
|
1373 |
Also see `magit-diff-visit-file' which visits the respective |
|
1374 |
blob, unless the diff shows changes in the worktree, the index, |
|
1375 |
or `HEAD'." |
|
1376 |
(interactive (list (or (magit-file-at-point) |
|
1377 |
(user-error "No file at point")) |
|
1378 |
current-prefix-arg)) |
|
1379 |
(magit-diff-visit-file file other-window t)) |
|
1380 |
|
|
1381 |
(defun magit-diff-visit--range-end () |
|
1382 |
(let ((rev (magit-diff--dwim))) |
|
1383 |
(if (symbolp rev) |
|
1384 |
rev |
|
1385 |
(setq rev (if (consp rev) |
|
1386 |
(cdr rev) |
|
1387 |
(cdr (magit-split-range rev)))) |
|
1388 |
(if (magit-rev-head-p rev) |
|
1389 |
'unstaged |
|
1390 |
rev)))) |
|
1391 |
|
|
1392 |
(defun magit-diff-visit--range-beginning () |
|
1393 |
(let ((rev (magit-diff--dwim))) |
|
1394 |
(cond ((consp rev) |
|
1395 |
(concat (cdr rev) "^")) |
|
1396 |
((stringp rev) |
|
1397 |
(car (magit-split-range rev))) |
|
1398 |
(t |
|
1399 |
rev)))) |
|
1400 |
|
|
1401 |
(defun magit-diff-visit--hunk () |
|
1402 |
(when-let ((scope (magit-diff-scope))) |
|
1403 |
(let ((section (magit-current-section))) |
|
1404 |
(cl-case scope |
|
1405 |
((file files) |
|
1406 |
(setq section (car (oref section children)))) |
|
1407 |
(list |
|
1408 |
(setq section (car (oref section children))) |
|
1409 |
(when section |
|
1410 |
(setq section (car (oref section children)))))) |
|
1411 |
(and |
|
1412 |
;; Unmerged files appear in the list of staged changes |
|
1413 |
;; but unlike in the list of unstaged changes no diffs |
|
1414 |
;; are shown here. In that case `section' is nil. |
|
1415 |
section |
|
1416 |
;; Currently the `hunk' type is also abused for file |
|
1417 |
;; mode changes, which we are not interested in here. |
|
1418 |
;; Such sections have no value. |
|
1419 |
(oref section value) |
|
1420 |
section)))) |
|
1421 |
|
|
1422 |
(defun magit-diff-visit--offset (file rev hunk-start line-offset) |
|
1423 |
(let ((offset 0)) |
|
1424 |
(with-temp-buffer |
|
1425 |
(save-excursion |
|
1426 |
(magit-with-toplevel |
|
1427 |
(magit-git-insert "diff" rev "--" file))) |
|
1428 |
(catch 'found |
|
1429 |
(while (re-search-forward |
|
1430 |
"^@@ -\\([0-9]+\\),\\([0-9]+\\) \\+\\([0-9]+\\),\\([0-9]+\\) @@" |
|
1431 |
nil t) |
|
1432 |
(let* ((abeg (string-to-number (match-string 1))) |
|
1433 |
(alen (string-to-number (match-string 2))) |
|
1434 |
(bbeg (string-to-number (match-string 3))) |
|
1435 |
(blen (string-to-number (match-string 4))) |
|
1436 |
(aend (+ abeg alen)) |
|
1437 |
(bend (+ bbeg blen)) |
|
1438 |
(hend (+ hunk-start line-offset))) |
|
1439 |
(if (<= abeg hunk-start) |
|
1440 |
(if (or (>= aend hend) |
|
1441 |
(>= bend hend)) |
|
1442 |
(let ((line 0)) |
|
1443 |
(while (<= line alen) |
|
1444 |
(forward-line 1) |
|
1445 |
(cl-incf line) |
|
1446 |
(cond ((looking-at "^\\+") (cl-incf offset)) |
|
1447 |
((looking-at "^-") (cl-decf offset))))) |
|
1448 |
(cl-incf offset (- blen alen))) |
|
1449 |
(throw 'found nil)))))) |
|
1450 |
(+ hunk-start line-offset offset))) |
|
1451 |
|
|
1452 |
(defun magit-diff-hunk-line (section) |
|
1453 |
(let* ((value (oref section value)) |
|
1454 |
(prefix (- (length value) 2)) |
|
1455 |
(cpos (marker-position (oref section content))) |
|
1456 |
(stop (line-number-at-pos)) |
|
1457 |
(cstart (save-excursion (goto-char cpos) |
|
1458 |
(line-number-at-pos))) |
|
1459 |
(prior (and (= (length value) 3) |
|
1460 |
(save-excursion (goto-char (line-beginning-position)) |
|
1461 |
(looking-at "-")))) |
|
1462 |
(offset 0) |
|
1463 |
(line (if prior |
|
1464 |
(cadr value) |
|
1465 |
(car (last value))))) |
|
1466 |
(string-match (format "^%s\\([0-9]+\\)" (if prior "-" "\\+")) line) |
|
1467 |
(setq line (string-to-number (match-string 1 line))) |
|
1468 |
(when (> cstart stop) |
|
1469 |
(save-excursion |
|
1470 |
(goto-char cpos) |
|
1471 |
(re-search-forward "^[-+]") |
|
1472 |
(setq stop (line-number-at-pos)))) |
|
1473 |
(save-excursion |
|
1474 |
(goto-char cpos) |
|
1475 |
(while (< (line-number-at-pos) stop) |
|
1476 |
(unless (string-match-p |
|
1477 |
(if prior "\\+" "-") |
|
1478 |
(buffer-substring (point) (+ (point) prefix))) |
|
1479 |
(cl-incf offset)) |
|
1480 |
(forward-line))) |
|
1481 |
(list line offset))) |
|
1482 |
|
|
1483 |
(defun magit-diff-hunk-column (section visit-beginning) |
|
1484 |
(if (or (< (point) |
|
1485 |
(oref section content)) |
|
1486 |
(and (not visit-beginning) |
|
1487 |
(save-excursion (beginning-of-line) (looking-at-p "-")))) |
|
1488 |
0 |
|
1489 |
(max 0 (- (+ (current-column) 2) |
|
1490 |
(length (oref section value)))))) |
|
1491 |
|
|
1492 |
(defun magit-diff-visit-directory (directory &optional other-window) |
|
1493 |
(if (equal (magit-toplevel directory) |
|
1494 |
(magit-toplevel)) |
|
1495 |
(dired-jump other-window (concat directory "/.")) |
|
1496 |
(let ((display-buffer-overriding-action |
|
1497 |
(if other-window |
|
1498 |
'(nil (inhibit-same-window t)) |
|
1499 |
'(display-buffer-same-window)))) |
|
1500 |
(magit-status-internal directory)))) |
|
1501 |
|
|
1502 |
;;;; Scroll commands |
|
1503 |
|
|
1504 |
(defun magit-diff-show-or-scroll-up () |
|
1505 |
"Update the commit or diff buffer for the thing at point. |
|
1506 |
|
|
1507 |
Either show the commit or stash at point in the appropriate |
|
1508 |
buffer, or if that buffer is already being displayed in the |
|
1509 |
current frame and contains information about that commit or |
|
1510 |
stash, then instead scroll the buffer up. If there is no |
|
1511 |
commit or stash at point, then prompt for a commit." |
|
1512 |
(interactive) |
|
1513 |
(magit-diff-show-or-scroll 'scroll-up)) |
|
1514 |
|
|
1515 |
(defun magit-diff-show-or-scroll-down () |
|
1516 |
"Update the commit or diff buffer for the thing at point. |
|
1517 |
|
|
1518 |
Either show the commit or stash at point in the appropriate |
|
1519 |
buffer, or if that buffer is already being displayed in the |
|
1520 |
current frame and contains information about that commit or |
|
1521 |
stash, then instead scroll the buffer down. If there is no |
|
1522 |
commit or stash at point, then prompt for a commit." |
|
1523 |
(interactive) |
|
1524 |
(magit-diff-show-or-scroll 'scroll-down)) |
|
1525 |
|
|
1526 |
(defun magit-diff-show-or-scroll (fn) |
|
1527 |
(let (rev cmd buf win) |
|
1528 |
(cond |
|
1529 |
(magit-blame-mode |
|
1530 |
(setq rev (oref (magit-current-blame-chunk) orig-rev)) |
|
1531 |
(setq cmd 'magit-show-commit) |
|
1532 |
(setq buf (magit-mode-get-buffer 'magit-revision-mode))) |
|
1533 |
((derived-mode-p 'git-rebase-mode) |
|
1534 |
(save-excursion |
|
1535 |
(goto-char (line-beginning-position)) |
|
1536 |
(--if-let (and git-rebase-line |
|
1537 |
(looking-at git-rebase-line) |
|
1538 |
(match-string 2)) |
|
1539 |
(progn (setq rev it) |
|
1540 |
(setq cmd 'magit-show-commit) |
|
1541 |
(setq buf (magit-mode-get-buffer 'magit-revision-mode))) |
|
1542 |
(user-error "No commit on this line")))) |
|
1543 |
(t |
|
1544 |
(magit-section-case |
|
1545 |
(branch |
|
1546 |
(setq rev (magit-ref-maybe-qualify (oref it value))) |
|
1547 |
(setq cmd 'magit-show-commit) |
|
1548 |
(setq buf (magit-mode-get-buffer 'magit-revision-mode))) |
|
1549 |
(commit |
|
1550 |
(setq rev (oref it value)) |
|
1551 |
(setq cmd 'magit-show-commit) |
|
1552 |
(setq buf (magit-mode-get-buffer 'magit-revision-mode))) |
|
1553 |
(stash |
|
1554 |
(setq rev (oref it value)) |
|
1555 |
(setq cmd 'magit-stash-show) |
|
1556 |
(setq buf (magit-mode-get-buffer 'magit-stash-mode)))))) |
|
1557 |
(if rev |
|
1558 |
(if (and buf |
|
1559 |
(setq win (get-buffer-window buf)) |
|
1560 |
(with-current-buffer buf |
|
1561 |
(and (equal rev (car magit-refresh-args)) |
|
1562 |
(equal (magit-rev-parse rev) |
|
1563 |
magit-buffer-revision-hash)))) |
|
1564 |
(with-selected-window win |
|
1565 |
(condition-case nil |
|
1566 |
(funcall fn) |
|
1567 |
(error |
|
1568 |
(goto-char (pcase fn |
|
1569 |
(`scroll-up (point-min)) |
|
1570 |
(`scroll-down (point-max))))))) |
|
1571 |
(let ((magit-display-buffer-noselect t)) |
|
1572 |
(if (eq cmd 'magit-show-commit) |
|
1573 |
(apply #'magit-show-commit rev (magit-show-commit--arguments)) |
|
1574 |
(funcall cmd rev)))) |
|
1575 |
(call-interactively #'magit-show-commit)))) |
|
1576 |
|
|
1577 |
;;; Diff Mode |
|
1578 |
|
|
1579 |
(defvar magit-diff-mode-map |
|
1580 |
(let ((map (make-sparse-keymap))) |
|
1581 |
(set-keymap-parent map magit-mode-map) |
|
1582 |
(define-key map "\C-c\C-d" 'magit-diff-while-committing) |
|
1583 |
(define-key map "\C-c\C-b" 'magit-go-backward) |
|
1584 |
(define-key map "\C-c\C-f" 'magit-go-forward) |
|
1585 |
(define-key map "\s" 'scroll-up) |
|
1586 |
(define-key map "\d" 'scroll-down) |
|
1587 |
(define-key map "j" 'magit-jump-to-diffstat-or-diff) |
|
1588 |
(define-key map [remap write-file] 'magit-patch-save) |
|
1589 |
map) |
|
1590 |
"Keymap for `magit-diff-mode'.") |
|
1591 |
|
|
1592 |
(define-derived-mode magit-diff-mode magit-mode "Magit Diff" |
|
1593 |
"Mode for looking at a Git diff. |
|
1594 |
|
|
1595 |
This mode is documented in info node `(magit)Diff Buffer'. |
|
1596 |
|
|
1597 |
\\<magit-mode-map>\ |
|
1598 |
Type \\[magit-refresh] to refresh the current buffer. |
|
1599 |
Type \\[magit-section-toggle] to expand or hide the section at point. |
|
1600 |
Type \\[magit-visit-thing] to visit the hunk or file at point. |
|
1601 |
|
|
1602 |
Staging and applying changes is documented in info node |
|
1603 |
`(magit)Staging and Unstaging' and info node `(magit)Applying'. |
|
1604 |
|
|
1605 |
\\<magit-hunk-section-map>Type \ |
|
1606 |
\\[magit-apply] to apply the change at point, \ |
|
1607 |
\\[magit-stage] to stage, |
|
1608 |
\\[magit-unstage] to unstage, \ |
|
1609 |
\\[magit-discard] to discard, or \ |
|
1610 |
\\[magit-reverse] to reverse it. |
|
1611 |
|
|
1612 |
\\{magit-diff-mode-map}" |
|
1613 |
:group 'magit-diff |
|
1614 |
(hack-dir-local-variables-non-file-buffer) |
|
1615 |
(setq imenu-prev-index-position-function |
|
1616 |
'magit-imenu--diff-prev-index-position-function) |
|
1617 |
(setq imenu-extract-index-name-function |
|
1618 |
'magit-imenu--diff-extract-index-name-function) |
|
1619 |
(setq-local bookmark-make-record-function |
|
1620 |
'magit-bookmark--diff-make-record)) |
|
1621 |
|
|
1622 |
(defun magit-diff-refresh-buffer (rev-or-range const _args files) |
|
1623 |
"Refresh the current `magit-diff-mode' buffer. |
|
1624 |
|
|
1625 |
In such buffers the buffer-local value of `magit-refresh-args' |
|
1626 |
has the same form as the arguments of this function. The value |
|
1627 |
is set in `magit-mode-setup'." |
|
1628 |
(magit-set-header-line-format |
|
1629 |
(if (member "--no-index" const) |
|
1630 |
(apply #'format "Differences between %s and %s" files) |
|
1631 |
(concat (if rev-or-range |
|
1632 |
(if (string-match-p "\\(\\.\\.\\|\\^-\\)" |
|
1633 |
rev-or-range) |
|
1634 |
(format "Changes in %s" rev-or-range) |
|
1635 |
(format "Changes from %s to working tree" rev-or-range)) |
|
1636 |
(if (member "--cached" const) |
|
1637 |
"Staged changes" |
|
1638 |
"Unstaged changes")) |
|
1639 |
(pcase (length files) |
|
1640 |
(0) |
|
1641 |
(1 (concat " in file " (car files))) |
|
1642 |
(_ (concat " in files " |
|
1643 |
(mapconcat #'identity files ", "))))))) |
|
1644 |
(magit-insert-section (diffbuf) |
|
1645 |
(magit-run-section-hook 'magit-diff-sections-hook rev-or-range))) |
|
1646 |
|
|
1647 |
(defun magit-insert-diff (rev-or-range) |
|
1648 |
"Insert the diff into this `magit-diff-mode' buffer." |
|
1649 |
(let ((magit-git-global-arguments |
|
1650 |
(remove "--literal-pathspecs" magit-git-global-arguments))) |
|
1651 |
(magit-git-wash #'magit-diff-wash-diffs |
|
1652 |
"diff" rev-or-range "-p" "--no-prefix" |
|
1653 |
(and (member "--stat" (nth 2 magit-refresh-args)) "--numstat") |
|
1654 |
(nth 1 magit-refresh-args) |
|
1655 |
(nth 2 magit-refresh-args) "--" |
|
1656 |
(nth 3 magit-refresh-args)))) |
|
1657 |
|
|
1658 |
(defvar magit-file-section-map |
|
1659 |
(let ((map (make-sparse-keymap))) |
|
1660 |
(unless (featurep 'jkl) |
|
1661 |
(define-key map (kbd "C-j") 'magit-diff-visit-file-worktree)) |
|
1662 |
(define-key map [C-return] 'magit-diff-visit-file-worktree) |
|
1663 |
(define-key map [remap magit-visit-thing] 'magit-diff-visit-file) |
|
1664 |
(define-key map [remap magit-delete-thing] 'magit-discard) |
|
1665 |
(define-key map [remap magit-revert-no-commit] 'magit-reverse) |
|
1666 |
(define-key map "a" 'magit-apply) |
|
1667 |
(define-key map "C" 'magit-commit-add-log) |
|
1668 |
(define-key map "s" 'magit-stage) |
|
1669 |
(define-key map "u" 'magit-unstage) |
|
1670 |
(define-key map "&" 'magit-do-async-shell-command) |
|
1671 |
(define-key map "\C-c\C-t" 'magit-diff-trace-definition) |
|
1672 |
(define-key map "\C-c\C-e" 'magit-diff-edit-hunk-commit) |
|
1673 |
map) |
|
1674 |
"Keymap for `file' sections.") |
|
1675 |
|
|
1676 |
(defvar magit-hunk-section-map |
|
1677 |
(let ((map (make-sparse-keymap))) |
|
1678 |
(unless (featurep 'jkl) |
|
1679 |
(define-key map (kbd "C-j") 'magit-diff-visit-file-worktree)) |
|
1680 |
(define-key map [C-return] 'magit-diff-visit-file-worktree) |
|
1681 |
(define-key map [remap magit-visit-thing] 'magit-diff-visit-file) |
|
1682 |
(define-key map [remap magit-delete-thing] 'magit-discard) |
|
1683 |
(define-key map [remap magit-revert-no-commit] 'magit-reverse) |
|
1684 |
(define-key map "a" 'magit-apply) |
|
1685 |
(define-key map "C" 'magit-commit-add-log) |
|
1686 |
(define-key map "s" 'magit-stage) |
|
1687 |
(define-key map "u" 'magit-unstage) |
|
1688 |
(define-key map "&" 'magit-do-async-shell-command) |
|
1689 |
(define-key map "\C-c\C-t" 'magit-diff-trace-definition) |
|
1690 |
(define-key map "\C-c\C-e" 'magit-diff-edit-hunk-commit) |
|
1691 |
map) |
|
1692 |
"Keymap for `hunk' sections.") |
|
1693 |
|
|
1694 |
(defconst magit-diff-headline-re |
|
1695 |
(concat "^\\(@@@?\\|diff\\|Submodule\\|" |
|
1696 |
"\\* Unmerged path\\|merged\\|changed in both\\|" |
|
1697 |
"added in remote\\|removed in remote\\)")) |
|
1698 |
|
|
1699 |
(defconst magit-diff-statline-re |
|
1700 |
(concat "^ ?" |
|
1701 |
"\\(.*\\)" ; file |
|
1702 |
"\\( +| +\\)" ; separator |
|
1703 |
"\\([0-9]+\\|Bin\\(?: +[0-9]+ -> [0-9]+ bytes\\)?$\\) ?" |
|
1704 |
"\\(\\+*\\)" ; add |
|
1705 |
"\\(-*\\)$")) ; del |
|
1706 |
|
|
1707 |
(defun magit-diff-wash-diffs (args &optional limit) |
|
1708 |
(when (member "--stat" args) |
|
1709 |
(magit-diff-wash-diffstat)) |
|
1710 |
(when (re-search-forward magit-diff-headline-re limit t) |
|
1711 |
(goto-char (line-beginning-position)) |
|
1712 |
(magit-wash-sequence (apply-partially 'magit-diff-wash-diff args)) |
|
1713 |
(insert ?\n))) |
|
1714 |
|
|
1715 |
(defun magit-jump-to-diffstat-or-diff () |
|
1716 |
"Jump to the diffstat or diff. |
|
1717 |
When point is on a file inside the diffstat section, then jump |
|
1718 |
to the respective diff section, otherwise jump to the diffstat |
|
1719 |
section or a child thereof." |
|
1720 |
(interactive) |
|
1721 |
(--if-let (magit-get-section |
|
1722 |
(append (magit-section-case |
|
1723 |
([file diffstat] `((file . ,(oref it value)))) |
|
1724 |
(file `((file . ,(oref it value)) (diffstat))) |
|
1725 |
(t '((diffstat)))) |
|
1726 |
(magit-section-ident magit-root-section))) |
|
1727 |
(magit-section-goto it) |
|
1728 |
(user-error "No diffstat in this buffer"))) |
|
1729 |
|
|
1730 |
(defun magit-diff-wash-diffstat () |
|
1731 |
(let (heading (beg (point))) |
|
1732 |
(when (re-search-forward "^ ?\\([0-9]+ +files? change[^\n]*\n\\)" nil t) |
|
1733 |
(setq heading (match-string 1)) |
|
1734 |
(magit-delete-match) |
|
1735 |
(goto-char beg) |
|
1736 |
(magit-insert-section (diffstat) |
|
1737 |
(insert (propertize heading 'face 'magit-diff-file-heading)) |
|
1738 |
(magit-insert-heading) |
|
1739 |
(let (files) |
|
1740 |
(while (looking-at "^[-0-9]+\t[-0-9]+\t\\(.+\\)$") |
|
1741 |
(push (magit-decode-git-path |
|
1742 |
(let ((f (match-string 1))) |
|
1743 |
(if (string-match " => " f) |
|
1744 |
(substring f (match-end 0)) |
|
1745 |
f))) |
|
1746 |
files) |
|
1747 |
(magit-delete-line)) |
|
1748 |
(setq files (nreverse files)) |
|
1749 |
(while (looking-at magit-diff-statline-re) |
|
1750 |
(magit-bind-match-strings (file sep cnt add del) nil |
|
1751 |
(magit-delete-line) |
|
1752 |
(when (string-match " +$" file) |
|
1753 |
(setq sep (concat (match-string 0 file) sep)) |
|
1754 |
(setq file (substring file 0 (match-beginning 0)))) |
|
1755 |
(let ((le (length file)) ld) |
|
1756 |
(setq file (magit-decode-git-path file)) |
|
1757 |
(setq ld (length file)) |
|
1758 |
(when (> le ld) |
|
1759 |
(setq sep (concat (make-string (- le ld) ?\s) sep)))) |
|
1760 |
(magit-insert-section (file (pop files)) |
|
1761 |
(insert (propertize file 'face 'magit-filename) sep cnt " ") |
|
1762 |
(when add |
|
1763 |
(insert (propertize add 'face 'magit-diffstat-added))) |
|
1764 |
(when del |
|
1765 |
(insert (propertize del 'face 'magit-diffstat-removed))) |
|
1766 |
(insert "\n"))))) |
|
1767 |
(if (looking-at "^$") (forward-line) (insert "\n")))))) |
|
1768 |
|
|
1769 |
(defun magit-diff-wash-diff (args) |
|
1770 |
(cond |
|
1771 |
((looking-at "^Submodule") |
|
1772 |
(magit-diff-wash-submodule)) |
|
1773 |
((looking-at "^\\* Unmerged path \\(.*\\)") |
|
1774 |
(let ((file (magit-decode-git-path (match-string 1)))) |
|
1775 |
(magit-delete-line) |
|
1776 |
(unless (and (derived-mode-p 'magit-status-mode) |
|
1777 |
(not (member "--cached" args))) |
|
1778 |
(magit-insert-section (file file) |
|
1779 |
(insert (propertize |
|
1780 |
(format "unmerged %s%s" file |
|
1781 |
(pcase (cddr (car (magit-file-status file))) |
|
1782 |
(`(?D ?D) " (both deleted)") |
|
1783 |
(`(?D ?U) " (deleted by us)") |
|
1784 |
(`(?U ?D) " (deleted by them)") |
|
1785 |
(`(?A ?A) " (both added)") |
|
1786 |
(`(?A ?U) " (added by us)") |
|
1787 |
(`(?U ?A) " (added by them)") |
|
1788 |
(`(?U ?U) ""))) |
|
1789 |
'face 'magit-diff-file-heading)) |
|
1790 |
(insert ?\n)))) |
|
1791 |
t) |
|
1792 |
((looking-at (concat "^\\(merged\\|changed in both\\|" |
|
1793 |
"added in remote\\|removed in remote\\)")) |
|
1794 |
(let ((status (pcase (match-string 1) |
|
1795 |
("merged" "merged") |
|
1796 |
("changed in both" "conflict") |
|
1797 |
("added in remote" "new file") |
|
1798 |
("removed in remote" "deleted"))) |
|
1799 |
file orig base modes) |
|
1800 |
(magit-delete-line) |
|
1801 |
(while (looking-at |
|
1802 |
"^ \\([^ ]+\\) +[0-9]\\{6\\} \\([a-z0-9]\\{40\\}\\) \\(.+\\)$") |
|
1803 |
(magit-bind-match-strings (side _blob name) nil |
|
1804 |
(pcase side |
|
1805 |
("result" (setq file name)) |
|
1806 |
("our" (setq orig name)) |
|
1807 |
("their" (setq file name)) |
|
1808 |
("base" (setq base name)))) |
|
1809 |
(magit-delete-line)) |
|
1810 |
(when orig (setq orig (magit-decode-git-path orig))) |
|
1811 |
(when file (setq file (magit-decode-git-path file))) |
|
1812 |
(magit-diff-insert-file-section (or file base) orig status modes nil))) |
|
1813 |
((looking-at |
|
1814 |
"^diff --\\(?:\\(git\\) \\(?:\\(.+?\\) \\2\\)?\\|\\(cc\\|combined\\) \\(.+\\)\\)") |
|
1815 |
(let ((status (cond ((equal (match-string 1) "git") "modified") |
|
1816 |
((derived-mode-p 'magit-revision-mode) "resolved") |
|
1817 |
(t "unmerged"))) |
|
1818 |
(file (or (match-string 2) (match-string 4))) |
|
1819 |
(beg (point)) |
|
1820 |
orig header modes) |
|
1821 |
(save-excursion |
|
1822 |
(forward-line 1) |
|
1823 |
(setq header (buffer-substring |
|
1824 |
beg (if (re-search-forward magit-diff-headline-re nil t) |
|
1825 |
(match-beginning 0) |
|
1826 |
(point-max))))) |
|
1827 |
(magit-delete-line) |
|
1828 |
(while (not (or (eobp) (looking-at magit-diff-headline-re))) |
|
1829 |
(if (looking-at "^old mode \\([^\n]+\\)\nnew mode \\([^\n]+\\)\n") |
|
1830 |
(progn (setq modes (match-string 0)) |
|
1831 |
(magit-delete-match)) |
|
1832 |
(cond |
|
1833 |
((looking-at "^--- \\([^/].*?\\)\t?$") ; i.e. not /dev/null |
|
1834 |
(setq orig (match-string 1))) |
|
1835 |
((looking-at "^\\+\\+\\+ \\([^/].*?\\)\t?$") |
|
1836 |
(setq file (match-string 1))) |
|
1837 |
((looking-at "^\\(copy\\|rename\\) from \\(.+\\)$") |
|
1838 |
(setq orig (match-string 2))) |
|
1839 |
((looking-at "^\\(copy\\|rename\\) to \\(.+\\)$") |
|
1840 |
(setq file (match-string 2)) |
|
1841 |
(setq status (if (equal (match-string 1) "copy") "new file" "renamed"))) |
|
1842 |
((looking-at "^\\(new file\\|deleted\\)") |
|
1843 |
(setq status (match-string 1)))) |
|
1844 |
(magit-delete-line))) |
|
1845 |
(when orig |
|
1846 |
(setq orig (magit-decode-git-path orig))) |
|
1847 |
(setq file (magit-decode-git-path file)) |
|
1848 |
;; KLUDGE `git-log' ignores `--no-prefix' when `-L' is used. |
|
1849 |
(when (and (derived-mode-p 'magit-log-mode) |
|
1850 |
(--first (string-match-p "\\`-L" it) |
|
1851 |
(nth 1 magit-refresh-args))) |
|
1852 |
(setq file (substring file 2)) |
|
1853 |
(when orig |
|
1854 |
(setq orig (substring orig 2)))) |
|
1855 |
(magit-diff-insert-file-section file orig status modes header))))) |
|
1856 |
|
|
1857 |
(defun magit-diff-insert-file-section (file orig status modes header) |
|
1858 |
(magit-insert-section section |
|
1859 |
(file file (or (equal status "deleted") |
|
1860 |
(derived-mode-p 'magit-status-mode))) |
|
1861 |
(insert (propertize (format "%-10s %s\n" status |
|
1862 |
(if (or (not orig) (equal orig file)) |
|
1863 |
file |
|
1864 |
(format "%s -> %s" orig file))) |
|
1865 |
'face 'magit-diff-file-heading)) |
|
1866 |
(magit-insert-heading) |
|
1867 |
(unless (equal orig file) |
|
1868 |
(oset section source orig)) |
|
1869 |
(oset section header header) |
|
1870 |
(when modes |
|
1871 |
(magit-insert-section (hunk) |
|
1872 |
(insert modes))) |
|
1873 |
(magit-wash-sequence #'magit-diff-wash-hunk))) |
|
1874 |
|
|
1875 |
(defun magit-diff-wash-submodule () |
|
1876 |
;; See `show_submodule_summary' in submodule.c and "this" commit. |
|
1877 |
(when (looking-at "^Submodule \\([^ ]+\\)") |
|
1878 |
(let ((module (match-string 1)) |
|
1879 |
untracked modified) |
|
1880 |
(when (looking-at "^Submodule [^ ]+ contains untracked content$") |
|
1881 |
(magit-delete-line) |
|
1882 |
(setq untracked t)) |
|
1883 |
(when (looking-at "^Submodule [^ ]+ contains modified content$") |
|
1884 |
(magit-delete-line) |
|
1885 |
(setq modified t)) |
|
1886 |
(cond |
|
1887 |
((and (looking-at "^Submodule \\([^ ]+\\) \\([^ :]+\\)\\( (rewind)\\)?:$") |
|
1888 |
(equal (match-string 1) module)) |
|
1889 |
(magit-bind-match-strings (_module range rewind) nil |
|
1890 |
(magit-delete-line) |
|
1891 |
(while (looking-at "^ \\([<>]\\) \\(.+\\)$") |
|
1892 |
(magit-delete-line)) |
|
1893 |
(when rewind |
|
1894 |
(setq range (replace-regexp-in-string "[^.]\\(\\.\\.\\)[^.]" |
|
1895 |
"..." range t t 1))) |
|
1896 |
(magit-insert-section (magit-module-section module t) |
|
1897 |
(magit-insert-heading |
|
1898 |
(propertize (concat "modified " module) |
|
1899 |
'face 'magit-diff-file-heading) |
|
1900 |
" (" |
|
1901 |
(cond (rewind "rewind") |
|
1902 |
((string-match-p "\\.\\.\\." range) "non-ff") |
|
1903 |
(t "new commits")) |
|
1904 |
(and (or modified untracked) |
|
1905 |
(concat ", " |
|
1906 |
(and modified "modified") |
|
1907 |
(and modified untracked " and ") |
|
1908 |
(and untracked "untracked") |
|
1909 |
" content")) |
|
1910 |
")") |
|
1911 |
(let ((default-directory |
|
1912 |
(file-name-as-directory |
|
1913 |
(expand-file-name module (magit-toplevel))))) |
|
1914 |
(magit-git-wash (apply-partially 'magit-log-wash-log 'module) |
|
1915 |
"log" "--oneline" "--left-right" range) |
|
1916 |
(delete-char -1))))) |
|
1917 |
((and (looking-at "^Submodule \\([^ ]+\\) \\([^ ]+\\) (\\([^)]+\\))$") |
|
1918 |
(equal (match-string 1) module)) |
|
1919 |
(magit-bind-match-strings (_module _range msg) nil |
|
1920 |
(magit-delete-line) |
|
1921 |
(magit-insert-section (magit-module-section module) |
|
1922 |
(magit-insert-heading |
|
1923 |
(propertize (concat "submodule " module) |
|
1924 |
'face 'magit-diff-file-heading) |
|
1925 |
" (" msg ")")))) |
|
1926 |
(t |
|
1927 |
(magit-insert-section (magit-module-section module) |
|
1928 |
(magit-insert-heading |
|
1929 |
(propertize (concat "modified " module) |
|
1930 |
'face 'magit-diff-file-heading) |
|
1931 |
" (" |
|
1932 |
(and modified "modified") |
|
1933 |
(and modified untracked " and ") |
|
1934 |
(and untracked "untracked") |
|
1935 |
" content)"))))))) |
|
1936 |
|
|
1937 |
(defun magit-diff-wash-hunk () |
|
1938 |
(when (looking-at "^@\\{2,\\} \\(.+?\\) @\\{2,\\}\\(?: \\(.*\\)\\)?") |
|
1939 |
(let ((heading (match-string 0)) |
|
1940 |
(value (cons (match-string 2) (split-string (match-string 1))))) |
|
1941 |
(magit-delete-line) |
|
1942 |
(magit-insert-section it (hunk value) |
|
1943 |
(insert (propertize (concat heading "\n") 'face 'magit-diff-hunk-heading)) |
|
1944 |
(magit-insert-heading) |
|
1945 |
(while (not (or (eobp) (looking-at "^[^-+\s\\]"))) |
|
1946 |
(forward-line)) |
|
1947 |
(oset it end (point)) |
|
1948 |
(oset it washer 'magit-diff-paint-hunk))) |
|
1949 |
t)) |
|
1950 |
|
|
1951 |
(defun magit-diff-expansion-threshold (section) |
|
1952 |
"Keep new diff sections collapsed if washing takes too long." |
|
1953 |
(and (magit-file-section-p section) |
|
1954 |
(> (float-time (time-subtract (current-time) magit-refresh-start-time)) |
|
1955 |
magit-diff-expansion-threshold) |
|
1956 |
'hide)) |
|
1957 |
|
|
1958 |
;;; Revision Mode |
|
1959 |
|
|
1960 |
(define-derived-mode magit-revision-mode magit-diff-mode "Magit Rev" |
|
1961 |
"Mode for looking at a Git commit. |
|
1962 |
|
|
1963 |
This mode is documented in info node `(magit)Revision Buffer'. |
|
1964 |
|
|
1965 |
\\<magit-mode-map>\ |
|
1966 |
Type \\[magit-refresh] to refresh the current buffer. |
|
1967 |
Type \\[magit-section-toggle] to expand or hide the section at point. |
|
1968 |
Type \\[magit-visit-thing] to visit the hunk or file at point. |
|
1969 |
|
|
1970 |
Staging and applying changes is documented in info node |
|
1971 |
`(magit)Staging and Unstaging' and info node `(magit)Applying'. |
|
1972 |
|
|
1973 |
\\<magit-hunk-section-map>Type \ |
|
1974 |
\\[magit-apply] to apply the change at point, \ |
|
1975 |
\\[magit-stage] to stage, |
|
1976 |
\\[magit-unstage] to unstage, \ |
|
1977 |
\\[magit-discard] to discard, or \ |
|
1978 |
\\[magit-reverse] to reverse it. |
|
1979 |
|
|
1980 |
\\{magit-revision-mode-map}" |
|
1981 |
:group 'magit-revision |
|
1982 |
(hack-dir-local-variables-non-file-buffer) |
|
1983 |
(setq-local bookmark-make-record-function |
|
1984 |
'magit-bookmark--revision-make-record)) |
|
1985 |
|
|
1986 |
(defun magit-revision-refresh-buffer (rev __const _args files) |
|
1987 |
(magit-set-header-line-format |
|
1988 |
(concat (capitalize (magit-object-type rev)) |
|
1989 |
" " |
|
1990 |
rev |
|
1991 |
(pcase (length files) |
|
1992 |
(0) |
|
1993 |
(1 (concat " limited to file " (car files))) |
|
1994 |
(_ (concat " limited to files " |
|
1995 |
(mapconcat #'identity files ", ")))))) |
|
1996 |
(setq magit-buffer-revision-hash (magit-rev-parse rev)) |
|
1997 |
(magit-insert-section (commitbuf) |
|
1998 |
(magit-run-section-hook 'magit-revision-sections-hook rev))) |
|
1999 |
|
|
2000 |
(defun magit-insert-revision-diff (rev) |
|
2001 |
"Insert the diff into this `magit-revision-mode' buffer." |
|
2002 |
(let ((magit-git-global-arguments |
|
2003 |
(remove "--literal-pathspecs" magit-git-global-arguments))) |
|
2004 |
;; Before v2.2.0, "--format=" did not mean "no output". |
|
2005 |
;; Instead the default format was used. So use "--format=%n" |
|
2006 |
;; and then delete the empty lines. |
|
2007 |
(magit-git-wash (lambda (args) |
|
2008 |
(delete-region (point) (progn (forward-line 3) (point))) |
|
2009 |
(magit-diff-wash-diffs args)) |
|
2010 |
"show" "-p" "--cc" "--format=%n" "--no-prefix" |
|
2011 |
(and (member "--stat" (nth 2 magit-refresh-args)) "--numstat") |
|
2012 |
(nth 2 magit-refresh-args) (concat rev "^{commit}") "--" |
|
2013 |
(nth 3 magit-refresh-args)))) |
|
2014 |
|
|
2015 |
(defun magit-insert-revision-tag (rev) |
|
2016 |
"Insert tag message and headers into a revision buffer. |
|
2017 |
This function only inserts anything when `magit-show-commit' is |
|
2018 |
called with a tag as argument, when that is called with a commit |
|
2019 |
or a ref which is not a branch, then it inserts nothing." |
|
2020 |
(when (equal (magit-object-type rev) "tag") |
|
2021 |
(magit-insert-section (taginfo) |
|
2022 |
(let ((beg (point))) |
|
2023 |
;; "git verify-tag -v" would output what we need, but the gpg |
|
2024 |
;; output is send to stderr and we have no control over the |
|
2025 |
;; order in which stdout and stderr are inserted, which would |
|
2026 |
;; make parsing hard. We are forced to use "git cat-file tag" |
|
2027 |
;; instead, which inserts the signature instead of verifying |
|
2028 |
;; it. We remove that later and then insert the verification |
|
2029 |
;; output using "git verify-tag" (without the "-v"). |
|
2030 |
(magit-git-insert "cat-file" "tag" rev) |
|
2031 |
(goto-char beg) |
|
2032 |
(forward-line 3) |
|
2033 |
(delete-region beg (point))) |
|
2034 |
(looking-at "^tagger \\([^<]+\\) <\\([^>]+\\)") |
|
2035 |
(let ((heading (format "Tagger: %s <%s>" |
|
2036 |
(match-string 1) |
|
2037 |
(match-string 2)))) |
|
2038 |
(magit-delete-line) |
|
2039 |
(insert (propertize heading 'face 'magit-section-secondary-heading))) |
|
2040 |
(magit-insert-heading) |
|
2041 |
(if (re-search-forward "-----BEGIN PGP SIGNATURE-----" nil t) |
|
2042 |
(progn |
|
2043 |
(let ((beg (match-beginning 0))) |
|
2044 |
(re-search-forward "-----END PGP SIGNATURE-----") |
|
2045 |
(delete-region beg (point))) |
|
2046 |
(insert ?\n) |
|
2047 |
(process-file magit-git-executable nil t nil "verify-tag" rev)) |
|
2048 |
(goto-char (point-max))) |
|
2049 |
(insert ?\n)))) |
|
2050 |
|
|
2051 |
(defvar magit-commit-message-section-map |
|
2052 |
(let ((map (make-sparse-keymap))) |
|
2053 |
(define-key map [remap magit-visit-thing] 'magit-show-commit) |
|
2054 |
map) |
|
2055 |
"Keymap for `commit-message' sections.") |
|
2056 |
|
|
2057 |
(defun magit-insert-revision-message (rev) |
|
2058 |
"Insert the commit message into a revision buffer." |
|
2059 |
(magit-insert-section section (commit-message) |
|
2060 |
(oset section heading-highlight-face 'magit-diff-hunk-heading-highlight) |
|
2061 |
(let ((beg (point))) |
|
2062 |
(insert (save-excursion ; The risky var query can move point. |
|
2063 |
(with-temp-buffer |
|
2064 |
(magit-rev-insert-format "%B" rev) |
|
2065 |
(magit-revision--wash-message)))) |
|
2066 |
(if (= (point) (+ beg 2)) |
|
2067 |
(progn (backward-delete-char 2) |
|
2068 |
(insert "(no message)\n")) |
|
2069 |
(goto-char beg) |
|
2070 |
(save-excursion |
|
2071 |
(while (search-forward "\r\n" nil t) ; Remove trailing CRs. |
|
2072 |
(delete-region (match-beginning 0) (1+ (match-beginning 0))))) |
|
2073 |
(when magit-revision-fill-summary-line |
|
2074 |
(let ((fill-column (min magit-revision-fill-summary-line |
|
2075 |
(window-width)))) |
|
2076 |
(fill-region (point) (line-end-position)))) |
|
2077 |
(when magit-revision-use-hash-sections |
|
2078 |
(save-excursion |
|
2079 |
(while (not (eobp)) |
|
2080 |
(re-search-forward "\\_<" nil 'move) |
|
2081 |
(let ((beg (point))) |
|
2082 |
(re-search-forward "\\_>" nil t) |
|
2083 |
(when (> (point) beg) |
|
2084 |
(let ((text (buffer-substring-no-properties beg (point)))) |
|
2085 |
(when (pcase magit-revision-use-hash-sections |
|
2086 |
(`quickest ; false negatives and positives |
|
2087 |
(and (>= (length text) 7) |
|
2088 |
(string-match-p "[0-9]" text) |
|
2089 |
(string-match-p "[a-z]" text))) |
|
2090 |
(`quicker ; false negatives (number-less hashes) |
|
2091 |
(and (>= (length text) 7) |
|
2092 |
(string-match-p "[0-9]" text) |
|
2093 |
(magit-rev-verify-commit text))) |
|
2094 |
(`quick ; false negatives (short hashes) |
|
2095 |
(and (>= (length text) 7) |
|
2096 |
(magit-rev-verify-commit text))) |
|
2097 |
(`slow |
|
2098 |
(magit-rev-verify-commit text))) |
|
2099 |
(put-text-property beg (point) 'face 'magit-hash) |
|
2100 |
(let ((end (point))) |
|
2101 |
(goto-char beg) |
|
2102 |
(magit-insert-section (commit text) |
|
2103 |
(goto-char end)))))))))) |
|
2104 |
(save-excursion |
|
2105 |
(forward-line) |
|
2106 |
(add-face-text-property beg (point) 'magit-diff-hunk-heading) |
|
2107 |
(magit-insert-heading)) |
|
2108 |
(when magit-diff-highlight-keywords |
|
2109 |
(save-excursion |
|
2110 |
(while (re-search-forward "\\[[^[]*\\]" nil t) |
|
2111 |
(let ((beg (match-beginning 0)) |
|
2112 |
(end (match-end 0))) |
|
2113 |
(put-text-property |
|
2114 |
beg end 'face |
|
2115 |
(if-let ((face (get-text-property beg 'face))) |
|
2116 |
(list face 'magit-keyword) |
|
2117 |
'magit-keyword)))))) |
|
2118 |
(goto-char (point-max)))))) |
|
2119 |
|
|
2120 |
(defun magit-insert-revision-notes (rev) |
|
2121 |
"Insert commit notes into a revision buffer." |
|
2122 |
(let* ((var "core.notesRef") |
|
2123 |
(def (or (magit-get var) "refs/notes/commits"))) |
|
2124 |
(dolist (ref (or (magit-list-active-notes-refs))) |
|
2125 |
(magit-insert-section section (notes ref (not (equal ref def))) |
|
2126 |
(oset section heading-highlight-face 'magit-diff-hunk-heading-highlight) |
|
2127 |
(let ((beg (point))) |
|
2128 |
(insert (with-temp-buffer |
|
2129 |
(magit-git-insert "-c" (concat "core.notesRef=" ref) |
|
2130 |
"notes" "show" rev) |
|
2131 |
(magit-revision--wash-message))) |
|
2132 |
(if (= (point) beg) |
|
2133 |
(magit-cancel-section) |
|
2134 |
(goto-char beg) |
|
2135 |
(end-of-line) |
|
2136 |
(insert (format " (%s)" |
|
2137 |
(propertize (if (string-prefix-p "refs/notes/" ref) |
|
2138 |
(substring ref 11) |
|
2139 |
ref) |
|
2140 |
'face 'magit-refname))) |
|
2141 |
(forward-char) |
|
2142 |
(add-face-text-property beg (point) 'magit-diff-hunk-heading) |
|
2143 |
(magit-insert-heading) |
|
2144 |
(goto-char (point-max)) |
|
2145 |
(insert ?\n))))))) |
|
2146 |
|
|
2147 |
(defun magit-revision--wash-message () |
|
2148 |
(let ((major-mode 'git-commit-mode)) |
|
2149 |
(hack-dir-local-variables) |
|
2150 |
(hack-local-variables-apply)) |
|
2151 |
(unless (memq git-commit-major-mode '(nil text-mode)) |
|
2152 |
(funcall git-commit-major-mode) |
|
2153 |
(font-lock-ensure)) |
|
2154 |
(buffer-string)) |
|
2155 |
|
|
2156 |
(defun magit-insert-revision-headers (rev) |
|
2157 |
"Insert headers about the commit into a revision buffer." |
|
2158 |
(magit-insert-section (headers) |
|
2159 |
;; Before v2.2.0, "%D" was not supported. |
|
2160 |
(--when-let (magit-rev-format "%d" rev "--decorate=full") |
|
2161 |
(insert (magit-format-ref-labels it) ?\s)) |
|
2162 |
(insert (propertize (magit-rev-parse (concat rev "^{commit}")) |
|
2163 |
'face 'magit-hash)) |
|
2164 |
(magit-insert-heading) |
|
2165 |
(let ((beg (point))) |
|
2166 |
(magit-rev-insert-format magit-revision-headers-format rev) |
|
2167 |
(magit-insert-revision-gravatars rev beg)) |
|
2168 |
(when magit-revision-insert-related-refs |
|
2169 |
(dolist (parent (magit-commit-parents rev)) |
|
2170 |
(magit-insert-section (commit parent) |
|
2171 |
(let ((line (magit-rev-format "%h %s" parent))) |
|
2172 |
(string-match "^\\([^ ]+\\) \\(.*\\)" line) |
|
2173 |
(magit-bind-match-strings (hash msg) line |
|
2174 |
(insert "Parent: ") |
|
2175 |
(insert (propertize hash 'face 'magit-hash)) |
|
2176 |
(insert " " msg "\n"))))) |
|
2177 |
(magit--insert-related-refs |
|
2178 |
rev "--merged" "Merged" |
|
2179 |
(eq magit-revision-insert-related-refs 'all)) |
|
2180 |
(magit--insert-related-refs |
|
2181 |
rev "--contains" "Contained" |
|
2182 |
(eq magit-revision-insert-related-refs '(all mixed))) |
|
2183 |
(when-let ((follows (magit-get-current-tag rev t))) |
|
2184 |
(let ((tag (car follows)) |
|
2185 |
(cnt (cadr follows))) |
|
2186 |
(magit-insert-section (tag tag) |
|
2187 |
(insert (format "Follows: %s (%s)\n" |
|
2188 |
(propertize tag 'face 'magit-tag) |
|
2189 |
(propertize (number-to-string cnt) |
|
2190 |
'face 'magit-branch-local)))))) |
|
2191 |
(when-let ((precedes (magit-get-next-tag rev t))) |
|
2192 |
(let ((tag (car precedes)) |
|
2193 |
(cnt (cadr precedes))) |
|
2194 |
(magit-insert-section (tag tag) |
|
2195 |
(insert (format "Precedes: %s (%s)\n" |
|
2196 |
(propertize tag 'face 'magit-tag) |
|
2197 |
(propertize (number-to-string cnt) |
|
2198 |
'face 'magit-tag)))))) |
|
2199 |
(insert ?\n)))) |
|
2200 |
|
|
2201 |
(defun magit--insert-related-refs (rev arg title remote) |
|
2202 |
(when-let ((refs (magit-list-related-branches arg rev (and remote "-a")))) |
|
2203 |
(insert title ":" (make-string (- 10 (length title)) ?\s)) |
|
2204 |
(dolist (branch refs) |
|
2205 |
(if (<= (+ (current-column) 1 (length branch)) |
|
2206 |
(window-width)) |
|
2207 |
(insert ?\s) |
|
2208 |
(insert ?\n (make-string 12 ?\s))) |
|
2209 |
(magit-insert-section (branch branch) |
|
2210 |
(insert (propertize branch 'face |
|
2211 |
(if (string-prefix-p "remotes/" branch) |
|
2212 |
'magit-branch-remote |
|
2213 |
'magit-branch-local))))) |
|
2214 |
(insert ?\n))) |
|
2215 |
|
|
2216 |
(defun magit-insert-revision-gravatars (rev beg) |
|
2217 |
(when (and magit-revision-show-gravatars |
|
2218 |
(window-system)) |
|
2219 |
(require 'gravatar) |
|
2220 |
(pcase-let ((`(,author . ,committer) |
|
2221 |
(pcase magit-revision-show-gravatars |
|
2222 |
(`t '("^Author: " . "^Commit: ")) |
|
2223 |
(`author '("^Author: " . nil)) |
|
2224 |
(`committer '(nil . "^Commit: ")) |
|
2225 |
(_ magit-revision-show-gravatars)))) |
|
2226 |
(--when-let (and author (magit-rev-format "%aE" rev)) |
|
2227 |
(magit-insert-revision-gravatar beg rev it author)) |
|
2228 |
(--when-let (and committer (magit-rev-format "%cE" rev)) |
|
2229 |
(magit-insert-revision-gravatar beg rev it committer))))) |
|
2230 |
|
|
2231 |
(defun magit-insert-revision-gravatar (beg rev email regexp) |
|
2232 |
(save-excursion |
|
2233 |
(goto-char beg) |
|
2234 |
(when (re-search-forward regexp nil t) |
|
2235 |
(let* ((column (length (match-string 0))) |
|
2236 |
(font-obj (query-font (font-at (point) (get-buffer-window)))) |
|
2237 |
(size (* 2 (+ (aref font-obj 4) |
|
2238 |
(aref font-obj 5)))) |
|
2239 |
(align-to (+ column |
|
2240 |
(ceiling (/ size (aref font-obj 7) 1.0)) |
|
2241 |
1)) |
|
2242 |
(gravatar-size (- size 2))) |
|
2243 |
(ignore-errors ; service may be unreachable |
|
2244 |
(gravatar-retrieve email 'magit-insert-revision-gravatar-cb |
|
2245 |
(list rev (point-marker) align-to column))))))) |
|
2246 |
|
|
2247 |
(defun magit-insert-revision-gravatar-cb (image rev marker align-to column) |
|
2248 |
(unless (eq image 'error) |
|
2249 |
(when-let ((buffer (marker-buffer marker))) |
|
2250 |
(with-current-buffer buffer |
|
2251 |
(save-excursion |
|
2252 |
(goto-char marker) |
|
2253 |
;; The buffer might display another revision by now or |
|
2254 |
;; it might have been refreshed, in which case another |
|
2255 |
;; process might already have inserted the image. |
|
2256 |
(when (and (equal rev (car magit-refresh-args)) |
|
2257 |
(not (eq (car-safe |
|
2258 |
(car-safe |
|
2259 |
(get-text-property (point) 'display))) |
|
2260 |
'image))) |
|
2261 |
(let ((top `((,@image :ascent center :relief 1) |
|
2262 |
(slice 0.0 0.0 1.0 0.5))) |
|
2263 |
(bot `((,@image :ascent center :relief 1) |
|
2264 |
(slice 0.0 0.5 1.0 1.0))) |
|
2265 |
(align `((space :align-to ,align-to)))) |
|
2266 |
(when magit-revision-use-gravatar-kludge |
|
2267 |
(cl-rotatef top bot)) |
|
2268 |
(let ((inhibit-read-only t)) |
|
2269 |
(insert (propertize " " 'display top)) |
|
2270 |
(insert (propertize " " 'display align)) |
|
2271 |
(forward-line) |
|
2272 |
(forward-char column) |
|
2273 |
(insert (propertize " " 'display bot)) |
|
2274 |
(insert (propertize " " 'display align)))))))))) |
|
2275 |
|
|
2276 |
;;; Diff Sections |
|
2277 |
|
|
2278 |
(defvar magit-unstaged-section-map |
|
2279 |
(let ((map (make-sparse-keymap))) |
|
2280 |
(define-key map [remap magit-visit-thing] 'magit-diff-unstaged) |
|
2281 |
(define-key map [remap magit-delete-thing] 'magit-discard) |
|
2282 |
(define-key map "s" 'magit-stage) |
|
2283 |
(define-key map "u" 'magit-unstage) |
|
2284 |
map) |
|
2285 |
"Keymap for the `unstaged' section.") |
|
2286 |
|
|
2287 |
(magit-define-section-jumper magit-jump-to-unstaged "Unstaged changes" unstaged) |
|
2288 |
|
|
2289 |
(defun magit-insert-unstaged-changes () |
|
2290 |
"Insert section showing unstaged changes." |
|
2291 |
(magit-insert-section (unstaged) |
|
2292 |
(magit-insert-heading "Unstaged changes:") |
|
2293 |
(magit-git-wash #'magit-diff-wash-diffs |
|
2294 |
"diff" magit-diff-section-arguments "--no-prefix" |
|
2295 |
"--" magit-diff-section-file-args))) |
|
2296 |
|
|
2297 |
(defvar magit-staged-section-map |
|
2298 |
(let ((map (make-sparse-keymap))) |
|
2299 |
(define-key map [remap magit-visit-thing] 'magit-diff-staged) |
|
2300 |
(define-key map [remap magit-delete-thing] 'magit-discard) |
|
2301 |
(define-key map [remap magit-revert-no-commit] 'magit-reverse) |
|
2302 |
(define-key map "s" 'magit-stage) |
|
2303 |
(define-key map "u" 'magit-unstage) |
|
2304 |
map) |
|
2305 |
"Keymap for the `staged' section.") |
|
2306 |
|
|
2307 |
(magit-define-section-jumper magit-jump-to-staged "Staged changes" staged) |
|
2308 |
|
|
2309 |
(defun magit-insert-staged-changes () |
|
2310 |
"Insert section showing staged changes." |
|
2311 |
;; Avoid listing all files as deleted when visiting a bare repo. |
|
2312 |
(unless (magit-bare-repo-p) |
|
2313 |
(magit-insert-section (staged) |
|
2314 |
(magit-insert-heading "Staged changes:") |
|
2315 |
(magit-git-wash #'magit-diff-wash-diffs |
|
2316 |
"diff" "--cached" magit-diff-section-arguments "--no-prefix" |
|
2317 |
"--" magit-diff-section-file-args)))) |
|
2318 |
|
|
2319 |
;;; Diff Type |
|
2320 |
|
|
2321 |
(defun magit-diff-type (&optional section) |
|
2322 |
"Return the diff type of SECTION. |
|
2323 |
|
|
2324 |
The returned type is one of the symbols `staged', `unstaged', |
|
2325 |
`committed', or `undefined'. This type serves a similar purpose |
|
2326 |
as the general type common to all sections (which is stored in |
|
2327 |
the `type' slot of the corresponding `magit-section' struct) but |
|
2328 |
takes additional information into account. When the SECTION |
|
2329 |
isn't related to diffs and the buffer containing it also isn't |
|
2330 |
a diff-only buffer, then return nil. |
|
2331 |
|
|
2332 |
Currently the type can also be one of `tracked' and `untracked' |
|
2333 |
but these values are not handled explicitly everywhere they |
|
2334 |
should be and a possible fix could be to just return nil here. |
|
2335 |
|
|
2336 |
The section has to be a `diff' or `hunk' section, or a section |
|
2337 |
whose children are of type `diff'. If optional SECTION is nil, |
|
2338 |
return the diff type for the current section. In buffers whose |
|
2339 |
major mode is `magit-diff-mode' SECTION is ignored and the type |
|
2340 |
is determined using other means. In `magit-revision-mode' |
|
2341 |
buffers the type is always `committed'. |
|
2342 |
|
|
2343 |
Do not confuse this with `magit-diff-scope' (which see)." |
|
2344 |
(--when-let (or section (magit-current-section)) |
|
2345 |
(cond ((derived-mode-p 'magit-revision-mode 'magit-stash-mode) 'committed) |
|
2346 |
((derived-mode-p 'magit-diff-mode) |
|
2347 |
(let ((range (nth 0 magit-refresh-args)) |
|
2348 |
(const (nth 1 magit-refresh-args))) |
|
2349 |
(cond ((member "--no-index" const) 'undefined) |
|
2350 |
((or (not range) |
|
2351 |
(magit-rev-eq range "HEAD")) |
|
2352 |
(if (member "--cached" const) |
|
2353 |
'staged |
|
2354 |
'unstaged)) |
|
2355 |
((member "--cached" const) |
|
2356 |
(if (magit-rev-head-p range) |
|
2357 |
'staged |
|
2358 |
'undefined)) ; i.e. committed and staged |
|
2359 |
(t 'committed)))) |
|
2360 |
((derived-mode-p 'magit-status-mode) |
|
2361 |
(let ((stype (oref it type))) |
|
2362 |
(if (memq stype '(staged unstaged tracked untracked)) |
|
2363 |
stype |
|
2364 |
(pcase stype |
|
2365 |
((or `file `module) |
|
2366 |
(let* ((parent (oref it parent)) |
|
2367 |
(type (oref parent type))) |
|
2368 |
(if (memq type '(file module)) |
|
2369 |
(magit-diff-type parent) |
|
2370 |
type))) |
|
2371 |
(`hunk (-> it |
|
2372 |
(oref parent) |
|
2373 |
(oref parent) |
|
2374 |
(oref type))))))) |
|
2375 |
((derived-mode-p 'magit-log-mode) |
|
2376 |
(if (or (and (magit-section-match 'commit section) |
|
2377 |
(oref section children)) |
|
2378 |
(magit-section-match [* file commit] section)) |
|
2379 |
'committed |
|
2380 |
'undefined)) |
|
2381 |
(t 'undefined)))) |
|
2382 |
|
|
2383 |
(cl-defun magit-diff-scope (&optional (section nil ssection) strict) |
|
2384 |
"Return the diff scope of SECTION or the selected section(s). |
|
2385 |
|
|
2386 |
A diff's \"scope\" describes what part of a diff is selected, it is |
|
2387 |
a symbol, one of `region', `hunk', `hunks', `file', `files', or |
|
2388 |
`list'. Do not confuse this with the diff \"type\", as returned by |
|
2389 |
`magit-diff-type'. |
|
2390 |
|
|
2391 |
If optional SECTION is non-nil, then return the scope of that, |
|
2392 |
ignoring the sections selected by the region. Otherwise return |
|
2393 |
the scope of the current section, or if the region is active and |
|
2394 |
selects a valid group of diff related sections, the type of these |
|
2395 |
sections, i.e. `hunks' or `files'. If SECTION, or if that is nil |
|
2396 |
the current section, is a `hunk' section; and the region region |
|
2397 |
starts and ends inside the body of a that section, then the type |
|
2398 |
is `region'. If the region is empty after a mouse click, then |
|
2399 |
`hunk' is returned instead of `region'. |
|
2400 |
|
|
2401 |
If optional STRICT is non-nil, then return nil if the diff type of |
|
2402 |
the section at point is `untracked' or the section at point is not |
|
2403 |
actually a `diff' but a `diffstat' section." |
|
2404 |
(let ((siblings (and (not ssection) (magit-region-sections nil t)))) |
|
2405 |
(setq section (or section (car siblings) (magit-current-section))) |
|
2406 |
(when (and section |
|
2407 |
(or (not strict) |
|
2408 |
(and (not (eq (magit-diff-type section) 'untracked)) |
|
2409 |
(not (eq (--when-let (oref section parent) |
|
2410 |
(oref it type)) |
|
2411 |
'diffstat))))) |
|
2412 |
(pcase (list (oref section type) |
|
2413 |
(and siblings t) |
|
2414 |
(magit-diff-use-hunk-region-p) |
|
2415 |
ssection) |
|
2416 |
(`(hunk nil t ,_) |
|
2417 |
(if (magit-section-internal-region-p section) 'region 'hunk)) |
|
2418 |
(`(hunk t t nil) 'hunks) |
|
2419 |
(`(hunk ,_ ,_ ,_) 'hunk) |
|
2420 |
(`(file t t nil) 'files) |
|
2421 |
(`(file ,_ ,_ ,_) 'file) |
|
2422 |
(`(module t t nil) 'files) |
|
2423 |
(`(module ,_ ,_ ,_) 'file) |
|
2424 |
(`(,(or `staged `unstaged `untracked) |
|
2425 |
nil ,_ ,_) 'list))))) |
|
2426 |
|
|
2427 |
(defun magit-diff-use-hunk-region-p () |
|
2428 |
(and (region-active-p) |
|
2429 |
(not (and (if (version< emacs-version "25.1") |
|
2430 |
(eq this-command 'mouse-drag-region) |
|
2431 |
;; TODO implement this from first principals |
|
2432 |
;; currently it's trial-and-error |
|
2433 |
(or (eq this-command 'mouse-drag-region) |
|
2434 |
(eq last-command 'mouse-drag-region) |
|
2435 |
;; When another window was previously |
|
2436 |
;; selected then the last-command is |
|
2437 |
;; some byte-code function. |
|
2438 |
(byte-code-function-p last-command))) |
|
2439 |
(eq (region-end) (region-beginning)))))) |
|
2440 |
|
|
2441 |
;;; Diff Highlight |
|
2442 |
|
|
2443 |
(defun magit-diff-unhighlight (section selection) |
|
2444 |
"Remove the highlighting of the diff-related SECTION." |
|
2445 |
(when (magit-hunk-section-p section) |
|
2446 |
(magit-diff-paint-hunk section selection nil) |
|
2447 |
t)) |
|
2448 |
|
|
2449 |
(defun magit-diff-highlight (section selection) |
|
2450 |
"Highlight the diff-related SECTION. |
|
2451 |
If SECTION is not a diff-related section, then do nothing and |
|
2452 |
return nil. If SELECTION is non-nil, then it is a list of sections |
|
2453 |
selected by the region, including SECTION. All of these sections |
|
2454 |
are highlighted." |
|
2455 |
(if (and (magit-section-match 'commit section) |
|
2456 |
(oref section children)) |
|
2457 |
(progn (if selection |
|
2458 |
(dolist (section selection) |
|
2459 |
(magit-diff-highlight-list section selection)) |
|
2460 |
(magit-diff-highlight-list section)) |
|
2461 |
t) |
|
2462 |
(when-let ((scope (magit-diff-scope section t))) |
|
2463 |
(cond ((eq scope 'region) |
|
2464 |
(magit-diff-paint-hunk section selection t)) |
|
2465 |
(selection |
|
2466 |
(dolist (section selection) |
|
2467 |
(magit-diff-highlight-recursive section selection))) |
|
2468 |
(t |
|
2469 |
(magit-diff-highlight-recursive section))) |
|
2470 |
t))) |
|
2471 |
|
|
2472 |
(defun magit-diff-highlight-recursive (section &optional selection) |
|
2473 |
(pcase (magit-diff-scope section) |
|
2474 |
(`list (magit-diff-highlight-list section selection)) |
|
2475 |
(`file (magit-diff-highlight-file section selection)) |
|
2476 |
(`hunk (magit-diff-highlight-heading section selection) |
|
2477 |
(magit-diff-paint-hunk section selection t)) |
|
2478 |
(_ (magit-section-highlight section nil)))) |
|
2479 |
|
|
2480 |
(defun magit-diff-highlight-list (section &optional selection) |
|
2481 |
(let ((beg (oref section start)) |
|
2482 |
(cnt (oref section content)) |
|
2483 |
(end (oref section end))) |
|
2484 |
(when (or (eq this-command 'mouse-drag-region) |
|
2485 |
(not selection)) |
|
2486 |
(unless (and (region-active-p) |
|
2487 |
(<= (region-beginning) beg)) |
|
2488 |
(magit-section-make-overlay beg cnt 'magit-section-highlight)) |
|
2489 |
(unless (oref section hidden) |
|
2490 |
(dolist (child (oref section children)) |
|
2491 |
(when (or (eq this-command 'mouse-drag-region) |
|
2492 |
(not (and (region-active-p) |
|
2493 |
(<= (region-beginning) |
|
2494 |
(oref child start))))) |
|
2495 |
(magit-diff-highlight-recursive child selection))))) |
|
2496 |
(when magit-diff-highlight-hunk-body |
|
2497 |
(magit-section-make-overlay (1- end) end 'magit-section-highlight)))) |
|
2498 |
|
|
2499 |
(defun magit-diff-highlight-file (section &optional selection) |
|
2500 |
(magit-diff-highlight-heading section selection) |
|
2501 |
(unless (oref section hidden) |
|
2502 |
(dolist (child (oref section children)) |
|
2503 |
(magit-diff-highlight-recursive child selection)))) |
|
2504 |
|
|
2505 |
(defun magit-diff-highlight-heading (section &optional selection) |
|
2506 |
(magit-section-make-overlay |
|
2507 |
(oref section start) |
|
2508 |
(or (oref section content) |
|
2509 |
(oref section end)) |
|
2510 |
(pcase (list (oref section type) |
|
2511 |
(and (member section selection) |
|
2512 |
(not (eq this-command 'mouse-drag-region)))) |
|
2513 |
(`(file t) 'magit-diff-file-heading-selection) |
|
2514 |
(`(file nil) 'magit-diff-file-heading-highlight) |
|
2515 |
(`(module t) 'magit-diff-file-heading-selection) |
|
2516 |
(`(module nil) 'magit-diff-file-heading-highlight) |
|
2517 |
(`(hunk t) 'magit-diff-hunk-heading-selection) |
|
2518 |
(`(hunk nil) 'magit-diff-hunk-heading-highlight)))) |
|
2519 |
|
|
2520 |
;;; Hunk Paint |
|
2521 |
|
|
2522 |
(cl-defun magit-diff-paint-hunk |
|
2523 |
(section &optional selection |
|
2524 |
(highlight (magit-section-selected-p section selection))) |
|
2525 |
(let (paint) |
|
2526 |
(unless magit-diff-highlight-hunk-body |
|
2527 |
(setq highlight nil)) |
|
2528 |
(cond (highlight |
|
2529 |
(unless (oref section hidden) |
|
2530 |
(add-to-list 'magit-section-highlighted-sections section) |
|
2531 |
(cond ((memq section magit-section-unhighlight-sections) |
|
2532 |
(setq magit-section-unhighlight-sections |
|
2533 |
(delq section magit-section-unhighlight-sections))) |
|
2534 |
(magit-diff-highlight-hunk-body |
|
2535 |
(setq paint t))))) |
|
2536 |
(t |
|
2537 |
(cond ((and (oref section hidden) |
|
2538 |
(memq section magit-section-unhighlight-sections)) |
|
2539 |
(add-to-list 'magit-section-highlighted-sections section) |
|
2540 |
(setq magit-section-unhighlight-sections |
|
2541 |
(delq section magit-section-unhighlight-sections))) |
|
2542 |
(t |
|
2543 |
(setq paint t))))) |
|
2544 |
(when paint |
|
2545 |
(save-excursion |
|
2546 |
(goto-char (oref section start)) |
|
2547 |
(let ((end (oref section end)) |
|
2548 |
(merging (looking-at "@@@")) |
|
2549 |
(stage nil) |
|
2550 |
(tab-width (magit-diff-tab-width |
|
2551 |
(magit-section-parent-value section)))) |
|
2552 |
(forward-line) |
|
2553 |
(while (< (point) end) |
|
2554 |
(when (and magit-diff-hide-trailing-cr-characters |
|
2555 |
(char-equal ?\r (char-before (line-end-position)))) |
|
2556 |
(put-text-property (1- (line-end-position)) (line-end-position) |
|
2557 |
'invisible t)) |
|
2558 |
(put-text-property |
|
2559 |
(point) (1+ (line-end-position)) 'face |
|
2560 |
(cond |
|
2561 |
((looking-at "^\\+\\+?\\([<=|>]\\)\\{7\\}") |
|
2562 |
(setq stage (pcase (list (match-string 1) highlight) |
|
2563 |
(`("<" nil) 'magit-diff-our) |
|
2564 |
(`("<" t) 'magit-diff-our-highlight) |
|
2565 |
(`("|" nil) 'magit-diff-base) |
|
2566 |
(`("|" t) 'magit-diff-base-highlight) |
|
2567 |
(`("=" nil) 'magit-diff-their) |
|
2568 |
(`("=" t) 'magit-diff-their-highlight) |
|
2569 |
(`(">" nil) nil))) |
|
2570 |
'magit-diff-conflict-heading) |
|
2571 |
((looking-at (if merging "^\\(\\+\\| \\+\\)" "^\\+")) |
|
2572 |
(magit-diff-paint-tab merging tab-width) |
|
2573 |
(magit-diff-paint-whitespace merging) |
|
2574 |
(or stage |
|
2575 |
(if highlight 'magit-diff-added-highlight 'magit-diff-added))) |
|
2576 |
((looking-at (if merging "^\\(-\\| -\\)" "^-")) |
|
2577 |
(magit-diff-paint-tab merging tab-width) |
|
2578 |
(if highlight 'magit-diff-removed-highlight 'magit-diff-removed)) |
|
2579 |
(t |
|
2580 |
(magit-diff-paint-tab merging tab-width) |
|
2581 |
(if highlight 'magit-diff-context-highlight 'magit-diff-context)))) |
|
2582 |
(forward-line)))))) |
|
2583 |
(magit-diff-update-hunk-refinement section)) |
|
2584 |
|
|
2585 |
(defvar magit-diff--tab-width-cache nil) |
|
2586 |
|
|
2587 |
(defun magit-diff-tab-width (file) |
|
2588 |
(setq file (expand-file-name file)) |
|
2589 |
(cl-flet ((cache (value) |
|
2590 |
(let ((elt (assoc file magit-diff--tab-width-cache))) |
|
2591 |
(if elt |
|
2592 |
(setcdr elt value) |
|
2593 |
(setq magit-diff--tab-width-cache |
|
2594 |
(cons (cons file value) |
|
2595 |
magit-diff--tab-width-cache)))) |
|
2596 |
value)) |
|
2597 |
(cond |
|
2598 |
((not magit-diff-adjust-tab-width) |
|
2599 |
tab-width) |
|
2600 |
((--when-let (find-buffer-visiting file) |
|
2601 |
(cache (buffer-local-value 'tab-width it)))) |
|
2602 |
((--when-let (assoc file magit-diff--tab-width-cache) |
|
2603 |
(or (cdr it) |
|
2604 |
tab-width))) |
|
2605 |
((or (eq magit-diff-adjust-tab-width 'always) |
|
2606 |
(and (numberp magit-diff-adjust-tab-width) |
|
2607 |
(>= magit-diff-adjust-tab-width |
|
2608 |
(nth 7 (file-attributes file))))) |
|
2609 |
(cache (buffer-local-value 'tab-width (find-file-noselect file)))) |
|
2610 |
(t |
|
2611 |
(cache nil) |
|
2612 |
tab-width)))) |
|
2613 |
|
|
2614 |
(defun magit-diff-paint-tab (merging width) |
|
2615 |
(save-excursion |
|
2616 |
(forward-char (if merging 2 1)) |
|
2617 |
(while (= (char-after) ?\t) |
|
2618 |
(put-text-property (point) (1+ (point)) |
|
2619 |
'display (list (list 'space :width width))) |
|
2620 |
(forward-char)))) |
|
2621 |
|
|
2622 |
(defun magit-diff-paint-whitespace (merging) |
|
2623 |
(when (and magit-diff-paint-whitespace |
|
2624 |
(or (derived-mode-p 'magit-status-mode) |
|
2625 |
(not (eq magit-diff-paint-whitespace 'status)))) |
|
2626 |
(let ((prefix (if merging "^[-\\+\s]\\{2\\}" "^[-\\+]")) |
|
2627 |
(indent |
|
2628 |
(if (local-variable-p 'magit-diff-highlight-indentation) |
|
2629 |
magit-diff-highlight-indentation |
|
2630 |
(setq-local |
|
2631 |
magit-diff-highlight-indentation |
|
2632 |
(cdr (--first (string-match-p (car it) default-directory) |
|
2633 |
(nreverse |
|
2634 |
(default-value |
|
2635 |
'magit-diff-highlight-indentation)))))))) |
|
2636 |
(when (and magit-diff-highlight-trailing |
|
2637 |
(looking-at (concat prefix ".*?\\([ \t]+\\)$"))) |
|
2638 |
(let ((ov (make-overlay (match-beginning 1) (match-end 1) nil t))) |
|
2639 |
(overlay-put ov 'face 'magit-diff-whitespace-warning) |
|
2640 |
(overlay-put ov 'evaporate t))) |
|
2641 |
(when (or (and (eq indent 'tabs) |
|
2642 |
(looking-at (concat prefix "\\( *\t[ \t]*\\)"))) |
|
2643 |
(and (integerp indent) |
|
2644 |
(looking-at (format "%s\\([ \t]* \\{%s,\\}[ \t]*\\)" |
|
2645 |
prefix indent)))) |
|
2646 |
(let ((ov (make-overlay (match-beginning 1) (match-end 1) nil t))) |
|
2647 |
(overlay-put ov 'face 'magit-diff-whitespace-warning) |
|
2648 |
(overlay-put ov 'evaporate t)))))) |
|
2649 |
|
|
2650 |
(defun magit-diff-update-hunk-refinement (&optional section) |
|
2651 |
(if section |
|
2652 |
(unless (oref section hidden) |
|
2653 |
(pcase (list magit-diff-refine-hunk |
|
2654 |
(oref section refined) |
|
2655 |
(eq section (magit-current-section))) |
|
2656 |
((or `(all nil ,_) `(t nil t)) |
|
2657 |
(oset section refined t) |
|
2658 |
(save-excursion |
|
2659 |
(goto-char (oref section start)) |
|
2660 |
;; `diff-refine-hunk' does not handle combined diffs. |
|
2661 |
(unless (looking-at "@@@") |
|
2662 |
;; Avoid fsyncing many small temp files |
|
2663 |
(let ((write-region-inhibit-fsync t)) |
|
2664 |
(diff-refine-hunk))))) |
|
2665 |
((or `(nil t ,_) `(t t nil)) |
|
2666 |
(oset section refined nil) |
|
2667 |
(remove-overlays (oref section start) |
|
2668 |
(oref section end) |
|
2669 |
'diff-mode 'fine)))) |
|
2670 |
(cl-labels ((recurse (section) |
|
2671 |
(if (magit-section-match 'hunk section) |
|
2672 |
(magit-diff-update-hunk-refinement section) |
|
2673 |
(dolist (child (oref section children)) |
|
2674 |
(recurse child))))) |
|
2675 |
(recurse magit-root-section)))) |
|
2676 |
|
|
2677 |
|
|
2678 |
;;; Hunk Region |
|
2679 |
|
|
2680 |
(defun magit-diff-hunk-region-beginning () |
|
2681 |
(save-excursion (goto-char (region-beginning)) |
|
2682 |
(line-beginning-position))) |
|
2683 |
|
|
2684 |
(defun magit-diff-hunk-region-end () |
|
2685 |
(save-excursion (goto-char (region-end)) |
|
2686 |
(line-end-position))) |
|
2687 |
|
|
2688 |
(defun magit-diff-update-hunk-region (section) |
|
2689 |
"Highlight the hunk-internal region if any." |
|
2690 |
(when (eq (magit-diff-scope section t) 'region) |
|
2691 |
(magit-diff--make-hunk-overlay |
|
2692 |
(oref section start) |
|
2693 |
(1- (oref section content)) |
|
2694 |
'face 'magit-diff-lines-heading |
|
2695 |
'display (magit-diff-hunk-region-header section) |
|
2696 |
'after-string (magit-diff--hunk-after-string 'magit-diff-lines-heading)) |
|
2697 |
(run-hook-with-args 'magit-diff-highlight-hunk-region-functions section) |
|
2698 |
t)) |
|
2699 |
|
|
2700 |
(defun magit-diff-highlight-hunk-region-dim-outside (section) |
|
2701 |
"Dim the parts of the hunk that are outside the hunk-internal region. |
|
2702 |
This is done by using the same foreground and background color |
|
2703 |
for added and removed lines as for context lines." |
|
2704 |
(let ((face (if magit-diff-highlight-hunk-body |
|
2705 |
'magit-diff-context-highlight |
|
2706 |
'magit-diff-context))) |
|
2707 |
(when magit-diff-unmarked-lines-keep-foreground |
|
2708 |
(setq face (list :background (face-attribute face :background)))) |
|
2709 |
(magit-diff--make-hunk-overlay (oref section content) |
|
2710 |
(magit-diff-hunk-region-beginning) |
|
2711 |
'face face |
|
2712 |
'priority 2) |
|
2713 |
(magit-diff--make-hunk-overlay (1+ (magit-diff-hunk-region-end)) |
|
2714 |
(oref section end) |
|
2715 |
'face face |
|
2716 |
'priority 2))) |
|
2717 |
|
|
2718 |
(defun magit-diff-highlight-hunk-region-using-face (_section) |
|
2719 |
"Highlight the hunk-internal region by making it bold. |
|
2720 |
Or rather highlight using the face `magit-diff-hunk-region', though |
|
2721 |
changing only the `:weight' and/or `:slant' is recommended for that |
|
2722 |
face." |
|
2723 |
(magit-diff--make-hunk-overlay (magit-diff-hunk-region-beginning) |
|
2724 |
(1+ (magit-diff-hunk-region-end)) |
|
2725 |
'face 'magit-diff-hunk-region)) |
|
2726 |
|
|
2727 |
(defun magit-diff-highlight-hunk-region-using-overlays (section) |
|
2728 |
"Emphasize the hunk-internal region using delimiting horizontal lines. |
|
2729 |
This is implemented as single-pixel newlines places inside overlays." |
|
2730 |
(if (window-system) |
|
2731 |
(let ((beg (magit-diff-hunk-region-beginning)) |
|
2732 |
(end (magit-diff-hunk-region-end)) |
|
2733 |
(str (propertize |
|
2734 |
(concat (propertize "\s" 'display '(space :height (1))) |
|
2735 |
(propertize "\n" 'line-height t)) |
|
2736 |
'face 'magit-diff-lines-boundary))) |
|
2737 |
(magit-diff--make-hunk-overlay beg (1+ beg) 'before-string str) |
|
2738 |
(magit-diff--make-hunk-overlay end (1+ end) 'after-string str)) |
|
2739 |
(magit-diff-highlight-hunk-region-using-face section))) |
|
2740 |
|
|
2741 |
(defun magit-diff-highlight-hunk-region-using-underline (section) |
|
2742 |
"Emphasize the hunk-internal region using delimiting horizontal lines. |
|
2743 |
This is implemented by overlining and underlining the first and |
|
2744 |
last (visual) lines of the region." |
|
2745 |
(if (window-system) |
|
2746 |
(let* ((beg (magit-diff-hunk-region-beginning)) |
|
2747 |
(end (magit-diff-hunk-region-end)) |
|
2748 |
(beg-eol (save-excursion (goto-char beg) |
|
2749 |
(end-of-visual-line) |
|
2750 |
(point))) |
|
2751 |
(end-bol (save-excursion (goto-char end) |
|
2752 |
(beginning-of-visual-line) |
|
2753 |
(point))) |
|
2754 |
(color (face-background 'magit-diff-lines-boundary nil t))) |
|
2755 |
(cl-flet ((ln (b e &rest face) |
|
2756 |
(magit-diff--make-hunk-overlay |
|
2757 |
b e 'face face 'after-string |
|
2758 |
(magit-diff--hunk-after-string face)))) |
|
2759 |
(if (= beg end-bol) |
|
2760 |
(ln beg beg-eol :overline color :underline color) |
|
2761 |
(ln beg beg-eol :overline color) |
|
2762 |
(ln end-bol end :underline color)))) |
|
2763 |
(magit-diff-highlight-hunk-region-using-face section))) |
|
2764 |
|
|
2765 |
(defun magit-diff--make-hunk-overlay (start end &rest args) |
|
2766 |
(let ((ov (make-overlay start end nil t))) |
|
2767 |
(overlay-put ov 'evaporate t) |
|
2768 |
(while args (overlay-put ov (pop args) (pop args))) |
|
2769 |
(push ov magit-region-overlays) |
|
2770 |
ov)) |
|
2771 |
|
|
2772 |
(defun magit-diff--hunk-after-string (face) |
|
2773 |
(propertize "\s" |
|
2774 |
'face face |
|
2775 |
'display (list 'space :align-to |
|
2776 |
`(+ (0 . right) |
|
2777 |
,(min (window-hscroll) |
|
2778 |
(- (line-end-position) |
|
2779 |
(line-beginning-position))))) |
|
2780 |
;; This prevents the cursor from being rendered at the |
|
2781 |
;; edge of the window. |
|
2782 |
'cursor t)) |
|
2783 |
|
|
2784 |
;;; Hunk Utilities |
|
2785 |
|
|
2786 |
(defun magit-diff-inside-hunk-body-p () |
|
2787 |
"Return non-nil if point is inside the body of a hunk." |
|
2788 |
(and (magit-section-match 'hunk) |
|
2789 |
(> (point) |
|
2790 |
(oref (magit-current-section) content)))) |
|
2791 |
|
|
2792 |
;;; Diff Extract |
|
2793 |
|
|
2794 |
(defun magit-diff-file-header (section) |
|
2795 |
(when (magit-hunk-section-p section) |
|
2796 |
(setq section (oref section parent))) |
|
2797 |
(when (magit-file-section-p section) |
|
2798 |
(oref section header))) |
|
2799 |
|
|
2800 |
(defun magit-diff-hunk-region-header (section) |
|
2801 |
(let ((patch (magit-diff-hunk-region-patch section))) |
|
2802 |
(string-match "\n" patch) |
|
2803 |
(substring patch 0 (1- (match-end 0))))) |
|
2804 |
|
|
2805 |
(defun magit-diff-hunk-region-patch (section &optional args) |
|
2806 |
(let ((op (if (member "--reverse" args) "+" "-")) |
|
2807 |
(sbeg (oref section start)) |
|
2808 |
(rbeg (magit-diff-hunk-region-beginning)) |
|
2809 |
(rend (region-end)) |
|
2810 |
(send (oref section end)) |
|
2811 |
(patch nil)) |
|
2812 |
(save-excursion |
|
2813 |
(goto-char sbeg) |
|
2814 |
(while (< (point) send) |
|
2815 |
(looking-at "\\(.\\)\\([^\n]*\n\\)") |
|
2816 |
(cond ((or (string-match-p "[@ ]" (match-string-no-properties 1)) |
|
2817 |
(and (>= (point) rbeg) |
|
2818 |
(<= (point) rend))) |
|
2819 |
(push (match-string-no-properties 0) patch)) |
|
2820 |
((equal op (match-string-no-properties 1)) |
|
2821 |
(push (concat " " (match-string-no-properties 2)) patch))) |
|
2822 |
(forward-line))) |
|
2823 |
(with-temp-buffer |
|
2824 |
(insert (mapconcat 'identity (reverse patch) "")) |
|
2825 |
(diff-fixup-modifs (point-min) (point-max)) |
|
2826 |
(setq patch (buffer-string))) |
|
2827 |
patch)) |
|
2828 |
|
|
2829 |
;;; _ |
|
2830 |
(provide 'magit-diff) |
|
2831 |
;;; magit-diff.el ends here |