Klimi's new dotfiles with stow.
25개 이상의 토픽을 선택하실 수 없습니다. Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

558 lines
22 KiB

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