Klimi's new dotfiles with stow.
Du kannst nicht mehr als 25 Themen auswählen Themen müssen entweder mit einem Buchstaben oder einer Ziffer beginnen. Sie können Bindestriche („-“) enthalten und bis zu 35 Zeichen lang sein.

739 Zeilen
31 KiB

vor 5 Jahren
  1. ;;; magit-apply.el --- apply Git diffs -*- 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 commands for applying Git diffs or parts
  22. ;; of such a diff. The supported "apply variants" are apply, stage,
  23. ;; unstage, discard, and reverse - more than Git itself knows about,
  24. ;; at least at the porcelain level.
  25. ;;; Code:
  26. (eval-when-compile
  27. (require 'subr-x))
  28. (require 'magit-core)
  29. (require 'magit-diff)
  30. (require 'magit-wip)
  31. (require 'transient) ; See #3732.
  32. ;; For `magit-apply'
  33. (declare-function magit-am "magit-sequence" ())
  34. (declare-function magit-patch-apply "magit-files" ())
  35. ;; For `magit-discard-files'
  36. (declare-function magit-checkout-stage "magit-merge" (file arg))
  37. (declare-function magit-checkout-read-stage "magit-merge" (file))
  38. (defvar auto-revert-verbose)
  39. ;; For `magit-stage-untracked'
  40. (declare-function magit-submodule-add-1 "magit-submodule"
  41. (url &optional path name args))
  42. (declare-function magit-submodule-read-name-for-path "magit-submodule"
  43. (path &optional prefer-short))
  44. (declare-function borg--maybe-absorb-gitdir "borg" (pkg))
  45. (declare-function borg--sort-submodule-sections "borg" (file))
  46. (defvar borg-user-emacs-directory)
  47. ;;; Options
  48. (defcustom magit-delete-by-moving-to-trash t
  49. "Whether Magit uses the system's trash can.
  50. You should absolutely not disable this and also remove `discard'
  51. from `magit-no-confirm'. You shouldn't do that even if you have
  52. all of the Magit-Wip modes enabled, because those modes do not
  53. track any files that are not tracked in the proper branch."
  54. :package-version '(magit . "2.1.0")
  55. :group 'magit-essentials
  56. :type 'boolean)
  57. (defcustom magit-unstage-committed t
  58. "Whether unstaging a committed change reverts it instead.
  59. A committed change cannot be unstaged, because staging and
  60. unstaging are actions that are concerned with the differences
  61. between the index and the working tree, not with committed
  62. changes.
  63. If this option is non-nil (the default), then typing \"u\"
  64. \(`magit-unstage') on a committed change, causes it to be
  65. reversed in the index but not the working tree. For more
  66. information see command `magit-reverse-in-index'."
  67. :package-version '(magit . "2.4.1")
  68. :group 'magit-commands
  69. :type 'boolean)
  70. (defcustom magit-reverse-atomically nil
  71. "Whether to reverse changes atomically.
  72. If some changes can be reversed while others cannot, then nothing
  73. is reversed if the value of this option is non-nil. But when it
  74. is nil, then the changes that can be reversed are reversed and
  75. for the other changes diff files are created that contain the
  76. rejected reversals."
  77. :package-version '(magit . "2.7.0")
  78. :group 'magit-commands
  79. :type 'boolean)
  80. (defcustom magit-post-stage-hook nil
  81. "Hook run after staging changes.
  82. This hook is run by `magit-refresh' if `this-command'
  83. is a member of `magit-post-stage-hook-commands'."
  84. :package-version '(magit . "2.90.0")
  85. :group 'magit-commands
  86. :type 'hook)
  87. (defvar magit-post-stage-hook-commands
  88. '(magit-stage magit-stage-file magit-stage-modified))
  89. (defcustom magit-post-unstage-hook nil
  90. "Hook run after unstaging changes.
  91. This hook is run by `magit-refresh' if `this-command'
  92. is a member of `magit-post-unstage-hook-commands'."
  93. :package-version '(magit . "2.90.0")
  94. :group 'magit-commands
  95. :type 'hook)
  96. (defvar magit-post-unstage-hook-commands
  97. '(magit-unstage magit-unstage-file magit-unstage-all))
  98. ;;; Commands
  99. ;;;; Apply
  100. (defun magit-apply (&rest args)
  101. "Apply the change at point to the working tree.
  102. With a prefix argument fallback to a 3-way merge. Doing
  103. so causes the change to be applied to the index as well."
  104. (interactive (and current-prefix-arg (list "--3way")))
  105. (--when-let (magit-apply--get-selection)
  106. (pcase (list (magit-diff-type) (magit-diff-scope))
  107. (`(,(or `unstaged `staged) ,_)
  108. (user-error "Change is already in the working tree"))
  109. (`(untracked ,(or `file `files))
  110. (call-interactively 'magit-am))
  111. (`(,_ region) (magit-apply-region it args))
  112. (`(,_ hunk) (magit-apply-hunk it args))
  113. (`(,_ hunks) (magit-apply-hunks it args))
  114. (`(rebase-sequence file)
  115. (call-interactively 'magit-patch-apply))
  116. (`(,_ file) (magit-apply-diff it args))
  117. (`(,_ files) (magit-apply-diffs it args)))))
  118. (defun magit-apply--section-content (section)
  119. (buffer-substring-no-properties (if (magit-hunk-section-p section)
  120. (oref section start)
  121. (oref section content))
  122. (oref section end)))
  123. (defun magit-apply-diffs (sections &rest args)
  124. (setq sections (magit-apply--get-diffs sections))
  125. (magit-apply-patch sections args
  126. (mapconcat
  127. (lambda (s)
  128. (concat (magit-diff-file-header s)
  129. (magit-apply--section-content s)))
  130. sections "")))
  131. (defun magit-apply-diff (section &rest args)
  132. (setq section (car (magit-apply--get-diffs (list section))))
  133. (magit-apply-patch section args
  134. (concat (magit-diff-file-header section)
  135. (magit-apply--section-content section))))
  136. (defun magit-apply--adjust-hunk-new-starts (hunks)
  137. "Adjust new line numbers in headers of HUNKS for partial application.
  138. HUNKS should be a list of ordered, contiguous hunks to be applied
  139. from a file. For example, if there is a sequence of hunks with
  140. the headers
  141. @@ -2,6 +2,7 @@
  142. @@ -10,6 +11,7 @@
  143. @@ -18,6 +20,7 @@
  144. and only the second and third are to be applied, they would be
  145. adjusted as \"@@ -10,6 +10,7 @@\" and \"@@ -18,6 +19,7 @@\"."
  146. (let* ((first-hunk (car hunks))
  147. (offset (if (string-match diff-hunk-header-re-unified first-hunk)
  148. (- (string-to-number (match-string 3 first-hunk))
  149. (string-to-number (match-string 1 first-hunk)))
  150. (error "Hunk does not have expected header"))))
  151. (if (= offset 0)
  152. hunks
  153. (mapcar (lambda (hunk)
  154. (if (string-match diff-hunk-header-re-unified hunk)
  155. (replace-match (number-to-string
  156. (- (string-to-number (match-string 3 hunk))
  157. offset))
  158. t t hunk 3)
  159. (error "Hunk does not have expected header")))
  160. hunks))))
  161. (defun magit-apply--adjust-hunk-new-start (hunk)
  162. (car (magit-apply--adjust-hunk-new-starts (list hunk))))
  163. (defun magit-apply-hunks (sections &rest args)
  164. (let ((section (oref (car sections) parent)))
  165. (when (string-match "^diff --cc" (oref section value))
  166. (user-error "Cannot un-/stage resolution hunks. Stage the whole file"))
  167. (magit-apply-patch
  168. section args
  169. (concat (oref section header)
  170. (mapconcat #'identity
  171. (magit-apply--adjust-hunk-new-starts
  172. (mapcar #'magit-apply--section-content sections))
  173. "")))))
  174. (defun magit-apply-hunk (section &rest args)
  175. (when (string-match "^diff --cc" (magit-section-parent-value section))
  176. (user-error "Cannot un-/stage resolution hunks. Stage the whole file"))
  177. (magit-apply-patch (oref section parent) args
  178. (concat (magit-diff-file-header section)
  179. (magit-apply--adjust-hunk-new-start
  180. (magit-apply--section-content section)))))
  181. (defun magit-apply-region (section &rest args)
  182. (when (string-match "^diff --cc" (magit-section-parent-value section))
  183. (user-error "Cannot un-/stage resolution hunks. Stage the whole file"))
  184. (magit-apply-patch (oref section parent) args
  185. (concat (magit-diff-file-header section)
  186. (magit-apply--adjust-hunk-new-start
  187. (magit-diff-hunk-region-patch section args)))))
  188. (defun magit-apply-patch (section:s args patch)
  189. (let* ((files (if (atom section:s)
  190. (list (oref section:s value))
  191. (--map (oref it value) section:s)))
  192. (command (symbol-name this-command))
  193. (command (if (and command (string-match "^magit-\\([^-]+\\)" command))
  194. (match-string 1 command)
  195. "apply"))
  196. (ignore-context (magit-diff-ignore-any-space-p)))
  197. (unless (magit-diff-context-p)
  198. (user-error "Not enough context to apply patch. Increase the context"))
  199. (when (and magit-wip-before-change-mode (not inhibit-magit-refresh))
  200. (magit-wip-commit-before-change files (concat " before " command)))
  201. (with-temp-buffer
  202. (insert patch)
  203. (magit-run-git-with-input
  204. "apply" args "-p0"
  205. (and ignore-context "-C0")
  206. "--ignore-space-change" "-"))
  207. (unless inhibit-magit-refresh
  208. (when magit-wip-after-apply-mode
  209. (magit-wip-commit-after-apply files (concat " after " command)))
  210. (magit-refresh))))
  211. (defun magit-apply--get-selection ()
  212. (or (magit-region-sections '(hunk file module) t)
  213. (let ((section (magit-current-section)))
  214. (pcase (oref section type)
  215. ((or `hunk `file `module) section)
  216. ((or `staged `unstaged `untracked
  217. `stashed-index `stashed-worktree `stashed-untracked)
  218. (oref section children))
  219. (_ (user-error "Cannot apply this, it's not a change"))))))
  220. (defun magit-apply--get-diffs (sections)
  221. (magit-section-case
  222. ([file diffstat]
  223. (--map (or (magit-get-section
  224. (append `((file . ,(oref it value)))
  225. (magit-section-ident magit-root-section)))
  226. (error "Cannot get required diff headers"))
  227. sections))
  228. (t sections)))
  229. (defun magit-apply--diff-ignores-whitespace-p ()
  230. (and (cl-intersection magit-buffer-diff-args
  231. '("--ignore-space-at-eol"
  232. "--ignore-space-change"
  233. "--ignore-all-space"
  234. "--ignore-blank-lines")
  235. :test #'equal)
  236. t))
  237. ;;;; Stage
  238. (defun magit-stage (&optional intent)
  239. "Add the change at point to the staging area.
  240. With a prefix argument, INTENT, and an untracked file (or files)
  241. at point, stage the file but not its content."
  242. (interactive "P")
  243. (--if-let (and (derived-mode-p 'magit-mode) (magit-apply--get-selection))
  244. (pcase (list (magit-diff-type)
  245. (magit-diff-scope)
  246. (magit-apply--diff-ignores-whitespace-p))
  247. (`(untracked ,_ ,_) (magit-stage-untracked intent))
  248. (`(unstaged region ,_) (magit-apply-region it "--cached"))
  249. (`(unstaged hunk ,_) (magit-apply-hunk it "--cached"))
  250. (`(unstaged hunks ,_) (magit-apply-hunks it "--cached"))
  251. (`(unstaged file t) (magit-apply-diff it "--cached"))
  252. (`(unstaged files t) (magit-apply-diffs it "--cached"))
  253. (`(unstaged list t) (magit-apply-diffs it "--cached"))
  254. (`(unstaged file nil) (magit-stage-1 "-u" (list (oref it value))))
  255. (`(unstaged files nil) (magit-stage-1 "-u" (magit-region-values nil t)))
  256. (`(unstaged list nil) (magit-stage-modified))
  257. (`(staged ,_ ,_) (user-error "Already staged"))
  258. (`(committed ,_ ,_) (user-error "Cannot stage committed changes"))
  259. (`(undefined ,_ ,_) (user-error "Cannot stage this change")))
  260. (call-interactively 'magit-stage-file)))
  261. ;;;###autoload
  262. (defun magit-stage-file (file)
  263. "Stage all changes to FILE.
  264. With a prefix argument or when there is no file at point ask for
  265. the file to be staged. Otherwise stage the file at point without
  266. requiring confirmation."
  267. (interactive
  268. (let* ((atpoint (magit-section-value-if 'file))
  269. (current (magit-file-relative-name))
  270. (choices (nconc (magit-unstaged-files)
  271. (magit-untracked-files)))
  272. (default (car (member (or atpoint current) choices))))
  273. (list (if (or current-prefix-arg (not default))
  274. (magit-completing-read "Stage file" choices
  275. nil t nil nil default)
  276. default))))
  277. (magit-with-toplevel
  278. (magit-stage-1 nil (list file))))
  279. ;;;###autoload
  280. (defun magit-stage-modified (&optional all)
  281. "Stage all changes to files modified in the worktree.
  282. Stage all new content of tracked files and remove tracked files
  283. that no longer exist in the working tree from the index also.
  284. With a prefix argument also stage previously untracked (but not
  285. ignored) files."
  286. (interactive "P")
  287. (when (magit-anything-staged-p)
  288. (magit-confirm 'stage-all-changes))
  289. (magit-with-toplevel
  290. (magit-stage-1 (if all "--all" "-u") magit-buffer-diff-files)))
  291. (defun magit-stage-1 (arg &optional files)
  292. (magit-wip-commit-before-change files " before stage")
  293. (magit-run-git "add" arg (if files (cons "--" files) "."))
  294. (when magit-auto-revert-mode
  295. (mapc #'magit-turn-on-auto-revert-mode-if-desired files))
  296. (magit-wip-commit-after-apply files " after stage"))
  297. (defun magit-stage-untracked (&optional intent)
  298. (let* ((section (magit-current-section))
  299. (files (pcase (magit-diff-scope)
  300. (`file (list (oref section value)))
  301. (`files (magit-region-values nil t))
  302. (`list (magit-untracked-files))))
  303. plain repos)
  304. (dolist (file files)
  305. (if (and (not (file-symlink-p file))
  306. (magit-git-repo-p file t))
  307. (push file repos)
  308. (push file plain)))
  309. (magit-wip-commit-before-change files " before stage")
  310. (when plain
  311. (magit-run-git "add" (and intent "--intent-to-add")
  312. "--" plain)
  313. (when magit-auto-revert-mode
  314. (mapc #'magit-turn-on-auto-revert-mode-if-desired plain)))
  315. (dolist (repo repos)
  316. (save-excursion
  317. (goto-char (oref (magit-get-section
  318. `((file . ,repo) (untracked) (status)))
  319. start))
  320. (let* ((topdir (magit-toplevel))
  321. (package
  322. (and (equal (bound-and-true-p borg-user-emacs-directory)
  323. topdir)
  324. (file-name-nondirectory (directory-file-name repo)))))
  325. (magit-submodule-add-1
  326. (let ((default-directory
  327. (file-name-as-directory (expand-file-name repo))))
  328. (or (magit-get "remote" (magit-get-some-remote) "url")
  329. (concat (file-name-as-directory ".") repo)))
  330. repo
  331. (magit-submodule-read-name-for-path repo package))
  332. (when package
  333. (borg--sort-submodule-sections
  334. (expand-file-name ".gitmodules" topdir))
  335. (let ((default-directory borg-user-emacs-directory))
  336. (borg--maybe-absorb-gitdir package))
  337. (when (and (y-or-n-p
  338. (format "Also build and activate `%s' drone?" package))
  339. (fboundp 'borg-build)
  340. (fboundp 'borg-activate))
  341. (borg-build package)
  342. (borg-activate package))))))
  343. (magit-wip-commit-after-apply files " after stage")))
  344. ;;;; Unstage
  345. (defun magit-unstage ()
  346. "Remove the change at point from the staging area."
  347. (interactive)
  348. (--when-let (magit-apply--get-selection)
  349. (pcase (list (magit-diff-type)
  350. (magit-diff-scope)
  351. (magit-apply--diff-ignores-whitespace-p))
  352. (`(untracked ,_ ,_) (user-error "Cannot unstage untracked changes"))
  353. (`(unstaged file ,_) (magit-unstage-intent (list (oref it value))))
  354. (`(unstaged files ,_) (magit-unstage-intent (magit-region-values nil t)))
  355. (`(unstaged ,_ ,_) (user-error "Already unstaged"))
  356. (`(staged region ,_) (magit-apply-region it "--reverse" "--cached"))
  357. (`(staged hunk ,_) (magit-apply-hunk it "--reverse" "--cached"))
  358. (`(staged hunks ,_) (magit-apply-hunks it "--reverse" "--cached"))
  359. (`(staged file t) (magit-apply-diff it "--reverse" "--cached"))
  360. (`(staged files t) (magit-apply-diffs it "--reverse" "--cached"))
  361. (`(staged list t) (magit-apply-diffs it "--reverse" "--cached"))
  362. (`(staged file nil) (magit-unstage-1 (list (oref it value))))
  363. (`(staged files nil) (magit-unstage-1 (magit-region-values nil t)))
  364. (`(staged list nil) (magit-unstage-all))
  365. (`(committed ,_ ,_) (if magit-unstage-committed
  366. (magit-reverse-in-index)
  367. (user-error "Cannot unstage committed changes")))
  368. (`(undefined ,_ ,_) (user-error "Cannot unstage this change")))))
  369. ;;;###autoload
  370. (defun magit-unstage-file (file)
  371. "Unstage all changes to FILE.
  372. With a prefix argument or when there is no file at point ask for
  373. the file to be unstaged. Otherwise unstage the file at point
  374. without requiring confirmation."
  375. (interactive
  376. (let* ((atpoint (magit-section-value-if 'file))
  377. (current (magit-file-relative-name))
  378. (choices (magit-staged-files))
  379. (default (car (member (or atpoint current) choices))))
  380. (list (if (or current-prefix-arg (not default))
  381. (magit-completing-read "Unstage file" choices
  382. nil t nil nil default)
  383. default))))
  384. (magit-with-toplevel
  385. (magit-unstage-1 (list file))))
  386. (defun magit-unstage-1 (files)
  387. (magit-wip-commit-before-change files " before unstage")
  388. (if (magit-no-commit-p)
  389. (magit-run-git "rm" "--cached" "--" files)
  390. (magit-run-git "reset" "HEAD" "--" files))
  391. (magit-wip-commit-after-apply files " after unstage"))
  392. (defun magit-unstage-intent (files)
  393. (if-let ((staged (magit-staged-files))
  394. (intent (--filter (member it staged) files)))
  395. (magit-unstage-1 intent)
  396. (user-error "Already unstaged")))
  397. ;;;###autoload
  398. (defun magit-unstage-all ()
  399. "Remove all changes from the staging area."
  400. (interactive)
  401. (when (or (magit-anything-unstaged-p)
  402. (magit-untracked-files))
  403. (magit-confirm 'unstage-all-changes))
  404. (magit-wip-commit-before-change nil " before unstage")
  405. (magit-run-git "reset" "HEAD" "--" magit-buffer-diff-files)
  406. (magit-wip-commit-after-apply nil " after unstage"))
  407. ;;;; Discard
  408. (defun magit-discard ()
  409. "Remove the change at point."
  410. (interactive)
  411. (--when-let (magit-apply--get-selection)
  412. (pcase (list (magit-diff-type) (magit-diff-scope))
  413. (`(committed ,_) (user-error "Cannot discard committed changes"))
  414. (`(undefined ,_) (user-error "Cannot discard this change"))
  415. (`(,_ region) (magit-discard-region it))
  416. (`(,_ hunk) (magit-discard-hunk it))
  417. (`(,_ hunks) (magit-discard-hunks it))
  418. (`(,_ file) (magit-discard-file it))
  419. (`(,_ files) (magit-discard-files it))
  420. (`(,_ list) (magit-discard-files it)))))
  421. (defun magit-discard-region (section)
  422. (magit-confirm 'discard "Discard region")
  423. (magit-discard-apply section 'magit-apply-region))
  424. (defun magit-discard-hunk (section)
  425. (magit-confirm 'discard "Discard hunk")
  426. (magit-discard-apply section 'magit-apply-hunk))
  427. (defun magit-discard-apply (section apply)
  428. (if (eq (magit-diff-type section) 'unstaged)
  429. (funcall apply section "--reverse")
  430. (if (magit-anything-unstaged-p
  431. nil (if (magit-file-section-p section)
  432. (oref section value)
  433. (magit-section-parent-value section)))
  434. (progn (let ((inhibit-magit-refresh t))
  435. (funcall apply section "--reverse" "--cached")
  436. (funcall apply section "--reverse" "--reject"))
  437. (magit-refresh))
  438. (funcall apply section "--reverse" "--index"))))
  439. (defun magit-discard-hunks (sections)
  440. (magit-confirm 'discard (format "Discard %s hunks from %s"
  441. (length sections)
  442. (magit-section-parent-value (car sections))))
  443. (magit-discard-apply-n sections 'magit-apply-hunks))
  444. (defun magit-discard-apply-n (sections apply)
  445. (let ((section (car sections)))
  446. (if (eq (magit-diff-type section) 'unstaged)
  447. (funcall apply sections "--reverse")
  448. (if (magit-anything-unstaged-p
  449. nil (if (magit-file-section-p section)
  450. (oref section value)
  451. (magit-section-parent-value section)))
  452. (progn (let ((inhibit-magit-refresh t))
  453. (funcall apply sections "--reverse" "--cached")
  454. (funcall apply sections "--reverse" "--reject"))
  455. (magit-refresh))
  456. (funcall apply sections "--reverse" "--index")))))
  457. (defun magit-discard-file (section)
  458. (magit-discard-files (list section)))
  459. (defun magit-discard-files (sections)
  460. (let ((auto-revert-verbose nil)
  461. (type (magit-diff-type (car sections)))
  462. (status (magit-file-status))
  463. files delete resurrect rename discard discard-new resolve)
  464. (dolist (section sections)
  465. (let ((file (oref section value)))
  466. (push file files)
  467. (pcase (cons (pcase type
  468. (`staged ?X)
  469. (`unstaged ?Y)
  470. (`untracked ?Z))
  471. (cddr (assoc file status)))
  472. (`(?Z) (dolist (f (magit-untracked-files nil file))
  473. (push f delete)))
  474. ((or `(?Z ?? ??) `(?Z ?! ?!)) (push file delete))
  475. ((or `(?Z ?D ? ) `(,_ ?D ?D)) (push file delete))
  476. ((or `(,_ ?U ,_) `(,_ ,_ ?U)) (push file resolve))
  477. (`(,_ ?A ?A) (push file resolve))
  478. (`(?X ?M ,(or ? ?M ?D)) (push section discard))
  479. (`(?Y ,_ ?M ) (push section discard))
  480. (`(?X ?A ?M ) (push file discard-new))
  481. (`(?X ?C ?M ) (push file discard-new))
  482. (`(?X ?A ,(or ? ?D)) (push file delete))
  483. (`(?X ?C ,(or ? ?D)) (push file delete))
  484. (`(?X ?D ,(or ? ?M )) (push file resurrect))
  485. (`(?Y ,_ ?D ) (push file resurrect))
  486. (`(?X ?R ,(or ? ?M ?D)) (push file rename)))))
  487. (unwind-protect
  488. (let ((inhibit-magit-refresh t))
  489. (magit-wip-commit-before-change files " before discard")
  490. (when resolve
  491. (magit-discard-files--resolve (nreverse resolve)))
  492. (when resurrect
  493. (magit-discard-files--resurrect (nreverse resurrect)))
  494. (when delete
  495. (magit-discard-files--delete (nreverse delete) status))
  496. (when rename
  497. (magit-discard-files--rename (nreverse rename) status))
  498. (when (or discard discard-new)
  499. (magit-discard-files--discard (nreverse discard)
  500. (nreverse discard-new)))
  501. (magit-wip-commit-after-apply files " after discard"))
  502. (magit-refresh))))
  503. (defun magit-discard-files--resolve (files)
  504. (if-let ((arg (and (cdr files)
  505. (magit-read-char-case
  506. (format "For these %i files\n%s\ncheckout:\n"
  507. (length files)
  508. (mapconcat (lambda (file)
  509. (concat " " file))
  510. files "\n"))
  511. t
  512. (?o "[o]ur stage" "--ours")
  513. (?t "[t]heir stage" "--theirs")
  514. (?c "[c]onflict" "--merge")
  515. (?i "decide [i]ndividually" nil)))))
  516. (dolist (file files)
  517. (magit-checkout-stage file arg))
  518. (dolist (file files)
  519. (magit-checkout-stage file (magit-checkout-read-stage file)))))
  520. (defun magit-discard-files--resurrect (files)
  521. (magit-confirm-files 'resurrect files)
  522. (if (eq (magit-diff-type) 'staged)
  523. (magit-call-git "reset" "--" files)
  524. (magit-call-git "checkout" "--" files)))
  525. (defun magit-discard-files--delete (files status)
  526. (magit-confirm-files (if magit-delete-by-moving-to-trash 'trash 'delete)
  527. files)
  528. (let ((delete-by-moving-to-trash magit-delete-by-moving-to-trash))
  529. (dolist (file files)
  530. (when (string-match-p "\\`\\\\?~" file)
  531. (error "Refusing to delete %S, too dangerous" file))
  532. (pcase (nth 3 (assoc file status))
  533. ((guard (memq (magit-diff-type) '(unstaged untracked)))
  534. (dired-delete-file file dired-recursive-deletes
  535. magit-delete-by-moving-to-trash)
  536. (dired-clean-up-after-deletion file))
  537. (?\s (delete-file file t)
  538. (magit-call-git "rm" "--cached" "--" file))
  539. (?M (let ((temp (magit-git-string "checkout-index" "--temp" file)))
  540. (string-match
  541. (format "\\(.+?\\)\t%s" (regexp-quote file)) temp)
  542. (rename-file (match-string 1 temp)
  543. (setq temp (concat file ".~{index}~")))
  544. (delete-file temp t))
  545. (magit-call-git "rm" "--cached" "--force" "--" file))
  546. (?D (magit-call-git "checkout" "--" file)
  547. (delete-file file t)
  548. (magit-call-git "rm" "--cached" "--force" "--" file))))))
  549. (defun magit-discard-files--rename (files status)
  550. (magit-confirm 'rename "Undo rename %s" "Undo %i renames" nil
  551. (mapcar (lambda (file)
  552. (setq file (assoc file status))
  553. (format "%s -> %s" (cadr file) (car file)))
  554. files))
  555. (dolist (file files)
  556. (let ((orig (cadr (assoc file status))))
  557. (if (file-exists-p file)
  558. (progn
  559. (--when-let (file-name-directory orig)
  560. (make-directory it t))
  561. (magit-call-git "mv" file orig))
  562. (magit-call-git "rm" "--cached" "--" file)
  563. (magit-call-git "reset" "--" orig)))))
  564. (defun magit-discard-files--discard (sections new-files)
  565. (let ((files (--map (oref it value) sections)))
  566. (magit-confirm-files 'discard (append files new-files)
  567. (format "Discard %s changes in" (magit-diff-type)))
  568. (if (eq (magit-diff-type (car sections)) 'unstaged)
  569. (magit-call-git "checkout" "--" files)
  570. (when new-files
  571. (magit-call-git "add" "--" new-files)
  572. (magit-call-git "reset" "--" new-files))
  573. (let ((binaries (magit-binary-files "--cached")))
  574. (when binaries
  575. (setq sections
  576. (--remove (member (oref it value) binaries)
  577. sections)))
  578. (cond ((= (length sections) 1)
  579. (magit-discard-apply (car sections) 'magit-apply-diff))
  580. (sections
  581. (magit-discard-apply-n sections 'magit-apply-diffs)))
  582. (when binaries
  583. (let ((modified (magit-unstaged-files t)))
  584. (setq binaries (--separate (member it modified) binaries)))
  585. (when (cadr binaries)
  586. (magit-call-git "reset" "--" (cadr binaries)))
  587. (when (car binaries)
  588. (user-error
  589. (concat
  590. "Cannot discard staged changes to binary files, "
  591. "which also have unstaged changes. Unstage instead."))))))))
  592. ;;;; Reverse
  593. (defun magit-reverse (&rest args)
  594. "Reverse the change at point in the working tree.
  595. With a prefix argument fallback to a 3-way merge. Doing
  596. so causes the change to be applied to the index as well."
  597. (interactive (and current-prefix-arg (list "--3way")))
  598. (--when-let (magit-apply--get-selection)
  599. (pcase (list (magit-diff-type) (magit-diff-scope))
  600. (`(untracked ,_) (user-error "Cannot reverse untracked changes"))
  601. (`(unstaged ,_) (user-error "Cannot reverse unstaged changes"))
  602. (`(,_ region) (magit-reverse-region it args))
  603. (`(,_ hunk) (magit-reverse-hunk it args))
  604. (`(,_ hunks) (magit-reverse-hunks it args))
  605. (`(,_ file) (magit-reverse-file it args))
  606. (`(,_ files) (magit-reverse-files it args))
  607. (`(,_ list) (magit-reverse-files it args)))))
  608. (defun magit-reverse-region (section args)
  609. (magit-confirm 'reverse "Reverse region")
  610. (magit-reverse-apply section 'magit-apply-region args))
  611. (defun magit-reverse-hunk (section args)
  612. (magit-confirm 'reverse "Reverse hunk")
  613. (magit-reverse-apply section 'magit-apply-hunk args))
  614. (defun magit-reverse-hunks (sections args)
  615. (magit-confirm 'reverse
  616. (format "Reverse %s hunks from %s"
  617. (length sections)
  618. (magit-section-parent-value (car sections))))
  619. (magit-reverse-apply sections 'magit-apply-hunks args))
  620. (defun magit-reverse-file (section args)
  621. (magit-reverse-files (list section) args))
  622. (defun magit-reverse-files (sections args)
  623. (pcase-let ((`(,binaries ,sections)
  624. (let ((bs (magit-binary-files
  625. (cond ((derived-mode-p 'magit-revision-mode)
  626. magit-buffer-range)
  627. ((derived-mode-p 'magit-diff-mode)
  628. magit-buffer-range)
  629. (t
  630. "--cached")))))
  631. (--separate (member (oref it value) bs)
  632. sections))))
  633. (magit-confirm-files 'reverse (--map (oref it value) sections))
  634. (cond ((= (length sections) 1)
  635. (magit-reverse-apply (car sections) 'magit-apply-diff args))
  636. (sections
  637. (magit-reverse-apply sections 'magit-apply-diffs args)))
  638. (when binaries
  639. (user-error "Cannot reverse binary files"))))
  640. (defun magit-reverse-apply (section:s apply args)
  641. (funcall apply section:s "--reverse" args
  642. (and (not magit-reverse-atomically)
  643. (not (member "--3way" args))
  644. "--reject")))
  645. (defun magit-reverse-in-index (&rest args)
  646. "Reverse the change at point in the index but not the working tree.
  647. Use this command to extract a change from `HEAD', while leaving
  648. it in the working tree, so that it can later be committed using
  649. a separate commit. A typical workflow would be:
  650. 0. Optionally make sure that there are no uncommitted changes.
  651. 1. Visit the `HEAD' commit and navigate to the change that should
  652. not have been included in that commit.
  653. 2. Type \"u\" (`magit-unstage') to reverse it in the index.
  654. This assumes that `magit-unstage-committed-changes' is non-nil.
  655. 3. Type \"c e\" to extend `HEAD' with the staged changes,
  656. including those that were already staged before.
  657. 4. Optionally stage the remaining changes using \"s\" or \"S\"
  658. and then type \"c c\" to create a new commit."
  659. (interactive)
  660. (magit-reverse (cons "--cached" args)))
  661. ;;; _
  662. (provide 'magit-apply)
  663. ;;; magit-apply.el ends here