update packages

This commit is contained in:
2025-02-26 20:16:44 +01:00
parent 59db017445
commit 45d49daef0
291 changed files with 16240 additions and 522600 deletions

View File

@@ -31,6 +31,7 @@ All Contributors
- Aaron Madlon-Kay
- Abdo Roig-Maranges
- Adam Benanti
- Adam Jones
- Adam Kruszewski
- Adam Porter
- Adam Spiers
@@ -56,6 +57,7 @@ All Contributors
- Andrew Kirkpatrick
- Andrew Psaltis
- Andrew Schwartzmeyer
- Andrew Zipperer
- Andrey Smirnov
- Andriy Kmit'
- Andy Sawyer
@@ -151,6 +153,7 @@ All Contributors
- Frédéric Giquel
- Fritz Grabo
- Fritz Stelzer
- gemmaro
- Geoff Shannon
- George Kadianakis
- Géza Herman
@@ -169,16 +172,20 @@ All Contributors
- Ian Eure
- Ian Milligan
- Ilya Grigoriev
- Iñaki Arenaza
- Ingmar Sittl
- Ingo Lohmar
- Ioan-Adrian Ratiu
- Ivan Brennan
- Jacob Ilsø
- Jan Tatarik
- Jasper St. Pierre
- JD Smith
- Jean-Louis Giordano
- Jeff Bellegarde
- Jeff Dairiki
- Jeremy Meng
- Jeremy Sowden
- Jesse Alama
- Jim Blandy
- Joakim Jalap
@@ -232,6 +239,7 @@ All Contributors
- Louis Roché
- Luís Oliveira
- Luke Amdor
- Magnar Sveen
- Magnus Malm
- Mak Kolybabi
- Manuel Vázquez Acosta
@@ -250,6 +258,7 @@ All Contributors
- Markus Beppler
- Martin Joerg
- Martin Polden
- Matt Beshara
- Matthew Fluet
- Matthew Kraai
- Matthieu Hauglustaine
@@ -277,18 +286,22 @@ All Contributors
- Nicolas Dudebout
- Nicolas Petton
- Nicolas Richard
- Nikita Leshenko
- Nikolay Martynov
- Noam Postavsky
- N. Troy de Freitas
- Ola x Nilsson
- Ola Nilsson
- Ole Arndt
- Oleh Krehel
- Orivej Desh
- Óscar Fuentes
- Pancho Horrillo
- Park Zhou
- Paul Pogonyshev
- Paul Stadig
- Pavel Holejsovsky
- Pekka Pessi
- Pengji Zhang
- Peter Eisentraut
- Peter Jaros
- Peter J. Weisberg
@@ -303,7 +316,9 @@ All Contributors
- Phil Sainty
- Pierre Neidhardt
- Pieter Praet
- Pieter van Oostrum
- Prathamesh Sonpatki
- Pratyush Yadav
- Pritam Baral
- rabio
- Radon Rosborough
@@ -320,6 +335,7 @@ All Contributors
- Robin Green
- Roey Darwish Dror
- Roger Crew
- Roland Marchand
- Romain Francoise
- Ron Parker
- Roy Crihfield
@@ -352,6 +368,7 @@ All Contributors
- Steven Thomas
- Steven Vancoillie
- Steve Purcell
- StrawberryTea
- Suhail Shergill
- Sylvain Rousseau
- Syohei Yoshida

View File

@@ -1,9 +1,9 @@
;;; git-rebase.el --- Edit Git rebase files -*- lexical-binding:t -*-
;; Copyright (C) 2008-2023 The Magit Project Contributors
;; Copyright (C) 2008-2025 The Magit Project Contributors
;; Author: Phil Jackson <phil@shellarchive.co.uk>
;; Maintainer: Jonas Bernoulli <jonas@bernoul.li>
;; Maintainer: Jonas Bernoulli <emacs.magit@jonas.bernoulli.dev>
;; SPDX-License-Identifier: GPL-3.0-or-later
@@ -181,7 +181,7 @@
(put 'git-rebase-kill-line :advertised-binding (kbd "k"))
(easy-menu-define git-rebase-mode-menu git-rebase-mode-map
"Git-Rebase mode menu"
"Git-Rebase mode menu."
'("Rebase"
["Pick" git-rebase-pick t]
["Reword" git-rebase-reword t]
@@ -375,13 +375,11 @@ non-nil, return the beginning and end of the current rebase line,
if any."
(cond
((use-region-p)
(let ((beg (save-excursion (goto-char (region-beginning))
(line-beginning-position)))
(end (save-excursion (goto-char (region-end))
(line-end-position))))
(when (and (git-rebase-line-p beg)
(git-rebase-line-p end))
(list beg (1+ end)))))
(let ((beg (magit--bol-position (region-beginning)))
(end (magit--eol-position (region-end))))
(and (git-rebase-line-p beg)
(git-rebase-line-p end)
(list beg (1+ end)))))
((and fallback (git-rebase-line-p))
(list (line-beginning-position)
(1+ (line-end-position))))))
@@ -705,6 +703,7 @@ Rebase files are generated when you run \"git rebase -i\" or run
`magit-interactive-rebase'. They describe how Git should perform
the rebase. See the documentation for git-rebase (e.g., by
running \"man git-rebase\" at the command line) for details."
:interactive nil
:group 'git-rebase
(setq comment-start (or (magit-get "core.commentChar") "#"))
(setq git-rebase-comment-re (concat "^" (regexp-quote comment-start)))
@@ -718,8 +717,10 @@ running \"man git-rebase\" at the command line) for details."
(when git-rebase-confirm-cancel
(add-hook 'with-editor-cancel-query-functions
#'git-rebase-cancel-confirm nil t))
(setq-local redisplay-highlight-region-function #'git-rebase-highlight-region)
(setq-local redisplay-unhighlight-region-function #'git-rebase-unhighlight-region)
(setq-local redisplay-highlight-region-function
#'git-rebase-highlight-region)
(setq-local redisplay-unhighlight-region-function
#'git-rebase-unhighlight-region)
(add-hook 'with-editor-pre-cancel-hook #'git-rebase-autostash-save nil t)
(add-hook 'with-editor-post-cancel-hook #'git-rebase-autostash-apply nil t)
(setq imenu-prev-index-position-function
@@ -775,7 +776,7 @@ running \"man git-rebase\" at the command line) for details."
(git-rebase-match-comment-line 0 'font-lock-comment-face)
("\\[[^[]*\\]"
0 'magit-keyword t)
("\\(?:fixup!\\|squash!\\)"
("\\(?:fixup!\\|squash!\\|amend!\\)"
0 'magit-keyword-squash t)
(,(format "^%s Rebase \\([^ ]*\\) onto \\([^ ]*\\)" comment-start)
(1 'git-rebase-comment-hash t)
@@ -786,10 +787,10 @@ running \"man git-rebase\" at the command line) for details."
(1 'git-rebase-label t))))
(defun git-rebase-mode-show-keybindings ()
"Modify the \"Commands:\" section of the comment Git generates
at the bottom of the file so that in place of the one-letter
abbreviation for the command, it shows the command's keybinding.
By default, this is the same except for the \"pick\" command."
"Modify the \"Commands:\" section of the comment Git generates.
Modify that section to replace Git's one-letter command abbreviation,
with the key bindings used in Magit. By default, these are the same,
except for the \"pick\" command."
(let ((inhibit-read-only t))
(save-excursion
(goto-char (point-min))

View File

@@ -1,9 +1,9 @@
;;; magit-apply.el --- Apply Git diffs -*- lexical-binding:t -*-
;; Copyright (C) 2008-2023 The Magit Project Contributors
;; Copyright (C) 2008-2025 The Magit Project Contributors
;; Author: Jonas Bernoulli <jonas@bernoul.li>
;; Maintainer: Jonas Bernoulli <jonas@bernoul.li>
;; Author: Jonas Bernoulli <emacs.magit@jonas.bernoulli.dev>
;; Maintainer: Jonas Bernoulli <emacs.magit@jonas.bernoulli.dev>
;; SPDX-License-Identifier: GPL-3.0-or-later
@@ -49,11 +49,6 @@
(path &optional prefer-short))
(defvar borg-user-emacs-directory)
(cl-eval-when (compile load)
(when (< emacs-major-version 26)
(defalias 'smerge-keep-upper 'smerge-keep-mine)
(defalias 'smerge-keep-lower 'smerge-keep-other)))
;;; Options
(defcustom magit-delete-by-moving-to-trash t
@@ -192,10 +187,8 @@ adjusted as \"@@ -10,6 +10,7 @@\" and \"@@ -18,6 +19,7 @@\"."
(magit-apply-patch
file args
(concat (oref file header)
(mapconcat #'identity
(magit-apply--adjust-hunk-new-starts
(mapcar #'magit-apply--section-content hunks))
"")))))
(string-join (magit-apply--adjust-hunk-new-starts
(mapcar #'magit-apply--section-content hunks)))))))
(defun magit-apply-hunk (hunk &rest args)
(let ((file (oref hunk parent)))
@@ -224,7 +217,7 @@ adjusted as \"@@ -10,6 +10,7 @@\" and \"@@ -18,6 +19,7 @@\"."
(defun magit-apply-patch (section:s args patch)
(let* ((files (if (atom section:s)
(list (oref section:s value))
(--map (oref it value) section:s)))
(mapcar (##oref % value) section:s)))
(command (symbol-name this-command))
(command (if (and command (string-match "^magit-\\([^-]+\\)" command))
(match-string 1 command)
@@ -258,11 +251,12 @@ adjusted as \"@@ -10,6 +10,7 @@\" and \"@@ -18,6 +19,7 @@\"."
(defun magit-apply--get-diffs (sections)
(magit-section-case
([file diffstat]
(--map (or (magit-get-section
(append `((file . ,(oref it value)))
(magit-section-ident magit-root-section)))
(error "Cannot get required diff headers"))
sections))
(mapcar (lambda (section)
(or (magit-get-section
(append `((file . ,(oref section value)))
(magit-section-ident magit-root-section)))
(error "Cannot get required diff headers")))
sections))
(t sections)))
(defun magit-apply--ignore-whitespace-p (selection type scope)
@@ -344,7 +338,7 @@ With prefix argument FORCE, offer ignored files for completion."
;; For backward compatibility, and because of
;; the function's name, don't require a list.
(magit-stage-1 (and force "--force")
(if (listp files) files (list files)))))
(ensure-list files))))
;;;###autoload
(defun magit-stage-modified (&optional all)
@@ -413,10 +407,10 @@ ignored) files."
(magit-wip-commit-after-apply files " after stage")))
(defvar magit-post-stage-hook-commands
'(magit-stage
magit-stage-buffer-file
magit-stage-file
magit-stage-modified))
(list #'magit-stage
#'magit-stage-buffer-file
#'magit-stage-file
#'magit-stage-modified))
(defun magit-run-post-stage-hook ()
(when (memq this-command magit-post-stage-hook-commands)
@@ -472,7 +466,7 @@ ignored) files."
(magit-with-toplevel
;; For backward compatibility, and because of
;; the function's name, don't require a list.
(magit-unstage-1 (if (listp files) files (list files)))))
(magit-unstage-1 (ensure-list files))))
(defun magit-unstage-1 (files)
(magit-wip-commit-before-change files " before unstage")
@@ -483,7 +477,7 @@ ignored) files."
(defun magit-unstage-intent (files)
(if-let ((staged (magit-staged-files))
(intent (--filter (member it staged) files)))
(intent (seq-filter (##member % staged) files)))
(magit-unstage-1 intent)
(user-error "Already unstaged")))
@@ -501,10 +495,10 @@ ignored) files."
(magit-wip-commit-after-apply nil " after unstage"))
(defvar magit-post-unstage-hook-commands
'(magit-unstage
magit-unstage-buffer-file
magit-unstage-file
magit-unstage-all))
(list #'magit-unstage
#'magit-unstage-buffer-file
#'magit-unstage-file
#'magit-unstage-all))
(defun magit-run-post-unstage-hook ()
(when (memq this-command magit-post-unstage-hook-commands)
@@ -555,9 +549,10 @@ of a side, then keep that side without prompting."
(funcall apply section "--reverse" "--index"))))
(defun magit-discard-hunks (sections)
(magit-confirm 'discard (format "Discard %s hunks from %s"
(length sections)
(magit-section-parent-value (car sections))))
(magit-confirm 'discard
(list "Discard %d hunks from %s"
(length sections)
(magit-section-parent-value (car sections))))
(magit-discard-apply-n sections #'magit-apply-hunks))
(defun magit-discard-apply-n (sections apply)
@@ -689,7 +684,7 @@ of a side, then keep that side without prompting."
(magit-call-git "reset" "--" orig)))))
(defun magit-discard-files--discard (sections new-files)
(let ((files (--map (oref it value) sections)))
(let ((files (mapcar (##oref % value) sections)))
(magit-confirm-files 'discard (append files new-files)
(format "Discard %s changes in" (magit-diff-type)))
(if (eq (magit-diff-type (car sections)) 'unstaged)
@@ -700,15 +695,15 @@ of a side, then keep that side without prompting."
(let ((binaries (magit-binary-files "--cached")))
(when binaries
(setq sections
(--remove (member (oref it value) binaries)
sections)))
(seq-remove (##member (oref % value) binaries)
sections)))
(cond ((length= sections 1)
(magit-discard-apply (car sections) 'magit-apply-diff))
(sections
(magit-discard-apply-n sections #'magit-apply-diffs)))
(when binaries
(let ((modified (magit-unstaged-files t)))
(setq binaries (--separate (member it modified) binaries)))
(setq binaries (magit--separate (##member % modified) binaries)))
(when (cadr binaries)
(magit-call-git "reset" "--" (cadr binaries)))
(when (car binaries)
@@ -745,9 +740,9 @@ so causes the change to be applied to the index as well."
(defun magit-reverse-hunks (sections args)
(magit-confirm 'reverse
(format "Reverse %s hunks from %s"
(length sections)
(magit-section-parent-value (car sections))))
(list "Reverse %d hunks from %s"
(length sections)
(magit-section-parent-value (car sections))))
(magit-reverse-apply sections #'magit-apply-hunks args))
(defun magit-reverse-file (section args)
@@ -762,9 +757,9 @@ so causes the change to be applied to the index as well."
magit-buffer-range)
(t
"--cached")))))
(--separate (member (oref it value) bs)
sections))))
(magit-confirm-files 'reverse (--map (oref it value) sections))
(magit--separate (##member (oref % value) bs)
sections))))
(magit-confirm-files 'reverse (mapcar (##oref % value) sections))
(cond ((length= sections 1)
(magit-reverse-apply (car sections) #'magit-apply-diff args))
(sections
@@ -789,7 +784,7 @@ a separate commit. A typical workflow would be:
1. Visit the `HEAD' commit and navigate to the change that should
not have been included in that commit.
2. Type \"u\" (`magit-unstage') to reverse it in the index.
This assumes that `magit-unstage-committed-changes' is non-nil.
This assumes that `magit-unstage-committed' is non-nil.
3. Type \"c e\" to extend `HEAD' with the staged changes,
including those that were already staged before.
4. Optionally stage the remaining changes using \"s\" or \"S\"

View File

@@ -1,9 +1,9 @@
;;; magit-autorevert.el --- Revert buffers when files in repository change -*- lexical-binding:t -*-
;; Copyright (C) 2008-2023 The Magit Project Contributors
;; Copyright (C) 2008-2025 The Magit Project Contributors
;; Author: Jonas Bernoulli <jonas@bernoul.li>
;; Maintainer: Jonas Bernoulli <jonas@bernoul.li>
;; Author: Jonas Bernoulli <emacs.magit@jonas.bernoulli.dev>
;; Maintainer: Jonas Bernoulli <emacs.magit@jonas.bernoulli.dev>
;; SPDX-License-Identifier: GPL-3.0-or-later
@@ -20,9 +20,16 @@
;; You should have received a copy of the GNU General Public License
;; along with Magit. If not, see <https://www.gnu.org/licenses/>.
;;; Commentary:
;; This library implements support for automatically reverting buffers
;; when visited files in the repository change.
;; See (info "(magit)Automatic Reverting of File-Visiting Buffers").
;;; Code:
(require 'magit-git)
(require 'magit-process)
(require 'autorevert)
@@ -58,9 +65,9 @@ is enabled."
:group 'auto-revert
:group 'magit-auto-revert
:group 'magit-related
:type '(radio (const :tag "No filter" nil)
(function-item magit-auto-revert-buffer-p)
(function-item magit-auto-revert-repository-buffer-p)
:type `(radio (const :tag "No filter" nil)
(function-item ,#'magit-auto-revert-buffer-p)
(function-item ,#'magit-auto-revert-repository-buffer-p)
function))
(defcustom magit-auto-revert-tracked-only t
@@ -98,19 +105,21 @@ seconds of user inactivity. That is not desirable."
;;; Mode
(defun magit-turn-on-auto-revert-mode-if-desired (&optional file)
(if file
(when-let ((buffer (find-buffer-visiting file)))
(with-current-buffer buffer
(magit-turn-on-auto-revert-mode-if-desired)))
(when (and (not auto-revert-mode) ; see #3014
(not global-auto-revert-mode) ; see #3460
buffer-file-name
(file-readable-p buffer-file-name)
(compat-call executable-find (magit-git-executable) t)
(magit-toplevel)
(or (not magit-auto-revert-tracked-only)
(magit-file-tracked-p buffer-file-name)))
(auto-revert-mode 1))))
(cond (file
(when-let ((buffer (find-buffer-visiting file)))
(with-current-buffer buffer
(magit-turn-on-auto-revert-mode-if-desired))))
((and (not auto-revert-mode) ; see #3014
(not global-auto-revert-mode) ; see #3460
buffer-file-name
(or auto-revert-remote-files ; see #5422
(not (file-remote-p buffer-file-name)))
(file-readable-p buffer-file-name)
(compat-call executable-find (magit-git-executable) t)
(magit-toplevel)
(or (not magit-auto-revert-tracked-only)
(magit-file-tracked-p buffer-file-name)))
(auto-revert-mode 1))))
;;;###autoload
(define-globalized-minor-mode magit-auto-revert-mode auto-revert-mode
@@ -152,7 +161,7 @@ and code surrounding the definition of this function."
(magit-auto-revert-mode 1)
(magit-message
"Turning on magit-auto-revert-mode...done%s"
(let ((elapsed (float-time (time-subtract nil start))))
(let ((elapsed (float-time (time-since start))))
(if (> elapsed 0.2)
(format " (%.3fs, %s buffers checked)" elapsed
(length (buffer-list)))
@@ -244,7 +253,7 @@ defaults to nil) for any BUFFER."
;; ^ `tramp-handle-file-in-directory-p' lacks this optimization.
(file-in-directory-p dir top))))))
(defun auto-revert-buffers--buffer-list-filter (fn)
(define-advice auto-revert-buffers (:around (fn) buffer-list-filter)
(cl-incf magit-auto-revert-counter)
(if (or global-auto-revert-mode
(not auto-revert-buffer-list)
@@ -257,9 +266,6 @@ defaults to nil) for any BUFFER."
(unless auto-revert-timer
(auto-revert-set-timer))))
(advice-add 'auto-revert-buffers :around
#'auto-revert-buffers--buffer-list-filter)
;;; _
(provide 'magit-autorevert)
;;; magit-autorevert.el ends here

View File

@@ -1,9 +1,9 @@
;;; magit-base.el --- Early birds -*- lexical-binding:t; coding:utf-8 -*-
;; Copyright (C) 2008-2023 The Magit Project Contributors
;; Copyright (C) 2008-2025 The Magit Project Contributors
;; Author: Jonas Bernoulli <jonas@bernoul.li>
;; Maintainer: Jonas Bernoulli <jonas@bernoul.li>
;; Author: Jonas Bernoulli <emacs.magit@jonas.bernoulli.dev>
;; Maintainer: Jonas Bernoulli <emacs.magit@jonas.bernoulli.dev>
;; SPDX-License-Identifier: GPL-3.0-or-later
@@ -32,20 +32,21 @@
;;; Code:
(defconst magit--minimal-git "2.2.0")
(defconst magit--minimal-emacs "25.1")
;; Also update EMACS_VERSION in "default.mk".
(defconst magit--minimal-emacs "27.1")
(defconst magit--minimal-git "2.25.0")
(require 'cl-lib)
(require 'compat)
(require 'dash)
(require 'eieio)
(require 'llama)
(require 'subr-x)
;; For older Emacs releases we depend on an updated `seq' release from
;; GNU ELPA, for `seq-keep'. Unfortunately something else may already
;; have required `seq', before `package' had a chance to put the more
;; recent version earlier on the `load-path'.
(when (and (featurep' seq)
(when (and (featurep 'seq)
(not (fboundp 'seq-keep)))
(unload-feature 'seq 'force))
(require 'seq)
@@ -54,7 +55,7 @@
(require 'magit-section)
(eval-when-compile (require 'ido))
(eval-when-compile (require 'info))
(declare-function Info-get-token "info" (pos start all &optional errorstring))
(eval-when-compile (require 'vc-git))
@@ -81,13 +82,14 @@ option to use `ivy-completing-read' or
`ivy-completing-read', note that the items may always be shown in
alphabetical order, depending on your version of Ivy."
:group 'magit-essentials
:type '(radio (function-item magit-builtin-completing-read)
(function-item magit-ido-completing-read)
:type `(radio (function-item ,#'magit-builtin-completing-read)
(function-item ,#'magit-ido-completing-read)
(function-item ivy-completing-read)
(function-item helm--completing-read-default)
(function :tag "Other function")))
(defcustom magit-dwim-selection
;; Do not function-quote to avoid circular dependencies.
'((magit-stash-apply nil t)
(magit-ediff-resolve-all nil t)
(magit-ediff-resolve-rest nil t)
@@ -144,6 +146,8 @@ The value has the form ((COMMAND nil|PROMPT DEFAULT)...).
(const untrack)
(const rename)
(const reset-bisect)
(const abort-cherry-pick)
(const abort-revert)
(const abort-rebase)
(const abort-merge)
(const merge-dirty)
@@ -211,6 +215,13 @@ Sequences:
`reset-bisect' Aborting (known to Git as \"resetting\") a
bisect operation loses all information collected so far.
`abort-cherry-pick' Aborting a cherry-pick throws away all
conflict resolutions which has already been carried out by the
user.
`abort-revert' Aborting a revert throws away all conflict
resolutions which has already been carried out by the user.
`abort-rebase' Aborting a rebase throws away all already
modified commits, but it's possible to restore those from the
reflog.
@@ -318,7 +329,7 @@ Various:
Global settings:
Instead of adding all of the above symbols to the value of this
option you can also set it to the atom `t', which has the same
option you can also set it to the atom t, which has the same
effect as adding all of the above symbols. Doing that most
certainly is a bad idea, especially because other symbols might
be added in the future. So even if you don't want to be asked
@@ -462,36 +473,41 @@ and delay of your graphical environment or operating system."
;;; Section Classes
(defclass magit-commit-section (magit-section) ())
(defclass magit-commit-section (magit-section)
((keymap :initform 'magit-commit-section-map)))
(setf (alist-get 'commit magit--section-type-alist) 'magit-commit-section)
(defclass magit-diff-section (magit-section) () :abstract t)
(defclass magit-diff-section (magit-section)
((keymap :initform 'magit-diff-section-map))
:abstract t)
(defclass magit-file-section (magit-diff-section)
((keymap :initform 'magit-file-section-map)
(source :initform nil)
(header :initform nil)
(binary :initform nil)))
(source :initform nil :initarg :source)
(header :initform nil :initarg :header)
(binary :initform nil :initarg :binary)))
(defclass magit-module-section (magit-file-section)
((keymap :initform 'magit-module-section-map)
(range :initform nil)))
(range :initform nil :initarg :range)))
(defclass magit-hunk-section (magit-diff-section)
((keymap :initform 'magit-hunk-section-map)
(refined :initform nil)
(combined :initform nil)
(from-range :initform nil)
(combined :initform nil :initarg :combined)
(from-range :initform nil :initarg :from-range)
(from-ranges :initform nil)
(to-range :initform nil)
(about :initform nil)))
(to-range :initform nil :initarg :to-range)
(about :initform nil :initarg :about)))
(setf (alist-get 'file magit--section-type-alist) 'magit-file-section)
(setf (alist-get 'module magit--section-type-alist) 'magit-module-section)
(setf (alist-get 'hunk magit--section-type-alist) 'magit-hunk-section)
(defclass magit-log-section (magit-section) () :abstract t)
(defclass magit-log-section (magit-section)
((keymap :initform 'magit-log-section-map))
:abstract t)
(defclass magit-unpulled-section (magit-log-section) ())
(defclass magit-unpushed-section (magit-log-section) ())
(defclass magit-unmerged-section (magit-log-section) ())
@@ -506,9 +522,29 @@ and delay of your graphical environment or operating system."
(defvar helm-crm-default-separator)
(defvar ivy-sort-functions-alist)
(defvar ivy-sort-matches-functions-alist)
(defvar vertico-sort-function)
(defvar magit-completing-read--silent-default nil)
(defvar magit-completing-read-default-prompt-predicate
(lambda ()
(and (eq magit-completing-read-function
'magit-builtin-completing-read)
(not (or (bound-and-true-p helm-mode)
(bound-and-true-p ivy-mode)
(bound-and-true-p selectrum-mode)
(bound-and-true-p vertico-mode)))))
"Function used to determine whether to add default to prompt.
This is used by `magit-completing-read' (which see).
The default function returns nil, when a completion frameworks is used
for which this is undesirable. More precisely, it returns nil, when
`magit-completing-read-function' is not `magit-builtin-completing-read',
or one of `helm-mode', `ivy-mode', `selectrum-mode' or `vertico-mode'
is enabled. When this function returns nil, then nil is passed to
`format-prompt' (which see), instead of the default (DEF or FALLBACK).")
(defun magit-completing-read ( prompt collection &optional
predicate require-match initial-input
hist def fallback)
@@ -548,13 +584,11 @@ acts similarly to `completing-read', except for the following:
is not, then this function always asks the user to choose a
candidate, just as if both defaults were nil.
- \": \" is appended to PROMPT.
- PROMPT is modified to end with \" (default DEF|FALLBACK): \"
provided that DEF or FALLBACK is non-nil, that neither
`ivy-mode' nor `helm-mode' is enabled, and that
`magit-completing-read-function' is set to its default value of
`magit-builtin-completing-read'."
- `format-prompt' is called on PROMPT and DEF (or FALLBACK if
DEF is nil). This appends \": \" to the prompt and may also
add the default to the prompt, using the format specified by
`minibuffer-default-prompt-format' and depending on
`magit-completing-read-default-prompt-predicate'."
(setq magit-completing-read--silent-default nil)
(if-let ((dwim (and def
(nth 2 (seq-find (pcase-lambda (`(,cmd ,re ,_))
@@ -571,13 +605,16 @@ acts similarly to `completing-read', except for the following:
(unless def
(setq def fallback))
(let ((command this-command)
(reply (funcall magit-completing-read-function
(concat prompt ": ")
(if (and def (not (member def collection)))
(cons def collection)
collection)
predicate
require-match initial-input hist def)))
(reply (funcall
magit-completing-read-function
(magit--format-prompt prompt def)
(if (and (not (functionp collection))
def
(not (member def collection)))
(cons def collection)
collection)
predicate
require-match initial-input hist def)))
(setq this-command command)
;; Note: Avoid `string=' to support `helm-comp-read-use-marked'.
(if (equal reply "")
@@ -586,6 +623,13 @@ acts similarly to `completing-read', except for the following:
nil)
reply))))
(defun magit--format-prompt (prompt default)
(format-prompt (if (string-suffix-p ": " prompt)
(substring prompt 0 -2)
prompt)
(and (funcall magit-completing-read-default-prompt-predicate)
default)))
(defun magit--completion-table (collection)
(lambda (string pred action)
(if (eq action 'metadata)
@@ -595,22 +639,14 @@ acts similarly to `completing-read', except for the following:
(defun magit-builtin-completing-read
(prompt choices &optional predicate require-match initial-input hist def)
"Magit wrapper for standard `completing-read' function."
(unless (or (bound-and-true-p helm-mode)
(bound-and-true-p ivy-mode)
(bound-and-true-p vertico-mode)
(bound-and-true-p selectrum-mode))
(setq prompt (magit-prompt-with-default prompt def)))
(unless (or (bound-and-true-p helm-mode)
(bound-and-true-p ivy-mode))
(setq choices (magit--completion-table choices)))
(cl-letf (((symbol-function #'completion-pcm--all-completions)))
(when (< emacs-major-version 26)
(fset 'completion-pcm--all-completions
'magit-completion-pcm--all-completions))
(let ((ivy-sort-functions-alist nil))
(completing-read prompt choices
predicate require-match
initial-input hist def))))
(let ((ivy-sort-functions-alist nil)
(vertico-sort-function nil))
(completing-read prompt choices
predicate require-match
initial-input hist def)))
(define-obsolete-function-alias 'magit-completing-read-multiple*
'magit-completing-read-multiple "Magit-Section 4.0.0")
@@ -640,12 +676,6 @@ third-party completion frameworks."
(equal omit-nulls t))
(setq input string))
(funcall split-string string separators omit-nulls trim)))
;; In Emacs 25 this function has a bug, so we use a copy of the
;; version from Emacs 26. bef9c7aa3
((symbol-function #'completion-pcm--all-completions)
(if (< emacs-major-version 26)
'magit-completion-pcm--all-completions
(symbol-function #'completion-pcm--all-completions)))
;; Prevent `BUILT-IN' completion from messing up our existing
;; order of the completion candidates. aa5f098ab
(table (magit--completion-table table))
@@ -660,7 +690,8 @@ third-party completion frameworks."
(if no-split nil (bound-and-true-p helm-crm-default-separator)))
;; And now, the moment we have all been waiting for...
(values (completing-read-multiple
prompt table predicate require-match initial-input
(magit--format-prompt prompt def)
table predicate require-match initial-input
hist def inherit-input-method)))
(if no-split input values)))
@@ -685,12 +716,6 @@ back to built-in `completing-read' for now." :error)
(magit-builtin-completing-read prompt choices predicate require-match
initial-input hist def)))
(defun magit-prompt-with-default (prompt def)
(if (and def (length> prompt 2)
(string-equal ": " (substring prompt -2)))
(format "%s (default %s): " (substring prompt 0 -2) def)
prompt))
(defvar-keymap magit-minibuffer-local-ns-map
:parent minibuffer-local-map
"SPC" #'magit-whitespace-disallowed
@@ -713,7 +738,7 @@ This is similar to `read-string', but
which case that is returned,
* whitespace is not allowed and leading and trailing whitespace is
removed automatically if NO-WHITESPACE is non-nil,
* \": \" is appended to PROMPT, and
* `format-prompt' is used internally.
* an invalid DEFAULT-VALUE is silently ignored."
(when default-value
(when (consp default-value)
@@ -722,7 +747,7 @@ This is similar to `read-string', but
(setq default-value nil)))
(let* ((minibuffer-completion-table nil)
(val (read-from-minibuffer
(magit-prompt-with-default (concat prompt ": ") default-value)
(format-prompt prompt default-value)
initial-input (and no-whitespace magit-minibuffer-local-ns-map)
nil history default-value inherit-input-method))
(trim (lambda (regexp string)
@@ -754,10 +779,10 @@ This is similar to `read-string', but
(let ((parts (nconc (list ,@(mapcar #'cadr clauses))
,(and verbose '(list "[C-g] to abort")))))
(concat ,prompt
(mapconcat #'identity (butlast parts) ", ")
(string-join (butlast parts) ", ")
", or " (car (last parts)) " "))
',(mapcar #'car clauses))
,@(--map `(,(car it) ,@(cddr it)) clauses))
,@(mapcar (##`(,(car %) ,@(cddr %))) clauses))
(message "")))
(defun magit-y-or-n-p (prompt &optional action)
@@ -776,6 +801,16 @@ ACTION is a member of option `magit-slow-confirm'."
(cl-defun magit-confirm ( action &optional prompt prompt-n noabort
(items nil sitems) prompt-suffix)
(declare (indent defun))
(when (and prompt (listp prompt))
(setq prompt
(apply #'format (car prompt)
(mapcar (lambda (a) (if (stringp a) (string-replace "%" "%%" a) a))
(cdr prompt)))))
(when (and prompt-n (listp prompt-n))
(setq prompt-n
(apply #'format (car prompt-n)
(mapcar (lambda (a) (if (stringp a) (string-replace "%" "%%" a) a))
(cdr prompt-n)))))
(setq prompt-n (format (concat (or prompt-n prompt) "? ") (length items)))
(setq prompt (format (concat (or prompt (magit-confirm-make-prompt action))
"? ")
@@ -798,7 +833,7 @@ ACTION is a member of option `magit-slow-confirm'."
((length= items 1)
(and (magit-y-or-n-p prompt action) items))
((length> items 1)
(and (magit-y-or-n-p (concat (mapconcat #'identity items "\n")
(and (magit-y-or-n-p (concat (string-join items "\n")
"\n\n" prompt-n)
action)
items)))
@@ -838,30 +873,25 @@ See info node `(magit)Debugging Tools' for more information."
#'shell-quote-argument
`(,(concat invocation-directory invocation-name)
"-Q" "--eval" "(setq debug-on-error t)"
,@(cl-mapcan
,@(mapcan
(lambda (dir) (list "-L" dir))
(delete-dups
(cl-mapcan
(mapcan
(lambda (lib)
(let ((path (locate-library lib)))
(cond
(path
(list (file-name-directory path)))
((not (equal lib "libgit"))
(error "Cannot find mandatory dependency %s" lib)))))
(if-let ((path (locate-library lib)))
(list (file-name-directory path))
(error "Cannot find mandatory dependency %s" lib)))
'(;; Like `LOAD_PATH' in `default.mk'.
"compat"
"dash"
"libgit"
"llama"
"seq"
"transient"
"with-editor"
;; Obviously `magit' itself is needed too.
"magit"
;; While these are part of the Magit repository,
;; they are distributed as separate packages.
"magit-section"
"git-commit"
))))
;; While this is part of the Magit repository,
;; it is distributed as a separate package.
"magit-section"))))
;; Avoid Emacs bug#16406 by using full path.
"-l" ,(file-name-sans-extension (locate-library "magit")))
" ")))
@@ -882,11 +912,11 @@ as STRING."
(i 0))
`(let ((,s ,string))
(let ,(save-match-data
(cl-mapcan (lambda (sym)
(cl-incf i)
(and (not (eq (aref (symbol-name sym) 0) ?_))
(list (list sym (list 'match-string i s)))))
varlist))
(mapcan (lambda (sym)
(cl-incf i)
(and (not (eq (aref (symbol-name sym) 0) ?_))
(list (list sym (list 'match-string i s)))))
varlist))
,@body))))
(defun magit-delete-line ()
@@ -901,26 +931,23 @@ If optional NUM is specified, only delete that subexpression."
(defun magit-file-line (file)
"Return the first line of FILE as a string."
(when (file-regular-p file)
(with-temp-buffer
(insert-file-contents file)
(buffer-substring-no-properties (point-min)
(line-end-position)))))
(and (file-regular-p file)
(with-temp-buffer
(insert-file-contents file)
(buffer-substring-no-properties (point-min)
(line-end-position)))))
(defun magit-file-lines (file &optional keep-empty-lines)
"Return a list of strings containing one element per line in FILE.
Unless optional argument KEEP-EMPTY-LINES is t, trim all empty lines."
(when (file-regular-p file)
(with-temp-buffer
(insert-file-contents file)
(split-string (buffer-string) "\n" (not keep-empty-lines)))))
(and (file-regular-p file)
(with-temp-buffer
(insert-file-contents file)
(split-string (buffer-string) "\n" (not keep-empty-lines)))))
(defun magit-set-header-line-format (string)
"Set the header-line using STRING.
Propertize STRING with the `magit-header-line'. If the `face'
property of any part of STRING is already set, then that takes
precedence. Also pad the left side of STRING so that it aligns
with the text area."
"Set `header-line-format' in the current buffer based on STRING.
Pad the left side of STRING so that it aligns with the text area."
(setq header-line-format
(concat (propertize " " 'display '(space :align-to 0))
string)))
@@ -993,6 +1020,16 @@ one trailing newline is added."
(and (eq trim ?\n) "\n"))
str)))
(defun magit--separate (pred list)
"Separate elements of LIST that do and don't satisfy PRED.
Return a list of two lists; the first containing the elements that
do satisfy PRED and the second containing the elements that don't."
(let (y n)
(dolist (elt list)
(push elt (if (funcall pred elt) y n)))
(list (nreverse y)
(nreverse n))))
(defun magit--version> (v1 v2)
"Return t if version V1 is higher (younger) than V2.
This function should be named `version>' and be part of Emacs."
@@ -1005,77 +1042,6 @@ This function should be named `version>=' and be part of Emacs."
;;; Kludges for Emacs Bugs
(defun magit-file-accessible-directory-p (filename)
"Like `file-accessible-directory-p' but work around an Apple bug.
See http://debbugs.gnu.org/cgi/bugreport.cgi?bug=21573#17
and https://github.com/magit/magit/issues/2295."
(and (file-directory-p filename)
(file-accessible-directory-p filename)))
(when (< emacs-major-version 27)
;; Work around https://debbugs.gnu.org/cgi/bugreport.cgi?bug=21559.
;; Fixed by cb55ccae8be946f1562d74718086a4c8c8308ee5 in Emacs 27.1.
(with-eval-after-load 'vc-git
(defun vc-git-conflicted-files (directory)
"Return the list of files with conflicts in DIRECTORY."
(let* ((status
(vc-git--run-command-string directory "diff-files"
"--name-status"))
(lines (when status (split-string status "\n" 'omit-nulls)))
files)
(dolist (line lines files)
(when (string-match "\\([ MADRCU?!]\\)[ \t]+\\(.+\\)" line)
(let ((state (match-string 1 line))
(file (match-string 2 line)))
(when (equal state "U")
(push (expand-file-name file directory) files)))))))))
(when (< emacs-major-version 27)
(defun vc-git--call@bug21559 (fn buffer command &rest args)
"Backport https://debbugs.gnu.org/cgi/bugreport.cgi?bug=21559."
(let ((process-environment process-environment))
(when revert-buffer-in-progress-p
(push "GIT_OPTIONAL_LOCKS=0" process-environment))
(apply fn buffer command args)))
(advice-add 'vc-git--call :around 'vc-git--call@bug21559)
(defun vc-git-command@bug21559
(fn buffer okstatus file-or-list &rest flags)
"Backport https://debbugs.gnu.org/cgi/bugreport.cgi?bug=21559."
(let ((process-environment process-environment))
(when revert-buffer-in-progress-p
(push "GIT_OPTIONAL_LOCKS=0" process-environment))
(apply fn buffer okstatus file-or-list flags)))
(advice-add 'vc-git-command :around 'vc-git-command@bug21559)
(defun auto-revert-handler@bug21559 (fn)
"Backport https://debbugs.gnu.org/cgi/bugreport.cgi?bug=21559."
(let ((revert-buffer-in-progress-p t))
(funcall fn)))
(advice-add 'auto-revert-handler :around 'auto-revert-handler@bug21559)
)
(when (< emacs-major-version 26)
;; In Emacs 25 `completion-pcm--all-completions' reverses the
;; completion list. This is the version from Emacs 26, which
;; fixes that issue. bug#24676
(defun magit-completion-pcm--all-completions (prefix pattern table pred)
(if (completion-pcm--pattern-trivial-p pattern)
(all-completions (concat prefix (car pattern)) table pred)
(let* ((regex (completion-pcm--pattern->regex pattern))
(case-fold-search completion-ignore-case)
(completion-regexp-list (cons regex completion-regexp-list))
(compl (all-completions
(concat prefix
(if (stringp (car pattern)) (car pattern) ""))
table pred)))
(if (not (functionp table))
compl
(let ((poss ()))
(dolist (c compl)
(when (string-match-p regex c) (push c poss)))
(nreverse poss)))))))
(defun magit-which-function ()
"Return current function name based on point.
@@ -1134,7 +1100,7 @@ the value in the symbol's `saved-value' property if any, or
;;; Kludges for Info Manuals
;;;###autoload
(defun Info-follow-nearest-node--magit-gitman (fn &optional fork)
(define-advice Info-follow-nearest-node (:around (fn &optional fork) gitman)
(let ((node (Info-get-token
(point) "\\*note[ \n\t]+"
"\\*note[ \n\t]+\\([^:]*\\):\\(:\\|[ \n\t]*(\\)?")))
@@ -1145,19 +1111,12 @@ the value in the symbol's `saved-value' property if any, or
(man (match-string 1 node)))
('woman (require 'woman)
(woman (match-string 1 node)))
(_
(user-error "Invalid value for `magit-view-git-manual-method'")))
(_ (user-error "Invalid value for `magit-view-git-manual-method'")))
(funcall fn fork))))
;;;###autoload
(advice-add 'Info-follow-nearest-node :around
#'Info-follow-nearest-node--magit-gitman)
;; When making changes here, then also adjust the copy in docs/Makefile.
;;;###autoload
(advice-add 'org-man-export :around #'org-man-export--magit-gitman)
;;;###autoload
(defun org-man-export--magit-gitman (fn link description format)
(define-advice org-man-export (:around (fn link description format) gitman)
(if (and (eq format 'texinfo)
(string-prefix-p "git" link))
(string-replace "%s" link "
@@ -1177,47 +1136,31 @@ the %s(1) manpage.
;;; Kludges for Package Managers
(defun magit--straight-chase-links (filename)
(defun magit--chase-links (filename)
"Chase links in FILENAME until a name that is not a link.
This is the same as `file-chase-links', except that it also
handles fake symlinks that are created by the package manager
straight.el on Windows.
This is the same as `file-chase-links', except that it also handles
fake symlinks that are created by some source based package managers
\(Elpaca and Straight) on Windows.
See <https://github.com/raxod502/straight.el/issues/520>."
(when (and (bound-and-true-p straight-symlink-emulation-mode)
(fboundp 'straight-chase-emulated-symlink))
(when-let ((target (straight-chase-emulated-symlink filename)))
(unless (eq target 'broken)
(setq filename target))))
(when-let*
((manager (cond ((bound-and-true-p straight-symlink-mode) 'straight)
((bound-and-true-p elpaca-no-symlink-mode) 'elpaca)))
(build (pcase manager
('straight (bound-and-true-p straight-build-dir))
('elpaca (bound-and-true-p elpaca-builds-directory))))
((string-prefix-p build filename))
(repo (pcase manager
('straight
(and (bound-and-true-p straight-base-dir)
(expand-file-name "repos/magit/lisp/" straight-base-dir)))
('elpaca
(and (bound-and-true-p elpaca-repos-directory)
(expand-file-name "magit/lisp/" elpaca-repos-directory))))))
(setq filename (expand-file-name (file-name-nondirectory filename) repo)))
(file-chase-links filename))
;;; Kludges for older Emacs versions
(if (fboundp 'with-connection-local-variables)
(defalias 'magit--with-connection-local-variables
#'with-connection-local-variables)
(defmacro magit--with-connection-local-variables (&rest body)
"Abridged `with-connection-local-variables' for pre Emacs 27 compatibility.
Bind shell file name and switch for remote execution.
`with-connection-local-variables' isn't available until Emacs 27.
This kludge provides the minimal functionality required by
Magit."
`(if (file-remote-p default-directory)
(pcase-let ((`(,shell-file-name ,shell-command-switch)
(with-no-warnings ; about unknown tramp functions
(require 'tramp)
(let ((vec (tramp-dissect-file-name
default-directory)))
(list (tramp-get-method-parameter
vec 'tramp-remote-shell)
(mapconcat #'identity
(tramp-get-method-parameter
vec 'tramp-remote-shell-args)
" "))))))
,@body)
,@body)))
;;; Miscellaneous
(defun magit-message (format-string &rest args)
@@ -1225,7 +1168,7 @@ Magit."
Like `message', except that if the users configured option
`magit-no-message' to prevent the message corresponding to
FORMAT-STRING to be displayed, then don't."
(unless (--first (string-prefix-p it format-string) magit-no-message)
(unless (seq-find (##string-prefix-p % format-string) magit-no-message)
(apply #'message format-string args)))
(defun magit-msg (format-string &rest args)
@@ -1259,6 +1202,16 @@ Like `message', except that `message-log-max' is bound to nil."
ellipsis)))
(user-error "Variable magit-ellipsis is invalid"))))
(defun magit--ext-regexp-quote (string)
"Like `reqexp-quote', but for Extended Regular Expressions."
(let ((special (string-to-list "[*.\\?+^$({"))
(quoted nil))
(dolist (char string)
(when (memq char special)
(push ?\\ quoted))
(push char quoted))
(concat (nreverse quoted))))
;;; _
(provide 'magit-base)
;;; magit-base.el ends here

View File

@@ -1,9 +1,9 @@
;;; magit-bisect.el --- Bisect support for Magit -*- lexical-binding:t -*-
;; Copyright (C) 2008-2023 The Magit Project Contributors
;; Copyright (C) 2008-2025 The Magit Project Contributors
;; Author: Jonas Bernoulli <jonas@bernoul.li>
;; Maintainer: Jonas Bernoulli <jonas@bernoul.li>
;; Author: Jonas Bernoulli <emacs.magit@jonas.bernoulli.dev>
;; Maintainer: Jonas Bernoulli <emacs.magit@jonas.bernoulli.dev>
;; SPDX-License-Identifier: GPL-3.0-or-later
@@ -63,10 +63,8 @@
("-n" "Don't checkout commits" "--no-checkout")
("-p" "Follow only first parent of a merge" "--first-parent"
:if (lambda () (magit-git-version>= "2.29")))
(6 magit-bisect:--term-old
:if (lambda () (magit-git-version>= "2.7")))
(6 magit-bisect:--term-new
:if (lambda () (magit-git-version>= "2.7")))]
(magit-bisect:--term-old :level 6)
(magit-bisect:--term-new :level 6)]
["Actions"
("B" "Start" magit-bisect-start)
("s" "Start script" magit-bisect-run)]]
@@ -74,8 +72,7 @@
:if magit-bisect-in-progress-p
("B" "Bad" magit-bisect-bad)
("g" "Good" magit-bisect-good)
(6 "m" "Mark" magit-bisect-mark
:if (lambda () (magit-git-version>= "2.7")))
("m" "Mark" magit-bisect-mark :level 6)
("k" "Skip" magit-bisect-skip)
("r" "Reset" magit-bisect-reset)
("s" "Run script" magit-bisect-run)])
@@ -104,15 +101,7 @@ other actions from the bisect transient command (\
(interactive (if (magit-bisect-in-progress-p)
(user-error "Already bisecting")
(magit-bisect-start-read-args)))
(unless (magit-rev-ancestor-p good bad)
(user-error
"The %s revision (%s) has to be an ancestor of the %s one (%s)"
(or (transient-arg-value "--term-old=" args) "good")
good
(or (transient-arg-value "--term-new=" args) "bad")
bad))
(when (magit-anything-modified-p)
(user-error "Cannot bisect with uncommitted changes"))
(magit-bisect-start--assert bad good args)
(magit-repository-local-set 'bisect--first-parent
(transient-arg-value "--first-parent" args))
(magit-git-bisect "start" (list args bad good) t))
@@ -130,6 +119,17 @@ other actions from the bisect transient command (\
bad)
args)))
(defun magit-bisect-start--assert (bad good args)
(unless (magit-rev-ancestor-p good bad)
(user-error
"The %s revision (%s) has to be an ancestor of the %s one (%s)"
(or (transient-arg-value "--term-old=" args) "good")
good
(or (transient-arg-value "--term-new=" args) "bad")
bad))
(when (magit-anything-modified-p)
(user-error "Cannot bisect with uncommitted changes")))
;;;###autoload
(defun magit-bisect-reset ()
"After bisecting, cleanup bisection state and return to original `HEAD'."
@@ -197,17 +197,17 @@ bisect run'."
(magit-bisect-start-read-args))))
(cons (read-shell-command "Bisect shell command: ") args)))
(when (and bad good)
(magit-bisect-start--assert bad good args)
;; Avoid `magit-git-bisect' because it's asynchronous, but the
;; next `git bisect run' call requires the bisect to be started.
(magit-with-toplevel
(magit-process-git
(list :file (expand-file-name "BISECT_CMD_OUTPUT" (magit-gitdir)))
(magit-process-git-arguments
(list "bisect" "start" bad good args)))
"bisect" "start" bad good args)
(magit-refresh)))
(magit--with-connection-local-variables
(magit-git-bisect "run" (list shell-file-name
shell-command-switch cmdline))))
(with-connection-local-variables
(magit-git-bisect "run" (list shell-file-name
shell-command-switch cmdline))))
(defun magit-git-bisect (subcommand &optional args no-assert)
(unless (or no-assert (magit-bisect-in-progress-p))
@@ -256,7 +256,7 @@ bisect run'."
(done-re "^\\([a-z0-9]\\{40,\\}\\) is the first bad commit$")
(bad-line (or (and (string-match done-re (car lines))
(pop lines))
(--first (string-match done-re it) lines))))
(seq-find (##string-match done-re %) lines))))
(magit-insert-section ((eval (if bad-line 'commit 'bisect-output))
(and bad-line (match-string 1 bad-line)))
(magit-insert-heading
@@ -270,7 +270,7 @@ bisect run'."
"While bisecting, insert section visualizing the bisect state."
(when (magit-bisect-in-progress-p)
(magit-insert-section (bisect-view)
(magit-insert-heading "Bisect Rest:")
(magit-insert-heading t "Bisect Rest")
(magit-git-wash (apply-partially #'magit-log-wash-log 'bisect-vis)
"bisect" "visualize" "git" "log"
"--format=%h%x00%D%x00%s" "--decorate=full"
@@ -282,7 +282,7 @@ bisect run'."
"While bisecting, insert section logging bisect progress."
(when (magit-bisect-in-progress-p)
(magit-insert-section (bisect-log)
(magit-insert-heading "Bisect Log:")
(magit-insert-heading t "Bisect Log")
(magit-git-wash #'magit-wash-bisect-log "bisect" "log")
(insert ?\n))))
@@ -299,9 +299,9 @@ bisect run'."
(narrow-to-region beg (point))
(goto-char (point-min))
(magit-insert-section (bisect-item heading t)
(insert (propertize heading 'font-lock-face
'magit-section-secondary-heading))
(magit-insert-heading)
(magit-insert-heading
(propertize heading 'font-lock-face
'magit-section-secondary-heading))
(magit-wash-sequence
(apply-partially #'magit-log-wash-rev 'bisect-log
(magit-abbrev-length)))

View File

@@ -1,9 +1,9 @@
;;; magit-blame.el --- Blame support for Magit -*- lexical-binding:t -*-
;; Copyright (C) 2008-2023 The Magit Project Contributors
;; Copyright (C) 2008-2025 The Magit Project Contributors
;; Author: Jonas Bernoulli <jonas@bernoul.li>
;; Maintainer: Jonas Bernoulli <jonas@bernoul.li>
;; Author: Jonas Bernoulli <emacs.magit@jonas.bernoulli.dev>
;; Maintainer: Jonas Bernoulli <emacs.magit@jonas.bernoulli.dev>
;; SPDX-License-Identifier: GPL-3.0-or-later
@@ -107,7 +107,7 @@ the background color.
Blame information is displayed using overlays. Such extensive
use of overlays is known to slow down even basic operations, such
as moving the cursor. To reduce the number of overlays the margin
as moving the cursor. To reduce the number of overlays the margin
style had to be removed from the default value of this option.
Note that the margin overlays are created even if another style
@@ -157,25 +157,25 @@ and then turned on again when turning off the latter."
:type '(choice (const :tag "No lighter" "") string))
(defcustom magit-blame-goto-chunk-hook
'(magit-blame-maybe-update-revision-buffer
magit-blame-maybe-show-message)
(list #'magit-blame-maybe-update-revision-buffer
#'magit-blame-maybe-show-message)
"Hook run after point entered another chunk."
:package-version '(magit . "2.13.0")
:group 'magit-blame
:type 'hook
:get #'magit-hook-custom-get
:options '(magit-blame-maybe-update-revision-buffer
magit-blame-maybe-show-message))
:options (list #'magit-blame-maybe-update-revision-buffer
#'magit-blame-maybe-show-message))
;;; Faces
(defface magit-blame-highlight
`((((class color) (background light))
,@(and (>= emacs-major-version 27) '(:extend t))
'((((class color) (background light))
:extend t
:background "grey80"
:foreground "black")
(((class color) (background dark))
,@(and (>= emacs-major-version 27) '(:extend t))
:extend t
:background "grey25"
:foreground "white"))
"Face used for highlighting when blaming.
@@ -199,7 +199,7 @@ Also see option `magit-blame-styles'."
:group 'magit-faces)
(defface magit-blame-heading
`((t ,@(and (>= emacs-major-version 27) '(:extend t))
'((t :extend t
:inherit magit-blame-highlight
:weight normal
:slant normal))
@@ -274,18 +274,18 @@ Also see option `magit-blame-styles'."
(error "Cannot get blame chunk at eob"))
(car (magit-blame--parse-chunk type))))))
(noerror nil)
(t (error "Buffer does not visit a tracked file")))))))
((error "Buffer does not visit a tracked file")))))))
(defun magit-blame-chunk-at (pos)
(--some (overlay-get it 'magit-blame-chunk)
(overlays-at pos)))
(seq-some (##overlay-get % 'magit-blame-chunk)
(overlays-at pos)))
(defun magit-blame--overlay-at (&optional pos key)
(unless pos
(setq pos (point)))
(--first (overlay-get it (or key 'magit-blame-chunk))
(nconc (overlays-at pos)
(overlays-in pos pos))))
(seq-find (##overlay-get % (or key 'magit-blame-chunk))
(nconc (overlays-at pos)
(overlays-in pos pos))))
;;; Keymaps
@@ -346,7 +346,7 @@ in `magit-blame-read-only-mode-map' instead."
(and (cl-find-if (lambda (style)
(assq 'margin-format (cdr style)))
magit-blame-styles)))
(magit-blame--update-margin))
(magit-blame--update-margin 'enable))
(t
(when (process-live-p magit-blame-process)
(kill-process magit-blame-process)
@@ -365,7 +365,7 @@ in `magit-blame-read-only-mode-map' instead."
(kill-local-variable 'magit-blame-disabled-modes)
(kill-local-variable 'magit-blame-type)
(kill-local-variable 'magit-blame--style)
(magit-blame--update-margin)
(magit-blame--update-margin 'disable)
(magit-blame--remove-overlays))))
(defun magit-blame--refresh ()
@@ -453,6 +453,7 @@ modes is toggled, then this mode also gets toggled automatically.
(let ((status (process-status process)))
(when (memq status '(exit signal))
(kill-buffer (process-buffer process))
(kill-buffer (process-get process 'stderr-buf))
(if (and (eq status 'exit)
(zerop (process-exit-status process)))
(unless quiet
@@ -536,6 +537,8 @@ modes is toggled, then this mode also gets toggled automatically.
;;; Display
(defvar-local magit-blame--previous-margin-width nil)
(defsubst magit-blame--style-get (key)
(cdr (assoc key (cdr magit-blame--style))))
@@ -596,15 +599,20 @@ modes is toggled, then this mode also gets toggled automatically.
(magit-blame--update-heading-overlay ov)))
(defun magit-blame--make-highlight-overlay (chunk beg)
(let ((ov (make-overlay beg (save-excursion
(goto-char beg)
(1+ (line-end-position))))))
(let ((ov (make-overlay beg (1+ (magit--eol-position beg)))))
(overlay-put ov 'magit-blame-chunk chunk)
(overlay-put ov 'magit-blame-highlight t)
(magit-blame--update-highlight-overlay ov)))
(defun magit-blame--update-margin ()
(setq left-margin-width (or (magit-blame--style-get 'margin-width) 0))
(defun magit-blame--update-margin (&optional action)
(when (eq action 'enable)
(setq magit-blame--previous-margin-width left-margin-width))
(setq left-margin-width
(if (eq action 'disable)
(prog1 magit-blame--previous-margin-width
(setq magit-blame--previous-margin-width nil))
(or (magit-blame--style-get 'margin-width)
magit-blame--previous-margin-width)))
(set-window-buffer (selected-window) (current-buffer)))
(defun magit-blame--update-overlays ()
@@ -643,7 +651,10 @@ modes is toggled, then this mode also gets toggled automatically.
(overlay-put
ov 'before-string
(if-let ((format (magit-blame--style-get 'heading-format)))
(magit-blame--format-string ov format 'magit-blame-heading)
;; Use `default' as the last face to avoid picking up any face
;; attributes from the first character of the text on which we
;; put the overlay. See #5233.
(magit-blame--format-string ov format '(magit-blame-heading default))
(and (magit-blame--style-get 'show-lines)
(or (not (magit-blame--style-get 'margin-format))
(save-excursion
@@ -651,7 +662,7 @@ modes is toggled, then this mode also gets toggled automatically.
;; Special case of the special case described in
;; `magit-blame--make-margin-overlay'. For empty
;; lines it is not possible to show both overlays
;; without the line being to high.
;; without the line being too high.
(not (= (point) (line-end-position)))))
magit-blame-separator))))
@@ -682,11 +693,7 @@ modes is toggled, then this mode also gets toggled automatically.
(propertize format 'font-lock-face face)
(cl-flet* ((p0 (s f)
(propertize s 'font-lock-face
(if face
(if (listp face)
face
(list f face))
f)))
(if face (cons f (ensure-list face)) f)))
(p1 (k f)
(p0 (cdr (assoc k revinfo)) f))
(p2 (k1 k2 f)
@@ -709,13 +716,12 @@ modes is toggled, then this mode also gets toggled automatically.
str)))
(defun magit-blame--format-separator ()
(propertize
(concat (propertize "\s" 'display '(space :height (2)))
(propertize "\n" 'line-height t))
'font-lock-face `(:background
,(face-attribute 'magit-blame-heading
:background nil t)
,@(and (>= emacs-major-version 27) '(:extend t)))))
(propertize (concat (propertize "\s" 'display '(space :height (2)))
(propertize "\n" 'line-height t))
'font-lock-face
`( :extend t
:background
,(face-attribute 'magit-blame-heading :background nil t))))
(defun magit-blame--format-time-string (time tz)
(let* ((time-format (or (magit-blame--style-get 'time-format)
@@ -736,14 +742,13 @@ modes is toggled, then this mode also gets toggled automatically.
(defun magit-blame-maybe-show-message ()
(when (magit-blame--style-get 'show-message)
(let ((message-log-max 0))
(if-let ((msg (cdr (assoc "summary"
(gethash (oref (magit-current-blame-chunk)
orig-rev)
magit-blame-cache)))))
(progn (set-text-properties 0 (length msg) nil msg)
(message msg))
(message "Commit data not available yet. Still blaming.")))))
(if-let ((msg (cdr (assoc "summary"
(gethash (oref (magit-current-blame-chunk)
orig-rev)
magit-blame-cache)))))
(progn (set-text-properties 0 (length msg) nil msg)
(magit-msg "%S" msg))
(magit-msg "Commit data not available yet. Still blaming."))))
;;; Commands
@@ -810,7 +815,7 @@ not turn on `read-only-mode'."
(if-let ((chunk (magit-current-blame-chunk)))
(unless (oref chunk prev-rev)
(user-error "Chunk has no further history"))
(user-error "Commit data not available yet. Still blaming."))
(user-error "Still blaming, commit data not available yet"))
(unless (magit-file-relative-name nil (not magit-buffer-file-name))
(if buffer-file-name
(user-error "Buffer isn't visiting a tracked file")
@@ -878,7 +883,8 @@ then also kill the buffer."
(user-error "No more chunks")))
(defun magit-blame-next-chunk-same-commit (&optional previous)
"Move to the next chunk from the same commit.\n\n(fn)"
"Move to the next chunk from the same commit.
\n(fn)"
(interactive)
(if-let ((rev (oref (magit-current-blame-chunk) orig-rev)))
(let ((pos (point)) ov)
@@ -890,9 +896,9 @@ then also kill the buffer."
#'previous-single-char-property-change
#'next-single-char-property-change)
pos 'magit-blame-chunk)))
(when-let ((o (magit-blame--overlay-at pos)))
(when (equal (oref (magit-blame-chunk-at pos) orig-rev) rev)
(setq ov o)))))
(when-let ((o (magit-blame--overlay-at pos))
((equal (oref (magit-blame-chunk-at pos) orig-rev) rev)))
(setq ov o))))
(if ov
(goto-char (overlay-start ov))
(user-error "No more chunks from same commit")))

View File

@@ -1,11 +1,11 @@
;;; magit-bookmark.el --- Bookmark support for Magit -*- lexical-binding:t -*-
;;; magit-bookmark.el --- Bookmarks for Magit buffers -*- lexical-binding:t -*-
;; Copyright (C) 2008-2023 The Magit Project Contributors
;; Copyright (C) 2008-2025 The Magit Project Contributors
;; Inspired by an earlier implementation by Yuri Khan.
;; Author: Jonas Bernoulli <jonas@bernoul.li>
;; Maintainer: Jonas Bernoulli <jonas@bernoul.li>
;; Author: Jonas Bernoulli <emacs.magit@jonas.bernoulli.dev>
;; Maintainer: Jonas Bernoulli <emacs.magit@jonas.bernoulli.dev>
;; SPDX-License-Identifier: GPL-3.0-or-later
@@ -30,6 +30,23 @@
(require 'magit)
(require 'bookmark)
;;; Common
(cl-defmethod magit-bookmark-get-filename (&context (major-mode magit-mode))
(magit-toplevel))
(cl-defmethod magit-bookmark-get-buffer-create
(bookmark (mode (derived-mode magit-mode)))
(let ((default-directory (bookmark-get-filename bookmark))
(magit-display-buffer-function #'identity)
(magit-display-buffer-noselect t))
(apply (intern (format "%s-setup-buffer"
(substring (symbol-name mode) 0 -5)))
(mapcar (##bookmark-prop-get bookmark %)
(get mode 'magit-bookmark-variables)))))
;;; Diff
;;;; Diff
@@ -48,7 +65,7 @@
('undefined
(delq nil (list magit-buffer-typearg magit-buffer-range-hashed))))
(if magit-buffer-diff-files
(concat " -- " (mapconcat #'identity magit-buffer-diff-files " "))
(concat " -- " (string-join magit-buffer-diff-files " "))
"")))
;;;; Revision
@@ -62,7 +79,7 @@
(format "magit-revision(%s %s)"
(magit-rev-abbrev magit-buffer-revision)
(if magit-buffer-diff-files
(mapconcat #'identity magit-buffer-diff-files " ")
(string-join magit-buffer-diff-files " ")
(magit-rev-format "%s" magit-buffer-revision))))
;;;; Stash
@@ -76,9 +93,15 @@
(format "magit-stash(%s %s)"
(magit-rev-abbrev magit-buffer-revision)
(if magit-buffer-diff-files
(mapconcat #'identity magit-buffer-diff-files " ")
(string-join magit-buffer-diff-files " ")
(magit-rev-format "%s" magit-buffer-revision))))
(cl-defmethod magit-bookmark--get-child-value
(section &context (major-mode magit-stash-mode))
(string-replace magit-buffer-revision
magit-buffer-revision-hash
(oref section value)))
;;; Log
;;;; Log
@@ -89,9 +112,9 @@
(cl-defmethod magit-bookmark-name (&context (major-mode magit-log-mode))
(format "magit-log(%s%s)"
(mapconcat #'identity magit-buffer-revisions " ")
(string-join magit-buffer-revisions " ")
(if magit-buffer-log-files
(concat " -- " (mapconcat #'identity magit-buffer-log-files " "))
(concat " -- " (string-join magit-buffer-log-files " "))
"")))
;;;; Cherry

View File

@@ -1,9 +1,9 @@
;;; magit-branch.el --- Branch support -*- lexical-binding:t -*-
;; Copyright (C) 2008-2023 The Magit Project Contributors
;; Copyright (C) 2008-2025 The Magit Project Contributors
;; Author: Jonas Bernoulli <jonas@bernoul.li>
;; Maintainer: Jonas Bernoulli <jonas@bernoul.li>
;; Author: Jonas Bernoulli <emacs.magit@jonas.bernoulli.dev>
;; Maintainer: Jonas Bernoulli <emacs.magit@jonas.bernoulli.dev>
;; SPDX-License-Identifier: GPL-3.0-or-later
@@ -38,8 +38,8 @@
(defcustom magit-branch-read-upstream-first t
"Whether to read upstream before name of new branch when creating a branch.
`nil' Read the branch name first.
`t' Read the upstream first.
nil Read the branch name first.
t Read the upstream first.
`fallback' Read the upstream first, but if it turns out that the chosen
value is not a valid upstream (because it cannot be resolved
as an existing revision), then treat it as the name of the
@@ -177,7 +177,7 @@ When t, then rename the branch named OLD on the remote specified
remote and unless NEW already exists on the remote.
When `forge-only' and the `forge' package is available, then
behave like `t' if the remote points to a repository on a forge
behave like t if the remote points to a repository on a forge
(currently Github or Gitlab), otherwise like `local-only'."
:package-version '(magit . "2.90.0")
:group 'magit-commands
@@ -208,18 +208,24 @@ has to be used to view and change branch related variables."
(transient-define-prefix magit-branch (branch)
"Add, configure or remove a branch."
:man-page "git-branch"
["Arguments"
(7 "-r" "Recurse submodules when checking out an existing branch"
"--recurse-submodules"
:if (lambda () (magit-git-version>= "2.13")))]
["Variables"
:if (lambda ()
(and magit-branch-direct-configure
(oref transient--prefix scope)))
[:if (lambda () (and magit-branch-direct-configure (transient-scope)))
:description
(lambda ()
(concat (propertize "Configure " 'face 'transient-heading)
(propertize (transient-scope) 'face 'magit-branch-local)))
("d" magit-branch.<branch>.description)
("u" magit-branch.<branch>.merge/remote)
("r" magit-branch.<branch>.rebase)
("p" magit-branch.<branch>.pushRemote)]
[:if-non-nil magit-branch-direct-configure
:description "Configure repository defaults"
("R" magit-pull.rebase)
("P" magit-remote.pushDefault)
("B" "Update default branch" magit-update-default-branch
:inapt-if-not magit-get-some-remote)]
["Arguments"
(7 "-r" "Recurse submodules when checking out an existing branch"
"--recurse-submodules")]
[["Checkout"
("b" "branch/revision" magit-checkout)
("l" "local branch" magit-branch-checkout)
@@ -349,10 +355,10 @@ when using `magit-branch-and-checkout'."
(interactive
(let* ((current (magit-get-current-branch))
(local (magit-list-local-branch-names))
(remote (--filter (and (string-match "[^/]+/" it)
(not (member (substring it (match-end 0))
(cons "HEAD" local))))
(magit-list-remote-branch-names)))
(remote (seq-filter (##and (string-match "[^/]+/" %)
(not (member (substring % (match-end 0))
(cons "HEAD" local))))
(magit-list-remote-branch-names)))
(choices (nconc (delete current local) remote))
(atpoint (magit-branch-at-point))
(choice (magit-completing-read
@@ -418,22 +424,21 @@ when using `magit-branch-and-checkout'."
(defun magit-branch-read-args (prompt &optional default-start)
(if magit-branch-read-upstream-first
(let ((choice (magit-read-starting-point prompt nil default-start)))
(if (magit-rev-verify choice)
(list (magit-read-string-ns
(if magit-completing-read--silent-default
(format "%s (starting at `%s')" prompt choice)
"Name for new branch")
(let ((def (mapconcat #'identity
(cdr (split-string choice "/"))
"/")))
(and (member choice (magit-list-remote-branch-names))
(not (member def (magit-list-local-branch-names)))
def)))
choice)
(if (eq magit-branch-read-upstream-first 'fallback)
(list choice
(magit-read-starting-point prompt choice default-start))
(user-error "Not a valid starting-point: %s" choice))))
(cond
((magit-rev-verify choice)
(list (magit-read-string-ns
(if magit-completing-read--silent-default
(format "%s (starting at `%s')" prompt choice)
"Name for new branch")
(let ((def (string-join (cdr (split-string choice "/")) "/")))
(and (member choice (magit-list-remote-branch-names))
(not (member def (magit-list-local-branch-names)))
def)))
choice))
((eq magit-branch-read-upstream-first 'fallback)
(list choice
(magit-read-starting-point prompt choice default-start)))
((user-error "Not a valid starting-point: %s" choice))))
(let ((branch (magit-read-string-ns (concat prompt " named"))))
(if (magit-branch-p branch)
(magit-branch-read-args
@@ -540,15 +545,12 @@ When resetting to another branch and a prefix argument is used,
then also set the target branch as the upstream of the branch
that is being reset."
(interactive
(let* ((atpoint (magit-local-branch-at-point))
(branch (magit-read-local-branch "Reset branch" atpoint))
(minibuffer-default-add-function (magit--minibuf-default-add-commit)))
(let ((branch (magit-read-local-branch "Reset branch"
(magit-local-branch-at-point))))
(list branch
(magit-completing-read (format "Reset %s to" branch)
(delete branch (magit-list-branch-names))
nil nil nil 'magit-revision-history
(or (and (not (equal branch atpoint)) atpoint)
(magit-get-upstream-branch branch)))
(magit-read-branch-or-commit (format "Reset %s to" branch)
(magit-get-upstream-branch branch)
branch)
current-prefix-arg)))
(let ((magit-inhibit-refresh t))
(if (equal branch (magit-get-current-branch))
@@ -594,22 +596,23 @@ prompt is confusing."
(setq branches
(list (magit-read-branch-prefer-other
(if force "Force delete branch" "Delete branch")))))
(unless force
(when-let ((unmerged (seq-remove #'magit-branch-merged-p branches)))
(if (magit-confirm 'delete-unmerged-branch
"Delete unmerged branch %s"
"Delete %d unmerged branches"
'noabort unmerged)
(setq force branches)
(or (setq branches
(cl-set-difference branches unmerged :test #'equal))
(user-error "Abort")))))
(when-let (((not force))
(unmerged (seq-remove #'magit-branch-merged-p branches)))
(if (magit-confirm 'delete-unmerged-branch
"Delete unmerged branch %s"
"Delete %d unmerged branches"
'noabort unmerged)
(setq force branches)
(or (setq branches
(cl-set-difference branches unmerged :test #'equal))
(user-error "Abort"))))
(list branches force)))
(let* ((refs (mapcar #'magit-ref-fullname branches))
(ambiguous (--remove it refs)))
(when ambiguous
(let ((refs (mapcar #'magit-ref-fullname branches)))
;; If a member of refs is nil, that means that
;; the respective branch name is ambiguous.
(when-let ((ambiguous (seq-filter #'null refs)))
(user-error
"%s ambiguous. Please cleanup using git directly."
"%s ambiguous; please cleanup using git directly"
(let ((len (length ambiguous)))
(cond
((= len 1)
@@ -624,12 +627,12 @@ prompt is confusing."
(offset (1+ (length remote))))
(cond
((magit-confirm 'delete-branch-on-remote
(format "Deleting local %s. Also delete on %s"
(magit-ref-fullname (car branches))
remote)
(format "Deleting %d local refs. Also delete on %s"
(length refs)
remote)
(list "Deleting local %s. Also delete on %s"
(magit-ref-fullname (car branches))
remote)
(list "Deleting %d local refs. Also delete on %s"
(length refs)
remote)
'noabort refs)
;; The ref may actually point at another rev on the remote,
;; but this is better than nothing.
@@ -641,7 +644,7 @@ prompt is confusing."
"push"
(and (or force magit-branch-delete-never-verify) "--no-verify")
remote
(--map (concat ":" (substring it offset)) branches))
(mapcar (##concat ":" (substring % offset)) branches))
;; If that is not the case, then this deletes the tracking branches.
(set-process-sentinel
magit-this-process
@@ -715,8 +718,8 @@ prompt is confusing."
(when (member refspec refspecs)
(if (and (length= refspecs 1)
(magit-confirm 'delete-pr-remote
(format "Also delete remote %s (%s)" remote
"no pull-request branch remains")
(list "Also delete remote %s (%s)" remote
"no pull-request branch remains")
nil t))
(magit-call-git "remote" "rm" remote)
(magit-call-git "config" "--unset-all" variable
@@ -728,11 +731,11 @@ prompt is confusing."
(defun magit-delete-remote-branch-sentinel (remote refs process event)
(when (memq (process-status process) '(exit signal))
(if (= (process-exit-status process) 1)
(if-let ((on-remote (--map (concat "refs/remotes/" remote "/" it)
(magit-remote-list-branches remote)))
(rest (--filter (and (not (member it on-remote))
(magit-ref-exists-p it))
refs)))
(if-let ((on-remote (mapcar (##concat "refs/remotes/" remote "/" %)
(magit-remote-list-branches remote)))
(rest (seq-filter (##and (not (member % on-remote))
(magit-ref-exists-p %))
refs)))
(progn
(process-put process 'inhibit-refresh t)
(magit-process-sentinel process event)
@@ -784,14 +787,16 @@ the remote."
(not (eq magit-branch-rename-push-target 'local-only))
(or (not (eq magit-branch-rename-push-target 'forge-only))
(and (require (quote forge) nil t)
(fboundp 'forge--forge-remote-p)
(forge--forge-remote-p remote))))
(fboundp 'forge--split-forge-url)
(and-let* ((url (magit-git-string
"remote" "get-url" remote)))
(forge--split-forge-url url)))))
(let ((old-target (magit-get-push-branch old t))
(new-target (magit-get-push-branch new t))
(remote (magit-get-push-remote new)))
(when (and old-target
(not new-target)
(magit-y-or-n-p (format "Also rename %S to %S on \"%s\""
(magit-y-or-n-p (format "Also rename %S to %S on \"%s\"?"
old new remote)))
;; Rename on (i.e., within) the remote, but only if the
;; destination ref doesn't exist yet. If that ref already
@@ -820,14 +825,14 @@ and also rename the respective reflog file."
;;;###autoload
(defun magit-branch-unshelve (branch)
"Unshelve a BRANCH
"Unshelve a BRANCH.
Rename \"refs/shelved/BRANCH\" to \"refs/heads/BRANCH\",
and also rename the respective reflog file."
(interactive
(list (magit-completing-read
"Unshelve branch"
(--map (substring it 8)
(magit-list-refnames "refs/shelved"))
(mapcar (##substring % 8)
(magit-list-refnames "refs/shelved"))
nil t)))
(let ((old (concat "refs/shelved/" branch))
(new (concat "refs/heads/" branch)))
@@ -851,17 +856,16 @@ and also rename the respective reflog file."
:man-page "git-branch"
[:description
(lambda ()
(concat
(propertize "Configure " 'face 'transient-heading)
(propertize (oref transient--prefix scope) 'face 'magit-branch-local)))
("d" magit-branch.<branch>.description)
("u" magit-branch.<branch>.merge/remote)
("r" magit-branch.<branch>.rebase)
("p" magit-branch.<branch>.pushRemote)]
(concat (propertize "Configure " 'face 'transient-heading)
(propertize (transient-scope) 'face 'magit-branch-local)))
("d" magit-branch.<branch>.description)
("u" magit-branch.<branch>.merge/remote)
("r" magit-branch.<branch>.rebase)
("p" magit-branch.<branch>.pushRemote)]
["Configure repository defaults"
("R" magit-pull.rebase)
("P" magit-remote.pushDefault)
("b" "Update default branch" magit-update-default-branch
("B" "Update default branch" magit-update-default-branch
:inapt-if-not magit-get-some-remote)]
["Configure branch creation"
("a m" magit-branch.autoSetupMerge)
@@ -896,7 +900,7 @@ and also rename the respective reflog file."
:class 'magit--git-branch:upstream)
(cl-defmethod transient-init-value ((obj magit--git-branch:upstream))
(when-let* ((branch (oref transient--prefix scope))
(when-let* ((branch (transient-scope))
(remote (magit-get "branch" branch "remote"))
(merge (magit-get "branch" branch "merge")))
(oset obj value (list remote merge))))
@@ -904,19 +908,19 @@ and also rename the respective reflog file."
(cl-defmethod transient-infix-read ((obj magit--git-branch:upstream))
(if (oref obj value)
(oset obj value nil)
(magit-read-upstream-branch (oref transient--prefix scope) "Upstream")))
(magit-read-upstream-branch (transient-scope) "Upstream")))
(cl-defmethod transient-infix-set ((obj magit--git-branch:upstream) refname)
(magit-set-upstream-branch (oref transient--prefix scope) refname)
(magit-set-upstream-branch (transient-scope) refname)
(oset obj value
(and-let* ((branch (oref transient--prefix scope))
(and-let* ((branch (transient-scope))
(r (magit-get "branch" branch "remote"))
(m (magit-get "branch" branch "merge")))
(list r m)))
(magit-refresh))
(cl-defmethod transient-format ((obj magit--git-branch:upstream))
(let ((branch (oref transient--prefix scope)))
(let ((branch (transient-scope)))
(format-spec
(oref obj format)
`((?k . ,(transient-format-key obj))

View File

@@ -1,9 +1,9 @@
;;; magit-bundle.el --- Bundle support for Magit -*- lexical-binding:t -*-
;; Copyright (C) 2008-2023 The Magit Project Contributors
;; Copyright (C) 2008-2025 The Magit Project Contributors
;; Author: Jonas Bernoulli <jonas@bernoul.li>
;; Maintainer: Jonas Bernoulli <jonas@bernoul.li>
;; Author: Jonas Bernoulli <emacs.magit@jonas.bernoulli.dev>
;; Maintainer: Jonas Bernoulli <emacs.magit@jonas.bernoulli.dev>
;; SPDX-License-Identifier: GPL-3.0-or-later
@@ -20,6 +20,13 @@
;; You should have received a copy of the GNU General Public License
;; along with Magit. If not, see <https://www.gnu.org/licenses/>.
;;; Commentary:
;; This library implements support for "git bundle".
;; The entry point is the `magit-bundle' menu command.
;; See (man "git-bundle").
;;; Code:
(require 'magit)

View File

@@ -1,9 +1,9 @@
;;; magit-clone.el --- Clone a repository -*- lexical-binding:t -*-
;; Copyright (C) 2008-2023 The Magit Project Contributors
;; Copyright (C) 2008-2025 The Magit Project Contributors
;; Author: Jonas Bernoulli <jonas@bernoul.li>
;; Maintainer: Jonas Bernoulli <jonas@bernoul.li>
;; Author: Jonas Bernoulli <emacs.magit@jonas.bernoulli.dev>
;; Maintainer: Jonas Bernoulli <emacs.magit@jonas.bernoulli.dev>
;; SPDX-License-Identifier: GPL-3.0-or-later
@@ -135,9 +135,7 @@ directory where the repository has been cloned."
["Setup arguments"
("-o" "Set name of remote" ("-o" "--origin="))
("-b" "Set HEAD branch" ("-b" "--branch="))
(magit-clone:--filter
:if (lambda () (magit-git-version>= "2.17.0"))
:level 7)
(magit-clone:--filter :level 7)
("-g" "Separate git directory" "--separate-git-dir="
transient-read-directory :level 7)
("-t" "Use template directory" "--template="
@@ -150,9 +148,7 @@ directory where the repository has been cloned."
("s" "shallow" magit-clone-shallow)
("d" "shallow since date" magit-clone-shallow-since :level 7)
("e" "shallow excluding" magit-clone-shallow-exclude :level 7)
(">" "sparse checkout" magit-clone-sparse
:if (lambda () (magit-git-version>= "2.25.0"))
:level 6)
(">" "sparse checkout" magit-clone-sparse :level 6)
("b" "bare" magit-clone-bare)
("m" "mirror" magit-clone-mirror)]
(interactive (list (or magit-clone-always-transient current-prefix-arg)))
@@ -278,9 +274,6 @@ Then show the status buffer for the new repository."
(unless magit-clone-set-remote-head
(magit-remote-unset-head remote))))
(when (and sparse checkout)
(when (magit-git-version< "2.25.0")
(user-error
"`git sparse-checkout' not available until Git v2.25"))
(let ((default-directory directory))
(magit-call-git "sparse-checkout" "init" "--cone")
(magit-call-git "checkout" (magit-get-current-branch))))
@@ -315,7 +308,7 @@ Then show the status buffer for the new repository."
(concat "file://"
(magit-convert-filename-for-git
(read-directory-name "Clone repository: file://"))))
(?b "or [b]undle"
(?b "[b]undle"
(magit-convert-filename-for-git
(read-file-name "Clone from bundle: ")))))

View File

@@ -1,9 +1,9 @@
;;; magit-commit.el --- Create Git commits -*- lexical-binding:t -*-
;; Copyright (C) 2008-2023 The Magit Project Contributors
;; Copyright (C) 2008-2025 The Magit Project Contributors
;; Author: Jonas Bernoulli <jonas@bernoul.li>
;; Maintainer: Jonas Bernoulli <jonas@bernoul.li>
;; Author: Jonas Bernoulli <emacs.magit@jonas.bernoulli.dev>
;; Maintainer: Jonas Bernoulli <emacs.magit@jonas.bernoulli.dev>
;; SPDX-License-Identifier: GPL-3.0-or-later
@@ -127,26 +127,31 @@ Also see https://github.com/magit/magit/issues/4132."
("-n" "Disable hooks" ("-n" "--no-verify"))
("-R" "Claim authorship and reset author date" "--reset-author")
(magit:--author :description "Override the author")
(7 "-D" "Override the author date" "--date=" transient-read-date)
("-s" "Add Signed-off-by line" ("-s" "--signoff"))
(5 magit:--gpg-sign)
(magit-commit:--date :level 7)
(magit:--gpg-sign :level 5)
(magit:--signoff)
(magit-commit:--reuse-message)]
[["Create"
("c" "Commit" magit-commit-create)]
["Edit HEAD"
("e" "Extend" magit-commit-extend)
("w" "Reword" magit-commit-reword)
""
("a" "Amend" magit-commit-amend)
(6 "n" "Reshelve" magit-commit-reshelve)]
""
("w" "Reword" magit-commit-reword)
("d" "Reshelve" magit-commit-reshelve :level 0)]
["Edit"
("f" "Fixup" magit-commit-fixup)
("s" "Squash" magit-commit-squash)
("A" "Augment" magit-commit-augment)
(6 "x" "Absorb changes" magit-commit-autofixup)
(6 "X" "Absorb modules" magit-commit-absorb-modules)]
[""
("A" "Alter" magit-commit-alter)
("n" "Augment" magit-commit-augment)
("W" "Revise" magit-commit-revise)]
["Edit and rebase"
("F" "Instant fixup" magit-commit-instant-fixup)
("S" "Instant squash" magit-commit-instant-squash)]]
("S" "Instant squash" magit-commit-instant-squash)]
["Spread across commits"
("x" "Modified files" magit-commit-autofixup :level 6)
("X" "Updated modules" magit-commit-absorb-modules :level 6)]]
(interactive)
(if-let ((buffer (magit-commit-message-buffer)))
(switch-to-buffer buffer)
@@ -155,6 +160,13 @@ Also see https://github.com/magit/magit/issues/4132."
(defun magit-commit-arguments nil
(transient-args 'magit-commit))
(transient-define-argument magit-commit:--date ()
:description "Override the author date"
:class 'transient-option
:shortarg "-D"
:argument "--date="
:reader #'transient-read-date)
(transient-define-argument magit-commit:--reuse-message ()
:description "Reuse commit message"
:class 'transient-option
@@ -171,15 +183,12 @@ Also see https://github.com/magit/magit/issues/4132."
"ORIG_HEAD"))))
;;; Commands
;;;; Create
;;;###autoload
(defun magit-commit-create (&optional args)
"Create a new commit on `HEAD'.
With a prefix argument, amend to the commit at `HEAD' instead.
\n(git commit [--amend] ARGS)"
(interactive (if current-prefix-arg
(list (cons "--amend" (magit-commit-arguments)))
(list (magit-commit-arguments))))
"Create a new commit."
(interactive (list (magit-commit-arguments)))
(cond ((member "--all" args)
(setq this-command 'magit-commit--all))
((member "--allow-empty" args)
@@ -188,22 +197,17 @@ With a prefix argument, amend to the commit at `HEAD' instead.
(let ((default-directory (magit-toplevel)))
(magit-run-git-with-editor "commit" args))))
;;;###autoload
(defun magit-commit-amend (&optional args)
"Amend the last commit.
\n(git commit --amend ARGS)"
(interactive (list (magit-commit-arguments)))
(magit-commit-amend-assert)
(magit-run-git-with-editor "commit" "--amend" args))
;;;; Edit HEAD
;;;###autoload
(defun magit-commit-extend (&optional args override-date)
"Amend the last commit, without editing the message.
"Amend staged changes to the last commit, without editing its message.
With a prefix argument keep the committer date, otherwise change
it. The option `magit-commit-extend-override-date' can be used
to inverse the meaning of the prefix argument. \n(git commit
--amend --no-edit)"
With a prefix argument do not update the committer date; without an
argument update it. The option `magit-commit-extend-override-date'
can be used to inverse the meaning of the prefix argument. Called
non-interactively, the optional OVERRIDE-DATE argument controls this
behavior, and the option is of no relevance."
(interactive (list (magit-commit-arguments)
(if current-prefix-arg
(not magit-commit-extend-override-date)
@@ -216,17 +220,22 @@ to inverse the meaning of the prefix argument. \n(git commit
(("GIT_COMMITTER_DATE" (magit-rev-format "%cD")))
(magit-run-git-with-editor "commit" "--amend" "--no-edit" args)))))
;;;###autoload
(defun magit-commit-amend (&optional args)
"Amend staged changes (if any) to the last commit, and edit its message."
(interactive (list (magit-commit-arguments)))
(magit-commit-amend-assert)
(magit-run-git-with-editor "commit" "--amend" args))
;;;###autoload
(defun magit-commit-reword (&optional args override-date)
"Reword the last commit, ignoring staged changes.
"Reword the message of the last commit, without amending its tree.
With a prefix argument keep the committer date, otherwise change
it. The option `magit-commit-reword-override-date' can be used
to inverse the meaning of the prefix argument.
Non-interactively respect the optional OVERRIDE-DATE argument
and ignore the option.
\n(git commit --amend --only)"
With a prefix argument do not update the committer date; without an
argument update it. The option `magit-commit-reword-override-date'
can be used to inverse the meaning of the prefix argument. Called
non-interactively, the optional OVERRIDE-DATE argument controls this
behavior, and the option is of no relevance."
(interactive (list (magit-commit-arguments)
(if current-prefix-arg
(not magit-commit-reword-override-date)
@@ -239,66 +248,139 @@ and ignore the option.
(("GIT_COMMITTER_DATE" (magit-rev-format "%cD")))
(magit-run-git-with-editor "commit" "--amend" "--only" args))))
;;;; Edit
;;;###autoload
(defun magit-commit-fixup (&optional commit args)
"Create a fixup commit.
"Create a fixup commit, leaving the original commit message untouched.
With a prefix argument the target COMMIT has to be confirmed.
Otherwise the commit at point may be used without confirmation
depending on the value of option `magit-commit-squash-confirm'."
If there is a reachable commit at point, target that. Otherwise prompt
for a commit. If `magit-commit-squash-confirm' is non-nil, always make
the user explicitly select a commit, in a buffer dedicated to that task.
During a later rebase, when this commit gets squashed into its targeted
commit, the original message of the targeted commit is used as-is.
In other words, call \"git commit --fixup=COMMIT --no-edit\"."
(interactive (list (magit-commit-at-point)
(magit-commit-arguments)))
(magit-commit-squash-internal "--fixup" commit args))
(magit-commit-squash-internal "--fixup=" commit args))
;;;###autoload
(defun magit-commit-squash (&optional commit args)
"Create a squash commit, without editing the squash message.
"Create a squash commit, without the user authoring a commit message.
With a prefix argument the target COMMIT has to be confirmed.
Otherwise the commit at point may be used without confirmation
depending on the value of option `magit-commit-squash-confirm'.
If there is a reachable commit at point, target that. Otherwise prompt
for a commit. If `magit-commit-squash-confirm' is non-nil, always make
the user explicitly select a commit, in a buffer dedicated to that task.
If you want to immediately add a message to the squash commit,
then use `magit-commit-augment' instead of this command."
During a later rebase, when this commit gets squashed into its targeted
commit, the user is given a chance to edit the original message to take
the changes from the squash commit into account.
In other words, call \"git commit --squash=COMMIT --no-edit\"."
(interactive (list (magit-commit-at-point)
(magit-commit-arguments)))
(magit-commit-squash-internal "--squash" commit args))
(magit-commit-squash-internal "--squash=" commit args))
;;;###autoload
(defun magit-commit-alter (&optional commit args)
"Create a squash commit, authoring the final commit message now.
If there is a reachable commit at point, target that. Otherwise prompt
for a commit. If `magit-commit-squash-confirm' is non-nil, always make
the user explicitly select a commit, in a buffer dedicated to that task.
During a later rebase, when this commit gets squashed into its targeted
commit, the original message of the targeted commit is replaced with the
message of this commit, without the user automatically being given a
chance to edit again.
In other words, call \"git commit --fixup=amend:COMMIT --edit\"."
(interactive (list (magit-commit-at-point)
(magit-commit-arguments)))
(magit-commit-squash-internal "--fixup=amend:" commit args nil 'edit))
;;;###autoload
(defun magit-commit-augment (&optional commit args)
"Create a squash commit, editing the squash message.
"Create a squash commit, authoring a new temporary commit message.
With a prefix argument the target COMMIT has to be confirmed.
Otherwise the commit at point may be used without confirmation
depending on the value of option `magit-commit-squash-confirm'."
If there is a reachable commit at point, target that. Otherwise prompt
for a commit. If `magit-commit-squash-confirm' is non-nil, always make
the user explicitly select a commit, in a buffer dedicated to that task.
During a later rebase, when this commit gets squashed into its targeted
commit, the user is asked to write a final commit message, in a buffer
that starts out containing both the original commit message, as well as
the temporary commit message of the squash commit.
In other words, call \"git commit --squash=COMMIT --edit\"."
(interactive (list (magit-commit-at-point)
(magit-commit-arguments)))
(magit-commit-squash-internal "--squash" commit args nil t))
(magit-commit-squash-internal "--squash=" commit args nil 'edit))
;;;###autoload
(defun magit-commit-revise (&optional commit args)
"Reword the message of an existing commit, without editing its tree.
If there is a reachable commit at point, target that. Otherwise prompt
for a commit. If `magit-commit-squash-confirm' is non-nil, always make
the user explicitly select a commit, in a buffer dedicated to that task.
During a later rebase, when this commit gets squashed into its targeted
commit, a combined commit is created which uses the message of the fixup
commit and the tree of the targeted commit.
In other words, call \"git commit --fixup=reword:COMMIT --edit\"."
(interactive (list (magit-commit-at-point)
(magit-commit-arguments)))
(magit-commit-squash-internal "--fixup=reword:" commit args 'nopatch 'edit))
;;;; Edit and Rebase
;;;###autoload
(defun magit-commit-instant-fixup (&optional commit args)
"Create a fixup commit targeting COMMIT and instantly rebase."
"Create a fixup commit, and immediately combine it with its target.
If there is a reachable commit at point, target that. Otherwise prompt
for a commit. If `magit-commit-squash-confirm' is non-nil, always make
the user explicitly select a commit, in a buffer dedicated to that task.
Leave the original commit message of the targeted commit untouched.
Like `magit-commit-fixup' but also run a `--autofixup' rebase."
(interactive (list (magit-commit-at-point)
(magit-commit-arguments)))
(magit-commit-squash-internal "--fixup" commit args t))
(magit-commit-squash-internal "--fixup=" commit args nil nil 'rebase))
;;;###autoload
(defun magit-commit-instant-squash (&optional commit args)
"Create a squash commit targeting COMMIT and instantly rebase."
"Create a squash commit, and immediately combine it with its target.
If there is a reachable commit at point, target that. Otherwise prompt
for a commit. If `magit-commit-squash-confirm' is non-nil, always make
the user explicitly select a commit, in a buffer dedicated to that task.
Turing the rebase phase, when the two commits are being squashed, ask
the user to author the final commit message, based on the original
message of the targeted commit.
Like `magit-commit-squash' but also run a `--autofixup' rebase."
(interactive (list (magit-commit-at-point)
(magit-commit-arguments)))
(magit-commit-squash-internal "--squash" commit args t))
(magit-commit-squash-internal "--squash=" commit args nil nil 'rebase))
;;;; Internal
(defun magit-commit-squash-internal
(option commit &optional args rebase edit confirmed)
(when-let ((args (magit-commit-assert args (not edit))))
(when commit
(when (and rebase (not (magit-rev-ancestor-p commit "HEAD")))
(magit-read-char-case
(format "%s isn't an ancestor of HEAD. " commit) nil
(?c "[c]reate without rebasing" (setq rebase nil))
(?s "[s]elect other" (setq commit nil))
(?a "[a]bort" (user-error "Quit")))))
(option commit &optional args nopatch edit rebase confirmed)
(when-let ((args (magit-commit-assert args nopatch (not edit))))
(when (and commit rebase (not (magit-rev-ancestor-p commit "HEAD")))
(magit-read-char-case
(format "%s isn't an ancestor of HEAD. " commit) nil
(?c "[c]reate without rebasing" (setq rebase nil))
(?s "[s]elect other" (setq commit nil))
(?a "[a]bort" (user-error "Quit"))))
(when commit
(setq commit (magit-rebase-interactive-assert commit t)))
(if (and commit
@@ -307,9 +389,8 @@ depending on the value of option `magit-commit-squash-confirm'."
current-prefix-arg
magit-commit-squash-confirm))))
(let ((magit-commit-show-diff nil))
(push (concat option "=" commit) args)
(unless edit
(push "--no-edit" args))
(push (concat option commit) args)
(push (if edit "--edit" "--no-edit") args)
(if rebase
(magit-with-editor
(magit-call-git
@@ -323,7 +404,7 @@ depending on the value of option `magit-commit-squash-confirm'."
(magit-log-select
(lambda (commit)
(when (and (magit-commit-squash-internal option commit args
rebase edit t)
nopatch edit rebase t)
rebase)
(magit-commit-amend-assert commit)
(magit-rebase-interactive-1 commit
@@ -334,7 +415,7 @@ depending on the value of option `magit-commit-squash-confirm'."
(format "Type %%p on a commit to %s into it,"
(substring option 2))
nil nil nil commit))
(when magit-commit-show-diff
(when (and magit-commit-show-diff (not nopatch))
(let ((magit-display-buffer-noselect t))
(apply #'magit-diff-staged nil (magit-diff-arguments)))))))
@@ -347,8 +428,9 @@ depending on the value of option `magit-commit-squash-confirm'."
(concat m1 "%d public branches" m2)
nil branches))))
(defun magit-commit-assert (args &optional strict)
(defun magit-commit-assert (args &optional nopatch strict)
(cond
(nopatch (or args (list "--")))
((or (magit-anything-staged-p)
(and (magit-anything-unstaged-p)
;; ^ Everything of nothing is still nothing.
@@ -380,7 +462,7 @@ depending on the value of option `magit-commit-squash-confirm'."
(user-error "Nothing staged (or unstaged)"))
(magit-commit-ask-to-stage
(when (eq magit-commit-ask-to-stage 'verbose)
(magit-diff-unstaged))
(apply #'magit-diff-unstaged (magit-diff-arguments)))
(prog1 (when (or (eq magit-commit-ask-to-stage 'stage)
(y-or-n-p
"Nothing staged. Commit all uncommitted changes? "))
@@ -392,11 +474,13 @@ depending on the value of option `magit-commit-squash-confirm'."
(t
(user-error "Nothing staged"))))
;;;; Reshelve
(defvar magit--reshelve-history nil)
;;;###autoload
(defun magit-commit-reshelve (date update-author &optional args)
"Change the committer date and possibly the author date of `HEAD'.
"Change committer (and possibly author) date of the last commit.
The current time is used as the initial minibuffer input and the
original author or committer date is available as the previous
@@ -426,6 +510,8 @@ is updated:
(and update-author (concat "--date=" date))
args)))
;;;; Spread
;;;###autoload
(defun magit-commit-absorb-modules (phase commit)
"Spread modified modules across recent commits."
@@ -457,9 +543,10 @@ With a prefix argument use a transient command to select infix
arguments. This command requires git-absorb executable, which
is available from https://github.com/tummychow/git-absorb.
See `magit-commit-autofixup' for an alternative implementation."
:value '("-v")
["Arguments"
("-f" "Skip safety checks" ("-f" "--force"))
("-v" "Display more output" ("-v" "--verbose"))]
("-f" "Skip safety checks" ("-f" "--force"))
("-v" "Increase verbosity" ("-v" "--verbose"))]
["Actions"
("x" "Absorb" magit-commit-absorb)]
(interactive (if current-prefix-arg
@@ -482,27 +569,31 @@ See `magit-commit-autofixup' for an alternative implementation."
(when commit
(setq commit (magit-rebase-interactive-assert commit t)))
(if (and commit (eq phase 'run))
(progn (magit-run-git-async "absorb" "-v" args "-b" commit) t)
(progn (magit-run-git-async "absorb" args "-b" commit) t)
(magit-log-select
(lambda (commit)
(with-no-warnings ; about non-interactive use
(magit-commit-absorb 'run commit args)))
nil nil nil nil commit))))
(transient-augment-suffix magit-commit-absorb :transient 'transient--do-exit)
;;;###autoload (autoload 'magit-commit-autofixup "magit-commit" nil t)
(transient-define-prefix magit-commit-autofixup (phase commit args)
"Spread staged or unstaged changes across recent commits.
If there are any staged then spread only those, otherwise
spread all unstaged changes. With a prefix argument use a
transient command to select infix arguments.
If there are any staged then spread only those, otherwise spread all
unstaged changes. With a prefix argument use a transient command to
select infix arguments.
This command requires the git-autofixup script, which is
available from https://github.com/torbiak/git-autofixup.
See `magit-commit-absorb' for an alternative implementation."
This command requires the git-autofixup script, which is available from
https://github.com/torbiak/git-autofixup. See `magit-commit-absorb' for
an alternative implementation."
:value '("-vv")
["Arguments"
(magit-autofixup:--context)
(magit-autofixup:--strict)]
(magit-autofixup:--strict)
("-v" "Increase verbosity" "-vv")]
["Actions"
("x" "Absorb" magit-commit-autofixup)]
(interactive (if current-prefix-arg
@@ -520,13 +611,15 @@ See `magit-commit-absorb' for an alternative implementation."
(when commit
(setq commit (magit-rebase-interactive-assert commit t)))
(if (and commit (eq phase 'run))
(progn (magit-run-git-async "autofixup" "-vv" args commit) t)
(progn (magit-run-git-async "autofixup" args commit) t)
(magit-log-select
(lambda (commit)
(with-no-warnings ; about non-interactive use
(magit-commit-autofixup 'run commit args)))
nil nil nil nil commit))))
(transient-augment-suffix magit-commit-autofixup :transient 'transient--do-exit)
(transient-define-argument magit-autofixup:--context ()
:description "Diff context lines"
:class 'transient-option
@@ -541,12 +634,14 @@ See `magit-commit-absorb' for an alternative implementation."
:argument "--strict="
:reader #'transient-read-number-N0)
;;;; Hooks
(defvar magit-post-commit-hook-commands
'(magit-commit-extend
magit-commit-fixup
magit-commit-augment
magit-commit-instant-fixup
magit-commit-instant-squash))
(list #'magit-commit-extend
#'magit-commit-fixup
#'magit-commit-augment
#'magit-commit-instant-fixup
#'magit-commit-instant-squash))
(defun magit-run-post-commit-hook ()
(when (and (not this-command)
@@ -648,10 +743,10 @@ See `magit-commit-absorb' for an alternative implementation."
(defun magit-commit-message-buffer ()
(let* ((find-file-visit-truename t) ; git uses truename of COMMIT_EDITMSG
(topdir (magit-toplevel)))
(--first (equal topdir (with-current-buffer it
(and git-commit-mode (magit-toplevel))))
(append (buffer-list (selected-frame))
(buffer-list)))))
(seq-find (##equal topdir (with-current-buffer %
(and git-commit-mode (magit-toplevel))))
(append (buffer-list (selected-frame))
(buffer-list)))))
(defvar magit-commit-add-log-insert-function #'magit-commit-add-log-insert
"Used by `magit-commit-add-log' to insert a single entry.")

View File

@@ -1,9 +1,9 @@
;;; magit-core.el --- Core functionality -*- lexical-binding:t -*-
;; Copyright (C) 2008-2023 The Magit Project Contributors
;; Copyright (C) 2008-2025 The Magit Project Contributors
;; Author: Jonas Bernoulli <jonas@bernoul.li>
;; Maintainer: Jonas Bernoulli <jonas@bernoul.li>
;; Author: Jonas Bernoulli <emacs.magit@jonas.bernoulli.dev>
;; Maintainer: Jonas Bernoulli <emacs.magit@jonas.bernoulli.dev>
;; SPDX-License-Identifier: GPL-3.0-or-later
@@ -37,15 +37,6 @@
(require 'magit-transient)
(require 'magit-autorevert)
(when (and (not magit-inhibit-libgit)
(magit--libgit-available-p))
(condition-case err
(require 'magit-libgit)
(error
(setq magit-inhibit-libgit 'error)
(message "Error while loading `magit-libgit': %S" err)
(message "That is not fatal. The `libegit2' module just won't be used."))))
;;; Options
(defgroup magit nil

File diff suppressed because it is too large Load Diff

View File

@@ -1,9 +1,9 @@
;;; magit-ediff.el --- Ediff extension for Magit -*- lexical-binding:t -*-
;; Copyright (C) 2008-2023 The Magit Project Contributors
;; Copyright (C) 2008-2025 The Magit Project Contributors
;; Author: Jonas Bernoulli <jonas@bernoul.li>
;; Maintainer: Jonas Bernoulli <jonas@bernoul.li>
;; Author: Jonas Bernoulli <emacs.magit@jonas.bernoulli.dev>
;; Maintainer: Jonas Bernoulli <emacs.magit@jonas.bernoulli.dev>
;; SPDX-License-Identifier: GPL-3.0-or-later
@@ -42,8 +42,8 @@
:group 'magit-extensions)
(defcustom magit-ediff-quit-hook
'(magit-ediff-cleanup-auxiliary-buffers
magit-ediff-restore-previous-winconf)
(list #'magit-ediff-cleanup-auxiliary-buffers
#'magit-ediff-restore-previous-winconf)
"Hooks to run after finishing Ediff, when that was invoked using Magit.
The hooks are run in the Ediff control buffer. This is similar
to `ediff-quit-hook' but takes the needs of Magit into account.
@@ -53,8 +53,8 @@ invoked using Magit."
:group 'magit-ediff
:type 'hook
:get #'magit-hook-custom-get
:options '(magit-ediff-cleanup-auxiliary-buffers
magit-ediff-restore-previous-winconf))
:options (list #'magit-ediff-cleanup-auxiliary-buffers
#'magit-ediff-restore-previous-winconf))
(defcustom magit-ediff-dwim-resolve-function #'magit-ediff-resolve-rest
"The function `magit-ediff-dwim' uses to resolve conflicts."

View File

@@ -1,9 +1,9 @@
;;; magit-extras.el --- Additional functionality for Magit -*- lexical-binding:t -*-
;; Copyright (C) 2008-2023 The Magit Project Contributors
;; Copyright (C) 2008-2025 The Magit Project Contributors
;; Author: Jonas Bernoulli <jonas@bernoul.li>
;; Maintainer: Jonas Bernoulli <jonas@bernoul.li>
;; Author: Jonas Bernoulli <emacs.magit@jonas.bernoulli.dev>
;; Maintainer: Jonas Bernoulli <emacs.magit@jonas.bernoulli.dev>
;; SPDX-License-Identifier: GPL-3.0-or-later
@@ -64,7 +64,7 @@ alternative commands."
["Actions"
(" m" "Invoke mergetool" magit-git-mergetool)]
(interactive
(if (and (not (eq transient-current-prefix 'magit-git-mergetool))
(if (and (not (eq transient-current-command 'magit-git-mergetool))
current-prefix-arg)
(list nil nil t)
(list (magit-read-unmerged-file "Resolve")
@@ -197,27 +197,13 @@ blame to center around the line point is on."
(defun ido-enter-magit-status ()
"Drop into `magit-status' from file switching.
This command does not work in Emacs 26.1.
See https://github.com/magit/magit/issues/3634
and https://debbugs.gnu.org/cgi/bugreport.cgi?bug=31707.
To make this command available use something like:
(add-hook \\='ido-setup-hook
(lambda ()
(keymap-set ido-completion-map
\"C-x g\" \\='ido-enter-magit-status)))
Starting with Emacs 25.1 the Ido keymaps are defined just once
instead of every time Ido is invoked, so now you can modify it
like pretty much every other keymap:
(keymap-set ido-common-completion-map
\"C-x g\" \\='ido-enter-magit-status)"
(interactive)
(setq ido-exit 'fallback)
(setq ido-fallback #'magit-status) ; for Emacs >= 26.2
(with-no-warnings (setq fallback #'magit-status)) ; for Emacs 25
(setq ido-fallback #'magit-status)
(exit-minibuffer))
;;;###autoload
@@ -235,10 +221,10 @@ well. If you want to use another key, then you must set this
to nil before loading Magit to prevent \"m\" from being bound.")
(with-eval-after-load 'project
;; Only more recent versions of project.el have `project-prefix-map' and
;; `project-switch-commands', though project.el is available in Emacs 25.
(when (and magit-bind-magit-project-status
;; Added in Emacs 28.1.
(boundp 'project-prefix-map)
(boundp 'project-switch-commands)
;; Only modify if it hasn't already been modified.
(equal project-switch-commands
(eval (car (get 'project-switch-commands 'standard-value))
@@ -287,10 +273,7 @@ for a repository."
(interactive (list (or (magit-toplevel)
(magit-read-repository t))
current-prefix-arg))
;; Note: The ERROR argument of `dired-get-marked-files' isn't
;; available until Emacs 27.
(let ((files (or (dired-get-marked-files nil arg)
(user-error "No files specified"))))
(let ((files (dired-get-marked-files nil arg nil nil t)))
(magit-status-setup-buffer repo)
(magit-am-apply-patches files)))
@@ -392,32 +375,28 @@ in HEAD as well as staged changes in the diff to check."
(require 'diff-mode) ; `diff-add-log-current-defuns'.
(require 'vc-git) ; `vc-git-diff'.
(require 'add-log) ; `change-log-insert-entries'.
(cond
((and (fboundp 'change-log-insert-entries)
(fboundp 'diff-add-log-current-defuns))
(setq default-directory
(if (and (file-regular-p "gitdir")
(not (magit-git-true "rev-parse" "--is-inside-work-tree"))
(magit-git-true "rev-parse" "--is-inside-git-dir"))
(file-name-directory (magit-file-line "gitdir"))
(magit-toplevel)))
(let ((rev1 (if amending "HEAD^1" "HEAD"))
(rev2 nil))
;; Magit may have updated the files without notifying vc, but
;; `diff-add-log-current-defuns' relies on vc being up-to-date.
(mapc #'vc-file-clearprops (magit-staged-files))
(change-log-insert-entries
(with-temp-buffer
(vc-git-command (current-buffer) 1 nil
"diff-index" "--exit-code" "--patch"
(and (magit-anything-staged-p) "--cached")
rev1 "--")
;; `diff-find-source-location' consults these vars.
(defvar diff-vc-revisions)
(setq-local diff-vc-revisions (list rev1 rev2))
(setq-local diff-vc-backend 'Git)
(diff-add-log-current-defuns)))))
(t (user-error "`magit-generate-changelog' requires Emacs 27 or greater"))))
(setq default-directory
(if (and (file-regular-p "gitdir")
(not (magit-git-true "rev-parse" "--is-inside-work-tree"))
(magit-git-true "rev-parse" "--is-inside-git-dir"))
(file-name-directory (magit-file-line "gitdir"))
(magit-toplevel)))
(let ((rev1 (if amending "HEAD^1" "HEAD"))
(rev2 nil))
;; Magit may have updated the files without notifying vc, but
;; `diff-add-log-current-defuns' relies on vc being up-to-date.
(mapc #'vc-file-clearprops (magit-staged-files))
(change-log-insert-entries
(with-temp-buffer
(vc-git-command (current-buffer) 1 nil
"diff-index" "--exit-code" "--patch"
(and (magit-anything-staged-p) "--cached")
rev1 "--")
;; `diff-find-source-location' consults these vars.
(defvar diff-vc-revisions)
(setq-local diff-vc-revisions (list rev1 rev2))
(setq-local diff-vc-backend 'Git)
(diff-add-log-current-defuns)))))
;;;###autoload
(defun magit-add-change-log-entry (&optional whoami file-name other-window)
@@ -550,7 +529,8 @@ list returned by `magit-rebase-arguments'."
((not rev)
(when (and (magit-ref-p backup)
(not (magit-y-or-n-p
(format "Backup ref %s already exists. Override? " backup))))
(format "Backup ref %s already exists. Override? "
backup))))
(user-error "Abort"))
(magit-log-select
(lambda (rev)
@@ -744,8 +724,6 @@ the minibuffer too."
(delete-char -1)))))
(user-error "Revision stack is empty")))
(keymap-set git-commit-mode-map "C-c C-w" #'magit-pop-revision-stack)
;;;###autoload
(defun magit-copy-section-value (arg)
"Save the value of the current section for later use.
@@ -914,6 +892,20 @@ patch application, a cherry-pick, a revert, or a bisect."
((magit-sequencer-in-progress-p) (magit-sequencer-abort))
((magit-bisect-in-progress-p) (magit-bisect-reset))))
;;;###autoload
(defun magit-back-to-indentation ()
"Move point to the first non-whitespace character on this line.
In Magit diffs, also skip over - and + at the beginning of the line."
(interactive "^")
(beginning-of-line 1)
(when (and (magit-section-match 'hunk)
(looking-at (if (oref (magit-current-section) combined)
"^ ?[-+]+"
"^[-+]")))
(goto-char (match-end 0)))
(skip-syntax-forward " " (line-end-position))
(backward-prefix-chars))
;;; _
(provide 'magit-extras)
;;; magit-extras.el ends here

View File

@@ -1,9 +1,9 @@
;;; magit-fetch.el --- Download objects and refs -*- lexical-binding:t -*-
;; Copyright (C) 2008-2023 The Magit Project Contributors
;; Copyright (C) 2008-2025 The Magit Project Contributors
;; Author: Jonas Bernoulli <jonas@bernoul.li>
;; Maintainer: Jonas Bernoulli <jonas@bernoul.li>
;; Author: Jonas Bernoulli <emacs.magit@jonas.bernoulli.dev>
;; Maintainer: Jonas Bernoulli <emacs.magit@jonas.bernoulli.dev>
;; SPDX-License-Identifier: GPL-3.0-or-later
@@ -37,7 +37,8 @@
["Arguments"
("-p" "Prune deleted branches" ("-p" "--prune"))
("-t" "Fetch all tags" ("-t" "--tags"))
(7 "-u" "Fetch full history" "--unshallow")]
("-u" "Fetch full history" "--unshallow" :level 7)
("-F" "Force" ("-f" "--force"))]
["Fetch from"
("p" magit-fetch-from-pushremote)
("u" magit-fetch-from-upstream)
@@ -177,11 +178,6 @@ with a prefix argument."
(list nil (transient-args 'magit-fetch-modules))))
(if transient
(transient-setup 'magit-fetch-modules)
(when (magit-git-version< "2.8.0")
(when-let ((value (transient-arg-value "--jobs=" args)))
(message "Dropping --jobs; not supported by Git v%s"
(magit-git-version))
(setq args (remove (format "--jobs=%s" value) args))))
(magit-with-toplevel
(magit-run-git-async "fetch" "--recurse-submodules" args))))

View File

@@ -1,9 +1,9 @@
;;; magit-files.el --- Finding files -*- lexical-binding:t -*-
;; Copyright (C) 2008-2023 The Magit Project Contributors
;; Copyright (C) 2008-2025 The Magit Project Contributors
;; Author: Jonas Bernoulli <jonas@bernoul.li>
;; Maintainer: Jonas Bernoulli <jonas@bernoul.li>
;; Author: Jonas Bernoulli <emacs.magit@jonas.bernoulli.dev>
;; Maintainer: Jonas Bernoulli <emacs.magit@jonas.bernoulli.dev>
;; SPDX-License-Identifier: GPL-3.0-or-later
@@ -177,21 +177,22 @@ then only after asking. A non-nil value for REVERT is ignored if REV is
(setq buffer-file-coding-system last-coding-system-used))
(let ((buffer-file-name magit-buffer-file-name)
(after-change-major-mode-hook
(remq 'global-diff-hl-mode-enable-in-buffers
after-change-major-mode-hook)))
(seq-difference after-change-major-mode-hook
'(global-diff-hl-mode-enable-in-buffer ; Emacs >= 30
global-diff-hl-mode-enable-in-buffers ; Emacs < 30
eglot--maybe-activate-editing-mode)
#'eq)))
(normal-mode t))
(setq buffer-read-only t)
(set-buffer-modified-p nil)
(goto-char (point-min))))
(defun magit--lsp--disable-when-visiting-blob (fn &rest args)
(define-advice lsp (:around (fn &rest args) magit-find-file)
"Do nothing when visiting blob using `magit-find-file' and similar.
See also https://github.com/doomemacs/doomemacs/pull/6309."
(unless magit-buffer-revision
(apply fn args)))
(advice-add 'lsp :around #'magit--lsp--disable-when-visiting-blob)
;;; Find Index
(defvar magit-find-index-hook nil)
@@ -209,7 +210,7 @@ is done using `magit-find-index-noselect'."
(let ((file (magit-file-relative-name)))
(unless (equal magit-buffer-refname "{index}")
(user-error "%s isn't visiting the index" file))
(if (y-or-n-p (format "Update index with contents of %s" (buffer-name)))
(if (y-or-n-p (format "Update index with contents of %s?" (buffer-name)))
(let ((index (make-temp-name
(expand-file-name "magit-update-index-" (magit-gitdir))))
(buffer (current-buffer)))
@@ -491,7 +492,7 @@ Git, then fallback to using `delete-file'."
(interactive
(let ((rev (magit-read-branch-or-commit
"Checkout from revision" magit-buffer-revision)))
(list rev (magit-read-file-from-rev rev "Checkout file"))))
(list rev (magit-read-file-from-rev rev "Checkout file" nil t))))
(magit-with-toplevel
(magit-run-git "checkout" rev "--" file)))
@@ -499,8 +500,11 @@ Git, then fallback to using `delete-file'."
(defvar magit-read-file-hist nil)
(defun magit-read-file-from-rev (rev prompt &optional default)
(defun magit-read-file-from-rev (rev prompt &optional default include-dirs)
(let ((files (magit-revision-files rev)))
(when include-dirs
(setq files (sort (nconc files (magit-revision-directories rev))
#'string<)))
(magit-completing-read
prompt files nil t nil 'magit-read-file-hist
(car (member (or default (magit-current-file)) files)))))

File diff suppressed because it is too large Load Diff

View File

@@ -1,9 +1,9 @@
;;; magit-gitignore.el --- Intentionally untracked files -*- lexical-binding:t -*-
;; Copyright (C) 2008-2023 The Magit Project Contributors
;; Copyright (C) 2008-2025 The Magit Project Contributors
;; Author: Jonas Bernoulli <jonas@bernoul.li>
;; Maintainer: Jonas Bernoulli <jonas@bernoul.li>
;; Author: Jonas Bernoulli <emacs.magit@jonas.bernoulli.dev>
;; Maintainer: Jonas Bernoulli <emacs.magit@jonas.bernoulli.dev>
;; SPDX-License-Identifier: GPL-3.0-or-later
@@ -116,11 +116,12 @@ Rules that are defined in that file affect all local repositories."
(base (and base (file-directory-p base) base))
(choices
(delete-dups
(--mapcat
(cons (concat "/" it)
(and-let* ((ext (file-name-extension it)))
(list (concat "/" (file-name-directory it) "*." ext)
(concat "*." ext))))
(mapcan
(lambda (file)
(cons (concat "/" file)
(and-let* ((ext (file-name-extension file)))
(list (concat "/" (file-name-directory file) "*." ext)
(concat "*." ext)))))
(sort (nconc
(magit-untracked-files nil base)
;; The untracked section of the status buffer lists

View File

@@ -1,9 +1,9 @@
;;; magit-log.el --- Inspect Git history -*- lexical-binding:t; coding:utf-8 -*-
;; Copyright (C) 2008-2023 The Magit Project Contributors
;; Copyright (C) 2008-2025 The Magit Project Contributors
;; Author: Jonas Bernoulli <jonas@bernoul.li>
;; Maintainer: Jonas Bernoulli <jonas@bernoul.li>
;; Author: Jonas Bernoulli <emacs.magit@jonas.bernoulli.dev>
;; Maintainer: Jonas Bernoulli <emacs.magit@jonas.bernoulli.dev>
;; SPDX-License-Identifier: GPL-3.0-or-later
@@ -38,7 +38,7 @@
(declare-function magit-insert-upstream-branch-header "magit-status"
(&optional branch pull keyword))
(declare-function magit-read-file-from-rev "magit-files"
(rev prompt &optional default))
(rev prompt &optional default include-dirs))
(declare-function magit-rebase--get-state-lines "magit-sequence"
(file))
(declare-function magit-show-commit "magit-diff"
@@ -55,6 +55,14 @@
(require 'crm)
(require 'which-func)
(make-obsolete-variable 'magit-log-highlight-keywords
'magit-log-wash-summary-hook
"Magit 4.3.0")
(make-obsolete-variable 'magit-log-format-message-function
'magit-log-wash-summary-hook
"Magit 4.3.0")
;;; Options
;;;; Log Mode
@@ -143,18 +151,30 @@ This is useful if you use really long branch names."
:group 'magit-log
:type 'boolean)
(defcustom magit-log-highlight-keywords t
"Whether to highlight bracketed keywords in commit summaries."
:package-version '(magit . "2.12.0")
(defcustom magit-log-wash-summary-hook
(list #'magit-highlight-squash-markers
#'magit-highlight-bracket-keywords)
"Functions used to highlight parts of each individual commit summary.
These functions are called in order, in a buffer that containing the
first line of the commit message. They should set text properties as
they see fit, usually just `font-lock-face'. Before each function is
called, point is at the beginning of the buffer.
See also the related `magit-revision-wash-message-hook'. You likely
want to use the same functions for both hooks."
:package-version '(magit . "4.3.0")
:group 'magit-log
:type 'boolean)
:type 'hook
:options (list #'magit-highlight-squash-markers
#'magit-highlight-bracket-keywords))
(defcustom magit-log-header-line-function #'magit-log-header-line-sentence
"Function used to generate text shown in header line of log buffers."
:package-version '(magit . "2.12.0")
:group 'magit-log
:type '(choice (function-item magit-log-header-line-arguments)
(function-item magit-log-header-line-sentence)
:type `(choice (function-item ,#'magit-log-header-line-arguments)
(function-item ,#'magit-log-header-line-sentence)
function))
(defcustom magit-log-trace-definition-function #'magit-which-function
@@ -164,9 +184,9 @@ You should prefer `magit-which-function' over `which-function'
because the latter may make use of Imenu's outdated cache."
:package-version '(magit . "3.0.0")
:group 'magit-log
:type '(choice (function-item magit-which-function)
(function-item which-function)
(function-item add-log-current-defun)
:type `(choice (function-item ,#'magit-which-function)
(function-item ,#'which-function)
(function-item ,#'add-log-current-defun)
function))
(defcustom magit-log-color-graph-limit 256
@@ -280,8 +300,8 @@ AUTHOR-WIDTH has to be an integer. When the name of the author
;;;; Cherry Mode
(defcustom magit-cherry-sections-hook
'(magit-insert-cherry-headers
magit-insert-cherry-commits)
(list #'magit-insert-cherry-headers
#'magit-insert-cherry-commits)
"Hook run to insert sections into the cherry buffer."
:package-version '(magit . "2.1.0")
:group 'magit-log
@@ -353,15 +373,15 @@ commits before and half after."
(pcase-let ((`(,args ,files)
(magit-log--get-value 'magit-log-mode
magit-prefix-use-buffer-arguments)))
(unless (eq transient-current-command 'magit-dispatch)
(when-let ((file (magit-file-relative-name)))
(setq files (list file))))
(oset obj value (if files `(("--" ,@files) ,args) args))))
(when-let (((not (eq transient-current-command 'magit-dispatch)))
(file (magit-file-relative-name)))
(setq files (list file)))
(oset obj value (if files `(("--" ,@files) ,@args) args))))
(cl-defmethod transient-init-value ((obj magit-log-refresh-prefix))
(oset obj value (if magit-buffer-log-files
`(("--" ,@magit-buffer-log-files)
,magit-buffer-log-args)
,@magit-buffer-log-args)
magit-buffer-log-args)))
(cl-defmethod transient-set-value ((obj magit-log-prefix))
@@ -387,13 +407,13 @@ commits before and half after."
(eq major-mode mode))
(setq args magit-buffer-log-args)
(setq files magit-buffer-log-files))
((and (memq use-buffer-args '(always selected))
(when-let ((buffer (magit-get-mode-buffer
mode nil
(eq use-buffer-args 'selected))))
(setq args (buffer-local-value 'magit-buffer-log-args buffer))
(setq files (buffer-local-value 'magit-buffer-log-files buffer))
t)))
((when-let (((memq use-buffer-args '(always selected)))
(buffer (magit-get-mode-buffer
mode nil
(eq use-buffer-args 'selected))))
(setq args (buffer-local-value 'magit-buffer-log-args buffer))
(setq files (buffer-local-value 'magit-buffer-log-files buffer))
t))
((plist-member (symbol-plist mode) 'magit-log-current-arguments)
(setq args (get mode 'magit-log-current-arguments)))
((when-let ((elt (assq (intern (format "magit-log:%s" mode))
@@ -422,51 +442,57 @@ commits before and half after."
;;; Commands
;;;; Prefix Commands
(eval-and-compile
(defvar magit-log-infix-arguments
;; The grouping in git-log(1) appears to be guided by implementation
;; details, so our logical grouping only follows it to an extend.
;; Arguments that are "misplaced" here:
;; 1. From "Commit Formatting".
;; 2. From "Common Diff Options".
;; 3. From unnamed first group.
;; 4. Implemented by Magit.
[:class transient-subgroups
["Commit limiting"
(magit-log:-n)
(magit:--author)
(7 magit-log:--since)
(7 magit-log:--until)
(magit-log:--grep)
(7 "-i" "Search case-insensitive" ("-i" "--regexp-ignore-case"))
(7 "-I" "Invert search pattern" "--invert-grep")
(magit-log:-G) ;2
(magit-log:-S) ;2
(magit-log:-L) ;2
(7 "=m" "Omit merges" "--no-merges")
(7 "=p" "First parent" "--first-parent")]
["History simplification"
( "-D" "Simplify by decoration" "--simplify-by-decoration")
(magit:--)
( "-f" "Follow renames when showing single-file log" "--follow") ;3
(6 "/s" "Only commits changing given paths" "--sparse")
(7 "/d" "Only selected commits plus meaningful history" "--dense")
(7 "/a" "Only commits existing directly on ancestry path" "--ancestry-path")
(6 "/f" "Do not prune history" "--full-history")
(7 "/m" "Prune some history" "--simplify-merges")]
["Commit ordering"
(magit-log:--*-order)
("-r" "Reverse order" "--reverse")]
["Formatting"
("-g" "Show graph" "--graph") ;1
("-c" "Show graph in color" "--color") ;2
("-d" "Show refnames" "--decorate") ;3
("=S" "Show signatures" "--show-signature") ;1
("-h" "Show header" "++header") ;4
("-p" "Show diffs" ("-p" "--patch")) ;2
("-s" "Show diffstats" "--stat")] ;2
]))
;;;###autoload (autoload 'magit-log "magit-log" nil t)
(transient-define-prefix magit-log ()
"Show a commit or reference log."
:man-page "git-log"
:class 'magit-log-prefix
;; The grouping in git-log(1) appears to be guided by implementation
;; details, so our logical grouping only follows it to an extend.
;; Arguments that are "misplaced" here:
;; 1. From "Commit Formatting".
;; 2. From "Common Diff Options".
;; 3. From unnamed first group.
;; 4. Implemented by Magit.
["Commit limiting"
(magit-log:-n)
(magit:--author)
(7 magit-log:--since)
(7 magit-log:--until)
(magit-log:--grep)
(7 "-i" "Search case-insensitive" ("-i" "--regexp-ignore-case"))
(7 "-I" "Invert search pattern" "--invert-grep")
(magit-log:-G) ;2
(magit-log:-S) ;2
(magit-log:-L) ;2
(7 "=m" "Omit merges" "--no-merges")
(7 "=p" "First parent" "--first-parent")]
["History simplification"
( "-D" "Simplify by decoration" "--simplify-by-decoration")
(magit:--)
( "-f" "Follow renames when showing single-file log" "--follow") ;3
(6 "/s" "Only commits changing given paths" "--sparse")
(7 "/d" "Only selected commits plus meaningful history" "--dense")
(7 "/a" "Only commits existing directly on ancestry path" "--ancestry-path")
(6 "/f" "Do not prune history" "--full-history")
(7 "/m" "Prune some history" "--simplify-merges")]
["Commit ordering"
(magit-log:--*-order)
("-r" "Reverse order" "--reverse")]
["Formatting"
("-g" "Show graph" "--graph") ;1
("-c" "Show graph in color" "--color") ;2
("-d" "Show refnames" "--decorate") ;3
("=S" "Show signatures" "--show-signature") ;1
("-h" "Show header" "++header") ;4
("-p" "Show diffs" ("-p" "--patch")) ;2
("-s" "Show diffstats" "--stat")] ;2
[magit-log-infix-arguments]
[["Log"
("l" "current" magit-log-current)
("h" "HEAD" magit-log-head)
@@ -498,36 +524,7 @@ commits before and half after."
:man-page "git-log"
:class 'magit-log-refresh-prefix
[:if-mode magit-log-mode
:class transient-subgroups
["Commit limiting"
(magit-log:-n)
(magit:--author)
(magit-log:--grep)
(7 "-i" "Search case-insensitive" ("-i" "--regexp-ignore-case"))
(7 "-I" "Invert search pattern" "--invert-grep")
(magit-log:-G)
(magit-log:-S)
(magit-log:-L)]
["History simplification"
( "-D" "Simplify by decoration" "--simplify-by-decoration")
(magit:--)
( "-f" "Follow renames when showing single-file log" "--follow") ;3
(6 "/s" "Only commits changing given paths" "--sparse")
(7 "/d" "Only selected commits plus meaningful history" "--dense")
(7 "/a" "Only commits existing directly on ancestry path" "--ancestry-path")
(6 "/f" "Do not prune history" "--full-history")
(7 "/m" "Prune some history" "--simplify-merges")]
["Commit ordering"
(magit-log:--*-order)
("-r" "Reverse order" "--reverse")]
["Formatting"
("-g" "Show graph" "--graph")
("-c" "Show graph in color" "--color")
("-d" "Show refnames" "--decorate")
("=S" "Show signatures" "--show-signature")
("-h" "Show header" "++header")
("-p" "Show diffs" ("-p" "--patch"))
("-s" "Show diffstats" "--stat")]]
magit-log-infix-arguments]
[:if-not-mode magit-log-mode
:description "Arguments"
(magit-log:-n)
@@ -540,10 +537,10 @@ commits before and half after."
("s" "buffer and set defaults" transient-set-and-exit)
("w" "buffer and save defaults" transient-save-and-exit)]
["Margin"
("L" "toggle visibility" magit-toggle-margin :transient t)
("l" "cycle style" magit-cycle-margin-style :transient t)
("d" "toggle details" magit-toggle-margin-details :transient t)
("x" "toggle shortstat" magit-toggle-log-margin-style :transient t)]
(magit-toggle-margin)
(magit-cycle-margin-style)
(magit-toggle-margin-details)
(magit-toggle-log-margin-style)]
[:if-mode magit-log-mode
:description "Toggle"
("b" "buffer lock" magit-toggle-buffer-lock)]]
@@ -685,16 +682,16 @@ previously checked out branch and its upstream and push-target."
(setq rebase (magit-ref-abbrev rebase))
(setq current rebase)
(setq head "HEAD"))
(t (setq current (magit-get-previous-branch)))))
((setq current (magit-get-previous-branch)))))
(cond (current
(setq current
(magit--propertize-face current'magit-branch-local))
(magit--propertize-face current 'magit-branch-local))
(setq target (magit-get-push-branch current t))
(setq upstream (magit-get-upstream-branch current))
(when upstream
(setq upup (and (magit-local-branch-p upstream)
(magit-get-upstream-branch upstream)))))
(t (setq head "HEAD")))
((setq head "HEAD")))
(delq nil (list current head target upstream upup)))
(magit-log-arguments)))
(magit-log-setup-buffer revs args files))
@@ -759,20 +756,7 @@ completion candidates."
With a prefix argument or when `--follow' is an active log
argument, then follow renames. When the region is active,
restrict the log to the lines that the region touches."
(interactive
(cons current-prefix-arg
(and (region-active-p)
(magit-file-relative-name)
(save-restriction
(widen)
(list (line-number-at-pos (region-beginning))
(line-number-at-pos
(let ((end (region-end)))
(if (char-after end)
end
;; Ensure that we don't get the line number
;; of a trailing newline.
(1- end)))))))))
(interactive (cons current-prefix-arg (magit-file-region-line-numbers)))
(require 'magit)
(if-let ((file (magit-file-relative-name)))
(magit-log-setup-buffer
@@ -782,9 +766,7 @@ restrict the log to the lines that the region touches."
(let ((args (car (magit-log-arguments))))
(when (and follow (not (member "--follow" args)))
(push "--follow" args))
(when (and (file-regular-p
(expand-file-name file (magit-toplevel)))
beg end)
(when (and beg end)
(setq args (cons (format "-L%s,%s:%s" beg end file)
(cl-delete "-L" args :test
#'string-prefix-p)))
@@ -901,7 +883,7 @@ limit. Otherwise set it to 256."
(defun magit-log-set-commit-limit (fn)
(let* ((val magit-buffer-log-args)
(arg (--first (string-match "^-n\\([0-9]+\\)?$" it) val))
(arg (seq-find (##string-match "^-n\\([0-9]+\\)?$" %) val))
(num (and arg (string-to-number (match-string 1 arg))))
(num (if num (funcall fn num 2) 256)))
(setq val (remove arg val))
@@ -912,8 +894,8 @@ limit. Otherwise set it to 256."
(magit-refresh))
(defun magit-log-get-commit-limit (&optional args)
(and-let* ((str (--first (string-match "^-n\\([0-9]+\\)?$" it)
(or args magit-buffer-log-args))))
(and-let* ((str (seq-find (##string-match "^-n\\([0-9]+\\)?$" %)
(or args magit-buffer-log-args))))
(string-to-number (match-string 1 str))))
;;;; Mode Commands
@@ -938,20 +920,19 @@ is displayed in the current frame."
(defun magit-log-move-to-parent (&optional n)
"Move to the Nth parent of the current commit."
(interactive "p")
(when (derived-mode-p 'magit-log-mode)
(when (magit-section-match 'commit)
(let* ((section (magit-current-section))
(parent-rev (format "%s^%s" (oref section value) (or n 1))))
(if-let ((parent-hash (magit-rev-parse "--short" parent-rev)))
(if-let ((parent (--first (equal (oref it value)
parent-hash)
(magit-section-siblings section 'next))))
(magit-section-goto parent)
(user-error
(substitute-command-keys
(concat "Parent " parent-hash " not found. Try typing "
"\\[magit-log-double-commit-limit] first"))))
(user-error "Parent %s does not exist" parent-rev))))))
(when (and (derived-mode-p 'magit-log-mode)
(magit-section-match 'commit))
(let* ((section (magit-current-section))
(parent-rev (format "%s^%s" (oref section value) (or n 1))))
(if-let ((parent-hash (magit-rev-parse "--short" parent-rev)))
(if-let ((parent (seq-find (##equal (oref % value) parent-hash)
(magit-section-siblings section 'next))))
(magit-section-goto parent)
(user-error
(substitute-command-keys
(concat "Parent " parent-hash " not found. Try typing "
"\\[magit-log-double-commit-limit] first"))))
(user-error "Parent %s does not exist" parent-rev)))))
(defun magit-log-move-to-revision (rev)
"Read a revision and move to it in current log buffer.
@@ -962,7 +943,15 @@ nothing else.
If invoked outside any log buffer, then display the log buffer
of the current repository first; creating it if necessary."
(interactive (list (magit-read-branch-or-commit "In log, jump to")))
(interactive
(list (or (magit-completing-read
"In log, jump to"
(magit-list-refnames nil t)
nil nil nil 'magit-revision-history
(or (and-let* ((rev (magit-commit-at-point)))
(magit-rev-fixup-target rev))
(magit-get-current-branch)))
(user-error "Nothing selected"))))
(with-current-buffer
(cond ((derived-mode-p 'magit-log-mode)
(current-buffer))
@@ -1019,6 +1008,59 @@ of the current repository first; creating it if necessary."
(transient-args 'magit-shortlog)))
(magit-git-shortlog rev-or-range args))
;;;; Movement Commands
(defvar magit-reference-movement-faces
'(magit-tag
magit-branch-remote
magit-branch-remote-head
magit-branch-local
magit-branch-current
magit-branch-upstream
magit-branch-warning
magit-head
magit-refname
magit-refname-stash
magit-refname-wip
magit-refname-pullreq))
(defvar-keymap magit-reference-navigation-repeat-map
:repeat t
"p" #'magit-previous-reference
"n" #'magit-next-reference
"r" #'magit-next-reference)
(defun magit-previous-reference ()
"Move to the previous Git reference appearing in the current buffer.
Move to the previous location that uses a face appearing in
`magit-reference-movement-faces'. If `repeat-mode' is enabled,
this command and its counterpart can be repeated using \
\\<magit-reference-navigation-repeat-map>\
\\[magit-previous-reference] and \\[magit-next-reference]."
(interactive)
(magit-next-reference t))
(defun magit-next-reference (&optional previous)
"Move to the next Git reference appearing in the current buffer.
Move to the next location that uses a face appearing in
`magit-reference-movement-faces'. If `repeat-mode' is enabled,
this command and its counterpart can be repeated using \
\\<magit-reference-navigation-repeat-map>\
\\[magit-previous-reference] and \\[magit-next-reference]."
(interactive)
(catch 'found
(let ((pos (point)))
(while (and (not (eobp))
(setq pos (if previous
(previous-single-property-change pos 'face)
(next-single-property-change pos 'face))))
(when (cl-intersection (ensure-list (get-text-property pos 'face))
magit-reference-movement-faces)
(throw 'found (goto-char pos))))
(message "No more references"))))
;;; Log Mode
(defvar magit-log-disable-graph-hack-args
@@ -1053,8 +1095,9 @@ Type \\[magit-cherry-pick] to apply the commit at point.
Type \\[magit-reset] to reset `HEAD' to the commit at point.
\\{magit-log-mode-map}"
:interactive nil
:group 'magit-log
(hack-dir-local-variables-non-file-buffer)
(magit-hack-dir-local-variables)
(setq magit--imenu-item-types 'commit))
(put 'magit-log-mode 'magit-log-default-arguments
@@ -1083,9 +1126,8 @@ Type \\[magit-reset] to reset `HEAD' to the commit at point.
(unless (length= files 1)
(setq args (remove "--follow" args)))
(when (and (car magit-log-remove-graph-args)
(--any-p (string-match-p
(concat "^" (regexp-opt magit-log-remove-graph-args)) it)
args))
(let ((re (concat "^" (regexp-opt magit-log-remove-graph-args))))
(seq-some (##string-match-p re %) args)))
(setq args (remove "--graph" args)))
(setq args (magit-log--maybe-drop-color-graph args limit))
(when-let* ((limit limit)
@@ -1097,8 +1139,8 @@ Type \\[magit-reset] to reset `HEAD' to the commit at point.
(not (member revs '("--all" "--branches")))
(not (seq-some
(lambda (arg)
(--any-p (string-prefix-p it arg)
magit-log-disable-graph-hack-args))
(seq-some (##string-prefix-p % arg)
magit-log-disable-graph-hack-args))
args))
(magit-git-string "rev-list" "--count"
"--first-parent" args revs))))
@@ -1149,14 +1191,14 @@ Type \\[magit-reset] to reset `HEAD' to the commit at point.
(defun magit-log-header-line-sentence (revs args files)
"Return string containing all arguments."
(concat "Commits in "
(mapconcat #'identity revs " ")
(string-join revs " ")
(and (member "--reverse" args)
" in reverse")
(and files (concat " touching "
(mapconcat #'identity files " ")))
(--some (and (string-prefix-p "-L" it)
(concat " " it))
args)))
(string-join files " ")))
(seq-some (##and (string-prefix-p "-L" %)
(concat " " %))
args)))
(defun magit-insert-log (revs &optional args files)
(declare (obsolete magit--insert-log "Magit 4.0.0"))
@@ -1200,8 +1242,8 @@ Do not add this to a hook variable."
(concat "\n" magit-log-revision-headers-format "\n"))
""))
(progn
(when-let ((order (--first (string-match "^\\+\\+order=\\(.+\\)$" it)
args)))
(when-let ((order (seq-find (##string-match "^\\+\\+order=\\(.+\\)$" %)
args)))
(setq args (cons (format "--%s-order" (match-string 1 order))
(remove order args))))
(when (member "--decorate" args)
@@ -1292,8 +1334,6 @@ Do not add this to a hook variable."
(defvar magit-log-count nil)
(defvar magit-log-format-message-function #'magit-log-propertize-keywords)
(defun magit-log-wash-log (style args)
(setq args (flatten-tree args))
(when (if (derived-mode-p 'magit-log-mode)
@@ -1353,12 +1393,16 @@ Do not add this to a hook variable."
;; of the youngest expired reflog entry.
(when (and (eq style 'reflog) (not date))
(cl-return-from magit-log-wash-rev t))
(magit-insert-section section (commit hash)
(pcase style
('stash (oset section type 'stash))
('module (oset section type 'module-commit))
('bisect-log (setq hash (magit-rev-parse "--short" hash))))
(setq hash (propertize hash 'font-lock-face
(magit-insert-section
((eval (pcase style
('stash 'stash)
('module 'module-commit)
(_ 'commit)))
hash)
(setq hash (propertize (if (eq style 'bisect-log)
(magit-rev-parse "--short" hash)
hash)
'font-lock-face
(pcase (and gpg (aref gpg 0))
(?G 'magit-signature-good)
(?B 'magit-signature-bad)
@@ -1398,7 +1442,7 @@ Do not add this to a hook variable."
(insert (magit-reflog-format-subject
(substring refsub 0
(if (string-search ":" refsub) -2 -1))))))
(insert (funcall magit-log-format-message-function hash msg))
(insert (magit-log--wash-summary msg))
(when (and refs magit-log-show-refname-after-summary)
(insert ?\s)
(insert (magit-format-ref-labels refs)))
@@ -1462,18 +1506,12 @@ Do not add this to a hook variable."
(insert graph ?\n))))))))
t)
(defun magit-log-propertize-keywords (_rev msg)
(let ((boundary 0))
(when (string-match "^\\(?:squash\\|fixup\\)! " msg boundary)
(setq boundary (match-end 0))
(magit--put-face (match-beginning 0) (1- boundary)
'magit-keyword-squash msg))
(when magit-log-highlight-keywords
(while (string-match "\\[[^][]*]" msg boundary)
(setq boundary (match-end 0))
(magit--put-face (match-beginning 0) boundary
'magit-keyword msg))))
msg)
(defun magit-log--wash-summary (summary)
(with-temp-buffer
(save-excursion (insert summary))
(run-hook-wrapped 'magit-log-wash-summary-hook
(lambda (fn) (prog1 nil (save-excursion (funcall fn)))))
(buffer-string)))
(defun magit-log-maybe-show-more-commits (section)
"When point is at the end of a log buffer, insert more commits.
@@ -1531,10 +1569,10 @@ If there is no blob buffer in the same frame, then do nothing."
(defun magit--maybe-update-blob-buffer ()
(when-let* ((commit (magit-section-value-if 'commit))
(buffer (--first (with-current-buffer it
(eq revert-buffer-function
'magit-revert-rev-file-buffer))
(mapcar #'window-buffer (window-list)))))
(buffer (seq-find (##with-current-buffer %
(eq revert-buffer-function
'magit-revert-rev-file-buffer))
(mapcar #'window-buffer (window-list)))))
(if magit--update-blob-buffer
(setq magit--update-blob-buffer (list commit buffer))
(setq magit--update-blob-buffer (list commit buffer))
@@ -1553,8 +1591,8 @@ If there is no blob buffer in the same frame, then do nothing."
(defun magit-log-goto-commit-section (rev)
(let ((abbrev (magit-rev-format "%h" rev)))
(when-let ((section (--first (equal (oref it value) abbrev)
(oref magit-root-section children))))
(when-let ((section (seq-find (##equal (oref % value) abbrev)
(oref magit-root-section children))))
(goto-char (oref section start)))))
(defun magit-log-goto-same-commit ()
@@ -1567,9 +1605,12 @@ If there is no blob buffer in the same frame, then do nothing."
(defvar-local magit-log-margin-show-shortstat nil)
(defun magit-toggle-log-margin-style ()
(transient-define-suffix magit-toggle-log-margin-style ()
"Toggle between the regular and the shortstat margin style.
The shortstat style is experimental and rather slow."
:description "Toggle shortstat"
:key "x"
:transient t
(interactive)
(setq magit-log-margin-show-shortstat
(not magit-log-margin-show-shortstat))
@@ -1584,7 +1625,8 @@ The shortstat style is experimental and rather slow."
(defun magit-log-format-author-margin (author date &optional previous-line)
(pcase-let ((`(,_ ,style ,width ,details ,details-width)
(or magit-buffer-margin
(symbol-value (magit-margin-option)))))
(symbol-value (magit-margin-option))
(error "No margin format specified for %s" major-mode))))
(magit-make-margin-overlay
(concat (and details
(concat (magit--propertize-face
@@ -1642,9 +1684,9 @@ The shortstat style is experimental and rather slow."
(if (eq style 'age-abbreviated)
1 ; single character
(+ 1 ; gap after digits
(apply #'max (--map (max (length (nth 1 it))
(length (nth 2 it)))
magit--age-spec)))))))))
(apply #'max (mapcar (##max (length (nth 1 %))
(length (nth 2 %)))
magit--age-spec)))))))))
;;; Select Mode
@@ -1675,7 +1717,7 @@ to visit the commit at point.
Type \\[magit-log-select-pick] to select the commit at point.
Type \\[magit-log-select-quit] to abort without selecting a commit."
:group 'magit-log
(hack-dir-local-variables-non-file-buffer))
(magit-hack-dir-local-variables))
(put 'magit-log-select-mode 'magit-log-default-arguments
'("--graph" "-n256" "--decorate"))
@@ -1709,8 +1751,14 @@ Type \\[magit-log-select-quit] to abort without selecting a commit."
(append args
(car (magit-log--get-value 'magit-log-select-mode
magit-direct-use-buffer-arguments))))
(when initial
(magit-log-goto-commit-section initial))
(if initial
(magit-log-goto-commit-section initial)
(while-let ((rev (magit-section-value-if 'commit))
((string-match-p "\\`\\(squash!\\|fixup!\\|amend!\\)"
(magit-rev-format "%s" rev)))
(section (magit-current-section))
(next (car (magit-section-siblings section 'next))))
(magit-section-goto next)))
(setq magit-log-select-pick-function pick)
(setq magit-log-select-quit-function quit)
(when magit-log-select-show-usage
@@ -1774,8 +1822,9 @@ to visit the commit at point.
Type \\[magit-cherry-pick] to apply the commit at point.
\\{magit-cherry-mode-map}"
:interactive nil
:group 'magit-log
(hack-dir-local-variables-non-file-buffer)
(magit-hack-dir-local-variables)
(setq magit--imenu-group-types 'cherries))
(defun magit-cherry-setup-buffer (head upstream)
@@ -1785,7 +1834,6 @@ Type \\[magit-cherry-pick] to apply the commit at point.
(magit-buffer-range (concat upstream ".." head))))
(defun magit-cherry-refresh-buffer ()
(setq magit-section-inhibit-markers t)
(setq magit-section-insert-in-reverse t)
(magit-insert-section (cherry)
(magit-run-section-hook 'magit-cherry-sections-hook)))
@@ -1818,7 +1866,7 @@ Type \\[magit-cherry-pick] to apply the commit at point.
(defun magit-insert-cherry-commits ()
"Insert commit sections into a `magit-cherry-mode' buffer."
(magit-insert-section (cherries)
(magit-insert-heading "Cherry commits:")
(magit-insert-heading t "Cherry commits")
(magit-git-wash (apply-partially #'magit-log-wash-log 'cherry)
"cherry" "-v" "--abbrev"
magit-buffer-upstream
@@ -1835,21 +1883,18 @@ keymap is the parent of their keymaps."
"<remap> <magit-visit-thing>" #'magit-diff-dwim
"<1>" (magit-menu-item "Visit diff" #'magit-diff-dwim))
(defvar-keymap magit-unpulled-section-map
:doc "Keymap for `unpulled' sections."
:parent magit-log-section-map)
(cl-defmethod magit-section-ident-value ((section magit-unpulled-section))
"\"..@{push}\" cannot be used as the value because that is
ambiguous if `push.default' does not allow a 1:1 mapping, and
many commands would fail because of that. But here that does
not matter and we need an unique value so we use that string
in the pushremote case."
"Return \"..@{push}\".
\"..@{push}\" cannot be used as the value because that is ambiguous
if `push.default' does not allow a 1:1 mapping, and many commands
would fail because of that. But here that does not matter and we
need an unique value, so we use that string in the pushremote case."
(let ((value (oref section value)))
(if (equal value "..@{upstream}") value "..@{push}")))
(magit-define-section-jumper magit-jump-to-unpulled-from-upstream
"Unpulled from @{upstream}" unpulled "..@{upstream}")
"Unpulled from @{upstream}" unpulled "..@{upstream}"
magit-insert-unpulled-from-upstream)
(defun magit-insert-unpulled-from-upstream ()
"Insert commits that haven't been pulled from the upstream yet."
@@ -1863,36 +1908,44 @@ in the pushremote case."
(magit-log-insert-child-count))))
(magit-define-section-jumper magit-jump-to-unpulled-from-pushremote
"Unpulled from <push-remote>" unpulled "..@{push}")
"Unpulled from <push-remote>" unpulled "..@{push}"
magit-insert-unpulled-from-pushremote)
(defun magit-insert-unpulled-from-pushremote ()
"Insert commits that haven't been pulled from the push-remote yet."
(when-let* ((target (magit-get-push-branch))
(range (concat ".." target)))
(when (magit--insert-pushremote-log-p)
(magit-insert-section (unpulled range t)
(magit-insert-heading
(format (propertize "Unpulled from %s."
'font-lock-face 'magit-section-heading)
(propertize target 'font-lock-face 'magit-branch-remote)))
(magit--insert-log nil range magit-buffer-log-args)
(magit-log-insert-child-count)))))
(defvar-keymap magit-unpushed-section-map
:doc "Keymap for `unpushed' sections."
:parent magit-log-section-map)
(range (concat ".." target))
((magit--insert-pushremote-log-p)))
(magit-insert-section (unpulled range t)
(magit-insert-heading
(format (propertize "Unpulled from %s."
'font-lock-face 'magit-section-heading)
(propertize target 'font-lock-face 'magit-branch-remote)))
(magit--insert-log nil range magit-buffer-log-args)
(magit-log-insert-child-count))))
(cl-defmethod magit-section-ident-value ((section magit-unpushed-section))
"\"..@{push}\" cannot be used as the value because that is
ambiguous if `push.default' does not allow a 1:1 mapping, and
many commands would fail because of that. But here that does
not matter and we need an unique value so we use that string
in the pushremote case."
"Return \"..@{push}\".
\"..@{push}\" cannot be used as the value because that is ambiguous
if `push.default' does not allow a 1:1 mapping, and many commands
would fail because of that. But here that does not matter and we
need an unique value, so we use that string in the pushremote case."
(let ((value (oref section value)))
(if (equal value "@{upstream}..") value "@{push}..")))
(magit-define-section-jumper magit-jump-to-unpushed-to-upstream
"Unpushed to @{upstream}" unpushed "@{upstream}..")
"Unpushed to @{upstream}" unpushed "@{upstream}.." nil
:if (lambda ()
(or (memq 'magit-insert-unpushed-to-upstream-or-recent
magit-status-sections-hook)
(memq 'magit-insert-unpushed-to-upstream
magit-status-sections-hook)))
:description (lambda ()
(let ((upstream (magit-get-upstream-branch)))
(if (or (not upstream)
(magit-rev-ancestor-p "HEAD" upstream))
"Recent commits"
"Unmerged into upstream"))))
(defun magit-insert-unpushed-to-upstream-or-recent ()
"Insert section showing unpushed or other recent commits.
@@ -1928,26 +1981,28 @@ Show the last `magit-log-section-commit-count' commits."
(or value range)
t)
(magit-insert-heading "Recent commits")
(magit--insert-log nil range
(magit--insert-log nil
(and (member "--graph" magit-buffer-log-args) range)
(cons (format "-n%d" magit-log-section-commit-count)
(--remove (string-prefix-p "-n" it)
magit-buffer-log-args))))))
(seq-remove (##string-prefix-p "-n" %)
magit-buffer-log-args))))))
(magit-define-section-jumper magit-jump-to-unpushed-to-pushremote
"Unpushed to <push-remote>" unpushed "@{push}..")
"Unpushed to <push-remote>" unpushed "@{push}.."
magit-insert-unpushed-to-pushremote)
(defun magit-insert-unpushed-to-pushremote ()
"Insert commits that haven't been pushed to the push-remote yet."
(when-let* ((target (magit-get-push-branch))
(range (concat target "..")))
(when (magit--insert-pushremote-log-p)
(magit-insert-section (unpushed range t)
(magit-insert-heading
(format (propertize "Unpushed to %s."
'font-lock-face 'magit-section-heading)
(propertize target 'font-lock-face 'magit-branch-remote)))
(magit--insert-log nil range magit-buffer-log-args)
(magit-log-insert-child-count)))))
(range (concat target ".."))
((magit--insert-pushremote-log-p)))
(magit-insert-section (unpushed range t)
(magit-insert-heading
(format (propertize "Unpushed to %s."
'font-lock-face 'magit-section-heading)
(propertize target 'font-lock-face 'magit-branch-remote)))
(magit--insert-log nil range magit-buffer-log-args)
(magit-log-insert-child-count))))
(defun magit--insert-pushremote-log-p ()
(magit--with-refresh-cache
@@ -1980,7 +2035,7 @@ not shared with any local commit) with \"+\", and all others with
\"-\"."
(when (magit-git-success "rev-parse" "@{upstream}")
(magit-insert-section (unpulled "..@{upstream}")
(magit-insert-heading "Unpulled commits:")
(magit-insert-heading t "Unpulled commits")
(magit-git-wash (apply-partially #'magit-log-wash-log 'cherry)
"cherry" "-v" (magit-abbrev-arg)
(magit-get-current-branch) "@{upstream}"))))
@@ -1993,7 +2048,7 @@ a patch-id not shared with any upstream commit) with \"+\", and
all others with \"-\"."
(when (magit-git-success "rev-parse" "@{upstream}")
(magit-insert-section (unpushed "@{upstream}..")
(magit-insert-heading "Unpushed commits:")
(magit-insert-heading t "Unpushed commits")
(magit-git-wash (apply-partially #'magit-log-wash-log 'cherry)
"cherry" "-v" (magit-abbrev-arg) "@{upstream}"))))

View File

@@ -1,9 +1,9 @@
;;; magit-margin.el --- Margins in Magit buffers -*- lexical-binding:t -*-
;; Copyright (C) 2008-2023 The Magit Project Contributors
;; Copyright (C) 2008-2025 The Magit Project Contributors
;; Author: Jonas Bernoulli <jonas@bernoul.li>
;; Maintainer: Jonas Bernoulli <jonas@bernoul.li>
;; Author: Jonas Bernoulli <emacs.magit@jonas.bernoulli.dev>
;; Maintainer: Jonas Bernoulli <emacs.magit@jonas.bernoulli.dev>
;; SPDX-License-Identifier: GPL-3.0-or-later
@@ -62,14 +62,16 @@ does not carry to other options."
"Change what information is displayed in the margin."
:info-manual "(magit) Log Margin"
["Margin"
("L" "Toggle visibility" magit-toggle-margin :transient t)
("l" "Cycle style" magit-cycle-margin-style :transient t)
("d" "Toggle details" magit-toggle-margin-details)
("v" "Change verbosity" magit-refs-set-show-commit-count
:if-derived magit-refs-mode)])
(magit-toggle-margin)
(magit-cycle-margin-style)
(magit-toggle-margin-details)
(magit-refs-set-show-commit-count)])
(defun magit-toggle-margin ()
(transient-define-suffix magit-toggle-margin ()
"Show or hide the Magit margin."
:description "Toggle visibility"
:key "L"
:transient t
(interactive)
(unless (magit-margin-option)
(user-error "Magit margin isn't supported in this buffer"))
@@ -79,8 +81,11 @@ does not carry to other options."
(defvar magit-margin-default-time-format nil
"See https://github.com/magit/magit/pull/4605.")
(defun magit-cycle-margin-style ()
(transient-define-suffix magit-cycle-margin-style ()
"Cycle style used for the Magit margin."
:description "Cycle style"
:key "l"
:transient t
(interactive)
(unless (magit-margin-option)
(user-error "Magit margin isn't supported in this buffer"))
@@ -95,8 +100,11 @@ does not carry to other options."
(_ 'age)))
(magit-set-buffer-margin nil t))
(defun magit-toggle-margin-details ()
(transient-define-suffix magit-toggle-margin-details ()
"Show or hide details in the Magit margin."
:description "Toggle details"
:key "d"
:transient t
(interactive)
(unless (magit-margin-option)
(user-error "Magit margin isn't supported in this buffer"))
@@ -118,7 +126,8 @@ does not carry to other options."
('magit-refs-mode 'magit-refs-margin)
('magit-stashes-mode 'magit-stashes-margin)
('magit-status-mode 'magit-status-margin)
('forge-notifications-mode 'magit-status-margin)))
('forge-notifications-mode 'magit-status-margin)
('forge-topics-mode 'magit-status-margin)))
(defun magit-set-buffer-margin (&optional reset refresh)
(when-let ((option (magit-margin-option)))

View File

@@ -1,9 +1,9 @@
;;; magit-merge.el --- Merge functionality -*- lexical-binding:t -*-
;; Copyright (C) 2008-2023 The Magit Project Contributors
;; Copyright (C) 2008-2025 The Magit Project Contributors
;; Author: Jonas Bernoulli <jonas@bernoul.li>
;; Maintainer: Jonas Bernoulli <jonas@bernoul.li>
;; Author: Jonas Bernoulli <emacs.magit@jonas.bernoulli.dev>
;; Maintainer: Jonas Bernoulli <emacs.magit@jonas.bernoulli.dev>
;; SPDX-License-Identifier: GPL-3.0-or-later
@@ -47,7 +47,8 @@
(5 "-b" "Ignore changes in amount of whitespace" "-Xignore-space-change")
(5 "-w" "Ignore whitespace when comparing lines" "-Xignore-all-space")
(5 magit-diff:--diff-algorithm :argument "-Xdiff-algorithm=")
(5 magit:--gpg-sign)]
(magit:--gpg-sign)
(magit:--signoff)]
["Actions"
:if-not magit-merge-in-progress-p
[("m" "Merge" magit-merge-plain)
@@ -291,10 +292,6 @@ then also remove the respective remote branch."
;;; Sections
(defvar-keymap magit-unmerged-section-map
:doc "Keymap for `unmerged' sections."
:parent magit-log-section-map)
(defun magit-insert-merge-log ()
"Insert section for the on-going merge.
Display the heads that are being merged.
@@ -306,7 +303,7 @@ If no merge is in progress, do nothing."
(range (magit--merge-range (car heads))))
(magit-insert-section (unmerged range)
(magit-insert-heading
(format "Merging %s:" (mapconcat #'identity heads ", ")))
(format "Merging %s:" (string-join heads ", ")))
(magit--insert-log nil
range
(let ((args magit-buffer-log-args))

View File

@@ -1,9 +1,9 @@
;;; magit-mode.el --- Create and refresh Magit buffers -*- lexical-binding:t -*-
;; Copyright (C) 2008-2023 The Magit Project Contributors
;; Copyright (C) 2008-2025 The Magit Project Contributors
;; Author: Jonas Bernoulli <jonas@bernoul.li>
;; Maintainer: Jonas Bernoulli <jonas@bernoul.li>
;; Author: Jonas Bernoulli <emacs.magit@jonas.bernoulli.dev>
;; Maintainer: Jonas Bernoulli <emacs.magit@jonas.bernoulli.dev>
;; SPDX-License-Identifier: GPL-3.0-or-later
@@ -31,11 +31,21 @@
(require 'magit-base)
(require 'magit-git)
(require 'benchmark)
(require 'browse-url)
(require 'format-spec)
(require 'help-mode)
(require 'transient)
(defvar bookmark-make-record-function)
(eval-when-compile (require 'elp))
(declare-function elp-reset-all "elp" ())
(declare-function elp-instrument-package "elp" (prefix))
(declare-function elp-results "elp" ())
(declare-function elp-restore-all "elp" ())
(defvar magit--wip-inhibit-autosave)
(defvar magit-wip-after-save-local-mode)
(declare-function magit-wip-get-ref "magit-wip" ())
@@ -44,17 +54,17 @@
;;; Options
(defcustom magit-mode-hook
'(magit-load-config-extensions)
(list #'magit-load-config-extensions)
"Hook run when entering a mode derived from Magit mode."
:package-version '(magit . "3.0.0")
:group 'magit-modes
:type 'hook
:options '(magit-load-config-extensions
bug-reference-mode))
:options (list #'magit-load-config-extensions
#'bug-reference-mode))
(defcustom magit-setup-buffer-hook
'(magit-maybe-save-repository-buffers
magit-set-buffer-margin)
(list #'magit-maybe-save-repository-buffers
'magit-set-buffer-margin) ; from magit-margin.el
"Hook run by `magit-setup-buffer'.
This is run right after displaying the buffer and right before
@@ -65,10 +75,11 @@ should be used instead of this one."
:package-version '(magit . "2.3.0")
:group 'magit-modes
:type 'hook
:options '(magit-maybe-save-repository-buffers
magit-set-buffer-margin))
:options (list #'magit-maybe-save-repository-buffers
'magit-set-buffer-margin))
(defcustom magit-pre-refresh-hook '(magit-maybe-save-repository-buffers)
(defcustom magit-pre-refresh-hook
(list #'magit-maybe-save-repository-buffers)
"Hook run before refreshing in `magit-refresh'.
This hook, or `magit-post-refresh-hook', should be used
@@ -80,9 +91,10 @@ inside your function."
:package-version '(magit . "2.4.0")
:group 'magit-refresh
:type 'hook
:options '(magit-maybe-save-repository-buffers))
:options (list #'magit-maybe-save-repository-buffers))
(defcustom magit-post-refresh-hook
;; Do not function-quote to avoid circular dependencies.
'(magit-auto-revert-buffers
magit-run-post-commit-hook
magit-run-post-stage-hook
@@ -111,36 +123,37 @@ All Magit buffers (buffers whose major-modes derive from
which in turn uses the function specified here."
:package-version '(magit . "2.3.0")
:group 'magit-buffers
:type '(radio (function-item magit-display-buffer-traditional)
(function-item magit-display-buffer-same-window-except-diff-v1)
(function-item magit-display-buffer-fullframe-status-v1)
(function-item magit-display-buffer-fullframe-status-topleft-v1)
(function-item magit-display-buffer-fullcolumn-most-v1)
(function-item display-buffer)
:type `(radio (function-item ,#'magit-display-buffer-traditional)
(function-item ,#'magit-display-buffer-same-window-except-diff-v1)
(function-item ,#'magit-display-buffer-fullframe-status-v1)
(function-item ,#'magit-display-buffer-fullframe-status-topleft-v1)
(function-item ,#'magit-display-buffer-fullcolumn-most-v1)
(function-item ,#'display-buffer)
(function :tag "Function")))
(defcustom magit-pre-display-buffer-hook '(magit-save-window-configuration)
(defcustom magit-pre-display-buffer-hook
(list #'magit-save-window-configuration)
"Hook run by `magit-display-buffer' before displaying the buffer."
:package-version '(magit . "2.3.0")
:group 'magit-buffers
:type 'hook
:get #'magit-hook-custom-get
:options '(magit-save-window-configuration))
:options (list #'magit-save-window-configuration))
(defcustom magit-post-display-buffer-hook '(magit-maybe-set-dedicated)
(defcustom magit-post-display-buffer-hook (list #'magit-maybe-set-dedicated)
"Hook run by `magit-display-buffer' after displaying the buffer."
:package-version '(magit . "2.3.0")
:group 'magit-buffers
:type 'hook
:get #'magit-hook-custom-get
:options '(magit-maybe-set-dedicated))
:options (list #'magit-maybe-set-dedicated))
(defcustom magit-generate-buffer-name-function
#'magit-generate-buffer-name-default-function
"The function used to generate the name for a Magit buffer."
:package-version '(magit . "2.3.0")
:group 'magit-buffers
:type '(radio (function-item magit-generate-buffer-name-default-function)
:type `(radio (function-item ,#'magit-generate-buffer-name-default-function)
(function :tag "Function")))
(defcustom magit-buffer-name-format "%x%M%v: %t%x"
@@ -189,9 +202,9 @@ support additional %-sequences."
"The function used to bury or kill the current Magit buffer."
:package-version '(magit . "3.2.0")
:group 'magit-buffers
:type '(radio (function-item quit-window)
(function-item magit-mode-quit-window)
(function-item magit-restore-window-configuration)
:type `(radio (function-item ,#'quit-window)
(function-item ,#'magit-mode-quit-window)
(function-item ,#'magit-restore-window-configuration)
(function :tag "Function")))
(defcustom magit-prefix-use-buffer-arguments 'selected
@@ -259,7 +272,8 @@ and Buffer Variables'."
(const :tag "use args from buffer if it is current" current)
(const :tag "never use args from buffer" never)))
(defcustom magit-region-highlight-hook '(magit-diff-update-hunk-region)
(defcustom magit-region-highlight-hook
'(magit-diff-update-hunk-region) ; from magit-diff.el
"Functions used to highlight the region.
Each function is run with the current section as only argument
@@ -271,11 +285,22 @@ then fall back to regular region highlighting."
:options '(magit-diff-update-hunk-region))
(defcustom magit-create-buffer-hook nil
"Normal hook run after creating a new `magit-mode' buffer."
"Normal hook run while creating a new `magit-mode' buffer.
Runs before the buffer is populated with sections. Also see
`magit-post-create-buffer-hook'."
:package-version '(magit . "2.90.0")
:group 'magit-refresh
:type 'hook)
(defcustom magit-post-create-buffer-hook nil
"Normal hook run after creating a new `magit-mode' buffer.
Runs after the buffer is populated with sections for the first
time. Also see `magit-create-buffer-hook' (which runs earlier)
and `magit-refresh-buffer-hook' (which runs on every refresh)."
:package-version '(magit . "4.0.0")
:group 'magit-refresh
:type 'hook)
(defcustom magit-refresh-buffer-hook nil
"Normal hook for `magit-refresh-buffer' to run after refreshing."
:package-version '(magit . "2.1.0")
@@ -394,15 +419,17 @@ recommended value."
"!" 'magit-run
">" 'magit-sparse-checkout
"C-c C-c" 'magit-dispatch
"C-c C-r" 'magit-next-reference
"C-c C-e" 'magit-edit-thing
"C-c C-o" 'magit-browse-thing
"C-c C-w" 'magit-copy-thing
"C-w" 'magit-copy-section-value
"M-w" 'magit-copy-buffer-revision
"<remap> <previous-line>" 'magit-previous-line
"<remap> <next-line>" 'magit-next-line
"<remap> <evil-previous-line>" 'evil-previous-visual-line
"<remap> <evil-next-line>" 'evil-next-visual-line)
"<remap> <back-to-indentation>" 'magit-back-to-indentation
"<remap> <previous-line>" 'magit-previous-line
"<remap> <next-line>" 'magit-next-line
"<remap> <evil-previous-line>" 'evil-previous-visual-line
"<remap> <evil-next-line>" 'evil-next-visual-line)
(defun magit-delete-thing ()
"This is a placeholder command, which signals an error if called.
@@ -410,6 +437,8 @@ Where applicable, other keymaps remap this command to another,
which actually deletes the thing at point."
(interactive)
(user-error "There is no thing at point that could be deleted"))
;; Starting with Emacs 28.1 we could use (declare (completion ignore)).
(put 'magit-delete-thing 'completion-predicate #'ignore)
(defun magit-visit-thing ()
"This is a placeholder command, which may signal an error if called.
@@ -418,7 +447,10 @@ which actually visits the thing at point."
(interactive)
(if (eq transient-current-command 'magit-dispatch)
(call-interactively (key-binding (this-command-keys)))
(user-error "There is no thing at point that could be visited")))
(if-let ((url (thing-at-point 'url t)))
(browse-url url)
(user-error "There is no thing at point that could be visited"))))
(put 'magit-visit-thing 'completion-predicate #'ignore)
(defun magit-edit-thing ()
"This is a placeholder command, which may signal an error if called.
@@ -429,13 +461,17 @@ buffer."
(if (eq transient-current-command 'magit-dispatch)
(call-interactively (key-binding (this-command-keys)))
(user-error "There is no thing at point that could be edited")))
(put 'magit-edit-thing 'completion-predicate #'ignore)
(defun magit-browse-thing ()
"This is a placeholder command, which signals an error if called.
"This is a placeholder command, which may signal an error if called.
Where applicable, other keymaps remap this command to another,
which actually visits thing at point using `browse-url'."
(interactive)
(user-error "There is no thing at point that could be browsed"))
(if-let ((url (thing-at-point 'url t)))
(browse-url url)
(user-error "There is no thing at point that could be browsed")))
(put 'magit-browse-thing 'completion-predicate #'ignore)
(defun magit-copy-thing ()
"This is a placeholder command, which signals an error if called.
@@ -444,6 +480,7 @@ which actually copies some representation of the thing at point
to the kill ring."
(interactive)
(user-error "There is no thing at point that we know how to copy"))
(put 'magit-copy-thing 'completion-predicate #'ignore)
;;;###autoload
(defun magit-info ()
@@ -457,7 +494,7 @@ to the kill ring."
'bug-reference-push-button))
(easy-menu-define magit-mode-menu magit-mode-map
"Magit menu"
"Magit menu."
;; Similar to `magit-dispatch' but exclude:
;; - commands that are available from context menus:
;; apply, reverse, discard, stage, unstage,
@@ -528,8 +565,9 @@ to the kill ring."
"Parent major mode from which Magit major modes inherit.
Magit is documented in info node `(magit)'."
:interactive nil
:group 'magit
(hack-dir-local-variables-non-file-buffer)
(magit-hack-dir-local-variables)
(face-remap-add-relative 'header-line 'magit-header-line)
(setq mode-line-process (magit-repository-local-get 'mode-line-process))
(setq-local revert-buffer-function #'magit-refresh-buffer)
@@ -538,6 +576,13 @@ Magit is documented in info node `(magit)'."
(setq-local imenu-default-goto-function #'magit--imenu-goto-function)
(setq-local isearch-filter-predicate #'magit-section--open-temporarily))
(defun magit-hack-dir-local-variables ()
"Like `hack-dir-local-variables-non-file-buffer' but ignore some variables."
(let ((ignored-local-variables
`(show-trailing-whitespace
,@ignored-local-variables)))
(hack-dir-local-variables-non-file-buffer)))
;;; Local Variables
(defvar-local magit-buffer-arguments nil)
@@ -579,9 +624,6 @@ The buffer's major-mode should derive from `magit-section-mode'."
(defvar-local magit-previous-section nil)
(put 'magit-previous-section 'permanent-local t)
(defvar-local magit--imenu-group-types nil)
(defvar-local magit--imenu-item-types nil)
;;; Setup Buffer
(defmacro magit-setup-buffer (mode &optional locked &rest bindings)
@@ -592,20 +634,25 @@ The buffer's major-mode should derive from `magit-section-mode'."
`(list ',var ,form))
bindings))))
(defun magit-setup-buffer-internal (mode locked bindings)
(defun magit-setup-buffer-internal ( mode locked bindings
&optional buffer-or-name directory)
(let* ((value (and locked
(with-temp-buffer
(pcase-dolist (`(,var ,val) bindings)
(set (make-local-variable var) val))
(let ((major-mode mode))
(magit-buffer-value)))))
(buffer (magit-get-mode-buffer mode value))
(buffer (if buffer-or-name
(get-buffer-create buffer-or-name)
(magit-get-mode-buffer mode value)))
(section (and buffer (magit-current-section)))
(created (not buffer)))
(unless buffer
(setq buffer (magit-generate-new-buffer mode value)))
(with-current-buffer buffer
(setq magit-previous-section section)
(when directory
(setq default-directory directory))
(funcall mode)
(magit-xref-setup #'magit-setup-buffer-internal bindings)
(pcase-dolist (`(,var ,val) bindings)
@@ -615,7 +662,9 @@ The buffer's major-mode should derive from `magit-section-mode'."
(magit-display-buffer buffer)
(with-current-buffer buffer
(run-hooks 'magit-setup-buffer-hook)
(magit-refresh-buffer))
(magit-refresh-buffer)
(when created
(run-hooks 'magit-post-create-buffer-hook)))
buffer))
;;; Display Buffer
@@ -786,10 +835,10 @@ into thinking a buffer belongs to a repo that it doesn't.")
(defun magit-mode-get-buffers ()
(let ((topdir (magit-toplevel)))
(--filter (with-current-buffer it
(and (derived-mode-p 'magit-mode)
(equal magit--default-directory topdir)))
(buffer-list))))
(seq-filter (##with-current-buffer %
(and (derived-mode-p 'magit-mode)
(equal magit--default-directory topdir)))
(buffer-list))))
(defvar-local magit-buffer-locked-p nil)
(put 'magit-buffer-locked-p 'permanent-local t)
@@ -841,7 +890,7 @@ If a frame, then only consider buffers on that frame."
(setq magit-buffer-locked-p (and value t))
(magit-restore-section-visibility-cache mode))
(when magit-uniquify-buffer-names
(add-to-list 'uniquify-list-buffers-directory-modes mode)
(cl-pushnew mode uniquify-list-buffers-directory-modes)
(with-current-buffer buffer
(setq list-buffers-directory (abbreviate-file-name default-directory)))
(let ((uniquify-buffer-name-style
@@ -859,7 +908,7 @@ The returned name is based on `magit-buffer-name-format' and
takes `magit-uniquify-buffer-names' and VALUE, if non-nil, into
account."
(let ((m (substring (symbol-name mode) 0 -5))
(v (and value (format "%s" (if (listp value) value (list value)))))
(v (and value (format "%s" (ensure-list value))))
(n (if magit-uniquify-buffer-names
(file-name-nondirectory
(directory-file-name default-directory))
@@ -935,14 +984,14 @@ current buffer is the last remaining Magit buffer that was
ever displayed in the selected window, then delete that
window."
(if (or (one-window-p)
(--first (let ((buffer (car it)))
(and (not (eq buffer (current-buffer)))
(buffer-live-p buffer)
(or (not (window-parameter nil 'magit-dedicated))
(with-current-buffer buffer
(derived-mode-p 'magit-mode
'magit-process-mode)))))
(window-prev-buffers)))
(seq-find (pcase-lambda (`(,buffer))
(and (not (eq buffer (current-buffer)))
(buffer-live-p buffer)
(or (not (window-parameter nil 'magit-dedicated))
(with-current-buffer buffer
(derived-mode-p 'magit-mode
'magit-process-mode)))))
(window-prev-buffers)))
(quit-window kill-buffer)
(let ((window (selected-window)))
(quit-window kill-buffer)
@@ -983,7 +1032,7 @@ Run hooks `magit-pre-refresh-hook' and `magit-post-refresh-hook'."
(let* ((c (caar magit--refresh-cache))
(a (+ c (cdar magit--refresh-cache))))
(message "Refreshing magit...done (%.3fs, cached %s/%s (%.0f%%))"
(float-time (time-subtract (current-time) start))
(float-time (time-since start))
c a (* (/ c (* a 1.0)) 100)))))
(run-hooks 'magit-unwind-refresh-hook))))
@@ -1005,6 +1054,7 @@ Run hooks `magit-pre-refresh-hook' and `magit-post-refresh-hook'."
(defun magit-refresh-buffer (&rest _ignore)
"Refresh the current Magit buffer."
(interactive)
(setq magit-refresh-start-time (current-time))
(let ((refresh (intern (format "%s-refresh-buffer"
(substring (symbol-name major-mode) 0 -5))))
@@ -1013,7 +1063,7 @@ Run hooks `magit-pre-refresh-hook' and `magit-post-refresh-hook'."
(when magit-refresh-verbose
(message "Refreshing buffer `%s'..." (buffer-name)))
(let* ((buffer (current-buffer))
(windows (cl-mapcan
(windows (mapcan
(lambda (window)
(with-selected-window window
(with-current-buffer buffer
@@ -1049,8 +1099,37 @@ Run hooks `magit-pre-refresh-hook' and `magit-post-refresh-hook'."
(set-buffer-modified-p nil))
(when magit-refresh-verbose
(message "Refreshing buffer `%s'...done (%.3fs)" (buffer-name)
(float-time (time-subtract (current-time)
magit-refresh-start-time)))))))
(float-time (time-since magit-refresh-start-time)))))))
(defun magit-profile-refresh-buffer ()
"Profile refreshing the current Magit buffer."
(interactive)
(require (quote elp))
(elp-reset-all)
(message "Profiling Magit and Forge...")
(elp-instrument-package "magit-")
(elp-instrument-package "forge-")
(magit-refresh-buffer)
(message "Profiling Magit and Forge...done")
(elp-results)
(elp-reset-all))
(defun magit-toggle-profiling ()
"Start profiling Magit, or if in progress, stop and display the results."
(interactive)
(require (quote elp))
(cond ((catch 'in-progress
(mapatoms (lambda (symbol)
(and (get symbol elp-timer-info-property)
(throw 'in-progress t)))))
(message "Stop profiling and display results...")
(elp-results)
(elp-restore-all))
(t
(message "Start profiling Magit and Forge...")
(elp-reset-all)
(elp-instrument-package "magit-")
(elp-instrument-package "forge-"))))
;;; Save File-Visiting Buffers
@@ -1083,12 +1162,11 @@ Note that refreshing a Magit buffer is done by re-creating its
contents from scratch, which can be slow in large repositories.
If you are not satisfied with Magit's performance, then you
should obviously not add this function to that hook."
(when (and (not magit--disable-save-buffers)
(magit-inside-worktree-p t))
(when-let ((buffer (ignore-errors
(magit-get-mode-buffer 'magit-status-mode))))
(add-to-list 'magit-after-save-refresh-buffers buffer)
(add-hook 'post-command-hook #'magit-after-save-refresh-buffers))))
(when-let (((and (not magit--disable-save-buffers)
(magit-inside-worktree-p t)))
(buf (ignore-errors (magit-get-mode-buffer 'magit-status-mode))))
(cl-pushnew buf magit-after-save-refresh-buffers)
(add-hook 'post-command-hook #'magit-after-save-refresh-buffers)))
(defun magit-maybe-save-repository-buffers ()
"Maybe save file-visiting buffers belonging to the current repository.
@@ -1106,10 +1184,6 @@ if you so desire."
(not (equal msg (current-message))))
(message "%s" msg)))))
(add-hook 'magit-pre-refresh-hook #'magit-maybe-save-repository-buffers)
(add-hook 'magit-pre-call-git-hook #'magit-maybe-save-repository-buffers)
(add-hook 'magit-pre-start-git-hook #'magit-maybe-save-repository-buffers)
(defvar-local magit-inhibit-refresh-save nil)
(defun magit-save-repository-buffers (&optional arg)
@@ -1256,6 +1330,7 @@ Later, when the buffer is buried, it may be restored by
'help-echo (purecopy "mouse-2, RET: go back to next history entry"))
(defvar magit-xref-modes
;; Do not function-quote to avoid circular dependencies.
'(magit-log-mode
magit-reflog-mode
magit-diff-mode
@@ -1338,11 +1413,16 @@ Unless specified, REPOSITORY is the current buffer's repository."
(defun magit-repository-local-delete (key &optional repository)
"Delete the repository-local value for KEY.
Unless specified, REPOSITORY is the current buffer's repository."
(when-let ((cache (assoc (or repository
(magit-repository-local-repository))
magit-repository-local-cache)))
(setf cache (compat-call assoc-delete-all key cache))))
Unless specified, REPOSITORY is the current buffer's repository.
If REPOSITORY is `all', then delete the value for KEY for all
repositories."
(if (eq repository 'all)
(dolist (cache magit-repository-local-cache)
(setf cache (compat-call assoc-delete-all key cache)))
(when-let ((cache (assoc (or repository
(magit-repository-local-repository))
magit-repository-local-cache)))
(setf cache (compat-call assoc-delete-all key cache)))))
(defmacro magit--with-repository-local-cache (key &rest body)
(declare (indent 1) (debug (form body)))
@@ -1369,9 +1449,9 @@ Unless specified, REPOSITORY is the current buffer's repository."
"Zap caches for the current repository.
Remove the repository's entry from `magit-repository-local-cache',
remove the host's entry from `magit--host-git-version-cache', set
`magit-section-visibility-cache' to nil for all Magit buffers of
the repository and set `magit--libgit-available-p' to `unknown'.
remove the host's entry from `magit--host-git-version-cache', and
set `magit-section-visibility-cache' to nil for all Magit buffers
of the repository.
With a prefix argument or if optional ALL is non-nil, discard the
mentioned caches completely."
@@ -1395,155 +1475,7 @@ mentioned caches completely."
:key #'car :test #'equal)))
(dolist (buffer (magit-mode-get-buffers))
(with-current-buffer buffer
(setq magit-section-visibility-cache nil)))))
(setq magit--libgit-available-p 'unknown))
;;; Imenu Support
(defun magit--imenu-create-index ()
;; If `which-function-mode' is active, then the create-index
;; function is called at the time the major-mode is being enabled.
;; Modes that derive from `magit-mode' have not populated the buffer
;; at that time yet, so we have to abort.
(and magit-root-section
(or magit--imenu-group-types
magit--imenu-item-types)
(let ((index
(cl-mapcan
(lambda (section)
(cond
(magit--imenu-group-types
(and (if (eq (car-safe magit--imenu-group-types) 'not)
(not (magit-section-match
(cdr magit--imenu-group-types)
section))
(magit-section-match magit--imenu-group-types section))
(and-let* ((children (oref section children)))
`((,(magit--imenu-index-name section)
,@(mapcar (lambda (s)
(cons (magit--imenu-index-name s)
(oref s start)))
children))))))
(magit--imenu-item-types
(and (magit-section-match magit--imenu-item-types section)
`((,(magit--imenu-index-name section)
. ,(oref section start)))))))
(oref magit-root-section children))))
(if (and magit--imenu-group-types (symbolp magit--imenu-group-types))
(cdar index)
index))))
(defun magit--imenu-index-name (section)
(let ((heading (buffer-substring-no-properties
(oref section start)
(1- (or (oref section content)
(oref section end))))))
(save-match-data
(cond
((and (magit-section-match [commit logbuf] section)
(string-match "[^ ]+\\([ *|]*\\).+" heading))
(replace-match " " t t heading 1))
((magit-section-match
'([branch local branchbuf] [tag tags branchbuf]) section)
(oref section value))
((magit-section-match [branch remote branchbuf] section)
(concat (oref (oref section parent) value) "/"
(oref section value)))
((string-match " ([0-9]+)\\'" heading)
(substring heading 0 (match-beginning 0)))
(t heading)))))
(defun magit--imenu-goto-function (_name position &rest _rest)
"Go to the section at POSITION.
Make sure it is visible, by showing its ancestors where
necessary. For use as `imenu-default-goto-function' in
`magit-mode' buffers."
(goto-char position)
(let ((section (magit-current-section)))
(while (setq section (oref section parent))
(when (oref section hidden)
(magit-section-show section)))))
;;; Bookmark support
(declare-function bookmark-get-filename "bookmark" (bookmark-name-or-record))
(declare-function bookmark-make-record-default "bookmark"
(&optional no-file no-context posn))
(declare-function bookmark-prop-get "bookmark" (bookmark-name-or-record prop))
(declare-function bookmark-prop-set "bookmark" (bookmark-name-or-record prop val))
(defun magit--make-bookmark ()
"Create a bookmark for the current Magit buffer.
Input values are the major-mode's `magit-bookmark-name' method,
and the buffer-local values of the variables referenced in its
`magit-bookmark-variables' property."
(require 'bookmark)
(if (plist-member (symbol-plist major-mode) 'magit-bookmark-variables)
;; `bookmark-make-record-default's return value does not match
;; (NAME . ALIST), even though it is used as the default value
;; of `bookmark-make-record-function', which states that such
;; functions must do that. See #4356.
(let ((bookmark (cons nil (bookmark-make-record-default 'no-file))))
(bookmark-prop-set bookmark 'handler #'magit--handle-bookmark)
(bookmark-prop-set bookmark 'mode major-mode)
(bookmark-prop-set bookmark 'filename (magit-toplevel))
(bookmark-prop-set bookmark 'defaults (list (magit-bookmark-name)))
(dolist (var (get major-mode 'magit-bookmark-variables))
(bookmark-prop-set bookmark var (symbol-value var)))
(bookmark-prop-set
bookmark 'magit-hidden-sections
(--keep (and (oref it hidden)
(cons (oref it type)
(if (derived-mode-p 'magit-stash-mode)
(string-replace magit-buffer-revision
magit-buffer-revision-hash
(oref it value))
(oref it value))))
(oref magit-root-section children)))
bookmark)
(user-error "Bookmarking is not implemented for %s buffers" major-mode)))
(defun magit--handle-bookmark (bookmark)
"Open a bookmark created by `magit--make-bookmark'.
Call the `magit-*-setup-buffer' function of the the major-mode
with the variables' values as arguments, which were recorded by
`magit--make-bookmark'. Ignore `magit-display-buffer-function'."
(let ((buffer (let ((default-directory (bookmark-get-filename bookmark))
(mode (bookmark-prop-get bookmark 'mode))
(magit-display-buffer-function #'identity)
(magit-display-buffer-noselect t))
(apply (intern (format "%s-setup-buffer"
(substring (symbol-name mode) 0 -5)))
(--map (bookmark-prop-get bookmark it)
(get mode 'magit-bookmark-variables))))))
(set-buffer buffer) ; That is the interface we have to adhere to.
(when-let ((hidden (bookmark-prop-get bookmark 'magit-hidden-sections)))
(with-current-buffer buffer
(dolist (child (oref magit-root-section children))
(if (member (cons (oref child type)
(oref child value))
hidden)
(magit-section-hide child)
(magit-section-show child)))))
;; Compatibility with `bookmark+' package. See #4356.
(when (bound-and-true-p bmkp-jump-display-function)
(funcall bmkp-jump-display-function (current-buffer)))
nil))
(put 'magit--handle-bookmark 'bookmark-handler-type "Magit")
(cl-defgeneric magit-bookmark-name ()
"Return name for bookmark to current buffer."
(format "%s%s"
(substring (symbol-name major-mode) 0 -5)
(if-let ((vars (get major-mode 'magit-bookmark-variables)))
(cl-mapcan (lambda (var)
(let ((val (symbol-value var)))
(if (and val (atom val))
(list val)
val)))
vars)
"")))
(setq magit-section-visibility-cache nil))))))
;;; Utilities
@@ -1557,14 +1489,33 @@ The additional output can be found in the *Messages* buffer."
(if magit-refresh-verbose "Enabled" "Disabled")))
(defun magit-run-hook-with-benchmark (hook)
(when hook
(if magit-refresh-verbose
(let ((start (current-time)))
(message "Running %s..." hook)
(run-hooks hook)
(message "Running %s...done (%.3fs)" hook
(float-time (time-subtract (current-time) start))))
(run-hooks hook))))
(cond
((not hook))
(magit-refresh-verbose
(message "Running %s..." hook)
(message "Running %s...done (%.3fs)" hook
(benchmark-elapse
(run-hook-wrapped
hook
(lambda (fn)
(message " %-50s %f" fn (benchmark-elapse (funcall fn))))))))
((run-hooks hook))))
(defun magit-file-region-line-numbers ()
"Return the bounds of the region as line numbers.
The returned value has the form (BEGINNING-LINE END-LINE). If
the region end at the beginning of a line, do not include that
line. Avoid including the line after the end of the file."
(and (or magit-buffer-file-name buffer-file-name)
(region-active-p)
(not (= (region-beginning) (region-end) (1+ (buffer-size))))
(let ((beg (region-beginning))
(end (min (region-end) (buffer-size))))
(list (line-number-at-pos beg t)
(line-number-at-pos (if (= (magit--bol-position end) end)
(max beg (1- end))
end)
t)))))
;;; _
(provide 'magit-mode)

View File

@@ -1,9 +1,9 @@
;;; magit-notes.el --- Notes support -*- lexical-binding:t -*-
;; Copyright (C) 2008-2023 The Magit Project Contributors
;; Copyright (C) 2008-2025 The Magit Project Contributors
;; Author: Jonas Bernoulli <jonas@bernoul.li>
;; Maintainer: Jonas Bernoulli <jonas@bernoul.li>
;; Author: Jonas Bernoulli <emacs.magit@jonas.bernoulli.dev>
;; Maintainer: Jonas Bernoulli <emacs.magit@jonas.bernoulli.dev>
;; SPDX-License-Identifier: GPL-3.0-or-later
@@ -192,8 +192,8 @@ Also see `magit-notes-merge'."
(defun magit-notes-read-args (prompt)
(list (magit-read-branch-or-commit prompt (magit-stash-at-point))
(and-let* ((str (--first (string-match "^--ref=\\(.+\\)" it)
(transient-args 'magit-notes))))
(and-let* ((str (seq-find (##string-match "^--ref=\\(.+\\)" %)
(transient-args 'magit-notes))))
(match-string 1 str))))
;;; _

View File

@@ -1,9 +1,9 @@
;;; magit-patch.el --- Creating and applying patches -*- lexical-binding:t -*-
;; Copyright (C) 2008-2023 The Magit Project Contributors
;; Copyright (C) 2008-2025 The Magit Project Contributors
;; Author: Jonas Bernoulli <jonas@bernoul.li>
;; Maintainer: Jonas Bernoulli <jonas@bernoul.li>
;; Author: Jonas Bernoulli <emacs.magit@jonas.bernoulli.dev>
;; Maintainer: Jonas Bernoulli <emacs.magit@jonas.bernoulli.dev>
;; SPDX-License-Identifier: GPL-3.0-or-later

View File

@@ -1,17 +1,19 @@
(define-package "magit" "20231103.1516" "A Git porcelain inside Emacs."
'((emacs "25.1")
(compat "29.1.3.4")
(dash "20221013")
(git-commit "20230101")
(magit-section "20230101")
(define-package "magit" "20250221.105" "A Git porcelain inside Emacs"
'((emacs "27.1")
(compat "30.0.2.0")
(llama "0.6.0")
(magit-section "4.3.0")
(seq "2.24")
(transient "20230201")
(with-editor "20230118"))
:commit "2b02bfb0495bf738e006d82f3a233e8f511b90b8" :authors
(transient "0.8.4")
(with-editor "3.4.3"))
:commit "9914feb4d5a2feab091076be554d80781594869d" :authors
'(("Marius Vollmer" . "marius.vollmer@gmail.com")
("Jonas Bernoulli" . "jonas@bernoul.li"))
("Jonas Bernoulli" . "emacs.magit@jonas.bernoulli.dev"))
:maintainers
'(("Jonas Bernoulli" . "emacs.magit@jonas.bernoulli.dev")
("Kyle Meyer" . "kyle@kyleam.com"))
:maintainer
'("Jonas Bernoulli" . "jonas@bernoul.li")
'("Jonas Bernoulli" . "emacs.magit@jonas.bernoulli.dev")
:keywords
'("git" "tools" "vc")
:url "https://github.com/magit/magit")

View File

@@ -1,9 +1,9 @@
;;; magit-process.el --- Process functionality -*- lexical-binding:t -*-
;; Copyright (C) 2008-2023 The Magit Project Contributors
;; Copyright (C) 2008-2025 The Magit Project Contributors
;; Author: Jonas Bernoulli <jonas@bernoul.li>
;; Maintainer: Jonas Bernoulli <jonas@bernoul.li>
;; Author: Jonas Bernoulli <emacs.magit@jonas.bernoulli.dev>
;; Maintainer: Jonas Bernoulli <emacs.magit@jonas.bernoulli.dev>
;; SPDX-License-Identifier: GPL-3.0-or-later
@@ -35,8 +35,10 @@
(require 'magit-mode)
(require 'ansi-color)
(require 'auth-source)
(require 'with-editor)
(defvar messages-buffer-name)
(defvar y-or-n-p-map)
;;; Options
@@ -91,16 +93,6 @@ When this is nil, no sections are ever removed."
:group 'magit-process
:type '(choice (const :tag "Never remove old sections" nil) integer))
(defvar magit-process-extreme-logging nil
"Whether `magit-process-file' logs to the *Messages* buffer.
Only intended for temporary use when you try to figure out how
Magit uses Git behind the scene. Output that normally goes to
the magit-process buffer continues to go there. Not all output
goes to either of these two buffers.
Also see `magit-git-debug'.")
(defcustom magit-process-error-tooltip-max-lines 20
"The number of lines for `magit-process-error-lines' to return.
@@ -114,21 +106,22 @@ displays the text of `magit-process-error-summary' instead."
integer))
(defcustom magit-credential-cache-daemon-socket
(--some (pcase-let ((`(,prog . ,args) (split-string it)))
(if (and prog
(seq-some (lambda (line)
(pcase-let ((`(,prog . ,args) (split-string line)))
(and prog
(string-match-p
"\\`\\(?:\\(?:/.*/\\)?git-credential-\\)?cache\\'" prog))
(or (cl-loop for (opt val) on args
if (string= opt "--socket")
return val)
(expand-file-name "~/.git-credential-cache/socket"))))
;; Note: `magit-process-file' is not yet defined when
;; evaluating this form, so we use `process-lines'.
(ignore-errors
(let ((process-environment
(append magit-git-environment process-environment)))
(process-lines magit-git-executable
"config" "--get-all" "credential.helper"))))
"\\`\\(?:\\(?:/.*/\\)?git-credential-\\)?cache\\'" prog)
(or (cl-loop for (opt val) on args
if (string= opt "--socket")
return val)
(expand-file-name "~/.git-credential-cache/socket")))))
;; Note: `magit-process-file' is not yet defined when
;; evaluating this form, so we use `process-lines'.
(ignore-errors
(let ((process-environment
(append magit-git-environment process-environment)))
(process-lines magit-git-executable
"config" "--get-all" "credential.helper"))))
"If non-nil, start a credential cache daemon using this socket.
When using Git's cache credential helper in the normal way, Emacs
@@ -152,32 +145,41 @@ itself from the hook, to avoid further futile attempts."
(const :tag "Don't start a cache daemon" nil)))
(defcustom magit-process-yes-or-no-prompt-regexp
(concat " [([]"
"\\([Yy]\\(?:es\\)?\\)"
"[/|]"
"\\([Nn]o?\\)"
;; OpenSSH v8 prints this. See #3969.
"\\(?:/\\[fingerprint\\]\\)?"
"[])] ?[?:]? ?$")
(eval-when-compile
(concat " [([]"
"\\([Yy]\\(?:es\\)?\\)"
"[/|]"
"\\([Nn]o?\\)"
;; OpenSSH v8 prints this. See #3969.
"\\(?:/\\[fingerprint\\]\\)?"
"[])] ?[?:]? ?$"))
"Regexp matching Yes-or-No prompts of Git and its subprocesses."
:package-version '(magit . "2.1.0")
:group 'magit-process
:type 'regexp)
(defcustom magit-process-password-prompt-regexps
'("^\\(Enter \\)?[Pp]assphrase\\( for \\(RSA \\)?key '.*'\\)?: ?$"
;; Match-group 99 is used to identify the "user@host" part.
"^\\(Enter \\|([^) ]+) \\)?\
[Pp]assword\\( for '?\\(https?://\\)?\\(?99:[^']*\\)'?\\)?: ?$"
"Please enter the passphrase for the ssh key"
"Please enter the passphrase to unlock the OpenPGP secret key"
"^.*'s password: ?$"
"^Token: $" ; For git-credential-manager-core (#4318).
;; See also history in test `magit-process:password-prompt-regexps'.
'(;; * CLI-prompt for passphrase for key:
"^\\(\\(Please e\\|E\\)nter \\(the \\)?p\\|P\\)assphrase.*: ?$"
;; * Password for something other than a host:
"^\\(\\(Please e\\|E\\)nter \\(the \\)?p\\|P\\)assword: ?$"
;; * Password for [user@]host (which we put in match group 99):
"^\\(\\(Please e\\|E\\)nter \\(the \\)?p\\|P\\)assword for \
[\"']?\\(https?://\\)?\\(?99:[^\"']+\\)[\"']?: ?$"
"^(\\(?1:[^) ]+\\)) Password for \\(?99:\\1\\): ?$" ;#4992
"^\\(?99:[^']+\\)\\('s\\)? password: ?$"
;; * Token for git-credential-manager-core (#4318):
"^Token: ?$"
;; * Secret for card:
"^Yubikey for .*: ?$"
"^Enter PIN for .*: ?$")
"^Enter PIN for .*: ?$"
;; * Unanchored TUI-prompt for passphrase for key:
"Please enter the passphrase for the ssh key"
"Please enter the passphrase to unlock the OpenPGP secret key")
"List of regexps matching password prompts of Git and its subprocesses.
Also see `magit-process-find-password-functions'."
:package-version '(magit . "3.0.0")
:package-version '(magit . "4.3.0")
:group 'magit-process
:type '(repeat (regexp)))
@@ -198,7 +200,7 @@ non-nil, then the password is read from the user instead."
:package-version '(magit . "2.3.0")
:group 'magit-process
:type 'hook
:options '(magit-process-password-auth-source))
:options (list #'magit-process-password-auth-source))
(defcustom magit-process-username-prompt-regexps
'("^Username for '.*': ?$")
@@ -257,6 +259,32 @@ string in the heading of its section."
:group 'magit-process
:type '(choice (const :tag "none" nil) string))
(defvar tramp-pipe-stty-settings)
(defvar magit-tramp-pipe-stty-settings ""
"Override `tramp-pipe-stty-settings' in `magit-start-process'.
The default for that Tramp variable is \"-icanon min 1 time 0\",
which causes staging of individual hunks to hang. Using \"\"
prevents that, but apparently has other issues, which is why it
isn't the default.
This variable defaults to \"\" and is used to override the Tramp
variable in `magit-start-process'. This only has an effect when
using Tramp 2.6.2 or greater. This can also be set to `pty', in
which case a pty is used instead of a pipe. That also prevents
the hanging, but doesn't work for files with DOS line endings
\(see #20).
For connections that have `tramp-direct-async-process' enabled,
staging hunks hangs, unless this variable is set to `pty' (see
#5220).
To fall back to the value of `tramp-pipe-stty-settings', set this
variable to nil.
Also see https://github.com/magit/magit/issues/4720
and https://debbugs.gnu.org/cgi/bugreport.cgi?bug=62093.")
(defface magit-process-ok
'((t :inherit magit-section-heading :foreground "green"))
"Face for zero exit-status."
@@ -284,12 +312,14 @@ Used when `magit-process-display-mode-line-error' is non-nil."
(defvar-keymap magit-process-mode-map
:doc "Keymap for `magit-process-mode'."
:parent magit-mode-map
"<remap> <magit-refresh>" #'undefined
"<remap> <magit-delete-thing>" #'magit-process-kill)
(define-derived-mode magit-process-mode magit-mode "Magit Process"
"Mode for looking at Git process output."
:interactive nil
:group 'magit-process
(hack-dir-local-variables-non-file-buffer)
(magit-hack-dir-local-variables)
(setq magit--imenu-item-types 'process))
(defun magit-process-buffer (&optional nodisplay)
@@ -307,10 +337,10 @@ optional NODISPLAY is non-nil also display it."
(while (not (equal topdir prev))
(setq prev topdir)
(setq topdir (file-name-directory (directory-file-name topdir)))))))
(let ((buffer (or (--first (with-current-buffer it
(and (eq major-mode 'magit-process-mode)
(equal default-directory topdir)))
(buffer-list))
(let ((buffer (or (seq-find (##with-current-buffer %
(and (eq major-mode 'magit-process-mode)
(equal default-directory topdir)))
(buffer-list))
(magit-generate-new-buffer 'magit-process-mode
nil topdir))))
(with-current-buffer buffer
@@ -341,6 +371,27 @@ optional NODISPLAY is non-nil also display it."
(defvar magit-process-raise-error nil)
(defvar magit-process-record-invocations nil)
(defvar magit-process-record-buffer-name " *magit-process-file record*")
(defvar magit-process-record-entry-format "%T %%d $ %%a")
(defun magit-toggle-subprocess-record ()
"Toggle whether subprocess invocations are recorded.
When enabled, all subprocesses started by `magit-process-file' are
logged into the buffer specified by `magit-process-record-buffer-name'
using the format `magit-process-record-entry-format'. This is for
debugging purposes.
This is in addition to and distinct from the default logging done by
default, and additional logging enabled with ~magit-toggle-git-debug~.
For alternatives, see info node `(magit)Debugging Tools'."
(interactive)
(setq magit-process-record-invocations (not magit-process-record-invocations))
(message "Recording of subprocess invocations %s"
(if magit-process-record-invocations "enabled" "disabled")))
(defun magit-git (&rest args)
"Call Git synchronously in a separate process, for side-effects.
@@ -368,13 +419,13 @@ as well as the current repository's status buffer are refreshed.
Process output goes into a new section in the buffer returned by
`magit-process-buffer'."
(let ((magit--refresh-cache (list (cons 0 0))))
(magit-call-git args)
(when (member (car args) '("init" "clone"))
;; Creating a new repository invalidates the cache.
(setq magit--refresh-cache nil))
(magit-refresh)))
(prog1 (magit-call-git args)
(when (member (car args) '("init" "clone"))
;; Creating a new repository invalidates the cache.
(setq magit--refresh-cache nil))
(magit-refresh))))
(defvar magit-pre-call-git-hook nil)
(defvar magit-pre-call-git-hook (list #'magit-maybe-save-repository-buffers))
(defun magit-call-git (&rest args)
"Call Git synchronously in a separate process.
@@ -416,22 +467,24 @@ ensure unix eol conversion."
(defun magit-process-file (process &optional infile buffer display &rest args)
"Process files synchronously in a separate process.
Identical to `process-file' but temporarily enable Cygwin's
\"noglob\" option during the call and ensure unix eol
conversion."
(when magit-process-extreme-logging
(let ((inhibit-message t))
(message "$ %s" (magit-process--format-arguments process args))))
Similar to `process-file' but temporarily enable Cygwin's
\"noglob\" option during the call and ensure unix eol conversion."
(when magit-process-record-invocations
(let ((messages-buffer-name magit-process-record-buffer-name)
(inhibit-message t))
(message "%s"
(format-spec
(format-time-string magit-process-record-entry-format)
`((?d . ,(abbreviate-file-name default-directory))
(?a . ,(magit-process--format-arguments process args)))))))
(let ((process-environment (magit-process-environment))
(default-process-coding-system (magit--process-coding-system)))
(apply #'process-file process infile buffer display args)))
(defun magit-process-environment ()
;; The various w32 hacks are only applicable when running on the
;; local machine. As of Emacs 25.1, a local binding of
;; process-environment different from the top-level value affects
;; the environment used in
;; tramp-sh-handle-{start-file-process,process-file}.
;; The various w32 hacks are only applicable when running on the local
;; machine. A local binding of process-environment different from the
;; top-level value affects the environment used by Tramp.
(let ((local (not (file-remote-p default-directory))))
(append magit-git-environment
(and local
@@ -495,10 +548,10 @@ current when this function was called (if it is a Magit buffer
and still alive), as well as the respective Magit status buffer.
See `magit-start-process' for more information."
(message "Running %s %s" (magit-git-executable)
(let ((m (mapconcat #'identity (flatten-tree args) " ")))
(remove-list-of-text-properties 0 (length m) '(face) m)
m))
(magit-msg "Running %s %s" (magit-git-executable)
(let ((m (string-join (flatten-tree args) " ")))
(remove-list-of-text-properties 0 (length m) '(face) m)
m))
(magit-start-git nil args))
(defun magit-run-git-with-editor (&rest args)
@@ -534,7 +587,7 @@ See `magit-start-process' and `with-editor' for more information."
(set-process-sentinel magit-this-process #'magit-sequencer-process-sentinel)
magit-this-process)
(defvar magit-pre-start-git-hook nil)
(defvar magit-pre-start-git-hook (list #'magit-maybe-save-repository-buffers))
(defun magit-start-git (input &rest args)
"Start Git, prepare for refresh, and return the process object.
@@ -580,10 +633,20 @@ Magit status buffer."
((`(,process-buf . ,section)
(magit-process-setup program args))
(process
(let ((process-connection-type
;; Don't use a pty, because it would set icrnl
;; which would modify the input (issue #20).
(and (not input) magit-process-connection-type))
(let ((process-connection-type ;t=pty nil=pipe
(or
;; With Tramp, maybe force use a pty. #4720
(and (file-remote-p default-directory)
(eq magit-tramp-pipe-stty-settings 'pty))
;; Without input, don't use a pty, because it would
;; set icrnl, which would modify the input. #20
(and (not input) magit-process-connection-type)))
(tramp-pipe-stty-settings
(or (and (not (eq magit-tramp-pipe-stty-settings 'pty))
;; Defaults to "", to allow staging hunks over
;; Tramp again. #4720
magit-tramp-pipe-stty-settings)
(bound-and-true-p tramp-pipe-stty-settings)))
(process-environment (magit-process-environment))
(default-process-coding-system (magit--process-coding-system)))
(apply #'start-file-process
@@ -621,24 +684,32 @@ Magit status buffer."
(defun magit-parse-git-async (&rest args)
(setq args (magit-process-git-arguments args))
(let ((command-buf (current-buffer))
(process-buf (generate-new-buffer " *temp*"))
(stdout-buf (generate-new-buffer " *git-stdout*"))
(stderr-buf (generate-new-buffer " *git-stderr*"))
(toplevel (magit-toplevel)))
(with-current-buffer process-buf
(with-current-buffer stdout-buf
(setq default-directory toplevel)
(let ((process
(let ((process-connection-type nil)
(process-environment (magit-process-environment))
(default-process-coding-system
(magit--process-coding-system)))
(apply #'start-file-process "git" process-buf
(magit-git-executable) args))))
(let ((process-environment (magit-process-environment)))
(make-process :name "git"
:buffer stdout-buf
:stderr stderr-buf
:command (cons (magit-git-executable) args)
:coding (magit--process-coding-system)
:file-handler t))))
(process-put process 'command-buf command-buf)
(process-put process 'stderr-buf stderr-buf)
(process-put process 'parsed (point))
(setq magit-this-process process)
process))))
;;; Process Internals
(defclass magit-process-section (magit-section)
((process :initform nil)))
(setf (alist-get 'process magit--section-type-alist) 'magit-process-section)
(defun magit-process-setup (program args)
(magit-process-set-mode-line program args)
(let ((pwd default-directory)
@@ -647,8 +718,10 @@ Magit status buffer."
(prog1 (magit-process-insert-section pwd program args nil nil)
(backward-char 1))))))
(defun magit-process-insert-section (pwd program args &optional errcode errlog)
(defun magit-process-insert-section
(pwd program args &optional errcode errlog face)
(let ((inhibit-read-only t)
(magit-insert-section--current nil)
(magit-insert-section--parent magit-root-section)
(magit-insert-section--oldroot nil))
(goto-char (1- (point-max)))
@@ -659,11 +732,14 @@ Magit status buffer."
"run "))
(when magit-process-timestamp-format
(insert (format-time-string magit-process-timestamp-format) " "))
(unless (equal (expand-file-name pwd)
(expand-file-name default-directory))
(insert (file-relative-name pwd default-directory) ?\s))
(insert (magit-process--format-arguments program args))
(magit-insert-heading)
(let ((cmd (concat
(and (not (equal
(file-name-as-directory (expand-file-name pwd))
(file-name-as-directory (expand-file-name
default-directory))))
(concat (file-relative-name pwd default-directory) " "))
(magit-process--format-arguments program args))))
(magit-insert-heading (if face (propertize cmd 'face face) cmd)))
(when errlog
(if (bufferp errlog)
(insert (with-current-buffer errlog
@@ -682,7 +758,7 @@ Magit status buffer."
" "
(propertize (magit--ellipsis)
'font-lock-face 'magit-section-heading
'help-echo (mapconcat #'identity (seq-take args global) " "))
'help-echo (string-join (seq-take args global) " "))
" "
(propertize (mapconcat #'shell-quote-argument (seq-drop args global) " ")
'font-lock-face 'magit-section-heading))))
@@ -740,7 +816,7 @@ Magit status buffer."
(when (memq (process-status process) '(exit signal))
(magit-process-sentinel process event)
(when-let* ((process-buf (process-buffer process))
(- (buffer-live-p process-buf))
((buffer-live-p process-buf))
(status-buf (with-current-buffer process-buf
(magit-get-mode-buffer 'magit-status-mode))))
(with-current-buffer status-buf
@@ -764,7 +840,7 @@ Magit status buffer."
;; Find last ^M in string. If one was found, ignore
;; everything before it and delete the current line.
(when-let ((ret-pos (cl-position ?\r string :from-end t)))
(cl-callf substring string (1+ ret-pos))
(setq string (substring string (1+ ret-pos)))
(delete-region (line-beginning-position) (point)))
(setq string (magit-process-remove-bogus-errors string))
(insert (propertize string 'magit-section
@@ -831,10 +907,6 @@ PARENT is used as the parent of the returned keymap."
"Use `auth-source-search' to get a password.
If found, return the password. Otherwise, return nil.
To use this function add it to the appropriate hook
(add-hook \\='magit-process-find-password-functions
\\='magit-process-password-auth-source)
KEY typically derives from a prompt such as:
Password for \\='https://yourname@github.com\\='
in which case it would be the string
@@ -851,7 +923,7 @@ respective documentation.
After manually editing ~/.authinfo.gpg you must reset
the cache using
M-x auth-source-forget-all-cached RET
\\`M-x' `auth-source-forget-all-cached' \\`RET'
The above will save you from having to repeatedly type
your token or password, but you might still repeatedly
@@ -927,8 +999,8 @@ from the user."
(defun magit-process-match-prompt (prompts string)
"Match STRING against PROMPTS and set match data.
Return the matched string suffixed with \": \", if needed."
(when (--any-p (string-match it string) prompts)
Return the matched string, appending \": \" if needed."
(when (seq-some (##string-match % string) prompts)
(let ((prompt (match-string 0 string)))
(cond ((string-suffix-p ": " prompt) prompt)
((string-suffix-p ":" prompt) (concat prompt " "))
@@ -960,12 +1032,13 @@ as argument."
(memq magit-credential-cache-daemon-process
(list-system-processes)))
(setq magit-credential-cache-daemon-process
(or (--first (let* ((attr (process-attributes it))
(comm (cdr (assq 'comm attr)))
(user (cdr (assq 'user attr))))
(and (string= comm "git-credential-cache--daemon")
(string= user user-login-name)))
(list-system-processes))
(or (seq-find (lambda (process)
(let* ((attr (process-attributes process))
(comm (cdr (assq 'comm attr)))
(user (cdr (assq 'user attr))))
(and (string= comm "git-credential-cache--daemon")
(string= user user-login-name))))
(list-system-processes))
(condition-case nil
(start-process "git-credential-cache--daemon"
" *git-credential-cache--daemon*"
@@ -980,32 +1053,9 @@ as argument."
(add-hook 'magit-credential-hook #'magit-maybe-start-credential-cache-daemon)
(defun tramp-sh-handle-start-file-process--magit-tramp-process-environment
(fn name buffer program &rest args)
(if magit-tramp-process-environment
(apply fn name buffer
(car magit-tramp-process-environment)
(append (cdr magit-tramp-process-environment)
(cons program args)))
(apply fn name buffer program args)))
(advice-add 'tramp-sh-handle-start-file-process :around
#'tramp-sh-handle-start-file-process--magit-tramp-process-environment)
(defun tramp-sh-handle-process-file--magit-tramp-process-environment
(fn program &optional infile destination display &rest args)
(if magit-tramp-process-environment
(apply fn "env" infile destination display
(append magit-tramp-process-environment
(cons program args)))
(apply fn program infile destination display args)))
(advice-add 'tramp-sh-handle-process-file :around
#'tramp-sh-handle-process-file--magit-tramp-process-environment)
(defvar-keymap magit-mode-line-process-map
:doc "Keymap for `mode-line-process'."
"<mode-line> <mouse-1>" ''magit-process-buffer)
"<mode-line> <mouse-1>" 'magit-process-buffer)
(defun magit-process-set-mode-line (program args)
"Display the git command (sans arguments) in the mode line."
@@ -1159,31 +1209,7 @@ Limited by `magit-process-error-tooltip-max-lines'."
(dired-uncache default-dir))
(when (buffer-live-p process-buf)
(with-current-buffer process-buf
(let ((inhibit-read-only t)
(marker (oref section start)))
(goto-char marker)
(save-excursion
(delete-char 3)
(set-marker-insertion-type marker nil)
(insert (propertize (format "%3s" arg)
'magit-section section
'font-lock-face (if (= arg 0)
'magit-process-ok
'magit-process-ng)))
(set-marker-insertion-type marker t))
(when magit-process-finish-apply-ansi-colors
(ansi-color-apply-on-region (oref section content)
(oref section end)))
(if (= (oref section end)
(+ (line-end-position) 2))
(save-excursion
(goto-char (1+ (line-end-position)))
(delete-char -1)
(oset section content nil))
(when (and (= arg 0)
(not (--any-p (eq (window-buffer it) process-buf)
(window-list))))
(magit-section-hide section))))))
(magit-process-finish-section section arg)))
(if (= arg 0)
;; Unset the `mode-line-process' value upon success.
(magit-process-unset-mode-line default-dir)
@@ -1216,6 +1242,34 @@ Limited by `magit-process-error-tooltip-max-lines'."
(buffer-name process-buf))))))
arg)
(defun magit-process-finish-section (section exit-code)
(let ((inhibit-read-only t)
(buffer (current-buffer))
(marker (oref section start)))
(goto-char marker)
(save-excursion
(delete-char 3)
(set-marker-insertion-type marker nil)
(insert (propertize (format "%3s" exit-code)
'magit-section section
'font-lock-face (if (= exit-code 0)
'magit-process-ok
'magit-process-ng)))
(set-marker-insertion-type marker t))
(when magit-process-finish-apply-ansi-colors
(ansi-color-apply-on-region (oref section content)
(oref section end)))
(if (= (oref section end)
(+ (line-end-position) 2))
(save-excursion
(goto-char (1+ (line-end-position)))
(delete-char -1)
(oset section content nil))
(when (and (= exit-code 0)
(not (seq-some (##eq (window-buffer %) buffer)
(window-list))))
(magit-section-hide section)))))
(defun magit-process-display-buffer (process)
(when (process-live-p process)
(let ((buf (process-buffer process)))
@@ -1253,7 +1307,7 @@ Limited by `magit-process-error-tooltip-max-lines'."
(let ((inhibit-message t))
(when heading
(setq lines (cons heading lines)))
(message (mapconcat #'identity lines "\n"))))))
(message (string-join lines "\n"))))))
;;; _
(provide 'magit-process)

View File

@@ -1,9 +1,9 @@
;;; magit-pull.el --- Update local objects and refs -*- lexical-binding:t -*-
;; Copyright (C) 2008-2023 The Magit Project Contributors
;; Copyright (C) 2008-2025 The Magit Project Contributors
;; Author: Jonas Bernoulli <jonas@bernoul.li>
;; Maintainer: Jonas Bernoulli <jonas@bernoul.li>
;; Author: Jonas Bernoulli <emacs.magit@jonas.bernoulli.dev>
;; Maintainer: Jonas Bernoulli <emacs.magit@jonas.bernoulli.dev>
;; SPDX-License-Identifier: GPL-3.0-or-later
@@ -47,7 +47,8 @@
(lambda () (if magit-pull-or-fetch "Pull arguments" "Arguments"))
("-f" "Fast-forward only" "--ff-only")
("-r" "Rebase local commits" ("-r" "--rebase"))
("-A" "Autostash" "--autostash" :level 7)]
("-A" "Autostash" "--autostash" :level 7)
("-F" "Force" ("-f" "--force"))]
[:description
(lambda ()
(if-let ((branch (magit-get-current-branch)))

View File

@@ -1,9 +1,9 @@
;;; magit-push.el --- Update remote objects and refs -*- lexical-binding:t -*-
;; Copyright (C) 2008-2023 The Magit Project Contributors
;; Copyright (C) 2008-2025 The Magit Project Contributors
;; Author: Jonas Bernoulli <jonas@bernoul.li>
;; Maintainer: Jonas Bernoulli <jonas@bernoul.li>
;; Author: Jonas Bernoulli <emacs.magit@jonas.bernoulli.dev>
;; Maintainer: Jonas Bernoulli <emacs.magit@jonas.bernoulli.dev>
;; SPDX-License-Identifier: GPL-3.0-or-later
@@ -87,10 +87,8 @@ argument the push-remote can be changed before pushed to it."
(magit--select-push-remote "push there")))
(when changed
(magit-confirm 'set-and-push
(string-replace
"%" "%%"
(format "Really use \"%s\" as push-remote and push \"%s\" there"
remote branch))))
(list "Really use \"%s\" as push-remote and push \"%s\" there"
remote branch)))
(run-hooks 'magit-credential-hook)
(magit-run-git-async "push" "-v" args remote
(format "refs/heads/%s:refs/heads/%s"
@@ -130,8 +128,8 @@ the upstream."
(not (or (magit-get-upstream-branch branch)
(magit--unnamed-upstream-p remote merge)
(magit--valid-upstream-p remote merge))))
(let* ((branches (cl-union (--map (concat it "/" branch)
(magit-list-remotes))
(let* ((branches (cl-union (mapcar (##concat % "/" branch)
(magit-list-remotes))
(magit-list-remote-branch-names)
:test #'equal))
(upstream (magit-completing-read
@@ -150,10 +148,8 @@ the upstream."
;; is what the user wants to happen.
(setq merge (concat "refs/heads/" merge)))
(magit-confirm 'set-and-push
(string-replace
"%" "%%"
(format "Really use \"%s\" as upstream and push \"%s\" there"
upstream branch))))
(list "Really use \"%s\" as upstream and push \"%s\" there"
upstream branch)))
(cl-pushnew "--set-upstream" args :test #'equal))
(run-hooks 'magit-credential-hook)
(magit-run-git-async "push" "-v" args remote (concat branch ":" merge))))
@@ -309,7 +305,7 @@ what this command will do. To add it use something like:
(let ((remotes (magit-list-remotes)))
(cond
((and (magit-git-version>= "2.27")
(= (length remotes) 1))
(length= remotes 1))
(car remotes))
((member "origin" remotes) "origin"))))))
(if (null remote)
@@ -340,9 +336,9 @@ what this command will do. To add it use something like:
((not (string-match "/" ref))
(magit--propertize-face (format "%s/%s" remote ref)
'magit-branch-remote))
(t (format "%s as %s"
(magit--propertize-face remote 'bold)
(magit--propertize-face ref 'bold)))))
((format "%s as %s"
(magit--propertize-face remote 'bold)
(magit--propertize-face ref 'bold)))))
"nothing (no upstream)")))
("matching" (format "all matching to %s"
(magit--propertize-face remote 'bold)))))))))

View File

@@ -1,9 +1,9 @@
;;; magit-reflog.el --- Inspect ref history -*- lexical-binding:t -*-
;; Copyright (C) 2008-2023 The Magit Project Contributors
;; Copyright (C) 2008-2025 The Magit Project Contributors
;; Author: Jonas Bernoulli <jonas@bernoul.li>
;; Maintainer: Jonas Bernoulli <jonas@bernoul.li>
;; Author: Jonas Bernoulli <emacs.magit@jonas.bernoulli.dev>
;; Maintainer: Jonas Bernoulli <emacs.magit@jonas.bernoulli.dev>
;; SPDX-License-Identifier: GPL-3.0-or-later
@@ -150,8 +150,9 @@ Type \\[magit-cherry-pick] to apply the commit at point.
Type \\[magit-reset] to reset `HEAD' to the commit at point.
\\{magit-reflog-mode-map}"
:interactive nil
:group 'magit-log
(hack-dir-local-variables-non-file-buffer)
(magit-hack-dir-local-variables)
(setq magit--imenu-item-types 'commit))
(defun magit-reflog-setup-buffer (ref)
@@ -196,9 +197,7 @@ Type \\[magit-reset] to reset `HEAD' to the commit at point.
command))
(text (if (string= command "commit")
label
(mapconcat #'identity
(delq nil (list command option type))
" "))))
(string-join (delq nil (list command option type)) " "))))
(format "%-16s "
(magit--propertize-face
text (or (cdr (assoc label magit-reflog-labels))

View File

@@ -1,9 +1,9 @@
;;; magit-refs.el --- Listing references -*- lexical-binding:t -*-
;; Copyright (C) 2008-2023 The Magit Project Contributors
;; Copyright (C) 2008-2025 The Magit Project Contributors
;; Author: Jonas Bernoulli <jonas@bernoul.li>
;; Maintainer: Jonas Bernoulli <jonas@bernoul.li>
;; Author: Jonas Bernoulli <emacs.magit@jonas.bernoulli.dev>
;; Maintainer: Jonas Bernoulli <emacs.magit@jonas.bernoulli.dev>
;; SPDX-License-Identifier: GPL-3.0-or-later
@@ -42,11 +42,11 @@
:type 'hook)
(defcustom magit-refs-sections-hook
'(magit-insert-error-header
magit-insert-branch-description
magit-insert-local-branches
magit-insert-remote-branches
magit-insert-tags)
(list #'magit-insert-error-header
#'magit-insert-branch-description
#'magit-insert-local-branches
#'magit-insert-remote-branches
#'magit-insert-tags)
"Hook run to insert sections into a references buffer."
:package-version '(magit . "2.1.0")
:group 'magit-refs
@@ -100,6 +100,15 @@ in the heading preceding the list of its branches."
:group 'magit-refs
:type 'boolean)
(defcustom magit-refs-show-branch-descriptions nil
"Whether to show the description, if any, of local branches.
To distinguish branch descriptions from the commit summary of the tip,
which is shown when there is no description or this option is disabled,
descriptions use the bold face."
:package-version '(magit . "4.3.0")
:group 'magit-refs
:type 'boolean)
(defcustom magit-refs-margin
(list nil
(nth 1 magit-log-margin)
@@ -141,7 +150,7 @@ tags."
:group 'magit-margin
:type 'boolean)
(defcustom magit-refs-primary-column-width (cons 16 32)
(defcustom magit-refs-primary-column-width '(16 . 32)
"Width of the focus column in `magit-refs-mode' buffers.
The primary column is the column that contains the name of the
@@ -238,7 +247,7 @@ the outcome.
and lists of cherry commits relative to the reference at point
instead of relative to the current buffer or `HEAD'.
Instead of adding this symbol, consider pressing \"C-u y o RET\".
Instead of adding this symbol, consider pressing \\`C-u y o RET'.
`create-branch'
@@ -294,8 +303,9 @@ Type \\[magit-cherry-pick] to apply the commit at point.
Type \\[magit-reset] to reset `HEAD' to the commit at point.
\\{magit-refs-mode-map}"
:interactive nil
:group 'magit-refs
(hack-dir-local-variables-non-file-buffer)
(magit-hack-dir-local-variables)
(setq magit--imenu-group-types '(local remote tags)))
(defun magit-refs-setup-buffer (ref args)
@@ -309,7 +319,7 @@ Type \\[magit-reset] to reset `HEAD' to the commit at point.
(setq magit-refs-show-commit-count nil))
(magit-set-header-line-format
(format "%s %s" magit-buffer-upstream
(mapconcat #'identity magit-buffer-arguments " ")))
(string-join magit-buffer-arguments " ")))
(magit-insert-section (branchbuf)
(magit-run-section-hook 'magit-refs-sections-hook))
(add-hook 'kill-buffer-hook #'magit-preserve-section-visibility-cache))
@@ -357,7 +367,7 @@ Type \\[magit-reset] to reset `HEAD' to the commit at point.
(and-let* ((buffer (magit-get-mode-buffer
'magit-refs-mode nil
(eq use-buffer-args 'selected))))
(progn ; work around debbugs#31840
(progn
(setq args (buffer-local-value 'magit-buffer-arguments buffer))
t))))
(t
@@ -406,8 +416,12 @@ Compared with a branch read from the user."
(magit-show-refs-arguments)))
(magit-refs-setup-buffer ref args))
(defun magit-refs-set-show-commit-count ()
(transient-define-suffix magit-refs-set-show-commit-count ()
"Change for which refs the commit count is shown."
:description "Change verbosity"
:key "v"
:transient nil
:if-derived 'magit-refs-mode
(interactive)
(setq-local magit-refs-show-commit-count
(magit-read-char-case "Show commit counts for " nil
@@ -523,7 +537,7 @@ line is inserted at all."
(magit-insert-section (branchdesc branch t)
(magit-insert-heading branch ": " (car desc))
(when (cdr desc)
(insert (mapconcat #'identity (cdr desc) "\n"))
(insert (string-join (cdr desc) "\n"))
(insert "\n\n")))))
(defun magit-insert-tags ()
@@ -531,7 +545,7 @@ line is inserted at all."
(when-let ((tags (magit-git-lines "tag" "--list" "-n" magit-buffer-arguments)))
(let ((_head (magit-rev-parse "HEAD")))
(magit-insert-section (tags)
(magit-insert-heading "Tags:")
(magit-insert-heading (length tags) "Tags")
(dolist (tag tags)
(string-match "^\\([^ \t]+\\)[ \t]+\\([^ \t\n].*\\)?" tag)
(let ((tag (match-string 1 tag))
@@ -547,7 +561,7 @@ line is inserted at all."
magit-refs-primary-column-width)
(length tag)))
?\s)
(and msg (magit-log-propertize-keywords nil msg)))
(and msg (magit-log--wash-summary msg)))
(when (and magit-refs-margin-for-tags (magit-buffer-margin-p))
(magit-refs--format-margin tag))
(magit-refs--insert-cherry-commits tag)))))
@@ -574,14 +588,18 @@ line is inserted at all."
(cl-substitute nil ""
(split-string line "\0")
:test #'equal)))
(if head-branch
;; Note: Use `ref' instead of `branch' for the check
;; below because 'refname:short' shortens the remote
;; HEAD to '<remote>' instead of '<remote>/HEAD' as of
;; Git v2.40.0.
(progn (cl-assert
(equal ref (concat "refs/remotes/" remote "/HEAD")))
(setq head head-branch))
(cond
(head-branch
;; Note: Use `ref' instead of `branch' for the check
;; below because 'refname:short' shortens the remote
;; HEAD to '<remote>' instead of '<remote>/HEAD' as of
;; Git v2.40.0.
(cl-assert
(equal ref (concat "refs/remotes/" remote "/HEAD")))
(setq head head-branch))
((not (equal ref (concat "refs/remotes/" remote "/HEAD")))
;; ^ Skip mis-configured remotes where HEAD is not a
;; symref. See #5092.
(when (magit-refs--insert-refname-p branch)
(magit-insert-section (branch branch t)
(let ((headp (equal branch head))
@@ -598,23 +616,23 @@ line is inserted at all."
magit-refs-primary-column-width)
(length abbrev)))
?\s)
(and msg (magit-log-propertize-keywords nil msg))))
(and msg (magit-log--wash-summary msg))))
(when (magit-buffer-margin-p)
(magit-refs--format-margin branch))
(magit-refs--insert-cherry-commits branch)))))))
(magit-refs--insert-cherry-commits branch))))))))
(insert ?\n)
(magit-make-margin-overlay nil t))))
(defun magit-insert-local-branches ()
"Insert sections showing all local branches."
(magit-insert-section (local nil)
(magit-insert-heading "Branches:")
(magit-insert-heading t "Branches")
(dolist (line (magit-refs--format-local-branches))
(pcase-let ((`(,branch . ,strings) line))
(magit-insert-section
((eval (if branch 'branch 'commit))
(or branch (magit-rev-parse "HEAD"))
t)
((eval (if branch 'branch 'commit))
(or branch (magit-rev-parse "HEAD"))
t)
(apply #'magit-insert-heading strings)
(when (magit-buffer-margin-p)
(magit-refs--format-margin branch))
@@ -631,7 +649,7 @@ line is inserted at all."
%(upstream:short)%00%(upstream)%00%(upstream:track)%00"
(if magit-refs-show-push-remote "\
%(push:remotename)%00%(push)%00%(push:track)%00%(subject)"
"%00%00%00%(subject)"))
"%00%00%00%(subject)"))
"refs/heads"
magit-buffer-arguments))))
(unless (magit-get-current-branch)
@@ -644,8 +662,9 @@ line is inserted at all."
def
(pcase-let ((`(,min . ,max) def))
(min max (apply #'max min (mapcar #'car lines)))))))
(mapcar (pcase-lambda (`(,_ ,branch ,focus ,branch-desc ,u:ahead ,p:ahead
,u:behind ,upstream ,p:behind ,push ,msg))
(mapcar (pcase-lambda (`( ,_ ,branch ,focus
,branch-desc ,u:ahead ,p:ahead
,u:behind ,upstream ,p:behind ,msg))
(list branch focus branch-desc u:ahead p:ahead
(make-string (max 1 (- magit-refs-primary-column-width
(length (concat branch-desc
@@ -653,8 +672,7 @@ line is inserted at all."
p:ahead
u:behind))))
?\s)
u:behind upstream p:behind push
msg))
u:behind upstream p:behind msg))
lines)))
(defun magit-refs--format-local-branch (line)
@@ -668,7 +686,7 @@ line is inserted at all."
magit-refs-show-push-remote
(magit-rev-verify p:ref)
(not (equal p:ref u:ref))))
(branch-desc
(branch-pretty
(if branch
(magit-refs--propertize-branch
branch ref (and headp 'magit-branch-current))
@@ -701,10 +719,11 @@ line is inserted at all."
(match-string 1 p:track)
(and magit-refs-pad-commit-counts " "))
'magit-dimmed))))
(list (1+ (length (concat branch-desc u:ahead p:ahead u:behind)))
(list (1+ (length (concat branch-pretty u:ahead p:ahead u:behind)))
branch
(magit-refs--format-focus-column branch headp)
branch-desc u:ahead p:ahead u:behind
branch-pretty u:ahead p:ahead
u:behind
(and upstream
(concat (if (equal u:track "[gone]")
(magit--propertize-face upstream 'error)
@@ -715,7 +734,10 @@ line is inserted at all."
(magit--propertize-face
push 'magit-branch-remote)
" "))
(and msg (magit-log-propertize-keywords nil msg)))))))
(if-let ((magit-refs-show-branch-descriptions)
(desc (magit-get "branch" branch "description")))
(magit--propertize-face desc 'bold)
(and msg (magit-log--wash-summary msg))))))))
(defun magit-refs--format-focus-column (ref &optional type)
(let ((focus magit-buffer-upstream)

View File

@@ -1,9 +1,9 @@
;;; magit-remote.el --- Transfer Git commits -*- lexical-binding:t -*-
;; Copyright (C) 2008-2023 The Magit Project Contributors
;; Copyright (C) 2008-2025 The Magit Project Contributors
;; Author: Jonas Bernoulli <jonas@bernoul.li>
;; Maintainer: Jonas Bernoulli <jonas@bernoul.li>
;; Author: Jonas Bernoulli <emacs.magit@jonas.bernoulli.dev>
;; Maintainer: Jonas Bernoulli <emacs.magit@jonas.bernoulli.dev>
;; SPDX-License-Identifier: GPL-3.0-or-later
@@ -68,9 +68,7 @@ has to be used to view and change remote related variables."
:man-page "git-remote"
:value '("-f")
["Variables"
:if (lambda ()
(and magit-remote-direct-configure
(oref transient--prefix scope)))
:if (lambda () (and magit-remote-direct-configure (transient-scope)))
("u" magit-remote.<remote>.url)
("U" magit-remote.<remote>.fetch)
("s" magit-remote.<remote>.pushurl)
@@ -85,8 +83,8 @@ has to be used to view and change remote related variables."
[("C" "Configure..." magit-remote-configure)
("p" "Prune stale branches" magit-remote-prune)
("P" "Prune stale refspecs" magit-remote-prune-refspecs)
("b" magit-update-default-branch)
(7 "z" "Unshallow remote" magit-remote-unshallow)]]
(7 "z" "Unshallow remote" magit-remote-unshallow)]
[("d u" magit-update-default-branch)]]
(interactive (list (magit-get-current-remote)))
(transient-setup 'magit-remote nil nil :scope remote))
@@ -181,12 +179,13 @@ the now stale refspecs. Other stale branches are not removed."
(ours (match-string 3 refspec)))
(unless (if (string-match "\\*" theirs)
(let ((re (replace-match ".*" t t theirs)))
(--some (string-match-p re it) remote-refs))
(seq-some (##string-match-p re %) remote-refs))
(member theirs remote-refs))
(push (cons refspec
(if (string-match "\\*" ours)
(let ((re (replace-match ".*" t t ours)))
(--filter (string-match-p re it) tracking-refs))
(seq-filter (##string-match-p re %)
tracking-refs))
(list (car (member ours tracking-refs)))))
stale)))))
(if (not stale)
@@ -201,18 +200,18 @@ the now stale refspecs. Other stale branches are not removed."
variable))
(?r "[r]emove remote"
(magit-call-git "remote" "rm" remote))
(?a "or [a]abort"
(?a "[a]abort"
(user-error "Abort")))
(if (if (length= stale 1)
(pcase-let ((`(,refspec . ,refs) (car stale)))
(magit-confirm 'prune-stale-refspecs
(format "Prune stale refspec %s and branch %%s" refspec)
(format "Prune stale refspec %s and %%d branches" refspec)
(list "Prune stale refspec %s and branch %%s" refspec)
(list "Prune stale refspec %s and %%d branches" refspec)
nil refs))
(magit-confirm 'prune-stale-refspecs nil
(format "Prune %%d stale refspecs and %d branches"
(length (cl-mapcan (lambda (s) (copy-sequence (cdr s)))
stale)))
(length (mapcan (lambda (s) (copy-sequence (cdr s)))
stale)))
nil
(mapcar (pcase-lambda (`(,refspec . ,refs))
(concat refspec "\n"
@@ -282,8 +281,8 @@ Delete the symbolic-ref \"refs/remotes/<remote>/HEAD\"."
newname)
newname "master")))
(cond
((y-or-n-p (format "Default branch changed from `%s' to `%s' on %s.%s"
oldname newname remote " Do the same locally? "))
((y-or-n-p (format "Default branch changed from `%s' to `%s' on %s.%s?"
oldname newname remote " Do the same locally"))
(magit--set-default-branch newname oldname)
(magit-refresh))
((user-error "Abort")))))))
@@ -314,9 +313,8 @@ refspec."
:man-page "git-remote"
[:description
(lambda ()
(concat
(propertize "Configure " 'face 'transient-heading)
(propertize (oref transient--prefix scope) 'face 'magit-branch-remote)))
(concat (propertize "Configure " 'face 'transient-heading)
(propertize (transient-scope) 'face 'magit-branch-remote)))
("u" magit-remote.<remote>.url)
("U" magit-remote.<remote>.fetch)
("s" magit-remote.<remote>.pushurl)

View File

@@ -1,9 +1,9 @@
;;; magit-repos.el --- Listing repositories -*- lexical-binding:t -*-
;; Copyright (C) 2008-2023 The Magit Project Contributors
;; Copyright (C) 2008-2025 The Magit Project Contributors
;; Author: Jonas Bernoulli <jonas@bernoul.li>
;; Maintainer: Jonas Bernoulli <jonas@bernoul.li>
;; Author: Jonas Bernoulli <emacs.magit@jonas.bernoulli.dev>
;; Maintainer: Jonas Bernoulli <emacs.magit@jonas.bernoulli.dev>
;; SPDX-License-Identifier: GPL-3.0-or-later
@@ -56,27 +56,29 @@ This option controls which repositories are being listed by
:link '(info-link "(magit)Repository List")
:group 'magit-modes)
(defcustom magit-repolist-mode-hook '(hl-line-mode)
(defcustom magit-repolist-mode-hook (list #'hl-line-mode)
"Hook run after entering Magit-Repolist mode."
:package-version '(magit . "2.9.0")
:group 'magit-repolist
:type 'hook
:get #'magit-hook-custom-get
:options '(hl-line-mode))
:options (list #'hl-line-mode))
(defcustom magit-repolist-columns
'(("Name" 25 magit-repolist-column-ident nil)
("Version" 25 magit-repolist-column-version
`(("Name" 25 ,#'magit-repolist-column-ident
())
("Version" 25 ,#'magit-repolist-column-version
((:sort magit-repolist-version<)))
("B<U" 3 magit-repolist-column-unpulled-from-upstream
("B<U" 3 ,#'magit-repolist-column-unpulled-from-upstream
(;; (:help-echo "Upstream changes not in branch")
(:right-align t)
(:sort <)))
("B>U" 3 magit-repolist-column-unpushed-to-upstream
("B>U" 3 ,#'magit-repolist-column-unpushed-to-upstream
(;; (:help-echo "Local changes not in upstream")
(:right-align t)
(:sort <)))
("Path" 99 magit-repolist-column-path nil))
("Path" 99 ,#'magit-repolist-column-path
()))
"List of columns displayed by `magit-list-repositories'.
Each element has the form (HEADER WIDTH FORMAT PROPS).
@@ -116,9 +118,9 @@ than 9."
(sexp :tag "Value"))))))
(defcustom magit-repolist-column-flag-alist
'((magit-untracked-files . "N")
(magit-unstaged-files . "U")
(magit-staged-files . "S"))
`((,#'magit-untracked-files . "N")
(,#'magit-unstaged-files . "U")
(,#'magit-staged-files . "S"))
"Association list of predicates and flags for `magit-repolist-column-flag'.
Each element is of the form (FUNCTION . FLAG). Each FUNCTION is
@@ -178,14 +180,14 @@ repositories are displayed."
"Fetch all marked or listed repositories."
(interactive (list (magit-repolist--get-repos ?*)))
(run-hooks 'magit-credential-hook)
(magit-repolist--mapc (apply-partially #'magit-run-git "remote" "update")
(magit-repolist--mapc (##magit-run-git "remote" "update")
repos "Fetching in %s..."))
(defun magit-repolist-find-file-other-frame (repos file)
"Find a file in all marked or listed repositories."
(interactive (list (magit-repolist--get-repos ?*)
(read-string "Find file in repositories: ")))
(magit-repolist--mapc (apply-partially #'find-file-other-frame file) repos))
(magit-repolist--mapc (##find-file-other-frame file) repos))
(defun magit-repolist--ensure-padding ()
"Set `tabulated-list-padding' to 2, unless that is already non-zero."
@@ -240,17 +242,16 @@ If it contains \"%s\" then the directory is substituted for that."
(let ((base default-directory)
(len (length repos))
(i 0))
(mapc (lambda (repo)
(let ((default-directory
(file-name-as-directory (expand-file-name repo base))))
(if msg
(let ((msg (concat (format "(%s/%s) " (cl-incf i) len)
(format msg default-directory))))
(message msg)
(funcall fn)
(message (concat msg "done")))
(funcall fn))))
repos)))
(dolist (repo repos)
(let ((default-directory
(file-name-as-directory (expand-file-name repo base))))
(if msg
(let ((msg (concat (format "(%s/%s) " (cl-incf i) len)
(format msg default-directory))))
(message msg)
(funcall fn)
(message (concat msg "done")))
(funcall fn))))))
;;;; Mode
@@ -265,9 +266,11 @@ If it contains \"%s\" then the directory is substituted for that."
(define-derived-mode magit-repolist-mode tabulated-list-mode "Repos"
"Major mode for browsing a list of Git repositories."
:interactive nil
:group 'magit-repolist
(setq-local x-stretch-cursor nil)
(setq tabulated-list-padding 0)
(add-hook 'tabulated-list-revert-hook #'magit-repolist-refresh nil t)
(setq-local tabulated-list-revert-hook (list #'magit-repolist-refresh t))
(setq imenu-prev-index-position-function
#'magit-repolist--imenu-prev-index-position)
(setq imenu-extract-index-name-function #'tabulated-list-get-id))
@@ -323,9 +326,9 @@ If it contains \"%s\" then the directory is substituted for that."
""))
magit-repolist-columns)))))
(magit-list-repos-uniquify
(--map (cons (file-name-nondirectory (directory-file-name it))
it)
(magit-list-repos)))))
(mapcar (##cons (file-name-nondirectory (directory-file-name %))
%)
(magit-list-repos)))))
(message "Listing repositories...")
(tabulated-list-init-header)
(tabulated-list-print t)
@@ -379,7 +382,8 @@ Usually this is just its basename."
(when (match-end 2)
(magit--put-face (match-beginning 2) (match-end 2) 'bold v))
(when (match-end 4)
(magit--put-face (match-beginning 4) (match-end 4) 'error v))
(magit--put-face (or (match-beginning 3) (match-beginning 4))
(match-end 4) 'error v))
(when (and (equal (match-string 2 v) "1")
(string-match-p magit-repolist-column-version-resume-regexp
(magit-rev-format "%s")))
@@ -497,18 +501,18 @@ instead."
(or (magit-toplevel) default-directory)))))
(defun magit-list-repos ()
(cl-mapcan (pcase-lambda (`(,dir . ,depth))
(magit-list-repos-1 dir depth))
magit-repository-directories))
(mapcan (pcase-lambda (`(,dir . ,depth))
(magit-list-repos-1 dir depth))
magit-repository-directories))
(defun magit-list-repos-1 (directory depth)
(cond ((file-readable-p (expand-file-name ".git" directory))
(list (file-name-as-directory directory)))
((and (> depth 0) (magit-file-accessible-directory-p directory))
(--mapcat (and (file-directory-p it)
(magit-list-repos-1 it (1- depth)))
(directory-files directory t
directory-files-no-dot-files-regexp t)))))
((and (> depth 0) (file-accessible-directory-p directory))
(mapcan (##and (file-directory-p %)
(magit-list-repos-1 % (1- depth)))
(directory-files directory t
directory-files-no-dot-files-regexp t)))))
(defun magit-list-repos-uniquify (alist)
(let (result (dict (make-hash-table :test #'equal)))
@@ -519,22 +523,24 @@ instead."
(if (length= value 1)
(push (cons key (car value)) result)
(setq result
(append result
(magit-list-repos-uniquify
(--map (cons (concat
key "\\"
(file-name-nondirectory
(directory-file-name
(substring it 0 (- (1+ (length key)))))))
it)
value))))))
(append
result
(magit-list-repos-uniquify
(mapcar (lambda (v)
(cons (concat
key "\\"
(file-name-nondirectory
(directory-file-name
(substring v 0 (- (1+ (length key)))))))
v))
value))))))
dict)
result))
(defun magit-repos-alist ()
(magit-list-repos-uniquify
(--map (cons (file-name-nondirectory (directory-file-name it)) it)
(magit-list-repos))))
(mapcar (##cons (file-name-nondirectory (directory-file-name %)) %)
(magit-list-repos))))
;;; _
(provide 'magit-repos)

View File

@@ -1,9 +1,9 @@
;;; magit-reset.el --- Reset functionality -*- lexical-binding:t -*-
;; Copyright (C) 2008-2023 The Magit Project Contributors
;; Copyright (C) 2008-2025 The Magit Project Contributors
;; Author: Jonas Bernoulli <jonas@bernoul.li>
;; Maintainer: Jonas Bernoulli <jonas@bernoul.li>
;; Author: Jonas Bernoulli <emacs.magit@jonas.bernoulli.dev>
;; Maintainer: Jonas Bernoulli <emacs.magit@jonas.bernoulli.dev>
;; SPDX-License-Identifier: GPL-3.0-or-later

View File

@@ -1,9 +1,9 @@
;;; magit-sequence.el --- History manipulation in Magit -*- lexical-binding:t -*-
;; Copyright (C) 2008-2023 The Magit Project Contributors
;; Copyright (C) 2008-2025 The Magit Project Contributors
;; Author: Jonas Bernoulli <jonas@bernoul.li>
;; Maintainer: Jonas Bernoulli <jonas@bernoul.li>
;; Author: Jonas Bernoulli <emacs.magit@jonas.bernoulli.dev>
;; Maintainer: Jonas Bernoulli <emacs.magit@jonas.bernoulli.dev>
;; SPDX-License-Identifier: GPL-3.0-or-later
@@ -91,31 +91,36 @@
(defun magit-sequencer-continue ()
"Resume the current cherry-pick or revert sequence."
(interactive)
(if (magit-sequencer-in-progress-p)
(if (magit-anything-unmerged-p)
(user-error "Cannot continue due to unresolved conflicts")
(magit-run-git-sequencer
(if (magit-revert-in-progress-p) "revert" "cherry-pick") "--continue"))
(user-error "No cherry-pick or revert in progress")))
(cond
((not (magit-sequencer-in-progress-p))
(user-error "No cherry-pick or revert in progress"))
((magit-anything-unmerged-p)
(user-error "Cannot continue due to unresolved conflicts"))
((magit-run-git-sequencer
(if (magit-revert-in-progress-p) "revert" "cherry-pick") "--continue"))))
;;;###autoload
(defun magit-sequencer-skip ()
"Skip the stopped at commit during a cherry-pick or revert sequence."
(interactive)
(if (magit-sequencer-in-progress-p)
(progn (magit-call-git "reset" "--hard")
(magit-sequencer-continue))
(user-error "No cherry-pick or revert in progress")))
(unless (magit-sequencer-in-progress-p)
(user-error "No cherry-pick or revert in progress"))
(magit-call-git "reset" "--hard")
(magit-sequencer-continue))
;;;###autoload
(defun magit-sequencer-abort ()
"Abort the current cherry-pick or revert sequence.
This discards all changes made since the sequence started."
(interactive)
(if (magit-sequencer-in-progress-p)
(magit-run-git-sequencer
(if (magit-revert-in-progress-p) "revert" "cherry-pick") "--abort")
(user-error "No cherry-pick or revert in progress")))
(cond
((not (magit-sequencer-in-progress-p))
(user-error "No cherry-pick or revert in progress"))
((magit-revert-in-progress-p)
(magit-confirm 'abort-revert "Really abort revert")
(magit-run-git-sequencer "revert" "--abort"))
((magit-confirm 'abort-cherry-pick "Really abort cherry-pick")
(magit-run-git-sequencer "cherry-pick" "--abort"))))
(defun magit-sequencer-in-progress-p ()
(or (magit-cherry-pick-in-progress-p)
@@ -139,8 +144,8 @@ This discards all changes made since the sequence started."
("-F" "Attempt fast-forward" "--ff")
("-x" "Reference cherry in commit message" "-x")
("-e" "Edit commit messages" ("-e" "--edit"))
("-s" "Add Signed-off-by lines" ("-s" "--signoff"))
(5 magit:--gpg-sign)]
(magit:--gpg-sign)
(magit:--signoff)]
[:if-not magit-sequencer-in-progress-p
["Apply here"
("A" "Pick" magit-cherry-copy)
@@ -338,11 +343,11 @@ the process manually."
(let ((merges (seq-filter #'magit-merge-commit-p commits)))
(cond
((not merges)
(--remove (string-prefix-p "--mainline=" it) args))
(seq-remove (##string-prefix-p "--mainline=" %) args))
((cl-set-difference commits merges :test #'equal)
(user-error "Cannot %s merge and non-merge commits at once"
command))
((--first (string-prefix-p "--mainline=" it) args)
((seq-find (##string-prefix-p "--mainline=" %) args)
args)
(t
(cons (format "--mainline=%s"
@@ -373,8 +378,8 @@ the process manually."
("-e" "Edit commit message" ("-e" "--edit"))
("-E" "Don't edit commit message" "--no-edit")
("=s" magit-merge:--strategy)
("-s" "Add Signed-off-by lines" ("-s" "--signoff"))
(5 magit:--gpg-sign)]
(magit:--gpg-sign)
(magit:--signoff)]
["Actions"
:if-not magit-sequencer-in-progress-p
("V" "Revert commit(s)" magit-revert-and-commit)
@@ -434,8 +439,8 @@ without prompting."
("-b" "Limit removal of email cruft" "--keep-non-patch")
("-d" "Use author date as committer date" "--committer-date-is-author-date")
("-t" "Use current time as author date" "--ignore-date")
("-s" "Add Signed-off-by lines" ("-s" "--signoff"))
(5 magit:--gpg-sign)]
(magit:--gpg-sign)
(magit:--signoff)]
["Apply"
:if-not magit-am-in-progress-p
("m" "maildir" magit-am-apply-maildir)
@@ -469,9 +474,9 @@ without prompting."
nil default))))
(magit-am-arguments)))
(magit-run-git-sequencer "am" args "--"
(--map (magit-convert-filename-for-git
(expand-file-name it))
files)))
(mapcar (##magit-convert-filename-for-git
(expand-file-name %))
files)))
;;;###autoload
(defun magit-am-apply-maildir (&optional maildir args)
@@ -485,28 +490,29 @@ without prompting."
(defun magit-am-continue ()
"Resume the current patch applying sequence."
(interactive)
(if (magit-am-in-progress-p)
(if (magit-anything-unstaged-p t)
(error "Cannot continue due to unstaged changes")
(magit-run-git-sequencer "am" "--continue"))
(user-error "Not applying any patches")))
(cond
((not (magit-am-in-progress-p))
(user-error "Not applying any patches"))
((magit-anything-unstaged-p t)
(user-error "Cannot continue due to unstaged changes"))
((magit-run-git-sequencer "am" "--continue"))))
;;;###autoload
(defun magit-am-skip ()
"Skip the stopped at patch during a patch applying sequence."
(interactive)
(if (magit-am-in-progress-p)
(magit-run-git-sequencer "am" "--skip")
(user-error "Not applying any patches")))
(unless (magit-am-in-progress-p)
(user-error "Not applying any patches"))
(magit-run-git-sequencer "am" "--skip"))
;;;###autoload
(defun magit-am-abort ()
"Abort the current patch applying sequence.
This discards all changes made since the sequence started."
(interactive)
(if (magit-am-in-progress-p)
(magit-run-git "am" "--abort")
(user-error "Not applying any patches")))
(unless (magit-am-in-progress-p)
(user-error "Not applying any patches"))
(magit-run-git "am" "--abort"))
(defun magit-am-in-progress-p ()
(file-exists-p (expand-file-name "rebase-apply/applying" (magit-gitdir))))
@@ -524,8 +530,7 @@ This discards all changes made since the sequence started."
("-p" "Preserve merges" ("-p" "--preserve-merges")
:if (lambda () (magit-git-version< "2.33.0")))
("-r" "Rebase merges" ("-r" "--rebase-merges=")
magit-rebase-merges-select-mode
:if (lambda () (magit-git-version>= "2.18.0")))
magit-rebase-merges-select-mode)
("-u" "Update branches" "--update-refs"
:if (lambda () (magit-git-version>= "2.38.0")))
(7 magit-merge:--strategy)
@@ -539,7 +544,8 @@ This discards all changes made since the sequence started."
("-i" "Interactive" ("-i" "--interactive"))
("-h" "Disable hooks" "--no-verify")
(7 magit-rebase:--exec)
(5 magit:--gpg-sign)]
(magit:--gpg-sign)
(magit:--signoff)]
[:if-not magit-rebase-in-progress-p
:description (lambda ()
(format (propertize "Rebase %s onto" 'face 'transient-heading)
@@ -683,7 +689,7 @@ START has to be selected from a list of recent commits."
(when (and commit (not noassert))
(setq commit (magit-rebase-interactive-assert
commit delay-edit-confirm
(--some (string-prefix-p "--rebase-merges" it) args))))
(seq-some (##string-prefix-p "--rebase-merges" %) args))))
(if (and commit (not confirm))
(let ((process-environment process-environment))
(when editor
@@ -720,7 +726,7 @@ START has to be selected from a list of recent commits."
;; merely to add new commits *after* it. Try not to
;; ask users whether they really want to edit public
;; commits, when they don't actually intend to do so.
(not (--all-p (magit-rev-equal it commit) branches))))
(not (seq-every-p (##magit-rev-equal % commit) branches))))
(let ((m1 "Some of these commits have already been published to ")
(m2 ".\nDo you really want to modify them"))
(magit-confirm (or magit--rebase-published-symbol 'rebase-published)
@@ -805,7 +811,7 @@ START has to be selected from a list of recent commits."
(edit "edit")
(remove "noop\n# pick")
(reword "reword")
(t (error "unknown action: %s" action)))
(t (error "Unknown action: %s" action)))
commit)))
;;;###autoload
@@ -1013,7 +1019,7 @@ status buffer (i.e., the reverse of how they will be applied)."
(propertize "merge" 'font-lock-face 'magit-sequence-pick)
"\s"
(magit-format-rev-summary hash) "\n"))
(error "failed to parse merge message hash"))))))
(error "Failed to parse merge message hash"))))))
(let ((dir (magit-gitdir)))
(magit-sequence-insert-sequence
(magit-file-line (expand-file-name "rebase-merge/stopped-sha" dir))
@@ -1025,9 +1031,9 @@ status buffer (i.e., the reverse of how they will be applied)."
(defun magit-rebase-insert-apply-sequence (onto)
(let* ((dir (magit-gitdir))
(rewritten
(--map (car (split-string it))
(magit-file-lines
(expand-file-name "rebase-apply/rewritten" dir))))
(mapcar (##car (split-string %))
(magit-file-lines
(expand-file-name "rebase-apply/rewritten" dir))))
(stop (magit-file-line
(expand-file-name "rebase-apply/original-commit" dir))))
(dolist (patch (nreverse (cdr (magit-rebase-patches))))
@@ -1049,10 +1055,10 @@ status buffer (i.e., the reverse of how they will be applied)."
(setq done (magit-git-lines "log" "--format=%H" (concat onto "..HEAD")))
(when (and stop (not (member (magit-rev-parse stop) done)))
(let ((id (magit-patch-id stop)))
(if-let ((matched (--first (equal (magit-patch-id it) id) done)))
(if-let ((matched (seq-find (##equal (magit-patch-id %) id) done)))
(setq stop matched)
(cond
((--first (magit-rev-equal it stop) done)
((seq-find (##magit-rev-equal % stop) done)
;; The commit's testament has been executed.
(magit-sequence-insert-commit "void" stop 'magit-sequence-drop))
;; The faith of the commit is still undecided...
@@ -1078,7 +1084,7 @@ status buffer (i.e., the reverse of how they will be applied)."
(t "work")))
stop 'magit-sequence-part))
;; The commit is definitely gone...
((--first (magit-rev-equal it stop) done)
((seq-find (##magit-rev-equal % stop) done)
;; ...but all of its changes are still in effect.
(magit-sequence-insert-commit "poof" stop 'magit-sequence-drop))
(t

View File

@@ -1,9 +1,9 @@
;;; magit-sparse-checkout.el --- Sparse checkout support for Magit -*- lexical-binding:t -*-
;; Copyright (C) 2008-2023 The Magit Project Contributors
;; Copyright (C) 2008-2025 The Magit Project Contributors
;; Author: Kyle Meyer <kyle@kyleam.com>
;; Maintainer: Jonas Bernoulli <jonas@bernoul.li>
;; Maintainer: Jonas Bernoulli <emacs.magit@jonas.bernoulli.dev>
;; SPDX-License-Identifier: GPL-3.0-or-later
@@ -43,13 +43,6 @@
"Return non-nil if working tree is a sparse checkout."
(magit-get-boolean "core.sparsecheckout"))
(defun magit-sparse-checkout--assert-version ()
;; Older versions of Git have the ability to define sparse checkout
;; patterns in .git/info/sparse-checkout, but the sparse-checkout
;; command isn't available until 2.25.0.
(when (magit-git-version< "2.25.0")
(user-error "`git sparse-checkout' not available until Git v2.25")))
(defun magit-sparse-checkout--auto-enable ()
(if (magit-sparse-checkout-enabled-p)
(unless (magit-get-boolean "core.sparsecheckoutcone")
@@ -89,7 +82,6 @@ See the `git sparse-checkout' manpage for details about
(defun magit-sparse-checkout-enable (&optional args)
"Convert the working tree to a sparse checkout."
(interactive (list (transient-args 'magit-sparse-checkout)))
(magit-sparse-checkout--assert-version)
(magit-run-git-async "sparse-checkout" "init" "--cone" args))
;;;###autoload
@@ -104,7 +96,6 @@ directories, call `magit-sparse-checkout-add' instead."
;; dealing with very large trees, listing all subdirectories
;; may need to be reconsidered.
(magit-revision-directories "HEAD"))))
(magit-sparse-checkout--assert-version)
(magit-sparse-checkout--auto-enable)
(magit-run-git-async "sparse-checkout" "set" directories))
@@ -124,7 +115,6 @@ directories, call `magit-sparse-checkout-set' instead."
(regexp-opt (magit-sparse-checkout-directories)))))
(lambda (d) (string-match-p re d)))
(magit-revision-directories "HEAD")))))
(magit-sparse-checkout--assert-version)
(magit-sparse-checkout--auto-enable)
(magit-run-git-async "sparse-checkout" "add" directories))
@@ -135,7 +125,6 @@ Some operations such as merging or rebasing may need to check out
files that aren't included in the sparse checkout. Call this
command to reset to the sparse checkout state."
(interactive)
(magit-sparse-checkout--assert-version)
(magit-run-git-async "sparse-checkout" "reapply"))
;;;###autoload
@@ -145,7 +134,6 @@ Note that disabling the sparse checkout does not clear the
configured directories. Call `magit-sparse-checkout-enable' to
restore the previous sparse checkout."
(interactive)
(magit-sparse-checkout--assert-version)
(magit-run-git-async "sparse-checkout" "disable"))
;;; Miscellaneous

View File

@@ -1,9 +1,9 @@
;;; magit-stash.el --- Stash support for Magit -*- lexical-binding:t -*-
;; Copyright (C) 2008-2023 The Magit Project Contributors
;; Copyright (C) 2008-2025 The Magit Project Contributors
;; Author: Jonas Bernoulli <jonas@bernoul.li>
;; Maintainer: Jonas Bernoulli <jonas@bernoul.li>
;; Author: Jonas Bernoulli <emacs.magit@jonas.bernoulli.dev>
;; Maintainer: Jonas Bernoulli <emacs.magit@jonas.bernoulli.dev>
;; SPDX-License-Identifier: GPL-3.0-or-later
@@ -39,10 +39,10 @@
;;;; Diff options
(defcustom magit-stash-sections-hook
'(magit-insert-stash-notes
magit-insert-stash-worktree
magit-insert-stash-index
magit-insert-stash-untracked)
(list #'magit-insert-stash-notes
#'magit-insert-stash-worktree
#'magit-insert-stash-index
#'magit-insert-stash-untracked)
"Hook run to insert sections into stash diff buffers."
:package-version '(magit . "2.1.0")
:group 'magit-stash
@@ -129,8 +129,8 @@ while two prefix arguments are equivalent to `--all'."
(interactive
(progn (when (and (magit-merge-in-progress-p)
(not (magit-y-or-n-p "\
Stashing and resetting during a merge conflict. \
Applying the resulting stash won't restore the merge state. \
Stashing and resetting during a merge conflict. \
Applying the resulting stash won't restore the merge state. \
Proceed anyway? ")))
(user-error "Abort"))
(magit-stash-read-args)))
@@ -143,7 +143,7 @@ Unstaged and untracked changes are not stashed. The stashed
changes are applied in reverse to both the index and the
worktree. This command can fail when the worktree is not clean.
Applying the resulting stash has the inverse effect."
(interactive (list (magit-stash-read-message)))
(interactive (list (funcall magit-stash-read-message-function)))
(magit-stash-save message t nil nil t 'worktree))
;;;###autoload
@@ -256,27 +256,30 @@ specifying a list of files to be stashed."
(list t)))
(if transient
(transient-setup 'magit-stash-push)
(magit-run-git "stash" "push" args)))
(magit-run-git "stash" "push"
(seq-filter #'atom args)
(assoc "--" args))))
;;;###autoload
(defun magit-stash-apply (stash)
"Apply a stash to the working tree.
First try \"git stash apply --index\", which tries to preserve
the index stored in the stash, if any. This may fail because
applying the stash could result in conflicts and those have to
be stored in the index, making it impossible to also store the
stash's index there as well.
When using a Git release before v2.38.0, simply run \"git stash
apply\" or with a prefix argument \"git stash apply --index\".
If the above failed, then try \"git stash apply\". This fails
\(with or without \"--index\") if there are any uncommitted
changes to files that are also modified in the stash.
When using Git v2.38.0 or later, behave more intelligently:
If both of the above failed, then apply using \"git apply\".
If there are no conflicting files, use \"--3way\". If there are
conflicting files, then using \"--3way\" requires that those
files are staged first, which may be undesirable, so prompt
the user whether to use \"--3way\" or \"--reject\"."
First try \"git stash apply --index\", which tries to preserve the
index stored in the stash, if any. This may fail because applying
the stash could result in conflicts and those have to be stored in
the index, making it impossible to also store the stash's index
there.
If \"git stash\" fails, then potentially fall back to using \"git
apply\". If the stash does not touch any unstaged files, then pass
\"--3way\" to that command. Otherwise ask the user whether to use
that argument or \"--reject\". Customize `magit-no-confirm' if you
want to fall back to using \"--3way\", without being prompted."
(interactive (list (magit-read-stash "Apply stash")))
(magit-stash--apply "apply" stash))
@@ -284,48 +287,82 @@ the user whether to use \"--3way\" or \"--reject\"."
(defun magit-stash-pop (stash)
"Apply a stash to the working tree, on success remove it from stash list.
First try \"git stash pop --index\", which tries to preserve
the index stored in the stash, if any. This may fail because
applying the stash could result in conflicts and those have to
be stored in the index, making it impossible to also store the
stash's index there as well.
When using a Git release before v2.38.0, simply run \"git stash
pop\" or with a prefix argument \"git stash pop --index\".
If the above failed, then try \"git stash apply\". This fails
\(with or without \"--index\") if there are any uncommitted
changes to files that are also modified in the stash.
When using Git v2.38.0 or later, behave more intelligently:
If both of the above failed, then apply using \"git apply\".
If there are no conflicting files, use \"--3way\". If there are
conflicting files, then using \"--3way\" requires that those
files are staged first, which may be undesirable, so prompt
the user whether to use \"--3way\" or \"--reject\"."
First try \"git stash apply --index\", which tries to preserve the
index stored in the stash, if any. This may fail because applying
the stash could result in conflicts and those have to be stored in
the index, making it impossible to also store the stash's index
there.
If \"git stash\" fails, then potentially fall back to using \"git
apply\". If the stash does not touch any unstaged files, then pass
\"--3way\" to that command. Otherwise ask the user whether to use
that argument or \"--reject\". Customize `magit-no-confirm' if you
want to fall back to using \"--3way\", without being prompted."
(interactive (list (magit-read-stash "Pop stash")))
(magit-stash--apply "pop" stash))
(defun magit-stash--apply (action stash)
(or (= (magit-call-git "stash" action "--index" stash) 0)
;; The stash's index could not be applied, so always keep the stash.
(= (magit-call-git "stash" "apply" stash) 0)
(let* ((range (format "%s^..%s" stash stash))
(stashed (magit-git-items "diff" "-z" "--name-only" range "--"))
(conflicts (cl-sort (cl-union (magit-unstaged-files t stashed)
(magit-untracked-files t stashed)
:test #'equal)
#'string<))
(arg (cond
((not conflicts) "--3way")
((magit-confirm-files
'stash-apply-3way conflicts
"Apply stash using `--3way', which requires first staging"
"(else use `--reject')"
t)
(magit-stage-1 nil conflicts)
"--3way")
("--reject"))))
(with-temp-buffer
(magit-git-insert "diff" range)
(magit-run-git-with-input "apply" arg "-"))))
(magit-refresh))
(if (magit-git-version< "2.38.0")
(magit-run-git "stash" action stash (and current-prefix-arg "--index"))
(magit-stash--apply-1 action stash)
(magit-refresh)))
(defun magit-stash--apply-1 (action stash)
(or
(magit--run-git-stash action "--index" stash)
;; The stash's index could not be applied, so always keep the stash.
(magit--run-git-stash "apply" stash)
(let* ((range (format "%s^..%s" stash stash))
(stashed (magit-git-items "diff" "-z" "--name-only" range "--"))
(conflicts (cl-sort (cl-union (magit-unstaged-files t stashed)
(magit-untracked-files t stashed)
:test #'equal)
#'string<))
(arg (if (or (not conflicts)
(memq 'stash-apply-3way magit-no-confirm))
"--3way"
(magit-read-char-case
(concat
"Could not apply stash because of unstaged changes.\n\n"
"To do a tree-way merge, these files have to be staged\n"
(mapconcat (lambda (f) (format " %s" f)) conflicts "\n")
"\n")
nil
(?s (format
"\n[s] stage file%s and apply with \"git apply --3way\""
(if (length> conflicts 1) "s" ""))
"--3way")
(?r "\n[r] apply with \"git apply --reject\"" "--reject")
(?c "\n[c] cancel" nil)))))
(when arg
(when (and (equal arg "--3way") conflicts)
(magit-stage-1 nil conflicts))
(with-temp-buffer
(magit-git-insert "diff" range)
(magit-run-git-with-input "apply" arg "-"))))))
(defun magit--run-git-stash (&rest args)
(magit--with-temp-process-buffer
(let ((exit (save-excursion
(with-environment-variables (("LC_ALL" "en_US.utf8"))
(magit-process-git t "stash" args))))
(buffer (current-buffer))
(failed (looking-at "\\`error: ")))
(with-current-buffer (magit-process-buffer t)
(magit-process-finish-section
(magit-process-insert-section default-directory magit-git-executable
(magit-process-git-arguments args)
exit buffer)
exit))
(pcase (list exit failed)
(`(0 ,_) t) ; no conflict
(`(1 nil) t) ; successfully installed conflict
(_ nil))))) ; could not install conflict, or genuine error
;;;###autoload
(defun magit-stash-drop (stash)
@@ -348,7 +385,7 @@ When the region is active offer to drop all contained stashes."
(defun magit-stash-clear (ref)
"Remove all stashes saved in REF's reflog by deleting REF."
(interactive (let ((ref (or (magit-section-value-if 'stashes) "refs/stash")))
(magit-confirm t (format "Drop all stashes in %s" ref))
(magit-confirm t (list "Drop all stashes in %s" ref))
(list ref)))
(magit-run-git "update-ref" "-d" ref))
@@ -375,7 +412,7 @@ Then apply STASH, dropping it if it applies cleanly."
;;;###autoload
(defun magit-stash-format-patch (stash)
"Create a patch from STASH"
"Create a patch from STASH."
(interactive (list (magit-read-stash "Create patch from stash")))
(with-temp-file (magit-rev-format "0001-%f.patch" stash)
(magit-git-insert "stash" "show" "-p" stash))
@@ -413,7 +450,7 @@ Then apply STASH, dropping it if it applies cleanly."
(t "local"))))))
(defun magit-stash-store (message ref commit)
(magit-update-ref ref message commit t))
(magit-update-ref ref message commit))
(defun magit-stash-create (message index worktree untracked)
(unless (magit-rev-parse "--verify" "HEAD")
@@ -467,7 +504,7 @@ Then apply STASH, dropping it if it applies cleanly."
"<1>" (magit-menu-item "Visit %v" #'magit-stash-show))
(magit-define-section-jumper magit-jump-to-stashes
"Stashes" stashes "refs/stash")
"Stashes" stashes "refs/stash" magit-insert-stashes)
(cl-defun magit-insert-stashes (&optional (ref "refs/stash")
(heading "Stashes:"))
@@ -511,8 +548,9 @@ instead of \"Stashes:\"."
(define-derived-mode magit-stashes-mode magit-reflog-mode "Magit Stashes"
"Mode for looking at lists of stashes."
:interactive nil
:group 'magit-log
(hack-dir-local-variables-non-file-buffer))
(magit-hack-dir-local-variables))
(defun magit-stashes-setup-buffer ()
(magit-setup-buffer #'magit-stashes-mode nil
@@ -520,9 +558,10 @@ instead of \"Stashes:\"."
(defun magit-stashes-refresh-buffer ()
(magit-insert-section (stashesbuf)
(magit-insert-heading (if (equal magit-buffer-refname "refs/stash")
"Stashes:"
(format "Stashes [%s]:" magit-buffer-refname)))
(magit-insert-heading t
(if (equal magit-buffer-refname "refs/stash")
"Stashes"
(format "Stashes [%s]" magit-buffer-refname)))
(magit-git-wash (apply-partially #'magit-log-wash-log 'stash)
"reflog" "--format=%gd%x00%aN%x00%at%x00%gs" magit-buffer-refname)))
@@ -571,10 +610,14 @@ If there is no stash buffer in the same frame, then do nothing."
(define-derived-mode magit-stash-mode magit-diff-mode "Magit Stash"
"Mode for looking at individual stashes."
:interactive nil
:group 'magit-diff
(hack-dir-local-variables-non-file-buffer)
(magit-hack-dir-local-variables)
(setq magit--imenu-group-types '(commit)))
(put 'magit-stash-mode 'magit-diff-default-arguments
'("--no-ext-diff"))
(defun magit-stash-setup-buffer (stash args files)
(magit-setup-buffer #'magit-stash-mode nil
(magit-buffer-revision stash)
@@ -607,13 +650,11 @@ If there is no stash buffer in the same frame, then do nothing."
"Insert section showing notes for a stash.
This shows the notes for stash@{N} but not for the other commits
that make up the stash."
(magit-insert-section section (note)
(magit-insert-heading "Notes")
(magit-insert-section (note)
(magit-insert-heading t "Notes")
(magit-git-insert "notes" "show" magit-buffer-revision)
(if (= (point)
(oref section content))
(magit-cancel-section)
(insert "\n"))))
(magit-cancel-section 'if-empty)
(insert "\n")))
(defun magit-insert-stash-index ()
"Insert section showing staged changes of the stash."

View File

@@ -1,9 +1,9 @@
;;; magit-status.el --- The grand overview -*- lexical-binding:t -*-
;; Copyright (C) 2008-2023 The Magit Project Contributors
;; Copyright (C) 2008-2025 The Magit Project Contributors
;; Author: Jonas Bernoulli <jonas@bernoul.li>
;; Maintainer: Jonas Bernoulli <jonas@bernoul.li>
;; Author: Jonas Bernoulli <emacs.magit@jonas.bernoulli.dev>
;; Maintainer: Jonas Bernoulli <emacs.magit@jonas.bernoulli.dev>
;; SPDX-License-Identifier: GPL-3.0-or-later
@@ -41,12 +41,12 @@
:type 'hook)
(defcustom magit-status-headers-hook
'(magit-insert-error-header
magit-insert-diff-filter-header
magit-insert-head-branch-header
magit-insert-upstream-branch-header
magit-insert-push-branch-header
magit-insert-tags-header)
(list #'magit-insert-error-header
#'magit-insert-diff-filter-header
#'magit-insert-head-branch-header
#'magit-insert-upstream-branch-header
#'magit-insert-push-branch-header
#'magit-insert-tags-header)
"Hook run to insert headers into the status buffer.
This hook is run by `magit-insert-status-headers', which in turn
@@ -55,32 +55,32 @@ all."
:package-version '(magit . "2.1.0")
:group 'magit-status
:type 'hook
:options '(magit-insert-error-header
magit-insert-diff-filter-header
magit-insert-repo-header
magit-insert-remote-header
magit-insert-head-branch-header
magit-insert-upstream-branch-header
magit-insert-push-branch-header
magit-insert-tags-header))
:options (list #'magit-insert-error-header
#'magit-insert-diff-filter-header
#'magit-insert-repo-header
#'magit-insert-remote-header
#'magit-insert-head-branch-header
#'magit-insert-upstream-branch-header
#'magit-insert-push-branch-header
#'magit-insert-tags-header))
(defcustom magit-status-sections-hook
'(magit-insert-status-headers
magit-insert-merge-log
magit-insert-rebase-sequence
magit-insert-am-sequence
magit-insert-sequencer-sequence
magit-insert-bisect-output
magit-insert-bisect-rest
magit-insert-bisect-log
magit-insert-untracked-files
magit-insert-unstaged-changes
magit-insert-staged-changes
magit-insert-stashes
magit-insert-unpushed-to-pushremote
magit-insert-unpushed-to-upstream-or-recent
magit-insert-unpulled-from-pushremote
magit-insert-unpulled-from-upstream)
(list #'magit-insert-status-headers
#'magit-insert-merge-log
#'magit-insert-rebase-sequence
#'magit-insert-am-sequence
#'magit-insert-sequencer-sequence
#'magit-insert-bisect-output
#'magit-insert-bisect-rest
#'magit-insert-bisect-log
#'magit-insert-untracked-files
#'magit-insert-unstaged-changes
#'magit-insert-staged-changes
#'magit-insert-stashes
#'magit-insert-unpushed-to-pushremote
#'magit-insert-unpushed-to-upstream-or-recent
#'magit-insert-unpulled-from-pushremote
#'magit-insert-unpulled-from-upstream)
"Hook run to insert sections into a status buffer."
:package-version '(magit . "2.12.0")
:group 'magit-status
@@ -142,6 +142,49 @@ The functions which respect this option are
:group 'magit-status
:type 'boolean)
(defcustom magit-status-show-untracked-files t
"Whether to list untracked files in the status buffer.
- If nil, do not list any untracked files.
- If t, list untracked files, but if a directory does not contain any
untracked files, then only list that directory, not the contained
untracked files.
- If all, then list each individual untracked files. This is can be
very slow and is discouraged.
The corresponding values for the Git variable are \"no\", \"normal\"
and \"all\".
To disable listing untracked files in a specific repository only, add
the following to \".dir-locals.el\":
((magit-status-mode
(magit-status-show-untracked-files . \"no\")))
Alternatively (and mostly for historic reasons), it is possible to use
`git-config' to set the repository-local value:
git config set --local status.showUntrackedFiles no
This does *not* override the (if any) local value of this Lisp variable,
but it does override its global value.
See the last section in the git-status(1) manpage, to speed up the part
of the work Git is responsible for. Turning that list into sections is
also not free, so Magit only lists `magit-status-file-list-limit' files."
:package-version '(magit . "4.3.0")
:group 'magit-status
:type 'boolean
:safe 'booleanp)
(defcustom magit-status-file-list-limit 100
"How many files to list in file list sections in the status buffer.
For performance reasons, it is recommended that you do not
increase this limit."
:package-version '(magit . "4.3.0")
:group 'magit-status
:type 'natnum)
(defcustom magit-status-margin
(list nil
(nth 1 magit-log-margin)
@@ -342,38 +385,22 @@ init file: (global-set-key (kbd \"C-x g\") \\='magit-status-quick)."
(transient-define-prefix magit-status-jump ()
"In a Magit-Status buffer, jump to a section."
["Jump to"
[("z " "Stashes" magit-jump-to-stashes
:if (lambda () (memq 'magit-insert-stashes magit-status-sections-hook)))
("t " "Tracked" magit-jump-to-tracked
:if (lambda () (memq 'magit-insert-tracked-files magit-status-sections-hook)))
("n " "Untracked" magit-jump-to-untracked
:if (lambda () (memq 'magit-insert-untracked-files magit-status-sections-hook)))
("u " "Unstaged" magit-jump-to-unstaged
:if (lambda () (memq 'magit-insert-unstaged-changes magit-status-sections-hook)))
("s " "Staged" magit-jump-to-staged
:if (lambda () (memq 'magit-insert-staged-changes magit-status-sections-hook)))]
[("fu" "Unpulled from upstream" magit-jump-to-unpulled-from-upstream
:if (lambda () (memq 'magit-insert-unpulled-from-upstream magit-status-sections-hook)))
("fp" "Unpulled from pushremote" magit-jump-to-unpulled-from-pushremote
:if (lambda () (memq 'magit-insert-unpulled-from-pushremote magit-status-sections-hook)))
("pu" magit-jump-to-unpushed-to-upstream
:if (lambda ()
(or (memq 'magit-insert-unpushed-to-upstream-or-recent magit-status-sections-hook)
(memq 'magit-insert-unpushed-to-upstream magit-status-sections-hook)))
:description (lambda ()
(let ((upstream (magit-get-upstream-branch)))
(if (or (not upstream)
(magit-rev-ancestor-p "HEAD" upstream))
"Recent commits"
"Unmerged into upstream"))))
("pp" "Unpushed to pushremote" magit-jump-to-unpushed-to-pushremote
:if (lambda () (memq 'magit-insert-unpushed-to-pushremote magit-status-sections-hook)))
("a " "Assumed unstaged" magit-jump-to-assume-unchanged
:if (lambda () (memq 'magit-insert-assume-unchanged-files magit-status-sections-hook)))
("w " "Skip worktree" magit-jump-to-skip-worktree
:if (lambda () (memq 'magit-insert-skip-worktree-files magit-status-sections-hook)))]
[("i" "Using Imenu" imenu)]])
[["Jump to"
("z " magit-jump-to-stashes)
("t " magit-jump-to-tracked)
("n " magit-jump-to-untracked)
("i " magit-jump-to-ignored)
("u " magit-jump-to-unstaged)
("s " magit-jump-to-staged)]
[""
("fu" magit-jump-to-unpulled-from-upstream)
("fp" magit-jump-to-unpulled-from-pushremote)
("pu" magit-jump-to-unpushed-to-upstream)
("pp" magit-jump-to-unpushed-to-pushremote)
("a " magit-jump-to-assume-unchanged)
("w " magit-jump-to-skip-worktree)]
["Jump using"
("j" "Imenu" imenu)]])
(define-derived-mode magit-status-mode magit-mode "Magit"
"Mode for looking at Git status.
@@ -401,10 +428,11 @@ Staging and applying changes is documented in info node
Type \\[magit-commit] to create a commit.
\\{magit-status-mode-map}"
:interactive nil
:group 'magit-status
(hack-dir-local-variables-non-file-buffer)
(magit-hack-dir-local-variables)
(when magit-status-initial-section
(add-hook 'magit-refresh-buffer-hook
(add-hook 'magit-post-create-buffer-hook
#'magit-status-goto-initial-section nil t))
(setq magit--imenu-group-types '(not branch commit)))
@@ -426,8 +454,8 @@ Type \\[magit-commit] to create a commit.
magit-status-use-buffer-arguments))
(file (and magit-status-goto-file-position
(magit-file-relative-name)))
(line (and file (line-number-at-pos)))
(col (and file (current-column)))
(line (and file (save-restriction (widen) (line-number-at-pos))))
(col (and file (save-restriction (widen) (current-column))))
(buf (magit-setup-buffer #'magit-status-mode nil
(magit-buffer-diff-args (nth 0 d))
(magit-buffer-diff-files (nth 1 d))
@@ -454,20 +482,19 @@ Type \\[magit-commit] to create a commit.
(defun magit-status-goto-initial-section ()
"Jump to the section specified by `magit-status-initial-section'."
(when-let ((section
(--some (if (integerp it)
(nth (1- it)
(magit-section-siblings (magit-current-section)
'next))
(magit-get-section it))
magit-status-initial-section)))
(seq-some (lambda (initial)
(if (integerp initial)
(nth (1- initial)
(magit-section-siblings
(magit-current-section) 'next))
(magit-get-section initial)))
magit-status-initial-section)))
(goto-char (oref section start))
(when-let ((vis (cdr (assq 'magit-status-initial-section
magit-section-initial-visibility-alist))))
(if (eq vis 'hide)
(magit-section-hide section)
(magit-section-show section))))
(remove-hook 'magit-refresh-buffer-hook
#'magit-status-goto-initial-section t))
(magit-section-show section)))))
(defun magit-status-maybe-update-revision-buffer (&optional _)
"When moving in the status buffer, update the revision buffer.
@@ -532,7 +559,7 @@ the status buffer causes this section to disappear again."
(when magit-buffer-diff-files
(insert " -- ")))
(when magit-buffer-diff-files
(insert (mapconcat #'identity magit-buffer-diff-files " ")))
(insert (string-join magit-buffer-diff-files " ")))
(insert ?\n))))
;;;; Reference Headers
@@ -554,13 +581,13 @@ instead. The optional BRANCH argument is for internal use only."
(insert (propertize commit 'font-lock-face 'magit-hash) ?\s))
(insert (propertize branch 'font-lock-face 'magit-branch-local))
(insert ?\s)
(insert (funcall magit-log-format-message-function branch summary))
(insert (magit-log--wash-summary summary))
(insert ?\n))
(magit-insert-section (commit commit)
(insert (format "%-10s" "Head: "))
(insert (propertize commit 'font-lock-face 'magit-hash))
(insert ?\s)
(insert (funcall magit-log-format-message-function nil summary))
(insert (magit-log--wash-summary summary))
(insert ?\n))))))
(defun magit-insert-upstream-branch-header (&optional branch upstream keyword)
@@ -587,10 +614,9 @@ arguments are for internal use only."
'font-lock-face 'magit-hash)
" "))
upstream " "
(funcall magit-log-format-message-function upstream
(funcall magit-log-format-message-function nil
(or (magit-rev-format "%s" upstream)
"(no commit message)"))))
(magit-log--wash-summary
(or (magit-rev-format "%s" upstream)
"(no commit message)")))
(cond
((magit--unnamed-upstream-p remote merge)
(concat (propertize merge 'font-lock-face 'magit-branch-remote)
@@ -626,10 +652,8 @@ arguments are for internal use only."
'font-lock-face 'magit-hash)
" "))
target " "
(funcall magit-log-format-message-function target
(funcall magit-log-format-message-function nil
(or (magit-rev-format "%s" target)
"(no commit message)"))))
(magit-log--wash-summary (or (magit-rev-format "%s" target)
"(no commit message)")))
(let ((remote (magit-get-push-remote branch)))
(if (magit-remote-p remote)
(concat target " "
@@ -706,116 +730,98 @@ remote in alphabetic order."
"<2>" (magit-menu-item "Discard files" #'magit-discard)
"<1>" (magit-menu-item "Stage files" #'magit-stage))
(magit-define-section-jumper magit-jump-to-untracked "Untracked files" untracked)
(magit-define-section-jumper magit-jump-to-untracked
"Untracked files" untracked nil magit-insert-untracked-files)
(magit-define-section-jumper magit-jump-to-tracked
"Tracked files" tracked nil magit-insert-tracked-files)
(magit-define-section-jumper magit-jump-to-ignored
"Ignored files" ignored nil magit-insert-ignored-files)
(magit-define-section-jumper magit-jump-to-skip-worktree
"Skip-worktree files" skip-worktree nil magit-insert-skip-worktree-files)
(magit-define-section-jumper magit-jump-to-assume-unchanged
"Assume-unchanged files" assume-unchanged nil
magit-insert-assume-unchanged-files)
(defun magit-insert-untracked-files ()
"Maybe insert a list or tree of untracked files.
"Maybe insert list of untracked files.
Do so depending on the value of `status.showUntrackedFiles'.
Note that even if the value is `all', Magit still initially
only shows directories. But the directory sections can then
be expanded using \"TAB\".
If the first element of `magit-buffer-diff-files' is a
directory, then limit the list to files below that. The value
value of that variable can be set using \"D -- DIRECTORY RET g\"."
(let* ((show (or (magit-get "status.showUntrackedFiles") "normal"))
(base (car magit-buffer-diff-files))
(base (and base (file-directory-p base) base)))
(unless (equal show "no")
(if (equal show "all")
(when-let ((files (magit-untracked-files nil base)))
(magit-insert-section (untracked)
(magit-insert-heading "Untracked files:")
(magit-insert-files files base)
(insert ?\n)))
(when-let ((files
(--mapcat (and (eq (aref it 0) ??)
(list (substring it 3)))
(magit-git-items "status" "-z" "--porcelain"
(magit-ignore-submodules-p t)
"--" base))))
(magit-insert-section (untracked)
(magit-insert-heading "Untracked files:")
(dolist (file files)
(magit-insert-section (file file)
(insert (propertize file 'font-lock-face 'magit-filename) ?\n)))
(insert ?\n)))))))
(magit-define-section-jumper magit-jump-to-tracked "Tracked files" tracked)
List files if `magit-status-show-untracked-files' is non-nil, but also
take the local value of Git variable `status.showUntrackedFiles' into
account. The local value of the Lisp variable takes precedence over the
local value of the Git variable. The global value of the Git variable
is always ignored."
(when-let*
((value (or (and (local-variable-p 'magit-status-show-untracked-files)
magit-status-show-untracked-files)
(pcase (magit-get "--local" "status.showUntrackedFiles")
((or "no" "off" "false" "0") 'no)
((or "yes" "on" "true" "1") t)
("all" 'all))
magit-status-show-untracked-files))
((not (eq value 'no))))
(magit-insert-files
'untracked
(lambda (files)
(mapcan (lambda (line)
(and (eq (aref line 0) ??)
(list (substring line 3))))
(apply #'magit-git-items "status" "-z" "--porcelain"
(format "--untracked-files=%s"
(if (eq value 'all) "all" "normal"))
"--" files))))))
(defun magit-insert-tracked-files ()
"Insert a tree of tracked files.
If the first element of `magit-buffer-diff-files' is a
directory, then limit the list to files below that. The value
value of that variable can be set using \"D -- DIRECTORY RET g\"."
(when-let ((files (magit-list-files)))
(let* ((base (car magit-buffer-diff-files))
(base (and base (file-directory-p base) base)))
(magit-insert-section (tracked nil t)
(magit-insert-heading "Tracked files:")
(magit-insert-files files base)
(insert ?\n)))))
"Insert a list of tracked files.
Honor the buffer's file filter, which can be set using \"D - -\"."
(magit-insert-files 'tracked #'magit-list-files))
(defun magit-insert-ignored-files ()
"Insert a tree of ignored files.
If the first element of `magit-buffer-diff-files' is a
directory, then limit the list to files below that. The value
of that variable can be set using \"D -- DIRECTORY RET g\"."
(when-let ((files (magit-ignored-files)))
(let* ((base (car magit-buffer-diff-files))
(base (and base (file-directory-p base) base)))
(magit-insert-section (tracked nil t)
(magit-insert-heading "Ignored files:")
(magit-insert-files files base)
(insert ?\n)))))
(magit-define-section-jumper magit-jump-to-skip-worktree "Skip-worktree files" skip-worktree)
"Insert a list of ignored files.
Honor the buffer's file filter, which can be set using \"D - -\"."
(magit-insert-files 'ignored
(lambda (args) (magit-ignored-files "--directory" args))))
(defun magit-insert-skip-worktree-files ()
"Insert a tree of skip-worktree files.
If the first element of `magit-buffer-diff-files' is a
directory, then limit the list to files below that. The value
of that variable can be set using \"D -- DIRECTORY RET g\"."
(when-let ((files (magit-skip-worktree-files)))
(let* ((base (car magit-buffer-diff-files))
(base (and base (file-directory-p base) base)))
(magit-insert-section (skip-worktree nil t)
(magit-insert-heading "Skip-worktree files:")
(magit-insert-files files base)
(insert ?\n)))))
(magit-define-section-jumper magit-jump-to-assume-unchanged "Assume-unchanged files" assume-unchanged)
"Insert a list of skip-worktree files.
Honor the buffer's file filter, which can be set using \"D - -\"."
(magit-insert-files 'skip-worktree #'magit-skip-worktree-files))
(defun magit-insert-assume-unchanged-files ()
"Insert a tree of files that are assumed to be unchanged.
"Insert a list of files that are assumed to be unchanged.
Honor the buffer's file filter, which can be set using \"D - -\"."
(magit-insert-files 'assume-unchanged #'magit-assume-unchanged-files))
If the first element of `magit-buffer-diff-files' is a
directory, then limit the list to files below that. The value
of that variable can be set using \"D -- DIRECTORY RET g\"."
(when-let ((files (magit-assume-unchanged-files)))
(let* ((base (car magit-buffer-diff-files))
(base (and base (file-directory-p base) base)))
(magit-insert-section (assume-unchanged nil t)
(magit-insert-heading "Assume-unchanged files:")
(magit-insert-files files base)
(insert ?\n)))))
(defun magit-insert-files (files directory)
(while (and files (string-prefix-p (or directory "") (car files)))
(let ((dir (file-name-directory (car files))))
(if (equal dir directory)
(let ((file (pop files)))
(magit-insert-section (file file)
(insert (propertize file 'font-lock-face 'magit-filename) ?\n)))
(magit-insert-section (file dir t)
(insert (propertize dir 'file 'magit-filename) ?\n)
(magit-insert-heading)
(setq files (magit-insert-files files dir))))))
files)
(defun magit-insert-files (type fn)
(when-let ((files (funcall fn
(and magit-buffer-diff-files
(cons "--" magit-buffer-diff-files)))))
(magit-insert-section section ((eval type) nil t)
(magit-insert-heading (length files)
(let ((title (symbol-name type)))
(format "%c%s files"
(capitalize (aref title 0))
(substring title 1))))
(magit-insert-section-body
(let ((magit-section-insert-in-reverse t)
(limit magit-status-file-list-limit))
(while (and files (> limit 0))
(cl-decf limit)
(let ((file (pop files)))
(magit-insert-section (file file)
(insert (funcall magit-format-file-function
'list file 'magit-filename))
(insert ?\n))))
(when files
(magit-insert-section (info)
(insert (propertize
(format "%s files not listed\n" (length files))
'face 'warning)))))
(insert ?\n)
(oset section children (nreverse (oref section children)))))))
;;; _
(provide 'magit-status)

View File

@@ -1,9 +1,9 @@
;;; magit-submodule.el --- Submodule support for Magit -*- lexical-binding:t -*-
;; Copyright (C) 2008-2023 The Magit Project Contributors
;; Copyright (C) 2008-2025 The Magit Project Contributors
;; Author: Jonas Bernoulli <jonas@bernoul.li>
;; Maintainer: Jonas Bernoulli <jonas@bernoul.li>
;; Author: Jonas Bernoulli <emacs.magit@jonas.bernoulli.dev>
;; Maintainer: Jonas Bernoulli <emacs.magit@jonas.bernoulli.dev>
;; SPDX-License-Identifier: GPL-3.0-or-later
@@ -20,6 +20,12 @@
;; You should have received a copy of the GNU General Public License
;; along with Magit. If not, see <https://www.gnu.org/licenses/>.
;;; Commentary:
;; This library implements support for "git submodule".
;; See (info "(magit)Submodules").
;;; Code:
(require 'magit)
@@ -29,11 +35,11 @@
;;; Options
(defcustom magit-module-sections-hook
'(magit-insert-modules-overview
magit-insert-modules-unpulled-from-upstream
magit-insert-modules-unpulled-from-pushremote
magit-insert-modules-unpushed-to-upstream
magit-insert-modules-unpushed-to-pushremote)
(list #'magit-insert-modules-overview
#'magit-insert-modules-unpulled-from-upstream
#'magit-insert-modules-unpulled-from-pushremote
#'magit-insert-modules-unpushed-to-upstream
#'magit-insert-modules-unpushed-to-pushremote)
"Hook run by `magit-insert-modules'.
That function isn't part of `magit-status-sections-hook's default
@@ -53,35 +59,37 @@ is inserted. If it is nil, then all sections listed in
:group 'magit-status
:type 'boolean)
(defcustom magit-submodule-list-mode-hook '(hl-line-mode)
(defcustom magit-submodule-list-mode-hook (list #'hl-line-mode)
"Hook run after entering Magit-Submodule-List mode."
:package-version '(magit . "2.9.0")
:group 'magit-repolist
:type 'hook
:get 'magit-hook-custom-get
:options '(hl-line-mode))
:options (list #'hl-line-mode))
(defcustom magit-submodule-list-columns
'(("Path" 25 magit-modulelist-column-path nil)
("Version" 25 magit-repolist-column-version
`(("Path" 25 ,#'magit-modulelist-column-path
())
("Version" 25 ,#'magit-repolist-column-version
((:sort magit-repolist-version<)))
("Branch" 20 magit-repolist-column-branch nil)
("B<U" 3 magit-repolist-column-unpulled-from-upstream
("Branch" 20 ,#'magit-repolist-column-branch
())
("B<P" 3 ,#'magit-repolist-column-unpulled-from-pushremote
((:right-align t)
(:sort <)))
("B>U" 3 magit-repolist-column-unpushed-to-upstream
("B<U" 3 ,#'magit-repolist-column-unpulled-from-upstream
((:right-align t)
(:sort <)))
("B<P" 3 magit-repolist-column-unpulled-from-pushremote
("B>P" 3 ,#'magit-repolist-column-unpushed-to-pushremote
((:right-align t)
(:sort <)))
("B>P" 3 magit-repolist-column-unpushed-to-pushremote
("B>U" 3 ,#'magit-repolist-column-unpushed-to-upstream
((:right-align t)
(:sort <)))
("B" 3 magit-repolist-column-branches
("S" 3 ,#'magit-repolist-column-stashes
((:right-align t)
(:sort <)))
("S" 3 magit-repolist-column-stashes
("B" 3 ,#'magit-repolist-column-branches
((:right-align t)
(:sort <))))
"List of columns displayed by `magit-list-submodules'.
@@ -177,8 +185,8 @@ and also setting this variable to t will lead to tears."
("f" "Fetch modules" magit-fetch-modules)])
(defun magit-submodule-arguments (&rest filters)
(--filter (and (member it filters) it)
(transient-args 'magit-submodule)))
(seq-filter (##and (member % filters) %)
(transient-args 'magit-submodule)))
(defclass magit--git-submodule-suffix (transient-suffix)
())
@@ -246,8 +254,7 @@ it is nil, then PATH also becomes the name."
(magit-process-sentinel process event)
(process-put process 'inhibit-refresh t)
(magit-process-sentinel process event)
(when (magit-git-version>= "2.12.0")
(magit-call-git "submodule" "absorbgitdirs" path))
(magit-call-git "submodule" "absorbgitdirs" path)
(magit-refresh)))))))
;;;###autoload
@@ -257,10 +264,10 @@ it is nil, then PATH also becomes the name."
(push (if prefer-short path name) minibuffer-history)
(magit-read-string-ns
"Submodule name" nil (cons 'minibuffer-history 2)
(or (--keep (pcase-let ((`(,var ,val) (split-string it "=")))
(and (equal val path)
(cadr (split-string var "\\."))))
(magit-git-lines "config" "--list" "-f" ".gitmodules"))
(or (seq-keep (##pcase-let ((`(,var ,val) (split-string % "=")))
(and (equal val path)
(cadr (split-string var "\\."))))
(magit-git-lines "config" "--list" "-f" ".gitmodules"))
(if prefer-short name path)))))
;;;###autoload (autoload 'magit-submodule-register "magit-submodule" nil t)
@@ -283,7 +290,7 @@ single module from the user."
(magit-run-git-async "submodule" "init" "--" modules)))
;;;###autoload (autoload 'magit-submodule-populate "magit-submodule" nil t)
(transient-define-suffix magit-submodule-populate (modules)
(transient-define-suffix magit-submodule-populate (modules args)
"Create MODULES working directories, checking out the recorded commits.
With a prefix argument act on all suitable modules. Otherwise,
@@ -293,11 +300,13 @@ single module from the user."
;; This is the command that actually "initializes" modules.
;; A module is initialized when it has a working directory,
;; a gitlink, and a .gitmodules entry.
:description "Populate git submodule update --init"
:class 'magit--git-submodule-suffix
:description "Populate git submodule update --init [--recursive]"
(interactive
(list (magit-module-confirm "Populate" 'magit-module-no-worktree-p)))
(list (magit-module-confirm "Populate" 'magit-module-no-worktree-p)
(magit-submodule-arguments "--recursive")))
(magit-with-toplevel
(magit-run-git-async "submodule" "update" "--init" "--" modules)))
(magit-run-git-async "submodule" "update" "--init" args "--" modules)))
;;;###autoload (autoload 'magit-submodule-update "magit-submodule" nil t)
(transient-define-suffix magit-submodule-update (modules args)
@@ -346,8 +355,8 @@ With a prefix argument act on all suitable modules. Otherwise,
if the region selects modules, then act on those. Otherwise, if
there is a module at point, then act on that. Otherwise read a
single module from the user."
;; Even though a package is "uninitialized" (it has no worktree)
;; the super-projects $GIT_DIR/config may never-the-less set the
;; Even when a submodule is "uninitialized" (it has no worktree)
;; the super-project's $GIT_DIR/config may never-the-less set the
;; module's url. This may happen if you `deinit' and then `init'
;; to register (NOT initialize). Because the purpose of `deinit'
;; is to remove the working directory AND to remove the url, this
@@ -381,8 +390,6 @@ to recover from making a mistake here, but don't count on it."
(list (magit-read-module-path "Remove module")))
(magit-submodule-arguments "--force")
current-prefix-arg))
(when (magit-git-version< "2.12.0")
(error "This command requires Git v2.12.0"))
(when magit-submodule-remove-trash-gitdirs
(setq trash-gitdirs t))
(magit-with-toplevel
@@ -408,16 +415,16 @@ to recover from making a mistake here, but don't count on it."
(if (cdr modified)
(message "Omitting %s modules with uncommitted changes: %s"
(length modified)
(mapconcat #'identity modified ", "))
(string-join modified ", "))
(message "Omitting module %s, it has uncommitted changes"
(car modified)))
(setq modules (cl-set-difference modules modified :test #'equal))))
(when modules
(let ((alist
(and trash-gitdirs
(--map (split-string it "\0")
(magit-git-lines "submodule" "foreach" "-q"
"printf \"$sm_path\\0$name\n\"")))))
(mapcar (##split-string % "\0")
(magit-git-lines "submodule" "foreach" "-q"
"printf \"$sm_path\\0$name\n\"")))))
(magit-git "submodule" "absorbgitdirs" "--" modules)
(magit-git "submodule" "deinit" args "--" modules)
(magit-git "rm" args "--" modules)
@@ -488,7 +495,7 @@ or, failing that, the abbreviated HEAD commit hash."
(dolist (module modules)
(let ((default-directory
(expand-file-name (file-name-as-directory module))))
(magit-insert-section (magit-module-section module t)
(magit-insert-section (module module t)
(insert (propertize (format path-format module)
'font-lock-face 'magit-diff-file-heading))
(if (not (file-exists-p ".git"))
@@ -588,39 +595,38 @@ These sections can be expanded to show the respective commits."
(defun magit--insert-modules-logs (heading type range)
"For internal use, don't add to a hook."
(unless (magit-ignore-submodules-p)
(when-let ((modules (magit-list-module-paths)))
(magit-insert-section section ((eval type) nil t)
(string-match "\\`\\(.+\\) \\([^ ]+\\)\\'" heading)
(magit-insert-heading
(propertize (match-string 1 heading)
'font-lock-face 'magit-section-heading)
" "
(propertize (match-string 2 heading)
'font-lock-face 'magit-branch-remote)
":")
(magit-with-toplevel
(dolist (module modules)
(when (magit-module-worktree-p module)
(let ((default-directory
(expand-file-name (file-name-as-directory module))))
(when (magit-file-accessible-directory-p default-directory)
(magit-insert-section sec (magit-module-section module t)
(magit-insert-heading
(propertize module
'font-lock-face 'magit-diff-file-heading)
":")
(oset sec range range)
(magit-git-wash
(apply-partially #'magit-log-wash-log 'module)
"-c" "push.default=current" "log" "--oneline" range)
(when (> (point)
(oref sec content))
(delete-char -1))))))))
(if (> (point)
(oref section content))
(insert ?\n)
(magit-cancel-section))))))
(when-let (((not (magit-ignore-submodules-p)))
(modules (magit-list-module-paths)))
(magit-insert-section ((eval type) nil t)
(string-match "\\`\\(.+\\) \\([^ ]+\\)\\'" heading)
(magit-insert-heading
(propertize (match-string 1 heading)
'font-lock-face 'magit-section-heading)
" "
(propertize (match-string 2 heading)
'font-lock-face 'magit-branch-remote)
":")
(dolist (module modules)
(when-let* ((default-directory (expand-file-name module))
((file-exists-p (expand-file-name ".git")))
(lines (magit-git-lines "-c" "push.default=current"
"log" "--oneline" range))
(count (length lines))
((> count 0)))
(magit-insert-section
( module module t
:range range)
(magit-insert-heading count
(propertize module 'font-lock-face 'magit-diff-file-heading))
(dolist (line lines)
(string-match magit-log-module-re line)
(let ((rev (match-string 1 line))
(msg (match-string 2 line)))
(magit-insert-section (module-commit rev t)
(insert (propertize rev 'font-lock-face 'magit-hash) " "
(magit-log--wash-summary msg) "\n")))))))
(magit-cancel-section 'if-empty)
(insert ?\n))))
;;; List
@@ -634,15 +640,12 @@ These sections can be expanded to show the respective commits."
:doc "Local keymap for Magit-Submodule-List mode buffers."
:parent magit-repolist-mode-map)
(define-derived-mode magit-submodule-list-mode tabulated-list-mode "Modules"
(define-derived-mode magit-submodule-list-mode magit-repolist-mode "Modules"
"Major mode for browsing a list of Git submodules."
:group 'magit-repolist-mode
(setq-local x-stretch-cursor nil)
(setq tabulated-list-padding 0)
(add-hook 'tabulated-list-revert-hook #'magit-submodule-list-refresh nil t)
(setq imenu-prev-index-position-function
#'magit-repolist--imenu-prev-index-position)
(setq imenu-extract-index-name-function #'tabulated-list-get-id))
:interactive nil
:group 'magit-repolist
(setq-local tabulated-list-revert-hook
(list #'magit-submodule-list-refresh t)))
(defvar-local magit-submodule-list-predicate nil)
@@ -666,7 +669,7 @@ These sections can be expanded to show the respective commits."
(and (file-exists-p ".git")
(or (not magit-submodule-list-predicate)
(funcall magit-submodule-list-predicate module))
(list module
(list default-directory
(vconcat
(mapcar (pcase-lambda (`(,title ,width ,fn ,props))
(or (funcall fn `((:path ,module)

View File

@@ -1,9 +1,9 @@
;;; magit-subtree.el --- Subtree support for Magit -*- lexical-binding:t -*-
;; Copyright (C) 2008-2023 The Magit Project Contributors
;; Copyright (C) 2008-2025 The Magit Project Contributors
;; Author: Jonas Bernoulli <jonas@bernoul.li>
;; Maintainer: Jonas Bernoulli <jonas@bernoul.li>
;; Author: Jonas Bernoulli <emacs.magit@jonas.bernoulli.dev>
;; Maintainer: Jonas Bernoulli <emacs.magit@jonas.bernoulli.dev>
;; SPDX-License-Identifier: GPL-3.0-or-later
@@ -20,6 +20,13 @@
;; You should have received a copy of the GNU General Public License
;; along with Magit. If not, see <https://www.gnu.org/licenses/>.
;;; Commentary:
;; This library implements support for "git subtree".
;; The entry point is the `magit-subtree' menu command.
;; See (info "(magit)Subtree").
;;; Code:
(require 'magit)
@@ -30,7 +37,7 @@
(transient-define-prefix magit-subtree ()
"Import or export subtrees."
:man-page "git-subtree"
["Actions"
["Subtree actions"
("i" "Import" magit-subtree-import)
("e" "Export" magit-subtree-export)])
@@ -42,7 +49,7 @@
(magit-subtree:--prefix)
(magit-subtree:--message)
("-s" "Squash" "--squash")]
["Actions"
["Subtree import actions"
[("a" "Add" magit-subtree-add)
("c" "Add commit" magit-subtree-add-commit)]
[("m" "Merge" magit-subtree-merge)
@@ -59,7 +66,7 @@
(magit-subtree:--onto)
("-i" "Ignore joins" "--ignore-joins")
("-j" "Rejoin" "--rejoin")]
["Actions"
["Subtree export actions"
("p" "Push" magit-subtree-push)
("s" "Split" magit-subtree-split)])
@@ -107,14 +114,14 @@
:reader #'magit-transient-read-revision)
(defun magit-subtree-prefix (transient prompt)
(if-let ((arg (--first (string-prefix-p "--prefix=" it)
(transient-args transient))))
(if-let ((arg (seq-find (##string-prefix-p "--prefix=" %)
(transient-args transient))))
(substring arg 9)
(magit-subtree-read-prefix prompt)))
(defun magit-subtree-arguments (transient)
(--remove (string-prefix-p "--prefix=" it)
(transient-args transient)))
(seq-remove (##string-prefix-p "--prefix=" %)
(transient-args transient)))
(defun magit-git-subtree (subcmd prefix &rest args)
(magit-run-git-async "subtree" subcmd (concat "--prefix=" prefix) args))

View File

@@ -1,9 +1,9 @@
;;; magit-tag.el --- Tag functionality -*- lexical-binding:t -*-
;; Copyright (C) 2008-2023 The Magit Project Contributors
;; Copyright (C) 2008-2025 The Magit Project Contributors
;; Author: Jonas Bernoulli <jonas@bernoul.li>
;; Maintainer: Jonas Bernoulli <jonas@bernoul.li>
;; Author: Jonas Bernoulli <emacs.magit@jonas.bernoulli.dev>
;; Maintainer: Jonas Bernoulli <emacs.magit@jonas.bernoulli.dev>
;; SPDX-License-Identifier: GPL-3.0-or-later
@@ -38,9 +38,10 @@
"Create or delete a tag."
:man-page "git-tag"
["Arguments"
("-f" "Force" ("-f" "--force"))
("-a" "Annotate" ("-a" "--annotate"))
("-s" "Sign" ("-s" "--sign"))
("-f" "Force" ("-f" "--force"))
("-e" "Edit message" ("-e" "--edit"))
("-a" "Annotate" ("-a" "--annotate"))
("-s" "Sign" ("-s" "--sign"))
(magit-tag:--local-user)]
[["Create"
("t" "tag" magit-tag-create)
@@ -113,7 +114,7 @@ defaulting to the tag at point.
(when tags
(magit-call-git "tag" "-d" tags))
(when remote-tags
(magit-run-git-async "push" remote (--map (concat ":" it) remote-tags))))
(magit-run-git-async "push" remote (mapcar (##concat ":" %) remote-tags))))
(defvar magit-tag-version-regexp-alist
'(("^[-._+ ]?snapshot\\.?$" . -4)
@@ -155,17 +156,15 @@ prompt for the name of the new tag using the highest existing
tag as initial input and leaving it to the user to increment the
desired part of the version string.
If `--annotate' is enabled, then prompt for the message of the
new tag. Base the proposed tag message on the message of the
highest tag, provided that that contains the corresponding
version string and substituting the new version string for that.
Otherwise propose something like \"Foo-Bar 1.2.3\", given, for
example, a TAG \"v1.2.3\" and a repository located at something
like \"/path/to/foo-bar\"."
When creating an annotated tag, prepare a message based on the message
of the highest existing tag, provided that contains the corresponding
version string, and substituting the new version string for that. If
that is not the case, propose a message using a reasonable format."
(interactive
(save-match-data
(pcase-let*
((`(,pver ,ptag ,pmsg) (car (magit--list-releases)))
((args (magit-tag-arguments))
(`(,pver ,ptag ,pmsg) (car (magit--list-releases)))
(msg (magit-rev-format "%s"))
(ver (and (string-match magit-release-commit-regexp msg)
(match-string 1 msg)))
@@ -176,6 +175,10 @@ like \"/path/to/foo-bar\"."
(user-error "Use `sisyphus-create-release' first")))
(tag (cond
((not ptag)
;; Force the user to review the message used for the
;; initial release tag, in case they do not like the
;; default format.
(cl-pushnew "--edit" args :test #'equal)
(read-string "Create first release tag: "
(if (and ver (string-match-p "\\`[0-9]" ver))
(concat "v" ver)
@@ -189,23 +192,23 @@ like \"/path/to/foo-bar\"."
(format "Create release tag (previous was %s): " ptag)
ptag))))
(ver (and (string-match magit-release-tag-regexp tag)
(match-string 2 tag)))
(args (magit-tag-arguments)))
(match-string 2 tag))))
(list tag
(and (member "--annotate" args)
(read-string
(format "Message for %S: " tag)
(cond ((and pver (string-match (regexp-quote pver) pmsg))
(replace-match ver t t pmsg))
((and ptag (string-match (regexp-quote ptag) pmsg))
(replace-match tag t t pmsg))
(t (format "%s %s"
(capitalize
(file-name-nondirectory
(directory-file-name (magit-toplevel))))
ver)))))
(and (seq-some (apply-partially
#'string-match-p
"\\`--\\(annotate\\|local-user\\|sign\\)")
args)
(cond ((and pver (string-match (regexp-quote pver) pmsg))
(replace-match ver t t pmsg))
((and ptag (string-match (regexp-quote ptag) pmsg))
(replace-match tag t t pmsg))
((format "%s %s"
(capitalize
(file-name-nondirectory
(directory-file-name (magit-toplevel))))
ver))))
args))))
(magit-run-git-async "tag" args (and msg (list "-m" msg)) tag)
(magit-run-git-with-editor "tag" args (and msg (list "-m" msg)) tag)
(set-process-sentinel
magit-this-process
(lambda (process event)
@@ -223,7 +226,7 @@ a tag qualifies as a release tag."
(mapcar
#'cdr
(nreverse
(cl-sort (cl-mapcan
(cl-sort (mapcan
(lambda (line)
(and (string-match " +" line)
(let ((tag (substring line 0 (match-beginning 0)))

View File

@@ -1,9 +1,9 @@
;;; magit-transient.el --- Support for transients -*- lexical-binding:t -*-
;; Copyright (C) 2008-2023 The Magit Project Contributors
;; Copyright (C) 2008-2025 The Magit Project Contributors
;; Author: Jonas Bernoulli <jonas@bernoul.li>
;; Maintainer: Jonas Bernoulli <jonas@bernoul.li>
;; Author: Jonas Bernoulli <emacs.magit@jonas.bernoulli.dev>
;; Maintainer: Jonas Bernoulli <emacs.magit@jonas.bernoulli.dev>
;; SPDX-License-Identifier: GPL-3.0-or-later

View File

@@ -1,6 +1,6 @@
;;; magit-version.el --- the Magit version you are using
(setq magit-version "3.3.0")
(setq magit-version "4.3.0")
(provide 'migit-version)

View File

@@ -1,9 +1,9 @@
;;; magit-wip.el --- Commit snapshots to work-in-progress refs -*- lexical-binding:t -*-
;; Copyright (C) 2008-2023 The Magit Project Contributors
;; Copyright (C) 2008-2025 The Magit Project Contributors
;; Author: Jonas Bernoulli <jonas@bernoul.li>
;; Maintainer: Jonas Bernoulli <jonas@bernoul.li>
;; Author: Jonas Bernoulli <emacs.magit@jonas.bernoulli.dev>
;; Maintainer: Jonas Bernoulli <emacs.magit@jonas.bernoulli.dev>
;; SPDX-License-Identifier: GPL-3.0-or-later
@@ -158,14 +158,13 @@ variant `magit-wip-after-save-mode'."
(let (files)
(if-let ((elt (assoc top magit--wip-activation-cache)))
(setq files (cddr elt))
(progn
(setq files (let ((default-directory top))
(magit-tracked-files)))
(push `(,top ,top ,@files)
magit--wip-activation-cache)
(unless (eq default-directory top)
(push `(,default-directory ,top ,@files)
magit--wip-activation-cache))))
(setq files (let ((default-directory top))
(magit-tracked-files)))
(push `(,top ,top ,@files)
magit--wip-activation-cache)
(unless (eq default-directory top)
(push `(,default-directory ,top ,@files)
magit--wip-activation-cache)))
(member (file-relative-name buffer-file-name) files))
(push (list default-directory nil)
magit--wip-activation-cache)
@@ -186,13 +185,13 @@ variant `magit-wip-after-save-mode'."
Also see `magit-wip-after-save-mode' which calls this function
automatically whenever a buffer visiting a tracked file is saved."
(interactive (list "wip-save %s after save"))
(unless magit--wip-inhibit-autosave
(when-let ((ref (magit-wip-get-ref)))
(magit-with-toplevel
(let ((file (file-relative-name buffer-file-name)))
(magit-wip-commit-worktree
ref (list file)
(format (or msg "autosave %s after save") file)))))))
(when-let (((not magit--wip-inhibit-autosave))
(ref (magit-wip-get-ref)))
(magit-with-toplevel
(let ((file (file-relative-name buffer-file-name)))
(magit-wip-commit-worktree
ref (list file)
(format (or msg "autosave %s after save") file))))))
;;;###autoload
(define-minor-mode magit-wip-after-apply-mode
@@ -318,11 +317,9 @@ commit message."
;; Note: `update-index' is used instead of `add'
;; because `add' will fail if a file is already
;; deleted in the temporary index.
(magit-call-git
"update-index" "--add" "--remove"
(and (magit-git-version>= "2.25.0")
"--ignore-skip-worktree-entries")
"--" files)
(magit-call-git "update-index" "--add" "--remove"
"--ignore-skip-worktree-entries"
"--" files)
(magit-with-toplevel
(magit-call-git "add" "-u" ".")))
(magit-git-string "write-tree"))))
@@ -359,9 +356,9 @@ commit message."
(setq msg (concat
(cond ((= len 0) "autosave tracked files")
((> len 1) (format "autosave %s files" len))
(t (concat "autosave "
(file-relative-name (car files)
(magit-toplevel)))))
((concat "autosave "
(file-relative-name (car files)
(magit-toplevel)))))
msg))))
(magit-update-ref wipref msg
(magit-git-string "commit-tree" "--no-gpg-sign"
@@ -461,7 +458,7 @@ many \"branches\" of each wip ref are shown."
(while (and reflog (> count 1))
;; "start autosaving ..." is the current message, but it used
;; to be "restart autosaving ...", and those messages may
;; still be around (e.g., if gc.reflogExpire is to "never").
;; still be around (e.g., if gc.reflogExpire is set to "never").
(setq reflog (cl-member "^[^ ]+ [^:]+: \\(?:re\\)?start autosaving"
reflog :test #'string-match-p))
(when (and (cadr reflog)

View File

@@ -1,9 +1,9 @@
;;; magit-worktree.el --- Worktree support -*- lexical-binding:t -*-
;; Copyright (C) 2008-2023 The Magit Project Contributors
;; Copyright (C) 2008-2025 The Magit Project Contributors
;; Author: Jonas Bernoulli <jonas@bernoul.li>
;; Maintainer: Jonas Bernoulli <jonas@bernoul.li>
;; Author: Jonas Bernoulli <emacs.magit@jonas.bernoulli.dev>
;; Maintainer: Jonas Bernoulli <emacs.magit@jonas.bernoulli.dev>
;; SPDX-License-Identifier: GPL-3.0-or-later
@@ -61,20 +61,20 @@ Used by `magit-worktree-checkout' and `magit-worktree-branch'."
(list (funcall magit-worktree-read-directory-name-function
(format "Checkout %s in new worktree: " branch))
branch)))
(magit-run-git "worktree" "add" (magit--expand-worktree path) branch)
(magit-diff-visit-directory path))
(when (zerop (magit-run-git "worktree" "add"
(magit--expand-worktree path) branch))
(magit-diff-visit-directory path)))
;;;###autoload
(defun magit-worktree-branch (path branch start-point &optional force)
(defun magit-worktree-branch (path branch start-point)
"Create a new BRANCH and check it out in a new worktree at PATH."
(interactive
`(,(funcall magit-worktree-read-directory-name-function
"Create worktree: ")
,@(magit-branch-read-args "Create and checkout branch")
,current-prefix-arg))
(magit-run-git "worktree" "add" (if force "-B" "-b")
branch (magit--expand-worktree path) start-point)
(magit-diff-visit-directory path))
,@(magit-branch-read-args "Create and checkout branch")))
(when (zerop (magit-run-git "worktree" "add" "-b" branch
(magit--expand-worktree path) start-point))
(magit-diff-visit-directory path)))
;;;###autoload
(defun magit-worktree-move (worktree path)
@@ -113,7 +113,7 @@ The primary worktree cannot be deleted."
(user-error "Deleting %s would delete the shared .git directory" worktree)
(let ((primary (file-name-as-directory (caar (magit-list-worktrees)))))
(magit-confirm-files (if magit-delete-by-moving-to-trash 'trash 'delete)
(list "worktree"))
(list worktree))
(when (file-exists-p worktree)
(let ((delete-by-moving-to-trash magit-delete-by-moving-to-trash))
(delete-directory worktree t magit-delete-by-moving-to-trash)))
@@ -160,7 +160,7 @@ If there is only one worktree, then insert nothing."
(let ((worktrees (magit-list-worktrees)))
(when (length> worktrees 1)
(magit-insert-section (worktrees)
(magit-insert-heading "Worktrees:")
(magit-insert-heading t "Worktrees")
(let* ((cols
(mapcar
(lambda (config)
@@ -178,7 +178,7 @@ If there is only one worktree, then insert nothing."
(bare "(bare)"))
config)))
worktrees))
(align (1+ (apply #'max (--map (string-width (car it)) cols)))))
(align (1+ (apply #'max (mapcar (##string-width (car %)) cols)))))
(pcase-dolist (`(,head . ,config) cols)
(magit--insert-worktree
config

View File

@@ -1,10 +1,10 @@
;;; magit.el --- A Git porcelain inside Emacs -*- lexical-binding:t; coding:utf-8 -*-
;; Copyright (C) 2008-2023 The Magit Project Contributors
;; Copyright (C) 2008-2025 The Magit Project Contributors
;; Author: Marius Vollmer <marius.vollmer@gmail.com>
;; Jonas Bernoulli <jonas@bernoul.li>
;; Maintainer: Jonas Bernoulli <jonas@bernoul.li>
;; Jonas Bernoulli <emacs.magit@jonas.bernoulli.dev>
;; Maintainer: Jonas Bernoulli <emacs.magit@jonas.bernoulli.dev>
;; Kyle Meyer <kyle@kyleam.com>
;; Former-Maintainers:
;; Nicolas Dudebout <nicolas.dudebout@gatech.edu>
@@ -17,16 +17,15 @@
;; Homepage: https://github.com/magit/magit
;; Keywords: git tools vc
;; Package-Version: 3.3.0.50-git
;; Package-Version: 4.3.0
;; Package-Requires: (
;; (emacs "25.1")
;; (compat "29.1.3.4")
;; (dash "2.19.1")
;; (git-commit "3.3.0")
;; (magit-section "3.3.0")
;; (emacs "27.1")
;; (compat "30.0.2.0")
;; (llama "0.6.0")
;; (magit-section "4.3.0")
;; (seq "2.24")
;; (transient "0.3.6")
;; (with-editor "3.0.5"))
;; (transient "0.8.4")
;; (with-editor "3.4.3"))
;; SPDX-License-Identifier: GPL-3.0-or-later
@@ -194,7 +193,7 @@ and/or `magit-branch-remote-head'."
(defface magit-keyword-squash
'((t :inherit font-lock-warning-face))
"Face for squash! and fixup! keywords in commit messages."
"Face for squash! and similar keywords in commit messages."
:group 'magit-faces)
(defface magit-signature-good
@@ -259,20 +258,20 @@ the current Emacs session.
If the value is nil, no bindings are added.
If `default', maybe add:
If \\+`default', maybe add:
C-x g `magit-status'
C-x M-g `magit-dispatch'
C-c M-g `magit-file-dispatch'
\\`C-x' \\`g' `magit-status'
\\`C-x' \\`M-g' `magit-dispatch'
\\`C-c' \\`M-g' `magit-file-dispatch'
If `recommended', maybe add:
C-x g `magit-status'
C-c g `magit-dispatch'
C-c f `magit-file-dispatch'
\\`C-x' \\`g' `magit-status'
\\`C-c' \\`g' `magit-dispatch'
\\`C-c' \\`f' `magit-file-dispatch'
These bindings are strongly recommended, but we cannot use
them by default, because the \"C-c <LETTER>\" namespace is
them by default, because the \\`C-c <LETTER>' namespace is
strictly reserved for bindings added by the user.
The bindings in the chosen set may be added when
@@ -440,7 +439,7 @@ This affects `magit-git-command', `magit-git-command-topdir',
(defun magit-git-command (command)
"Execute COMMAND asynchronously; display output.
Interactively, prompt for COMMAND in the minibuffer. \"git \" is
Interactively, prompt for COMMAND in the minibuffer. \"git \" is
used as initial input, but can be deleted to run another command.
With a prefix argument COMMAND is run in the top-level directory
@@ -452,7 +451,7 @@ of the current working tree, otherwise in `default-directory'."
(defun magit-git-command-topdir (command)
"Execute COMMAND asynchronously; display output.
Interactively, prompt for COMMAND in the minibuffer. \"git \" is
Interactively, prompt for COMMAND in the minibuffer. \"git \" is
used as initial input, but can be deleted to run another command.
COMMAND is run in the top-level directory of the current
@@ -482,9 +481,10 @@ is run in the top-level directory of the current working tree."
(defun magit--shell-command (command &optional directory)
(let ((default-directory (or directory default-directory)))
(with-environment-variables (("GIT_PAGER" "cat"))
(magit--with-connection-local-variables
(magit-start-process shell-file-name nil
shell-command-switch command))))
(with-connection-local-variables
(magit-with-editor
(magit-start-process shell-file-name nil
shell-command-switch command)))))
(magit-process-buffer))
(defun magit-read-shell-command (&optional toplevel initial-input)
@@ -501,20 +501,29 @@ is run in the top-level directory of the current working tree."
;;; Shared Infix Arguments
(transient-define-argument magit:--signoff ()
:description "Add Signed-off-by trailer"
:class 'transient-switch
:key "+s"
:shortarg "-s"
:argument "--signoff"
:level 6)
(transient-define-argument magit:--gpg-sign ()
:description "Sign using gpg"
:class 'transient-option
:shortarg "-S"
:argument "--gpg-sign="
:allow-empty t
:reader #'magit-read-gpg-signing-key)
:reader #'magit-read-gpg-signing-key
:level 5)
(defvar magit-gpg-secret-key-hist nil)
(defun magit-read-gpg-secret-key
(prompt &optional initial-input history predicate default)
(require 'epa)
(let* ((keys (cl-mapcan
(let* ((keys (mapcan
(lambda (cert)
(and (or (not predicate)
(funcall predicate cert))
@@ -561,6 +570,7 @@ is run in the top-level directory of the current working tree."
(1 'font-lock-keyword-face)
(2 'font-lock-function-name-face nil t))
(,(concat "(" (regexp-opt '("magit-insert-section"
"magit-insert-heading"
"magit-section-case"
"magit-bind-match-strings"
"magit-with-temp-index"
@@ -579,7 +589,7 @@ is run in the top-level directory of the current working tree."
Use the function by the same name instead of this variable.")
;;;###autoload
(defun magit-version (&optional print-dest interactive)
(defun magit-version (&optional print-dest interactive nowarn)
"Return the version of Magit currently in use.
If optional argument PRINT-DEST is non-nil, also print the used
@@ -597,7 +607,7 @@ the output in the kill ring.
'("magit.el" "magit.el.gz")))
(let ((load-suffixes (reverse load-suffixes))) ; prefer .el than .elc
(setq toplib (locate-library "magit"))))
(setq toplib (and toplib (magit--straight-chase-links toplib)))
(setq toplib (and toplib (magit--chase-links toplib)))
(push toplib debug)
(when toplib
(let* ((topdir (file-name-directory toplib))
@@ -605,7 +615,7 @@ the output in the kill ring.
".git" (file-name-directory
(directory-file-name topdir))))
(static (locate-library "magit-version.el" nil (list topdir)))
(static (and static (magit--straight-chase-links static))))
(static (and static (magit--chase-links static))))
(or (progn
(push 'repo debug)
(when (and (file-exists-p gitdir)
@@ -657,7 +667,7 @@ the output in the kill ring.
(if (stringp magit-version)
(when print-dest
(let ((str (format
"Magit %s%s, Transient %s, Git %s, Emacs %s, %s"
"Magit %s%s, Transient %s,%s Git %s, Emacs %s, %s"
(or magit-version "(unknown)")
(or (and (ignore-errors
(magit--version>= magit-version "2008"))
@@ -679,6 +689,18 @@ the output in the kill ring.
(locate-library "transient.el" t))
(lm-header "Package-Version"))))
"(unknown)")
(let ((lib (locate-library "forge.el" t)))
(or (and lib
(format
" Forge %s,"
(or (ignore-errors
(require 'lisp-mnt)
(with-temp-buffer
(insert-file-contents lib)
(and (fboundp 'lm-header)
(lm-header "Package-Version"))))
"(unknown)")))
""))
(magit--safe-git-version)
emacs-version
system-type)))
@@ -689,8 +711,7 @@ the output in the kill ring.
(setq magit-version 'error)
(when magit-version
(push magit-version debug))
(unless (equal (getenv "CI") "true")
;; The repository is a sparse clone.
(unless (or nowarn (equal (getenv "CI") "true"))
(message "Cannot determine Magit's version %S" debug)))
magit-version))
@@ -770,7 +791,7 @@ For X11 something like ~/.xinitrc should work.\n"
(unless (bound-and-true-p byte-compile-current-file)
(if after-init-time
(progn (magit-startup-asserts)
(magit-version))
(magit-version nil nil t))
(add-hook 'after-init-hook #'magit-startup-asserts t)
(add-hook 'after-init-hook #'magit-version t)))

File diff suppressed because it is too large Load Diff