config

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

magit-process.el (55024B)


      1 ;;; magit-process.el --- Process functionality  -*- 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 ;; This library implements the tools used to run Git for side-effects.
     26 
     27 ;; Note that the functions used to run Git and then consume its
     28 ;; output, are defined in `magit-git.el'.  There's a bit of overlap
     29 ;; though.
     30 
     31 ;;; Code:
     32 
     33 (require 'magit-base)
     34 (require 'magit-git)
     35 (require 'magit-mode)
     36 
     37 (require 'ansi-color)
     38 (require 'auth-source)
     39 (require 'with-editor)
     40 
     41 (defvar y-or-n-p-map)
     42 
     43 ;;; Options
     44 
     45 (defcustom magit-process-connection-type (not (eq system-type 'cygwin))
     46   "Connection type used for the Git process.
     47 
     48 If nil, use pipes: this is usually more efficient, and works on Cygwin.
     49 If t, use ptys: this enables Magit to prompt for passphrases when needed."
     50   :group 'magit-process
     51   :type '(choice (const :tag "pipe" nil)
     52                  (const :tag "pty" t)))
     53 
     54 (defcustom magit-need-cygwin-noglob
     55   (and (eq system-type 'windows-nt)
     56        (with-temp-buffer
     57          (let ((process-environment
     58                 (append magit-git-environment process-environment)))
     59            (condition-case e
     60                (process-file magit-git-executable
     61                              nil (current-buffer) nil
     62                              "-c" "alias.echo=!echo" "echo" "x{0}")
     63              (file-error
     64               (lwarn 'magit-process :warning
     65                      "Could not run Git: %S" e))))
     66          (equal "x0\n" (buffer-string))))
     67   "Whether to use a workaround for Cygwin's globbing behavior.
     68 
     69 If non-nil, add environment variables to `process-environment' to
     70 prevent the git.exe distributed by Cygwin and MSYS2 from
     71 attempting to perform glob expansion when called from a native
     72 Windows build of Emacs.  See #2246."
     73   :package-version '(magit . "2.3.0")
     74   :group 'magit-process
     75   :type '(choice (const :tag "Yes" t)
     76                  (const :tag "No" nil)))
     77 
     78 (defcustom magit-process-popup-time -1
     79   "Popup the process buffer if a command takes longer than this many seconds."
     80   :group 'magit-process
     81   :type '(choice (const :tag "Never" -1)
     82                  (const :tag "Immediately" 0)
     83                  (integer :tag "After this many seconds")))
     84 
     85 (defcustom magit-process-log-max 32
     86   "Maximum number of sections to keep in a process log buffer.
     87 When adding a new section would go beyond the limit set here,
     88 then the older half of the sections are remove.  Sections that
     89 belong to processes that are still running are never removed.
     90 When this is nil, no sections are ever removed."
     91   :package-version '(magit . "2.1.0")
     92   :group 'magit-process
     93   :type '(choice (const :tag "Never remove old sections" nil) integer))
     94 
     95 (defvar magit-process-extreme-logging nil
     96   "Whether `magit-process-file' logs to the *Messages* buffer.
     97 
     98 Only intended for temporary use when you try to figure out how
     99 Magit uses Git behind the scene.  Output that normally goes to
    100 the magit-process buffer continues to go there.  Not all output
    101 goes to either of these two buffers.
    102 
    103 Also see `magit-git-debug'.")
    104 
    105 (defcustom magit-process-error-tooltip-max-lines 20
    106   "The number of lines for `magit-process-error-lines' to return.
    107 
    108 These are displayed in a tooltip for `mode-line-process' errors.
    109 
    110 If `magit-process-error-tooltip-max-lines' is nil, the tooltip
    111 displays the text of `magit-process-error-summary' instead."
    112   :package-version '(magit . "2.12.0")
    113   :group 'magit-process
    114   :type '(choice (const :tag "Use summary line" nil)
    115                  integer))
    116 
    117 (defcustom magit-credential-cache-daemon-socket
    118   (--some (pcase-let ((`(,prog . ,args) (split-string it)))
    119             (if (and prog
    120                      (string-match-p
    121                       "\\`\\(?:\\(?:/.*/\\)?git-credential-\\)?cache\\'" prog))
    122                 (or (cl-loop for (opt val) on args
    123                              if (string= opt "--socket")
    124                              return val)
    125                     (expand-file-name "~/.git-credential-cache/socket"))))
    126           ;; Note: `magit-process-file' is not yet defined when
    127           ;; evaluating this form, so we use `process-lines'.
    128           (ignore-errors
    129             (let ((process-environment
    130                    (append magit-git-environment process-environment)))
    131               (process-lines magit-git-executable
    132                              "config" "--get-all" "credential.helper"))))
    133   "If non-nil, start a credential cache daemon using this socket.
    134 
    135 When using Git's cache credential helper in the normal way, Emacs
    136 sends a SIGHUP to the credential daemon after the git subprocess
    137 has exited, causing the daemon to also quit.  This can be avoided
    138 by starting the `git-credential-cache--daemon' process directly
    139 from Emacs.
    140 
    141 The function `magit-maybe-start-credential-cache-daemon' takes
    142 care of starting the daemon if necessary, using the value of this
    143 option as the socket.  If this option is nil, then it does not
    144 start any daemon.  Likewise if another daemon is already running,
    145 then it starts no new daemon.  This function has to be a member
    146 of the hook variable `magit-credential-hook' for this to work.
    147 If an error occurs while starting the daemon, most likely because
    148 the necessary executable is missing, then the function removes
    149 itself from the hook, to avoid further futile attempts."
    150   :package-version '(magit . "2.3.0")
    151   :group 'magit-process
    152   :type '(choice (file  :tag "Socket")
    153                  (const :tag "Don't start a cache daemon" nil)))
    154 
    155 (defcustom magit-process-yes-or-no-prompt-regexp
    156   (concat " [([]"
    157           "\\([Yy]\\(?:es\\)?\\)"
    158           "[/|]"
    159           "\\([Nn]o?\\)"
    160           ;; OpenSSH v8 prints this.  See #3969.
    161           "\\(?:/\\[fingerprint\\]\\)?"
    162           "[])] ?[?:]? ?$")
    163   "Regexp matching Yes-or-No prompts of Git and its subprocesses."
    164   :package-version '(magit . "2.1.0")
    165   :group 'magit-process
    166   :type 'regexp)
    167 
    168 (defcustom magit-process-password-prompt-regexps
    169   '("^\\(Enter \\)?[Pp]assphrase\\( for \\(RSA \\)?key '.*'\\)?: ?$"
    170     ;; Match-group 99 is used to identify the "user@host" part.
    171     "^\\(Enter \\|([^) ]+) \\)?\
    172 [Pp]assword\\( for '?\\(https?://\\)?\\(?99:[^']*\\)'?\\)?: ?$"
    173     "Please enter the passphrase for the ssh key"
    174     "Please enter the passphrase to unlock the OpenPGP secret key"
    175     "^.*'s password: ?$"
    176     "^Token: $" ; For git-credential-manager-core (#4318).
    177     "^Yubikey for .*: ?$"
    178     "^Enter PIN for .*: ?$")
    179   "List of regexps matching password prompts of Git and its subprocesses.
    180 Also see `magit-process-find-password-functions'."
    181   :package-version '(magit . "3.0.0")
    182   :group 'magit-process
    183   :type '(repeat (regexp)))
    184 
    185 (defcustom magit-process-find-password-functions nil
    186   "List of functions to try in sequence to get a password.
    187 
    188 These functions may be called when git asks for a password, which
    189 is detected using `magit-process-password-prompt-regexps'.  They
    190 are called if and only if matching the prompt resulted in the
    191 value of the 99th submatch to be non-nil.  Therefore users can
    192 control for which prompts these functions should be called by
    193 putting the host name in the 99th submatch, or not.
    194 
    195 If the functions are called, then they are called in the order
    196 given, with the host name as only argument, until one of them
    197 returns non-nil.  If they are not called or none of them returns
    198 non-nil, then the password is read from the user instead."
    199   :package-version '(magit . "2.3.0")
    200   :group 'magit-process
    201   :type 'hook
    202   :options '(magit-process-password-auth-source))
    203 
    204 (defcustom magit-process-username-prompt-regexps
    205   '("^Username for '.*': ?$")
    206   "List of regexps matching username prompts of Git and its subprocesses."
    207   :package-version '(magit . "2.1.0")
    208   :group 'magit-process
    209   :type '(repeat (regexp)))
    210 
    211 (defcustom magit-process-prompt-functions nil
    212   "List of functions used to forward arbitrary questions to the user.
    213 
    214 Magit has dedicated support for forwarding username and password
    215 prompts and Yes-or-No questions asked by Git and its subprocesses
    216 to the user.  This can be customized using other options in the
    217 `magit-process' customization group.
    218 
    219 If you encounter a new question that isn't handled by default,
    220 then those options should be used instead of this hook.
    221 
    222 However subprocesses may also ask questions that differ too much
    223 from what the code related to the above options assume, and this
    224 hook allows users to deal with such questions explicitly.
    225 
    226 Each function is called with the process and the output string
    227 as arguments until one of the functions returns non-nil.  The
    228 function is responsible for asking the user the appropriate
    229 question using, e.g., `read-char-choice' and then forwarding the
    230 answer to the process using `process-send-string'.
    231 
    232 While functions such as `magit-process-yes-or-no-prompt' may not
    233 be sufficient to handle some prompt, it may still be of benefit
    234 to look at the implementations to gain some insights on how to
    235 implement such functions."
    236   :package-version '(magit . "3.0.0")
    237   :group 'magit-process
    238   :type 'hook)
    239 
    240 (defcustom magit-process-ensure-unix-line-ending t
    241   "Whether Magit should ensure a unix coding system when talking to Git."
    242   :package-version '(magit . "2.6.0")
    243   :group 'magit-process
    244   :type 'boolean)
    245 
    246 (defcustom magit-process-display-mode-line-error t
    247   "Whether Magit should retain and highlight process errors in the mode line."
    248   :package-version '(magit . "2.12.0")
    249   :group 'magit-process
    250   :type 'boolean)
    251 
    252 (defcustom magit-process-timestamp-format nil
    253   "Format of timestamp for each process in the process buffer.
    254 If non-nil, pass this to `format-time-string' when creating a
    255 process section in the process buffer, and insert the returned
    256 string in the heading of its section."
    257   :package-version '(magit . "4.0.0")
    258   :group 'magit-process
    259   :type '(choice (const :tag "none" nil) string))
    260 
    261 (defvar tramp-pipe-stty-settings)
    262 (defvar magit-tramp-pipe-stty-settings ""
    263   "Override `tramp-pipe-stty-settings' in `magit-start-process'.
    264 
    265 The default for that Tramp variable is \"-icanon min 1 time 0\",
    266 which causes staging of individual hunks to hang.  Using \"\"
    267 prevents that, but apparently has other issues, which is why it
    268 isn't the default.
    269 
    270 This variable defaults to \"\" and is used to override the Tramp
    271 variable in `magit-start-process'.  This only has an effect when
    272 using Tramp 2.6.2 or greater.  This can also be set to `pty', in
    273 which case a pty is used instead of a pipe.  That also prevents
    274 the hanging, but comes with its own problems (see #20).  To fall
    275 back to the value of `tramp-pipe-stty-settings', set this
    276 variable to nil.
    277 
    278 Also see https://github.com/magit/magit/issues/4720
    279 and https://debbugs.gnu.org/cgi/bugreport.cgi?bug=62093.")
    280 
    281 (defface magit-process-ok
    282   '((t :inherit magit-section-heading :foreground "green"))
    283   "Face for zero exit-status."
    284   :group 'magit-faces)
    285 
    286 (defface magit-process-ng
    287   '((t :inherit magit-section-heading :foreground "red"))
    288   "Face for non-zero exit-status."
    289   :group 'magit-faces)
    290 
    291 (defface magit-mode-line-process
    292   '((t :inherit mode-line-emphasis))
    293   "Face for `mode-line-process' status when Git is running for side-effects."
    294   :group 'magit-faces)
    295 
    296 (defface magit-mode-line-process-error
    297   '((t :inherit error))
    298   "Face for `mode-line-process' error status.
    299 
    300 Used when `magit-process-display-mode-line-error' is non-nil."
    301   :group 'magit-faces)
    302 
    303 ;;; Process Mode
    304 
    305 (defvar-keymap magit-process-mode-map
    306   :doc "Keymap for `magit-process-mode'."
    307   :parent magit-mode-map
    308   "<remap> <magit-delete-thing>" #'magit-process-kill)
    309 
    310 (define-derived-mode magit-process-mode magit-mode "Magit Process"
    311   "Mode for looking at Git process output."
    312   :group 'magit-process
    313   (hack-dir-local-variables-non-file-buffer)
    314   (setq magit--imenu-item-types 'process))
    315 
    316 (defun magit-process-buffer (&optional nodisplay)
    317   "Display the current repository's process buffer.
    318 
    319 If that buffer doesn't exist yet, then create it.
    320 Non-interactively return the buffer and unless
    321 optional NODISPLAY is non-nil also display it."
    322   (interactive)
    323   (let ((topdir (magit-toplevel)))
    324     (unless topdir
    325       (magit--with-safe-default-directory nil
    326         (setq topdir default-directory)
    327         (let (prev)
    328           (while (not (equal topdir prev))
    329             (setq prev topdir)
    330             (setq topdir (file-name-directory (directory-file-name topdir)))))))
    331     (let ((buffer (or (--first (with-current-buffer it
    332                                  (and (eq major-mode 'magit-process-mode)
    333                                       (equal default-directory topdir)))
    334                                (buffer-list))
    335                       (magit-generate-new-buffer 'magit-process-mode
    336                                                  nil topdir))))
    337       (with-current-buffer buffer
    338         (if magit-root-section
    339             (when magit-process-log-max
    340               (magit-process-truncate-log))
    341           (magit-process-mode)
    342           (let ((inhibit-read-only t)
    343                 (magit-insert-section--parent  nil)
    344                 (magit-insert-section--oldroot nil))
    345             (make-local-variable 'text-property-default-nonsticky)
    346             (magit-insert-section (processbuf)
    347               (insert "\n")))))
    348       (unless nodisplay
    349         (magit-display-buffer buffer))
    350       buffer)))
    351 
    352 (defun magit-process-kill ()
    353   "Kill the process at point."
    354   (interactive)
    355   (when-let ((process (magit-section-value-if 'process)))
    356     (unless (eq (process-status process) 'run)
    357       (user-error "Process isn't running"))
    358     (magit-confirm 'kill-process)
    359     (kill-process process)))
    360 
    361 ;;; Synchronous Processes
    362 
    363 (defvar magit-process-raise-error nil)
    364 
    365 (defun magit-git (&rest args)
    366   "Call Git synchronously in a separate process, for side-effects.
    367 
    368 Option `magit-git-executable' specifies the Git executable.
    369 The arguments ARGS specify arguments to Git, they are flattened
    370 before use.
    371 
    372 Process output goes into a new section in the buffer returned by
    373 `magit-process-buffer'.  If Git exits with a non-zero status,
    374 then raise an error."
    375   (let ((magit-process-raise-error t))
    376     (magit-call-git args)))
    377 
    378 (defun magit-run-git (&rest args)
    379   "Call Git synchronously in a separate process, and refresh.
    380 
    381 Function `magit-git-executable' specifies the Git executable and
    382 option `magit-git-global-arguments' specifies constant arguments.
    383 The arguments ARGS specify arguments to Git, they are flattened
    384 before use.
    385 
    386 After Git returns, the current buffer (if it is a Magit buffer)
    387 as well as the current repository's status buffer are refreshed.
    388 
    389 Process output goes into a new section in the buffer returned by
    390 `magit-process-buffer'."
    391   (let ((magit--refresh-cache (list (cons 0 0))))
    392     (magit-call-git args)
    393     (when (member (car args) '("init" "clone"))
    394       ;; Creating a new repository invalidates the cache.
    395       (setq magit--refresh-cache nil))
    396     (magit-refresh)))
    397 
    398 (defvar magit-pre-call-git-hook nil)
    399 
    400 (defun magit-call-git (&rest args)
    401   "Call Git synchronously in a separate process.
    402 
    403 Function `magit-git-executable' specifies the Git executable and
    404 option `magit-git-global-arguments' specifies constant arguments.
    405 The arguments ARGS specify arguments to Git, they are flattened
    406 before use.
    407 
    408 Process output goes into a new section in the buffer returned by
    409 `magit-process-buffer'."
    410   (run-hooks 'magit-pre-call-git-hook)
    411   (let ((default-process-coding-system (magit--process-coding-system)))
    412     (apply #'magit-call-process
    413            (magit-git-executable)
    414            (magit-process-git-arguments args))))
    415 
    416 (defun magit-call-process (program &rest args)
    417   "Call PROGRAM synchronously in a separate process.
    418 Process output goes into a new section in the buffer returned by
    419 `magit-process-buffer'."
    420   (pcase-let ((`(,process-buf . ,section)
    421                (magit-process-setup program args)))
    422     (magit-process-finish
    423      (let ((inhibit-read-only t))
    424        (apply #'magit-process-file program nil process-buf nil args))
    425      process-buf (current-buffer) default-directory section)))
    426 
    427 (defun magit-process-git (destination &rest args)
    428   "Call Git synchronously in a separate process, returning its exit code.
    429 DESTINATION specifies how to handle the output, like for
    430 `call-process', except that file handlers are supported.
    431 Enable Cygwin's \"noglob\" option during the call and
    432 ensure unix eol conversion."
    433   (apply #'magit-process-file
    434          (magit-git-executable)
    435          nil destination nil
    436          (magit-process-git-arguments args)))
    437 
    438 (defun magit-process-file (process &optional infile buffer display &rest args)
    439   "Process files synchronously in a separate process.
    440 Identical to `process-file' but temporarily enable Cygwin's
    441 \"noglob\" option during the call and ensure unix eol
    442 conversion."
    443   (when magit-process-extreme-logging
    444     (let ((inhibit-message t))
    445       (message "$ %s" (magit-process--format-arguments process args))))
    446   (let ((process-environment (magit-process-environment))
    447         (default-process-coding-system (magit--process-coding-system)))
    448     (apply #'process-file process infile buffer display args)))
    449 
    450 (defun magit-process-environment ()
    451   ;; The various w32 hacks are only applicable when running on the
    452   ;; local machine.  As of Emacs 25.1, a local binding of
    453   ;; process-environment different from the top-level value affects
    454   ;; the environment used in
    455   ;; tramp-sh-handle-{start-file-process,process-file}.
    456   (let ((local (not (file-remote-p default-directory))))
    457     (append magit-git-environment
    458             (and local
    459                  (cdr (assoc magit-git-executable magit-git-w32-path-hack)))
    460             (and local magit-need-cygwin-noglob
    461                  (mapcar (lambda (var)
    462                            (concat var "=" (if-let ((val (getenv var)))
    463                                                (concat val " noglob")
    464                                              "noglob")))
    465                          '("CYGWIN" "MSYS")))
    466             process-environment)))
    467 
    468 (defvar magit-this-process nil)
    469 
    470 (defun magit-run-git-with-input (&rest args)
    471   "Call Git in a separate process.
    472 ARGS is flattened and then used as arguments to Git.
    473 
    474 The current buffer's content is used as the process's standard
    475 input.  The buffer is assumed to be temporary and thus OK to
    476 modify.
    477 
    478 Function `magit-git-executable' specifies the Git executable and
    479 option `magit-git-global-arguments' specifies constant arguments.
    480 The remaining arguments ARGS specify arguments to Git, they are
    481 flattened before use."
    482   (when (eq system-type 'windows-nt)
    483     ;; On w32, git expects UTF-8 encoded input, ignore any user
    484     ;; configuration telling us otherwise (see #3250).
    485     (encode-coding-region (point-min) (point-max) 'utf-8-unix))
    486   (if (file-remote-p default-directory)
    487       ;; We lack `process-file-region', so fall back to asynch +
    488       ;; waiting in remote case.
    489       (progn
    490         (magit-start-git (current-buffer) args)
    491         (while (and magit-this-process
    492                     (eq (process-status magit-this-process) 'run))
    493           (sleep-for 0.005)))
    494     (run-hooks 'magit-pre-call-git-hook)
    495     (pcase-let* ((process-environment (magit-process-environment))
    496                  (default-process-coding-system (magit--process-coding-system))
    497                  (flat-args (magit-process-git-arguments args))
    498                  (`(,process-buf . ,section)
    499                   (magit-process-setup (magit-git-executable) flat-args))
    500                  (inhibit-read-only t))
    501       (magit-process-finish
    502        (apply #'call-process-region (point-min) (point-max)
    503               (magit-git-executable) nil process-buf nil flat-args)
    504        process-buf nil default-directory section))))
    505 
    506 ;;; Asynchronous Processes
    507 
    508 (defun magit-run-git-async (&rest args)
    509   "Start Git, prepare for refresh, and return the process object.
    510 ARGS is flattened and then used as arguments to Git.
    511 
    512 Display the command line arguments in the echo area.
    513 
    514 After Git returns some buffers are refreshed: the buffer that was
    515 current when this function was called (if it is a Magit buffer
    516 and still alive), as well as the respective Magit status buffer.
    517 
    518 See `magit-start-process' for more information."
    519   (message "Running %s %s" (magit-git-executable)
    520            (let ((m (mapconcat #'identity (flatten-tree args) " ")))
    521              (remove-list-of-text-properties 0 (length m) '(face) m)
    522              m))
    523   (magit-start-git nil args))
    524 
    525 (defun magit-run-git-with-editor (&rest args)
    526   "Export GIT_EDITOR and start Git.
    527 Also prepare for refresh and return the process object.
    528 ARGS is flattened and then used as arguments to Git.
    529 
    530 Display the command line arguments in the echo area.
    531 
    532 After Git returns some buffers are refreshed: the buffer that was
    533 current when this function was called (if it is a Magit buffer
    534 and still alive), as well as the respective Magit status buffer.
    535 
    536 See `magit-start-process' and `with-editor' for more information."
    537   (magit--record-separated-gitdir)
    538   (magit-with-editor (magit-run-git-async args)))
    539 
    540 (defun magit-run-git-sequencer (&rest args)
    541   "Export GIT_EDITOR and start Git.
    542 Also prepare for refresh and return the process object.
    543 ARGS is flattened and then used as arguments to Git.
    544 
    545 Display the command line arguments in the echo area.
    546 
    547 After Git returns some buffers are refreshed: the buffer that was
    548 current when this function was called (if it is a Magit buffer
    549 and still alive), as well as the respective Magit status buffer.
    550 If the sequence stops at a commit, make the section representing
    551 that commit the current section by moving `point' there.
    552 
    553 See `magit-start-process' and `with-editor' for more information."
    554   (apply #'magit-run-git-with-editor args)
    555   (set-process-sentinel magit-this-process #'magit-sequencer-process-sentinel)
    556   magit-this-process)
    557 
    558 (defvar magit-pre-start-git-hook nil)
    559 
    560 (defun magit-start-git (input &rest args)
    561   "Start Git, prepare for refresh, and return the process object.
    562 
    563 If INPUT is non-nil, it has to be a buffer or the name of an
    564 existing buffer.  The buffer content becomes the processes
    565 standard input.
    566 
    567 Function `magit-git-executable' specifies the Git executable and
    568 option `magit-git-global-arguments' specifies constant arguments.
    569 The remaining arguments ARGS specify arguments to Git, they are
    570 flattened before use.
    571 
    572 After Git returns some buffers are refreshed: the buffer that was
    573 current when this function was called (if it is a Magit buffer
    574 and still alive), as well as the respective Magit status buffer.
    575 
    576 See `magit-start-process' for more information."
    577   (run-hooks 'magit-pre-start-git-hook)
    578   (let ((default-process-coding-system (magit--process-coding-system)))
    579     (apply #'magit-start-process (magit-git-executable) input
    580            (magit-process-git-arguments args))))
    581 
    582 (defun magit-start-process (program &optional input &rest args)
    583   "Start PROGRAM, prepare for refresh, and return the process object.
    584 
    585 If optional argument INPUT is non-nil, it has to be a buffer or
    586 the name of an existing buffer.  The buffer content becomes the
    587 processes standard input.
    588 
    589 The process is started using `start-file-process' and then setup
    590 to use the sentinel `magit-process-sentinel' and the filter
    591 `magit-process-filter'.  Information required by these functions
    592 is stored in the process object.  When this function returns the
    593 process has not started to run yet so it is possible to override
    594 the sentinel and filter.
    595 
    596 After the process returns, `magit-process-sentinel' refreshes the
    597 buffer that was current when `magit-start-process' was called (if
    598 it is a Magit buffer and still alive), as well as the respective
    599 Magit status buffer."
    600   (pcase-let*
    601       ((`(,process-buf . ,section)
    602         (magit-process-setup program args))
    603        (process
    604         (let ((process-connection-type ;t=pty nil=pipe
    605                (or
    606                 ;; With Tramp, maybe force use a pty.  #4720
    607                 (and (file-remote-p default-directory)
    608                      (eq magit-tramp-pipe-stty-settings 'pty))
    609                 ;; Without input, don't use a pty, because it would
    610                 ;; set icrnl, which would modify the input.  #20
    611                 (and (not input) magit-process-connection-type)))
    612               (tramp-pipe-stty-settings
    613                (or (and (not (eq magit-tramp-pipe-stty-settings 'pty))
    614                         ;; Defaults to "", to allow staging hunks over
    615                         ;; Tramp again.  #4720
    616                         magit-tramp-pipe-stty-settings)
    617                    tramp-pipe-stty-settings))
    618               (process-environment (magit-process-environment))
    619               (default-process-coding-system (magit--process-coding-system)))
    620           (apply #'start-file-process
    621                  (file-name-nondirectory program)
    622                  process-buf program args))))
    623     (with-editor-set-process-filter process #'magit-process-filter)
    624     (set-process-sentinel process #'magit-process-sentinel)
    625     (set-process-buffer   process process-buf)
    626     (when (eq system-type 'windows-nt)
    627       ;; On w32, git expects UTF-8 encoded input, ignore any user
    628       ;; configuration telling us otherwise.
    629       (set-process-coding-system process nil 'utf-8-unix))
    630     (process-put process 'section section)
    631     (process-put process 'command-buf (current-buffer))
    632     (process-put process 'default-dir default-directory)
    633     (when magit-inhibit-refresh
    634       (process-put process 'inhibit-refresh t))
    635     (oset section process process)
    636     (with-current-buffer process-buf
    637       (set-marker (process-mark process) (point)))
    638     (when input
    639       (with-current-buffer input
    640         (process-send-region process (point-min) (point-max))
    641         ;; `process-send-eof' appears to be broken over
    642         ;;  Tramp from Windows. See #3624 and bug#43226.
    643         (if (and (eq system-type 'windows-nt)
    644                  (file-remote-p (process-get process 'default-dir) nil t))
    645             (process-send-string process "")
    646           (process-send-eof process))))
    647     (setq magit-this-process process)
    648     (oset section value process)
    649     (magit-process-display-buffer process)
    650     process))
    651 
    652 (defun magit-parse-git-async (&rest args)
    653   (setq args (magit-process-git-arguments args))
    654   (let ((command-buf (current-buffer))
    655         (process-buf (generate-new-buffer " *temp*"))
    656         (toplevel (magit-toplevel)))
    657     (with-current-buffer process-buf
    658       (setq default-directory toplevel)
    659       (let ((process
    660              (let ((process-connection-type nil)
    661                    (process-environment (magit-process-environment))
    662                    (default-process-coding-system
    663                     (magit--process-coding-system)))
    664                (apply #'start-file-process "git" process-buf
    665                       (magit-git-executable) args))))
    666         (process-put process 'command-buf command-buf)
    667         (process-put process 'parsed (point))
    668         (setq magit-this-process process)
    669         process))))
    670 
    671 ;;; Process Internals
    672 
    673 (defun magit-process-setup (program args)
    674   (magit-process-set-mode-line program args)
    675   (let ((pwd default-directory)
    676         (buf (magit-process-buffer t)))
    677     (cons buf (with-current-buffer buf
    678                 (prog1 (magit-process-insert-section pwd program args nil nil)
    679                   (backward-char 1))))))
    680 
    681 (defun magit-process-insert-section (pwd program args &optional errcode errlog)
    682   (let ((inhibit-read-only t)
    683         (magit-insert-section--parent magit-root-section)
    684         (magit-insert-section--oldroot nil))
    685     (goto-char (1- (point-max)))
    686     (magit-insert-section (process)
    687       (insert (if errcode
    688                   (format "%3s " (propertize (number-to-string errcode)
    689                                              'font-lock-face 'magit-process-ng))
    690                 "run "))
    691       (when magit-process-timestamp-format
    692         (insert (format-time-string magit-process-timestamp-format) " "))
    693       (unless (equal (expand-file-name pwd)
    694                      (expand-file-name default-directory))
    695         (insert (file-relative-name pwd default-directory) ?\s))
    696       (insert (magit-process--format-arguments program args))
    697       (magit-insert-heading)
    698       (when errlog
    699         (if (bufferp errlog)
    700             (insert (with-current-buffer errlog
    701                       (buffer-substring-no-properties (point-min) (point-max))))
    702           (insert-file-contents errlog)
    703           (goto-char (1- (point-max)))))
    704       (insert "\n"))))
    705 
    706 (defun magit-process--format-arguments (program args)
    707   (cond
    708    ((and args (equal program (magit-git-executable)))
    709     (let ((global (length magit-git-global-arguments)))
    710       (concat
    711        (propertize (file-name-nondirectory program)
    712                    'font-lock-face 'magit-section-heading)
    713        " "
    714        (propertize (magit--ellipsis)
    715                    'font-lock-face 'magit-section-heading
    716                    'help-echo (mapconcat #'identity (seq-take args global) " "))
    717        " "
    718        (propertize (mapconcat #'shell-quote-argument (seq-drop args global) " ")
    719                    'font-lock-face 'magit-section-heading))))
    720    ((and args (equal program shell-file-name))
    721     (propertize (cadr args)
    722                 'font-lock-face 'magit-section-heading))
    723    (t
    724     (concat (propertize (file-name-nondirectory program)
    725                         'font-lock-face 'magit-section-heading)
    726             " "
    727             (propertize (mapconcat #'shell-quote-argument args " ")
    728                         'font-lock-face 'magit-section-heading)))))
    729 
    730 (defun magit-process-truncate-log ()
    731   (let* ((head nil)
    732          (tail (oref magit-root-section children))
    733          (count (length tail)))
    734     (when (> (1+ count) magit-process-log-max)
    735       (while (and (cdr tail)
    736                   (> count (/ magit-process-log-max 2)))
    737         (let* ((inhibit-read-only t)
    738                (section (car tail))
    739                (process (oref section process)))
    740           (cond ((not process))
    741                 ((memq (process-status process) '(exit signal))
    742                  (delete-region (oref section start)
    743                                 (1+ (oref section end)))
    744                  (cl-decf count))
    745                 (t
    746                  (push section head))))
    747         (pop tail))
    748       (oset magit-root-section children
    749             (nconc (reverse head) tail)))))
    750 
    751 (defun magit-process-sentinel (process event)
    752   "Default sentinel used by `magit-start-process'."
    753   (when (memq (process-status process) '(exit signal))
    754     (setq event (substring event 0 -1))
    755     (when (string-match "^finished" event)
    756       (message (concat (capitalize (process-name process)) " finished")))
    757     (magit-process-finish process)
    758     (when (eq process magit-this-process)
    759       (setq magit-this-process nil))
    760     (unless (process-get process 'inhibit-refresh)
    761       (let ((command-buf (process-get process 'command-buf)))
    762         (if (buffer-live-p command-buf)
    763             (with-current-buffer command-buf
    764               (magit-refresh))
    765           (with-temp-buffer
    766             (setq default-directory (process-get process 'default-dir))
    767             (magit-refresh)))))))
    768 
    769 (defun magit-sequencer-process-sentinel (process event)
    770   "Special sentinel used by `magit-run-git-sequencer'."
    771   (when (memq (process-status process) '(exit signal))
    772     (magit-process-sentinel process event)
    773     (when-let* ((process-buf (process-buffer process))
    774                 ((buffer-live-p process-buf))
    775                 (status-buf (with-current-buffer process-buf
    776                               (magit-get-mode-buffer 'magit-status-mode))))
    777       (with-current-buffer status-buf
    778         (when-let ((section
    779                     (magit-get-section
    780                      `((commit . ,(magit-rev-parse "HEAD"))
    781                        (,(pcase (car (seq-drop
    782                                       (process-command process)
    783                                       (1+ (length magit-git-global-arguments))))
    784                            ((or "rebase" "am") 'rebase-sequence)
    785                            ((or "cherry-pick" "revert") 'sequence)))
    786                        (status)))))
    787           (goto-char (oref section start))
    788           (magit-section-update-highlight))))))
    789 
    790 (defun magit-process-filter (proc string)
    791   "Default filter used by `magit-start-process'."
    792   (with-current-buffer (process-buffer proc)
    793     (let ((inhibit-read-only t))
    794       (goto-char (process-mark proc))
    795       ;; Find last ^M in string.  If one was found, ignore
    796       ;; everything before it and delete the current line.
    797       (when-let ((ret-pos (cl-position ?\r string :from-end t)))
    798         (cl-callf substring string (1+ ret-pos))
    799         (delete-region (line-beginning-position) (point)))
    800       (setq string (magit-process-remove-bogus-errors string))
    801       (insert (propertize string 'magit-section
    802                           (process-get proc 'section)))
    803       (set-marker (process-mark proc) (point))
    804       ;; Make sure prompts are matched after removing ^M.
    805       (magit-process-yes-or-no-prompt proc string)
    806       (magit-process-username-prompt  proc string)
    807       (magit-process-password-prompt  proc string)
    808       (run-hook-with-args-until-success 'magit-process-prompt-functions
    809                                         proc string))))
    810 
    811 (defun magit-process-make-keymap (process parent)
    812   "Remap `abort-minibuffers' to a command that also kills PROCESS.
    813 PARENT is used as the parent of the returned keymap."
    814   (let ((cmd (lambda ()
    815                (interactive)
    816                (ignore-errors (kill-process process))
    817                (if (fboundp 'abort-minibuffers)
    818                    (abort-minibuffers)
    819                  (abort-recursive-edit)))))
    820     (define-keymap :parent parent
    821       "C-g" cmd
    822       "<remap> <abort-minibuffers>" cmd
    823       "<remap> <abort-recursive-edit>" cmd)))
    824 
    825 (defmacro magit-process-kill-on-abort (process &rest body)
    826   (declare (indent 1)
    827            (debug (form body))
    828            (obsolete magit-process-make-keymap "Magit 4.0.0"))
    829   `(let ((minibuffer-local-map
    830           (magit-process-make-keymap ,process minibuffer-local-map)))
    831      ,@body))
    832 
    833 (defun magit-process-remove-bogus-errors (str)
    834   (save-match-data
    835     (when (string-match "^\\(\\*ERROR\\*: \\)Canceled by user" str)
    836       (setq str (replace-match "" nil nil str 1)))
    837     (when (string-match "^error: There was a problem with the editor.*\n" str)
    838       (setq str (replace-match "" nil nil str)))
    839     (when (string-match
    840            "^Please supply the message using either -m or -F option\\.\n" str)
    841       (setq str (replace-match "" nil nil str))))
    842   str)
    843 
    844 (defun magit-process-yes-or-no-prompt (process string)
    845   "Forward Yes-or-No prompts to the user."
    846   (when-let ((beg (string-match magit-process-yes-or-no-prompt-regexp string)))
    847     (process-send-string
    848      process
    849      (if (save-match-data
    850            (let ((max-mini-window-height 30)
    851                  (minibuffer-local-map
    852                   (magit-process-make-keymap process minibuffer-local-map))
    853                  ;; In case yes-or-no-p is fset to that, but does
    854                  ;; not cover use-dialog-box-p and y-or-n-p-read-key.
    855                  (y-or-n-p-map
    856                   (magit-process-make-keymap process y-or-n-p-map)))
    857              (yes-or-no-p (substring string 0 beg))))
    858          (concat (downcase (match-string 1 string)) "\n")
    859        (concat (downcase (match-string 2 string)) "\n")))))
    860 
    861 (defun magit-process-password-auth-source (key)
    862   "Use `auth-source-search' to get a password.
    863 If found, return the password.  Otherwise, return nil.
    864 
    865 To use this function add it to the appropriate hook
    866   (add-hook \\='magit-process-find-password-functions
    867             \\='magit-process-password-auth-source)
    868 
    869 KEY typically derives from a prompt such as:
    870   Password for \\='https://yourname@github.com\\='
    871 in which case it would be the string
    872   yourname@github.com
    873 which matches the ~/.authinfo.gpg entry
    874   machine github.com login yourname password 12345
    875 or iff that is undefined, for backward compatibility
    876   machine yourname@github.com password 12345
    877 
    878 On github.com you should not use your password but a
    879 personal access token, see [1].  For information about
    880 the peculiarities of other forges, please consult the
    881 respective documentation.
    882 
    883 After manually editing ~/.authinfo.gpg you must reset
    884 the cache using
    885   M-x auth-source-forget-all-cached RET
    886 
    887 The above will save you from having to repeatedly type
    888 your token or password, but you might still repeatedly
    889 be asked for your username.  To prevent that, change an
    890 URL like
    891   https://github.com/foo/bar.git
    892 to
    893   https://yourname@github.com/foo/bar.git
    894 
    895 Instead of changing all such URLs manually, they can
    896 be translated on the fly by doing this once
    897   git config --global \
    898     url.https://yourname@github.com.insteadOf \
    899     https://github.com
    900 
    901 [1]: https://docs.github.com/en/github/authenticating-to-github/creating-a-personal-access-token."
    902   (require 'auth-source)
    903   (and (fboundp 'auth-source-search)
    904        (string-match "\\`\\(.+\\)@\\([^@]+\\)\\'" key)
    905        (let* ((user (match-string 1 key))
    906               (host (match-string 2 key))
    907               (secret
    908                (plist-get
    909                 (car (or (auth-source-search :max 1 :host host :user user)
    910                          (auth-source-search :max 1 :host key)))
    911                 :secret)))
    912          (if (functionp secret)
    913              (funcall secret)
    914            secret))))
    915 
    916 (defun magit-process-git-credential-manager-core (process string)
    917   "Authenticate using `git-credential-manager-core'.
    918 
    919 To use this function add it to the appropriate hook
    920   (add-hook \\='magit-process-prompt-functions
    921             \\='magit-process-git-credential-manager-core)"
    922   (and (string-match "^option (enter for default): $" string)
    923        (progn
    924          (magit-process-buffer)
    925          (let ((option (format "%c\n"
    926                                (read-char-choice "Option: " '(?\r ?\j ?1 ?2)))))
    927            (insert-before-markers-and-inherit option)
    928            (process-send-string process option)))))
    929 
    930 (defun magit-process-password-prompt (process string)
    931   "Find a password based on prompt STRING and send it to git.
    932 Use `magit-process-password-prompt-regexps' to find a known
    933 prompt.  If and only if one is found, then call functions in
    934 `magit-process-find-password-functions' until one of them returns
    935 the password.  If all functions return nil, then read the password
    936 from the user."
    937   (when-let ((prompt (magit-process-match-prompt
    938                       magit-process-password-prompt-regexps string)))
    939     (process-send-string
    940      process
    941      (concat (or (and-let* ((key (match-string 99 string)))
    942                    (run-hook-with-args-until-success
    943                     'magit-process-find-password-functions key))
    944                  (let ((read-passwd-map
    945                         (magit-process-make-keymap process read-passwd-map)))
    946                    (read-passwd prompt)))
    947              "\n"))))
    948 
    949 (defun magit-process-username-prompt (process string)
    950   "Forward username prompts to the user."
    951   (when-let ((prompt (magit-process-match-prompt
    952                       magit-process-username-prompt-regexps string)))
    953     (process-send-string
    954      process
    955      (let ((minibuffer-local-map
    956             (magit-process-make-keymap process minibuffer-local-map)))
    957        (concat (read-string prompt nil nil (user-login-name)) "\n")))))
    958 
    959 (defun magit-process-match-prompt (prompts string)
    960   "Match STRING against PROMPTS and set match data.
    961 Return the matched string suffixed with \": \", if needed."
    962   (when (--any-p (string-match it string) prompts)
    963     (let ((prompt (match-string 0 string)))
    964       (cond ((string-suffix-p ": " prompt) prompt)
    965             ((string-suffix-p ":"  prompt) (concat prompt " "))
    966             (t                             (concat prompt ": "))))))
    967 
    968 (defun magit--process-coding-system ()
    969   (let ((fro (or magit-git-output-coding-system
    970                  (car default-process-coding-system)))
    971         (to (cdr default-process-coding-system)))
    972     (if magit-process-ensure-unix-line-ending
    973         (cons (coding-system-change-eol-conversion fro 'unix)
    974               (coding-system-change-eol-conversion to 'unix))
    975       (cons fro to))))
    976 
    977 (defvar magit-credential-hook nil
    978   "Hook run before Git needs credentials.")
    979 
    980 (defvar magit-credential-cache-daemon-process nil)
    981 
    982 (defun magit-maybe-start-credential-cache-daemon ()
    983   "Maybe start a `git-credential-cache--daemon' process.
    984 
    985 If such a process is already running or if the value of option
    986 `magit-credential-cache-daemon-socket' is nil, then do nothing.
    987 Otherwise start the process passing the value of that options
    988 as argument."
    989   (unless (or (not magit-credential-cache-daemon-socket)
    990               (process-live-p magit-credential-cache-daemon-process)
    991               (memq magit-credential-cache-daemon-process
    992                     (list-system-processes)))
    993     (setq magit-credential-cache-daemon-process
    994           (or (--first (let* ((attr (process-attributes it))
    995                               (comm (cdr (assq 'comm attr)))
    996                               (user (cdr (assq 'user attr))))
    997                          (and (string= comm "git-credential-cache--daemon")
    998                               (string= user user-login-name)))
    999                        (list-system-processes))
   1000               (condition-case nil
   1001                   (start-process "git-credential-cache--daemon"
   1002                                  " *git-credential-cache--daemon*"
   1003                                  (magit-git-executable)
   1004                                  "credential-cache--daemon"
   1005                                  magit-credential-cache-daemon-socket)
   1006                 ;; Some Git implementations (e.g., Windows) won't have
   1007                 ;; this program; if we fail the first time, stop trying.
   1008                 ((debug error)
   1009                  (remove-hook 'magit-credential-hook
   1010                               #'magit-maybe-start-credential-cache-daemon)))))))
   1011 
   1012 (add-hook 'magit-credential-hook #'magit-maybe-start-credential-cache-daemon)
   1013 
   1014 (defun tramp-sh-handle-start-file-process--magit-tramp-process-environment
   1015     (fn name buffer program &rest args)
   1016   (if magit-tramp-process-environment
   1017       (apply fn name buffer
   1018              (car magit-tramp-process-environment)
   1019              (append (cdr magit-tramp-process-environment)
   1020                      (cons program args)))
   1021     (apply fn name buffer program args)))
   1022 
   1023 (advice-add 'tramp-sh-handle-start-file-process :around
   1024             #'tramp-sh-handle-start-file-process--magit-tramp-process-environment)
   1025 
   1026 (defun tramp-sh-handle-process-file--magit-tramp-process-environment
   1027     (fn program &optional infile destination display &rest args)
   1028   (if magit-tramp-process-environment
   1029       (apply fn "env" infile destination display
   1030              (append magit-tramp-process-environment
   1031                      (cons program args)))
   1032     (apply fn program infile destination display args)))
   1033 
   1034 (advice-add 'tramp-sh-handle-process-file :around
   1035             #'tramp-sh-handle-process-file--magit-tramp-process-environment)
   1036 
   1037 (defvar-keymap magit-mode-line-process-map
   1038   :doc "Keymap for `mode-line-process'."
   1039   "<mode-line> <mouse-1>" ''magit-process-buffer)
   1040 
   1041 (defun magit-process-set-mode-line (program args)
   1042   "Display the git command (sans arguments) in the mode line."
   1043   (when (equal program (magit-git-executable))
   1044     (setq args (nthcdr (length magit-git-global-arguments) args)))
   1045   (let ((str (concat " " (propertize
   1046                           (concat (file-name-nondirectory program)
   1047                                   (and args (concat " " (car args))))
   1048                           'mouse-face 'highlight
   1049                           'keymap magit-mode-line-process-map
   1050                           'help-echo "mouse-1: Show process buffer"
   1051                           'font-lock-face 'magit-mode-line-process))))
   1052     (magit-repository-local-set 'mode-line-process str)
   1053     (dolist (buf (magit-mode-get-buffers))
   1054       (with-current-buffer buf
   1055         (setq mode-line-process str)))
   1056     (force-mode-line-update t)))
   1057 
   1058 (defun magit-process-set-mode-line-error-status (&optional error str)
   1059   "Apply an error face to the string set by `magit-process-set-mode-line'.
   1060 
   1061 If ERROR is supplied, include it in the `mode-line-process' tooltip.
   1062 
   1063 If STR is supplied, it replaces the `mode-line-process' text."
   1064   (setq str (or str (magit-repository-local-get 'mode-line-process)))
   1065   (when str
   1066     (setq error (format "%smouse-1: Show process buffer"
   1067                         (if (stringp error)
   1068                             (concat error "\n\n")
   1069                           "")))
   1070     (setq str (concat " " (propertize
   1071                            (substring-no-properties str 1)
   1072                            'mouse-face 'highlight
   1073                            'keymap magit-mode-line-process-map
   1074                            'help-echo error
   1075                            'font-lock-face 'magit-mode-line-process-error)))
   1076     (magit-repository-local-set 'mode-line-process str)
   1077     (dolist (buf (magit-mode-get-buffers))
   1078       (with-current-buffer buf
   1079         (setq mode-line-process str)))
   1080     (force-mode-line-update t)
   1081     ;; We remove any error status from the mode line when a magit
   1082     ;; buffer is refreshed (see `magit-refresh-buffer'), but we must
   1083     ;; ensure that we ignore any refreshes during the remainder of the
   1084     ;; current command -- otherwise a newly-set error status would be
   1085     ;; removed before it was seen.  We set a flag which prevents the
   1086     ;; status from being removed prior to the next command, so that
   1087     ;; the error status is guaranteed to remain visible until then.
   1088     (let ((repokey (magit-repository-local-repository)))
   1089       ;; The following closure captures the repokey value, and is
   1090       ;; added to `pre-command-hook'.
   1091       (cl-labels ((enable-magit-process-unset-mode-line ()
   1092                     ;; Remove ourself from the hook variable, so
   1093                     ;; that we only run once.
   1094                     (remove-hook 'pre-command-hook
   1095                                  #'enable-magit-process-unset-mode-line)
   1096                     ;; Clear the inhibit flag for the repository in
   1097                     ;; which we set it.
   1098                     (magit-repository-local-set
   1099                      'inhibit-magit-process-unset-mode-line nil repokey)))
   1100         ;; Set the inhibit flag until the next command is invoked.
   1101         (magit-repository-local-set
   1102          'inhibit-magit-process-unset-mode-line t repokey)
   1103         (add-hook 'pre-command-hook
   1104                   #'enable-magit-process-unset-mode-line)))))
   1105 
   1106 (defun magit-process-unset-mode-line-error-status ()
   1107   "Remove any current error status from the mode line."
   1108   (let ((status (or mode-line-process
   1109                     (magit-repository-local-get 'mode-line-process))))
   1110     (when (and status
   1111                (eq (get-text-property 1 'font-lock-face status)
   1112                    'magit-mode-line-process-error))
   1113       (magit-process-unset-mode-line))))
   1114 
   1115 (add-hook 'magit-refresh-buffer-hook
   1116           #'magit-process-unset-mode-line-error-status)
   1117 
   1118 (defun magit-process-unset-mode-line (&optional directory)
   1119   "Remove the git command from the mode line."
   1120   (let ((default-directory (or directory default-directory)))
   1121     (unless (magit-repository-local-get 'inhibit-magit-process-unset-mode-line)
   1122       (magit-repository-local-set 'mode-line-process nil)
   1123       (dolist (buf (magit-mode-get-buffers))
   1124         (with-current-buffer buf (setq mode-line-process nil)))
   1125       (force-mode-line-update t))))
   1126 
   1127 (defvar magit-process-error-message-regexps
   1128   (list "^\\*ERROR\\*: Canceled by user$"
   1129         "^\\(?:error\\|fatal\\|git\\): \\(.*\\)$"
   1130         "^\\(Cannot rebase:.*\\)$"))
   1131 
   1132 (define-error 'magit-git-error "Git error")
   1133 
   1134 (defun magit-process-error-summary (process-buf section)
   1135   "A one-line error summary from the given SECTION."
   1136   (or (and (buffer-live-p process-buf)
   1137            (with-current-buffer process-buf
   1138              (and (oref section content)
   1139                   (save-excursion
   1140                     (goto-char (oref section end))
   1141                     (run-hook-wrapped
   1142                      'magit-process-error-message-regexps
   1143                      (lambda (re)
   1144                        (save-excursion
   1145                          (and (re-search-backward
   1146                                re (oref section start) t)
   1147                               (or (match-string-no-properties 1)
   1148                                   (and (not magit-process-raise-error)
   1149                                        'suppressed))))))))))
   1150       "Git failed"))
   1151 
   1152 (defun magit-process-error-tooltip (process-buf section)
   1153   "Returns the text from SECTION of the PROCESS-BUF buffer.
   1154 
   1155 Limited by `magit-process-error-tooltip-max-lines'."
   1156   (and (integerp magit-process-error-tooltip-max-lines)
   1157        (> magit-process-error-tooltip-max-lines 0)
   1158        (buffer-live-p process-buf)
   1159        (with-current-buffer process-buf
   1160          (save-excursion
   1161            (goto-char (or (oref section content)
   1162                           (oref section start)))
   1163            (buffer-substring-no-properties
   1164             (point)
   1165             (save-excursion
   1166               (forward-line magit-process-error-tooltip-max-lines)
   1167               (goto-char
   1168                (if (> (point) (oref section end))
   1169                    (oref section end)
   1170                  (point)))
   1171               ;; Remove any trailing whitespace.
   1172               (when (re-search-backward "[^[:space:]\n]"
   1173                                         (oref section start) t)
   1174                 (forward-char 1))
   1175               (point)))))))
   1176 
   1177 (defvar-local magit-this-error nil)
   1178 
   1179 (defvar magit-process-finish-apply-ansi-colors nil)
   1180 
   1181 (defun magit-process-finish (arg &optional process-buf command-buf
   1182                                  default-dir section)
   1183   (unless (integerp arg)
   1184     (setq process-buf (process-buffer arg))
   1185     (setq command-buf (process-get arg 'command-buf))
   1186     (setq default-dir (process-get arg 'default-dir))
   1187     (setq section     (process-get arg 'section))
   1188     (setq arg         (process-exit-status arg)))
   1189   (when (fboundp 'dired-uncache)
   1190     (dired-uncache default-dir))
   1191   (when (buffer-live-p process-buf)
   1192     (with-current-buffer process-buf
   1193       (let ((inhibit-read-only t)
   1194             (marker (oref section start)))
   1195         (goto-char marker)
   1196         (save-excursion
   1197           (delete-char 3)
   1198           (set-marker-insertion-type marker nil)
   1199           (insert (propertize (format "%3s" arg)
   1200                               'magit-section section
   1201                               'font-lock-face (if (= arg 0)
   1202                                                   'magit-process-ok
   1203                                                 'magit-process-ng)))
   1204           (set-marker-insertion-type marker t))
   1205         (when magit-process-finish-apply-ansi-colors
   1206           (ansi-color-apply-on-region (oref section content)
   1207                                       (oref section end)))
   1208         (if (= (oref section end)
   1209                (+ (line-end-position) 2))
   1210             (save-excursion
   1211               (goto-char (1+ (line-end-position)))
   1212               (delete-char -1)
   1213               (oset section content nil))
   1214           (when (and (= arg 0)
   1215                      (not (--any-p (eq (window-buffer it) process-buf)
   1216                                    (window-list))))
   1217             (magit-section-hide section))))))
   1218   (if (= arg 0)
   1219       ;; Unset the `mode-line-process' value upon success.
   1220       (magit-process-unset-mode-line default-dir)
   1221     ;; Otherwise process the error.
   1222     (let ((msg (magit-process-error-summary process-buf section)))
   1223       ;; Change `mode-line-process' to an error face upon failure.
   1224       (if magit-process-display-mode-line-error
   1225           (magit-process-set-mode-line-error-status
   1226            (or (magit-process-error-tooltip process-buf section)
   1227                msg))
   1228         (magit-process-unset-mode-line default-dir))
   1229       ;; Either signal the error, or else display the error summary in
   1230       ;; the status buffer and with a message in the echo area.
   1231       (cond
   1232        (magit-process-raise-error
   1233         (signal 'magit-git-error (list (format "%s (in %s)" msg default-dir))))
   1234        ((not (eq msg 'suppressed))
   1235         (when (buffer-live-p process-buf)
   1236           (with-current-buffer process-buf
   1237             (when-let ((status-buf (magit-get-mode-buffer 'magit-status-mode)))
   1238               (with-current-buffer status-buf
   1239                 (setq magit-this-error msg)))))
   1240         (message "%s ... [%s buffer %s for details]" msg
   1241                  (if-let ((key (and (buffer-live-p command-buf)
   1242                                     (with-current-buffer command-buf
   1243                                       (car (where-is-internal
   1244                                             'magit-process-buffer))))))
   1245                      (format "Hit %s to see" (key-description key))
   1246                    "See")
   1247                  (buffer-name process-buf))))))
   1248   arg)
   1249 
   1250 (defun magit-process-display-buffer (process)
   1251   (when (process-live-p process)
   1252     (let ((buf (process-buffer process)))
   1253       (cond ((not (buffer-live-p buf)))
   1254             ((= magit-process-popup-time 0)
   1255              (if (minibufferp)
   1256                  (switch-to-buffer-other-window buf)
   1257                (pop-to-buffer buf)))
   1258             ((> magit-process-popup-time 0)
   1259              (run-with-timer magit-process-popup-time nil
   1260                              (lambda (p)
   1261                                (when (eq (process-status p) 'run)
   1262                                  (let ((buf (process-buffer p)))
   1263                                    (when (buffer-live-p buf)
   1264                                      (if (minibufferp)
   1265                                          (switch-to-buffer-other-window buf)
   1266                                        (pop-to-buffer buf))))))
   1267                              process))))))
   1268 
   1269 (defun magit--log-action (summary line list)
   1270   (let (heading lines)
   1271     (if (cdr list)
   1272         (progn (setq heading (funcall summary list))
   1273                (setq lines (mapcar line list)))
   1274       (setq heading (funcall line (car list))))
   1275     (with-current-buffer (magit-process-buffer t)
   1276       (goto-char (1- (point-max)))
   1277       (let ((inhibit-read-only t))
   1278         (magit-insert-section (message)
   1279           (magit-insert-heading (concat "  * " heading))
   1280           (when lines
   1281             (dolist (line lines)
   1282               (insert line "\n"))
   1283             (insert "\n"))))
   1284       (let ((inhibit-message t))
   1285         (when heading
   1286           (setq lines (cons heading lines)))
   1287         (message (mapconcat #'identity lines "\n"))))))
   1288 
   1289 ;;; _
   1290 (provide 'magit-process)
   1291 ;;; magit-process.el ends here