Klimi's new dotfiles with stow.
Vous ne pouvez pas sélectionner plus de 25 sujets Les noms de sujets doivent commencer par une lettre ou un nombre, peuvent contenir des tirets ('-') et peuvent comporter jusqu'à 35 caractères.

1029 lignes
42 KiB

il y a 4 ans
  1. ;;; magit-sequence.el --- history manipulation in Magit -*- lexical-binding: t -*-
  2. ;; Copyright (C) 2011-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. ;; Support for Git commands that replay commits and help the user make
  22. ;; changes along the way. Supports `cherry-pick', `revert', `rebase',
  23. ;; `rebase--interactive' and `am'.
  24. ;;; Code:
  25. (eval-when-compile
  26. (require 'subr-x))
  27. (require 'magit)
  28. ;; For `magit-rebase--todo'.
  29. (declare-function git-rebase-current-line "git-rebase" ())
  30. (eval-when-compile
  31. (cl-pushnew 'action-type eieio--known-slot-names)
  32. (cl-pushnew 'action eieio--known-slot-names)
  33. (cl-pushnew 'action-options eieio--known-slot-names)
  34. (cl-pushnew 'target eieio--known-slot-names))
  35. ;;; Options
  36. ;;;; Faces
  37. (defface magit-sequence-pick
  38. '((t :inherit default))
  39. "Face used in sequence sections."
  40. :group 'magit-faces)
  41. (defface magit-sequence-stop
  42. '((((class color) (background light)) :foreground "DarkOliveGreen4")
  43. (((class color) (background dark)) :foreground "DarkSeaGreen2"))
  44. "Face used in sequence sections."
  45. :group 'magit-faces)
  46. (defface magit-sequence-part
  47. '((((class color) (background light)) :foreground "Goldenrod4")
  48. (((class color) (background dark)) :foreground "LightGoldenrod2"))
  49. "Face used in sequence sections."
  50. :group 'magit-faces)
  51. (defface magit-sequence-head
  52. '((((class color) (background light)) :foreground "SkyBlue4")
  53. (((class color) (background dark)) :foreground "LightSkyBlue1"))
  54. "Face used in sequence sections."
  55. :group 'magit-faces)
  56. (defface magit-sequence-drop
  57. '((((class color) (background light)) :foreground "IndianRed")
  58. (((class color) (background dark)) :foreground "IndianRed"))
  59. "Face used in sequence sections."
  60. :group 'magit-faces)
  61. (defface magit-sequence-done
  62. '((t :inherit magit-hash))
  63. "Face used in sequence sections."
  64. :group 'magit-faces)
  65. (defface magit-sequence-onto
  66. '((t :inherit magit-sequence-done))
  67. "Face used in sequence sections."
  68. :group 'magit-faces)
  69. (defface magit-sequence-exec
  70. '((t :inherit magit-hash))
  71. "Face used in sequence sections."
  72. :group 'magit-faces)
  73. ;;; Common
  74. ;;;###autoload
  75. (defun magit-sequencer-continue ()
  76. "Resume the current cherry-pick or revert sequence."
  77. (interactive)
  78. (if (magit-sequencer-in-progress-p)
  79. (if (magit-anything-unstaged-p t)
  80. (user-error "Cannot continue due to unstaged changes")
  81. (magit-run-git-sequencer
  82. (if (magit-revert-in-progress-p) "revert" "cherry-pick") "--continue"))
  83. (user-error "No cherry-pick or revert in progress")))
  84. ;;;###autoload
  85. (defun magit-sequencer-skip ()
  86. "Skip the stopped at commit during a cherry-pick or revert sequence."
  87. (interactive)
  88. (if (magit-sequencer-in-progress-p)
  89. (progn (magit-call-git "reset" "--hard")
  90. (magit-sequencer-continue))
  91. (user-error "No cherry-pick or revert in progress")))
  92. ;;;###autoload
  93. (defun magit-sequencer-abort ()
  94. "Abort the current cherry-pick or revert sequence.
  95. This discards all changes made since the sequence started."
  96. (interactive)
  97. (if (magit-sequencer-in-progress-p)
  98. (magit-run-git-sequencer
  99. (if (magit-revert-in-progress-p) "revert" "cherry-pick") "--abort")
  100. (user-error "No cherry-pick or revert in progress")))
  101. (defun magit-sequencer-in-progress-p ()
  102. (or (magit-cherry-pick-in-progress-p)
  103. (magit-revert-in-progress-p)))
  104. ;;; Cherry-Pick
  105. (defvar magit-perl-executable "perl"
  106. "The Perl executable.")
  107. ;;;###autoload (autoload 'magit-cherry-pick "magit-sequence" nil t)
  108. (define-transient-command magit-cherry-pick ()
  109. "Apply or transplant commits."
  110. :man-page "git-cherry-pick"
  111. :value '("--ff")
  112. :incompatible '(("--ff" "-x"))
  113. ["Arguments"
  114. :if-not magit-sequencer-in-progress-p
  115. (magit-cherry-pick:--mainline)
  116. ("=s" magit-merge:--strategy)
  117. ("-F" "Attempt fast-forward" "--ff")
  118. ("-x" "Reference cherry in commit message" "-x")
  119. ("-e" "Edit commit messages" ("-e" "--edit"))
  120. ("-s" "Add Signed-off-by lines" ("-s" "--signoff"))
  121. (5 magit:--gpg-sign)]
  122. [:if-not magit-sequencer-in-progress-p
  123. ["Apply here"
  124. ("A" "Pick" magit-cherry-copy)
  125. ("a" "Apply" magit-cherry-apply)
  126. ("h" "Harvest" magit-cherry-harvest)]
  127. ["Apply elsewhere"
  128. ("d" "Donate" magit-cherry-donate)
  129. ("n" "Spinout" magit-cherry-spinout)
  130. ("s" "Spinoff" magit-cherry-spinoff)]]
  131. ["Actions"
  132. :if magit-sequencer-in-progress-p
  133. ("A" "Continue" magit-sequencer-continue)
  134. ("s" "Skip" magit-sequencer-skip)
  135. ("a" "Abort" magit-sequencer-abort)])
  136. (define-infix-argument magit-cherry-pick:--mainline ()
  137. :description "Replay merge relative to parent"
  138. :class 'transient-option
  139. :shortarg "-m"
  140. :argument "--mainline="
  141. :reader 'transient-read-number-N+)
  142. (defun magit-cherry-pick-read-args (prompt)
  143. (list (or (nreverse (magit-region-values 'commit))
  144. (magit-read-other-branch-or-commit prompt))
  145. (transient-args 'magit-cherry-pick)))
  146. (defun magit--cherry-move-read-args (verb away fn)
  147. (declare (indent defun))
  148. (let ((commits (or (nreverse (magit-region-values 'commit))
  149. (list (funcall (if away
  150. 'magit-read-branch-or-commit
  151. 'magit-read-other-branch-or-commit)
  152. (format "%s cherry" (capitalize verb))))))
  153. (current (magit-get-current-branch)))
  154. (unless current
  155. (user-error "Cannot %s cherries while HEAD is detached" verb))
  156. (let ((reachable (magit-rev-ancestor-p (car commits) current))
  157. (msg "Cannot %s cherries that %s reachable from HEAD"))
  158. (pcase (list away reachable)
  159. (`(nil t) (user-error msg verb "are"))
  160. (`(t nil) (user-error msg verb "are not"))))
  161. `(,commits
  162. ,@(funcall fn commits)
  163. ,(transient-args 'magit-cherry-pick))))
  164. (defun magit--cherry-spinoff-read-args (verb)
  165. (magit--cherry-move-read-args verb t
  166. (lambda (commits)
  167. (magit-branch-read-args
  168. (format "Create branch from %s cherries" (length commits))
  169. (magit-get-upstream-branch)))))
  170. ;;;###autoload
  171. (defun magit-cherry-copy (commits &optional args)
  172. "Copy COMMITS from another branch onto the current branch.
  173. Prompt for a commit, defaulting to the commit at point. If
  174. the region selects multiple commits, then pick all of them,
  175. without prompting."
  176. (interactive (magit-cherry-pick-read-args "Cherry-pick"))
  177. (magit--cherry-pick commits args))
  178. ;;;###autoload
  179. (defun magit-cherry-apply (commits &optional args)
  180. "Apply the changes in COMMITS but do not commit them.
  181. Prompt for a commit, defaulting to the commit at point. If
  182. the region selects multiple commits, then apply all of them,
  183. without prompting."
  184. (interactive (magit-cherry-pick-read-args "Apply changes from commit"))
  185. (magit--cherry-pick commits (cons "--no-commit" (remove "--ff" args))))
  186. ;;;###autoload
  187. (defun magit-cherry-harvest (commits branch &optional args)
  188. "Move COMMITS from another BRANCH onto the current branch.
  189. Remove the COMMITS from BRANCH and stay on the current branch.
  190. If a conflict occurs, then you have to fix that and finish the
  191. process manually."
  192. (interactive
  193. (magit--cherry-move-read-args "harvest" nil
  194. (lambda (commits)
  195. (list (let ((branches (magit-list-containing-branches (car commits))))
  196. (pcase (length branches)
  197. (0 nil)
  198. (1 (car branches))
  199. (_ (magit-completing-read
  200. (format "Remove %s cherries from branch" (length commits))
  201. branches nil t))))))))
  202. (magit--cherry-move commits branch (magit-get-current-branch) args nil t))
  203. ;;;###autoload
  204. (defun magit-cherry-donate (commits branch &optional args)
  205. "Move COMMITS from the current branch onto another existing BRANCH.
  206. Remove COMMITS from the current branch and stay on that branch.
  207. If a conflict occurs, then you have to fix that and finish the
  208. process manually."
  209. (interactive
  210. (magit--cherry-move-read-args "donate" t
  211. (lambda (commits)
  212. (list (magit-read-other-branch (format "Move %s cherries to branch"
  213. (length commits)))))))
  214. (magit--cherry-move commits (magit-get-current-branch) branch args))
  215. ;;;###autoload
  216. (defun magit-cherry-spinout (commits branch start-point &optional args)
  217. "Move COMMITS from the current branch onto a new BRANCH.
  218. Remove COMMITS from the current branch and stay on that branch.
  219. If a conflict occurs, then you have to fix that and finish the
  220. process manually."
  221. (interactive (magit--cherry-spinoff-read-args "spinout"))
  222. (magit--cherry-move commits (magit-get-current-branch) branch args
  223. start-point))
  224. ;;;###autoload
  225. (defun magit-cherry-spinoff (commits branch start-point &optional args)
  226. "Move COMMITS from the current branch onto a new BRANCH.
  227. Remove COMMITS from the current branch and checkout BRANCH.
  228. If a conflict occurs, then you have to fix that and finish
  229. the process manually."
  230. (interactive (magit--cherry-spinoff-read-args "spinoff"))
  231. (magit--cherry-move commits (magit-get-current-branch) branch args
  232. start-point t))
  233. (defun magit--cherry-move (commits src dst args
  234. &optional start-point checkout-dst)
  235. (let ((current (magit-get-current-branch)))
  236. (unless (magit-branch-p dst)
  237. (let ((magit-process-raise-error t))
  238. (magit-call-git "branch" dst start-point))
  239. (--when-let (magit-get-indirect-upstream-branch start-point)
  240. (magit-call-git "branch" "--set-upstream-to" it dst)))
  241. (unless (equal dst current)
  242. (let ((magit-process-raise-error t))
  243. (magit-call-git "checkout" dst)))
  244. (if (not src) ; harvest only
  245. (magit--cherry-pick commits args)
  246. (let ((tip (car (last commits)))
  247. (keep (concat (car commits) "^")))
  248. (magit--cherry-pick commits args)
  249. (set-process-sentinel
  250. magit-this-process
  251. (lambda (process event)
  252. (when (memq (process-status process) '(exit signal))
  253. (if (> (process-exit-status process) 0)
  254. (magit-process-sentinel process event)
  255. (process-put process 'inhibit-refresh t)
  256. (magit-process-sentinel process event)
  257. (cond
  258. ((magit-rev-equal tip src)
  259. (magit-call-git "update-ref"
  260. "-m" (format "reset: moving to %s" keep)
  261. (magit-ref-fullname src)
  262. keep tip)
  263. (if (not checkout-dst)
  264. (magit-run-git "checkout" src)
  265. (magit-refresh)))
  266. (t
  267. (magit-git "checkout" src)
  268. (let ((process-environment process-environment))
  269. (push (format "%s=%s -i -ne '/^pick (%s)/ or print'"
  270. "GIT_SEQUENCE_EDITOR"
  271. magit-perl-executable
  272. (mapconcat #'magit-rev-abbrev commits "|"))
  273. process-environment)
  274. (magit-run-git-sequencer "rebase" "-i" keep))
  275. (when checkout-dst
  276. (set-process-sentinel
  277. magit-this-process
  278. (lambda (process event)
  279. (when (memq (process-status process) '(exit signal))
  280. (if (> (process-exit-status process) 0)
  281. (magit-process-sentinel process event)
  282. (process-put process 'inhibit-refresh t)
  283. (magit-process-sentinel process event)
  284. (magit-run-git "checkout" dst))))))))))))))))
  285. (defun magit--cherry-pick (commits args &optional revert)
  286. (let ((command (if revert "revert" "cherry-pick")))
  287. (when (stringp commits)
  288. (setq commits (if (string-match-p "\\.\\." commits)
  289. (split-string commits "\\.\\.")
  290. (list commits))))
  291. (magit-run-git-sequencer
  292. (if revert "revert" "cherry-pick")
  293. (pcase-let ((`(,merge ,non-merge)
  294. (-separate 'magit-merge-commit-p commits)))
  295. (cond
  296. ((not merge)
  297. (--remove (string-prefix-p "--mainline=" it) args))
  298. (non-merge
  299. (user-error "Cannot %s merge and non-merge commits at once"
  300. command))
  301. ((--first (string-prefix-p "--mainline=" it) args)
  302. args)
  303. (t
  304. (cons (format "--mainline=%s"
  305. (read-number "Replay merges relative to parent: "))
  306. args))))
  307. commits)))
  308. (defun magit-cherry-pick-in-progress-p ()
  309. ;; .git/sequencer/todo does not exist when there is only one commit left.
  310. (file-exists-p (magit-git-dir "CHERRY_PICK_HEAD")))
  311. ;;; Revert
  312. ;;;###autoload (autoload 'magit-revert "magit-sequence" nil t)
  313. (define-transient-command magit-revert ()
  314. "Revert existing commits, with or without creating new commits."
  315. :man-page "git-revert"
  316. :value '("--edit")
  317. ["Arguments"
  318. :if-not magit-sequencer-in-progress-p
  319. (magit-cherry-pick:--mainline)
  320. ("-e" "Edit commit message" ("-e" "--edit"))
  321. ("-E" "Don't edit commit message" "--no-edit")
  322. ("=s" magit-merge:--strategy)
  323. ("-s" "Add Signed-off-by lines" ("-s" "--signoff"))
  324. (5 magit:--gpg-sign)]
  325. ["Actions"
  326. :if-not magit-sequencer-in-progress-p
  327. ("V" "Revert commit(s)" magit-revert-and-commit)
  328. ("v" "Revert changes" magit-revert-no-commit)]
  329. ["Actions"
  330. :if magit-sequencer-in-progress-p
  331. ("V" "Continue" magit-sequencer-continue)
  332. ("s" "Skip" magit-sequencer-skip)
  333. ("a" "Abort" magit-sequencer-abort)])
  334. (defun magit-revert-read-args (prompt)
  335. (list (or (magit-region-values 'commit)
  336. (magit-read-branch-or-commit prompt))
  337. (transient-args 'magit-revert)))
  338. ;;;###autoload
  339. (defun magit-revert-and-commit (commit &optional args)
  340. "Revert COMMIT by creating a new commit.
  341. Prompt for a commit, defaulting to the commit at point. If
  342. the region selects multiple commits, then revert all of them,
  343. without prompting."
  344. (interactive (magit-revert-read-args "Revert commit"))
  345. (magit--cherry-pick commit args t))
  346. ;;;###autoload
  347. (defun magit-revert-no-commit (commit &optional args)
  348. "Revert COMMIT by applying it in reverse to the worktree.
  349. Prompt for a commit, defaulting to the commit at point. If
  350. the region selects multiple commits, then revert all of them,
  351. without prompting."
  352. (interactive (magit-revert-read-args "Revert changes"))
  353. (magit--cherry-pick commit (cons "--no-commit" args) t))
  354. (defun magit-revert-in-progress-p ()
  355. ;; .git/sequencer/todo does not exist when there is only one commit left.
  356. (file-exists-p (magit-git-dir "REVERT_HEAD")))
  357. ;;; Patch
  358. ;;;###autoload (autoload 'magit-am "magit-sequence" nil t)
  359. (define-transient-command magit-am ()
  360. "Apply patches received by email."
  361. :man-page "git-am"
  362. :value '("--3way")
  363. ["Arguments"
  364. :if-not magit-am-in-progress-p
  365. ("-3" "Fall back on 3way merge" ("-3" "--3way"))
  366. (magit-apply:-p)
  367. ("-c" "Remove text before scissors line" ("-c" "--scissors"))
  368. ("-k" "Inhibit removal of email cruft" ("-k" "--keep"))
  369. ("-b" "Limit removal of email cruft" "--keep-non-patch")
  370. ("-d" "Use author date as committer date" "--committer-date-is-author-date")
  371. ("-D" "Use committer date as author date" "--ignore-date")
  372. ("-s" "Add Signed-off-by lines" ("-s" "--signoff"))
  373. (5 magit:--gpg-sign)]
  374. ["Apply"
  375. :if-not magit-am-in-progress-p
  376. ("m" "maildir" magit-am-apply-maildir)
  377. ("w" "patches" magit-am-apply-patches)
  378. ("a" "plain patch" magit-patch-apply)]
  379. ["Actions"
  380. :if magit-am-in-progress-p
  381. ("w" "Continue" magit-am-continue)
  382. ("s" "Skip" magit-am-skip)
  383. ("a" "Abort" magit-am-abort)])
  384. (defun magit-am-arguments ()
  385. (transient-args 'magit-am))
  386. (define-infix-argument magit-apply:-p ()
  387. :description "Remove leading slashes from paths"
  388. :class 'transient-option
  389. :argument "-p"
  390. :reader 'transient-read-number-N+)
  391. ;;;###autoload
  392. (defun magit-am-apply-patches (&optional files args)
  393. "Apply the patches FILES."
  394. (interactive (list (or (magit-region-values 'file)
  395. (list (let ((default (magit-file-at-point)))
  396. (read-file-name
  397. (if default
  398. (format "Apply patch (%s): " default)
  399. "Apply patch: ")
  400. nil default))))
  401. (magit-am-arguments)))
  402. (magit-run-git-sequencer "am" args "--"
  403. (--map (magit-convert-filename-for-git
  404. (expand-file-name it))
  405. files)))
  406. ;;;###autoload
  407. (defun magit-am-apply-maildir (&optional maildir args)
  408. "Apply the patches from MAILDIR."
  409. (interactive (list (read-file-name "Apply mbox or Maildir: ")
  410. (magit-am-arguments)))
  411. (magit-run-git-sequencer "am" args (magit-convert-filename-for-git
  412. (expand-file-name maildir))))
  413. ;;;###autoload
  414. (defun magit-am-continue ()
  415. "Resume the current patch applying sequence."
  416. (interactive)
  417. (if (magit-am-in-progress-p)
  418. (if (magit-anything-unstaged-p t)
  419. (error "Cannot continue due to unstaged changes")
  420. (magit-run-git-sequencer "am" "--continue"))
  421. (user-error "Not applying any patches")))
  422. ;;;###autoload
  423. (defun magit-am-skip ()
  424. "Skip the stopped at patch during a patch applying sequence."
  425. (interactive)
  426. (if (magit-am-in-progress-p)
  427. (magit-run-git-sequencer "am" "--skip")
  428. (user-error "Not applying any patches")))
  429. ;;;###autoload
  430. (defun magit-am-abort ()
  431. "Abort the current patch applying sequence.
  432. This discards all changes made since the sequence started."
  433. (interactive)
  434. (if (magit-am-in-progress-p)
  435. (magit-run-git "am" "--abort")
  436. (user-error "Not applying any patches")))
  437. (defun magit-am-in-progress-p ()
  438. (file-exists-p (magit-git-dir "rebase-apply/applying")))
  439. ;;; Rebase
  440. ;;;###autoload (autoload 'magit-rebase "magit-sequence" nil t)
  441. (define-transient-command magit-rebase ()
  442. "Transplant commits and/or modify existing commits."
  443. :man-page "git-rebase"
  444. ["Arguments"
  445. :if-not magit-rebase-in-progress-p
  446. ("-k" "Keep empty commits" "--keep-empty")
  447. ("-p" "Preserve merges" ("-p" "--preserve-merges"))
  448. ("-d" "Lie about committer date" "--committer-date-is-author-date")
  449. ("-a" "Autosquash" "--autosquash")
  450. ("-A" "Autostash" "--autostash")
  451. ("-i" "Interactive" ("-i" "--interactive"))
  452. ("-h" "Disable hooks" "--no-verify")
  453. (5 magit:--gpg-sign)
  454. (5 "-r" "Rebase merges" "--rebase-merges=" magit-rebase-merges-select-mode)]
  455. [:if-not magit-rebase-in-progress-p
  456. :description (lambda ()
  457. (format (propertize "Rebase %s onto" 'face 'transient-heading)
  458. (propertize (or (magit-get-current-branch) "HEAD")
  459. 'face 'magit-branch-local)))
  460. ("p" magit-rebase-onto-pushremote)
  461. ("u" magit-rebase-onto-upstream)
  462. ("e" "elsewhere" magit-rebase-branch)]
  463. ["Rebase"
  464. :if-not magit-rebase-in-progress-p
  465. [("i" "interactively" magit-rebase-interactive)
  466. ("s" "a subset" magit-rebase-subset)]
  467. [("m" "to modify a commit" magit-rebase-edit-commit)
  468. ("w" "to reword a commit" magit-rebase-reword-commit)
  469. ("k" "to remove a commit" magit-rebase-remove-commit)
  470. ("f" "to autosquash" magit-rebase-autosquash)
  471. (6 "t" "to change dates" magit-reshelve-since)]]
  472. ["Actions"
  473. :if magit-rebase-in-progress-p
  474. ("r" "Continue" magit-rebase-continue)
  475. ("s" "Skip" magit-rebase-skip)
  476. ("e" "Edit" magit-rebase-edit)
  477. ("a" "Abort" magit-rebase-abort)])
  478. (defun magit-rebase-merges-select-mode (&rest _ignore)
  479. (magit-read-char-case nil t
  480. (?n "[n]o-rebase-cousins" "no-rebase-cousins")
  481. (?r "[r]ebase-cousins" "rebase-cousins")))
  482. (defun magit-rebase-arguments ()
  483. (transient-args 'magit-rebase))
  484. (defun magit-git-rebase (target args)
  485. (magit-run-git-sequencer "rebase" args target))
  486. ;;;###autoload (autoload 'magit-rebase-onto-pushremote "magit-sequence" nil t)
  487. (define-suffix-command magit-rebase-onto-pushremote (args)
  488. "Rebase the current branch onto its push-remote branch.
  489. When the push-remote is not configured, then read the push-remote
  490. from the user, set it, and then rebase onto it. With a prefix
  491. argument the push-remote can be changed before rebasing onto to
  492. it."
  493. :if 'magit-get-current-branch
  494. :description 'magit-pull--pushbranch-description
  495. (interactive (list (magit-rebase-arguments)))
  496. (pcase-let ((`(,branch ,remote)
  497. (magit--select-push-remote "rebase onto that")))
  498. (magit-git-rebase (concat remote "/" branch) args)))
  499. ;;;###autoload (autoload 'magit-rebase-onto-upstream "magit-sequence" nil t)
  500. (define-suffix-command magit-rebase-onto-upstream (args)
  501. "Rebase the current branch onto its upstream branch.
  502. With a prefix argument or when the upstream is either not
  503. configured or unusable, then let the user first configure
  504. the upstream."
  505. :if 'magit-get-current-branch
  506. :description 'magit-rebase--upstream-description
  507. (interactive (list (magit-rebase-arguments)))
  508. (let* ((branch (or (magit-get-current-branch)
  509. (user-error "No branch is checked out")))
  510. (upstream (magit-get-upstream-branch branch)))
  511. (when (or current-prefix-arg (not upstream))
  512. (setq upstream
  513. (magit-read-upstream-branch
  514. branch (format "Set upstream of %s and rebase onto that" branch)))
  515. (magit-set-upstream-branch branch upstream))
  516. (magit-git-rebase upstream args)))
  517. (defun magit-rebase--upstream-description ()
  518. (when-let ((branch (magit-get-current-branch)))
  519. (or (magit-get-upstream-branch branch)
  520. (let ((remote (magit-get "branch" branch "remote"))
  521. (merge (magit-get "branch" branch "merge"))
  522. (u (magit--propertize-face "@{upstream}" 'bold)))
  523. (cond
  524. ((magit--unnamed-upstream-p remote merge)
  525. (concat u ", replacing unnamed"))
  526. ((magit--valid-upstream-p remote merge)
  527. (concat u ", replacing non-existent"))
  528. ((or remote merge)
  529. (concat u ", replacing invalid"))
  530. (t
  531. (concat u ", setting that")))))))
  532. ;;;###autoload
  533. (defun magit-rebase-branch (target args)
  534. "Rebase the current branch onto a branch read in the minibuffer.
  535. All commits that are reachable from `HEAD' but not from the
  536. selected branch TARGET are being rebased."
  537. (interactive (list (magit-read-other-branch-or-commit "Rebase onto")
  538. (magit-rebase-arguments)))
  539. (message "Rebasing...")
  540. (magit-git-rebase target args)
  541. (message "Rebasing...done"))
  542. ;;;###autoload
  543. (defun magit-rebase-subset (newbase start args)
  544. "Rebase a subset of the current branch's history onto a new base.
  545. Rebase commits from START to `HEAD' onto NEWBASE.
  546. START has to be selected from a list of recent commits."
  547. (interactive (list (magit-read-other-branch-or-commit
  548. "Rebase subset onto" nil
  549. (magit-get-upstream-branch))
  550. nil
  551. (magit-rebase-arguments)))
  552. (if start
  553. (progn (message "Rebasing...")
  554. (magit-run-git-sequencer "rebase" "--onto" newbase start args)
  555. (message "Rebasing...done"))
  556. (magit-log-select
  557. `(lambda (commit)
  558. (magit-rebase-subset ,newbase (concat commit "^") (list ,@args)))
  559. (concat "Type %p on a commit to rebase it "
  560. "and commits above it onto " newbase ","))))
  561. (defun magit-rebase-interactive-1
  562. (commit args message &optional editor delay-edit-confirm noassert confirm)
  563. (declare (indent 2))
  564. (when commit
  565. (if (eq commit :merge-base)
  566. (setq commit (--if-let (magit-get-upstream-branch)
  567. (magit-git-string "merge-base" it "HEAD")
  568. nil))
  569. (unless (magit-rev-ancestor-p commit "HEAD")
  570. (user-error "%s isn't an ancestor of HEAD" commit))
  571. (if (magit-commit-parents commit)
  572. (setq commit (concat commit "^"))
  573. (setq args (cons "--root" args)))))
  574. (when (and commit (not noassert))
  575. (setq commit (magit-rebase-interactive-assert
  576. commit delay-edit-confirm
  577. (--some (string-prefix-p "--rebase-merges" it) args))))
  578. (if (and commit (not confirm))
  579. (let ((process-environment process-environment))
  580. (when editor
  581. (push (concat "GIT_SEQUENCE_EDITOR="
  582. (if (functionp editor)
  583. (funcall editor commit)
  584. editor))
  585. process-environment))
  586. (magit-run-git-sequencer "rebase" "-i" args
  587. (unless (member "--root" args) commit)))
  588. (magit-log-select
  589. `(lambda (commit)
  590. (magit-rebase-interactive-1 commit (list ,@args)
  591. ,message ,editor ,delay-edit-confirm ,noassert))
  592. message)))
  593. (defvar magit--rebase-published-symbol nil)
  594. (defvar magit--rebase-public-edit-confirmed nil)
  595. (defun magit-rebase-interactive-assert
  596. (since &optional delay-edit-confirm rebase-merges)
  597. (let* ((commit (magit-rebase--target-commit since))
  598. (branches (magit-list-publishing-branches commit)))
  599. (setq magit--rebase-public-edit-confirmed
  600. (delete (magit-toplevel) magit--rebase-public-edit-confirmed))
  601. (when (and branches
  602. (or (not delay-edit-confirm)
  603. ;; The user might have stopped at a published commit
  604. ;; merely to add new commits *after* it. Try not to
  605. ;; ask users whether they really want to edit public
  606. ;; commits, when they don't actually intend to do so.
  607. (not (--all-p (magit-rev-equal it commit) branches))))
  608. (let ((m1 "Some of these commits have already been published to ")
  609. (m2 ".\nDo you really want to modify them"))
  610. (magit-confirm (or magit--rebase-published-symbol 'rebase-published)
  611. (concat m1 "%s" m2)
  612. (concat m1 "%i public branches" m2)
  613. nil branches))
  614. (push (magit-toplevel) magit--rebase-public-edit-confirmed)))
  615. (if (and (magit-git-lines "rev-list" "--merges" (concat since "..HEAD"))
  616. (not rebase-merges))
  617. (magit-read-char-case "Proceed despite merge in rebase range? " nil
  618. (?c "[c]ontinue" since)
  619. (?s "[s]elect other" nil)
  620. (?a "[a]bort" (user-error "Quit")))
  621. since))
  622. (defun magit-rebase--target-commit (since)
  623. (if (string-suffix-p "^" since)
  624. ;; If SINCE is "REV^", then the user selected
  625. ;; "REV", which is the first commit that will
  626. ;; be replaced. (from^..to] <=> [from..to]
  627. (substring since 0 -1)
  628. ;; The "--root" argument is being used.
  629. since))
  630. ;;;###autoload
  631. (defun magit-rebase-interactive (commit args)
  632. "Start an interactive rebase sequence."
  633. (interactive (list (magit-commit-at-point)
  634. (magit-rebase-arguments)))
  635. (magit-rebase-interactive-1 commit args
  636. "Type %p on a commit to rebase it and all commits above it,"
  637. nil t))
  638. ;;;###autoload
  639. (defun magit-rebase-autosquash (args)
  640. "Combine squash and fixup commits with their intended targets."
  641. (interactive (list (magit-rebase-arguments)))
  642. (magit-rebase-interactive-1 :merge-base
  643. (nconc (list "--autosquash" "--keep-empty") args)
  644. "Type %p on a commit to squash into it and then rebase as necessary,"
  645. "true" nil t))
  646. ;;;###autoload
  647. (defun magit-rebase-edit-commit (commit args)
  648. "Edit a single older commit using rebase."
  649. (interactive (list (magit-commit-at-point)
  650. (magit-rebase-arguments)))
  651. (magit-rebase-interactive-1 commit args
  652. "Type %p on a commit to edit it,"
  653. (apply-partially #'magit-rebase--perl-editor 'edit)
  654. t))
  655. ;;;###autoload
  656. (defun magit-rebase-reword-commit (commit args)
  657. "Reword a single older commit using rebase."
  658. (interactive (list (magit-commit-at-point)
  659. (magit-rebase-arguments)))
  660. (magit-rebase-interactive-1 commit args
  661. "Type %p on a commit to reword its message,"
  662. (apply-partially #'magit-rebase--perl-editor 'reword)))
  663. ;;;###autoload
  664. (defun magit-rebase-remove-commit (commit args)
  665. "Remove a single older commit using rebase."
  666. (interactive (list (magit-commit-at-point)
  667. (magit-rebase-arguments)))
  668. (magit-rebase-interactive-1 commit args
  669. "Type %p on a commit to remove it,"
  670. (apply-partially #'magit-rebase--perl-editor 'remove)
  671. nil nil t))
  672. (defun magit-rebase--perl-editor (action since)
  673. (let ((commit (magit-rev-abbrev (magit-rebase--target-commit since))))
  674. (format "%s -i -p -e '++$x if not $x and s/^pick %s/%s %s/'"
  675. magit-perl-executable
  676. commit
  677. (cl-case action
  678. (edit "edit")
  679. (remove "# pick")
  680. (reword "reword")
  681. (t (error "unknown action: %s" action)))
  682. commit)))
  683. ;;;###autoload
  684. (defun magit-rebase-continue (&optional noedit)
  685. "Restart the current rebasing operation.
  686. In some cases this pops up a commit message buffer for you do
  687. edit. With a prefix argument the old message is reused as-is."
  688. (interactive "P")
  689. (if (magit-rebase-in-progress-p)
  690. (if (magit-anything-unstaged-p t)
  691. (user-error "Cannot continue rebase with unstaged changes")
  692. (when (and (magit-anything-staged-p)
  693. (file-exists-p (magit-git-dir "rebase-merge"))
  694. (not (member (magit-toplevel)
  695. magit--rebase-public-edit-confirmed)))
  696. (magit-commit-amend-assert))
  697. (if noedit
  698. (let ((process-environment process-environment))
  699. (push "GIT_EDITOR=true" process-environment)
  700. (magit-run-git-async (magit--rebase-resume-command) "--continue")
  701. (set-process-sentinel magit-this-process
  702. #'magit-sequencer-process-sentinel)
  703. magit-this-process)
  704. (magit-run-git-sequencer (magit--rebase-resume-command) "--continue")))
  705. (user-error "No rebase in progress")))
  706. ;;;###autoload
  707. (defun magit-rebase-skip ()
  708. "Skip the current commit and restart the current rebase operation."
  709. (interactive)
  710. (unless (magit-rebase-in-progress-p)
  711. (user-error "No rebase in progress"))
  712. (magit-run-git-sequencer (magit--rebase-resume-command) "--skip"))
  713. ;;;###autoload
  714. (defun magit-rebase-edit ()
  715. "Edit the todo list of the current rebase operation."
  716. (interactive)
  717. (unless (magit-rebase-in-progress-p)
  718. (user-error "No rebase in progress"))
  719. (magit-run-git-sequencer "rebase" "--edit-todo"))
  720. ;;;###autoload
  721. (defun magit-rebase-abort ()
  722. "Abort the current rebase operation, restoring the original branch."
  723. (interactive)
  724. (unless (magit-rebase-in-progress-p)
  725. (user-error "No rebase in progress"))
  726. (magit-confirm 'abort-rebase "Abort this rebase")
  727. (magit-run-git (magit--rebase-resume-command) "--abort"))
  728. (defun magit-rebase-in-progress-p ()
  729. "Return t if a rebase is in progress."
  730. (or (file-exists-p (magit-git-dir "rebase-merge"))
  731. (file-exists-p (magit-git-dir "rebase-apply/onto"))))
  732. (defun magit--rebase-resume-command ()
  733. (if (file-exists-p (magit-git-dir "rebase-recursive")) "rbr" "rebase"))
  734. ;;; Sections
  735. (defun magit-insert-sequencer-sequence ()
  736. "Insert section for the on-going cherry-pick or revert sequence.
  737. If no such sequence is in progress, do nothing."
  738. (let ((picking (magit-cherry-pick-in-progress-p)))
  739. (when (or picking (magit-revert-in-progress-p))
  740. (magit-insert-section (sequence)
  741. (magit-insert-heading (if picking "Cherry Picking" "Reverting"))
  742. (when-let ((lines
  743. (cdr (magit-file-lines (magit-git-dir "sequencer/todo")))))
  744. (dolist (line (nreverse lines))
  745. (when (string-match
  746. "^\\(pick\\|revert\\) \\([^ ]+\\) \\(.*\\)$" line)
  747. (magit-bind-match-strings (cmd hash msg) line
  748. (magit-insert-section (commit hash)
  749. (insert (propertize cmd 'font-lock-face 'magit-sequence-pick)
  750. " " (propertize hash 'font-lock-face 'magit-hash)
  751. " " msg "\n"))))))
  752. (magit-sequence-insert-sequence
  753. (magit-file-line (magit-git-dir (if picking
  754. "CHERRY_PICK_HEAD"
  755. "REVERT_HEAD")))
  756. (magit-file-line (magit-git-dir "sequencer/head")))
  757. (insert "\n")))))
  758. (defun magit-insert-am-sequence ()
  759. "Insert section for the on-going patch applying sequence.
  760. If no such sequence is in progress, do nothing."
  761. (when (magit-am-in-progress-p)
  762. (magit-insert-section (rebase-sequence)
  763. (magit-insert-heading "Applying patches")
  764. (let ((patches (nreverse (magit-rebase-patches)))
  765. patch commit)
  766. (while patches
  767. (setq patch (pop patches))
  768. (setq commit (magit-commit-p
  769. (cadr (split-string (magit-file-line patch)))))
  770. (cond ((and commit patches)
  771. (magit-sequence-insert-commit
  772. "pick" commit 'magit-sequence-pick))
  773. (patches
  774. (magit-sequence-insert-am-patch
  775. "pick" patch 'magit-sequence-pick))
  776. (commit
  777. (magit-sequence-insert-sequence commit "ORIG_HEAD"))
  778. (t
  779. (magit-sequence-insert-am-patch
  780. "stop" patch 'magit-sequence-stop)
  781. (magit-sequence-insert-sequence nil "ORIG_HEAD")))))
  782. (insert ?\n))))
  783. (defun magit-sequence-insert-am-patch (type patch face)
  784. (magit-insert-section (file patch)
  785. (let ((title
  786. (with-temp-buffer
  787. (insert-file-contents patch nil nil 4096)
  788. (unless (re-search-forward "^Subject: " nil t)
  789. (goto-char (point-min)))
  790. (buffer-substring (point) (line-end-position)))))
  791. (insert (propertize type 'font-lock-face face)
  792. ?\s (propertize (file-name-nondirectory patch)
  793. 'font-lock-face 'magit-hash)
  794. ?\s title
  795. ?\n))))
  796. (defun magit-insert-rebase-sequence ()
  797. "Insert section for the on-going rebase sequence.
  798. If no such sequence is in progress, do nothing."
  799. (when (magit-rebase-in-progress-p)
  800. (let* ((interactive (file-directory-p (magit-git-dir "rebase-merge")))
  801. (dir (if interactive "rebase-merge/" "rebase-apply/"))
  802. (name (-> (concat dir "head-name") magit-git-dir magit-file-line))
  803. (onto (-> (concat dir "onto") magit-git-dir magit-file-line))
  804. (onto (or (magit-rev-name onto name)
  805. (magit-rev-name onto "refs/heads/*") onto))
  806. (name (or (magit-rev-name name "refs/heads/*") name)))
  807. (magit-insert-section (rebase-sequence)
  808. (magit-insert-heading (format "Rebasing %s onto %s" name onto))
  809. (if interactive
  810. (magit-rebase-insert-merge-sequence onto)
  811. (magit-rebase-insert-apply-sequence onto))
  812. (insert ?\n)))))
  813. (defun magit-rebase--todo ()
  814. "Return `git-rebase-action' instances for remaining rebase actions.
  815. These are ordered in that the same way they'll be sorted in the
  816. status buffer (i.e. the reverse of how they will be applied)."
  817. (let ((comment-start (or (magit-get "core.commentChar") "#"))
  818. lines)
  819. (with-temp-buffer
  820. (insert-file-contents (magit-git-dir "rebase-merge/git-rebase-todo"))
  821. (while (not (eobp))
  822. (let ((ln (git-rebase-current-line)))
  823. (when (oref ln action-type)
  824. (push ln lines)))
  825. (forward-line)))
  826. lines))
  827. (defun magit-rebase-insert-merge-sequence (onto)
  828. (dolist (line (magit-rebase--todo))
  829. (with-slots (action-type action action-options target) line
  830. (pcase action-type
  831. (`commit
  832. (magit-sequence-insert-commit action target 'magit-sequence-pick))
  833. ((or (or `exec `label)
  834. (and `merge (guard (not action-options))))
  835. (insert (propertize action 'font-lock-face 'magit-sequence-onto) "\s"
  836. (propertize target 'font-lock-face 'git-rebase-label) "\n"))
  837. (`merge
  838. (if-let ((hash (and (string-match "-[cC] \\([^ ]+\\)" action-options)
  839. (match-string 1 action-options))))
  840. (magit-insert-section (commit hash)
  841. (magit-insert-heading
  842. (propertize "merge" 'font-lock-face 'magit-sequence-pick)
  843. "\s"
  844. (magit-format-rev-summary hash) "\n"))
  845. (error "failed to parse merge message hash"))))))
  846. (magit-sequence-insert-sequence
  847. (magit-file-line (magit-git-dir "rebase-merge/stopped-sha"))
  848. onto
  849. (--when-let (magit-file-lines (magit-git-dir "rebase-merge/done"))
  850. (cadr (split-string (car (last it)))))))
  851. (defun magit-rebase-insert-apply-sequence (onto)
  852. (let ((rewritten
  853. (--map (car (split-string it))
  854. (magit-file-lines (magit-git-dir "rebase-apply/rewritten"))))
  855. (stop (magit-file-line (magit-git-dir "rebase-apply/original-commit"))))
  856. (dolist (patch (nreverse (cdr (magit-rebase-patches))))
  857. (let ((hash (cadr (split-string (magit-file-line patch)))))
  858. (unless (or (member hash rewritten)
  859. (equal hash stop))
  860. (magit-sequence-insert-commit "pick" hash 'magit-sequence-pick)))))
  861. (magit-sequence-insert-sequence
  862. (magit-file-line (magit-git-dir "rebase-apply/original-commit"))
  863. onto))
  864. (defun magit-rebase-patches ()
  865. (directory-files (magit-git-dir "rebase-apply") t "^[0-9]\\{4\\}$"))
  866. (defun magit-sequence-insert-sequence (stop onto &optional orig)
  867. (let ((head (magit-rev-parse "HEAD")) done)
  868. (setq onto (if onto (magit-rev-parse onto) head))
  869. (setq done (magit-git-lines "log" "--format=%H" (concat onto "..HEAD")))
  870. (when (and stop (not (member (magit-rev-parse stop) done)))
  871. (let ((id (magit-patch-id stop)))
  872. (--if-let (--first (equal (magit-patch-id it) id) done)
  873. (setq stop it)
  874. (cond
  875. ((--first (magit-rev-equal it stop) done)
  876. ;; The commit's testament has been executed.
  877. (magit-sequence-insert-commit "void" stop 'magit-sequence-drop))
  878. ;; The faith of the commit is still undecided...
  879. ((magit-anything-unmerged-p)
  880. ;; ...and time travel isn't for the faint of heart.
  881. (magit-sequence-insert-commit "join" stop 'magit-sequence-part))
  882. ((magit-anything-modified-p t)
  883. ;; ...and the dust hasn't settled yet...
  884. (magit-sequence-insert-commit
  885. (let* ((magit--refresh-cache nil)
  886. (staged (magit-commit-tree "oO" nil "HEAD"))
  887. (unstaged (magit-commit-worktree "oO" "--reset")))
  888. (cond
  889. ;; ...but we could end up at the same tree just by committing.
  890. ((or (magit-rev-equal staged stop)
  891. (magit-rev-equal unstaged stop)) "goal")
  892. ;; ...but the changes are still there, untainted.
  893. ((or (equal (magit-patch-id staged) id)
  894. (equal (magit-patch-id unstaged) id)) "same")
  895. ;; ...and some changes are gone and/or others were added.
  896. (t "work")))
  897. stop 'magit-sequence-part))
  898. ;; The commit is definitely gone...
  899. ((--first (magit-rev-equal it stop) done)
  900. ;; ...but all of its changes are still in effect.
  901. (magit-sequence-insert-commit "poof" stop 'magit-sequence-drop))
  902. (t
  903. ;; ...and some changes are gone and/or other changes were added.
  904. (magit-sequence-insert-commit "gone" stop 'magit-sequence-drop)))
  905. (setq stop nil))))
  906. (dolist (rev done)
  907. (apply 'magit-sequence-insert-commit
  908. (cond ((equal rev stop)
  909. ;; ...but its reincarnation lives on.
  910. ;; Or it didn't die in the first place.
  911. (list (if (and (equal rev head)
  912. (equal (magit-patch-id rev)
  913. (magit-patch-id orig)))
  914. "stop" ; We haven't done anything yet.
  915. "like") ; There are new commits.
  916. rev (if (equal rev head)
  917. 'magit-sequence-head
  918. 'magit-sequence-stop)))
  919. ((equal rev head)
  920. (list "done" rev 'magit-sequence-head))
  921. (t
  922. (list "done" rev 'magit-sequence-done)))))
  923. (magit-sequence-insert-commit "onto" onto
  924. (if (equal onto head)
  925. 'magit-sequence-head
  926. 'magit-sequence-onto))))
  927. (defun magit-sequence-insert-commit (type hash face)
  928. (magit-insert-section (commit hash)
  929. (magit-insert-heading
  930. (propertize type 'font-lock-face face) "\s"
  931. (magit-format-rev-summary hash) "\n")))
  932. ;;; _
  933. (provide 'magit-sequence)
  934. ;;; magit-sequence.el ends here