dotemacs

My Emacs configuration
git clone git://git.entf.net/dotemacs
Log | Files | Refs | LICENSE

magit-merge.el (12151B)


      1 ;;; magit-merge.el --- merge functionality  -*- lexical-binding: t -*-
      2 
      3 ;; Copyright (C) 2010-2021  The Magit Project Contributors
      4 ;;
      5 ;; You should have received a copy of the AUTHORS.md file which
      6 ;; lists all contributors.  If not, see http://magit.vc/authors.
      7 
      8 ;; Author: Jonas Bernoulli <jonas@bernoul.li>
      9 ;; Maintainer: Jonas Bernoulli <jonas@bernoul.li>
     10 
     11 ;; SPDX-License-Identifier: GPL-3.0-or-later
     12 
     13 ;; Magit is free software; you can redistribute it and/or modify it
     14 ;; under the terms of the GNU General Public License as published by
     15 ;; the Free Software Foundation; either version 3, or (at your option)
     16 ;; any later version.
     17 ;;
     18 ;; Magit is distributed in the hope that it will be useful, but WITHOUT
     19 ;; ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
     20 ;; or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public
     21 ;; License for more details.
     22 ;;
     23 ;; You should have received a copy of the GNU General Public License
     24 ;; along with Magit.  If not, see http://www.gnu.org/licenses.
     25 
     26 ;;; Commentary:
     27 
     28 ;; This library implements merge commands.
     29 
     30 ;;; Code:
     31 
     32 (require 'magit)
     33 (require 'magit-diff)
     34 
     35 (declare-function magit-git-push "magit-push" (branch target args))
     36 
     37 ;;; Commands
     38 
     39 ;;;###autoload (autoload 'magit-merge "magit" nil t)
     40 (transient-define-prefix magit-merge ()
     41   "Merge branches."
     42   :man-page "git-merge"
     43   :incompatible '(("--ff-only" "--no-ff"))
     44   ["Arguments"
     45    :if-not magit-merge-in-progress-p
     46    ("-f" "Fast-forward only" "--ff-only")
     47    ("-n" "No fast-forward"   "--no-ff")
     48    (magit-merge:--strategy)
     49    (5 magit-merge:--strategy-option)
     50    (5 "-b" "Ignore changes in amount of whitespace" "-Xignore-space-change")
     51    (5 "-w" "Ignore whitespace when comparing lines" "-Xignore-all-space")
     52    (5 magit-diff:--diff-algorithm :argument "-Xdiff-algorithm=")
     53    (5 magit:--gpg-sign)]
     54   ["Actions"
     55    :if-not magit-merge-in-progress-p
     56    [("m" "Merge"                  magit-merge-plain)
     57     ("e" "Merge and edit message" magit-merge-editmsg)
     58     ("n" "Merge but don't commit" magit-merge-nocommit)
     59     ("a" "Absorb"                 magit-merge-absorb)]
     60    [("p" "Preview merge"          magit-merge-preview)
     61     ""
     62     ("s" "Squash merge"           magit-merge-squash)
     63     ("i" "Dissolve"               magit-merge-into)]]
     64   ["Actions"
     65    :if magit-merge-in-progress-p
     66    ("m" "Commit merge" magit-commit-create)
     67    ("a" "Abort merge"  magit-merge-abort)])
     68 
     69 (defun magit-merge-arguments ()
     70   (transient-args 'magit-merge))
     71 
     72 (transient-define-argument magit-merge:--strategy ()
     73   :description "Strategy"
     74   :class 'transient-option
     75   ;; key for merge and rebase: "-s"
     76   ;; key for cherry-pick and revert: "=s"
     77   ;; shortarg for merge and rebase: "-s"
     78   ;; shortarg for cherry-pick and revert: none
     79   :key "-s"
     80   :argument "--strategy="
     81   :choices '("resolve" "recursive" "octopus" "ours" "subtree"))
     82 
     83 (transient-define-argument magit-merge:--strategy-option ()
     84   :description "Strategy Option"
     85   :class 'transient-option
     86   :key "-X"
     87   :argument "--strategy-option="
     88   :choices '("ours" "theirs" "patience"))
     89 
     90 ;;;###autoload
     91 (defun magit-merge-plain (rev &optional args nocommit)
     92   "Merge commit REV into the current branch; using default message.
     93 
     94 Unless there are conflicts or a prefix argument is used create a
     95 merge commit using a generic commit message and without letting
     96 the user inspect the result.  With a prefix argument pretend the
     97 merge failed to give the user the opportunity to inspect the
     98 merge.
     99 
    100 \(git merge --no-edit|--no-commit [ARGS] REV)"
    101   (interactive (list (magit-read-other-branch-or-commit "Merge")
    102                      (magit-merge-arguments)
    103                      current-prefix-arg))
    104   (magit-merge-assert)
    105   (magit-run-git-async "merge" (if nocommit "--no-commit" "--no-edit") args rev))
    106 
    107 ;;;###autoload
    108 (defun magit-merge-editmsg (rev &optional args)
    109   "Merge commit REV into the current branch; and edit message.
    110 Perform the merge and prepare a commit message but let the user
    111 edit it.
    112 \n(git merge --edit --no-ff [ARGS] REV)"
    113   (interactive (list (magit-read-other-branch-or-commit "Merge")
    114                      (magit-merge-arguments)))
    115   (magit-merge-assert)
    116   (cl-pushnew "--no-ff" args :test #'equal)
    117   (apply #'magit-run-git-with-editor "merge" "--edit"
    118          (append (delete "--ff-only" args)
    119                  (list rev))))
    120 
    121 ;;;###autoload
    122 (defun magit-merge-nocommit (rev &optional args)
    123   "Merge commit REV into the current branch; pretending it failed.
    124 Pretend the merge failed to give the user the opportunity to
    125 inspect the merge and change the commit message.
    126 \n(git merge --no-commit --no-ff [ARGS] REV)"
    127   (interactive (list (magit-read-other-branch-or-commit "Merge")
    128                      (magit-merge-arguments)))
    129   (magit-merge-assert)
    130   (cl-pushnew "--no-ff" args :test #'equal)
    131   (magit-run-git-async "merge" "--no-commit" args rev))
    132 
    133 ;;;###autoload
    134 (defun magit-merge-into (branch &optional args)
    135   "Merge the current branch into BRANCH and remove the former.
    136 
    137 Before merging, force push the source branch to its push-remote,
    138 provided the respective remote branch already exists, ensuring
    139 that the respective pull-request (if any) won't get stuck on some
    140 obsolete version of the commits that are being merged.  Finally
    141 if `forge-branch-pullreq' was used to create the merged branch,
    142 branch, then also remove the respective remote branch."
    143   (interactive
    144    (list (magit-read-other-local-branch
    145           (format "Merge `%s' into"
    146                   (or (magit-get-current-branch)
    147                       (magit-rev-parse "HEAD")))
    148           nil
    149           (when-let ((upstream (magit-get-upstream-branch))
    150                      (upstream (cdr (magit-split-branch-name upstream))))
    151             (and (magit-branch-p upstream) upstream)))
    152          (magit-merge-arguments)))
    153   (let ((current (magit-get-current-branch))
    154         (head (magit-rev-parse "HEAD")))
    155     (when (zerop (magit-call-git "checkout" branch))
    156       (if current
    157           (magit--merge-absorb current args)
    158         (magit-run-git-with-editor "merge" args head)))))
    159 
    160 ;;;###autoload
    161 (defun magit-merge-absorb (branch &optional args)
    162   "Merge BRANCH into the current branch and remove the former.
    163 
    164 Before merging, force push the source branch to its push-remote,
    165 provided the respective remote branch already exists, ensuring
    166 that the respective pull-request (if any) won't get stuck on some
    167 obsolete version of the commits that are being merged.  Finally
    168 if `forge-branch-pullreq' was used to create the merged branch,
    169 then also remove the respective remote branch."
    170   (interactive (list (magit-read-other-local-branch "Absorb branch")
    171                      (magit-merge-arguments)))
    172   (magit--merge-absorb branch args))
    173 
    174 (defun magit--merge-absorb (branch args)
    175   (when (equal branch (magit-main-branch))
    176     (unless (yes-or-no-p
    177              (format "Do you really want to merge `%s' into another branch? "
    178                      branch))
    179       (user-error "Abort")))
    180   (if-let ((target (magit-get-push-branch branch t)))
    181       (progn
    182         (magit-git-push branch target (list "--force-with-lease"))
    183         (set-process-sentinel
    184          magit-this-process
    185          (lambda (process event)
    186            (when (memq (process-status process) '(exit signal))
    187              (if (not (zerop (process-exit-status process)))
    188                  (magit-process-sentinel process event)
    189                (process-put process 'inhibit-refresh t)
    190                (magit-process-sentinel process event)
    191                (magit--merge-absorb-1 branch args))))))
    192     (magit--merge-absorb-1 branch args)))
    193 
    194 (defun magit--merge-absorb-1 (branch args)
    195   (if-let ((pr (magit-get "branch" branch "pullRequest")))
    196       (magit-run-git-async
    197        "merge" args "-m"
    198        (format "Merge branch '%s'%s [#%s]"
    199                branch
    200                (let ((current (magit-get-current-branch)))
    201                  (if (equal current (magit-main-branch))
    202                      ""
    203                    (format " into %s" current)))
    204                pr)
    205        branch)
    206     (magit-run-git-async "merge" args "--no-edit" branch))
    207   (set-process-sentinel
    208    magit-this-process
    209    (lambda (process event)
    210      (when (memq (process-status process) '(exit signal))
    211        (if (> (process-exit-status process) 0)
    212            (magit-process-sentinel process event)
    213          (process-put process 'inhibit-refresh t)
    214          (magit-process-sentinel process event)
    215          (magit-branch-maybe-delete-pr-remote branch)
    216          (magit-branch-unset-pushRemote branch)
    217          (magit-run-git "branch" "-D" branch))))))
    218 
    219 ;;;###autoload
    220 (defun magit-merge-squash (rev)
    221   "Squash commit REV into the current branch; don't create a commit.
    222 \n(git merge --squash REV)"
    223   (interactive (list (magit-read-other-branch-or-commit "Squash")))
    224   (magit-merge-assert)
    225   (magit-run-git-async "merge" "--squash" rev))
    226 
    227 ;;;###autoload
    228 (defun magit-merge-preview (rev)
    229   "Preview result of merging REV into the current branch."
    230   (interactive (list (magit-read-other-branch-or-commit "Preview merge")))
    231   (magit-merge-preview-setup-buffer rev))
    232 
    233 ;;;###autoload
    234 (defun magit-merge-abort ()
    235   "Abort the current merge operation.
    236 \n(git merge --abort)"
    237   (interactive)
    238   (unless (file-exists-p (magit-git-dir "MERGE_HEAD"))
    239     (user-error "No merge in progress"))
    240   (magit-confirm 'abort-merge)
    241   (magit-run-git-async "merge" "--abort"))
    242 
    243 (defun magit-checkout-stage (file arg)
    244   "During a conflict checkout and stage side, or restore conflict."
    245   (interactive
    246    (let ((file (magit-completing-read "Checkout file"
    247                                       (magit-tracked-files) nil nil nil
    248                                       'magit-read-file-hist
    249                                       (magit-current-file))))
    250      (cond ((member file (magit-unmerged-files))
    251             (list file (magit-checkout-read-stage file)))
    252            ((yes-or-no-p (format "Restore conflicts in %s? " file))
    253             (list file "--merge"))
    254            (t
    255             (user-error "Quit")))))
    256   (pcase (cons arg (cddr (car (magit-file-status file))))
    257     ((or `("--ours"   ?D ,_)
    258          `("--theirs" ,_ ?D))
    259      (magit-run-git "rm" "--" file))
    260     (_ (if (equal arg "--merge")
    261            ;; This fails if the file was deleted on one
    262            ;; side.  And we cannot do anything about it.
    263            (magit-run-git "checkout" "--merge" "--" file)
    264          (magit-call-git "checkout" arg "--" file)
    265          (magit-run-git "add" "-u" "--" file)))))
    266 
    267 ;;; Utilities
    268 
    269 (defun magit-merge-in-progress-p ()
    270   (file-exists-p (magit-git-dir "MERGE_HEAD")))
    271 
    272 (defun magit--merge-range (&optional head)
    273   (unless head
    274     (setq head (magit-get-shortname
    275                 (car (magit-file-lines (magit-git-dir "MERGE_HEAD"))))))
    276   (and head
    277        (concat (magit-git-string "merge-base" "--octopus" "HEAD" head)
    278                ".." head)))
    279 
    280 (defun magit-merge-assert ()
    281   (or (not (magit-anything-modified-p t))
    282       (magit-confirm 'merge-dirty
    283         "Merging with dirty worktree is risky.  Continue")))
    284 
    285 (defun magit-checkout-read-stage (file)
    286   (magit-read-char-case (format "For %s checkout: " file) t
    287     (?o "[o]ur stage"   "--ours")
    288     (?t "[t]heir stage" "--theirs")
    289     (?c "[c]onflict"    "--merge")))
    290 
    291 ;;; Sections
    292 
    293 (defvar magit-unmerged-section-map
    294   (let ((map (make-sparse-keymap)))
    295     (define-key map [remap magit-visit-thing] 'magit-diff-dwim)
    296     map)
    297   "Keymap for `unmerged' sections.")
    298 
    299 (defun magit-insert-merge-log ()
    300   "Insert section for the on-going merge.
    301 Display the heads that are being merged.
    302 If no merge is in progress, do nothing."
    303   (when (magit-merge-in-progress-p)
    304     (let* ((heads (mapcar #'magit-get-shortname
    305                           (magit-file-lines (magit-git-dir "MERGE_HEAD"))))
    306            (range (magit--merge-range (car heads))))
    307       (magit-insert-section (unmerged range)
    308         (magit-insert-heading
    309           (format "Merging %s:" (mapconcat #'identity heads ", ")))
    310         (magit-insert-log
    311          range
    312          (let ((args magit-buffer-log-args))
    313            (unless (member "--decorate=full" magit-buffer-log-args)
    314              (push "--decorate=full" args))
    315            args))))))
    316 
    317 ;;; _
    318 (provide 'magit-merge)
    319 ;;; magit-merge.el ends here