config

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

magit-process.el (54763B)


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