magit-process.el (55157B)
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 local 452 ;; machine. A local binding of process-environment different from the 453 ;; top-level value affects the environment used in 454 ;; tramp-sh-handle-{start-file-process,process-file}. 455 (let ((local (not (file-remote-p default-directory)))) 456 (append magit-git-environment 457 (and local 458 (cdr (assoc magit-git-executable magit-git-w32-path-hack))) 459 (and local magit-need-cygwin-noglob 460 (mapcar (lambda (var) 461 (concat var "=" (if-let ((val (getenv var))) 462 (concat val " noglob") 463 "noglob"))) 464 '("CYGWIN" "MSYS"))) 465 process-environment))) 466 467 (defvar magit-this-process nil) 468 469 (defun magit-run-git-with-input (&rest args) 470 "Call Git in a separate process. 471 ARGS is flattened and then used as arguments to Git. 472 473 The current buffer's content is used as the process's standard 474 input. The buffer is assumed to be temporary and thus OK to 475 modify. 476 477 Function `magit-git-executable' specifies the Git executable and 478 option `magit-git-global-arguments' specifies constant arguments. 479 The remaining arguments ARGS specify arguments to Git, they are 480 flattened before use." 481 (when (eq system-type 'windows-nt) 482 ;; On w32, git expects UTF-8 encoded input, ignore any user 483 ;; configuration telling us otherwise (see #3250). 484 (encode-coding-region (point-min) (point-max) 'utf-8-unix)) 485 (if (file-remote-p default-directory) 486 ;; We lack `process-file-region', so fall back to asynch + 487 ;; waiting in remote case. 488 (progn 489 (magit-start-git (current-buffer) args) 490 (while (and magit-this-process 491 (eq (process-status magit-this-process) 'run)) 492 (sleep-for 0.005))) 493 (run-hooks 'magit-pre-call-git-hook) 494 (pcase-let* ((process-environment (magit-process-environment)) 495 (default-process-coding-system (magit--process-coding-system)) 496 (flat-args (magit-process-git-arguments args)) 497 (`(,process-buf . ,section) 498 (magit-process-setup (magit-git-executable) flat-args)) 499 (inhibit-read-only t)) 500 (magit-process-finish 501 (apply #'call-process-region (point-min) (point-max) 502 (magit-git-executable) nil process-buf nil flat-args) 503 process-buf nil default-directory section)))) 504 505 ;;; Asynchronous Processes 506 507 (defun magit-run-git-async (&rest args) 508 "Start Git, prepare for refresh, and return the process object. 509 ARGS is flattened and then used as arguments to Git. 510 511 Display the command line arguments in the echo area. 512 513 After Git returns some buffers are refreshed: the buffer that was 514 current when this function was called (if it is a Magit buffer 515 and still alive), as well as the respective Magit status buffer. 516 517 See `magit-start-process' for more information." 518 (message "Running %s %s" (magit-git-executable) 519 (let ((m (mapconcat #'identity (flatten-tree args) " "))) 520 (remove-list-of-text-properties 0 (length m) '(face) m) 521 m)) 522 (magit-start-git nil args)) 523 524 (defun magit-run-git-with-editor (&rest args) 525 "Export GIT_EDITOR and start Git. 526 Also prepare for refresh and return the process object. 527 ARGS is flattened and then used as arguments to Git. 528 529 Display the command line arguments in the echo area. 530 531 After Git returns some buffers are refreshed: the buffer that was 532 current when this function was called (if it is a Magit buffer 533 and still alive), as well as the respective Magit status buffer. 534 535 See `magit-start-process' and `with-editor' for more information." 536 (magit--record-separated-gitdir) 537 (magit-with-editor (magit-run-git-async args))) 538 539 (defun magit-run-git-sequencer (&rest args) 540 "Export GIT_EDITOR and start Git. 541 Also prepare for refresh and return the process object. 542 ARGS is flattened and then used as arguments to Git. 543 544 Display the command line arguments in the echo area. 545 546 After Git returns some buffers are refreshed: the buffer that was 547 current when this function was called (if it is a Magit buffer 548 and still alive), as well as the respective Magit status buffer. 549 If the sequence stops at a commit, make the section representing 550 that commit the current section by moving `point' there. 551 552 See `magit-start-process' and `with-editor' for more information." 553 (apply #'magit-run-git-with-editor args) 554 (set-process-sentinel magit-this-process #'magit-sequencer-process-sentinel) 555 magit-this-process) 556 557 (defvar magit-pre-start-git-hook nil) 558 559 (defun magit-start-git (input &rest args) 560 "Start Git, prepare for refresh, and return the process object. 561 562 If INPUT is non-nil, it has to be a buffer or the name of an 563 existing buffer. The buffer content becomes the processes 564 standard input. 565 566 Function `magit-git-executable' specifies the Git executable and 567 option `magit-git-global-arguments' specifies constant arguments. 568 The remaining arguments ARGS specify arguments to Git, they are 569 flattened before use. 570 571 After Git returns some buffers are refreshed: the buffer that was 572 current when this function was called (if it is a Magit buffer 573 and still alive), as well as the respective Magit status buffer. 574 575 See `magit-start-process' for more information." 576 (run-hooks 'magit-pre-start-git-hook) 577 (let ((default-process-coding-system (magit--process-coding-system))) 578 (apply #'magit-start-process (magit-git-executable) input 579 (magit-process-git-arguments args)))) 580 581 (defun magit-start-process (program &optional input &rest args) 582 "Start PROGRAM, prepare for refresh, and return the process object. 583 584 If optional argument INPUT is non-nil, it has to be a buffer or 585 the name of an existing buffer. The buffer content becomes the 586 processes standard input. 587 588 The process is started using `start-file-process' and then setup 589 to use the sentinel `magit-process-sentinel' and the filter 590 `magit-process-filter'. Information required by these functions 591 is stored in the process object. When this function returns the 592 process has not started to run yet so it is possible to override 593 the sentinel and filter. 594 595 After the process returns, `magit-process-sentinel' refreshes the 596 buffer that was current when `magit-start-process' was called (if 597 it is a Magit buffer and still alive), as well as the respective 598 Magit status buffer." 599 (pcase-let* 600 ((`(,process-buf . ,section) 601 (magit-process-setup program args)) 602 (process 603 (let ((process-connection-type ;t=pty nil=pipe 604 (or 605 ;; With Tramp, maybe force use a pty. #4720 606 (and (file-remote-p default-directory) 607 (eq magit-tramp-pipe-stty-settings 'pty)) 608 ;; Without input, don't use a pty, because it would 609 ;; set icrnl, which would modify the input. #20 610 (and (not input) magit-process-connection-type))) 611 (tramp-pipe-stty-settings 612 (or (and (not (eq magit-tramp-pipe-stty-settings 'pty)) 613 ;; Defaults to "", to allow staging hunks over 614 ;; Tramp again. #4720 615 magit-tramp-pipe-stty-settings) 616 tramp-pipe-stty-settings)) 617 (process-environment (magit-process-environment)) 618 (default-process-coding-system (magit--process-coding-system))) 619 (apply #'start-file-process 620 (file-name-nondirectory program) 621 process-buf program args)))) 622 (with-editor-set-process-filter process #'magit-process-filter) 623 (set-process-sentinel process #'magit-process-sentinel) 624 (set-process-buffer process process-buf) 625 (when (eq system-type 'windows-nt) 626 ;; On w32, git expects UTF-8 encoded input, ignore any user 627 ;; configuration telling us otherwise. 628 (set-process-coding-system process nil 'utf-8-unix)) 629 (process-put process 'section section) 630 (process-put process 'command-buf (current-buffer)) 631 (process-put process 'default-dir default-directory) 632 (when magit-inhibit-refresh 633 (process-put process 'inhibit-refresh t)) 634 (oset section process process) 635 (with-current-buffer process-buf 636 (set-marker (process-mark process) (point))) 637 (when input 638 (with-current-buffer input 639 (process-send-region process (point-min) (point-max)) 640 ;; `process-send-eof' appears to be broken over 641 ;; Tramp from Windows. See #3624 and bug#43226. 642 (if (and (eq system-type 'windows-nt) 643 (file-remote-p (process-get process 'default-dir) nil t)) 644 (process-send-string process "") 645 (process-send-eof process)))) 646 (setq magit-this-process process) 647 (oset section value process) 648 (magit-process-display-buffer process) 649 process)) 650 651 (defun magit-parse-git-async (&rest args) 652 (setq args (magit-process-git-arguments args)) 653 (let ((command-buf (current-buffer)) 654 (process-buf (generate-new-buffer " *temp*")) 655 (toplevel (magit-toplevel))) 656 (with-current-buffer process-buf 657 (setq default-directory toplevel) 658 (let ((process 659 (let ((process-connection-type nil) 660 (process-environment (magit-process-environment)) 661 (default-process-coding-system 662 (magit--process-coding-system))) 663 (apply #'start-file-process "git" process-buf 664 (magit-git-executable) args)))) 665 (process-put process 'command-buf command-buf) 666 (process-put process 'parsed (point)) 667 (setq magit-this-process process) 668 process)))) 669 670 ;;; Process Internals 671 672 (defclass magit-process-section (magit-section) 673 ((process :initform nil))) 674 675 (setf (alist-get 'process magit--section-type-alist) 'magit-process-section) 676 677 (defun magit-process-setup (program args) 678 (magit-process-set-mode-line program args) 679 (let ((pwd default-directory) 680 (buf (magit-process-buffer t))) 681 (cons buf (with-current-buffer buf 682 (prog1 (magit-process-insert-section pwd program args nil nil) 683 (backward-char 1)))))) 684 685 (defun magit-process-insert-section (pwd program args &optional errcode errlog) 686 (let ((inhibit-read-only t) 687 (magit-insert-section--parent magit-root-section) 688 (magit-insert-section--oldroot nil)) 689 (goto-char (1- (point-max))) 690 (magit-insert-section (process) 691 (insert (if errcode 692 (format "%3s " (propertize (number-to-string errcode) 693 'font-lock-face 'magit-process-ng)) 694 "run ")) 695 (when magit-process-timestamp-format 696 (insert (format-time-string magit-process-timestamp-format) " ")) 697 (unless (equal (expand-file-name pwd) 698 (expand-file-name default-directory)) 699 (insert (file-relative-name pwd default-directory) ?\s)) 700 (insert (magit-process--format-arguments program args)) 701 (magit-insert-heading) 702 (when errlog 703 (if (bufferp errlog) 704 (insert (with-current-buffer errlog 705 (buffer-substring-no-properties (point-min) (point-max)))) 706 (insert-file-contents errlog) 707 (goto-char (1- (point-max))))) 708 (insert "\n")))) 709 710 (defun magit-process--format-arguments (program args) 711 (cond 712 ((and args (equal program (magit-git-executable))) 713 (let ((global (length magit-git-global-arguments))) 714 (concat 715 (propertize (file-name-nondirectory program) 716 'font-lock-face 'magit-section-heading) 717 " " 718 (propertize (magit--ellipsis) 719 'font-lock-face 'magit-section-heading 720 'help-echo (mapconcat #'identity (seq-take args global) " ")) 721 " " 722 (propertize (mapconcat #'shell-quote-argument (seq-drop args global) " ") 723 'font-lock-face 'magit-section-heading)))) 724 ((and args (equal program shell-file-name)) 725 (propertize (cadr args) 726 'font-lock-face 'magit-section-heading)) 727 (t 728 (concat (propertize (file-name-nondirectory program) 729 'font-lock-face 'magit-section-heading) 730 " " 731 (propertize (mapconcat #'shell-quote-argument args " ") 732 'font-lock-face 'magit-section-heading))))) 733 734 (defun magit-process-truncate-log () 735 (let* ((head nil) 736 (tail (oref magit-root-section children)) 737 (count (length tail))) 738 (when (> (1+ count) magit-process-log-max) 739 (while (and (cdr tail) 740 (> count (/ magit-process-log-max 2))) 741 (let* ((inhibit-read-only t) 742 (section (car tail)) 743 (process (oref section process))) 744 (cond ((not process)) 745 ((memq (process-status process) '(exit signal)) 746 (delete-region (oref section start) 747 (1+ (oref section end))) 748 (cl-decf count)) 749 (t 750 (push section head)))) 751 (pop tail)) 752 (oset magit-root-section children 753 (nconc (reverse head) tail))))) 754 755 (defun magit-process-sentinel (process event) 756 "Default sentinel used by `magit-start-process'." 757 (when (memq (process-status process) '(exit signal)) 758 (setq event (substring event 0 -1)) 759 (when (string-match "^finished" event) 760 (message (concat (capitalize (process-name process)) " finished"))) 761 (magit-process-finish process) 762 (when (eq process magit-this-process) 763 (setq magit-this-process nil)) 764 (unless (process-get process 'inhibit-refresh) 765 (let ((command-buf (process-get process 'command-buf))) 766 (if (buffer-live-p command-buf) 767 (with-current-buffer command-buf 768 (magit-refresh)) 769 (with-temp-buffer 770 (setq default-directory (process-get process 'default-dir)) 771 (magit-refresh))))))) 772 773 (defun magit-sequencer-process-sentinel (process event) 774 "Special sentinel used by `magit-run-git-sequencer'." 775 (when (memq (process-status process) '(exit signal)) 776 (magit-process-sentinel process event) 777 (when-let* ((process-buf (process-buffer process)) 778 ((buffer-live-p process-buf)) 779 (status-buf (with-current-buffer process-buf 780 (magit-get-mode-buffer 'magit-status-mode)))) 781 (with-current-buffer status-buf 782 (when-let ((section 783 (magit-get-section 784 `((commit . ,(magit-rev-parse "HEAD")) 785 (,(pcase (car (seq-drop 786 (process-command process) 787 (1+ (length magit-git-global-arguments)))) 788 ((or "rebase" "am") 'rebase-sequence) 789 ((or "cherry-pick" "revert") 'sequence))) 790 (status))))) 791 (goto-char (oref section start)) 792 (magit-section-update-highlight)))))) 793 794 (defun magit-process-filter (proc string) 795 "Default filter used by `magit-start-process'." 796 (with-current-buffer (process-buffer proc) 797 (let ((inhibit-read-only t)) 798 (goto-char (process-mark proc)) 799 ;; Find last ^M in string. If one was found, ignore 800 ;; everything before it and delete the current line. 801 (when-let ((ret-pos (cl-position ?\r string :from-end t))) 802 (cl-callf substring string (1+ ret-pos)) 803 (delete-region (line-beginning-position) (point))) 804 (setq string (magit-process-remove-bogus-errors string)) 805 (insert (propertize string 'magit-section 806 (process-get proc 'section))) 807 (set-marker (process-mark proc) (point)) 808 ;; Make sure prompts are matched after removing ^M. 809 (magit-process-yes-or-no-prompt proc string) 810 (magit-process-username-prompt proc string) 811 (magit-process-password-prompt proc string) 812 (run-hook-with-args-until-success 'magit-process-prompt-functions 813 proc string)))) 814 815 (defun magit-process-make-keymap (process parent) 816 "Remap `abort-minibuffers' to a command that also kills PROCESS. 817 PARENT is used as the parent of the returned keymap." 818 (let ((cmd (lambda () 819 (interactive) 820 (ignore-errors (kill-process process)) 821 (if (fboundp 'abort-minibuffers) 822 (abort-minibuffers) 823 (abort-recursive-edit))))) 824 (define-keymap :parent parent 825 "C-g" cmd 826 "<remap> <abort-minibuffers>" cmd 827 "<remap> <abort-recursive-edit>" cmd))) 828 829 (defmacro magit-process-kill-on-abort (process &rest body) 830 (declare (indent 1) 831 (debug (form body)) 832 (obsolete magit-process-make-keymap "Magit 4.0.0")) 833 `(let ((minibuffer-local-map 834 (magit-process-make-keymap ,process minibuffer-local-map))) 835 ,@body)) 836 837 (defun magit-process-remove-bogus-errors (str) 838 (save-match-data 839 (when (string-match "^\\(\\*ERROR\\*: \\)Canceled by user" str) 840 (setq str (replace-match "" nil nil str 1))) 841 (when (string-match "^error: There was a problem with the editor.*\n" str) 842 (setq str (replace-match "" nil nil str))) 843 (when (string-match 844 "^Please supply the message using either -m or -F option\\.\n" str) 845 (setq str (replace-match "" nil nil str)))) 846 str) 847 848 (defun magit-process-yes-or-no-prompt (process string) 849 "Forward Yes-or-No prompts to the user." 850 (when-let ((beg (string-match magit-process-yes-or-no-prompt-regexp string))) 851 (process-send-string 852 process 853 (if (save-match-data 854 (let ((max-mini-window-height 30) 855 (minibuffer-local-map 856 (magit-process-make-keymap process minibuffer-local-map)) 857 ;; In case yes-or-no-p is fset to that, but does 858 ;; not cover use-dialog-box-p and y-or-n-p-read-key. 859 (y-or-n-p-map 860 (magit-process-make-keymap process y-or-n-p-map))) 861 (yes-or-no-p (substring string 0 beg)))) 862 (concat (downcase (match-string 1 string)) "\n") 863 (concat (downcase (match-string 2 string)) "\n"))))) 864 865 (defun magit-process-password-auth-source (key) 866 "Use `auth-source-search' to get a password. 867 If found, return the password. Otherwise, return nil. 868 869 To use this function add it to the appropriate hook 870 (add-hook \\='magit-process-find-password-functions 871 \\='magit-process-password-auth-source) 872 873 KEY typically derives from a prompt such as: 874 Password for \\='https://yourname@github.com\\=' 875 in which case it would be the string 876 yourname@github.com 877 which matches the ~/.authinfo.gpg entry 878 machine github.com login yourname password 12345 879 or iff that is undefined, for backward compatibility 880 machine yourname@github.com password 12345 881 882 On github.com you should not use your password but a 883 personal access token, see [1]. For information about 884 the peculiarities of other forges, please consult the 885 respective documentation. 886 887 After manually editing ~/.authinfo.gpg you must reset 888 the cache using 889 M-x auth-source-forget-all-cached RET 890 891 The above will save you from having to repeatedly type 892 your token or password, but you might still repeatedly 893 be asked for your username. To prevent that, change an 894 URL like 895 https://github.com/foo/bar.git 896 to 897 https://yourname@github.com/foo/bar.git 898 899 Instead of changing all such URLs manually, they can 900 be translated on the fly by doing this once 901 git config --global \ 902 url.https://yourname@github.com.insteadOf \ 903 https://github.com 904 905 [1]: https://docs.github.com/en/github/authenticating-to-github/creating-a-personal-access-token." 906 (require 'auth-source) 907 (and (fboundp 'auth-source-search) 908 (string-match "\\`\\(.+\\)@\\([^@]+\\)\\'" key) 909 (let* ((user (match-string 1 key)) 910 (host (match-string 2 key)) 911 (secret 912 (plist-get 913 (car (or (auth-source-search :max 1 :host host :user user) 914 (auth-source-search :max 1 :host key))) 915 :secret))) 916 (if (functionp secret) 917 (funcall secret) 918 secret)))) 919 920 (defun magit-process-git-credential-manager-core (process string) 921 "Authenticate using `git-credential-manager-core'. 922 923 To use this function add it to the appropriate hook 924 (add-hook \\='magit-process-prompt-functions 925 \\='magit-process-git-credential-manager-core)" 926 (and (string-match "^option (enter for default): $" string) 927 (progn 928 (magit-process-buffer) 929 (let ((option (format "%c\n" 930 (read-char-choice "Option: " '(?\r ?\j ?1 ?2))))) 931 (insert-before-markers-and-inherit option) 932 (process-send-string process option))))) 933 934 (defun magit-process-password-prompt (process string) 935 "Find a password based on prompt STRING and send it to git. 936 Use `magit-process-password-prompt-regexps' to find a known 937 prompt. If and only if one is found, then call functions in 938 `magit-process-find-password-functions' until one of them returns 939 the password. If all functions return nil, then read the password 940 from the user." 941 (when-let ((prompt (magit-process-match-prompt 942 magit-process-password-prompt-regexps string))) 943 (process-send-string 944 process 945 (concat (or (and-let* ((key (match-string 99 string))) 946 (run-hook-with-args-until-success 947 'magit-process-find-password-functions key)) 948 (let ((read-passwd-map 949 (magit-process-make-keymap process read-passwd-map))) 950 (read-passwd prompt))) 951 "\n")))) 952 953 (defun magit-process-username-prompt (process string) 954 "Forward username prompts to the user." 955 (when-let ((prompt (magit-process-match-prompt 956 magit-process-username-prompt-regexps string))) 957 (process-send-string 958 process 959 (let ((minibuffer-local-map 960 (magit-process-make-keymap process minibuffer-local-map))) 961 (concat (read-string prompt nil nil (user-login-name)) "\n"))))) 962 963 (defun magit-process-match-prompt (prompts string) 964 "Match STRING against PROMPTS and set match data. 965 Return the matched string suffixed with \": \", if needed." 966 (when (--any-p (string-match it string) prompts) 967 (let ((prompt (match-string 0 string))) 968 (cond ((string-suffix-p ": " prompt) prompt) 969 ((string-suffix-p ":" prompt) (concat prompt " ")) 970 (t (concat prompt ": ")))))) 971 972 (defun magit--process-coding-system () 973 (let ((fro (or magit-git-output-coding-system 974 (car default-process-coding-system))) 975 (to (cdr default-process-coding-system))) 976 (if magit-process-ensure-unix-line-ending 977 (cons (coding-system-change-eol-conversion fro 'unix) 978 (coding-system-change-eol-conversion to 'unix)) 979 (cons fro to)))) 980 981 (defvar magit-credential-hook nil 982 "Hook run before Git needs credentials.") 983 984 (defvar magit-credential-cache-daemon-process nil) 985 986 (defun magit-maybe-start-credential-cache-daemon () 987 "Maybe start a `git-credential-cache--daemon' process. 988 989 If such a process is already running or if the value of option 990 `magit-credential-cache-daemon-socket' is nil, then do nothing. 991 Otherwise start the process passing the value of that options 992 as argument." 993 (unless (or (not magit-credential-cache-daemon-socket) 994 (process-live-p magit-credential-cache-daemon-process) 995 (memq magit-credential-cache-daemon-process 996 (list-system-processes))) 997 (setq magit-credential-cache-daemon-process 998 (or (--first (let* ((attr (process-attributes it)) 999 (comm (cdr (assq 'comm attr))) 1000 (user (cdr (assq 'user attr)))) 1001 (and (string= comm "git-credential-cache--daemon") 1002 (string= user user-login-name))) 1003 (list-system-processes)) 1004 (condition-case nil 1005 (start-process "git-credential-cache--daemon" 1006 " *git-credential-cache--daemon*" 1007 (magit-git-executable) 1008 "credential-cache--daemon" 1009 magit-credential-cache-daemon-socket) 1010 ;; Some Git implementations (e.g., Windows) won't have 1011 ;; this program; if we fail the first time, stop trying. 1012 ((debug error) 1013 (remove-hook 'magit-credential-hook 1014 #'magit-maybe-start-credential-cache-daemon))))))) 1015 1016 (add-hook 'magit-credential-hook #'magit-maybe-start-credential-cache-daemon) 1017 1018 (defun tramp-sh-handle-start-file-process--magit-tramp-process-environment 1019 (fn name buffer program &rest args) 1020 (if magit-tramp-process-environment 1021 (apply fn name buffer 1022 (car magit-tramp-process-environment) 1023 (append (cdr magit-tramp-process-environment) 1024 (cons program args))) 1025 (apply fn name buffer program args))) 1026 1027 (advice-add 'tramp-sh-handle-start-file-process :around 1028 #'tramp-sh-handle-start-file-process--magit-tramp-process-environment) 1029 1030 (defun tramp-sh-handle-process-file--magit-tramp-process-environment 1031 (fn program &optional infile destination display &rest args) 1032 (if magit-tramp-process-environment 1033 (apply fn "env" infile destination display 1034 (append magit-tramp-process-environment 1035 (cons program args))) 1036 (apply fn program infile destination display args))) 1037 1038 (advice-add 'tramp-sh-handle-process-file :around 1039 #'tramp-sh-handle-process-file--magit-tramp-process-environment) 1040 1041 (defvar-keymap magit-mode-line-process-map 1042 :doc "Keymap for `mode-line-process'." 1043 "<mode-line> <mouse-1>" ''magit-process-buffer) 1044 1045 (defun magit-process-set-mode-line (program args) 1046 "Display the git command (sans arguments) in the mode line." 1047 (when (equal program (magit-git-executable)) 1048 (setq args (nthcdr (length magit-git-global-arguments) args))) 1049 (let ((str (concat " " (propertize 1050 (concat (file-name-nondirectory program) 1051 (and args (concat " " (car args)))) 1052 'mouse-face 'highlight 1053 'keymap magit-mode-line-process-map 1054 'help-echo "mouse-1: Show process buffer" 1055 'font-lock-face 'magit-mode-line-process)))) 1056 (magit-repository-local-set 'mode-line-process str) 1057 (dolist (buf (magit-mode-get-buffers)) 1058 (with-current-buffer buf 1059 (setq mode-line-process str))) 1060 (force-mode-line-update t))) 1061 1062 (defun magit-process-set-mode-line-error-status (&optional error str) 1063 "Apply an error face to the string set by `magit-process-set-mode-line'. 1064 1065 If ERROR is supplied, include it in the `mode-line-process' tooltip. 1066 1067 If STR is supplied, it replaces the `mode-line-process' text." 1068 (setq str (or str (magit-repository-local-get 'mode-line-process))) 1069 (when str 1070 (setq error (format "%smouse-1: Show process buffer" 1071 (if (stringp error) 1072 (concat error "\n\n") 1073 ""))) 1074 (setq str (concat " " (propertize 1075 (substring-no-properties str 1) 1076 'mouse-face 'highlight 1077 'keymap magit-mode-line-process-map 1078 'help-echo error 1079 'font-lock-face 'magit-mode-line-process-error))) 1080 (magit-repository-local-set 'mode-line-process str) 1081 (dolist (buf (magit-mode-get-buffers)) 1082 (with-current-buffer buf 1083 (setq mode-line-process str))) 1084 (force-mode-line-update t) 1085 ;; We remove any error status from the mode line when a magit 1086 ;; buffer is refreshed (see `magit-refresh-buffer'), but we must 1087 ;; ensure that we ignore any refreshes during the remainder of the 1088 ;; current command -- otherwise a newly-set error status would be 1089 ;; removed before it was seen. We set a flag which prevents the 1090 ;; status from being removed prior to the next command, so that 1091 ;; the error status is guaranteed to remain visible until then. 1092 (let ((repokey (magit-repository-local-repository))) 1093 ;; The following closure captures the repokey value, and is 1094 ;; added to `pre-command-hook'. 1095 (cl-labels ((enable-magit-process-unset-mode-line () 1096 ;; Remove ourself from the hook variable, so 1097 ;; that we only run once. 1098 (remove-hook 'pre-command-hook 1099 #'enable-magit-process-unset-mode-line) 1100 ;; Clear the inhibit flag for the repository in 1101 ;; which we set it. 1102 (magit-repository-local-set 1103 'inhibit-magit-process-unset-mode-line nil repokey))) 1104 ;; Set the inhibit flag until the next command is invoked. 1105 (magit-repository-local-set 1106 'inhibit-magit-process-unset-mode-line t repokey) 1107 (add-hook 'pre-command-hook 1108 #'enable-magit-process-unset-mode-line))))) 1109 1110 (defun magit-process-unset-mode-line-error-status () 1111 "Remove any current error status from the mode line." 1112 (let ((status (or mode-line-process 1113 (magit-repository-local-get 'mode-line-process)))) 1114 (when (and status 1115 (eq (get-text-property 1 'font-lock-face status) 1116 'magit-mode-line-process-error)) 1117 (magit-process-unset-mode-line)))) 1118 1119 (add-hook 'magit-refresh-buffer-hook 1120 #'magit-process-unset-mode-line-error-status) 1121 1122 (defun magit-process-unset-mode-line (&optional directory) 1123 "Remove the git command from the mode line." 1124 (let ((default-directory (or directory default-directory))) 1125 (unless (magit-repository-local-get 'inhibit-magit-process-unset-mode-line) 1126 (magit-repository-local-set 'mode-line-process nil) 1127 (dolist (buf (magit-mode-get-buffers)) 1128 (with-current-buffer buf (setq mode-line-process nil))) 1129 (force-mode-line-update t)))) 1130 1131 (defvar magit-process-error-message-regexps 1132 (list "^\\*ERROR\\*: Canceled by user$" 1133 "^\\(?:error\\|fatal\\|git\\): \\(.*\\)$" 1134 "^\\(Cannot rebase:.*\\)$")) 1135 1136 (define-error 'magit-git-error "Git error") 1137 1138 (defun magit-process-error-summary (process-buf section) 1139 "A one-line error summary from the given SECTION." 1140 (or (and (buffer-live-p process-buf) 1141 (with-current-buffer process-buf 1142 (and (oref section content) 1143 (save-excursion 1144 (goto-char (oref section end)) 1145 (run-hook-wrapped 1146 'magit-process-error-message-regexps 1147 (lambda (re) 1148 (save-excursion 1149 (and (re-search-backward 1150 re (oref section start) t) 1151 (or (match-string-no-properties 1) 1152 (and (not magit-process-raise-error) 1153 'suppressed)))))))))) 1154 "Git failed")) 1155 1156 (defun magit-process-error-tooltip (process-buf section) 1157 "Returns the text from SECTION of the PROCESS-BUF buffer. 1158 1159 Limited by `magit-process-error-tooltip-max-lines'." 1160 (and (integerp magit-process-error-tooltip-max-lines) 1161 (> magit-process-error-tooltip-max-lines 0) 1162 (buffer-live-p process-buf) 1163 (with-current-buffer process-buf 1164 (save-excursion 1165 (goto-char (or (oref section content) 1166 (oref section start))) 1167 (buffer-substring-no-properties 1168 (point) 1169 (save-excursion 1170 (forward-line magit-process-error-tooltip-max-lines) 1171 (goto-char 1172 (if (> (point) (oref section end)) 1173 (oref section end) 1174 (point))) 1175 ;; Remove any trailing whitespace. 1176 (when (re-search-backward "[^[:space:]\n]" 1177 (oref section start) t) 1178 (forward-char 1)) 1179 (point))))))) 1180 1181 (defvar-local magit-this-error nil) 1182 1183 (defvar magit-process-finish-apply-ansi-colors nil) 1184 1185 (defun magit-process-finish (arg &optional process-buf command-buf 1186 default-dir section) 1187 (unless (integerp arg) 1188 (setq process-buf (process-buffer arg)) 1189 (setq command-buf (process-get arg 'command-buf)) 1190 (setq default-dir (process-get arg 'default-dir)) 1191 (setq section (process-get arg 'section)) 1192 (setq arg (process-exit-status arg))) 1193 (when (fboundp 'dired-uncache) 1194 (dired-uncache default-dir)) 1195 (when (buffer-live-p process-buf) 1196 (with-current-buffer process-buf 1197 (let ((inhibit-read-only t) 1198 (marker (oref section start))) 1199 (goto-char marker) 1200 (save-excursion 1201 (delete-char 3) 1202 (set-marker-insertion-type marker nil) 1203 (insert (propertize (format "%3s" arg) 1204 'magit-section section 1205 'font-lock-face (if (= arg 0) 1206 'magit-process-ok 1207 'magit-process-ng))) 1208 (set-marker-insertion-type marker t)) 1209 (when magit-process-finish-apply-ansi-colors 1210 (ansi-color-apply-on-region (oref section content) 1211 (oref section end))) 1212 (if (= (oref section end) 1213 (+ (line-end-position) 2)) 1214 (save-excursion 1215 (goto-char (1+ (line-end-position))) 1216 (delete-char -1) 1217 (oset section content nil)) 1218 (when (and (= arg 0) 1219 (not (--any-p (eq (window-buffer it) process-buf) 1220 (window-list)))) 1221 (magit-section-hide section)))))) 1222 (if (= arg 0) 1223 ;; Unset the `mode-line-process' value upon success. 1224 (magit-process-unset-mode-line default-dir) 1225 ;; Otherwise process the error. 1226 (let ((msg (magit-process-error-summary process-buf section))) 1227 ;; Change `mode-line-process' to an error face upon failure. 1228 (if magit-process-display-mode-line-error 1229 (magit-process-set-mode-line-error-status 1230 (or (magit-process-error-tooltip process-buf section) 1231 msg)) 1232 (magit-process-unset-mode-line default-dir)) 1233 ;; Either signal the error, or else display the error summary in 1234 ;; the status buffer and with a message in the echo area. 1235 (cond 1236 (magit-process-raise-error 1237 (signal 'magit-git-error (list (format "%s (in %s)" msg default-dir)))) 1238 ((not (eq msg 'suppressed)) 1239 (when (buffer-live-p process-buf) 1240 (with-current-buffer process-buf 1241 (when-let ((status-buf (magit-get-mode-buffer 'magit-status-mode))) 1242 (with-current-buffer status-buf 1243 (setq magit-this-error msg))))) 1244 (message "%s ... [%s buffer %s for details]" msg 1245 (if-let ((key (and (buffer-live-p command-buf) 1246 (with-current-buffer command-buf 1247 (car (where-is-internal 1248 'magit-process-buffer)))))) 1249 (format "Hit %s to see" (key-description key)) 1250 "See") 1251 (buffer-name process-buf)))))) 1252 arg) 1253 1254 (defun magit-process-display-buffer (process) 1255 (when (process-live-p process) 1256 (let ((buf (process-buffer process))) 1257 (cond ((not (buffer-live-p buf))) 1258 ((= magit-process-popup-time 0) 1259 (if (minibufferp) 1260 (switch-to-buffer-other-window buf) 1261 (pop-to-buffer buf))) 1262 ((> magit-process-popup-time 0) 1263 (run-with-timer magit-process-popup-time nil 1264 (lambda (p) 1265 (when (eq (process-status p) 'run) 1266 (let ((buf (process-buffer p))) 1267 (when (buffer-live-p buf) 1268 (if (minibufferp) 1269 (switch-to-buffer-other-window buf) 1270 (pop-to-buffer buf)))))) 1271 process)))))) 1272 1273 (defun magit--log-action (summary line list) 1274 (let (heading lines) 1275 (if (cdr list) 1276 (progn (setq heading (funcall summary list)) 1277 (setq lines (mapcar line list))) 1278 (setq heading (funcall line (car list)))) 1279 (with-current-buffer (magit-process-buffer t) 1280 (goto-char (1- (point-max))) 1281 (let ((inhibit-read-only t)) 1282 (magit-insert-section (message) 1283 (magit-insert-heading (concat " * " heading)) 1284 (when lines 1285 (dolist (line lines) 1286 (insert line "\n")) 1287 (insert "\n")))) 1288 (let ((inhibit-message t)) 1289 (when heading 1290 (setq lines (cons heading lines))) 1291 (message (mapconcat #'identity lines "\n")))))) 1292 1293 ;;; _ 1294 (provide 'magit-process) 1295 ;;; magit-process.el ends here