config

Personal configuration.
git clone git://code.dwrz.net/config
Log | Files | Refs

magit-sequence.el (46578B)


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