commit | author | age
|
5cb5f7
|
1 |
;;; magit-files.el --- finding files -*- lexical-binding: t -*- |
C |
2 |
|
|
3 |
;; Copyright (C) 2010-2018 The Magit Project Contributors |
|
4 |
;; |
|
5 |
;; You should have received a copy of the AUTHORS.md file which |
|
6 |
;; lists all contributors. If not, see http://magit.vc/authors. |
|
7 |
|
|
8 |
;; Author: 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 finding blobs, staged files, |
|
27 |
;; and Git configuration files. It also implements modes useful in |
|
28 |
;; buffers visiting files and blobs, and the commands used by those |
|
29 |
;; modes. |
|
30 |
|
|
31 |
;;; Code: |
|
32 |
|
|
33 |
(eval-when-compile |
|
34 |
(require 'subr-x)) |
|
35 |
|
|
36 |
(require 'magit) |
|
37 |
|
|
38 |
;;; Find Blob |
|
39 |
|
|
40 |
(defvar magit-find-file-hook nil) |
|
41 |
(add-hook 'magit-find-file-hook #'magit-blob-mode) |
|
42 |
|
|
43 |
;;;###autoload |
|
44 |
(defun magit-find-file (rev file) |
|
45 |
"View FILE from REV. |
|
46 |
Switch to a buffer visiting blob REV:FILE, |
|
47 |
creating one if none already exists." |
|
48 |
(interactive (magit-find-file-read-args "Find file")) |
|
49 |
(switch-to-buffer (magit-find-file-noselect rev file))) |
|
50 |
|
|
51 |
;;;###autoload |
|
52 |
(defun magit-find-file-other-window (rev file) |
|
53 |
"View FILE from REV, in another window. |
|
54 |
Like `magit-find-file', but create a new window or reuse an |
|
55 |
existing one." |
|
56 |
(interactive (magit-find-file-read-args "Find file in other window")) |
|
57 |
(switch-to-buffer-other-window (magit-find-file-noselect rev file))) |
|
58 |
|
|
59 |
(defun magit-find-file-read-args (prompt) |
|
60 |
(let ((rev (magit-read-branch-or-commit "Find file from revision"))) |
|
61 |
(list rev (magit-read-file-from-rev rev prompt)))) |
|
62 |
|
|
63 |
(defun magit-find-file-noselect (rev file) |
|
64 |
"Read FILE from REV into a buffer and return the buffer. |
|
65 |
FILE must be relative to the top directory of the repository." |
|
66 |
(magit-find-file-noselect-1 rev file 'magit-find-file-hook)) |
|
67 |
|
|
68 |
(defun magit-find-file-noselect-1 (rev file hookvar &optional revert) |
|
69 |
"Read FILE from REV into a buffer and return the buffer. |
|
70 |
FILE must be relative to the top directory of the repository. |
|
71 |
An empty REV stands for index." |
|
72 |
(let ((topdir (magit-toplevel))) |
|
73 |
(when (file-name-absolute-p file) |
|
74 |
(setq file (file-relative-name file topdir))) |
|
75 |
(with-current-buffer (magit-get-revision-buffer-create rev file) |
|
76 |
(when (or (not magit-buffer-file-name) |
|
77 |
(if (eq revert 'ask-revert) |
|
78 |
(y-or-n-p (format "%s already exists; revert it? " |
|
79 |
(buffer-name)))) |
|
80 |
revert) |
|
81 |
(setq magit-buffer-revision |
|
82 |
(if (string= rev "") "{index}" (magit-rev-format "%H" rev))) |
|
83 |
(setq magit-buffer-refname rev) |
|
84 |
(setq magit-buffer-file-name (expand-file-name file topdir)) |
|
85 |
(setq default-directory |
|
86 |
(let ((dir (file-name-directory magit-buffer-file-name))) |
|
87 |
(if (file-exists-p dir) dir topdir))) |
|
88 |
(setq-local revert-buffer-function #'magit-revert-rev-file-buffer) |
|
89 |
(revert-buffer t t) |
|
90 |
(run-hooks hookvar)) |
|
91 |
(current-buffer)))) |
|
92 |
|
|
93 |
(defun magit-get-revision-buffer-create (rev file) |
|
94 |
(magit-get-revision-buffer rev file t)) |
|
95 |
|
|
96 |
(defun magit-get-revision-buffer (rev file &optional create) |
|
97 |
(funcall (if create 'get-buffer-create 'get-buffer) |
|
98 |
(format "%s.~%s~" file (if (equal rev "") "index" |
|
99 |
(subst-char-in-string ?/ ?_ rev))))) |
|
100 |
|
|
101 |
(defun magit-revert-rev-file-buffer (_ignore-auto noconfirm) |
|
102 |
(when (or noconfirm |
|
103 |
(and (not (buffer-modified-p)) |
|
104 |
(catch 'found |
|
105 |
(dolist (regexp revert-without-query) |
|
106 |
(when (string-match regexp magit-buffer-file-name) |
|
107 |
(throw 'found t))))) |
|
108 |
(yes-or-no-p (format "Revert buffer from git %s? " |
|
109 |
(if (equal magit-buffer-refname "") "{index}" |
|
110 |
(concat "revision " magit-buffer-refname))))) |
|
111 |
(let* ((inhibit-read-only t) |
|
112 |
(default-directory (magit-toplevel)) |
|
113 |
(file (file-relative-name magit-buffer-file-name)) |
|
114 |
(coding-system-for-read (or coding-system-for-read 'undecided))) |
|
115 |
(erase-buffer) |
|
116 |
(magit-git-insert "cat-file" "-p" (concat magit-buffer-refname ":" file)) |
|
117 |
(setq buffer-file-coding-system last-coding-system-used)) |
|
118 |
(let ((buffer-file-name magit-buffer-file-name) |
|
119 |
(after-change-major-mode-hook |
|
120 |
(remq 'global-diff-hl-mode-enable-in-buffers |
|
121 |
after-change-major-mode-hook))) |
|
122 |
(normal-mode t)) |
|
123 |
(setq buffer-read-only t) |
|
124 |
(set-buffer-modified-p nil) |
|
125 |
(goto-char (point-min)))) |
|
126 |
|
|
127 |
;;; Find Index |
|
128 |
|
|
129 |
(defvar magit-find-index-hook nil) |
|
130 |
|
|
131 |
(defun magit-find-file-index-noselect (file &optional revert) |
|
132 |
"Read FILE from the index into a buffer and return the buffer. |
|
133 |
FILE must to be relative to the top directory of the repository." |
|
134 |
(magit-find-file-noselect-1 "" file 'magit-find-index-hook |
|
135 |
(or revert 'ask-revert))) |
|
136 |
|
|
137 |
(defun magit-update-index () |
|
138 |
"Update the index with the contents of the current buffer. |
|
139 |
The current buffer has to be visiting a file in the index, which |
|
140 |
is done using `magit-find-index-noselect'." |
|
141 |
(interactive) |
|
142 |
(let ((file (magit-file-relative-name))) |
|
143 |
(unless (equal magit-buffer-refname "") |
|
144 |
(user-error "%s isn't visiting the index" file)) |
|
145 |
(if (y-or-n-p (format "Update index with contents of %s" (buffer-name))) |
|
146 |
(let ((index (make-temp-file "index")) |
|
147 |
(buffer (current-buffer))) |
|
148 |
(when magit-wip-before-change-mode |
|
149 |
(magit-wip-commit-before-change (list file) " before un-/stage")) |
|
150 |
(let ((coding-system-for-write buffer-file-coding-system)) |
|
151 |
(with-temp-file index |
|
152 |
(insert-buffer-substring buffer))) |
|
153 |
(magit-with-toplevel |
|
154 |
(magit-call-git "update-index" "--cacheinfo" |
|
155 |
(substring (magit-git-string "ls-files" "-s" file) |
|
156 |
0 6) |
|
157 |
(magit-git-string "hash-object" "-t" "blob" "-w" |
|
158 |
(concat "--path=" file) |
|
159 |
"--" index) |
|
160 |
file)) |
|
161 |
(set-buffer-modified-p nil) |
|
162 |
(when magit-wip-after-apply-mode |
|
163 |
(magit-wip-commit-after-apply (list file) " after un-/stage"))) |
|
164 |
(message "Abort"))) |
|
165 |
(--when-let (magit-mode-get-buffer 'magit-status-mode) |
|
166 |
(with-current-buffer it (magit-refresh))) |
|
167 |
t) |
|
168 |
|
|
169 |
;;; Find Config File |
|
170 |
|
|
171 |
(defun magit-find-git-config-file (filename &optional wildcards) |
|
172 |
"Edit a file located in the current repository's git directory. |
|
173 |
|
|
174 |
When \".git\", located at the root of the working tree, is a |
|
175 |
regular file, then that makes it cumbersome to open a file |
|
176 |
located in the actual git directory. |
|
177 |
|
|
178 |
This command is like `find-file', except that it temporarily |
|
179 |
binds `default-directory' to the actual git directory, while |
|
180 |
reading the FILENAME." |
|
181 |
(interactive |
|
182 |
(let ((default-directory (magit-git-dir))) |
|
183 |
(find-file-read-args "Find file: " |
|
184 |
(confirm-nonexistent-file-or-buffer)))) |
|
185 |
(find-file filename wildcards)) |
|
186 |
|
|
187 |
(defun magit-find-git-config-file-other-window (filename &optional wildcards) |
|
188 |
"Edit a file located in the current repository's git directory, in another window. |
|
189 |
|
|
190 |
When \".git\", located at the root of the working tree, is a |
|
191 |
regular file, then that makes it cumbersome to open a file |
|
192 |
located in the actual git directory. |
|
193 |
|
|
194 |
This command is like `find-file-other-window', except that it |
|
195 |
temporarily binds `default-directory' to the actual git |
|
196 |
directory, while reading the FILENAME." |
|
197 |
(interactive |
|
198 |
(let ((default-directory (magit-git-dir))) |
|
199 |
(find-file-read-args "Find file in other window: " |
|
200 |
(confirm-nonexistent-file-or-buffer)))) |
|
201 |
(find-file-other-window filename wildcards)) |
|
202 |
|
|
203 |
(defun magit-find-git-config-file-other-frame (filename &optional wildcards) |
|
204 |
"Edit a file located in the current repository's git directory, in another frame. |
|
205 |
|
|
206 |
When \".git\", located at the root of the working tree, is a |
|
207 |
regular file, then that makes it cumbersome to open a file |
|
208 |
located in the actual git directory. |
|
209 |
|
|
210 |
This command is like `find-file-other-frame', except that it |
|
211 |
temporarily binds `default-directory' to the actual git |
|
212 |
directory, while reading the FILENAME." |
|
213 |
(interactive |
|
214 |
(let ((default-directory (magit-git-dir))) |
|
215 |
(find-file-read-args "Find file in other frame: " |
|
216 |
(confirm-nonexistent-file-or-buffer)))) |
|
217 |
(find-file-other-frame filename wildcards)) |
|
218 |
|
|
219 |
;;; File Mode |
|
220 |
|
|
221 |
(defvar magit-file-mode-map |
|
222 |
(let ((map (make-sparse-keymap))) |
|
223 |
(define-key map "\C-xg" 'magit-status) |
|
224 |
(define-key map "\C-x\M-g" 'magit-dispatch-popup) |
|
225 |
(define-key map "\C-c\M-g" 'magit-file-popup) |
|
226 |
map) |
|
227 |
"Keymap for `magit-file-mode'.") |
|
228 |
|
|
229 |
;;;###autoload (autoload 'magit-file-popup "magit" nil t) |
|
230 |
(magit-define-popup magit-file-popup |
|
231 |
"Popup console for Magit commands in file-visiting buffers." |
|
232 |
:actions '((?s "Stage" magit-stage-file) |
|
233 |
(?D "Diff..." magit-diff-buffer-file-popup) |
|
234 |
(?L "Log..." magit-log-buffer-file-popup) |
|
235 |
(?B "Blame..." magit-blame-popup) nil |
|
236 |
(?u "Unstage" magit-unstage-file) |
|
237 |
(?d "Diff" magit-diff-buffer-file) |
|
238 |
(?l "Log" magit-log-buffer-file) |
|
239 |
(?b "Blame" magit-blame-addition) |
|
240 |
(?p "Prev blob" magit-blob-previous) |
|
241 |
(?c "Commit" magit-commit-popup) nil |
|
242 |
(?t "Trace" magit-log-trace-definition) |
|
243 |
(?r (lambda () |
|
244 |
(with-current-buffer magit-pre-popup-buffer |
|
245 |
(and (not buffer-file-name) |
|
246 |
(propertize "...removal" 'face 'default)))) |
|
247 |
magit-blame-removal) |
|
248 |
(?n "Next blob" magit-blob-next) |
|
249 |
(?e "Edit line" magit-edit-line-commit) |
|
250 |
nil nil |
|
251 |
(?f (lambda () |
|
252 |
(with-current-buffer magit-pre-popup-buffer |
|
253 |
(and (not buffer-file-name) |
|
254 |
(propertize "...reverse" 'face 'default)))) |
|
255 |
magit-blame-reverse) |
|
256 |
nil) |
|
257 |
:max-action-columns 5) |
|
258 |
|
|
259 |
(defvar magit-file-mode-lighter "") |
|
260 |
|
|
261 |
(define-minor-mode magit-file-mode |
|
262 |
"Enable some Magit features in a file-visiting buffer. |
|
263 |
|
|
264 |
Currently this only adds the following key bindings. |
|
265 |
\n\\{magit-file-mode-map}" |
|
266 |
:package-version '(magit . "2.2.0") |
|
267 |
:lighter magit-file-mode-lighter |
|
268 |
:keymap magit-file-mode-map) |
|
269 |
|
|
270 |
(defun magit-file-mode-turn-on () |
|
271 |
(and buffer-file-name |
|
272 |
(magit-inside-worktree-p t) |
|
273 |
(magit-file-mode))) |
|
274 |
|
|
275 |
;;;###autoload |
|
276 |
(define-globalized-minor-mode global-magit-file-mode |
|
277 |
magit-file-mode magit-file-mode-turn-on |
|
278 |
:package-version '(magit . "2.13.0") |
|
279 |
:link '(info-link "(magit)Minor Mode for Buffers Visiting Files") |
|
280 |
:group 'magit-essentials |
|
281 |
:group 'magit-modes |
|
282 |
:init-value t) |
|
283 |
;; Unfortunately `:init-value t' only sets the value of the mode |
|
284 |
;; variable but does not cause the mode function to be called, and we |
|
285 |
;; cannot use `:initialize' to call that explicitly because the option |
|
286 |
;; is defined before the functions, so we have to do it here. |
|
287 |
(cl-eval-when (load) |
|
288 |
(when global-magit-file-mode |
|
289 |
(global-magit-file-mode 1))) |
|
290 |
|
|
291 |
;;; Blob Mode |
|
292 |
|
|
293 |
(defvar magit-blob-mode-map |
|
294 |
(let ((map (make-sparse-keymap))) |
|
295 |
(cond ((featurep 'jkl) |
|
296 |
(define-key map "i" 'magit-blob-previous) |
|
297 |
(define-key map "k" 'magit-blob-next) |
|
298 |
(define-key map "j" 'magit-blame-addition) |
|
299 |
(define-key map "l" 'magit-blame-removal) |
|
300 |
(define-key map "f" 'magit-blame-reverse)) |
|
301 |
(t |
|
302 |
(define-key map "p" 'magit-blob-previous) |
|
303 |
(define-key map "n" 'magit-blob-next) |
|
304 |
(define-key map "b" 'magit-blame-addition) |
|
305 |
(define-key map "r" 'magit-blame-removal) |
|
306 |
(define-key map "f" 'magit-blame-reverse))) |
|
307 |
(define-key map "q" 'magit-kill-this-buffer) |
|
308 |
map) |
|
309 |
"Keymap for `magit-blob-mode'.") |
|
310 |
|
|
311 |
(define-minor-mode magit-blob-mode |
|
312 |
"Enable some Magit features in blob-visiting buffers. |
|
313 |
|
|
314 |
Currently this only adds the following key bindings. |
|
315 |
\n\\{magit-blob-mode-map}" |
|
316 |
:package-version '(magit . "2.3.0")) |
|
317 |
|
|
318 |
(defun magit-blob-next () |
|
319 |
"Visit the next blob which modified the current file." |
|
320 |
(interactive) |
|
321 |
(if magit-buffer-file-name |
|
322 |
(magit-blob-visit (or (magit-blob-successor magit-buffer-revision |
|
323 |
magit-buffer-file-name) |
|
324 |
magit-buffer-file-name) |
|
325 |
(line-number-at-pos)) |
|
326 |
(if (buffer-file-name (buffer-base-buffer)) |
|
327 |
(user-error "You have reached the end of time") |
|
328 |
(user-error "Buffer isn't visiting a file or blob")))) |
|
329 |
|
|
330 |
(defun magit-blob-previous () |
|
331 |
"Visit the previous blob which modified the current file." |
|
332 |
(interactive) |
|
333 |
(if-let ((file (or magit-buffer-file-name |
|
334 |
(buffer-file-name (buffer-base-buffer))))) |
|
335 |
(--if-let (magit-blob-ancestor magit-buffer-revision file) |
|
336 |
(magit-blob-visit it (line-number-at-pos)) |
|
337 |
(user-error "You have reached the beginning of time")) |
|
338 |
(user-error "Buffer isn't visiting a file or blob"))) |
|
339 |
|
|
340 |
(defun magit-blob-visit (blob-or-file line) |
|
341 |
(if (stringp blob-or-file) |
|
342 |
(find-file blob-or-file) |
|
343 |
(pcase-let ((`(,rev ,file) blob-or-file)) |
|
344 |
(magit-find-file rev file) |
|
345 |
(apply #'message "%s (%s %s ago)" |
|
346 |
(magit-rev-format "%s" rev) |
|
347 |
(magit--age (magit-rev-format "%ct" rev))))) |
|
348 |
(goto-char (point-min)) |
|
349 |
(forward-line (1- line))) |
|
350 |
|
|
351 |
(defun magit-blob-ancestor (rev file) |
|
352 |
(let ((lines (magit-with-toplevel |
|
353 |
(magit-git-lines "log" "-2" "--format=%H" "--name-only" |
|
354 |
"--follow" (or rev "HEAD") "--" file)))) |
|
355 |
(if rev (cddr lines) (butlast lines 2)))) |
|
356 |
|
|
357 |
(defun magit-blob-successor (rev file) |
|
358 |
(let ((lines (magit-with-toplevel |
|
359 |
(magit-git-lines "log" "--format=%H" "--name-only" "--follow" |
|
360 |
"HEAD" "--" file)))) |
|
361 |
(catch 'found |
|
362 |
(while lines |
|
363 |
(if (equal (nth 2 lines) rev) |
|
364 |
(throw 'found (list (nth 0 lines) (nth 1 lines))) |
|
365 |
(setq lines (nthcdr 2 lines))))))) |
|
366 |
|
|
367 |
;;; File Commands |
|
368 |
|
|
369 |
(defun magit-file-rename (file newname) |
|
370 |
"Rename the FILE to NEWNAME. |
|
371 |
If FILE isn't tracked in Git, fallback to using `rename-file'." |
|
372 |
(interactive |
|
373 |
(let* ((file (magit-read-file "Rename file")) |
|
374 |
(dir (file-name-directory file)) |
|
375 |
(newname (read-file-name (format "Rename %s to file: " file) |
|
376 |
(and dir (expand-file-name dir))))) |
|
377 |
(list (expand-file-name file (magit-toplevel)) |
|
378 |
(expand-file-name newname)))) |
|
379 |
(if (magit-file-tracked-p (magit-convert-filename-for-git file)) |
|
380 |
(let ((oldbuf (get-file-buffer file))) |
|
381 |
(when (and oldbuf (buffer-modified-p oldbuf)) |
|
382 |
(user-error "Save %s before moving it" file)) |
|
383 |
(when (file-exists-p newname) |
|
384 |
(user-error "%s already exists" newname)) |
|
385 |
(magit-run-git "mv" |
|
386 |
(magit-convert-filename-for-git file) |
|
387 |
(magit-convert-filename-for-git newname)) |
|
388 |
(when oldbuf |
|
389 |
(with-current-buffer oldbuf |
|
390 |
(let ((buffer-read-only buffer-read-only)) |
|
391 |
(set-visited-file-name newname)) |
|
392 |
(if (fboundp 'vc-refresh-state) |
|
393 |
(vc-refresh-state) |
|
394 |
(with-no-warnings |
|
395 |
(vc-find-file-hook)))))) |
|
396 |
(rename-file file newname current-prefix-arg) |
|
397 |
(magit-refresh))) |
|
398 |
|
|
399 |
(defun magit-file-untrack (files &optional force) |
|
400 |
"Untrack the selected FILES or one file read in the minibuffer. |
|
401 |
|
|
402 |
With a prefix argument FORCE do so even when the files have |
|
403 |
staged as well as unstaged changes." |
|
404 |
(interactive (list (or (--if-let (magit-region-values 'file t) |
|
405 |
(progn |
|
406 |
(unless (magit-file-tracked-p (car it)) |
|
407 |
(user-error "Already untracked")) |
|
408 |
(magit-confirm-files 'untrack it "Untrack")) |
|
409 |
(list (magit-read-tracked-file "Untrack file")))) |
|
410 |
current-prefix-arg)) |
|
411 |
(magit-run-git "rm" "--cached" (and force "--force") "--" files)) |
|
412 |
|
|
413 |
(defun magit-file-delete (files &optional force) |
|
414 |
"Delete the selected FILES or one file read in the minibuffer. |
|
415 |
|
|
416 |
With a prefix argument FORCE do so even when the files have |
|
417 |
uncommitted changes. When the files aren't being tracked in |
|
418 |
Git, then fallback to using `delete-file'." |
|
419 |
(interactive (list (--if-let (magit-region-values 'file t) |
|
420 |
(magit-confirm-files 'delete it "Delete") |
|
421 |
(list (magit-read-file "Delete file"))) |
|
422 |
current-prefix-arg)) |
|
423 |
(if (magit-file-tracked-p (car files)) |
|
424 |
(magit-call-git "rm" (and force "--force") "--" files) |
|
425 |
(let ((topdir (magit-toplevel))) |
|
426 |
(dolist (file files) |
|
427 |
(delete-file (expand-file-name file topdir) t)))) |
|
428 |
(magit-refresh)) |
|
429 |
|
|
430 |
;;;###autoload |
|
431 |
(defun magit-file-checkout (rev file) |
|
432 |
"Checkout FILE from REV." |
|
433 |
(interactive |
|
434 |
(let ((rev (magit-read-branch-or-commit |
|
435 |
"Checkout from revision" magit-buffer-revision))) |
|
436 |
(list rev (magit-read-file-from-rev rev "Checkout file")))) |
|
437 |
(magit-with-toplevel |
|
438 |
(magit-run-git "checkout" rev "--" file))) |
|
439 |
|
|
440 |
;;; Read File |
|
441 |
|
|
442 |
(defvar magit-read-file-hist nil) |
|
443 |
|
|
444 |
(defun magit-read-file-from-rev (rev prompt &optional default) |
|
445 |
(let ((files (magit-revision-files rev))) |
|
446 |
(magit-completing-read |
|
447 |
prompt files nil t nil 'magit-read-file-hist |
|
448 |
(car (member (or default (magit-current-file)) files))))) |
|
449 |
|
|
450 |
(defun magit-read-file (prompt &optional tracked-only) |
|
451 |
(let ((choices (nconc (magit-list-files) |
|
452 |
(unless tracked-only (magit-untracked-files))))) |
|
453 |
(magit-completing-read |
|
454 |
prompt choices nil t nil nil |
|
455 |
(car (member (or (magit-section-value-if '(file submodule)) |
|
456 |
(magit-file-relative-name nil tracked-only)) |
|
457 |
choices))))) |
|
458 |
|
|
459 |
(defun magit-read-tracked-file (prompt) |
|
460 |
(magit-read-file prompt t)) |
|
461 |
|
|
462 |
(defun magit-read-file-choice (prompt files &optional error default) |
|
463 |
"Read file from FILES. |
|
464 |
|
|
465 |
If FILES has only one member, return that instead of prompting. |
|
466 |
If FILES has no members, give a user error. ERROR can be given |
|
467 |
to provide a more informative error. |
|
468 |
|
|
469 |
If DEFAULT is non-nil, use this as the default value instead of |
|
470 |
`magit-current-file'." |
|
471 |
(pcase (length files) |
|
472 |
(0 (user-error (or error "No file choices"))) |
|
473 |
(1 (car files)) |
|
474 |
(_ (magit-completing-read |
|
475 |
prompt files nil t nil 'magit-read-file-hist |
|
476 |
(car (member (or default (magit-current-file)) files)))))) |
|
477 |
|
|
478 |
(defun magit-read-changed-file (rev-or-range prompt &optional default) |
|
479 |
(magit-read-file-choice |
|
480 |
prompt |
|
481 |
(magit-changed-files rev-or-range) |
|
482 |
default |
|
483 |
(concat "No file changed in " rev-or-range))) |
|
484 |
|
|
485 |
(defun magit-read-files (prompt initial-contents) |
|
486 |
(mapconcat 'identity |
|
487 |
(completing-read-multiple (or prompt "File,s: ") |
|
488 |
(magit-list-files) |
|
489 |
nil nil initial-contents) ",")) |
|
490 |
|
|
491 |
;;; _ |
|
492 |
(provide 'magit-files) |
|
493 |
;;; magit-files.el ends here |