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