update packages and add valign

This commit is contained in:
2026-04-05 20:00:27 +02:00
parent b062fb98e3
commit 03fb00e374
640 changed files with 109768 additions and 39311 deletions

View File

@@ -1,11 +1,12 @@
((nil
(indent-tabs-mode . nil))
(emacs-lisp-mode
(checkdoc-allow-quoting-nil-and-t . t)
(lisp-indent-local-overrides . ((cond . 0) (interactive . 0))))
(makefile-mode
(indent-tabs-mode . t)
(outline-regexp . "#\\(#+\\)")
(mode . outline-minor))
(emacs-lisp-mode
(checkdoc-allow-quoting-nil-and-t . t))
(mode . outline-minor)
(outline-regexp . "#\\(#+\\)"))
(git-commit-mode
(git-commit-major-mode . git-commit-elisp-text-mode))
(".github/PULL_REQUEST_TEMPLATE"

View File

@@ -49,6 +49,7 @@ All Contributors
- Alex Kreisher
- Alex Ott
- Allen Li
- Andrea Alberti
- Andreas Fuchs
- Andreas Liljeqvist
- Andreas Rottmann
@@ -230,6 +231,7 @@ All Contributors
- Lele Gaifax
- Lénaïc Huard
- Leo Liu
- Leonard Lausen
- Leonardo Etcheverry
- Leo Vivier
- Li Chen

View File

@@ -1,6 +1,6 @@
;;; git-commit.el --- Edit Git commit messages -*- lexical-binding:t; coding:utf-8 -*-
;; Copyright (C) 2008-2025 The Magit Project Contributors
;; Copyright (C) 2008-2026 The Magit Project Contributors
;; Author: Jonas Bernoulli <emacs.magit@jonas.bernoulli.dev>
;; Sebastian Wiesner <lunaryorn@gmail.com>
@@ -110,6 +110,7 @@
(require 'transient)
(require 'with-editor)
(defvar dabbrev--abbrev-char-regexp)
(defvar diff-default-read-only)
(defvar flyspell-generic-check-word-predicate)
(defvar font-lock-beg)
@@ -118,6 +119,12 @@
(defvar git-commit-need-summary-line)
(declare-function dabbrev--reset-global-variables "dabbrev" ())
(declare-function dabbrev-capf "dabbrev" ())
(declare-function magit-commit-diff--args "magit-commit" ())
(declare-function magit-diff--modified-defuns "magit-diff" ())
(declare-function magit-diff-arguments "magit-diff" (&optional mode))
(define-obsolete-variable-alias
'git-commit-known-pseudo-headers
'git-commit-trailers
@@ -132,42 +139,6 @@
:link '(info-link "(magit)Editing Commit Messages")
:group 'tools)
(define-minor-mode global-git-commit-mode
"Edit Git commit messages.
This global mode arranges for `git-commit-setup' to be called
when a Git commit message file is opened. That usually happens
when Git uses the Emacsclient as $GIT_EDITOR to have the user
provide such a commit message.
Loading the library `git-commit' by default enables this mode,
but the library is not automatically loaded because doing that
would pull in many dependencies and increase startup time too
much. You can either rely on `magit' loading this library or
you can load it explicitly. Autoloading is not an alternative
because in this case autoloading would immediately trigger
full loading."
:group 'git-commit
:type 'boolean
:global t
:init-value t
:initialize
(lambda (symbol exp)
(custom-initialize-default symbol exp)
(when global-git-commit-mode
(add-hook 'find-file-hook #'git-commit-setup-check-buffer)
(remove-hook 'after-change-major-mode-hook
#'git-commit-setup-font-lock-in-buffer)))
(cond
(global-git-commit-mode
(add-hook 'find-file-hook #'git-commit-setup-check-buffer)
(add-hook 'after-change-major-mode-hook
#'git-commit-setup-font-lock-in-buffer))
(t
(remove-hook 'find-file-hook #'git-commit-setup-check-buffer)
(remove-hook 'after-change-major-mode-hook
#'git-commit-setup-font-lock-in-buffer))))
(defcustom git-commit-major-mode #'text-mode
"Major mode used to edit Git commit messages.
@@ -198,9 +169,11 @@ Also note that `git-commit-mode' (which see) is not a major-mode.")
(defcustom git-commit-setup-hook
(list #'git-commit-ensure-comment-gap
#'git-commit-save-message
#'git-commit-setup-capf
#'git-commit-setup-changelog-support
#'git-commit-turn-on-auto-fill
#'git-commit-setup-auto-fill
#'git-commit-propertize-diff
#'git-commit-collapse-diff
#'bug-reference-mode)
"Hook run at the end of `git-commit-setup'."
:group 'git-commit
@@ -208,12 +181,14 @@ Also note that `git-commit-mode' (which see) is not a major-mode.")
:get #'magit-hook-custom-get
:options '(git-commit-ensure-comment-gap
git-commit-save-message
git-commit-setup-capf
git-commit-setup-changelog-support
magit-generate-changelog
git-commit-turn-on-auto-fill
git-commit-turn-on-orglink
git-commit-turn-on-flyspell
git-commit-setup-auto-fill
git-commit-setup-orglink
git-commit-setup-flyspell
git-commit-propertize-diff
git-commit-collapse-diff
bug-reference-mode))
(defcustom git-commit-finish-query-functions
@@ -363,6 +338,11 @@ In this context a \"keyword\" is text surrounded by brackets."
"Face used for headings in commit message comments."
:group 'git-commit-faces)
(defface git-commit-comment-button
'((t :inherit git-commit-comment-heading :underline t))
"Face used for buttons in commit message comments."
:group 'git-commit-faces)
(defface git-commit-comment-file
'((t :inherit git-commit-trailer-value))
"Face used for file names in commit message comments."
@@ -437,7 +417,43 @@ the redundant bindings, then set this to nil, before loading
["Cancel" with-editor-cancel t]
["Commit" with-editor-finish t]))
;;; Hooks
;;; Global Mode
(define-minor-mode global-git-commit-mode
"Edit Git commit messages.
This global mode arranges for `git-commit-setup' to be called
when a Git commit message file is opened. That usually happens
when Git uses the Emacsclient as $GIT_EDITOR to have the user
provide such a commit message.
Loading the library `git-commit' by default enables this mode,
but the library is not automatically loaded because doing that
would pull in many dependencies and increase startup time too
much. You can either rely on `magit' loading this library or
you can load it explicitly. Autoloading is not an alternative
because in this case autoloading would immediately trigger
full loading."
:group 'git-commit
:type 'boolean
:global t
:init-value t
:initialize
(lambda (symbol exp)
(custom-initialize-default symbol exp)
(when global-git-commit-mode
(add-hook 'find-file-hook #'git-commit-setup-check-buffer)
(remove-hook 'after-change-major-mode-hook
#'git-commit-setup-font-lock-in-buffer)))
(cond
(global-git-commit-mode
(add-hook 'find-file-hook #'git-commit-setup-check-buffer)
(add-hook 'after-change-major-mode-hook
#'git-commit-setup-font-lock-in-buffer))
(t
(remove-hook 'find-file-hook #'git-commit-setup-check-buffer)
(remove-hook 'after-change-major-mode-hook
#'git-commit-setup-font-lock-in-buffer))))
(defconst git-commit-filename-regexp "/\\(\
\\(\\(COMMIT\\|NOTES\\|PULLREQ\\|MERGEREQ\\|TAG\\)_EDIT\\|MERGE_\\|\\)MSG\
@@ -458,8 +474,6 @@ the redundant bindings, then set this to nil, before loading
(string-match-p git-commit-filename-regexp buffer-file-name))
(git-commit-setup)))
(defvar git-commit-mode)
(defun git-commit-file-not-found ()
;; cygwin git will pass a cygwin path (/cygdrive/c/foo/.git/...),
;; try to handle this in window-nt Emacs.
@@ -480,6 +494,10 @@ the redundant bindings, then set this to nil, before loading
(when (eq system-type 'windows-nt)
(add-hook 'find-file-not-found-functions #'git-commit-file-not-found))
;;; Local Mode
(defvar git-commit-mode)
(defconst git-commit-default-usage-message "\
Type \\[with-editor-finish] to finish, \
\\[with-editor-cancel] to cancel, and \
@@ -504,6 +522,12 @@ Used as the local value of `header-line-format', in buffer using
(setq git-commit-usage-message nil) ; show a shorter message")
(defun git-commit-setup ()
;; If an error occurs when `emacsclient' is used, and it turns out
;; to not be triggered by something in this function, then another
;; likely source are functions on `server-switch-hook'. Debugging
;; is suppressed while running that hook, so it may be necessary to
;; force debugging by modifying those functions directly. Enabling
;; `magit-process-record-invocations' may also help.
(let ((gitdir default-directory)
(cd (and git-commit-cd-to-toplevel
(or (car (rassoc default-directory magit--separated-gitdirs))
@@ -568,9 +592,9 @@ Used as the local value of `header-line-format', in buffer using
(with-demoted-errors "Error running git-commit-setup-hook: %S"
(run-hooks 'git-commit-setup-hook))
(set-buffer-modified-p nil)
(when-let ((format git-commit-header-line-format))
(when$ git-commit-header-line-format
(setq header-line-format
(if (stringp format) (substitute-command-keys format) format)))
(if (stringp $) (substitute-command-keys $) $)))
(when git-commit-usage-message
(setq with-editor-usage-message git-commit-usage-message))
(with-editor-usage-message))
@@ -585,6 +609,8 @@ used."
(put 'git-commit-mode 'permanent-local t)
;;; Setup
(defun git-commit-ensure-comment-gap ()
"Separate initial empty line from initial comment.
If the buffer begins with an empty line followed by a comment, insert
@@ -595,25 +621,36 @@ the input isn't tacked to the comment."
(when (looking-at (format "\\`\n%s" comment-start))
(open-line 1))))
(defun git-commit-setup-capf ()
"Teach `complete-symbol' about `dabbrev-capf'.
When \"git commit\"'s \"--verbose\" argument is used, this allows
completing modified symbols and other text appearing in the diff."
(require 'dabbrev)
(unless dabbrev--abbrev-char-regexp
;; Initialize (not "reset") variables. See #5545.
(dabbrev--reset-global-variables))
(add-hook 'completion-at-point-functions #'dabbrev-capf -90 t))
(defun git-commit-setup-changelog-support ()
"Treat ChangeLog entries as unindented paragraphs."
(setq-local fill-paragraph-function #'log-edit-fill-entry)
(setq-local fill-indent-according-to-mode t)
(setq-local paragraph-start (concat paragraph-start "\\|\\*\\|(")))
(defun git-commit-turn-on-auto-fill ()
(defun git-commit-setup-auto-fill ()
"Unconditionally turn on Auto Fill mode.
Ensure auto filling happens everywhere, except in the summary line."
(auto-fill-mode 1)
(setq-local comment-auto-fill-only-comments nil)
(when git-commit-need-summary-line
(setq-local auto-fill-function #'git-commit-auto-fill-except-summary)))
(setq-local auto-fill-function #'git-commit--auto-fill-except-summary)))
(defun git-commit-auto-fill-except-summary ()
(defun git-commit--auto-fill-except-summary ()
"Do not fill summary line."
(unless (eq (line-beginning-position) 1)
(do-auto-fill)))
(defun git-commit-turn-on-orglink ()
(defun git-commit-setup-orglink ()
"Turn on Orglink mode if it is available.
If `git-commit-major-mode' is `org-mode', then silently forgo
turning on `orglink-mode'."
@@ -623,15 +660,14 @@ turning on `orglink-mode'."
(setq-local orglink-match-anywhere t)
(orglink-mode 1)))
(defun git-commit-turn-on-flyspell ()
(defun git-commit-setup-flyspell ()
"Unconditionally turn on Flyspell mode.
Also check text that is already in the buffer, while avoiding to check
most text that Git will strip from the final message, such as the last
comment and anything below the cut line (\"--- >8 ---\")."
(require 'flyspell)
(flyspell-mode 1)
(setq flyspell-generic-check-word-predicate
#'git-commit-flyspell-verify)
(setq flyspell-generic-check-word-predicate #'git-commit--flyspell-verify)
(let ((end nil)
;; The "cut line" is defined in "git/wt-status.c". It appears
;; in the commit message when `commit.verbose' is set to true.
@@ -647,10 +683,37 @@ comment and anything below the cut line (\"--- >8 ---\")."
(setq end (point)))
(flyspell-region (point-min) end)))
(defun git-commit-flyspell-verify ()
(defun git-commit--flyspell-verify ()
"Do not check spelling in comments."
(not (= (char-after (line-beginning-position))
(aref comment-start 0))))
(defun git-commit-collapse-diff ()
"Collapse inline diff and add button to allow expanding it."
(save-excursion
(goto-char (point-min))
(when (re-search-forward (format "%s -+ >8 -+" comment-start) nil t)
(let ((elt '(git-commit-diff t)))
(add-to-invisibility-spec elt)
(make-button (line-beginning-position) (point)
'face 'git-commit-comment-button
'keymap (define-keymap :parent button-map
"<return>" #'push-button
"<tab>" #'push-button)
'action (lambda (_)
(if (memq elt buffer-invisibility-spec)
(remove-from-invisibility-spec elt)
(add-to-invisibility-spec elt))
;; KLUDGE Force "redisplay".
(when-let ((w1 (selected-window))
(w2 (next-window)))
(select-window w2)
(select-window w1)))))
(let ((ov (make-overlay (point) (point-max))))
(overlay-put ov 'invisible 'git-commit-diff)))))
;;; Finish
(defun git-commit-finish-query-functions (force)
(run-hook-with-args-until-failure
'git-commit-finish-query-functions force))
@@ -722,9 +785,9 @@ With a numeric prefix ARG, go forward ARG messages."
"Search backward through message history for a match for STRING.
Save current message first."
(interactive
(list (read-string (format-prompt "Comment substring"
log-edit-last-comment-match)
nil nil log-edit-last-comment-match)))
(list (read-string (format-prompt "Comment substring"
log-edit-last-comment-match)
nil nil log-edit-last-comment-match)))
(cl-letf (((symbol-function #'log-edit-previous-comment)
(symbol-function #'git-commit-prev-message)))
(log-edit-comment-search-backward string)))
@@ -733,9 +796,9 @@ Save current message first."
"Search forward through message history for a match for STRING.
Save current message first."
(interactive
(list (read-string (format-prompt "Comment substring"
log-edit-last-comment-match)
nil nil log-edit-last-comment-match)))
(list (read-string (format-prompt "Comment substring"
log-edit-last-comment-match)
nil nil log-edit-last-comment-match)))
(cl-letf (((symbol-function #'log-edit-previous-comment)
(symbol-function #'git-commit-prev-message)))
(log-edit-comment-search-forward string)))
@@ -784,6 +847,55 @@ Save current message first."
(setq str (replace-match "\n" t t str)))
str))))
;;; Changelog
(defun git-commit--modified-defuns ()
(if (save-excursion
(goto-char (point-min))
(re-search-forward "^diff --git" nil t))
(magit-diff--modified-defuns)
(with-temp-buffer
(pcase-let ((`(,rev ,arg) (magit-commit-diff--args)))
(save-excursion
(magit-git-insert "diff" "-p" arg (car (magit-diff-arguments)) rev)))
(magit-diff--modified-defuns))))
;;;###autoload
(defun git-commit-insert-changelog-gnu ()
"Insert a GNU-style changelog at point while authorig a commit message.
The modified definitions are extracted from the diff in the message
buffer, which is only available if \"git commit\" was invoked with
\"--verbose\"."
(interactive)
(unless git-commit-mode
(user-error "Not in a commit message buffer"))
;; Like `change-log-insert-entries'.
(pcase-dolist (`(,file . ,defuns) (git-commit--modified-defuns))
(if (not defuns)
(insert "* " file ":\n")
(insert "* " file " ")
(dolist (def defuns)
(insert "(" def "):\n")))))
;;;###autoload
(defun git-commit-insert-changelog-plain ()
"Insert a simple changelog at point while authorig a commit message.
Defuns are slightly indented and quoted like in elisp docstrings.
The exact format is still subject to change.
The modified definitions are extracted from the diff in the message
buffer, which is only available if \"git commit\" was invoked with
\"--verbose\"."
(interactive)
(unless git-commit-mode
(user-error "Not in a commit message buffer"))
(pcase-dolist (`(,file . ,defuns) (git-commit--modified-defuns))
(insert file ":\n")
(dolist (def defuns)
(insert " `" def "'\n"))))
;;; Trailers
(transient-define-prefix git-commit-insert-trailer ()
@@ -793,19 +905,22 @@ See also manpage git-interpret-trailer(1). This command does
not use that Git command, but the initial description still
serves as a good introduction."
[[:description (##cond (prefix-arg
"Insert ... by someone ")
("Insert ... by yourself"))
"Insert trailer ... by someone ")
("Insert trailer ... by yourself"))
("a" "Ack" git-commit-ack)
("m" "Modified" git-commit-modified)
("r" "Reviewed" git-commit-review)
("s" "Signed-off" git-commit-signoff)
("t" "Tested" git-commit-test)]
["Insert ... by someone"
["Insert trailer ... by someone"
("C-c" "Cc" git-commit-cc)
("C-r" "Reported" git-commit-reported)
("C-i" "Suggested" git-commit-suggested)
("C-a" "Co-authored" git-commit-co-authored)
("C-d" "Co-developed" git-commit-co-developed)]])
("C-d" "Co-developed" git-commit-co-developed)]]
["Insert changelog"
("l g" "GNU-style" git-commit-insert-changelog-gnu)
("l p" "plain" git-commit-insert-changelog-plain)])
(defun git-commit-ack (name mail)
"Insert a trailer acknowledging that you have looked at the commit."
@@ -915,20 +1030,20 @@ completion candidates. The input must have the form \"NAME <EMAIL>\"."
(setq leading-comment-end (point))
(goto-char (point-max))
(cond
;; Look backwards for existing trailers.
((re-search-backward (git-commit--trailer-regexp) nil t)
(end-of-line)
(insert ?\n string)
(unless (= (char-after) ?\n)
(insert ?\n)))
;; Or place the new trailer right before the first non-leading
;; comments.
(t
(while (re-search-backward (concat "^" comment-start)
leading-comment-end t))
(unless (looking-back "\n\n" nil)
(insert ?\n))
(insert string ?\n))))
;; Look backwards for existing trailers.
((re-search-backward (git-commit--trailer-regexp) nil t)
(end-of-line)
(insert ?\n string)
(unless (= (char-after) ?\n)
(insert ?\n)))
;; Or place the new trailer right before the first non-leading
;; comments.
(t
(while (re-search-backward (concat "^" comment-start)
leading-comment-end t))
(unless (looking-back "\n\n" nil)
(insert ?\n))
(insert string ?\n))))
(unless (or (eobp) (= (char-after) ?\n))
(insert ?\n))))
@@ -1225,6 +1340,19 @@ commit, then the hook is not run at all."
'git-commit-trailer-token
"git-commit 4.0.0")
(define-obsolete-function-alias
'git-commit-turn-on-auto-fill
'git-commit-setup-auto-fill
"git-commit 4.6.0")
(define-obsolete-function-alias
'git-commit-turn-on-flyspell
'git-commit-setup-flyspell
"git-commit 4.6.0")
(define-obsolete-function-alias
'git-commit-turn-on-orglink
'git-commit-setup-orglink
"git-commit 4.6.0")
(provide 'git-commit)
;; Local Variables:
;; read-symbol-shorthands: (
@@ -1232,6 +1360,7 @@ commit, then the hook is not run at all."
;; ("and>" . "cond-let--and>")
;; ("and-let" . "cond-let--and-let")
;; ("if-let" . "cond-let--if-let")
;; ("when$" . "cond-let--when$")
;; ("when-let" . "cond-let--when-let")
;; ("while-let" . "cond-let--while-let")
;; ("match-string" . "match-string")

View File

@@ -1,6 +1,6 @@
;;; git-rebase.el --- Edit Git rebase files -*- lexical-binding:t -*-
;; Copyright (C) 2008-2025 The Magit Project Contributors
;; Copyright (C) 2008-2026 The Magit Project Contributors
;; Author: Phil Jackson <phil@shellarchive.co.uk>
;; Maintainer: Jonas Bernoulli <emacs.magit@jonas.bernoulli.dev>
@@ -371,14 +371,14 @@ BATCH also ignores commented lines."
(and (looking-at (concat re-start re)) type)))
git-rebase-line-regexps)]
(git-rebase-action
:action-type type
:action (and-let ((action (match-str 1)))
(or (cdr (assoc action git-rebase-short-options))
action))
:action-options (match-str 2)
:target (match-str 3)
:trailer (match-str 5)
:comment-p (and (match-str 99) t)))
:action-type type
:action (and-let ((action (match-str 1)))
(or (cdr (assoc action git-rebase-short-options))
action))
:action-options (match-str 2)
:target (match-str 3)
:trailer (match-str 5)
:comment-p (and (match-str 99) t)))
((not batch)
;; Use empty object rather than nil to ease handling.
(git-rebase-action)))))
@@ -399,23 +399,23 @@ of its action type."
(with-slots (action-type target trailer comment-p)
(git-rebase-current-line)
(cond
((and action (eq action-type 'commit))
(let ((inhibit-read-only t))
(magit-delete-line)
(insert (concat action " " target " "))
(when (magit-git-version>= "2.50.0")
(insert "# "))
(insert (concat trailer "\n"))))
((and (not action) action-type)
(let ((inhibit-read-only t))
(if comment-p
(delete-region beg (+ beg 2))
(insert comment-start " ")))
(forward-line))
;; In the case of --rebase-merges, commit lines may have
;; other lines with other action types, empty lines, and
;; "Branch" comments interspersed. Move along.
((forward-line)))))
((and action (eq action-type 'commit))
(let ((inhibit-read-only t))
(magit-delete-line)
(insert (concat action " " target " "))
(when (magit-git-version>= "2.50.0")
(insert "# "))
(insert (concat trailer "\n"))))
((and (not action) action-type)
(let ((inhibit-read-only t))
(if comment-p
(delete-region beg (+ beg 2))
(insert comment-start " ")))
(forward-line))
;; In the case of --rebase-merges, commit lines may have
;; other lines with other action types, empty lines, and
;; "Branch" comments interspersed. Move along.
((forward-line)))))
(goto-char (cond (git-rebase-auto-advance end-marker)
(pt-below-p (1- end-marker))
(beg)))
@@ -435,15 +435,15 @@ point or mark. If the region isn't active and FALLBACK is
non-nil, return the beginning and end of the current rebase line,
if any."
(cond
((use-region-p)
(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))))))
((use-region-p)
(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))))))
(defun git-rebase-move-line-down (n)
"Move the current commit (or command) N lines down.
@@ -875,22 +875,22 @@ except for the \"pick\" command."
(replace-match (make-string 10 ?\s) t t nil 1))
(setq cmd (intern (concat "git-rebase-" (match-str 4))))
(cond
((not (fboundp cmd))
(delete-line))
((eq cmd 'git-rebase-fixup)
(delete-line)
(git-rebase--insert-descriptions git-rebase-fixup-descriptions))
(t
(add-text-properties (line-beginning-position)
(1+ (line-end-position))
'(font-lock-face font-lock-comment-face))
(replace-match " " t t nil 2)
(replace-match
(string-pad
(save-match-data
(substitute-command-keys (format "\\[%s]" cmd)))
8)
t t nil 3))))))))))
((not (fboundp cmd))
(delete-line))
((eq cmd 'git-rebase-fixup)
(delete-line)
(git-rebase--insert-descriptions git-rebase-fixup-descriptions))
(t
(add-text-properties (line-beginning-position)
(1+ (line-end-position))
'(font-lock-face font-lock-comment-face))
(replace-match " " t t nil 2)
(replace-match
(string-pad
(save-match-data
(substitute-command-keys (format "\\[%s]" cmd)))
8)
t t nil 3))))))))))
(defun git-rebase--insert-descriptions (alist)
(pcase-dolist (`(,cmd . ,desc) alist)
@@ -948,6 +948,7 @@ is used as a value for `imenu-extract-index-name-function'."
;; ("and>" . "cond-let--and>")
;; ("and-let" . "cond-let--and-let")
;; ("if-let" . "cond-let--if-let")
;; ("when$" . "cond-let--when$")
;; ("when-let" . "cond-let--when-let")
;; ("while-let" . "cond-let--while-let")
;; ("match-string" . "match-string")

View File

@@ -1,6 +1,6 @@
;;; magit-apply.el --- Apply Git diffs -*- lexical-binding:t -*-
;; Copyright (C) 2008-2025 The Magit Project Contributors
;; Copyright (C) 2008-2026 The Magit Project Contributors
;; Author: Jonas Bernoulli <emacs.magit@jonas.bernoulli.dev>
;; Maintainer: Jonas Bernoulli <emacs.magit@jonas.bernoulli.dev>
@@ -115,19 +115,19 @@ is a member of `magit-post-unstage-hook-commands'."
With a prefix argument fallback to a 3-way merge. Doing
so causes the change to be applied to the index as well."
(interactive (and current-prefix-arg (list "--3way")))
(when-let ((s (magit-apply--get-selection)))
(when$ (magit-apply--get-selection)
(pcase (list (magit-diff-type) (magit-diff-scope))
(`(,(or 'unstaged 'staged) ,_)
(user-error "Change is already in the working tree"))
(`(untracked ,(or 'file 'files))
(call-interactively #'magit-am))
(`(,_ region) (magit-apply-region s args))
(`(,_ hunk) (magit-apply-hunk s args))
(`(,_ hunks) (magit-apply-hunks s args))
(`(,_ region) (magit-apply-region $ args))
(`(,_ hunk) (magit-apply-hunk $ args))
(`(,_ hunks) (magit-apply-hunks $ args))
(`(rebase-sequence file)
(call-interactively #'magit-patch-apply))
(`(,_ file) (magit-apply-diff s args))
(`(,_ files) (magit-apply-diffs s args)))))
(`(,_ file) (magit-apply-diff $ args))
(`(,_ files) (magit-apply-diffs $ args)))))
(defun magit-apply--section-content (section)
(buffer-substring-no-properties (if (magit-hunk-section-p section)
@@ -310,17 +310,17 @@ at point, stage the file but not its content."
"Read one or more files and stage all changes in those files.
With prefix argument FORCE, offer ignored files for completion."
(interactive
(let* ((choices (if current-prefix-arg
(magit-ignored-files)
(nconc (magit-unstaged-files)
(magit-untracked-files))))
(default (or (magit-section-value-if 'file)
(magit-file-relative-name)))
(default (car (member default choices))))
(list (magit-completing-read-multiple
(if current-prefix-arg "Stage ignored file,s: " "Stage file,s: ")
choices nil t nil nil default)
current-prefix-arg)))
(let* ((choices (if current-prefix-arg
(magit-ignored-files)
(nconc (magit-unstaged-files)
(magit-untracked-files))))
(default (or (magit-section-value-if 'file)
(magit-file-relative-name)))
(default (car (member default choices))))
(list (magit-completing-read-multiple
(if current-prefix-arg "Stage ignored file,s: " "Stage file,s: ")
choices nil t nil nil default)
current-prefix-arg)))
(magit-with-toplevel
(magit-stage-1 (and force "--force") files)))
@@ -433,12 +433,12 @@ ignored) files."
(defun magit-unstage-files (files)
"Read one or more files and unstage all changes to those files."
(interactive
(let* ((choices (magit-staged-files))
(default (or (magit-section-value-if 'file)
(magit-file-relative-name)))
(default (car (member default choices))))
(list (magit-completing-read-multiple "Unstage file,s: " choices
nil t nil nil default))))
(let* ((choices (magit-staged-files))
(default (or (magit-section-value-if 'file)
(magit-file-relative-name)))
(default (car (member default choices))))
(list (magit-completing-read-multiple "Unstage file,s: " choices
nil t nil nil default))))
(magit-with-toplevel
(magit-unstage-1 files)))
@@ -488,17 +488,17 @@ On a hunk or file with unresolved conflicts prompt which side to
keep (while discarding the other). If point is within the text
of a side, then keep that side without prompting."
(interactive)
(when-let ((s (magit-apply--get-selection)))
(when$ (magit-apply--get-selection)
(pcase (list (magit-diff-type) (magit-diff-scope))
(`(committed ,_) (user-error "Cannot discard committed changes"))
(`(undefined ,_) (user-error "Cannot discard this change"))
(`(untracked list) (magit-discard-untracked))
(`(,_ region) (magit-discard-region s))
(`(,_ hunk) (magit-discard-hunk s))
(`(,_ hunks) (magit-discard-hunks s))
(`(,_ file) (magit-discard-file s))
(`(,_ files) (magit-discard-files s))
(`(,_ list) (magit-discard-files s)))))
(`(,_ region) (magit-discard-region $))
(`(,_ hunk) (magit-discard-hunk $))
(`(,_ hunks) (magit-discard-hunks $))
(`(,_ file) (magit-discard-file $))
(`(,_ files) (magit-discard-files $))
(`(,_ list) (magit-discard-files $)))))
(defun magit-discard-untracked ()
(magit-discard-files--delete
@@ -642,13 +642,13 @@ of a side, then keep that side without prompting."
files))
(dolist (file files)
(let ((orig (cadr (assoc file status))))
(if (file-exists-p file)
(progn
(when-let ((path (file-name-directory orig)))
(make-directory path t))
(magit-call-git "mv" file orig))
(magit-call-git "rm" "--cached" "--" file)
(magit-call-git "reset" "--" orig)))))
(cond ((file-exists-p file)
(when$ (file-name-directory orig)
(make-directory $ t))
(magit-call-git "mv" file orig))
(t
(magit-call-git "rm" "--cached" "--" file)
(magit-call-git "reset" "--" orig))))))
(defun magit-discard-files--discard (sections new-files)
(let ((files (mapcar (##oref % value) sections)))
@@ -684,16 +684,16 @@ of a side, then keep that side without prompting."
With a prefix argument fallback to a 3-way merge. Doing
so causes the change to be applied to the index as well."
(interactive (and current-prefix-arg (list "--3way")))
(when-let ((s (magit-apply--get-selection)))
(when$ (magit-apply--get-selection)
(pcase (list (magit-diff-type) (magit-diff-scope))
(`(untracked ,_) (user-error "Cannot reverse untracked changes"))
(`(unstaged ,_) (user-error "Cannot reverse unstaged changes"))
(`(,_ region) (magit-reverse-region s args))
(`(,_ hunk) (magit-reverse-hunk s args))
(`(,_ hunks) (magit-reverse-hunks s args))
(`(,_ file) (magit-reverse-file s args))
(`(,_ files) (magit-reverse-files s args))
(`(,_ list) (magit-reverse-files s args)))))
(`(,_ region) (magit-reverse-region $ args))
(`(,_ hunk) (magit-reverse-hunk $ args))
(`(,_ hunks) (magit-reverse-hunks $ args))
(`(,_ file) (magit-reverse-file $ args))
(`(,_ files) (magit-reverse-files $ args))
(`(,_ list) (magit-reverse-files $ args)))))
(defun magit-reverse-region (section args)
(magit-confirm 'reverse "Reverse region")
@@ -717,9 +717,9 @@ so causes the change to be applied to the index as well."
(pcase-let ((`(,binaries ,sections)
(let ((bs (magit-binary-files
(cond ((derived-mode-p 'magit-revision-mode)
magit-buffer-range)
magit-buffer-diff-range)
((derived-mode-p 'magit-diff-mode)
magit-buffer-range)
magit-buffer-diff-range)
("--cached")))))
(magit--separate (##member (oref % value) bs)
sections))))
@@ -822,6 +822,7 @@ a separate commit. A typical workflow would be:
;; ("and>" . "cond-let--and>")
;; ("and-let" . "cond-let--and-let")
;; ("if-let" . "cond-let--if-let")
;; ("when$" . "cond-let--when$")
;; ("when-let" . "cond-let--when-let")
;; ("while-let" . "cond-let--while-let")
;; ("match-string" . "match-string")

View File

@@ -1,6 +1,6 @@
;;; magit-autorevert.el --- Revert buffers when files in repository change -*- lexical-binding:t -*-
;; Copyright (C) 2008-2025 The Magit Project Contributors
;; Copyright (C) 2008-2026 The Magit Project Contributors
;; Author: Jonas Bernoulli <emacs.magit@jonas.bernoulli.dev>
;; Maintainer: Jonas Bernoulli <emacs.magit@jonas.bernoulli.dev>

View File

@@ -1,6 +1,6 @@
;;; magit-base.el --- Early birds -*- lexical-binding:t; coding:utf-8 -*-
;; Copyright (C) 2008-2025 The Magit Project Contributors
;; Copyright (C) 2008-2026 The Magit Project Contributors
;; Author: Jonas Bernoulli <emacs.magit@jonas.bernoulli.dev>
;; Maintainer: Jonas Bernoulli <emacs.magit@jonas.bernoulli.dev>
@@ -494,7 +494,7 @@ and delay of your graphical environment or operating system."
(defclass magit-hunk-section (magit-diff-section)
((keymap :initform 'magit-hunk-section-map)
(painted :initform nil)
(fontified :initform nil) ;TODO
(fontified :initform nil)
(refined :initform nil)
(combined :initform nil :initarg :combined)
(from-range :initform nil :initarg :from-range)
@@ -824,13 +824,13 @@ ACTION is a member of option `magit-slow-confirm'."
(or (cond ((and (not (eq action t))
(or (eq magit-no-confirm t)
(memq action magit-no-confirm)
(cl-member-if (pcase-lambda (`(,key ,var . ,sub))
(and (memq key magit-no-confirm)
(memq action sub)
(or (not var)
(and (boundp var)
(symbol-value var)))))
magit--no-confirm-alist)))
(magit--any (pcase-lambda (`(,key ,var . ,sub))
(and (memq key magit-no-confirm)
(memq action sub)
(or (not var)
(and (boundp var)
(symbol-value var)))))
magit--no-confirm-alist)))
(or (not sitems) items))
((not sitems)
(magit-y-or-n-p prompt action))
@@ -963,32 +963,32 @@ Pad the left side of STRING so that it aligns with the text area."
(goto-char (point-min))
(while (search-forward "%" nil t)
(cond
;; Quoted percent sign.
((eq (char-after) ?%)
(delete-char 1))
;; Valid format spec.
((looking-at "\\([-0-9.]*\\)\\([a-zA-Z]\\)")
(let* ((num (match-str 1))
(spec (string-to-char (match-str 2)))
(val (assq spec specification)))
(unless val
(error "Invalid format character: `%%%c'" spec))
(setq val (cdr val))
;; Pad result to desired length.
(let ((text (format (concat "%" num "s") val)))
;; Insert first, to preserve text properties.
(if (next-property-change 0 (concat " " text))
;; If the inserted text has properties, then preserve those.
(insert text)
;; Otherwise preserve FORMAT's properties, like `format-spec'.
(insert-and-inherit text))
;; Delete the specifier body.
(delete-region (+ (match-beginning 0) (length text))
(+ (match-end 0) (length text)))
;; Delete the percent sign.
(delete-region (1- (match-beginning 0)) (match-beginning 0)))))
;; Signal an error on bogus format strings.
((error "Invalid format string"))))
;; Quoted percent sign.
((eq (char-after) ?%)
(delete-char 1))
;; Valid format spec.
((looking-at "\\([-0-9.]*\\)\\([a-zA-Z]\\)")
(let* ((num (match-str 1))
(spec (string-to-char (match-str 2)))
(val (assq spec specification)))
(unless val
(error "Invalid format character: `%%%c'" spec))
(setq val (cdr val))
;; Pad result to desired length.
(let ((text (format (concat "%" num "s") val)))
;; Insert first, to preserve text properties.
(if (next-property-change 0 (concat " " text))
;; If the inserted text has properties, then preserve those.
(insert text)
;; Otherwise preserve FORMAT's properties, like `format-spec'.
(insert-and-inherit text))
;; Delete the specifier body.
(delete-region (+ (match-beginning 0) (length text))
(+ (match-end 0) (length text)))
;; Delete the percent sign.
(delete-region (1- (match-beginning 0)) (match-beginning 0)))))
;; Signal an error on bogus format strings.
((error "Invalid format string"))))
(buffer-string)))
;;; Missing from Emacs
@@ -1013,6 +1013,24 @@ This function should be named `version>' and be part of Emacs."
This function should be named `version>=' and be part of Emacs."
(version-list-<= (version-to-list v2) (version-to-list v1)))
(defun magit--delete-text-properties (string &optional props)
"Delete text properties PROPS from STRING and return it.
If PROPS is nil, remove all properties. To leave STRING unchanged
and return a new string, instead use `magit--remove-text-properties'."
(set-text-properties 0 (length string) props string)
string)
(defun magit--remove-text-properties (string &optional props)
"Return a copy of STRING with text properties PROPS removed.
If PROPS is nil, remove all properties."
(magit--delete-text-properties (copy-sequence string) props))
;;; Emacs Compatibility
(static-if (fboundp 'member-if) ; Emacs 31.1
(defalias 'magit--any 'member-if)
(defalias 'magit--any 'cl-member-if))
;;; Kludges for Emacs Bugs
(defun magit-which-function ()
@@ -1187,6 +1205,16 @@ Like `message', except that `message-log-max' is bound to nil."
(push char quoted))
(concat (nreverse quoted))))
(defun magit--find-buffer (&rest plist)
"Like `find-buffer' but take multiple VARIABLE-VALUE pairs."
(seq-find (lambda (buf)
(while (and plist
(equal (buffer-local-value (car plist) buf)
(cadr plist)))
(setq plist (cddr plist)))
(not plist))
(buffer-list)))
;;; _
(provide 'magit-base)
;; Local Variables:
@@ -1195,6 +1223,7 @@ Like `message', except that `message-log-max' is bound to nil."
;; ("and>" . "cond-let--and>")
;; ("and-let" . "cond-let--and-let")
;; ("if-let" . "cond-let--if-let")
;; ("when$" . "cond-let--when$")
;; ("when-let" . "cond-let--when-let")
;; ("while-let" . "cond-let--while-let")
;; ("match-string" . "match-string")

View File

@@ -1,6 +1,6 @@
;;; magit-bisect.el --- Bisect support for Magit -*- lexical-binding:t -*-
;; Copyright (C) 2008-2025 The Magit Project Contributors
;; Copyright (C) 2008-2026 The Magit Project Contributors
;; Author: Jonas Bernoulli <emacs.magit@jonas.bernoulli.dev>
;; Maintainer: Jonas Bernoulli <emacs.magit@jonas.bernoulli.dev>
@@ -321,6 +321,7 @@ bisect run'."
;; ("and>" . "cond-let--and>")
;; ("and-let" . "cond-let--and-let")
;; ("if-let" . "cond-let--if-let")
;; ("when$" . "cond-let--when$")
;; ("when-let" . "cond-let--when-let")
;; ("while-let" . "cond-let--while-let")
;; ("match-string" . "match-string")

View File

@@ -1,6 +1,6 @@
;;; magit-blame.el --- Blame support for Magit -*- lexical-binding:t -*-
;; Copyright (C) 2008-2025 The Magit Project Contributors
;; Copyright (C) 2008-2026 The Magit Project Contributors
;; Author: Jonas Bernoulli <emacs.magit@jonas.bernoulli.dev>
;; Maintainer: Jonas Bernoulli <emacs.magit@jonas.bernoulli.dev>
@@ -261,7 +261,7 @@ Also see option `magit-blame-styles'."
(or (and (not (and type (not (eq type magit-blame-type))))
(magit-blame-chunk-at (point)))
(and type
(let ((rev (or magit-buffer-refname magit-buffer-revision))
(let ((rev magit-buffer-revision)
(file (and (not (derived-mode-p 'dired-mode))
(magit-file-relative-name
nil (not magit-buffer-file-name))))
@@ -418,8 +418,8 @@ modes is toggled, then this mode also gets toggled automatically.
(magit-blame-mode 1))
(message "Blaming...")
(magit-blame-run-process
(and$ (or magit-buffer-refname magit-buffer-revision)
(and (not (equal $ "{index}")) $))
(and (not (equal magit-buffer-revision "{index}"))
magit-buffer-revision)
(magit-file-relative-name nil (not magit-buffer-file-name))
(if (memq magit-blame-type '(final removal))
(cons "--reverse" args)
@@ -1006,6 +1006,7 @@ instead of the hash, like `kill-ring-save' would."
;; ("and>" . "cond-let--and>")
;; ("and-let" . "cond-let--and-let")
;; ("if-let" . "cond-let--if-let")
;; ("when$" . "cond-let--when$")
;; ("when-let" . "cond-let--when-let")
;; ("while-let" . "cond-let--while-let")
;; ("match-string" . "match-string")

View File

@@ -1,6 +1,6 @@
;;; magit-bookmark.el --- Bookmarks for Magit buffers -*- lexical-binding:t -*-
;; Copyright (C) 2008-2025 The Magit Project Contributors
;; Copyright (C) 2008-2026 The Magit Project Contributors
;; Inspired by an earlier implementation by Yuri Khan.
@@ -56,8 +56,8 @@
;;;; Diff
(put 'magit-diff-mode 'magit-bookmark-variables
'(magit-buffer-range-hashed
magit-buffer-typearg
'(magit-buffer-diff-range-oids
magit-buffer-diff-typearg
magit-buffer-diff-args
magit-buffer-diff-files))
@@ -66,9 +66,10 @@
(pcase (magit-diff-type)
('staged "staged")
('unstaged "unstaged")
('committed magit-buffer-range)
('committed magit-buffer-diff-range)
('undefined
(delq nil (list magit-buffer-typearg magit-buffer-range-hashed))))
(delq nil
(list magit-buffer-diff-typearg magit-buffer-diff-range-oids))))
(if magit-buffer-diff-files
(concat " -- " (string-join magit-buffer-diff-files " "))
"")))
@@ -76,7 +77,7 @@
;;;; Revision
(put 'magit-revision-mode 'magit-bookmark-variables
'(magit-buffer-revision-hash
'(magit-buffer-revision-oid
magit-buffer-diff-args
magit-buffer-diff-files))
@@ -90,7 +91,7 @@
;;;; Stash
(put 'magit-stash-mode 'magit-bookmark-variables
'(magit-buffer-revision-hash
'(magit-buffer-revision-oid
magit-buffer-diff-args
magit-buffer-diff-files))
@@ -104,20 +105,20 @@
(cl-defmethod magit-bookmark--get-child-value
(section &context (major-mode magit-stash-mode))
(string-replace magit-buffer-revision
magit-buffer-revision-hash
magit-buffer-revision-oid
(oref section value)))
;;; Log
;;;; Log
(put 'magit-log-mode 'magit-bookmark-variables
'(magit-buffer-revisions
'(magit-buffer-log-revisions
magit-buffer-log-args
magit-buffer-log-files))
(cl-defmethod magit-bookmark-name (&context (major-mode magit-log-mode))
(format "magit-log(%s%s)"
(string-join magit-buffer-revisions " ")
(string-join magit-buffer-log-revisions " ")
(if magit-buffer-log-files
(concat " -- " (string-join magit-buffer-log-files " "))
"")))
@@ -126,12 +127,12 @@
(put 'magit-cherry-mode 'magit-bookmark-variables
'(magit-buffer-refname
magit-buffer-upstream))
magit-buffer-cherry-upstream))
(cl-defmethod magit-bookmark-name (&context (major-mode magit-cherry-mode))
(format "magit-cherry(%s > %s)"
magit-buffer-refname
magit-buffer-upstream))
magit-buffer-cherry-upstream))
;;;; Reflog
@@ -146,8 +147,8 @@
(put 'magit-status-mode 'magit-bookmark-variables nil)
(put 'magit-refs-mode 'magit-bookmark-variables
'(magit-buffer-upstream
magit-buffer-arguments))
'(magit-buffer-refs-upstream
magit-buffer-refs-args))
(put 'magit-stashes-mode 'magit-bookmark-variables nil)
@@ -162,6 +163,7 @@
;; ("and>" . "cond-let--and>")
;; ("and-let" . "cond-let--and-let")
;; ("if-let" . "cond-let--if-let")
;; ("when$" . "cond-let--when$")
;; ("when-let" . "cond-let--when-let")
;; ("while-let" . "cond-let--while-let")
;; ("match-string" . "match-string")

View File

@@ -1,6 +1,6 @@
;;; magit-branch.el --- Branch support -*- lexical-binding:t -*-
;; Copyright (C) 2008-2025 The Magit Project Contributors
;; Copyright (C) 2008-2026 The Magit Project Contributors
;; Author: Jonas Bernoulli <emacs.magit@jonas.bernoulli.dev>
;; Maintainer: Jonas Bernoulli <emacs.magit@jonas.bernoulli.dev>
@@ -153,11 +153,11 @@ to set `magit-branch-prefer-remote-upstream' to a non-nil value.
However, I recommend that you use local branches as UPSTREAM."
:package-version '(magit . "2.9.0")
:group 'magit-commands
:type '(repeat (cons (string :tag "Use upstream")
(choice :tag "For branches" ;???
(regexp :tag "Matching")
(repeat :tag "Except"
(string :tag "Branch"))))))
:type '(alist :key-type (string :tag "Use upstream")
:value-type (choice :tag "For branches" ;???
(regexp :tag "Matching")
(repeat :tag "Except"
(string :tag "Branch")))))
(defcustom magit-branch-rename-push-target t
"Whether the push-remote setup is preserved when renaming a branch.
@@ -314,10 +314,10 @@ branch. This is similar to what `magit-branch-and-checkout'
does."
(declare (interactive-only magit-call-git))
(interactive
(let ((arg (magit-read-other-branch-or-commit "Checkout")))
(list arg
(and (not (magit-commit-p arg))
(magit-read-starting-point "Create and checkout branch" arg)))))
(let ((arg (magit-read-other-branch-or-commit "Checkout")))
(list arg
(and (not (magit-commit-p arg))
(magit-read-starting-point "Create and checkout branch" arg)))))
(when (string-match "\\`heads/\\(.+\\)" arg)
(setq arg (match-str 1 arg)))
(if start-point
@@ -352,50 +352,50 @@ value of `magit-branch-adjust-remote-upstream-alist', just like
when using `magit-branch-and-checkout'."
(declare (interactive-only magit-call-git))
(interactive
(let* ((current (magit-get-current-branch))
(local (magit-list-local-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
"Checkout branch" choices
nil nil nil 'magit-revision-history
(or (car (member atpoint choices))
(and atpoint
(car (member (and (string-match "[^/]+/" atpoint)
(substring atpoint (match-end 0)))
choices)))))))
(cond ((member choice remote)
(list (and (string-match "[^/]+/" choice)
(substring choice (match-end 0)))
choice))
((member choice local)
(list choice))
((list choice (magit-read-starting-point "Create" choice))))))
(let* ((current (magit-get-current-branch))
(local (magit-list-local-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
"Checkout branch" choices
nil nil nil 'magit-revision-history
(or (car (member atpoint choices))
(and atpoint
(car (member (and (string-match "[^/]+/" atpoint)
(substring atpoint (match-end 0)))
choices)))))))
(cond ((member choice remote)
(list (and (string-match "[^/]+/" choice)
(substring choice (match-end 0)))
choice))
((member choice local)
(list choice))
((list choice (magit-read-starting-point "Create" choice))))))
(cond
((not start-point)
(magit--checkout branch (magit-branch-arguments))
(magit-refresh))
(t
(when (magit-anything-modified-p t)
(user-error "Cannot checkout when there are uncommitted changes"))
(magit-run-git-async "checkout" (magit-branch-arguments)
"-b" branch start-point)
(set-process-sentinel
magit-this-process
(lambda (process event)
(when (memq (process-status process) '(exit signal))
(magit-branch-maybe-adjust-upstream branch start-point)
(when (magit-remote-branch-p start-point)
(pcase-let ((`(,remote . ,remote-branch)
(magit-split-branch-name start-point)))
(when (and (equal branch remote-branch)
(not (equal remote (magit-get "remote.pushDefault"))))
(magit-set remote "branch" branch "pushRemote"))))
(magit-process-sentinel process event)))))))
((not start-point)
(magit--checkout branch (magit-branch-arguments))
(magit-refresh))
(t
(when (magit-anything-modified-p t)
(user-error "Cannot checkout when there are uncommitted changes"))
(magit-run-git-async "checkout" (magit-branch-arguments)
"-b" branch start-point)
(set-process-sentinel
magit-this-process
(lambda (process event)
(when (memq (process-status process) '(exit signal))
(magit-branch-maybe-adjust-upstream branch start-point)
(when (magit-remote-branch-p start-point)
(pcase-let ((`(,remote . ,remote-branch)
(magit-split-branch-name start-point)))
(when (and (equal branch remote-branch)
(not (equal remote (magit-get "remote.pushDefault"))))
(magit-set remote "branch" branch "pushRemote"))))
(magit-process-sentinel process event)))))))
(defun magit-branch-maybe-adjust-upstream (branch start-point)
(when-let ((upstream
@@ -423,20 +423,20 @@ when using `magit-branch-and-checkout'."
(if magit-branch-read-upstream-first
(let ((choice (magit-read-starting-point prompt nil default-start)))
(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))))
((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
@@ -511,8 +511,8 @@ from the source branch's upstream, then an error is raised."
(if checkout
(magit-call-git "checkout" "-b" branch current)
(magit-call-git "branch" branch current)))
(when-let ((upstream (magit-get-indirect-upstream-branch current)))
(magit-call-git "branch" "--set-upstream-to" upstream branch))
(when$ (magit-get-indirect-upstream-branch current)
(magit-call-git "branch" "--set-upstream-to" $ branch))
(when (and tracked
(setq base
(if from
@@ -544,13 +544,13 @@ 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 ((branch (magit-read-local-branch "Reset branch"
(magit-local-branch-at-point))))
(list branch
(magit-read-branch-or-commit (format "Reset %s to" branch)
(magit-get-upstream-branch branch)
branch)
current-prefix-arg)))
(let ((branch (magit-read-local-branch "Reset branch"
(magit-local-branch-at-point))))
(list 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))
(if (and (magit-anything-modified-p)
@@ -560,8 +560,7 @@ that is being reset."
(magit-reset-hard to))
(magit-call-git "update-ref"
"-m" (format "reset: moving to %s" to)
(magit-git-string "rev-parse" "--symbolic-full-name"
branch)
(magit-ref-fullname branch)
to))
(when (and set-upstream (magit-branch-p to))
(magit-set-upstream-branch branch to)
@@ -588,24 +587,24 @@ prompt is confusing."
;; `magit-branch-rename'; but it turns out everyone wants to squeeze
;; a bit of extra functionality into this one, including myself.
(interactive
(let ((branches (magit-region-values 'branch t))
(force current-prefix-arg))
(if (length> branches 1)
(magit-confirm t nil "Delete %d branches" nil branches)
(setq branches
(list (magit-read-branch-prefer-other
(if force "Force delete branch" "Delete branch")))))
(cond-let
(force)
[[unmerged (seq-remove #'magit-branch-merged-p branches)]]
((magit-confirm 'delete-unmerged-branch
"Delete unmerged branch %s"
"Delete %d unmerged branches"
'noabort unmerged)
(setq force branches))
((setq branches (cl-set-difference branches unmerged :test #'equal)))
((user-error "Abort")))
(list branches force)))
(let ((branches (magit-region-values 'branch t))
(force current-prefix-arg))
(if (length> branches 1)
(magit-confirm t nil "Delete %d branches" nil branches)
(setq branches
(list (magit-read-branch-prefer-other
(if force "Force delete branch" "Delete branch")))))
(cond-let
(force)
[[unmerged (seq-remove #'magit-branch-merged-p branches)]]
((magit-confirm 'delete-unmerged-branch
"Delete unmerged branch %s"
"Delete %d unmerged branches"
'noabort unmerged)
(setq force branches))
((setq branches (cl-set-difference branches unmerged :test #'equal)))
((user-error "Abort")))
(list branches force)))
(let ((refs (mapcar #'magit-ref-fullname branches)))
;; If a member of refs is nil, that means that
;; the respective branch name is ambiguous.
@@ -614,86 +613,86 @@ prompt is confusing."
"%s ambiguous; please cleanup using git directly"
(let ((len (length ambiguous)))
(cond
((= len 1)
(format "%s is" (seq-find #'magit-ref-ambiguous-p branches)))
((= len (length refs))
(format "These %s names are" len))
((format "%s of these names are" len))))))
((= len 1)
(format "%s is" (seq-find #'magit-ref-ambiguous-p branches)))
((= len (length refs))
(format "These %s names are" len))
((format "%s of these names are" len))))))
(cond
((string-match "^refs/remotes/\\([^/]+\\)" (car refs))
(let* ((remote (match-str 1 (car refs)))
(offset (1+ (length remote))))
(cond
((magit-confirm 'delete-branch-on-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.
(dolist (ref refs)
(message "Delete %s (was %s)" ref
(magit-rev-parse "--short" ref)))
;; Assume the branches actually still exist on the remote.
(magit-run-git-async
"push"
(and (or force magit-branch-delete-never-verify) "--no-verify")
remote
(mapcar (##concat ":" (substring % offset)) branches))
;; If that is not the case, then this deletes the tracking branches.
(set-process-sentinel
magit-this-process
(apply-partially #'magit-delete-remote-branch-sentinel remote refs)))
(t
(dolist (ref refs)
(message "Delete %s (was %s)" ref
(magit-rev-parse "--short" ref))
(magit-call-git "update-ref" "-d" ref))
(magit-refresh)))))
((length> branches 1)
(setq branches (delete (magit-get-current-branch) branches))
(mapc #'magit-branch-maybe-delete-pr-remote branches)
(mapc #'magit-branch-unset-pushRemote branches)
(magit-run-git "branch" (if force "-D" "-d") branches))
(t ; And now for something completely different.
(let* ((branch (car branches))
(prompt (format "Branch %s is checked out. " branch))
(target (magit-get-indirect-upstream-branch branch t)))
(when (equal branch (magit-get-current-branch))
(when (or (equal branch target)
(not target))
(setq target (magit-main-branch)))
(pcase (if (or (equal branch target)
(not target))
(magit-read-char-case prompt nil
(?d "[d]etach HEAD & delete" 'detach)
(?a "[a]bort" 'abort))
(magit-read-char-case prompt nil
(?d "[d]etach HEAD & delete" 'detach)
(?c (format "[c]heckout %s & delete" target) 'target)
(?a "[a]bort" 'abort)))
(`detach (unless (or (equal force '(4))
(member branch force)
(magit-branch-merged-p branch t))
(magit-confirm 'delete-unmerged-branch
"Delete unmerged branch %s" ""
nil (list branch)))
(magit-call-git "checkout" "--detach"))
(`target (unless (or (equal force '(4))
(member branch force)
(magit-branch-merged-p branch target))
(magit-confirm 'delete-unmerged-branch
"Delete unmerged branch %s" ""
nil (list branch)))
(magit-call-git "checkout" target))
(`abort (user-error "Abort")))
(setq force t))
(magit-branch-maybe-delete-pr-remote branch)
(magit-branch-unset-pushRemote branch)
(magit-run-git "branch" (if force "-D" "-d") branch))))))
((string-match "^refs/remotes/\\([^/]+\\)" (car refs))
(let* ((remote (match-str 1 (car refs)))
(offset (1+ (length remote))))
(cond
((magit-confirm 'delete-branch-on-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.
(dolist (ref refs)
(message "Delete %s (was %s)" ref
(magit-rev-parse "--short" ref)))
;; Assume the branches actually still exist on the remote.
(magit-run-git-async
"push" "--delete"
(and (or force magit-branch-delete-never-verify) "--no-verify")
remote
(mapcar (##concat "refs/heads/" (substring % offset)) branches))
;; If that is not the case, then this deletes the tracking branches.
(set-process-sentinel
magit-this-process
(apply-partially #'magit-delete-remote-branch-sentinel remote refs)))
(t
(dolist (ref refs)
(message "Delete %s (was %s)" ref
(magit-rev-parse "--short" ref))
(magit-call-git "update-ref" "-d" ref))
(magit-refresh)))))
((length> branches 1)
(setq branches (delete (magit-get-current-branch) branches))
(mapc #'magit-branch-maybe-delete-pr-remote branches)
(mapc #'magit-branch-unset-pushRemote branches)
(magit-run-git "branch" (if force "-D" "-d") branches))
(t ; And now for something completely different.
(let* ((branch (car branches))
(prompt (format "Branch %s is checked out. " branch))
(target (magit-get-indirect-upstream-branch branch t)))
(when (equal branch (magit-get-current-branch))
(when (or (equal branch target)
(not target))
(setq target (magit-main-branch)))
(pcase (if (or (equal branch target)
(not target))
(magit-read-char-case prompt nil
(?d "[d]etach HEAD & delete" 'detach)
(?a "[a]bort" 'abort))
(magit-read-char-case prompt nil
(?d "[d]etach HEAD & delete" 'detach)
(?c (format "[c]heckout %s & delete" target) 'target)
(?a "[a]bort" 'abort)))
(`detach (unless (or (equal force '(4))
(member branch force)
(magit-branch-merged-p branch t))
(magit-confirm 'delete-unmerged-branch
"Delete unmerged branch %s" ""
nil (list branch)))
(magit-call-git "checkout" "--detach"))
(`target (unless (or (equal force '(4))
(member branch force)
(magit-branch-merged-p branch target))
(magit-confirm 'delete-unmerged-branch
"Delete unmerged branch %s" ""
nil (list branch)))
(magit-call-git "checkout" target))
(`abort (user-error "Abort")))
(setq force t))
(magit-branch-maybe-delete-pr-remote branch)
(magit-branch-unset-pushRemote branch)
(magit-run-git "branch" (if force "-D" "-d") branch))))))
(put 'magit-branch-delete 'interactive-only t)
@@ -758,11 +757,11 @@ the value of `magit-branch-rename-push-target' (which see) maybe
set `branch.NEW.pushRemote' and maybe rename the push-target on
the remote."
(interactive
(let ((branch (magit-read-local-branch "Rename branch")))
(list branch
(magit-read-string-ns (format "Rename branch '%s' to" branch)
nil 'magit-revision-history)
current-prefix-arg)))
(let ((branch (magit-read-local-branch "Rename branch")))
(list branch
(magit-read-string-ns (format "Rename branch '%s' to" branch)
nil 'magit-revision-history)
current-prefix-arg)))
(when (string-match "\\`heads/\\(.+\\)" old)
(setq old (match-str 1 old)))
(when (equal old new)
@@ -868,11 +867,11 @@ Also rename the respective reflog file."
("a m" magit-branch.autoSetupMerge)
("a r" magit-branch.autoSetupRebase)]
(interactive
(list (or (and (not current-prefix-arg)
(not (and magit-branch-direct-configure
(eq transient-current-command 'magit-branch)))
(magit-get-current-branch))
(magit--read-branch-scope))))
(list (or (and (not current-prefix-arg)
(not (and magit-branch-direct-configure
(eq transient-current-command 'magit-branch)))
(magit-get-current-branch))
(magit--read-branch-scope))))
(transient-setup 'magit-branch-configure nil nil :scope branch))
(defun magit--read-branch-scope (&optional obj)
@@ -891,7 +890,8 @@ Also rename the respective reflog file."
(magit-run-git-with-editor "branch" "--edit-description" branch))
(defclass magit--git-branch:upstream (magit--git-variable)
((format :initform " %k %m %M\n %r %R")))
((format :initform " %k %m %M\n %r %R")
(accessible-format :initform "%k %m is %M and %r is %R")))
(transient-define-infix magit-branch.<branch>.merge/remote ()
:class 'magit--git-branch:upstream)
@@ -919,7 +919,7 @@ Also rename the respective reflog file."
(cl-defmethod transient-format ((obj magit--git-branch:upstream))
(let ((branch (transient-scope)))
(format-spec
(oref obj format)
(transient--get-format obj)
`((?k . ,(transient-format-key obj))
(?r . ,(format "branch.%s.remote" branch))
(?m . ,(format "branch.%s.merge" branch))
@@ -977,6 +977,7 @@ Also rename the respective reflog file."
;; ("and>" . "cond-let--and>")
;; ("and-let" . "cond-let--and-let")
;; ("if-let" . "cond-let--if-let")
;; ("when$" . "cond-let--when$")
;; ("when-let" . "cond-let--when-let")
;; ("while-let" . "cond-let--while-let")
;; ("match-string" . "match-string")

View File

@@ -1,6 +1,6 @@
;;; magit-bundle.el --- Bundle support for Magit -*- lexical-binding:t -*-
;; Copyright (C) 2008-2025 The Magit Project Contributors
;; Copyright (C) 2008-2026 The Magit Project Contributors
;; Author: Jonas Bernoulli <emacs.magit@jonas.bernoulli.dev>
;; Maintainer: Jonas Bernoulli <emacs.magit@jonas.bernoulli.dev>
@@ -61,14 +61,14 @@
("t" "create tracked bundle" magit-bundle-create-tracked)
("u" "update tracked bundle" magit-bundle-update-tracked)]
(interactive
(and (eq transient-current-command 'magit-bundle-create)
(list (read-file-name "Create bundle: " nil nil nil
(concat (file-name-nondirectory
(directory-file-name (magit-toplevel)))
".bundle"))
(magit-completing-read-multiple "Refnames (zero or more): "
(magit-list-refnames))
(transient-args 'magit-bundle-create))))
(and (eq transient-current-command 'magit-bundle-create)
(list (read-file-name "Create bundle: " nil nil nil
(concat (file-name-nondirectory
(directory-file-name (magit-toplevel)))
".bundle"))
(magit-completing-read-multiple "Refnames (zero or more): "
(magit-list-refnames))
(transient-args 'magit-bundle-create))))
(if file
(magit-git-bundle "create" file refs args)
(transient-setup 'magit-bundle-create)))
@@ -77,17 +77,17 @@
(defun magit-bundle-create-tracked (file tag branch refs args)
"Create and track a new bundle."
(interactive
(let ((tag (magit-read-tag "Track bundle using tag"))
(branch (magit-read-branch "Bundle branch"))
(refs (magit-completing-read-multiple
"Additional refnames (zero or more): "
(magit-list-refnames))))
(list (read-file-name "File: " nil nil nil (concat tag ".bundle"))
tag branch
(if (equal branch (magit-get-current-branch))
(cons "HEAD" refs)
refs)
(transient-args 'magit-bundle-create))))
(let ((tag (magit-read-tag "Track bundle using tag"))
(branch (magit-read-branch "Bundle branch"))
(refs (magit-completing-read-multiple
"Additional refnames (zero or more): "
(magit-list-refnames))))
(list (read-file-name "File: " nil nil nil (concat tag ".bundle"))
tag branch
(if (equal branch (magit-get-current-branch))
(cons "HEAD" refs)
refs)
(transient-args 'magit-bundle-create))))
(magit-git-bundle "create" file (cons branch refs) args)
(magit-git "tag" "--force" tag branch
"-m" (concat ";; git-bundle tracking\n"
@@ -142,6 +142,7 @@
;; ("and>" . "cond-let--and>")
;; ("and-let" . "cond-let--and-let")
;; ("if-let" . "cond-let--if-let")
;; ("when$" . "cond-let--when$")
;; ("when-let" . "cond-let--when-let")
;; ("while-let" . "cond-let--while-let")
;; ("match-string" . "match-string")

View File

@@ -1,6 +1,6 @@
;;; magit-clone.el --- Clone a repository -*- lexical-binding:t -*-
;; Copyright (C) 2008-2025 The Magit Project Contributors
;; Copyright (C) 2008-2026 The Magit Project Contributors
;; Author: Jonas Bernoulli <emacs.magit@jonas.bernoulli.dev>
;; Maintainer: Jonas Bernoulli <emacs.magit@jonas.bernoulli.dev>
@@ -337,12 +337,12 @@ Then show the status buffer for the new repository."
url-format
`((?h . ,host)
(?n . ,(cond
((string-search "/" repo) repo)
((string-search "." user)
(if-let ((user (magit-get user)))
(concat user "/" repo)
(user-error "Set %S or specify owner explicitly" user)))
((concat user "/" repo))))))
((string-search "/" repo) repo)
((string-search "." user)
(if-let ((user (magit-get user)))
(concat user "/" repo)
(user-error "Set %S or specify owner explicitly" user)))
((concat user "/" repo))))))
(user-error
"Bogus `magit-clone-url-format' (bad type or missing default)")))
@@ -354,6 +354,7 @@ Then show the status buffer for the new repository."
;; ("and>" . "cond-let--and>")
;; ("and-let" . "cond-let--and-let")
;; ("if-let" . "cond-let--if-let")
;; ("when$" . "cond-let--when$")
;; ("when-let" . "cond-let--when-let")
;; ("while-let" . "cond-let--while-let")
;; ("match-string" . "match-string")

View File

@@ -1,6 +1,6 @@
;;; magit-commit.el --- Create Git commits -*- lexical-binding:t -*-
;; Copyright (C) 2008-2025 The Magit Project Contributors
;; Copyright (C) 2008-2026 The Magit Project Contributors
;; Author: Jonas Bernoulli <emacs.magit@jonas.bernoulli.dev>
;; Maintainer: Jonas Bernoulli <emacs.magit@jonas.bernoulli.dev>
@@ -144,6 +144,7 @@ This hook is still experimental.")
"Create a new commit or replace an existing commit."
:info-manual "(magit)Initiating a Commit"
:man-page "git-commit"
:value '("--verbose")
["Arguments"
("-a" "Stage all modified and deleted files" ("-a" "--all"))
("-e" "Allow empty commit" "--allow-empty")
@@ -457,49 +458,49 @@ Like `magit-commit-squash' but also run a `--autofixup' rebase."
(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.
(member "--all" args))
(and (not strict)
;; ^ For amend variants that don't make sense otherwise.
(or (member "--amend" args)
(member "--allow-empty" args)
(member "--reset-author" args)
(member "--signoff" args)
(transient-arg-value "--author=" args)
(transient-arg-value "--date=" args))))
(or args (list "--")))
((and (magit-rebase-in-progress-p)
(not (magit-anything-unstaged-p))
(y-or-n-p "Nothing staged. Continue in-progress rebase? "))
(setq this-command #'magit-rebase-continue)
(magit-run-git-sequencer "rebase" "--continue")
nil)
((file-exists-p (expand-file-name "MERGE_MSG" (magit-gitdir)))
(cond ((magit-anything-unmerged-p)
(user-error "Unresolved conflicts"))
((and (magit-anything-unstaged-p)
(not (y-or-n-p
"Proceed with merge despite unstaged changes? ")))
(user-error "Abort"))
((or args (list "--")))))
((not (magit-anything-unstaged-p))
(user-error "Nothing staged (or unstaged)"))
(magit-commit-ask-to-stage
(when (eq magit-commit-ask-to-stage 'verbose)
(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? "))
(setq this-command 'magit-commit--all)
(cons "--all" (or args (list "--"))))
(when (and (eq magit-commit-ask-to-stage 'verbose)
(derived-mode-p 'magit-diff-mode))
(magit-mode-bury-buffer))))
(t
(user-error "Nothing staged"))))
(nopatch (or args (list "--")))
((or (magit-anything-staged-p)
(and (magit-anything-unstaged-p)
;; ^ Everything of nothing is still nothing.
(member "--all" args))
(and (not strict)
;; ^ For amend variants that don't make sense otherwise.
(or (member "--amend" args)
(member "--allow-empty" args)
(member "--reset-author" args)
(member "--signoff" args)
(transient-arg-value "--author=" args)
(transient-arg-value "--date=" args))))
(or args (list "--")))
((and (magit-rebase-in-progress-p)
(not (magit-anything-unstaged-p))
(y-or-n-p "Nothing staged. Continue in-progress rebase? "))
(setq this-command #'magit-rebase-continue)
(magit-run-git-sequencer "rebase" "--continue")
nil)
((file-exists-p (expand-file-name "MERGE_MSG" (magit-gitdir)))
(cond ((magit-anything-unmerged-p)
(user-error "Unresolved conflicts"))
((and (magit-anything-unstaged-p)
(not (y-or-n-p
"Proceed with merge despite unstaged changes? ")))
(user-error "Abort"))
((or args (list "--")))))
((not (magit-anything-unstaged-p))
(user-error "Nothing staged (or unstaged)"))
(magit-commit-ask-to-stage
(when (eq magit-commit-ask-to-stage 'verbose)
(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? "))
(setq this-command 'magit-commit--all)
(cons "--all" (or args (list "--"))))
(when (and (eq magit-commit-ask-to-stage 'verbose)
(derived-mode-p 'magit-diff-mode))
(magit-mode-bury-buffer))))
(t
(user-error "Nothing staged"))))
;;;; Reshelve
@@ -520,18 +521,18 @@ is updated:
- The command was invoked with a prefix argument.
- Non-interactively if UPDATE-AUTHOR is nil."
(interactive
(let ((update-author (and (magit-rev-author-p "HEAD")
(not current-prefix-arg))))
(push (magit-rev-format (if update-author "%ad" "%cd") "HEAD"
(concat "--date=format:%F %T %z"))
magit--reshelve-history)
(list (read-string (if update-author
"Change author and committer dates to: "
"Change committer date to: ")
(cons (format-time-string "%F %T %z") 17)
'magit--reshelve-history)
update-author
(magit-commit-arguments))))
(let ((update-author (and (magit-rev-author-p "HEAD")
(not current-prefix-arg))))
(push (magit-rev-format (if update-author "%ad" "%cd") "HEAD"
(concat "--date=format:%F %T %z"))
magit--reshelve-history)
(list (read-string (if update-author
"Change author and committer dates to: "
"Change committer date to: ")
(cons (format-time-string "%F %T %z") 17)
'magit--reshelve-history)
update-author
(magit-commit-arguments))))
(with-environment-variables (("GIT_COMMITTER_DATE" date))
(magit-run-git "commit" "--amend" "--no-edit"
(and update-author (concat "--date=" date))
@@ -548,20 +549,19 @@ is updated:
(user-error "There are no modified modules that could be absorbed"))
(when commit
(setq commit (magit-rebase-interactive-assert commit t)))
(if (and commit (eq phase 'run))
(progn
(dolist (module modules)
(when-let ((msg (magit-git-string
"log" "-1" "--format=%s"
(concat commit "..") "--" module)))
(magit-git "commit" "-m" (concat "fixup! " msg)
"--only" "--" module)))
(magit-refresh)
t)
(magit-log-select
(lambda (commit)
(magit-commit-absorb-modules 'run commit))
nil nil nil nil commit))))
(cond ((and commit (eq phase 'run))
(dolist (module modules)
(when-let ((msg (magit-git-string
"log" "-1" "--format=%s"
(concat commit "..") "--" module)))
(magit-git "commit" "-m" (concat "fixup! " msg)
"--only" "--" module)))
(magit-refresh)
t)
((magit-log-select
(lambda (commit)
(magit-commit-absorb-modules 'run commit))
nil nil nil nil commit)))))
;;;###autoload(autoload 'magit-commit-absorb "magit-commit" nil t)
(transient-define-prefix magit-commit-absorb (phase commit args)
@@ -669,18 +669,25 @@ an alternative implementation."
'magit-commit--rebase
last-command))
(when (and git-commit-mode magit-commit-show-diff)
(when-let ((diff-buffer (magit-get-mode-buffer 'magit-diff-mode)))
;; This window just started displaying the commit message
;; buffer. Without this that buffer would immediately be
;; replaced with the diff buffer. See #2632.
(when-let ((diff-buffer
;; This signals an error if not inside a Git repository,
;; but the user may be visiting COMMIT_EDITMSG using a
;; tool other than git, which can be used outside a Git
;; repository. See #5527.
(ignore-error magit-outside-git-repo
(magit-get-mode-buffer 'magit-diff-mode))))
;; This window just started displaying the commit message buffer.
;; Without unrecording that buffer would immediately be replaced
;; with the diff buffer. See #2632.
(unrecord-window-buffer nil diff-buffer))
(message "Diffing changes to be committed (C-g to abort diffing)")
(let ((inhibit-quit nil))
(condition-case nil
(magit-commit-diff-1)
(with-demoted-errors "Error showing commit diff: %S"
(magit-commit-diff--show))
(quit)))))
(defun magit-commit-diff-1 ()
(defun magit-commit-diff--args ()
(let ((rev nil)
(arg "--cached")
(command (magit-repository-local-get 'this-commit-command))
@@ -695,6 +702,9 @@ an alternative implementation."
(and (file-exists-p f) (length (magit-file-lines f)))))
(noalt nil))
(pcase (list staged unstaged command)
((guard (not (magit-commit-p "HEAD^")))
(setq rev "HEAD")
(setq arg nil))
((and `(,_ ,_ magit-commit--rebase)
(guard (integerp squash)))
(setq rev (format "HEAD~%s" squash)))
@@ -715,21 +725,24 @@ an alternative implementation."
(setq rev "HEAD")
(setq arg nil)))
(cond
((not
(and (eq this-command 'magit-diff-while-committing)
(and-let ((buf (magit-get-mode-buffer
'magit-diff-mode nil 'selected)))
(and (equal rev (buffer-local-value 'magit-buffer-range buf))
(equal arg (buffer-local-value 'magit-buffer-typearg buf)))))))
((eq command 'magit-commit-amend)
(setq rev nil))
((or squash
(file-exists-p (expand-file-name "rebase-merge/amend" (magit-gitdir))))
(setq rev "HEAD^"))
(t
(message "No alternative diff while committing")
(setq noalt t)))
(unless noalt
((not
(and-let*
((_(eq this-command 'magit-diff-while-committing))
(buf (magit-get-mode-buffer 'magit-diff-mode nil 'selected))
(_(equal rev (buffer-local-value 'magit-buffer-diff-range buf)))
(_(equal arg (buffer-local-value 'magit-buffer-diff-typearg buf)))))))
((eq command 'magit-commit-amend)
(setq rev nil))
((or squash
(file-exists-p (expand-file-name "rebase-merge/amend" (magit-gitdir))))
(setq rev "HEAD^"))
((setq noalt t)))
(list rev arg noalt)))
(defun magit-commit-diff--show ()
(pcase-let ((`(,rev ,arg ,noalt) (magit-commit-diff--args)))
(if noalt
(message "No alternative diff while committing")
(let ((magit-inhibit-save-previous-winconf 'unset)
(magit-display-buffer-noselect t)
(display-buffer-overriding-action
@@ -858,6 +871,7 @@ Also see `git-commit-post-finish-hook'."
;; ("and>" . "cond-let--and>")
;; ("and-let" . "cond-let--and-let")
;; ("if-let" . "cond-let--if-let")
;; ("when$" . "cond-let--when$")
;; ("when-let" . "cond-let--when-let")
;; ("while-let" . "cond-let--while-let")
;; ("match-string" . "match-string")

View File

@@ -1,6 +1,6 @@
;;; magit-core.el --- Core functionality -*- lexical-binding:t -*-
;; Copyright (C) 2008-2025 The Magit Project Contributors
;; Copyright (C) 2008-2026 The Magit Project Contributors
;; Author: Jonas Bernoulli <emacs.magit@jonas.bernoulli.dev>
;; Maintainer: Jonas Bernoulli <emacs.magit@jonas.bernoulli.dev>
@@ -126,6 +126,7 @@ Each of these options falls into one or more of these categories:
;; ("and>" . "cond-let--and>")
;; ("and-let" . "cond-let--and-let")
;; ("if-let" . "cond-let--if-let")
;; ("when$" . "cond-let--when$")
;; ("when-let" . "cond-let--when-let")
;; ("while-let" . "cond-let--while-let")
;; ("match-string" . "match-string")

File diff suppressed because it is too large Load Diff

View File

@@ -1,6 +1,6 @@
;;; magit-dired.el --- Dired support for Magit -*- lexical-binding:t -*-
;; Copyright (C) 2008-2025 The Magit Project Contributors
;; Copyright (C) 2008-2026 The Magit Project Contributors
;; Author: Jonas Bernoulli <emacs.magit@jonas.bernoulli.dev>
;; Maintainer: Jonas Bernoulli <emacs.magit@jonas.bernoulli.dev>
@@ -116,6 +116,7 @@ Interactively, open the file at point."
;; ("and>" . "cond-let--and>")
;; ("and-let" . "cond-let--and-let")
;; ("if-let" . "cond-let--if-let")
;; ("when$" . "cond-let--when$")
;; ("when-let" . "cond-let--when-let")
;; ("while-let" . "cond-let--while-let")
;; ("match-string" . "match-string")

View File

@@ -1,6 +1,6 @@
;;; magit-ediff.el --- Ediff extension for Magit -*- lexical-binding:t -*-
;; Copyright (C) 2008-2025 The Magit Project Contributors
;; Copyright (C) 2008-2026 The Magit Project Contributors
;; Author: Jonas Bernoulli <emacs.magit@jonas.bernoulli.dev>
;; Maintainer: Jonas Bernoulli <emacs.magit@jonas.bernoulli.dev>
@@ -42,19 +42,17 @@
:group 'magit-extensions)
(defcustom magit-ediff-quit-hook
(list #'magit-ediff-cleanup-auxiliary-buffers
#'magit-ediff-restore-previous-winconf)
(list #'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.
The `ediff-quit-hook' is ignored by Ediff sessions which were
invoked using Magit."
:package-version '(magit . "2.2.0")
:package-version '(magit . "4.6.0")
:group 'magit-ediff
:type 'hook
:get #'magit-hook-custom-get
:options (list #'magit-ediff-cleanup-auxiliary-buffers
#'magit-ediff-restore-previous-winconf))
:options (list #'magit-ediff-restore-previous-winconf))
(defcustom magit-ediff-dwim-resolve-function #'magit-ediff-resolve-rest
"The function `magit-ediff-dwim' uses to resolve conflicts."
@@ -115,7 +113,7 @@ recommend you do not further complicate that by enabling this.")
(defvar magit-ediff-previous-winconf nil)
;;;###autoload(autoload 'magit-ediff "magit-ediff" nil)
;;;###autoload(autoload 'magit-ediff "magit-ediff" nil t)
(transient-define-prefix magit-ediff ()
"Show differences using the Ediff package."
:info-manual "(ediff)"
@@ -132,86 +130,8 @@ recommend you do not further complicate that by enabling this.")
("r" "Show range" magit-ediff-compare)
("z" "Show stash" magit-ediff-show-stash)]])
(defmacro magit-ediff-buffers (a b &optional c setup quit file)
"Run Ediff on two or three buffers.
This is a wrapper around `ediff-buffers-internal'.
A, B and C have the form (GET-BUFFER CREATE-BUFFER). If
GET-BUFFER returns a non-nil value, then that buffer is used and
it is not killed when exiting Ediff. Otherwise CREATE-BUFFER
must return a buffer and that is killed when exiting Ediff.
If non-nil, SETUP must be a function. It is called without
arguments after Ediff is done setting up buffers.
If non-nil, QUIT must be a function. It is added to
`ediff-quit-hook' and is called without arguments.
If FILE is non-nil, then perform a merge. The merge result
is put in FILE."
(let (get make kill (char ?A))
(dolist (spec (list a b c))
(if (not spec)
(push nil make)
(pcase-let ((`(,g ,m) spec))
(let ((b (intern (format "buf%c" char))))
(push `(,b ,g) get)
;; This is an unfortunate complication that I have added for
;; the benefit of one user. Pretend we used this instead:
;; (push `(or ,b ,m) make)
(push `(if ,b
(if magit-ediff-use-indirect-buffers
(prog1 (make-indirect-buffer
,b
(generate-new-buffer-name (buffer-name ,b))
t)
(setq ,b nil))
,b)
,m)
make)
(push `(unless ,b
;; For merge jobs Ediff switches buffer names around.
;; See (if ediff-merge-job ...) in `ediff-setup'.
(let ((var ,(if (and file (= char ?C))
'ediff-ancestor-buffer
(intern (format "ediff-buffer-%c" char)))))
(ediff-kill-buffer-carefully var)))
kill))
(cl-incf char))))
(setq get (nreverse get))
(setq make (nreverse make))
(setq kill (nreverse kill))
(let ((mconf (gensym "conf"))
(mfile (gensym "file")))
`(magit-with-toplevel
(let ((,mconf (current-window-configuration))
(,mfile ,file)
,@get)
(ediff-buffers-internal
,@make
(list ,@(and setup (list setup))
(lambda ()
;; We do not want to kill buffers that existed before
;; Ediff was invoked, so we cannot use Ediff's default
;; quit functions. Ediff splits quitting across two
;; hooks for merge jobs but we only ever use one.
(setq-local ediff-quit-merge-hook nil)
(setq-local ediff-quit-hook
(list
,@(and quit (list quit))
(lambda ()
,@kill
(let ((magit-ediff-previous-winconf ,mconf))
(run-hooks 'magit-ediff-quit-hook)))))))
(pcase (list ,(and c t) (and ,mfile t))
('(nil nil) 'ediff-buffers)
('(nil t) 'ediff-merge-buffers)
('(t nil) 'ediff-buffers3)
('(t t) 'ediff-merge-buffers-with-ancestor))
,mfile))))))
;;;###autoload
(defun magit-ediff-resolve-all (file)
;;;###autoload(autoload 'magit-ediff-resolve-all "magit-ediff" nil t)
(transient-define-suffix magit-ediff-resolve-all (file)
"Resolve all conflicts in the FILE at point using Ediff.
If there is no file at point or if it doesn't have any unmerged
@@ -219,16 +139,17 @@ changes, then prompt for a file.
See info node `(magit) Ediffing' for more information about this
and alternative commands."
:inapt-if-not #'magit-anything-unmerged-p
(interactive (list (magit-read-unmerged-file)))
(magit-with-toplevel
(let* ((dir (magit-gitdir))
(revA (or (magit-name-branch "HEAD")
(magit-commit-p "HEAD")))
(magit-commit-oid "HEAD")))
(revB (cl-find-if (##file-exists-p (expand-file-name % dir))
'("MERGE_HEAD" "CHERRY_PICK_HEAD" "REVERT_HEAD")))
(revB (or (magit-name-branch revB)
(magit-commit-p revB)))
(revC (magit-commit-p (magit-git-string "merge-base" revA revB)))
(magit-commit-oid revB)))
(revC (magit-commit-oid (magit-git-string "merge-base" revA revB)))
(fileA (magit--rev-file-name file revA revB))
(fileB (magit--rev-file-name file revB revA))
(fileC (or (magit--rev-file-name file revC revA)
@@ -256,7 +177,7 @@ and alternative commands."
,(format ">>>>>>> %s" revB)))))
(quit (lambda ()
;; For merge jobs Ediff switches buffer names around.
;; At this point `ediff-buffer-C' no longer refer to
;; At this point `ediff-buffer-C' no longer refers to
;; the ancestor buffer but to the merge result buffer.
;; See (if ediff-merge-job ...) in `ediff-setup'.
(when (buffer-live-p ediff-buffer-C)
@@ -266,24 +187,14 @@ and alternative commands."
(goto-char (point-min))
(unless (re-search-forward "^<<<<<<< " nil t)
(magit-stage-files (list file)))))))))
(cond (fileC
(magit-ediff-buffers
((magit-get-revision-buffer revA fileA)
(magit-find-file-noselect revA fileA))
((magit-get-revision-buffer revB fileB)
(magit-find-file-noselect revB fileB))
((magit-get-revision-buffer revC fileC)
(magit-find-file-noselect revC fileC))
setup quit file))
((magit-ediff-buffers
((magit-get-revision-buffer revA fileA)
(magit-find-file-noselect revA fileA))
((magit-get-revision-buffer revB fileB)
(magit-find-file-noselect revB fileB))
nil setup quit file)))))))
(magit-ediff-buffers
(magit-ediff--find-file revA fileA)
(magit-ediff--find-file revB fileB)
(and fileC (magit-ediff--find-file revC fileC))
setup quit file)))))
;;;###autoload
(defun magit-ediff-resolve-rest (file)
;;;###autoload(autoload 'magit-ediff-resolve-rest "magit-ediff" nil t)
(transient-define-suffix magit-ediff-resolve-rest (file)
"Resolve outstanding conflicts in the FILE at point using Ediff.
If there is no file at point or if it doesn't have any unmerged
@@ -291,6 +202,7 @@ changes, then prompt for a file.
See info node `(magit) Ediffing' for more information about this
and alternative commands."
:inapt-if-not #'magit-anything-unmerged-p
(interactive (list (magit-read-unmerged-file)))
(magit-with-toplevel
(with-current-buffer (find-file-noselect file)
@@ -314,32 +226,27 @@ and alternative commands."
(let ((magit-ediff-previous-winconf smerge-ediff-windows))
(run-hooks 'magit-ediff-quit-hook)))))))
;;;###autoload
(defun magit-ediff-stage (file)
;;;###autoload(autoload 'magit-ediff-stage "magit-ediff" nil t)
(transient-define-suffix magit-ediff-stage (file)
"Stage and unstage changes to FILE using Ediff.
FILE has to be relative to the top directory of the repository."
:inapt-if-not #'magit-anything-modified-p
(interactive
(let ((files (magit-tracked-files)))
(list (magit-completing-read "Selectively stage file" files nil t nil nil
(car (member (magit-current-file) files))))))
(let ((files (magit-tracked-files)))
(list (magit-completing-read "Selectively stage file" files nil t nil nil
(car (member (magit-current-file) files))))))
(magit-with-toplevel
(let* ((bufA (magit-get-revision-buffer "HEAD" file))
(bufB (magit-get-revision-buffer "{index}" file))
(lockB (and bufB (buffer-local-value 'buffer-read-only bufB)))
(bufC (get-file-buffer file))
(let* ((bufC (magit-ediff--find-file "{worktree}" file))
;; Use the same encoding for all three buffers or we
;; may end up changing the file in an unintended way.
(bufC* (or bufC (find-file-noselect file)))
(coding-system-for-read
(buffer-local-value 'buffer-file-coding-system bufC*))
(bufA* (magit-find-file-noselect "HEAD" file t))
(bufB* (magit-find-file-index-noselect file t)))
(with-current-buffer bufB* (setq buffer-read-only nil))
(buffer-local-value 'buffer-file-coding-system bufC))
(bufA (magit-ediff--find-file "HEAD" file))
(bufB (magit-ediff--find-file "{index}" file))
(lockB (buffer-local-value 'buffer-read-only bufB)))
(with-current-buffer bufB (setq buffer-read-only nil))
(magit-ediff-buffers
(bufA bufA*)
(bufB bufB*)
(bufC bufC*)
nil
bufA bufB bufC nil
(lambda ()
(when (buffer-live-p ediff-buffer-B)
(when lockB
@@ -353,13 +260,12 @@ FILE has to be relative to the top directory of the repository."
(when (y-or-n-p (format "Save file %s? " buffer-file-name))
(save-buffer)))))))))
;;;###autoload
(defun magit-ediff-compare (revA revB fileA fileB)
;;;###autoload(autoload 'magit-ediff-compare "magit-ediff" nil t)
(transient-define-suffix magit-ediff-compare (revA revB fileA fileB)
"Compare REVA:FILEA with REVB:FILEB using Ediff.
FILEA and FILEB have to be relative to the top directory of the
repository. If REVA or REVB is nil, then this stands for the
working tree state.
repository.
If the region is active, use the revisions on the first and last
line of the region. With a prefix argument, instead of diffing
@@ -367,15 +273,13 @@ the revisions, choose a revision to view changes along, starting
at the common ancestor of both revisions (i.e., use a \"...\"
range)."
(interactive
(pcase-let ((`(,revA ,revB) (magit-ediff-compare--read-revisions
nil current-prefix-arg)))
(nconc (list revA revB)
(magit-ediff-read-files revA revB))))
(pcase-let ((`(,revA ,revB) (magit-ediff-compare--read-revisions
nil current-prefix-arg)))
(nconc (list revA revB)
(magit-ediff-read-files revA revB))))
(magit-ediff-buffers
((if revA (magit-get-revision-buffer revA fileA) (get-file-buffer fileA))
(if revA (magit-find-file-noselect revA fileA) (find-file-noselect fileA)))
((if revB (magit-get-revision-buffer revB fileB) (get-file-buffer fileB))
(if revB (magit-find-file-noselect revB fileB) (find-file-noselect fileB)))))
(magit-ediff--find-file revA fileA)
(magit-ediff--find-file revB fileB)))
(defun magit-ediff-compare--read-revisions (&optional arg mbase)
(let ((input (or arg (magit-diff-read-range-or-commit
@@ -383,12 +287,14 @@ range)."
nil mbase))))
(if-let ((range (magit-split-range input)))
(list (car range) (cdr range))
(list input nil))))
(list input "{worktree}"))))
(defun magit-ediff-read-files (revA revB &optional fileB)
"Read file in REVB, return it and the corresponding file in REVA.
When FILEB is non-nil, use this as REVB's file instead of
prompting for it."
(when (equal revA "{worktree}") (setq revA nil))
(when (equal revB "{worktree}") (setq revB nil))
(unless (and fileB (member fileB (magit-revision-files revB)))
(setq fileB
(or (and fileB
@@ -416,8 +322,8 @@ prompting for it."
revA revB)))
fileB))
;;;###autoload
(defun magit-ediff-dwim ()
;;;###autoload(autoload 'magit-ediff-dwim "magit-ediff" nil t)
(transient-define-suffix magit-ediff-dwim ()
"Compare, stage, or resolve using Ediff.
This command tries to guess what file, and what commit or range
the user wants to compare, stage, or resolve using Ediff. It
@@ -459,7 +365,7 @@ mind at all, then it asks the user for a command to run."
(pcase (magit-diff-type)
('committed (pcase-let ((`(,a ,b)
(magit-ediff-compare--read-revisions
magit-buffer-range)))
magit-buffer-diff-range)))
(setq revA a)
(setq revB b)))
((guard (not magit-ediff-dwim-show-on-hunks))
@@ -490,55 +396,52 @@ mind at all, then it asks the user for a command to run."
(funcall command file))
((call-interactively command)))))))
;;;###autoload
(defun magit-ediff-show-staged (file)
;;;###autoload(autoload 'magit-ediff-show-staged "magit-ediff" nil t)
(transient-define-suffix magit-ediff-show-staged (file)
"Show staged changes using Ediff.
This only allows looking at the changes; to stage, unstage,
and discard changes using Ediff, use `magit-ediff-stage'.
FILE must be relative to the top directory of the repository."
:inapt-if-not #'magit-anything-staged-p
(interactive
(list (magit-read-file-choice "Show staged changes for file"
(magit-staged-files)
"No staged files")))
(magit-ediff-buffers ((magit-get-revision-buffer "HEAD" file)
(magit-find-file-noselect "HEAD" file))
((get-buffer (concat file ".~{index}~"))
(magit-find-file-index-noselect file t))))
(list (magit-read-file-choice "Show staged changes for file"
(magit-staged-files)
"No staged files")))
(magit-ediff-buffers (magit-ediff--find-file "HEAD" file)
(magit-ediff--find-file "{index}" file)))
;;;###autoload
(defun magit-ediff-show-unstaged (file)
;;;###autoload(autoload 'magit-ediff-show-unstaged "magit-ediff" nil t)
(transient-define-suffix magit-ediff-show-unstaged (file)
"Show unstaged changes using Ediff.
This only allows looking at the changes; to stage, unstage,
and discard changes using Ediff, use `magit-ediff-stage'.
FILE must be relative to the top directory of the repository."
:inapt-if-not #'magit-anything-unstaged-p
(interactive
(list (magit-read-file-choice "Show unstaged changes for file"
(magit-unstaged-files)
"No unstaged files")))
(magit-ediff-buffers ((get-buffer (concat file ".~{index}~"))
(magit-find-file-index-noselect file t))
((get-file-buffer file)
(find-file-noselect file))))
(list (magit-read-file-choice "Show unstaged changes for file"
(magit-unstaged-files)
"No unstaged files")))
(magit-ediff-buffers (magit-ediff--find-file "{index}" file)
(magit-ediff--find-file "{worktree}" file)))
;;;###autoload
(defun magit-ediff-show-working-tree (file)
;;;###autoload(autoload 'magit-ediff-show-working-tree "magit-ediff" nil t)
(transient-define-suffix magit-ediff-show-working-tree (file)
"Show changes between `HEAD' and working tree using Ediff.
FILE must be relative to the top directory of the repository."
:inapt-if-not #'magit-anything-modified-p
(interactive
(list (magit-read-file-choice "Show changes in file"
(magit-changed-files "HEAD")
"No changed files")))
(magit-ediff-buffers ((magit-get-revision-buffer "HEAD" file)
(magit-find-file-noselect "HEAD" file))
((get-file-buffer file)
(find-file-noselect file))))
(list (magit-read-file-choice "Show changes in file"
(magit-modified-files)
"No changed files")))
(magit-ediff-buffers (magit-ediff--find-file "HEAD" file)
(magit-ediff--find-file "{worktree}" file)))
;;;###autoload
(defun magit-ediff-show-commit (commit)
;;;###autoload(autoload 'magit-ediff-show-commit "magit-ediff" nil t)
(transient-define-suffix magit-ediff-show-commit (commit)
"Show changes introduced by COMMIT using Ediff."
(interactive (list (magit-read-branch-or-commit "Revision")))
(let ((revA (concat commit "^"))
@@ -547,12 +450,13 @@ FILE must be relative to the top directory of the repository."
revA revB
(magit-ediff-read-files revA revB (magit-current-file)))))
;;;###autoload
(defun magit-ediff-show-stash (stash)
;;;###autoload(autoload 'magit-ediff-show-stash "magit-ediff" nil t)
(transient-define-suffix magit-ediff-show-stash (stash)
"Show changes introduced by STASH using Ediff.
`magit-ediff-show-stash-with-index' controls whether a
three-buffer Ediff is used in order to distinguish changes in the
stash that were staged."
`magit-ediff-show-stash-with-index' controls whether a three-buffer
Ediff is used in order to distinguish changes in the stash that were
staged."
:inapt-if-not #'magit-list-stashes
(interactive (list (magit-read-stash "Stash")))
(pcase-let* ((revA (concat stash "^1"))
(revB (concat stash "^2"))
@@ -562,15 +466,64 @@ stash that were staged."
(if (and magit-ediff-show-stash-with-index
(member fileA (magit-changed-files revB revA)))
(magit-ediff-buffers
((magit-get-revision-buffer revA fileA)
(magit-find-file-noselect revA fileA))
((magit-get-revision-buffer revB fileB)
(magit-find-file-noselect revB fileB))
((magit-get-revision-buffer revC fileC)
(magit-find-file-noselect revC fileC)))
(magit-ediff--find-file revA fileA)
(magit-ediff--find-file revB fileB)
(magit-ediff--find-file revC fileC))
(magit-ediff-compare revA revC fileA fileC))))
(defun magit-ediff-cleanup-auxiliary-buffers ()
;;; Setup
(defun magit-ediff-buffers (a b &optional c setup quit file)
"Run Ediff on two or three buffers A, B and C.
If optional FILE is non-nil, then perform a merge. The merge result
is put in FILE.
Neutralize the hooks `ediff-quit-hook' and `ediff-quit-merge-hook'
because they usually feature functions that do not work for Magit.
Instead run optional QUIT (if non-nil), `magit-ediff--cleanup-buffers'
and `magit-ediff-quit-hook', with no arguments.
Optional SETUP, if non-nil, is called with no arguments after Ediff
is done setting up buffers."
(magit-with-toplevel
(ediff-buffers-internal
a b c
(let ((winconf (current-window-configuration)))
(list (lambda ()
(when setup
(funcall setup))
(setq-local ediff-quit-merge-hook nil)
(setq-local ediff-quit-hook nil)
(when quit
(add-hook 'ediff-quit-hook quit nil t))
(add-hook 'ediff-quit-hook #'magit-ediff--cleanup-buffers t t)
(add-hook 'ediff-quit-hook
(lambda ()
(let ((magit-ediff-previous-winconf winconf))
(run-hooks 'magit-ediff-quit-hook)))
t t))))
(pcase (list (and c t) (and file t))
('(nil nil) 'ediff-buffers)
('(nil t) 'ediff-merge-buffers)
('(t nil) 'ediff-buffers3)
('(t t) 'ediff-merge-buffers-with-ancestor))
file)))
(defun magit-ediff--find-file (rev file)
(let ((buffer (magit-find-file-noselect rev file t 'ediff)))
(when magit-ediff-use-indirect-buffers
(setq buffer (make-indirect-buffer
buffer (generate-new-buffer-name (buffer-name buffer)) t)))
buffer))
;;; Quit
(defun magit-ediff--cleanup-buffers ()
(magit-ediff--bury-buffer ediff-buffer-A)
(magit-ediff--bury-buffer ediff-buffer-B)
(magit-ediff--bury-buffer ediff-buffer-C)
(magit-ediff--bury-buffer ediff-ancestor-buffer)
(let* ((ctl-buf ediff-control-buffer)
(ctl-win (ediff-get-visible-buffer-window ctl-buf))
(ctl-frm ediff-control-frame)
@@ -596,6 +549,11 @@ stash that were staged."
(when (frame-live-p main-frame)
(select-frame main-frame))))
(defun magit-ediff--bury-buffer (buffer)
(when (and (ediff-buffer-live-p buffer)
(eq (buffer-local-value 'magit-buffer--volatile buffer) 'ediff))
(kill-buffer (get-buffer buffer))))
(defun magit-ediff-restore-previous-winconf ()
(set-window-configuration magit-ediff-previous-winconf))
@@ -607,6 +565,7 @@ stash that were staged."
;; ("and>" . "cond-let--and>")
;; ("and-let" . "cond-let--and-let")
;; ("if-let" . "cond-let--if-let")
;; ("when$" . "cond-let--when$")
;; ("when-let" . "cond-let--when-let")
;; ("while-let" . "cond-let--while-let")
;; ("match-string" . "match-string")

View File

@@ -1,6 +1,6 @@
;;; magit-extras.el --- Additional functionality for Magit -*- lexical-binding:t -*-
;; Copyright (C) 2008-2025 The Magit Project Contributors
;; Copyright (C) 2008-2026 The Magit Project Contributors
;; Author: Jonas Bernoulli <emacs.magit@jonas.bernoulli.dev>
;; Maintainer: Jonas Bernoulli <emacs.magit@jonas.bernoulli.dev>
@@ -60,11 +60,11 @@ alternative commands."
["Actions"
(" m" "Invoke mergetool" magit-git-mergetool)]
(interactive
(if (and (not (eq transient-current-command 'magit-git-mergetool))
current-prefix-arg)
(list nil nil t)
(list (magit-read-unmerged-file "Resolve")
(transient-args '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")
(transient-args 'magit-git-mergetool))))
(if transient
(transient-setup 'magit-git-mergetool)
(magit-run-git-async "mergetool" "--gui" args "--" file)))
@@ -131,18 +131,18 @@ prefix or when the current file cannot be determined let the user
choose. When the current buffer is visiting FILENAME instruct
blame to center around the line point is on."
(interactive
(let (revision filename)
(when (or current-prefix-arg
(progn
(setq revision "HEAD")
(not (setq filename (magit-file-relative-name nil 'tracked)))))
(setq revision (magit-read-branch-or-commit "Blame from revision"))
(setq filename (magit-read-file-from-rev revision "Blame file")))
(list revision filename
(and (equal filename
(ignore-errors
(magit-file-relative-name buffer-file-name)))
(line-number-at-pos)))))
(let (revision filename)
(when (or current-prefix-arg
(progn
(setq revision "HEAD")
(not (setq filename (magit-file-relative-name nil 'tracked)))))
(setq revision (magit-read-branch-or-commit "Blame from revision"))
(setq filename (magit-read-file-from-rev revision "Blame file")))
(list revision filename
(and (equal filename
(ignore-errors
(magit-file-relative-name buffer-file-name)))
(line-number-at-pos)))))
(magit-with-toplevel
(magit-process-git 0 "gui" "blame"
(and linenum (list (format "--line=%d" linenum)))
@@ -447,69 +447,69 @@ list returned by `magit-rebase-arguments'."
(user-error "Refusing to reshelve detached head")))
(backup (concat "refs/original/refs/heads/" current)))
(cond
((not commit)
(when (and (magit-ref-p backup)
(not (magit-y-or-n-p
(format "Backup ref %s already exists. Override? "
backup))))
(user-error "Abort"))
(magit-log-select
(lambda (rev)
(magit-reshelve-since rev keyid))
"Type %p on a commit to reshelve it and the commits above it,"))
(t
(cl-flet ((adjust (time offset)
(format-time-string
"%F %T %z"
(+ (floor time)
(* offset 60)
(- (car (decode-time time)))))))
(let* ((start (concat commit "^"))
(range (concat start ".." current))
(time-rev (adjust (float-time (string-to-number
(magit-rev-format "%at" start)))
1))
(time-now (adjust (float-time)
(- (string-to-number
(magit-git-string "rev-list" "--count"
range))))))
(push time-rev magit--reshelve-history)
(let ((date (floor
(float-time
(date-to-time
(read-string "Date for first commit: "
time-now 'magit--reshelve-history))))))
(with-environment-variables (("FILTER_BRANCH_SQUELCH_WARNING" "1"))
(magit-with-toplevel
(magit-run-git-async
"filter-branch" "--force" "--env-filter"
(format
"case $GIT_COMMIT in %s\nesac"
(mapconcat
(lambda (rev)
(prog1
(concat
(format "%s) " rev)
(and (not magit-reshelve-since-committer-only)
(format "export GIT_AUTHOR_DATE=\"%s\"; " date))
(format "export GIT_COMMITTER_DATE=\"%s\";;" date))
(cl-incf date 60)))
(magit-git-lines "rev-list" "--reverse" range)
" "))
(and keyid
(list "--commit-filter"
(format "git commit-tree --gpg-sign=%s \"$@\";"
keyid)))
range "--"))
(set-process-sentinel
magit-this-process
(lambda (process event)
(when (memq (process-status process) '(exit signal))
(if (> (process-exit-status process) 0)
(magit-process-sentinel process event)
(process-put process 'inhibit-refresh t)
(magit-process-sentinel process event)
(magit-run-git "update-ref" "-d" backup)))))))))))))
((not commit)
(when (and (magit-ref-p backup)
(not (magit-y-or-n-p
(format "Backup ref %s already exists. Override? "
backup))))
(user-error "Abort"))
(magit-log-select
(lambda (rev)
(magit-reshelve-since rev keyid))
"Type %p on a commit to reshelve it and the commits above it,"))
(t
(cl-flet ((adjust (time offset)
(format-time-string
"%F %T %z"
(+ (floor time)
(* offset 60)
(- (car (decode-time time)))))))
(let* ((start (concat commit "^"))
(range (concat start ".." current))
(time-rev (adjust (float-time (string-to-number
(magit-rev-format "%at" start)))
1))
(time-now (adjust (float-time)
(- (string-to-number
(magit-git-string "rev-list" "--count"
range))))))
(push time-rev magit--reshelve-history)
(let ((date (floor
(float-time
(date-to-time
(read-string "Date for first commit: "
time-now 'magit--reshelve-history))))))
(with-environment-variables (("FILTER_BRANCH_SQUELCH_WARNING" "1"))
(magit-with-toplevel
(magit-run-git-async
"filter-branch" "--force" "--env-filter"
(format
"case $GIT_COMMIT in %s\nesac"
(mapconcat
(lambda (rev)
(prog1
(concat
(format "%s) " rev)
(and (not magit-reshelve-since-committer-only)
(format "export GIT_AUTHOR_DATE=\"%s\"; " date))
(format "export GIT_COMMITTER_DATE=\"%s\";;" date))
(cl-incf date 60)))
(magit-git-lines "rev-list" "--reverse" range)
" "))
(and keyid
(list "--commit-filter"
(format "git commit-tree --gpg-sign=%s \"$@\";"
keyid)))
range "--"))
(set-process-sentinel
magit-this-process
(lambda (process event)
(when (memq (process-status process) '(exit signal))
(if (> (process-exit-status process) 0)
(magit-process-sentinel process event)
(process-put process 'inhibit-refresh t)
(magit-process-sentinel process event)
(magit-run-git "update-ref" "-d" backup)))))))))))))
;;; Revision Stack
@@ -593,16 +593,16 @@ revision). If not called inside a repository and with an empty
stack, or with two prefix arguments, then read the repository in
the minibuffer too."
(interactive
(if (or current-prefix-arg (not magit-revision-stack))
(let ((default-directory
(or (and (not (= (prefix-numeric-value current-prefix-arg) 16))
(or (magit-toplevel)
(cadr (car magit-revision-stack))))
(magit-read-repository))))
(list (magit-read-branch-or-commit "Insert revision")
default-directory))
(push (caar magit-revision-stack) magit-revision-history)
(pop magit-revision-stack)))
(if (or current-prefix-arg (not magit-revision-stack))
(let ((default-directory
(or (and (not (= (prefix-numeric-value current-prefix-arg) 16))
(or (magit-toplevel)
(cadr (car magit-revision-stack))))
(magit-read-repository))))
(list (magit-read-branch-or-commit "Insert revision")
default-directory))
(push (caar magit-revision-stack) magit-revision-history)
(pop magit-revision-stack)))
(unless rev
(user-error "Revision stack is empty"))
(pcase-let ((`(,pnt-format ,eob-format ,idx-format)
@@ -737,9 +737,9 @@ abbreviated revision to the `kill-ring' and the
(cl-case major-mode
(magit-diff-mode
(if (string-match "\\.\\.\\.?\\(.+\\)"
magit-buffer-range)
(match-str 1 magit-buffer-range)
magit-buffer-range))
magit-buffer-diff-range)
(match-str 1 magit-buffer-diff-range)
magit-buffer-diff-range))
(magit-status-mode "HEAD")))]
[_(magit-commit-p rev)]
(setq rev (magit-rev-parse
@@ -833,6 +833,7 @@ In Magit diffs, also skip over - and + at the beginning of the line."
;; ("and>" . "cond-let--and>")
;; ("and-let" . "cond-let--and-let")
;; ("if-let" . "cond-let--if-let")
;; ("when$" . "cond-let--when$")
;; ("when-let" . "cond-let--when-let")
;; ("while-let" . "cond-let--while-let")
;; ("match-string" . "match-string")

View File

@@ -1,6 +1,6 @@
;;; magit-fetch.el --- Download objects and refs -*- lexical-binding:t -*-
;; Copyright (C) 2008-2025 The Magit Project Contributors
;; Copyright (C) 2008-2026 The Magit Project Contributors
;; Author: Jonas Bernoulli <emacs.magit@jonas.bernoulli.dev>
;; Maintainer: Jonas Bernoulli <emacs.magit@jonas.bernoulli.dev>
@@ -81,10 +81,10 @@ push-remote."
(remote (magit-get-push-remote branch))
(v (magit--push-remote-variable branch t)))
(cond
((member remote (magit-list-remotes)) remote)
(remote
(format "%s, replacing invalid" v))
((format "%s, setting that" v)))))
((member remote (magit-list-remotes)) remote)
(remote
(format "%s, replacing invalid" v))
((format "%s, setting that" v)))))
;;;###autoload(autoload 'magit-fetch-from-upstream "magit-fetch" nil t)
(transient-define-suffix magit-fetch-from-upstream (remote args)
@@ -117,20 +117,20 @@ results in an error."
(defun magit-fetch-branch (remote branch args)
"Fetch a BRANCH from a REMOTE."
(interactive
(let ((remote (magit-read-remote-or-url "Fetch from remote or url")))
(list remote
(magit-read-remote-branch "Fetch branch" remote)
(magit-fetch-arguments))))
(let ((remote (magit-read-remote-or-url "Fetch from remote or url")))
(list remote
(magit-read-remote-branch "Fetch branch" remote)
(magit-fetch-arguments))))
(magit-git-fetch remote (cons branch args)))
;;;###autoload
(defun magit-fetch-refspec (remote refspec args)
"Fetch a REFSPEC from a REMOTE."
(interactive
(let ((remote (magit-read-remote-or-url "Fetch from remote or url")))
(list remote
(magit-read-refspec "Fetch using refspec" remote)
(magit-fetch-arguments))))
(let ((remote (magit-read-remote-or-url "Fetch from remote or url")))
(list remote
(magit-read-refspec "Fetch using refspec" remote)
(magit-fetch-arguments))))
(magit-git-fetch remote (cons refspec args)))
;;;###autoload
@@ -188,6 +188,7 @@ with a prefix argument."
;; ("and>" . "cond-let--and>")
;; ("and-let" . "cond-let--and-let")
;; ("if-let" . "cond-let--if-let")
;; ("when$" . "cond-let--when$")
;; ("when-let" . "cond-let--when-let")
;; ("while-let" . "cond-let--while-let")
;; ("match-string" . "match-string")

View File

@@ -1,6 +1,6 @@
;;; magit-files.el --- Finding files -*- lexical-binding:t -*-
;; Copyright (C) 2008-2025 The Magit Project Contributors
;; Copyright (C) 2008-2026 The Magit Project Contributors
;; Author: Jonas Bernoulli <emacs.magit@jonas.bernoulli.dev>
;; Maintainer: Jonas Bernoulli <emacs.magit@jonas.bernoulli.dev>
@@ -31,10 +31,21 @@
(require 'magit)
(declare-function ediff-quit "ediff-util" (reverse-default-keep-variants))
;;; Find Blob
(defvar magit-find-file-hook nil)
(add-hook 'magit-find-file-hook #'magit-blob-mode)
(define-obsolete-variable-alias 'magit-find-file-hook
'magit-find-blob-hook "Magit 4.6.0")
(define-obsolete-variable-alias 'magit-find-index-hook
'magit-find-blob-hook "Magit 4.6.0")
(defvar magit-find-blob-hook (list #'magit-blob-mode))
(defvar-local magit-buffer-blob-oid--init nil)
(defvar-local magit-buffer--volatile nil)
(put 'magit-buffer--volatile 'permanent-local t)
;;;###autoload
(defun magit-find-file (rev file)
@@ -44,7 +55,7 @@ already exists. If prior to calling this command the current
buffer and/or cursor position is about the same file, then go
to the line and column corresponding to that location."
(interactive (magit-find-file-read-args "Find file"))
(magit-find-file--internal rev file #'pop-to-buffer-same-window))
(pop-to-buffer-same-window (magit-find-file-noselect rev file)))
;;;###autoload
(defun magit-find-file-other-window (rev file)
@@ -54,7 +65,7 @@ already exists. If prior to calling this command the current
buffer and/or cursor position is about the same file, then go to
the line and column corresponding to that location."
(interactive (magit-find-file-read-args "Find file in other window"))
(magit-find-file--internal rev file #'switch-to-buffer-other-window))
(switch-to-buffer-other-window (magit-find-file-noselect rev file)))
;;;###autoload
(defun magit-find-file-other-frame (rev file)
@@ -64,140 +75,256 @@ already exists. If prior to calling this command the current
buffer and/or cursor position is about the same file, then go to
the line and column corresponding to that location."
(interactive (magit-find-file-read-args "Find file in other frame"))
(magit-find-file--internal rev file #'switch-to-buffer-other-frame))
(switch-to-buffer-other-frame (magit-find-file-noselect rev file)))
(defun magit-find-file-read-args (prompt)
(let ((pseudo-revs '("{worktree}" "{index}")))
(let ((rev (magit-completing-read "Find file from revision"
(append pseudo-revs
(magit-list-refnames nil t))
nil 'any nil 'magit-revision-history
(or (magit-branch-or-commit-at-point)
(magit-get-current-branch)))))
(list rev
(magit-read-file-from-rev (if (member rev pseudo-revs) "HEAD" rev)
prompt)))))
(let* ((pseudo-revs '("{worktree}" "{index}"))
(rev (magit-completing-read "Find file from revision"
(append pseudo-revs
(magit-list-refnames nil t))
nil 'any nil 'magit-revision-history
(or (magit-branch-or-commit-at-point)
(magit-get-current-branch)))))
(list rev
(magit-read-file-from-rev (if (member rev pseudo-revs) "HEAD" rev)
prompt))))
(defun magit-find-file--internal (rev file fn)
(let ((buf (magit-find-file-noselect rev file))
line col)
(when-let ((visited-file (magit-file-relative-name)))
(setq line (line-number-at-pos))
(setq col (current-column))
(cond
((not (equal visited-file file)))
((equal magit-buffer-revision rev))
((equal rev "{worktree}")
(setq line (magit-diff-visit--offset file magit-buffer-revision line)))
((equal rev "{index}")
(setq line (magit-diff-visit--offset file nil line)))
(magit-buffer-revision
(setq line (magit-diff-visit--offset
file (concat magit-buffer-revision ".." rev) line)))
((setq line (magit-diff-visit--offset file (list "-R" rev) line)))))
(funcall fn buf)
(when line
(with-current-buffer buf
(widen)
(goto-char (point-min))
(forward-line (1- line))
(move-to-column col)))
buf))
(defun magit-find-file-noselect (rev file &optional revert)
(defun magit-find-file-noselect (rev file &optional no-restore-position volatile)
"Read FILE from REV into a buffer and return the buffer.
REV is a revision or one of \"{worktree}\" or \"{index}\". FILE must
be relative to the top directory of the repository. Non-nil REVERT
means to revert the buffer. If `ask-revert', then only after asking.
A non-nil value for REVERT is ignored if REV is \"{worktree}\"."
(let* ((topdir (magit-toplevel))
(absolute (file-name-absolute-p file))
(file-abs (if absolute file (expand-file-name file topdir)))
(file-rel (if absolute (file-relative-name file topdir) file))
(defdir (file-name-directory file-abs))
(rev (magit--abbrev-if-hash rev)))
(if (equal rev "{worktree}")
(let ((revert-without-query
(if (and$ (find-buffer-visiting file-abs)
(buffer-local-value 'auto-revert-mode $))
(cons "." revert-without-query)
revert-without-query)))
(find-file-noselect file-abs))
(with-current-buffer (magit-get-revision-buffer-create rev file-rel)
(when (or (not magit-buffer-file-name)
(if (eq revert 'ask-revert)
(y-or-n-p (format "%s already exists; revert it? "
(buffer-name))))
revert)
(setq magit-buffer-revision rev)
(setq magit-buffer-refname rev)
(setq magit-buffer-file-name file-abs)
(setq default-directory (if (file-exists-p defdir) defdir topdir))
(setq-local revert-buffer-function #'magit-revert-rev-file-buffer)
(revert-buffer t t)
(run-hooks (if (equal rev "{index}")
'magit-find-index-hook
'magit-find-file-hook)))
(current-buffer)))))
REV is a revision or one of \"{worktree}\" or \"{index}\".
Non-interactively REV can also be a blob object."
(let* ((rev (pcase rev
('nil "{worktree}")
((and "{index}"
(guard (length> (magit--file-index-stages file) 1)))
"{worktree}")
(rev rev)))
(topdir (magit-toplevel))
(file (expand-file-name file topdir))
(file-relative (file-relative-name file topdir))
(buffer
(cond-let
((equal rev "{worktree}")
(let ((revert-without-query
(if (and$ (find-buffer-visiting file)
(buffer-local-value 'auto-revert-mode $))
(cons "." revert-without-query)
revert-without-query)))
(find-file-noselect file volatile)))
((not topdir)
(error "%s is not inside a Git repository" file))
([defdir (file-name-directory file)]
[rev (magit--abbrev-if-oid rev)]
(unless (file-in-directory-p file topdir)
(error "%s is not inside Git repository %s" file topdir))
(with-current-buffer
(magit--get-blob-buffer rev file-relative volatile)
(if (magit-blob-p rev)
(setq magit-buffer-blob-oid--init (magit-rev-parse rev))
(setq magit-buffer-revision rev))
(setq magit-buffer-file-name file)
(setq default-directory
(if (file-exists-p defdir) defdir topdir))
(setq-local revert-buffer-function #'magit--revert-blob-buffer)
(magit--refresh-blob-buffer)
(current-buffer)))
((error "Unexpected error")))))
(when (and (not no-restore-position)
(equal (magit-file-relative-name) file-relative))
(let ((pos (magit-find-file--position)))
(with-current-buffer buffer
(apply #'magit-find-file--restore-position pos))))
buffer))
(defun magit-get-revision-buffer-create (rev file)
(magit-get-revision-buffer rev file t))
(defun magit--get-blob-buffer (obj file &optional volatile)
;; If OBJ is a commit, is assummed to be abbreviated.
;; FILE is assumed to be relative to the top-level.
(cond-let
([buf (if (magit-blob-p obj)
(magit--find-buffer 'magit-buffer-blob-oid (magit-rev-parse obj)
'magit-buffer-file-name file)
(magit--find-buffer 'magit-buffer-revision obj
'magit-buffer-file-name file))]
(with-current-buffer buf
(when (and (not volatile) magit-buffer--volatile)
(setq magit-buffer--volatile nil)
(rename-buffer (magit--blob-buffer-name obj file))
(magit--blob-cache-remove buf)))
buf)
([buf (get-buffer-create (magit--blob-buffer-name obj file volatile))]
(with-current-buffer buf
(setq magit-buffer--volatile volatile)
(magit--blob-cache-put buf))
(buffer-enable-undo buf)
buf)))
(defun magit-get-revision-buffer (rev file &optional create)
(funcall (if create #'get-buffer-create #'get-buffer)
(format "%s.~%s~" file (subst-char-in-string ?/ ?_ rev))))
(defun magit--blob-buffer-name (obj file &optional volatile)
(format "%s%s.~%s~"
(if volatile " " "")
(or file (and (magit-blob-p obj) "{blob}"))
(subst-char-in-string ?/ ?_ obj)))
(defun magit-revert-rev-file-buffer (_ignore-auto noconfirm)
(when (or noconfirm
(and (not (buffer-modified-p))
(catch 'found
(dolist (regexp revert-without-query)
(when (string-match regexp magit-buffer-file-name)
(throw 'found t)))))
(yes-or-no-p (format "Revert buffer from Git %s? "
(if (equal magit-buffer-refname "{index}")
"index"
(concat "revision " magit-buffer-refname)))))
(let* ((inhibit-read-only t)
(default-directory (magit-toplevel))
(file (file-relative-name magit-buffer-file-name))
(coding-system-for-read (or coding-system-for-read 'undecided)))
(erase-buffer)
(magit-git-insert "cat-file" "-p"
(if (equal magit-buffer-refname "{index}")
(concat ":" file)
(concat magit-buffer-refname ":" file)))
(setq buffer-file-coding-system last-coding-system-used))
(let ((buffer-file-name magit-buffer-file-name)
(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)))
(defun magit--revert-blob-buffer (_ignore-auto _noconfirm)
(let ((pos (magit-find-file--position)))
(magit--refresh-blob-buffer t)
(apply #'magit-find-file--restore-position pos)))
(defun magit--refresh-blob-buffer (&optional force)
(let ((old-blob-oid magit-buffer-blob-oid))
(cond
(magit-buffer-revision
(setq magit-buffer-revision-oid
(magit-commit-oid magit-buffer-revision t))
(setq magit-buffer-blob-oid
(magit-blob-oid magit-buffer-revision magit-buffer-file-name)))
(magit-buffer-blob-oid--init
(setq magit-buffer-blob-oid magit-buffer-blob-oid--init)
(setq magit-buffer-blob-oid--init nil)))
(when (or force (not (equal old-blob-oid magit-buffer-blob-oid)))
(let ((inhibit-read-only t))
(erase-buffer)
(save-excursion
(magit--insert-blob-contents magit-buffer-revision
(magit-file-relative-name))))
(magit--blob-normal-mode))))
(defun magit--blob-normal-mode ()
(let ((buffer-file-name magit-buffer-file-name)
(after-change-major-mode-hook
;; Inhibit diff-hl and eglot; see bb8a65269d and 234a787b8c.
(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))
(buffer-name (buffer-name)))
;; `font-lock-mode' contains a hardcoded condition that prevents it
;; from being enabled in hidden buffers. Use a fake `buffer-name'
;; to trick it into believing the buffer is not hidden.
(if (eq (aref buffer-name 0) ?\s)
(letrec ((adv (lambda (fn &optional buffer)
(let ((name (funcall fn buffer)))
(if (equal name buffer-name)
(substring name 1)
name)))))
(advice-add 'buffer-name :around adv)
(unwind-protect
(normal-mode (not enable-local-variables))
(advice-remove 'buffer-name adv)))
;; We want `normal-mode' to respect nil `enable-local-variables'.
;; The FIND-FILE argument wasn't designed for our use case, so we
;; have to use this strange invocation to achieve that.
;; The FIND-FILE argument wasn't designed for our use case,
;; so we have to use this strange invocation to achieve that.
(normal-mode (not enable-local-variables)))
(setq buffer-read-only t)
(set-buffer-modified-p nil)
(goto-char (point-min))))
(run-hooks 'magit-find-blob-hook)))
(defun magit-find-file--position ()
(list (or magit-buffer-revision-oid magit-buffer-revision "{worktree}")
magit-buffer-blob-oid
(line-number-at-pos)
(current-column)))
(defun magit-find-file--restore-position (before blob line col)
(let ((file (magit-file-relative-name))
(rev (or magit-buffer-revision-oid magit-buffer-revision "{worktree}")))
(goto-char (point-min))
(forward-line
(1-
(pcase (list before rev)
((guard (equal magit-buffer-blob-oid blob)) line)
('("{worktree}" "{worktree}") line)
('("{worktree}" "{index}")
(magit-diff-visit--offset line file "-R"))
(`("{worktree}" ,_)
(magit-diff-visit--offset line file "-R" rev))
('("{index}" "{worktree}")
(magit-diff-visit--offset line file))
('("{index}" "{index}")
(magit-diff-visit--offset line (list blob magit-buffer-blob-oid file)))
(`("{index}" ,_)
(magit-diff-visit--offset line file "-R" "--cached"))
(`(,_ "{worktree}")
(magit-diff-visit--offset line file before))
(`(,_ "{index}")
(magit-diff-visit--offset line file "--cached"))
(_
(magit-diff-visit--offset line file before rev)))))
(move-to-column col)))
(defvar magit--blob-cache-limit 100
"Limit number of volatile blob buffers to be kept alive.
If nil, only use `magit--blob-cache-timeout'.")
(defvar magit--blob-cache-timeout 1800
"Limit age, since last access, of volatile blob buffers to be kept alive.
Age is tracked in seconds. If nil, only use `magit--blob-cache-limit'.")
(defvar magit--blob-cache-interval 600
"Seconds between volatile blob buffer pruning runs.")
(defvar magit--blob-cache-timer nil)
(defvar magit--blob-cache nil)
(defun magit--blob-cache-put (buffer)
(if-let ((elt (assq buffer magit--blob-cache)))
(setcdr elt (current-time))
(push (cons buffer (current-time)) magit--blob-cache))
(magit--blob-cache-start))
(defun magit--blob-cache-remove (buffer)
(and$ (assq buffer magit--blob-cache)
(delq $ magit--blob-cache)))
(defun magit--blob-cache-start ()
(when (and magit--blob-cache-interval
(not magit--blob-cache-timer))
(setq magit--blob-cache-timer
(run-with-timer magit--blob-cache-interval nil
#'magit--blob-cache-prune))))
(defun magit--blob-cache-prune ()
(when magit--blob-cache-timer
(cancel-timer magit--blob-cache-timer))
(pcase-let
((`(,active ,rest)
(magit--separate
(pcase-lambda (`(,buffer))
(or (get-buffer-window buffer t)
(not (eq (buffer-local-value 'magit-buffer--volatile buffer) t))))
magit--blob-cache)))
(when magit--blob-cache-timeout
(setq rest
(seq-filter (pcase-lambda (`(,buffer . ,time))
(cond
((not (buffer-live-p buffer)) nil)
((time-less-p (time-subtract (current-time) time)
magit--blob-cache-timeout)
buffer)
(t (kill-buffer buffer) nil)))
rest)))
(when-let* ((_ magit--blob-cache-limit)
(ceiling (- magit--blob-cache-limit (length active)))
(_ (length> rest ceiling)))
(let ((sorted (static-if (>= emacs-major-version 30)
(sort rest :key (##float-time (cdr %))
:lessp #'< :reverse t)
(cl-sort rest #'> :key (##float-time (cdr %))))))
(dolist (kill (nthcdr ceiling sorted))
(kill-buffer (car kill)))
(setq rest (ntake ceiling sorted))))
(setq magit--blob-cache (nconc active rest)))
(magit--blob-cache-start))
(defun magit--blob-cache-zap ()
(pcase-dolist (`(,buffer) magit--blob-cache)
(kill-buffer buffer))
(setq magit--blob-cache nil))
(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
(unless magit-buffer-blob-oid
(apply fn args)))
;;; Find Index
(defvar magit-find-index-hook nil)
(add-hook 'magit-find-index-hook #'magit-blob-mode)
(defun magit-find-file-index-noselect (file &optional revert)
"Read FILE from the index into a buffer and return the buffer.
FILE must to be relative to the top directory of the repository."
(magit-find-file-noselect "{index}" file (or revert 'ask-revert)))
;;; Update Index
(defun magit-update-index ()
"Update the index with the contents of the current buffer.
@@ -205,7 +332,7 @@ The current buffer has to be visiting a file in the index, which
is done using `magit-find-index-noselect'."
(interactive)
(let ((file (magit-file-relative-name)))
(unless (equal magit-buffer-refname "{index}")
(unless (equal magit-buffer-revision "{index}")
(user-error "%s isn't visiting the index" file))
(if (y-or-n-p (format "Update index with contents of %s?" (buffer-name)))
(let ((index (make-temp-name
@@ -230,8 +357,8 @@ is done using `magit-find-index-noselect'."
(set-buffer-modified-p nil)
(magit-run-after-apply-functions file "un-/stage"))
(message "Abort")))
(when-let ((buffer (magit-get-mode-buffer 'magit-status-mode)))
(with-current-buffer buffer
(when$ (magit-get-mode-buffer 'magit-status-mode)
(with-current-buffer $
(magit-refresh)))
t)
@@ -248,9 +375,9 @@ This command is like `find-file', except that it temporarily
binds `default-directory' to the actual git directory, while
reading the FILENAME."
(interactive
(let ((default-directory (magit-gitdir)))
(find-file-read-args "Find file: "
(confirm-nonexistent-file-or-buffer))))
(let ((default-directory (magit-gitdir)))
(find-file-read-args "Find file: "
(confirm-nonexistent-file-or-buffer))))
(find-file filename wildcards))
(defun magit-find-git-config-file-other-window (filename &optional wildcards)
@@ -264,9 +391,9 @@ This command is like `find-file-other-window', except that it
temporarily binds `default-directory' to the actual git
directory, while reading the FILENAME."
(interactive
(let ((default-directory (magit-gitdir)))
(find-file-read-args "Find file in other window: "
(confirm-nonexistent-file-or-buffer))))
(let ((default-directory (magit-gitdir)))
(find-file-read-args "Find file in other window: "
(confirm-nonexistent-file-or-buffer))))
(find-file-other-window filename wildcards))
(defun magit-find-git-config-file-other-frame (filename &optional wildcards)
@@ -280,9 +407,9 @@ This command is like `find-file-other-frame', except that it
temporarily binds `default-directory' to the actual git
directory, while reading the FILENAME."
(interactive
(let ((default-directory (magit-gitdir)))
(find-file-read-args "Find file in other frame: "
(confirm-nonexistent-file-or-buffer))))
(let ((default-directory (magit-gitdir)))
(find-file-read-args "Find file in other frame: "
(confirm-nonexistent-file-or-buffer))))
(find-file-other-frame filename wildcards))
;;; File Dispatch
@@ -345,6 +472,7 @@ to `magit-dispatch'."
(defvar-keymap magit-blob-mode-map
:doc "Keymap for `magit-blob-mode'."
"g" #'revert-buffer
"p" #'magit-blob-previous
"n" #'magit-blob-next
"b" #'magit-blame-addition
@@ -360,18 +488,32 @@ Currently this only adds the following key bindings.
:package-version '(magit . "2.3.0"))
(defun magit-bury-buffer (&optional kill-buffer)
"Bury the current buffer, or with a prefix argument kill it."
"Bury the current buffer, or with a prefix argument kill it.
If the buffer is used by an Ediff session, refuse to kill or bury just
that buffer. That former would break the session and the latter makes
little sense in this context. Instead offer to quit the whole session."
(interactive "P")
(if kill-buffer (kill-buffer) (bury-buffer)))
(cond ((bound-and-true-p ediff-this-buffer-ediff-sessions)
(ediff-quit nil))
(kill-buffer (kill-buffer))
((bury-buffer))))
(defun magit-bury-or-kill-buffer (&optional bury-buffer)
"Bury the current buffer if displayed in multiple windows, else kill it.
With a prefix argument only bury the buffer even if it is only displayed
in a single window."
With a prefix argument only bury the buffer even if it is only
displayed in a single window.
If the buffer is used by an Ediff session, refuse to kill or bury just
that buffer. That former would break the session and the latter makes
little sense in this context. Instead offer to quit the whole session."
(interactive "P")
(if (or bury-buffer (cdr (get-buffer-window-list nil nil t)))
(bury-buffer)
(kill-buffer)))
(cond ((bound-and-true-p ediff-this-buffer-ediff-sessions)
(ediff-quit nil))
((or bury-buffer (cdr (get-buffer-window-list nil nil t)))
(bury-buffer))
((kill-buffer))))
(defun magit-kill-this-buffer ()
"Kill the current buffer."
@@ -412,7 +554,7 @@ When visiting a blob or the version from the index, then go to
the same location in the respective file in the working tree."
(interactive)
(if-let ((file (magit-file-relative-name)))
(magit-find-file--internal "{worktree}" file #'pop-to-buffer-same-window)
(pop-to-buffer-same-window (magit-find-file-noselect "{worktree}" file))
(user-error "Not visiting a blob")))
(defun magit-blob-visit (rev file)
@@ -495,11 +637,11 @@ staged as well as unstaged changes."
NEWNAME may be a file or directory name. If FILE isn't tracked in
Git, fallback to using `rename-file'."
(interactive
(let* ((file (magit-read-file "Rename file"))
(path (expand-file-name file (magit-toplevel))))
(list path (expand-file-name
(read-file-name (format "Move %s to destination: " file)
(file-name-directory path))))))
(let* ((file (magit-read-file "Rename file"))
(path (expand-file-name file (magit-toplevel))))
(list path (expand-file-name
(read-file-name (format "Move %s to destination: " file)
(file-name-directory path))))))
(let ((oldbuf (get-file-buffer file))
(dstdir (file-name-directory newname))
(dstfile (if (directory-name-p newname)
@@ -548,9 +690,9 @@ Git, then fallback to using `delete-file'."
(defun magit-file-checkout (rev file)
"Checkout FILE from REV."
(interactive
(let ((rev (magit-read-branch-or-commit
"Checkout from revision" magit-buffer-revision)))
(list rev (magit-read-file-from-rev rev "Checkout file" nil t))))
(let ((rev (magit-read-branch-or-commit
"Checkout from revision" magit-buffer-revision)))
(list rev (magit-read-file-from-rev rev "Checkout file" nil t))))
(magit-with-toplevel
(magit-run-git "checkout" rev "--" file)))
@@ -624,6 +766,17 @@ If DEFAULT is non-nil, use this as the default value instead of
(define-obsolete-function-alias 'magit-find-file-noselect-1
'magit-find-file-noselect "Magit 4.4.0")
(defun magit-find-file--internal (rev file display)
(declare (obsolete magit-find-file-noselect "Magit 4.6.0"))
(let ((buf (magit-find-file-noselect rev file)))
(funcall display buf)
buf))
(defun magit-find-file-index-noselect (file)
"Read FILE from the index into a buffer and return the buffer."
(declare (obsolete magit-find-file-noselect "Magit 4.6.0"))
(magit-find-file-noselect "{index}" file t))
(provide 'magit-files)
;; Local Variables:
;; read-symbol-shorthands: (
@@ -631,6 +784,7 @@ If DEFAULT is non-nil, use this as the default value instead of
;; ("and>" . "cond-let--and>")
;; ("and-let" . "cond-let--and-let")
;; ("if-let" . "cond-let--if-let")
;; ("when$" . "cond-let--when$")
;; ("when-let" . "cond-let--when-let")
;; ("while-let" . "cond-let--while-let")
;; ("match-string" . "match-string")

View File

@@ -1,6 +1,6 @@
;;; magit-git.el --- Git functionality -*- lexical-binding:t -*-
;; Copyright (C) 2008-2025 The Magit Project Contributors
;; Copyright (C) 2008-2026 The Magit Project Contributors
;; Author: Jonas Bernoulli <emacs.magit@jonas.bernoulli.dev>
;; Maintainer: Jonas Bernoulli <emacs.magit@jonas.bernoulli.dev>
@@ -48,7 +48,6 @@
(defvar magit-buffer-file-name)
(defvar magit-buffer-log-args)
(defvar magit-buffer-log-files)
(defvar magit-buffer-refname)
(defvar magit-buffer-revision)
;; From `magit-process'.
@@ -72,6 +71,8 @@
(cl-pushnew 'orig-rev eieio--known-slot-names)
(cl-pushnew 'number eieio--known-slot-names))
(defvar crm-prompt) ; Emacs 31.1
;;; Options
;; For now this is shared between `magit-process' and `magit-git'.
@@ -393,7 +394,7 @@ to do the following.
* Prepend `magit-git-global-arguments' to ARGS.
* If ASYNC is non-nil and `magit-overriding-githook-directory' is non-nil
and valid, set `core.hooksPath' by adding additional aguments to ARGS.
and valid, set `core.hooksPath' by adding additional arguments to ARGS.
* Flatten ARGS, removing nil arguments.
* If `system-type' is `windows-nt', encode ARGS to `w32-ansi-code-page'."
(cond ((not async))
@@ -434,22 +435,6 @@ to do the following.
"Execute Git with ARGS, returning t if its exit code is 1."
(= (magit-git-exit-code args) 1))
(defun magit-git-string-p (&rest args)
"Execute Git with ARGS, returning the first line of its output.
If the exit code isn't zero or if there is no output, then return
nil. Neither of these results is considered an error; if that is
what you want, then use `magit-git-string-ng' instead.
This is an experimental replacement for `magit-git-string', and
still subject to major changes."
(magit--with-refresh-cache (cons default-directory args)
(magit--with-temp-process-buffer
(and (zerop (magit-process-git t args))
(not (bobp))
(progn
(goto-char (point-min))
(buffer-substring-no-properties (point) (line-end-position)))))))
(defun magit-git-string-ng (&rest args)
"Execute Git with ARGS, returning the first line of its output.
If the exit code isn't zero or if there is no output, then that
@@ -459,7 +444,7 @@ buffer (creating it if necessary) and the error message is shown
in the status buffer (provided it exists).
This is an experimental replacement for `magit-git-string', and
still subject to major changes. Also see `magit-git-string-p'."
still subject to major changes."
(magit--with-refresh-cache
(list default-directory 'magit-git-string-ng args)
(magit--with-temp-process-buffer
@@ -526,9 +511,9 @@ signal `magit-invalid-git-boolean'."
(defun magit-git-config-p (variable &optional default)
"Return the boolean value of the Git variable VARIABLE.
VARIABLE has to be specified as a string. Return DEFAULT (which
defaults to nil) if VARIABLE is unset. If VARIABLE's value isn't
a boolean, then raise an error."
VARIABLE has to be specified as a string. If VARIABLE is unset,
return nil by default, unless DEFAULT is non-nil, in which case
return t. Signal an error if VARIABLE is set but not a boolean."
(let ((args (list "config" "--bool" "--default" (if default "true" "false")
variable)))
(magit--with-refresh-cache (cons default-directory args)
@@ -561,12 +546,12 @@ insert the run command and stderr into the process buffer."
(goto-char (point-max))
(setq errmsg
(cond
((eq return-error 'full)
(let ((str (buffer-string)))
(and (not (equal str "")) str)))
((functionp magit-git-debug)
(funcall magit-git-debug (buffer-string)))
((magit--locate-error-message)))))
((eq return-error 'full)
(let ((str (buffer-string)))
(and (not (equal str "")) str)))
((functionp magit-git-debug)
(funcall magit-git-debug (buffer-string)))
((magit--locate-error-message)))))
(when magit-git-debug
(let ((magit-git-debug nil))
(with-current-buffer (magit-process-buffer t)
@@ -577,10 +562,12 @@ insert the run command and stderr into the process buffer."
exit log 'magit-section-secondary-heading)
exit)))))
(cond ((not magit-git-debug))
(errmsg (message "%s" errmsg))
(errmsg (message "magit--git-insert: %S" errmsg))
((zerop exit))
((message "Git returned with exit-code %s" exit))))
(or errmsg exit))
((message "magit--git-insert: %s %s"
"Git returned with exit-code" exit))))
(or (and return-error errmsg)
exit))
(ignore-errors (delete-file log))))
(magit-process-git (list t nil) args)))
@@ -591,16 +578,18 @@ insert the run command and stderr into the process buffer."
(match-str 1)))
(defun magit-git-string (&rest args)
"Execute Git with ARGS, returning the first line of its output.
If there is no output, return nil. If the output begins with a
newline, return an empty string."
"Execute Git with ARGS, returning the first line of its output (stdout).
If the exit code isn't zero or if there is no output, then return nil.
Neither of these results is considered an error; if that is what you
want, then use `magit-git-string-ng' instead."
(setq args (flatten-tree args))
(magit--with-refresh-cache (cons default-directory args)
(magit--with-temp-process-buffer
(apply #'magit-git-insert args)
(unless (bobp)
(goto-char (point-min))
(buffer-substring-no-properties (point) (line-end-position))))))
(and (zerop (apply #'magit-git-insert args))
(not (bobp))
(progn
(goto-char (point-min))
(buffer-substring-no-properties (point) (line-end-position)))))))
(defun magit-git-lines (&rest args)
"Execute Git with ARGS, returning its output as a list of lines.
@@ -708,29 +697,29 @@ format."
(status (magit-process-git t "version"))
(output (buffer-string)))
(cond
((not (zerop status))
(display-warning
'magit
(format "%S\n\nRunning \"%s --version\" failed with output:\n\n%s"
(if host
(format "Magit cannot find Git on host %S.\n
((not (zerop status))
(display-warning
'magit
(format "%S\n\nRunning \"%s --version\" failed with output:\n\n%s"
(if host
(format "Magit cannot find Git on host %S.\n
Check the value of `magit-remote-git-executable' using
`magit-debug-git-executable' and consult the info node
`(tramp)Remote programs'." host)
"Magit cannot find Git.\n
"Magit cannot find Git.\n
Check the values of `magit-git-executable' and `exec-path'
using `magit-debug-git-executable'.")
(magit-git-executable)
output)))
((save-match-data
(and (string-match magit--git-version-regexp output)
(let ((version (match-str 1 output)))
(push (cons host version)
magit--host-git-version-cache)
version))))
((error "Unexpected \"%s --version\" output: %S"
(magit-git-executable)
output)))))))))
(magit-git-executable)
output)))
((save-match-data
(and (string-match magit--git-version-regexp output)
(let ((version (match-str 1 output)))
(push (cons host version)
magit--host-git-version-cache)
version))))
((error "Unexpected \"%s --version\" output: %S"
(magit-git-executable)
output)))))))))
(defun magit-git-version-assert (&optional minimal who)
"Assert that the used Git version is greater than or equal to MINIMAL.
@@ -1019,9 +1008,7 @@ returning the truename."
(define-error 'magit-outside-git-repo "Not inside Git repository")
(define-error 'magit-corrupt-git-config "Corrupt Git configuration")
(define-error 'magit-git-executable-not-found
(concat "Git executable cannot be found "
"(see https://magit.vc/goto/e6a78ed2)"))
(define-error 'magit-git-executable-not-found "Git executable cannot be found")
(defun magit--assert-usable-git ()
(if (not (executable-find (magit-git-executable) t))
@@ -1121,8 +1108,8 @@ tracked file."
(file-relative-name file dir))))
(defun magit-file-ignored-p (file)
(magit-git-string-p "ls-files" "--others" "--ignored" "--exclude-standard"
"--" (magit-convert-filename-for-git file)))
(magit-git-string "ls-files" "--others" "--ignored" "--exclude-standard"
"--" (magit-convert-filename-for-git file)))
(defun magit-file-tracked-p (file)
(magit-git-success "ls-files" "--error-unmatch"
@@ -1145,6 +1132,16 @@ issue."
(and (not all) "--exclude-standard")
"--" files))
(defun magit--untracked-files (&optional directory all)
(magit-with-toplevel
(seq-keep (##and (eq (aref % 0) ??)
(substring % 3))
(magit-git-items "status" "-z" "--porcelain"
(if all
"--untracked-files=all"
"--untracked-files=normal")
"--" directory))))
(defun magit-list-untracked-files (&optional files)
"Return a list of untracked files.
@@ -1258,13 +1255,12 @@ or if no rename is detected."
(y (char-after (1+ pos)))
(file (buffer-substring (+ pos 3) (point))))
(forward-char)
(if (memq x '(?R ?C))
(progn
(setq pos (point))
(skip-chars-forward "[:print:]")
(push (list file (buffer-substring pos (point)) x y) status)
(forward-char))
(push (list file nil x y) status)))
(cond ((memq x '(?R ?C))
(setq pos (point))
(skip-chars-forward "[:print:]")
(push (list file (buffer-substring pos (point)) x y) status)
(forward-char))
((push (list file nil x y) status))))
(setq pos (point)))
status)))
@@ -1279,11 +1275,10 @@ or if no rename is detected."
"Failed to parse Cygwin mount: %S" mount)))
;; If --exec-path is not a native Windows path,
;; then we probably have a cygwin git.
(and (not (string-match-p
"\\`[a-zA-Z]:"
(car (magit--early-process-lines
magit-git-executable "--exec-path"))))
(magit--early-process-lines "mount")))
(and-let ((dirs (magit--early-process-lines
magit-git-executable "--exec-path")))
(and (not (string-match-p "\\`[a-zA-Z]:" (car dirs)))
(magit--early-process-lines "mount"))))
#'> :key (pcase-lambda (`(,cyg . ,_win)) (length cyg))))
"Alist of (CYGWIN . WIN32) directory names.
Sorted from longest to shortest CYGWIN name."
@@ -1344,6 +1339,36 @@ Sorted from longest to shortest CYGWIN name."
(and (derived-mode-p 'magit-log-mode)
(car magit-buffer-log-files))))
;;; Blobs
(defun magit-blob-p (obj)
(equal (magit-object-type obj) "blob"))
(defun magit-blob-oid (rev file)
(cond-let
((equal rev "{index}")
(cadr (car (magit--file-index-stages file))))
;; --object-only and --format were only added in Git v2.36.0.
([out (magit-git-string "ls-tree" "--full-tree" rev "--"
(magit-convert-filename-for-git file))]
(nth 2 (split-string out "[\s\t]")))))
(defun magit--file-index-stages (file)
(mapcar (##split-string % " ")
(magit-git-lines "ls-files" "--stage" "--"
(magit-convert-filename-for-git file))))
(defun magit--insert-blob-contents (obj file)
(let ((coding-system-for-read (or coding-system-for-read 'undecided)))
(if (magit-blob-p obj)
(magit-git-insert "cat-file" "blob" obj)
(magit-git-insert "cat-file" "-p"
(if (equal obj "{index}")
(concat ":" file)
(concat obj ":" file))))
(setq buffer-file-coding-system last-coding-system-used)
nil))
;;; Predicates
(defun magit-no-commit-p ()
@@ -1441,21 +1466,29 @@ string \"true\", otherwise return nil."
(equal (magit-git-str "rev-parse" args) "true"))
(defun magit-rev-verify (rev)
(magit-git-string-p "rev-parse" "--verify" rev))
(magit-git-string "rev-parse" "--verify" rev))
(defun magit-commit-p (rev)
"Return full hash for REV if it names an existing commit."
"Return non-nil if REV can be dereferences as a commit.
Otherwise return nil. Use `magit-commit-oid' if you actually need
the oid; eventually this function will return t instead of the oid,
as it currently does for backward compatibility."
;; TODO Return t instead of the oid.
(magit-rev-verify (magit--rev-dereference rev)))
(defalias 'magit-rev-verify-commit #'magit-commit-p)
(defalias 'magit-rev-hash #'magit-commit-p)
(defun magit-commit-oid (rev &optional noerror)
"Return commit oid for REV if it can be dereferences as a commit.
Otherwise signal an error, or return nil, if optional NOERROR is non-nil."
(cond ((magit-rev-verify (magit--rev-dereference rev)))
(noerror nil)
((error "%s cannot be dereferenced as a commit" rev))))
(defun magit--rev-dereference (rev)
"Return a rev that forces Git to interpret REV as a commit.
If REV is nil or has the form \":/TEXT\", return REV itself."
Do so by appending \"^{commit}\"; see \"--verify\" in git-rev-parse(1).
However, if REV is nil or has the form \":/TEXT\", return REV itself."
(cond ((not rev) nil)
((string-match-p "^:/" rev) rev)
((string-prefix-p ":/" rev) rev)
((concat rev "^{commit}"))))
(defun magit-rev-equal (a b)
@@ -1464,20 +1497,18 @@ If REV is nil or has the form \":/TEXT\", return REV itself."
(defun magit-rev-eq (a b)
"Return t if A and B refer to the same commit."
(let ((a (magit-commit-p a))
(b (magit-commit-p b)))
(and a b (equal a b))))
(and-let ((a (magit-commit-oid a t))
(b (magit-commit-oid b t)))
(equal a b)))
(defun magit-rev-ancestor-p (a b)
"Return non-nil if commit A is an ancestor of commit B."
(magit-git-success "merge-base" "--is-ancestor" a b))
(defun magit-rev-head-p (rev)
"Return t if REV can be dereferences as the `HEAD' commit."
(or (equal rev "HEAD")
(and rev
(not (string-search ".." rev))
(equal (magit-rev-parse rev)
(magit-rev-parse "HEAD")))))
(magit-rev-eq rev "HEAD")))
(defun magit-rev-author-p (rev)
"Return t if the user is the author of REV.
@@ -1584,9 +1615,9 @@ nil, then use \"heads/\"."
A symbolic-ref pointing to some ref, is `equal' to that ref,
as are two symbolic-refs pointing to the same ref. Refnames
may be abbreviated."
(let ((a (magit-ref-fullname a))
(b (magit-ref-fullname b)))
(and a b (equal a b))))
(and-let ((a (magit-ref-fullname a))
(b (magit-ref-fullname b)))
(equal a b)))
(defun magit-ref-eq (a b)
"Return t if the refnames A and B are `eq'.
@@ -1682,7 +1713,7 @@ to, or to some other symbolic-ref that points to the same ref."
(magit-current-blame-chunk))))
(oref chunk orig-rev))
(and magit-buffer-file-name
magit-buffer-refname)
magit-buffer-revision)
(and (derived-mode-p 'magit-stash-mode
'magit-merge-preview-mode
'magit-revision-mode)
@@ -1750,10 +1781,10 @@ The amount of time spent searching is limited by
(concat remote "/" newname))))
(pcase-dolist (`(,branch ,upstream) branches)
(cond
((equal upstream oldname)
(magit-set-upstream-branch branch new))
((equal upstream (concat remote "/" oldname))
(magit-set-upstream-branch branch (concat remote "/" newname))))))))
((equal upstream oldname)
(magit-set-upstream-branch branch new))
((equal upstream (concat remote "/" oldname))
(magit-set-upstream-branch branch (concat remote "/" newname))))))))
(defun magit--get-default-branch (&optional update)
(let ((remote (magit-primary-remote)))
@@ -1874,19 +1905,18 @@ according to the branch type."
(defun magit-get-push-branch (&optional branch verify)
(magit--with-refresh-cache
(list default-directory 'magit-get-push-branch branch verify)
(and-let* ((branch (or branch (setq branch (magit-get-current-branch))))
(remote (magit-get-push-remote branch))
(target (concat remote "/" branch)))
(and-let*
((branch (magit-ref-abbrev (or branch (magit-get-current-branch))))
(remote (magit-get-push-remote branch))
(target (concat remote "/" branch)))
(and (or (not verify)
(magit-rev-verify target))
(magit--propertize-face target 'magit-branch-remote)))))
(defun magit-get-@{push}-branch (&optional branch)
(let ((ref (magit-rev-parse "--symbolic-full-name"
(concat branch "@{push}"))))
(and ref
(string-prefix-p "refs/remotes/" ref)
(substring ref 13))))
(and-let* ((branch (magit-ref-abbrev (or branch (magit-get-current-branch))))
(target (magit-ref-fullname (concat branch "@{push}"))))
(magit-ref-abbrev target)))
(defun magit-get-remote (&optional branch)
(and (or branch (setq branch (magit-get-current-branch)))
@@ -2232,11 +2262,11 @@ specified using `core.worktree'."
(let* ((default-directory (car worktree))
(wt (and (not (magit-get-boolean "core.bare"))
(magit-get "core.worktree"))))
(if (and wt (file-exists-p (expand-file-name wt)))
(progn (setf (nth 0 worktree) (expand-file-name wt))
(setf (nth 2 worktree) (magit-rev-parse "HEAD"))
(setf (nth 3 worktree) (magit-get-current-branch)))
(setf (nth 3 worktree) t))))
(cond ((and wt (file-exists-p (expand-file-name wt)))
(setf (nth 0 worktree) (expand-file-name wt))
(setf (nth 2 worktree) (magit-rev-parse "HEAD"))
(setf (nth 3 worktree) (magit-get-current-branch)))
((setf (nth 3 worktree) t)))))
((string-equal line "detached")
(setf (nth 4 worktree) t))
((string-prefix-p line "locked")
@@ -2271,8 +2301,8 @@ specified using `core.worktree'."
'magit-branch-local
'magit-branch-remote)))
(defun magit-tag-p (rev)
(car (member rev (magit-list-tags))))
(defun magit-tag-p (obj)
(equal (magit-object-type obj) "tag"))
(defun magit-remote-p (string)
(car (member string (magit-list-remotes))))
@@ -2342,10 +2372,10 @@ If `first-parent' is set, traverse only first parents."
(defun magit-rev-abbrev (rev)
(magit-rev-parse (magit-abbrev-arg "short") rev))
(defun magit--abbrev-if-hash (rev)
(cond ((or (magit-ref-p rev) (member rev '("{index}" "{worktree}"))) rev)
((magit-rev-parse (magit-abbrev-arg "short") rev))
(rev)))
(defun magit--abbrev-if-oid (obj)
(cond ((or (magit-ref-p obj) (member obj '("{index}" "{worktree}"))) obj)
((magit-rev-parse (magit-abbrev-arg "short") obj))
(obj)))
(defun magit-commit-children (rev &optional args)
(seq-keep (lambda (line)
@@ -2469,18 +2499,18 @@ and this option only controls what face is used.")
(string-match "^[^/]*/" push)
(setq push (substring push 0 (match-end 0))))
(cond
((equal name current)
(setq head
(concat push
(magit--propertize-face
name 'magit-branch-current))))
((equal name target)
(setq upstream
(concat push
(magit--propertize-face
name '(magit-branch-upstream
magit-branch-local)))))
((push (concat push name) combined)))))
((equal name current)
(setq head
(concat push
(magit--propertize-face
name 'magit-branch-current))))
((equal name target)
(setq upstream
(concat push
(magit--propertize-face
name '(magit-branch-upstream
magit-branch-local)))))
((push (concat push name) combined)))))
(cond-let
((or upstream (not target)))
((member target remotes)
@@ -2568,8 +2598,8 @@ and this option only controls what face is used.")
(beg (or beg "HEAD"))
(end (or end "HEAD")))
(when abbrev
(setq beg (magit--abbrev-if-hash beg))
(setq end (magit--abbrev-if-hash end)))
(setq beg (magit--abbrev-if-oid beg))
(setq end (magit--abbrev-if-oid end)))
(pcase sep
(".." (cons beg end))
("..." (and$ (magit-git-string "merge-base" beg end)
@@ -2583,15 +2613,18 @@ and this option only controls what face is used.")
(list beg end sep)))))
(defun magit-hash-range (range)
"Return a string with the revisions in RANGE replaced with commit oids.
Either side of RANGE may be omitted, and RANGE may be just a revision.
If either revision cannot be dereferenced as a commit, signal an error."
(if (string-match magit-range-re range)
(magit-bind-match-strings (beg sep end) range
(and (or beg end)
(let ((beg-hash (and beg (magit-rev-hash beg)))
(end-hash (and end (magit-rev-hash end))))
(and (or (not beg) beg-hash)
(or (not end) end-hash)
(concat beg-hash sep end-hash)))))
(magit-rev-hash range)))
(let ((beg-oid (and beg (magit-commit-oid beg)))
(end-oid (and end (magit-commit-oid end))))
(and (or (not beg) beg-oid)
(or (not end) end-oid)
(concat beg-oid sep end-oid)))))
(magit-commit-oid range)))
(defvar magit-revision-faces
'(magit-hash
@@ -2645,7 +2678,7 @@ and this option only controls what face is used.")
(and (not (equal string "@"))
(or (and (>= (length string) 7)
(string-match-p "[a-z]" string)
(magit-commit-p string))
(magit-commit-oid string t))
(and (magit-ref-p string)
(member (get-text-property (point) 'face)
magit-revision-faces)))
@@ -2729,10 +2762,11 @@ and this option only controls what face is used.")
(lambda ()
(magit--minibuf-default-add-commit)
(setq-local crm-separator "\\.\\.\\.?"))
(magit-completing-read-multiple
(concat prompt ": ")
(magit-list-refnames)
nil 'any nil 'magit-revision-history default nil t)))
(let ((crm-prompt "%p"))
(magit-completing-read-multiple
(concat prompt ": ")
(magit-list-refnames)
nil 'any nil 'magit-revision-history default nil t))))
(defun magit-read-remote-branch
(prompt &optional remote default local-branch require-match)
@@ -2812,6 +2846,23 @@ and this option only controls what face is used.")
(magit-completing-read prompt (delete exclude (magit-list-refnames))
nil 'any nil 'magit-revision-history default))))
(defun magit-read-other-branches-or-commits
(prompt &optional exclude secondary-default)
(let* ((current (magit-get-current-branch))
(atpoint (magit-branch-or-commit-at-point))
(exclude (or exclude current))
(default (or (and (not (equal atpoint exclude))
(not (and (not current)
(magit-rev-equal atpoint "HEAD")))
atpoint)
(and (not (equal current exclude)) current)
secondary-default
(magit-get-previous-branch))))
(minibuffer-with-setup-hook #'magit--minibuf-default-add-commit
(magit-completing-read-multiple
prompt (delete exclude (magit-list-refnames))
nil 'any nil 'magit-revision-history default))))
(defun magit-read-other-local-branch
(prompt &optional exclude secondary-default)
(let* ((current (magit-get-current-branch))
@@ -2993,6 +3044,21 @@ out. Only existing branches can be selected."
(server-send-string client msg))))
;;; _
(define-obsolete-function-alias 'magit-git-string-p
#'magit-git-string "Magit 4.6.0")
(define-obsolete-function-alias 'magit-rev-verify-commit
#'magit-commit-p "Magit 4.6.0")
(define-obsolete-function-alias 'magit-rev-hash
#'magit-commit-p "Magit 4.6.0"
"Return oid for REV if it names an existing commit, nil otherwise.
Instead use `magit-commit-p' or `magit-commit-oid'.")
(define-obsolete-function-alias 'magit--abbrev-if-hash
#'magit--abbrev-if-oid "Magit 4.6.0")
(provide 'magit-git)
;; Local Variables:
;; read-symbol-shorthands: (
@@ -3000,6 +3066,7 @@ out. Only existing branches can be selected."
;; ("and>" . "cond-let--and>")
;; ("and-let" . "cond-let--and-let")
;; ("if-let" . "cond-let--if-let")
;; ("when$" . "cond-let--when$")
;; ("when-let" . "cond-let--when-let")
;; ("while-let" . "cond-let--while-let")
;; ("match-string" . "match-string")

View File

@@ -1,6 +1,6 @@
;;; magit-gitignore.el --- Intentionally untracked files -*- lexical-binding:t -*-
;; Copyright (C) 2008-2025 The Magit Project Contributors
;; Copyright (C) 2008-2026 The Magit Project Contributors
;; Author: Jonas Bernoulli <emacs.magit@jonas.bernoulli.dev>
;; Maintainer: Jonas Bernoulli <emacs.magit@jonas.bernoulli.dev>
@@ -35,16 +35,10 @@
"Instruct Git to ignore a file or pattern."
:man-page "gitignore"
["Gitignore"
("t" "shared at toplevel (.gitignore)"
magit-gitignore-in-topdir)
("s" "shared in subdirectory (path/to/.gitignore)"
magit-gitignore-in-subdir)
("p" "privately (.git/info/exclude)"
magit-gitignore-in-gitdir)
("g" magit-gitignore-on-system
:if (##magit-get "core.excludesfile")
:description (##format "privately for all repositories (%s)"
(magit-get "core.excludesfile")))]
("t" magit-gitignore-in-topdir)
("s" magit-gitignore-in-subdir)
("p" magit-gitignore-in-gitdir)
("g" magit-gitignore-on-system)]
["Skip worktree"
(7 "w" "do skip worktree" magit-skip-worktree)
(7 "W" "do not skip worktree" magit-no-skip-worktree)]
@@ -54,51 +48,56 @@
;;; Gitignore Commands
;;;###autoload
(defun magit-gitignore-in-topdir (rule)
;;;###autoload(autoload 'magit-gitignore-in-topdir "magit-gitignore" nil t)
(transient-define-suffix magit-gitignore-in-topdir (rule)
"Add the Git ignore RULE to the top-level \".gitignore\" file.
Since this file is tracked, it is shared with other clones of the
repository. Also stage the file."
:description "shared at toplevel (.gitignore)"
(interactive (list (magit-gitignore-read-pattern)))
(magit-with-toplevel
(magit--gitignore rule ".gitignore")
(magit-run-git "add" ".gitignore")))
(magit--gitignore rule (expand-file-name ".gitignore" (magit-toplevel)) t))
;;;###autoload
(defun magit-gitignore-in-subdir (rule directory)
;;;###autoload(autoload 'magit-gitignore-in-subdir "magit-gitignore" nil t)
(transient-define-suffix magit-gitignore-in-subdir (rule directory)
"Add the Git ignore RULE to a \".gitignore\" file in DIRECTORY.
Prompt the user for a directory and add the rule to the
\".gitignore\" file in that directory. Since such files are
tracked, they are shared with other clones of the repository.
Also stage the file."
(interactive (list (magit-gitignore-read-pattern)
(read-directory-name "Limit rule to files in: ")))
(magit-with-toplevel
(let ((file (expand-file-name ".gitignore" directory)))
(magit--gitignore rule file)
(magit-run-git "add" (magit-convert-filename-for-git file)))))
:description "shared in subdirectory (path/to/.gitignore)"
(interactive (let ((dir (expand-file-name
(read-directory-name
"Limit rule to files in: "
(and$ (magit-current-file)
(file-name-directory
(expand-file-name $ (magit-toplevel))))))))
(list (magit-gitignore-read-pattern dir) dir)))
(magit--gitignore rule (expand-file-name ".gitignore" directory) t))
;;;###autoload
(defun magit-gitignore-in-gitdir (rule)
;;;###autoload(autoload 'magit-gitignore-in-gitdir "magit-gitignore" nil t)
(transient-define-suffix magit-gitignore-in-gitdir (rule)
"Add the Git ignore RULE to \"$GIT_DIR/info/exclude\".
Rules in that file only affects this clone of the repository."
:description "privately (.git/info/exclude)"
(interactive (list (magit-gitignore-read-pattern)))
(magit--gitignore rule (expand-file-name "info/exclude" (magit-gitdir)))
(magit-refresh))
(magit--gitignore rule (expand-file-name "info/exclude" (magit-gitdir))))
;;;###autoload
(defun magit-gitignore-on-system (rule)
;;;###autoload(autoload 'magit-gitignore-on-system "magit-gitignore" nil t)
(transient-define-suffix magit-gitignore-on-system (rule)
"Add the Git ignore RULE to the file specified by `core.excludesFile'.
Rules that are defined in that file affect all local repositories."
:inapt-if-not (##magit-get "core.excludesfile")
:description (##format "privately for all repositories (%s)"
(or (magit-get "core.excludesfile")
"core.excludesfile is not set"))
(interactive (list (magit-gitignore-read-pattern)))
(magit--gitignore rule
(or (magit-get "core.excludesFile")
(error "Variable `core.excludesFile' isn't set")))
(magit-refresh))
(if-let ((file (magit-get "core.excludesFile")))
(magit--gitignore rule file)
(error "Variable `core.excludesFile' isn't set")))
(defun magit--gitignore (rule file)
(when-let ((directory (file-name-directory file)))
(make-directory directory t))
(defun magit--gitignore (rule file &optional stage)
(when$ (file-name-directory file)
(make-directory $ t))
(with-temp-buffer
(when (file-exists-p file)
(insert-file-contents file))
@@ -107,31 +106,21 @@ Rules that are defined in that file affect all local repositories."
(insert "\n"))
(insert (replace-regexp-in-string "\\(\\\\*\\)" "\\1\\1" rule))
(insert "\n")
(write-region nil nil file)))
(write-region nil nil file))
(if stage
(magit-with-toplevel
(magit-run-git "add" (magit-convert-filename-for-git file)))
(magit-refresh)))
(defun magit-gitignore-read-pattern ()
(let* ((default (magit-current-file))
(base (car magit-buffer-diff-files))
(base (and base (file-directory-p base) base))
(choices
(delete-dups
(mapcan
(lambda (file)
(cons (concat "/" file)
(and$ (file-name-extension file)
(list (concat "/" (file-name-directory file) "*." $)
(concat "*." $)))))
(sort (nconc
(magit-untracked-files nil base)
;; The untracked section of the status buffer lists
;; directories containing only untracked files.
;; Add those as candidates.
(seq-filter #'directory-name-p
(magit-list-files
"--other" "--exclude-standard" "--directory"
"--no-empty-directory" "--" base)))
#'string-lessp)))))
(defun magit-gitignore-read-pattern (&optional directory)
(let ((choices (magit--gitignore-patterns directory))
(default (magit-current-file)))
(when default
(when directory
(setq default
(substring default
(length
(file-relative-name directory (magit-toplevel))))))
(setq default (concat "/" default))
(unless (member default choices)
(setq default (concat "*." (file-name-extension default)))
@@ -140,18 +129,40 @@ Rules that are defined in that file affect all local repositories."
(magit-completing-read "File or pattern to ignore"
choices nil 'any nil nil default)))
(defun magit--gitignore-patterns (&optional directory)
(let* ((topdir (magit-toplevel))
(default-directory (or directory topdir))
(files (magit--untracked-files directory t))
;; Include directories that contain only untracked files.
(dirs (seq-filter (##equal (substring % -1) "/")
(magit--untracked-files directory)))
(globs nil)
(dirglobs nil))
(when directory
(let ((beg (length (file-relative-name directory topdir))))
(setq files (mapcar (##substring % beg) files))
(setq dirs (mapcar (##substring % beg) dirs))))
(dolist (file files)
(when-let ((ext (file-name-extension file)))
(cl-pushnew (concat "*." ext) globs :test #'equal)
(when-let ((dir (file-name-directory file)))
(cl-pushnew (concat dir "*." ext) dirglobs :test #'equal))))
(sort (nconc globs
(mapcar (##concat "/" %) (nconc files dirs dirglobs)))
#'string<)))
;;; Skip Worktree Commands
;;;###autoload
(defun magit-skip-worktree (file)
"Call \"git update-index --skip-worktree -- FILE\"."
(interactive
(list (magit-read-file-choice "Skip worktree for"
(magit-with-toplevel
(cl-set-difference
(magit-list-files)
(magit-skip-worktree-files)
:test #'equal)))))
(list (magit-read-file-choice "Skip worktree for"
(magit-with-toplevel
(cl-set-difference
(magit-list-files)
(magit-skip-worktree-files)
:test #'equal)))))
(magit-with-toplevel
(magit-run-git "update-index" "--skip-worktree" "--" file)))
@@ -159,9 +170,9 @@ Rules that are defined in that file affect all local repositories."
(defun magit-no-skip-worktree (file)
"Call \"git update-index --no-skip-worktree -- FILE\"."
(interactive
(list (magit-read-file-choice "Do not skip worktree for"
(magit-with-toplevel
(magit-skip-worktree-files)))))
(list (magit-read-file-choice "Do not skip worktree for"
(magit-with-toplevel
(magit-skip-worktree-files)))))
(magit-with-toplevel
(magit-run-git "update-index" "--no-skip-worktree" "--" file)))
@@ -171,12 +182,12 @@ Rules that are defined in that file affect all local repositories."
(defun magit-assume-unchanged (file)
"Call \"git update-index --assume-unchanged -- FILE\"."
(interactive
(list (magit-read-file-choice "Assume file to be unchanged"
(magit-with-toplevel
(cl-set-difference
(magit-list-files)
(magit-assume-unchanged-files)
:test #'equal)))))
(list (magit-read-file-choice "Assume file to be unchanged"
(magit-with-toplevel
(cl-set-difference
(magit-list-files)
(magit-assume-unchanged-files)
:test #'equal)))))
(magit-with-toplevel
(magit-run-git "update-index" "--assume-unchanged" "--" file)))
@@ -184,9 +195,9 @@ Rules that are defined in that file affect all local repositories."
(defun magit-no-assume-unchanged (file)
"Call \"git update-index --no-assume-unchanged -- FILE\"."
(interactive
(list (magit-read-file-choice "Do not assume file to be unchanged"
(magit-with-toplevel
(magit-assume-unchanged-files)))))
(list (magit-read-file-choice "Do not assume file to be unchanged"
(magit-with-toplevel
(magit-assume-unchanged-files)))))
(magit-with-toplevel
(magit-run-git "update-index" "--no-assume-unchanged" "--" file)))
@@ -198,6 +209,7 @@ Rules that are defined in that file affect all local repositories."
;; ("and>" . "cond-let--and>")
;; ("and-let" . "cond-let--and-let")
;; ("if-let" . "cond-let--if-let")
;; ("when$" . "cond-let--when$")
;; ("when-let" . "cond-let--when-let")
;; ("while-let" . "cond-let--while-let")
;; ("match-string" . "match-string")

View File

@@ -1,6 +1,6 @@
;;; magit-log.el --- Inspect Git history -*- lexical-binding:t; coding:utf-8 -*-
;; Copyright (C) 2008-2025 The Magit Project Contributors
;; Copyright (C) 2008-2026 The Magit Project Contributors
;; Author: Jonas Bernoulli <emacs.magit@jonas.bernoulli.dev>
;; Maintainer: Jonas Bernoulli <emacs.magit@jonas.bernoulli.dev>
@@ -374,6 +374,12 @@ commits before and half after."
:group 'magit-log
:type 'integer)
;;; Variables
(defvar-local magit-buffer-log-revisions nil)
(defvar-local magit-buffer-log-args nil)
(defvar-local magit-buffer-log-files nil)
;;; Arguments
;;;; Prefix Classes
@@ -569,19 +575,19 @@ commits before and half after."
("b" "buffer lock" magit-toggle-buffer-lock)]]
(interactive)
(cond
((not (eq transient-current-command 'magit-log-refresh))
(pcase major-mode
('magit-reflog-mode
(user-error "Cannot change log arguments in reflog buffers"))
('magit-cherry-mode
(user-error "Cannot change log arguments in cherry buffers")))
(transient-setup 'magit-log-refresh))
(t
(pcase-let ((`(,args ,files) (magit-log-arguments)))
(setq magit-buffer-log-args args)
(unless (derived-mode-p 'magit-log-select-mode)
(setq magit-buffer-log-files files)))
(magit-refresh))))
((not (eq transient-current-command 'magit-log-refresh))
(pcase major-mode
('magit-reflog-mode
(user-error "Cannot change log arguments in reflog buffers"))
('magit-cherry-mode
(user-error "Cannot change log arguments in cherry buffers")))
(transient-setup 'magit-log-refresh))
(t
(pcase-let ((`(,args ,files) (magit-log-arguments)))
(setq magit-buffer-log-args args)
(unless (derived-mode-p 'magit-log-select-mode)
(setq magit-buffer-log-files files)))
(magit-refresh))))
;;;; Infix Commands
@@ -696,26 +702,26 @@ When the upstream is a local branch, then also show its own
upstream. When `HEAD' is detached, then show log for that, the
previously checked out branch and its upstream and push-target."
(interactive
(cons (let ((current (magit-get-current-branch))
head rebase target upstream upup)
(unless current
(setq rebase (magit-rebase--get-state-lines "head-name"))
(cond (rebase
(setq rebase (magit-ref-abbrev rebase))
(setq current rebase)
(setq head "HEAD"))
((setq current (magit-get-previous-branch)))))
(cond (current
(setq current
(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)))))
((setq head "HEAD")))
(delq nil (list current head target upstream upup)))
(magit-log-arguments)))
(cons (let ((current (magit-get-current-branch))
head rebase target upstream upup)
(unless current
(setq rebase (magit-rebase--get-state-lines "head-name"))
(cond (rebase
(setq rebase (magit-ref-abbrev rebase))
(setq current rebase)
(setq head "HEAD"))
((setq current (magit-get-previous-branch)))))
(cond (current
(setq current
(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)))))
((setq head "HEAD")))
(delq nil (list current head target upstream upup)))
(magit-log-arguments)))
(magit-log-setup-buffer revs args files))
;;;###autoload
@@ -779,7 +785,7 @@ restrict the log to the lines that the region touches."
(require 'magit)
(if-let ((file (magit-file-relative-name)))
(magit-log-setup-buffer
(list (or magit-buffer-refname
(list (or magit-buffer-revision
(magit-get-current-branch)
"HEAD"))
(let ((args (car (magit-log-arguments))))
@@ -802,7 +808,7 @@ restrict the log to the lines that the region touches."
(user-error "Buffer isn't visiting a file"))
(or (funcall magit-log-trace-definition-function)
(user-error "No function at point found"))
(or magit-buffer-refname
(or magit-buffer-revision
(magit-get-current-branch)
"HEAD")))
(require 'magit)
@@ -844,10 +850,10 @@ directly on BRANCH, then show approximately
This command requires git-when-merged, which is available from
https://github.com/mhagger/git-when-merged."
(interactive
(append (let ((commit (magit-read-branch-or-commit "Log merge of commit")))
(list commit
(magit-read-other-branch "Merged into" commit)))
(magit-log-arguments)))
(append (let ((commit (magit-read-branch-or-commit "Log merge of commit")))
(list commit
(magit-read-other-branch "Merged into" commit)))
(magit-log-arguments)))
(unless (magit-git-executable-find "git-when-merged")
(user-error "This command requires git-when-merged (%s)"
"https://github.com/mhagger/git-when-merged"))
@@ -872,7 +878,7 @@ https://github.com/mhagger/git-when-merged."
(to (if (<= to 0)
branch
(format "%s~%s" branch to))))
(unless (magit-rev-verify-commit from)
(unless (magit-commit-p from)
(setq from (magit-git-string "rev-list" "--max-parents=0"
commit)))
(magit-log-setup-buffer (list (concat from ".." to))
@@ -970,13 +976,13 @@ 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-completing-read
"In log, jump to"
(magit-list-refnames nil t)
nil 'any nil 'magit-revision-history
(or (and$ (magit-commit-at-point)
(magit-rev-fixup-target $))
(magit-get-current-branch)))))
(list (magit-completing-read
"In log, jump to"
(magit-list-refnames nil t)
nil 'any nil 'magit-revision-history
(or (and$ (magit-commit-at-point)
(magit-rev-fixup-target $))
(magit-get-current-branch)))))
(with-current-buffer
(cond ((derived-mode-p 'magit-log-mode)
(current-buffer))
@@ -1020,16 +1026,16 @@ of the current repository first; creating it if necessary."
(defun magit-shortlog-since (commit args)
"Show a history summary for commits since REV."
(interactive
(list (magit-read-branch-or-commit "Shortlog since" (magit-get-current-tag))
(transient-args 'magit-shortlog)))
(list (magit-read-branch-or-commit "Shortlog since" (magit-get-current-tag))
(transient-args 'magit-shortlog)))
(magit-git-shortlog (concat commit "..") args))
;;;###autoload
(defun magit-shortlog-range (rev-or-range args)
"Show a history summary for commit or range REV-OR-RANGE."
(interactive
(list (magit-read-range-or-commit "Shortlog for revision or range")
(transient-args 'magit-shortlog)))
(list (magit-read-range-or-commit "Shortlog for revision or range")
(transient-args 'magit-shortlog)))
(magit-git-shortlog rev-or-range args))
;;;; Movement Commands
@@ -1131,7 +1137,7 @@ Type \\[magit-reset] to reset `HEAD' to the commit at point.
(require 'magit)
(with-current-buffer
(magit-setup-buffer #'magit-log-mode locked
(magit-buffer-revisions revs)
(magit-buffer-log-revisions revs)
(magit-buffer-log-args args)
(magit-buffer-log-files files))
(when (if focus
@@ -1141,7 +1147,7 @@ Type \\[magit-reset] to reset `HEAD' to the commit at point.
(current-buffer)))
(defun magit-log-refresh-buffer ()
(let ((revs magit-buffer-revisions)
(let ((revs magit-buffer-log-revisions)
(args magit-buffer-log-args)
(files magit-buffer-log-files)
(limit (magit-log-get-commit-limit)))
@@ -1181,24 +1187,24 @@ Type \\[magit-reset] to reset `HEAD' to the commit at point.
(defvar-local magit-log--color-graph nil)
(defun magit-log--maybe-drop-color-graph (args limit)
(if (member "--color" args)
(if (cond ((not (member "--graph" args)))
((not magit-log-color-graph-limit) nil)
((not limit)
(message "Dropping --color because -n isn't set (see %s)"
'magit-log-color-graph-limit))
((> limit magit-log-color-graph-limit)
(message "Dropping --color because -n is larger than %s"
'magit-log-color-graph-limit)))
(progn (setq args (remove "--color" args))
(setq magit-log--color-graph nil))
(setq magit-log--color-graph t))
(setq magit-log--color-graph nil))
(cond ((not (member "--color" args))
(setq magit-log--color-graph nil))
((cond ((not (member "--graph" args)) t)
((not magit-log-color-graph-limit) nil)
((not limit)
(message "Dropping --color because -n isn't set (see %s)"
'magit-log-color-graph-limit))
((> limit magit-log-color-graph-limit)
(message "Dropping --color because -n is larger than %s"
'magit-log-color-graph-limit)))
(setq args (remove "--color" args))
(setq magit-log--color-graph nil))
((setq magit-log--color-graph t)))
args)
(cl-defmethod magit-buffer-value (&context (major-mode magit-log-mode))
(append magit-buffer-revisions
(if (and magit-buffer-revisions magit-buffer-log-files)
(append magit-buffer-log-revisions
(if (and magit-buffer-log-revisions magit-buffer-log-files)
(cons "--" magit-buffer-log-files)
magit-buffer-log-files)))
@@ -1257,17 +1263,17 @@ Do not add this to a hook variable."
(setq args (remove "--show-signature" args))
(let ((limit (magit-log-get-commit-limit args)))
(cond
((not limit)
(message
"Dropping --show-signature because -n isn't set (see %s)"
'magit-log-show-signatures-limit)
"")
((> limit magit-log-show-signatures-limit)
(message
"Dropping --show-signature because -n is larger than %s"
'magit-log-show-signatures-limit)
"")
("%G?"))))
((not limit)
(message
"Dropping --show-signature because -n isn't set (see %s)"
'magit-log-show-signatures-limit)
"")
((> limit magit-log-show-signatures-limit)
(message
"Dropping --show-signature because -n is larger than %s"
'magit-log-show-signatures-limit)
"")
("%G?"))))
(if magit-log-margin-show-committer-date "%ct" "%at")
(if magit-log-trailer-labels
(format "%%(trailers:%s%s)"
@@ -1779,20 +1785,20 @@ Type \\[magit-log-select-quit] to abort without selecting a commit."
(defun magit-log-select-setup-buffer (revs args)
(magit-setup-buffer #'magit-log-select-mode nil
(magit-buffer-revisions revs)
(magit-buffer-log-revisions revs)
(magit-buffer-log-args args)))
(defun magit-log-select-refresh-buffer ()
(setq magit-section-inhibit-markers t)
(setq magit-section-insert-in-reverse t)
(magit-insert-section (logbuf)
(magit--insert-log t magit-buffer-revisions
(magit--insert-log t magit-buffer-log-revisions
(magit-log--maybe-drop-color-graph
magit-buffer-log-args
(magit-log-get-commit-limit)))))
(cl-defmethod magit-buffer-value (&context (major-mode magit-log-select-mode))
magit-buffer-revisions)
magit-buffer-log-revisions)
(defvar-local magit-log-select-pick-function nil)
(defvar-local magit-log-select-quit-function nil)
@@ -1881,11 +1887,14 @@ Type \\[magit-cherry-pick] to apply the commit at point.
(magit-hack-dir-local-variables)
(setq magit--imenu-group-types 'cherries))
(defvar-local magit-buffer-cherry-upstream nil)
(defvar-local magit-buffer-cherry-range nil)
(defun magit-cherry-setup-buffer (head upstream)
(magit-setup-buffer #'magit-cherry-mode nil
(magit-buffer-refname head)
(magit-buffer-upstream upstream)
(magit-buffer-range (concat upstream ".." head))))
(magit-buffer-cherry-upstream upstream)
(magit-buffer-cherry-range (concat upstream ".." head))))
(defun magit-cherry-refresh-buffer ()
(setq magit-section-insert-in-reverse t)
@@ -1893,15 +1902,15 @@ Type \\[magit-cherry-pick] to apply the commit at point.
(magit-run-section-hook 'magit-cherry-sections-hook)))
(cl-defmethod magit-buffer-value (&context (major-mode magit-cherry-mode))
magit-buffer-range)
magit-buffer-cherry-range)
;;;###autoload
(defun magit-cherry (head upstream)
"Show commits in a branch that are not merged in the upstream branch."
(interactive
(let ((head (magit-read-branch "Cherry head")))
(list head (magit-read-other-branch "Cherry upstream" head
(magit-get-upstream-branch head)))))
(let ((head (magit-read-branch "Cherry head")))
(list head (magit-read-other-branch "Cherry upstream" head
(magit-get-upstream-branch head)))))
(require 'magit)
(magit-cherry-setup-buffer head upstream))
@@ -1909,10 +1918,11 @@ Type \\[magit-cherry-pick] to apply the commit at point.
"Insert headers appropriate for `magit-cherry-mode' buffers."
(let ((branch (propertize magit-buffer-refname
'font-lock-face 'magit-branch-local))
(upstream (propertize magit-buffer-upstream 'font-lock-face
(if (magit-local-branch-p magit-buffer-upstream)
'magit-branch-local
'magit-branch-remote))))
(upstream (propertize
magit-buffer-cherry-upstream 'font-lock-face
(if (magit-local-branch-p magit-buffer-cherry-upstream)
'magit-branch-local
'magit-branch-remote))))
(magit-insert-head-branch-header branch)
(magit-insert-upstream-branch-header branch upstream "Upstream: ")
(insert ?\n)))
@@ -1923,7 +1933,7 @@ Type \\[magit-cherry-pick] to apply the commit at point.
(magit-insert-heading t "Cherry commits")
(magit-git-wash (apply-partially #'magit-log-wash-log 'cherry)
"cherry" "-v" "--abbrev"
magit-buffer-upstream
magit-buffer-cherry-upstream
magit-buffer-refname)))
;;; Log Sections
@@ -2114,6 +2124,7 @@ all others with \"-\"."
;; ("and>" . "cond-let--and>")
;; ("and-let" . "cond-let--and-let")
;; ("if-let" . "cond-let--if-let")
;; ("when$" . "cond-let--when$")
;; ("when-let" . "cond-let--when-let")
;; ("while-let" . "cond-let--while-let")
;; ("match-string" . "match-string")

View File

@@ -1,6 +1,6 @@
;;; magit-margin.el --- Margins in Magit buffers -*- lexical-binding:t -*-
;; Copyright (C) 2008-2025 The Magit Project Contributors
;; Copyright (C) 2008-2026 The Magit Project Contributors
;; Author: Jonas Bernoulli <emacs.magit@jonas.bernoulli.dev>
;; Maintainer: Jonas Bernoulli <emacs.magit@jonas.bernoulli.dev>
@@ -263,6 +263,7 @@ English.")
;; ("and>" . "cond-let--and>")
;; ("and-let" . "cond-let--and-let")
;; ("if-let" . "cond-let--if-let")
;; ("when$" . "cond-let--when$")
;; ("when-let" . "cond-let--when-let")
;; ("while-let" . "cond-let--while-let")
;; ("match-string" . "match-string")

View File

@@ -1,6 +1,6 @@
;;; magit-merge.el --- Merge functionality -*- lexical-binding:t -*-
;; Copyright (C) 2008-2025 The Magit Project Contributors
;; Copyright (C) 2008-2026 The Magit Project Contributors
;; Author: Jonas Bernoulli <emacs.magit@jonas.bernoulli.dev>
;; Maintainer: Jonas Bernoulli <emacs.magit@jonas.bernoulli.dev>
@@ -95,8 +95,10 @@ the user inspect the result. With a prefix argument pretend the
merge failed to give the user the opportunity to inspect the
merge.
To create an octopus-merge, separate branches with commas.
\(git merge --no-edit|--no-commit [ARGS] REV)"
(interactive (list (magit-read-other-branch-or-commit "Merge")
(interactive (list (magit-read-other-branches-or-commits "Merge")
(magit-merge-arguments)
current-prefix-arg))
(magit-merge-assert)
@@ -105,24 +107,30 @@ merge.
;;;###autoload
(defun magit-merge-editmsg (rev &optional args)
"Merge commit REV into the current branch; and edit message.
Perform the merge and prepare a commit message but let the user
edit it.
\n(git merge --edit --no-ff [ARGS] REV)"
(interactive (list (magit-read-other-branch-or-commit "Merge")
To create an octopus-merge, separate branches with commas.
\(git merge --edit --no-ff [ARGS] REV)"
(interactive (list (magit-read-other-branches-or-commits "Merge")
(magit-merge-arguments)))
(magit-merge-assert)
(cl-pushnew "--no-ff" args :test #'equal)
(apply #'magit-run-git-with-editor "merge" "--edit"
(append (delete "--ff-only" args)
(list rev))))
(magit-run-git-with-editor "merge" "--edit" (delete "--ff-only" args) rev))
;;;###autoload
(defun magit-merge-nocommit (rev &optional args)
"Merge commit REV into the current branch; pretending it failed.
Pretend the merge failed to give the user the opportunity to
inspect the merge and change the commit message.
\n(git merge --no-commit --no-ff [ARGS] REV)"
(interactive (list (magit-read-other-branch-or-commit "Merge")
To create an octopus-merge, separate branches with commas.
\(git merge --no-commit --no-ff [ARGS] REV)"
(interactive (list (magit-read-other-branches-or-commits "Merge")
(magit-merge-arguments)))
(magit-merge-assert)
(cl-pushnew "--no-ff" args :test #'equal)
@@ -139,12 +147,12 @@ obsolete version of the commits that are being merged. Finally
if `forge-branch-pullreq' was used to create the merged branch,
then also remove the respective remote branch."
(interactive
(list (let ((branch (magit-get-current-branch)))
(magit-read-other-local-branch
(format "Merge `%s' into" (or branch (magit-rev-parse "HEAD")))
nil
(and branch (magit-get-local-upstream-branch branch))))
(magit-merge-arguments)))
(list (let ((branch (magit-get-current-branch)))
(magit-read-other-local-branch
(format "Merge `%s' into" (or branch (magit-rev-parse "HEAD")))
nil
(and branch (magit-get-local-upstream-branch branch))))
(magit-merge-arguments)))
(let ((current (magit-get-current-branch))
(head (magit-rev-parse "HEAD")))
(when (zerop (magit-call-git "checkout" branch))
@@ -240,15 +248,15 @@ then also remove the respective remote branch."
(defun magit-checkout-stage (file arg)
"During a conflict checkout and stage side, or restore conflict."
(interactive
(let ((file (magit-completing-read "Checkout file"
(magit-tracked-files) nil 'any nil
'magit-read-file-hist
(magit-current-file))))
(cond ((member file (magit-unmerged-files))
(list file (magit-checkout-read-stage file)))
((yes-or-no-p (format "Restore conflicts in %s? " file))
(list file "--merge"))
((user-error "Quit")))))
(let ((file (magit-completing-read "Checkout file"
(magit-tracked-files) nil 'any nil
'magit-read-file-hist
(magit-current-file))))
(cond ((member file (magit-unmerged-files))
(list file (magit-checkout-read-stage file)))
((yes-or-no-p (format "Restore conflicts in %s? " file))
(list file "--merge"))
((user-error "Quit")))))
(pcase (cons arg (cddr (car (magit-file-status file))))
((or `("--ours" ?D ,_)
'("--ours" ?U ?A)
@@ -317,6 +325,7 @@ If no merge is in progress, do nothing."
;; ("and>" . "cond-let--and>")
;; ("and-let" . "cond-let--and-let")
;; ("if-let" . "cond-let--if-let")
;; ("when$" . "cond-let--when$")
;; ("when-let" . "cond-let--when-let")
;; ("while-let" . "cond-let--while-let")
;; ("match-string" . "match-string")

View File

@@ -1,6 +1,6 @@
;;; magit-mode.el --- Create and refresh Magit buffers -*- lexical-binding:t -*-
;; Copyright (C) 2008-2025 The Magit Project Contributors
;; Copyright (C) 2008-2026 The Magit Project Contributors
;; Author: Jonas Bernoulli <emacs.magit@jonas.bernoulli.dev>
;; Maintainer: Jonas Bernoulli <emacs.magit@jonas.bernoulli.dev>
@@ -51,6 +51,8 @@
(declare-function magit-wip-get-ref "magit-wip" ())
(declare-function magit-wip-commit-worktree "magit-wip" (ref files msg))
(declare-function magit--blob-cache-zap "magit-files" ())
;;; Options
(defcustom magit-mode-hook nil
@@ -578,35 +580,41 @@ Magit is documented in info node `(magit)'."
;;; Local Variables
(defvar-local magit-buffer-arguments nil)
(defvar-local magit-buffer-diff-type nil)
(defvar-local magit-buffer-diff-args nil)
(defvar-local magit-buffer-diff-files nil)
(defvar-local magit-buffer-diff-files-suspended nil)
(defvar-local magit-buffer-file-name nil)
(defvar-local magit-buffer-files nil)
(defvar-local magit-buffer-log-args nil)
(defvar-local magit-buffer-log-files nil)
(defvar-local magit-buffer-range nil)
(defvar-local magit-buffer-range-hashed nil)
(defvar-local magit-buffer-refname nil)
(defvaralias 'magit-buffer-refname 'magit-buffer-revision)
(defvar-local magit-buffer-revision nil)
(defvar-local magit-buffer-revision-hash nil)
(defvar-local magit-buffer-revisions nil)
(defvar-local magit-buffer-typearg nil)
(defvar-local magit-buffer-upstream nil)
(defvar-local magit-buffer-revision-oid nil)
(defvar-local magit-buffer-blob-oid nil)
(defvar-local magit-buffer-file-name nil)
;; These variables are also used in file-visiting buffers.
;; Because the user may change the major-mode, they have
;; to be permanent buffer-local.
(put 'magit-buffer-file-name 'permanent-local t)
(put 'magit-buffer-refname 'permanent-local t)
;; Preserve when major-mode is changed in file-visiting buffers.
(put 'magit-buffer-revision 'permanent-local t)
(put 'magit-buffer-revision-hash 'permanent-local t)
(put 'magit-buffer-revision-oid 'permanent-local t)
(put 'magit-buffer-blob-oid 'permanent-local t)
(put 'magit-buffer-file-name 'permanent-local t)
;; `magit-status' re-enables mode function but its refresher
;; function does not reinstate this.
(put 'magit-buffer-diff-files-suspended 'permanent-local t)
(eval-and-compile
(defvar magit-define-aliases-for:magit-buffer-* t)
(when magit-define-aliases-for:magit-buffer-*
;; Unfortunately defvar-local can only be used at top-level,
;; so instead we have to use make-variable-buffer-local below.
(defvar magit-buffer-arguments nil)
(make-obsolete-variable 'magit-buffer-arguments
"use a mode- or package-specific `magit-buffer-{*}-args' instead"
"magit 4.6.0")
(defvar magit-buffer-upstream nil)
(make-obsolete-variable 'magit-buffer-upstream
"use a mode- or package-specific `magit-buffer-{*}-upstream' instead"
"magit 4.6.0")
(define-obsolete-variable-alias 'magit-buffer-range-hashed
'magit-buffer-diff-range-oids "magit 4.6.0")
(define-obsolete-variable-alias 'magit-buffer-revisions
'magit-buffer-log-revisions "magit 4.6.0")
(define-obsolete-variable-alias 'magit-buffer-revision-hash
'magit-buffer-revision-oid "magit 4.6.0")
(define-obsolete-variable-alias 'magit-buffer-typearg
'magit-buffer-diff-typearg "magit 4.6.0")))
(make-variable-buffer-local 'magit-buffer-arguments)
(make-variable-buffer-local 'magit-buffer-upstream)
(defun magit-buffer-file-name ()
"Return `magit-buffer-file-name' or if that is nil `buffer-file-name'.
@@ -617,6 +625,7 @@ In an indirect buffer get the value for its base buffer."
(defun magit-buffer-revision ()
"Return `magit-buffer-revision' or if that is nil \"{worktree}\".
If not visiting a blob or file, or the file isn't being tracked,
return nil. If visiting a blob but `magit-buffer-revision' is nil,
return nil."
(or magit-buffer-revision
(and buffer-file-name
@@ -1098,21 +1107,21 @@ The arguments are for internal use."
(when magit-refresh-verbose
(message "%s buffer `%s'..." action (buffer-name)))
(cond
(created
(funcall refresh)
(cond (initial-section (funcall initial-section))
(select-section (funcall select-section))))
(t
(deactivate-mark)
(setq magit-section-pre-command-section nil)
(setq magit-section-highlight-overlays nil)
(setq magit-section-selection-overlays nil)
(setq magit-section-highlighted-sections nil)
(setq magit-section-focused-sections nil)
(let ((positions (magit--refresh-buffer-get-positions)))
(funcall refresh)
(cond (select-section (funcall select-section))
((magit--refresh-buffer-set-positions positions))))))
(created
(funcall refresh)
(cond (initial-section (funcall initial-section))
(select-section (funcall select-section))))
(t
(deactivate-mark)
(setq magit-section-pre-command-section nil)
(setq magit-section-highlight-overlays nil)
(setq magit-section-selection-overlays nil)
(setq magit-section-highlighted-sections nil)
(setq magit-section-focused-sections nil)
(let ((positions (magit--refresh-buffer-get-positions)))
(funcall refresh)
(cond (select-section (funcall select-section))
((magit--refresh-buffer-set-positions positions))))))
(let ((magit-section-cache-visibility nil))
(magit-section-show magit-root-section))
(run-hooks 'magit-refresh-buffer-hook)
@@ -1179,7 +1188,21 @@ The arguments are for internal use."
;; for the wrong buffer. Originally reported in #4196 and
;; fixed with 482c25a3204468a4f6c2fe12ff061666b61f5f4d.
(let ((magit-section-movement-hook nil))
(magit-section-goto-successor section line char)))))
(magit-section-goto-successor section line char)
;; To store the point value for the selected window, it isn't
;; enough for it to be current, the window has to "display" it.
;; The effect of `goto-char', used by the above function, is not
;; preserved, and using just `set-window-point' would affect the
;; wrong buffer.
(unless (eq (window-dedicated-p) t)
(let ((restore (window-buffer))
(window-scroll-functions nil)
(window-configuration-change-hook nil))
(unwind-protect
(progn
(set-window-buffer nil (current-buffer) t)
(set-window-point nil (point)))
(set-window-buffer nil restore t))))))))
(defun magit-revert-buffer (_ignore-auto _noconfirm)
"Wrapper around `magit-refresh-buffer' suitable as `revert-buffer-function'."
@@ -1539,13 +1562,14 @@ repositories."
"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', and
set `magit-section-visibility-cache' to nil for all Magit buffers
of the repository.
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 empty the `magit--blob-cache'.
With a prefix argument or if optional ALL is non-nil, discard the
mentioned caches completely."
(interactive)
(magit--blob-cache-zap)
(cond (all
(setq magit-repository-local-cache nil)
(setq magit--host-git-version-cache nil)
@@ -1580,21 +1604,21 @@ The additional output can be found in the *Messages* buffer."
(defun magit-run-hook-with-benchmark (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))))
((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
the region ends at the beginning of a line, do not include that
line. Avoid including the line after the end of the file."
(and (magit-buffer-file-name)
(region-active-p)
@@ -1615,6 +1639,7 @@ line. Avoid including the line after the end of the file."
;; ("and>" . "cond-let--and>")
;; ("and-let" . "cond-let--and-let")
;; ("if-let" . "cond-let--if-let")
;; ("when$" . "cond-let--when$")
;; ("when-let" . "cond-let--when-let")
;; ("while-let" . "cond-let--while-let")
;; ("match-string" . "match-string")

View File

@@ -1,6 +1,6 @@
;;; magit-notes.el --- Notes support -*- lexical-binding:t -*-
;; Copyright (C) 2008-2025 The Magit Project Contributors
;; Copyright (C) 2008-2026 The Magit Project Contributors
;; Author: Jonas Bernoulli <emacs.magit@jonas.bernoulli.dev>
;; Maintainer: Jonas Bernoulli <emacs.magit@jonas.bernoulli.dev>
@@ -204,6 +204,7 @@ Also see `magit-notes-merge'."
;; ("and>" . "cond-let--and>")
;; ("and-let" . "cond-let--and-let")
;; ("if-let" . "cond-let--if-let")
;; ("when$" . "cond-let--when$")
;; ("when-let" . "cond-let--when-let")
;; ("while-let" . "cond-let--while-let")
;; ("match-string" . "match-string")

View File

@@ -1,6 +1,6 @@
;;; magit-patch.el --- Creating and applying patches -*- lexical-binding:t -*-
;; Copyright (C) 2008-2025 The Magit Project Contributors
;; Copyright (C) 2008-2026 The Magit Project Contributors
;; Author: Jonas Bernoulli <emacs.magit@jonas.bernoulli.dev>
;; Maintainer: Jonas Bernoulli <emacs.magit@jonas.bernoulli.dev>
@@ -99,18 +99,18 @@ which creates patches for all commits that are reachable from
["Actions"
("c" "Create patches" magit-patch-create)]
(interactive
(if (not (eq transient-current-command 'magit-patch-create))
(list nil nil nil)
(cons (if-let ((revs (magit-region-values 'commit t)))
(concat (car (last revs)) "^.." (car revs))
(let ((range (magit-read-range-or-commit
"Create patches for range or commit")))
(if (string-search ".." range)
range
(format "%s~..%s" range range))))
(let ((args (transient-args 'magit-patch-create)))
(list (seq-filter #'stringp args)
(cdr (assoc "--" args)))))))
(if (not (eq transient-current-command 'magit-patch-create))
(list nil nil nil)
(cons (if-let ((revs (magit-region-values 'commit t)))
(concat (car (last revs)) "^.." (car revs))
(let ((range (magit-read-range-or-commit
"Create patches for range or commit")))
(if (string-search ".." range)
range
(format "%s~..%s" range range))))
(let ((args (transient-args 'magit-patch-create)))
(list (seq-filter #'stringp args)
(cdr (assoc "--" args)))))))
(if (not range)
(transient-setup 'magit-patch-create)
(magit-run-git "format-patch" range args "--" files)
@@ -247,14 +247,14 @@ which creates patches for all commits that are reachable from
["Actions"
("a" "Apply patch" magit-patch-apply)]
(interactive
(if (not (eq transient-current-command 'magit-patch-apply))
(list nil)
(list (expand-file-name
(read-file-name "Apply patch: "
default-directory nil nil
(and$ (magit-file-at-point)
(file-relative-name $))))
(transient-args 'magit-patch-apply))))
(if (not (eq transient-current-command 'magit-patch-apply))
(list nil)
(list (expand-file-name
(read-file-name "Apply patch: "
default-directory nil nil
(and$ (magit-file-at-point)
(file-relative-name $))))
(transient-args 'magit-patch-apply))))
(if (not file)
(transient-setup 'magit-patch-apply)
(magit-run-git "apply" args "--" (magit-convert-filename-for-git file))))
@@ -286,8 +286,8 @@ same differences as those shown in the buffer are always used."
current-prefix-arg))
(unless (derived-mode-p 'magit-diff-mode)
(user-error "Only diff buffers can be saved as patches"))
(let ((rev magit-buffer-range)
(typearg magit-buffer-typearg)
(let ((rev magit-buffer-diff-range)
(typearg magit-buffer-diff-typearg)
(args magit-buffer-diff-args)
(files magit-buffer-diff-files))
(cond ((eq magit-patch-save-arguments 'buffer)
@@ -313,9 +313,9 @@ START is a commit that already is in the upstream repository.
END is the last commit, usually a branch name, which upstream
is asked to pull. START has to be reachable from that commit."
(interactive
(list (magit-get "remote" (magit-read-remote "Remote") "url")
(magit-read-branch-or-commit "Start" (magit-get-upstream-branch))
(magit-read-branch-or-commit "End")))
(list (magit-get "remote" (magit-read-remote "Remote") "url")
(magit-read-branch-or-commit "Start" (magit-get-upstream-branch))
(magit-read-branch-or-commit "End")))
(require 'message)
(let ((dir default-directory))
;; mu4e changes default-directory
@@ -333,6 +333,7 @@ is asked to pull. START has to be reachable from that commit."
;; ("and>" . "cond-let--and>")
;; ("and-let" . "cond-let--and-let")
;; ("if-let" . "cond-let--if-let")
;; ("when$" . "cond-let--when$")
;; ("when-let" . "cond-let--when-let")
;; ("while-let" . "cond-let--while-let")
;; ("match-string" . "match-string")

View File

@@ -1,17 +1,17 @@
;; -*- no-byte-compile: t; lexical-binding: nil -*-
(define-package "magit" "20251217.1836"
(define-package "magit" "20260401.2251"
"A Git porcelain inside Emacs."
'((emacs "28.1")
(compat "30.1")
(cond-let "0.1")
(cond-let "0.2")
(llama "1.0")
(magit-section "4.4")
(magit-section "4.5")
(seq "2.24")
(transient "0.10")
(transient "0.12")
(with-editor "3.4"))
:url "https://github.com/magit/magit"
:commit "655bc502a3bdd7f07928524515a736e4b8101eaf"
:revdesc "655bc502a3bd"
:commit "6db34dc77d10fc9b8c925e79b4e0e21d9f78ac5c"
:revdesc "6db34dc77d10"
:keywords '("git" "tools" "vc")
:authors '(("Marius Vollmer" . "marius.vollmer@gmail.com")
("Jonas Bernoulli" . "emacs.magit@jonas.bernoulli.dev"))

View File

@@ -1,6 +1,6 @@
;;; magit-process.el --- Process functionality -*- lexical-binding:t -*-
;; Copyright (C) 2008-2025 The Magit Project Contributors
;; Copyright (C) 2008-2026 The Magit Project Contributors
;; Author: Jonas Bernoulli <emacs.magit@jonas.bernoulli.dev>
;; Maintainer: Jonas Bernoulli <emacs.magit@jonas.bernoulli.dev>
@@ -572,25 +572,26 @@ flattened before use."
;; On w32, git expects UTF-8 encoded input, ignore any user
;; configuration telling us otherwise (see #3250).
(encode-coding-region (point-min) (point-max) 'utf-8-unix))
(if (file-remote-p default-directory)
;; We lack `process-file-region', so fall back to asynch +
;; waiting in remote case.
(progn
(magit-start-git (current-buffer) args)
(while (and magit-this-process
(eq (process-status magit-this-process) 'run))
(sleep-for 0.005)))
(run-hooks 'magit-pre-call-git-hook)
(pcase-let* ((process-environment (magit-process-environment))
(default-process-coding-system (magit--process-coding-system))
(flat-args (magit-process-git-arguments args t))
(`(,process-buf . ,section)
(magit-process-setup (magit-git-executable) flat-args))
(inhibit-read-only t))
(magit-process-finish
(apply #'call-process-region (point-min) (point-max)
(magit-git-executable) nil process-buf nil flat-args)
process-buf nil default-directory section))))
(cond
((file-remote-p default-directory)
;; We lack `process-file-region', so fall back to asynch +
;; waiting in remote case.
(magit-start-git (current-buffer) args)
(while (and magit-this-process
(eq (process-status magit-this-process) 'run))
(sleep-for 0.005)))
(t
(run-hooks 'magit-pre-call-git-hook)
(pcase-let* ((process-environment (magit-process-environment))
(default-process-coding-system (magit--process-coding-system))
(flat-args (magit-process-git-arguments args t))
(`(,process-buf . ,section)
(magit-process-setup (magit-git-executable) flat-args))
(inhibit-read-only t))
(magit-process-finish
(apply #'call-process-region (point-min) (point-max)
(magit-git-executable) nil process-buf nil flat-args)
process-buf nil default-directory section)))))
;;; Asynchronous Processes
@@ -805,26 +806,26 @@ Magit status buffer."
(defun magit-process--format-arguments (program args)
(cond
((and args (equal program (magit-git-executable)))
(let ((global (magit-process-git-arguments--length)))
(concat
(propertize (file-name-nondirectory program)
'font-lock-face 'magit-section-heading)
" "
(propertize (magit--ellipsis)
'font-lock-face 'magit-section-heading
'help-echo (string-join (seq-take args global) " "))
" "
(propertize (mapconcat #'shell-quote-argument (seq-drop args global) " ")
'font-lock-face 'magit-section-heading))))
((and args (equal program shell-file-name))
(propertize (cadr args)
'font-lock-face 'magit-section-heading))
((concat (propertize (file-name-nondirectory program)
'font-lock-face 'magit-section-heading)
" "
(propertize (mapconcat #'shell-quote-argument args " ")
'font-lock-face 'magit-section-heading)))))
((and args (equal program (magit-git-executable)))
(let ((global (magit-process-git-arguments--length)))
(concat
(propertize (file-name-nondirectory program)
'font-lock-face 'magit-section-heading)
" "
(propertize (magit--ellipsis)
'font-lock-face 'magit-section-heading
'help-echo (string-join (seq-take args global) " "))
" "
(propertize (mapconcat #'shell-quote-argument (seq-drop args global) " ")
'font-lock-face 'magit-section-heading))))
((and args (equal program shell-file-name))
(propertize (cadr args)
'font-lock-face 'magit-section-heading))
((concat (propertize (file-name-nondirectory program)
'font-lock-face 'magit-section-heading)
" "
(propertize (mapconcat #'shell-quote-argument args " ")
'font-lock-face 'magit-section-heading)))))
(defun magit-process-truncate-log ()
(let* ((head nil)
@@ -1358,6 +1359,7 @@ Limited by `magit-process-error-tooltip-max-lines'."
;; ("and>" . "cond-let--and>")
;; ("and-let" . "cond-let--and-let")
;; ("if-let" . "cond-let--if-let")
;; ("when$" . "cond-let--when$")
;; ("when-let" . "cond-let--when-let")
;; ("while-let" . "cond-let--while-let")
;; ("match-string" . "match-string")

View File

@@ -1,6 +1,6 @@
;;; magit-pull.el --- Update local objects and refs -*- lexical-binding:t -*-
;; Copyright (C) 2008-2025 The Magit Project Contributors
;; Copyright (C) 2008-2026 The Magit Project Contributors
;; Author: Jonas Bernoulli <emacs.magit@jonas.bernoulli.dev>
;; Maintainer: Jonas Bernoulli <emacs.magit@jonas.bernoulli.dev>
@@ -138,15 +138,15 @@ the upstream."
(merge (magit-get "branch" branch "merge"))
(u (magit--propertize-face "@{upstream}" 'bold)))
(cond
((magit--unnamed-upstream-p remote merge)
(format "%s of %s"
(magit--propertize-face merge 'magit-branch-remote)
(magit--propertize-face remote 'bold)))
((magit--valid-upstream-p remote merge)
(concat u ", replacing non-existent"))
((or remote merge)
(concat u ", replacing invalid"))
((concat u ", setting that")))))))
((magit--unnamed-upstream-p remote merge)
(format "%s of %s"
(magit--propertize-face merge 'magit-branch-remote)
(magit--propertize-face remote 'bold)))
((magit--valid-upstream-p remote merge)
(concat u ", replacing non-existent"))
((or remote merge)
(concat u ", replacing invalid"))
((concat u ", setting that")))))))
;;;###autoload
(defun magit-pull-branch (source args)
@@ -166,6 +166,7 @@ the upstream."
;; ("and>" . "cond-let--and>")
;; ("and-let" . "cond-let--and-let")
;; ("if-let" . "cond-let--if-let")
;; ("when$" . "cond-let--when$")
;; ("when-let" . "cond-let--when-let")
;; ("while-let" . "cond-let--while-let")
;; ("match-string" . "match-string")

View File

@@ -1,6 +1,6 @@
;;; magit-push.el --- Update remote objects and refs -*- lexical-binding:t -*-
;; Copyright (C) 2008-2025 The Magit Project Contributors
;; Copyright (C) 2008-2026 The Magit Project Contributors
;; Author: Jonas Bernoulli <emacs.magit@jonas.bernoulli.dev>
;; Maintainer: Jonas Bernoulli <emacs.magit@jonas.bernoulli.dev>
@@ -100,14 +100,14 @@ argument the push-remote can be changed before pushed to it."
(remote (magit-get-push-remote branch))
(v (magit--push-remote-variable branch t)))
(cond
(target)
((member remote (magit-list-remotes))
(format "%s, creating it"
(magit--propertize-face (concat remote "/" branch)
'magit-branch-remote)))
(remote
(format "%s, replacing invalid" v))
((format "%s, setting that" v)))))
(target)
((member remote (magit-list-remotes))
(format "%s, creating it"
(magit--propertize-face (concat remote "/" branch)
'magit-branch-remote)))
(remote
(format "%s, replacing invalid" v))
((format "%s, setting that" v)))))
;;;###autoload(autoload 'magit-push-current-to-upstream "magit-push" nil t)
(transient-define-suffix magit-push-current-to-upstream (args)
@@ -160,27 +160,27 @@ the upstream."
(merge (magit-get "branch" branch "merge"))
(u (magit--propertize-face "@{upstream}" 'bold)))
(cond
((magit--unnamed-upstream-p remote merge)
(format "%s as %s"
(magit--propertize-face remote 'bold)
(magit--propertize-face merge 'magit-branch-remote)))
((magit--valid-upstream-p remote merge)
(format "%s creating %s"
(magit--propertize-face remote 'magit-branch-remote)
(magit--propertize-face merge 'magit-branch-remote)))
((or remote merge)
(concat u ", creating it and replacing invalid"))
((concat u ", creating it")))))))
((magit--unnamed-upstream-p remote merge)
(format "%s as %s"
(magit--propertize-face remote 'bold)
(magit--propertize-face merge 'magit-branch-remote)))
((magit--valid-upstream-p remote merge)
(format "%s creating %s"
(magit--propertize-face remote 'magit-branch-remote)
(magit--propertize-face merge 'magit-branch-remote)))
((or remote merge)
(concat u ", creating it and replacing invalid"))
((concat u ", creating it")))))))
;;;###autoload
(defun magit-push-current (target args)
"Push the current branch to a branch read in the minibuffer."
(interactive
(if-let ((current (magit-get-current-branch)))
(list (magit-read-remote-branch (format "Push %s to" current)
nil nil current 'confirm)
(magit-push-arguments))
(user-error "No branch is checked out")))
(if-let ((current (magit-get-current-branch)))
(list (magit-read-remote-branch (format "Push %s to" current)
nil nil current 'confirm)
(magit-push-arguments))
(user-error "No branch is checked out")))
(magit-git-push (magit-get-current-branch) target args))
;;;###autoload
@@ -188,18 +188,18 @@ the upstream."
"Push an arbitrary branch or commit somewhere.
Both the source and the target are read in the minibuffer."
(interactive
(let ((source (magit-read-local-branch-or-commit "Push")))
(list source
(magit-read-remote-branch
(format "Push %s to" source) nil
(cond ((magit-local-branch-p source)
(or (magit-get-push-branch source)
(magit-get-upstream-branch source)))
((magit-rev-ancestor-p source "HEAD")
(or (magit-get-push-branch)
(magit-get-upstream-branch))))
source 'confirm)
(magit-push-arguments))))
(let ((source (magit-read-local-branch-or-commit "Push")))
(list source
(magit-read-remote-branch
(format "Push %s to" source) nil
(cond ((magit-local-branch-p source)
(or (magit-get-push-branch source)
(magit-get-upstream-branch source)))
((magit-rev-ancestor-p source "HEAD")
(or (magit-get-push-branch)
(magit-get-upstream-branch))))
source 'confirm)
(magit-push-arguments))))
(magit-git-push source target args))
(defvar magit-push-refspecs-history nil)
@@ -212,12 +212,12 @@ use multiple REFSPECS, separate them with commas. Completion is
only available for the part before the colon, or when no colon
is used."
(interactive
(list (magit-read-remote "Push to remote")
(magit-completing-read-multiple
"Push refspec,s: "
(cons "HEAD" (magit-list-local-branch-names))
nil 'any nil 'magit-push-refspecs-history)
(magit-push-arguments)))
(list (magit-read-remote "Push to remote")
(magit-completing-read-multiple
"Push refspec,s: "
(cons "HEAD" (magit-list-local-branch-names))
nil 'any nil 'magit-push-refspecs-history)
(magit-push-arguments)))
(run-hooks 'magit-credential-hook)
(magit-run-git-async "push" "-v" args remote refspecs))
@@ -246,9 +246,9 @@ branch as default."
(defun magit-push-tag (tag remote &optional args)
"Push a tag to another repository."
(interactive
(let ((tag (magit-read-tag "Push tag")))
(list tag (magit-read-remote (format "Push %s to remote" tag) nil t)
(magit-push-arguments))))
(let ((tag (magit-read-tag "Push tag")))
(list tag (magit-read-remote (format "Push %s to remote" tag) nil t)
(magit-push-arguments))))
(run-hooks 'magit-credential-hook)
(magit-run-git-async "push" remote tag args))
@@ -256,10 +256,10 @@ branch as default."
(defun magit-push-notes-ref (ref remote &optional args)
"Push a notes ref to another repository."
(interactive
(let ((note (magit-notes-read-ref "Push notes")))
(list note
(magit-read-remote (format "Push %s to remote" note) nil t)
(magit-push-arguments))))
(let ((note (magit-notes-read-ref "Push notes")))
(list note
(magit-read-remote (format "Push %s to remote" note) nil t)
(magit-push-arguments))))
(run-hooks 'magit-credential-hook)
(magit-run-git-async "push" remote ref args))
@@ -300,12 +300,12 @@ what this command will do. To add it use something like:
;; Note: Avoid `magit-get-remote' because it
;; filters out the local repo case (".").
(magit-get "branch" branch "remote")
(cond-let
[[remotes (magit-list-remotes)]]
((and (magit-git-version>= "2.27")
(length= remotes 1))
(car remotes))
((car (member "origin" remotes)))))))
(cond-let
[[remotes (magit-list-remotes)]]
((and (magit-git-version>= "2.27")
(length= remotes 1))
(car remotes))
((car (member "origin" remotes)))))))
(if (null remote)
"nothing (no remote)"
(let ((refspec (magit-get "remote" remote "push")))
@@ -326,17 +326,17 @@ what this command will do. To add it use something like:
(format "%s to %s"
(magit--propertize-face branch 'magit-branch-current)
(cond
((string-prefix-p "refs/heads/" ref)
(magit--propertize-face
(format "%s/%s" remote
(substring ref (length "refs/heads/")))
'magit-branch-remote))
((not (string-match "/" ref))
(magit--propertize-face (format "%s/%s" remote ref)
'magit-branch-remote))
((format "%s as %s"
(magit--propertize-face remote 'bold)
(magit--propertize-face ref 'bold)))))
((string-prefix-p "refs/heads/" ref)
(magit--propertize-face
(format "%s/%s" remote
(substring ref (length "refs/heads/")))
'magit-branch-remote))
((not (string-match "/" ref))
(magit--propertize-face (format "%s/%s" remote ref)
'magit-branch-remote))
((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)))))))))
@@ -374,6 +374,7 @@ You can add this command as a suffix using something like:
;; ("and>" . "cond-let--and>")
;; ("and-let" . "cond-let--and-let")
;; ("if-let" . "cond-let--if-let")
;; ("when$" . "cond-let--when$")
;; ("when-let" . "cond-let--when-let")
;; ("while-let" . "cond-let--while-let")
;; ("match-string" . "match-string")

View File

@@ -1,6 +1,6 @@
;;; magit-reflog.el --- Inspect ref history -*- lexical-binding:t -*-
;; Copyright (C) 2008-2025 The Magit Project Contributors
;; Copyright (C) 2008-2026 The Magit Project Contributors
;; Author: Jonas Bernoulli <emacs.magit@jonas.bernoulli.dev>
;; Maintainer: Jonas Bernoulli <emacs.magit@jonas.bernoulli.dev>
@@ -211,6 +211,7 @@ Type \\[magit-reset] to reset `HEAD' to the commit at point.
;; ("and>" . "cond-let--and>")
;; ("and-let" . "cond-let--and-let")
;; ("if-let" . "cond-let--if-let")
;; ("when$" . "cond-let--when$")
;; ("when-let" . "cond-let--when-let")
;; ("while-let" . "cond-let--while-let")
;; ("match-string" . "match-string")

View File

@@ -1,6 +1,6 @@
;;; magit-refs.el --- Listing references -*- lexical-binding:t -*-
;; Copyright (C) 2008-2025 The Magit Project Contributors
;; Copyright (C) 2008-2026 The Magit Project Contributors
;; Author: Jonas Bernoulli <emacs.magit@jonas.bernoulli.dev>
;; Maintainer: Jonas Bernoulli <emacs.magit@jonas.bernoulli.dev>
@@ -308,24 +308,27 @@ Type \\[magit-reset] to reset `HEAD' to the commit at point.
(magit-hack-dir-local-variables)
(setq magit--imenu-group-types '(local remote tags)))
(defvar-local magit-buffer-refs-args nil)
(defvar-local magit-buffer-refs-upstream nil)
(defun magit-refs-setup-buffer (ref args)
(magit-setup-buffer #'magit-refs-mode nil
(magit-buffer-upstream ref)
(magit-buffer-arguments args)))
(magit-buffer-refs-upstream ref)
(magit-buffer-refs-args args)))
(defun magit-refs-refresh-buffer ()
(setq magit--right-margin-delayed (not (magit--right-margin-active)))
(unless (magit-rev-verify magit-buffer-upstream)
(unless (magit-rev-verify magit-buffer-refs-upstream)
(setq magit-refs-show-commit-count nil))
(magit-set-header-line-format
(format "%s %s" magit-buffer-upstream
(string-join magit-buffer-arguments " ")))
(format "%s %s" magit-buffer-refs-upstream
(string-join magit-buffer-refs-args " ")))
(magit-insert-section (branchbuf)
(magit-run-section-hook 'magit-refs-sections-hook))
(add-hook 'kill-buffer-hook #'magit-preserve-section-visibility-cache))
(cl-defmethod magit-buffer-value (&context (major-mode magit-refs-mode))
(cons magit-buffer-upstream magit-buffer-arguments))
(cons magit-buffer-refs-upstream magit-buffer-refs-args))
;;; Commands
@@ -360,11 +363,11 @@ Type \\[magit-reset] to reset `HEAD' to the commit at point.
((eq transient-current-command 'magit-show-refs)
(transient-args 'magit-show-refs))
((eq major-mode 'magit-refs-mode)
magit-buffer-arguments)
magit-buffer-refs-args)
([_(memq use-buffer-args '(always selected))]
[buffer (magit-get-mode-buffer 'magit-refs-mode nil
(eq use-buffer-args 'selected))]
(buffer-local-value 'magit-buffer-arguments buffer))
(buffer-local-value 'magit-buffer-refs-args buffer))
((alist-get 'magit-show-refs transient-values))))
(transient-define-argument magit-for-each-ref:--contains ()
@@ -468,13 +471,13 @@ Branch %s already exists.
(?r (magit-call-git "checkout" "-B" branch ref))
(?a (user-error "Abort"))))
(magit-call-git "checkout" "-b" branch ref))
(setq magit-buffer-upstream branch)
(setq magit-buffer-refs-upstream branch)
(magit-refresh)))
((or (memq 'checkout-any magit-visit-ref-behavior)
(and (memq 'checkout-branch magit-visit-ref-behavior)
(magit-section-match [branch local])))
(magit-call-git "checkout" ref)
(setq magit-buffer-upstream ref)
(setq magit-buffer-refs-upstream ref)
(magit-refresh))
((call-interactively #'magit-show-commit))))
@@ -540,7 +543,7 @@ line is inserted at all."
(defun magit-insert-tags ()
"Insert sections showing all tags."
(when-let ((tags (magit-git-lines "tag" "--list" "-n" magit-buffer-arguments)))
(when-let ((tags (magit-git-lines "tag" "--list" "-n" magit-buffer-refs-args)))
(let ((_head (magit-rev-parse "HEAD")))
(magit-insert-section (tags)
(magit-insert-heading (length tags) "Tags")
@@ -581,42 +584,42 @@ line is inserted at all."
(dolist (line (magit-git-lines "for-each-ref" "--format=\
%(symref:short)%00%(refname:short)%00%(refname)%00%(subject)"
(concat "refs/remotes/" remote)
magit-buffer-arguments))
magit-buffer-refs-args))
(pcase-let ((`(,head-branch ,branch ,ref ,msg)
(cl-substitute nil ""
(split-string line "\0")
:test #'equal)))
(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))
(abbrev (if magit-refs-show-remote-prefix
branch
(substring branch (1+ (length remote))))))
(magit-insert-heading
(magit-refs--format-focus-column branch)
(magit-refs--propertize-branch
abbrev ref (and headp 'magit-branch-remote-head))
(make-string
(max 1 (- (if (consp magit-refs-primary-column-width)
(car magit-refs-primary-column-width)
magit-refs-primary-column-width)
(length abbrev)))
?\s)
(and msg (magit-log--wash-summary msg))))
(magit-refs--maybe-format-margin branch)
(magit-refs--insert-cherry-commits branch))))))))
(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))
(abbrev (if magit-refs-show-remote-prefix
branch
(substring branch (1+ (length remote))))))
(magit-insert-heading
(magit-refs--format-focus-column branch)
(magit-refs--propertize-branch
abbrev ref (and headp 'magit-branch-remote-head))
(make-string
(max 1 (- (if (consp magit-refs-primary-column-width)
(car magit-refs-primary-column-width)
magit-refs-primary-column-width)
(length abbrev)))
?\s)
(and msg (magit-log--wash-summary msg))))
(magit-refs--maybe-format-margin branch)
(magit-refs--insert-cherry-commits branch))))))))
(insert ?\n)
(magit-make-margin-overlay))))
@@ -661,7 +664,7 @@ line is inserted at all."
%(push:remotename)%00%(push)%00%(push:track)%00%(subject)"
"%00%00%00%(subject)"))
"refs/heads"
magit-buffer-arguments))))
magit-buffer-refs-args))))
(unless (magit-get-current-branch)
(push (magit-refs--format-local-branch
(concat "*\0\0\0\0\0\0\0\0" (magit-rev-format "%s")))
@@ -750,7 +753,7 @@ line is inserted at all."
(and msg (magit-log--wash-summary msg))))))))
(defun magit-refs--format-focus-column (ref &optional type)
(let ((focus magit-buffer-upstream)
(let ((focus magit-buffer-refs-upstream)
(width (if magit-refs-show-commit-count
magit-refs-focus-column-width
1)))
@@ -766,7 +769,7 @@ line is inserted at all."
(eq magit-refs-show-commit-count 'all)
magit-refs-show-commit-count)
(pcase-let ((`(,behind ,ahead)
(magit-rev-diff-count magit-buffer-upstream ref)))
(magit-rev-diff-count magit-buffer-refs-upstream ref)))
(magit--propertize-face
(cond ((> ahead 0) (concat "<" (number-to-string ahead)))
((> behind 0) (concat (number-to-string behind) ">"))
@@ -795,7 +798,7 @@ line is inserted at all."
(let ((start (point))
(magit-insert-section--current nil))
(magit-git-wash (apply-partially #'magit-log-wash-log 'cherry)
"cherry" "-v" (magit-abbrev-arg) magit-buffer-upstream ref)
"cherry" "-v" (magit-abbrev-arg) magit-buffer-refs-upstream ref)
(if (= (point) start)
(message "No cherries for %s" ref)
(magit-make-margin-overlay)))))
@@ -814,6 +817,7 @@ line is inserted at all."
;; ("and>" . "cond-let--and>")
;; ("and-let" . "cond-let--and-let")
;; ("if-let" . "cond-let--if-let")
;; ("when$" . "cond-let--when$")
;; ("when-let" . "cond-let--when-let")
;; ("while-let" . "cond-let--while-let")
;; ("match-string" . "match-string")

View File

@@ -1,6 +1,6 @@
;;; magit-remote.el --- Transfer Git commits -*- lexical-binding:t -*-
;; Copyright (C) 2008-2025 The Magit Project Contributors
;; Copyright (C) 2008-2026 The Magit Project Contributors
;; Author: Jonas Bernoulli <emacs.magit@jonas.bernoulli.dev>
;; Maintainer: Jonas Bernoulli <emacs.magit@jonas.bernoulli.dev>
@@ -73,7 +73,8 @@ has to be used to view and change remote related variables."
("U" magit-remote.<remote>.fetch)
("s" magit-remote.<remote>.pushurl)
("S" magit-remote.<remote>.push)
("O" magit-remote.<remote>.tagopt)]
("O" magit-remote.<remote>.tagopt)
("h" magit-remote.<remote>.followremotehead)]
["Arguments for add"
("-f" "Fetch after add" "-f")]
["Actions"
@@ -98,31 +99,31 @@ has to be used to view and change remote related variables."
(defun magit-remote-add (remote url &optional args)
"Add a remote named REMOTE and fetch it."
(interactive
(let ((origin (magit-get "remote.origin.url"))
(remote (magit-read-string-ns "Remote name")))
(list remote
(magit-read-url
"Remote url"
(and origin
(string-match "\\([^:/]+\\)/[^/]+\\(\\.git\\)?\\'" origin)
(replace-match remote t t origin 1)))
(transient-args 'magit-remote))))
(if (pcase (list magit-remote-add-set-remote.pushDefault
(magit-get "remote.pushDefault"))
(`(,(pred stringp) ,_) t)
((or `(ask ,_) '(ask-if-unset nil))
(y-or-n-p (format "Set `remote.pushDefault' to \"%s\"? " remote))))
(progn (magit-call-git "remote" "add" args remote url)
(setf (magit-get "remote.pushDefault") remote)
(magit-refresh))
(magit-run-git-async "remote" "add" args remote url)))
(let ((origin (magit-get "remote.origin.url"))
(remote (magit-read-string-ns "Remote name")))
(list remote
(magit-read-url
"Remote url"
(and origin
(string-match "\\([^:/]+\\)/[^/]+\\(\\.git\\)?\\'" origin)
(replace-match remote t t origin 1)))
(transient-args 'magit-remote))))
(cond ((pcase (list magit-remote-add-set-remote.pushDefault
(magit-get "remote.pushDefault"))
(`(,(pred stringp) ,_) t)
((or `(ask ,_) '(ask-if-unset nil))
(y-or-n-p (format "Set `remote.pushDefault' to \"%s\"? " remote))))
(magit-call-git "remote" "add" args remote url)
(setf (magit-get "remote.pushDefault") remote)
(magit-refresh))
((magit-run-git-async "remote" "add" args remote url))))
;;;###autoload
(defun magit-remote-rename (old new)
"Rename the remote named OLD to NEW."
(interactive
(let ((remote (magit-read-remote "Rename remote")))
(list remote (magit-read-string-ns (format "Rename %s to" remote)))))
(let ((remote (magit-read-remote "Rename remote")))
(list remote (magit-read-string-ns (format "Rename %s to" remote)))))
(unless (string= old new)
(magit-call-git "remote" "rename" old new)
(magit-remote--cleanup-push-variables old new)
@@ -239,11 +240,11 @@ accordingly. With a prefix argument query for the branch to be
used, which allows you to select an incorrect value if you fancy
doing that."
(interactive
(let ((remote (magit-read-remote "Set HEAD for remote")))
(list remote
(and current-prefix-arg
(magit-read-remote-branch (format "Set %s/HEAD to" remote)
remote nil nil t)))))
(let ((remote (magit-read-remote "Set HEAD for remote")))
(list remote
(and current-prefix-arg
(magit-read-remote-branch (format "Set %s/HEAD to" remote)
remote nil nil t)))))
(magit-run-git "remote" "set-head" remote (or branch "--auto")))
;;;###autoload
@@ -262,28 +263,28 @@ Delete the symbolic-ref \"refs/remotes/<remote>/HEAD\"."
(pcase-let ((`(,_remote ,oldname) (magit--get-default-branch))
(`( ,remote ,newname) (magit--get-default-branch t)))
(cond
((equal oldname newname)
(setq oldname
(read-string
(format
"Name of default branch is still `%s', %s\n%s `%s': " oldname
"but the upstreams of some local branches might need updating."
"Name of upstream branches to replace with" newname)))
(magit--set-default-branch newname oldname)
(magit-refresh))
(t
(unless oldname
(setq oldname
(magit-read-other-local-branch
(format "Name of old default branch to be renamed to `%s'"
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"))
(magit--set-default-branch newname oldname)
(magit-refresh))
((user-error "Abort")))))))
((equal oldname newname)
(setq oldname
(read-string
(format
"Name of default branch is still `%s', %s\n%s `%s': " oldname
"but the upstreams of some local branches might need updating."
"Name of upstream branches to replace with" newname)))
(magit--set-default-branch newname oldname)
(magit-refresh))
(t
(unless oldname
(setq oldname
(magit-read-other-local-branch
(format "Name of old default branch to be renamed to `%s'"
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"))
(magit--set-default-branch newname oldname)
(magit-refresh))
((user-error "Abort")))))))
;;;###autoload
(defun magit-remote-unshallow (remote)
@@ -316,13 +317,14 @@ refspec."
("U" magit-remote.<remote>.fetch)
("s" magit-remote.<remote>.pushurl)
("S" magit-remote.<remote>.push)
("O" magit-remote.<remote>.tagopt)]
("O" magit-remote.<remote>.tagopt)
("h" magit-remote.<remote>.followremotehead)]
(interactive
(list (or (and (not current-prefix-arg)
(not (and magit-remote-direct-configure
(eq transient-current-command 'magit-remote)))
(magit-get-current-remote))
(magit--read-remote-scope))))
(list (or (and (not current-prefix-arg)
(not (and magit-remote-direct-configure
(eq transient-current-command 'magit-remote)))
(magit-get-current-remote))
(magit--read-remote-scope))))
(transient-setup 'magit-remote-configure nil nil :scope remote))
(defun magit--read-remote-scope (&optional obj)
@@ -364,6 +366,27 @@ refspec."
:variable "remote.%s.tagOpt"
:choices '("--no-tags" "--tags"))
(transient-define-infix magit-remote.<remote>.followremotehead ()
"How \"git fetch\" handles updates to \"remotes/<remote>/HEAD\".
This command sets the local value of the Git variable
`remote.<remote>.followRemoteHEAD', where <remote> is a stand-in for
the actual remote, as displayed in the menu, from which this command
is invoked. This variable is documented in (man \"git-config(1)\").
Unfortunately Git does not provide a variable to set a default for
all remotes of all repositories, but you can set the global value for
a remote name used in multiple repository, which will then be used as
the default for that remote in all repositories. You should consider
using \"always\" for remotes named \"origin\".
git config set --global remote.origin.followRemoteHEAD always"
:class 'magit--git-variable:choices
:scope #'magit--read-remote-scope
:variable "remote.%s.followRemoteHEAD"
:choices '("create" "always" "warn")
:default "create")
;;; Transfer Utilities
(defun magit--push-remote-variable (&optional branch short)
@@ -399,6 +422,7 @@ refspec."
;; ("and>" . "cond-let--and>")
;; ("and-let" . "cond-let--and-let")
;; ("if-let" . "cond-let--if-let")
;; ("when$" . "cond-let--when$")
;; ("when-let" . "cond-let--when-let")
;; ("while-let" . "cond-let--while-let")
;; ("match-string" . "match-string")

View File

@@ -1,6 +1,6 @@
;;; magit-repos.el --- Listing repositories -*- lexical-binding:t -*-
;; Copyright (C) 2008-2025 The Magit Project Contributors
;; Copyright (C) 2008-2026 The Magit Project Contributors
;; Author: Jonas Bernoulli <emacs.magit@jonas.bernoulli.dev>
;; Maintainer: Jonas Bernoulli <emacs.magit@jonas.bernoulli.dev>
@@ -535,7 +535,9 @@ instead."
(magit-list-repos-uniquify
(mapcar (lambda (v)
(cons (concat
key "\\"
key
(or (bound-and-true-p uniquify-separator)
"\\")
(file-name-nondirectory
(directory-file-name
(substring v 0 (- (1+ (length key)))))))
@@ -557,6 +559,7 @@ instead."
;; ("and>" . "cond-let--and>")
;; ("and-let" . "cond-let--and-let")
;; ("if-let" . "cond-let--if-let")
;; ("when$" . "cond-let--when$")
;; ("when-let" . "cond-let--when-let")
;; ("while-let" . "cond-let--while-let")
;; ("match-string" . "match-string")

View File

@@ -1,6 +1,6 @@
;;; magit-reset.el --- Reset functionality -*- lexical-binding:t -*-
;; Copyright (C) 2008-2025 The Magit Project Contributors
;; Copyright (C) 2008-2026 The Magit Project Contributors
;; Author: Jonas Bernoulli <emacs.magit@jonas.bernoulli.dev>
;; Maintainer: Jonas Bernoulli <emacs.magit@jonas.bernoulli.dev>
@@ -140,6 +140,7 @@ or \"detached head\" will be substituted for %s."
;; ("and>" . "cond-let--and>")
;; ("and-let" . "cond-let--and-let")
;; ("if-let" . "cond-let--if-let")
;; ("when$" . "cond-let--when$")
;; ("when-let" . "cond-let--when-let")
;; ("while-let" . "cond-let--while-let")
;; ("match-string" . "match-string")

View File

@@ -1,6 +1,6 @@
;;; magit-sequence.el --- History manipulation in Magit -*- lexical-binding:t -*-
;; Copyright (C) 2008-2025 The Magit Project Contributors
;; Copyright (C) 2008-2026 The Magit Project Contributors
;; Author: Jonas Bernoulli <emacs.magit@jonas.bernoulli.dev>
;; Maintainer: Jonas Bernoulli <emacs.magit@jonas.bernoulli.dev>
@@ -95,12 +95,12 @@
"Resume the current cherry-pick or revert sequence."
(interactive)
(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"))))
((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 ()
@@ -117,13 +117,13 @@
This discards all changes made since the sequence started."
(interactive)
(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"))))
((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)
@@ -229,18 +229,18 @@ Remove the COMMITS from BRANCH and stay on the current branch.
If a conflict occurs, then you have to fix that and finish the
process manually."
(interactive
(magit--cherry-move-read-args "harvest" nil
(lambda (commits)
(list (let ((branches (magit-list-containing-branches (car commits))))
(pcase (length branches)
(0 nil)
(1 (car branches))
(_ (magit-completing-read
(let ((len (length commits)))
(if (= len 1)
"Remove 1 cherry from branch"
(format "Remove %s cherries from branch" len)))
branches nil t))))))))
(magit--cherry-move-read-args "harvest" nil
(lambda (commits)
(list (let ((branches (magit-list-containing-branches (car commits))))
(pcase (length branches)
(0 nil)
(1 (car branches))
(_ (magit-completing-read
(let ((len (length commits)))
(if (= len 1)
"Remove 1 cherry from branch"
(format "Remove %s cherries from branch" len)))
branches nil t))))))))
(magit--cherry-move commits branch (magit-get-current-branch) args nil t))
;;;###autoload
@@ -250,14 +250,14 @@ Remove COMMITS from the current branch and stay on that branch.
If a conflict occurs, then you have to fix that and finish the
process manually. `HEAD' is allowed to be detached initially."
(interactive
(magit--cherry-move-read-args "donate" t
(lambda (commits)
(list (magit-read-other-branch
(let ((len (length commits)))
(if (= len 1)
"Move 1 cherry to branch"
(format "Move %s cherries to branch" len))))))
'allow-detached))
(magit--cherry-move-read-args "donate" t
(lambda (commits)
(list (magit-read-other-branch
(let ((len (length commits)))
(if (= len 1)
"Move 1 cherry to branch"
(format "Move %s cherries to branch" len))))))
'allow-detached))
(magit--cherry-move commits
(or (magit-get-current-branch)
(magit-rev-parse "HEAD"))
@@ -289,8 +289,8 @@ the process manually."
(unless (magit-branch-p dst)
(let ((magit-process-raise-error t))
(magit-call-git "branch" dst start-point))
(when-let ((upstream (magit-get-indirect-upstream-branch start-point)))
(magit-call-git "branch" "--set-upstream-to" upstream dst)))
(when$ (magit-get-indirect-upstream-branch start-point)
(magit-call-git "branch" "--set-upstream-to" $ dst)))
(unless (equal dst current)
(let ((magit-process-raise-error t))
(magit-call-git "checkout" dst)))
@@ -308,32 +308,32 @@ the process manually."
(process-put process 'inhibit-refresh t)
(magit-process-sentinel process event)
(cond
((magit-rev-equal tip src)
(magit-call-git "update-ref"
"-m" (format "reset: moving to %s" keep)
(magit-ref-fullname src)
keep tip)
(if (not checkout-dst)
(magit-run-git "checkout" src)
(magit-refresh)))
(t
(magit-git "checkout" src)
(with-environment-variables
(("GIT_SEQUENCE_EDITOR"
(format "%s -i -ne '/^pick (%s)/ or print'"
magit-perl-executable
(mapconcat #'magit-rev-abbrev commits "|"))))
(magit-run-git-sequencer "rebase" "-i" keep))
(when checkout-dst
(set-process-sentinel
magit-this-process
(lambda (process event)
(when (memq (process-status process) '(exit signal))
(if (> (process-exit-status process) 0)
(magit-process-sentinel process event)
(process-put process 'inhibit-refresh t)
(magit-process-sentinel process event)
(magit-run-git "checkout" dst))))))))))))))))
((magit-rev-equal tip src)
(magit-call-git "update-ref"
"-m" (format "reset: moving to %s" keep)
(magit-ref-fullname src)
keep tip)
(if (not checkout-dst)
(magit-run-git "checkout" src)
(magit-refresh)))
(t
(magit-git "checkout" src)
(with-environment-variables
(("GIT_SEQUENCE_EDITOR"
(format "%s -i -ne '/^pick (%s)/ or print'"
magit-perl-executable
(mapconcat #'magit-rev-abbrev commits "|"))))
(magit-run-git-sequencer "rebase" "-i" keep))
(when checkout-dst
(set-process-sentinel
magit-this-process
(lambda (process event)
(when (memq (process-status process) '(exit signal))
(if (> (process-exit-status process) 0)
(magit-process-sentinel process event)
(process-put process 'inhibit-refresh t)
(magit-process-sentinel process event)
(magit-run-git "checkout" dst))))))))))))))))
(defun magit--cherry-pick (commits args &optional revert)
(let ((command (if revert "revert" "cherry-pick")))
@@ -345,16 +345,16 @@ the process manually."
(if revert "revert" "cherry-pick")
(let ((merges (seq-filter #'magit-merge-commit-p commits)))
(cond
((not merges)
(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))
((seq-find (##string-prefix-p "--mainline=" %) args)
args)
((cons (format "--mainline=%s"
(read-number "Replay merges relative to parent: "))
args))))
((not merges)
(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))
((seq-find (##string-prefix-p "--mainline=" %) args)
args)
((cons (format "--mainline=%s"
(read-number "Replay merges relative to parent: "))
args))))
commits)))
(defun magit-cherry-pick-in-progress-p ()
@@ -491,11 +491,11 @@ without prompting."
"Resume the current patch applying sequence."
(interactive)
(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"))))
((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 ()
@@ -628,13 +628,13 @@ the upstream."
(merge (magit-get "branch" branch "merge"))
(u (magit--propertize-face "@{upstream}" 'bold)))
(cond
((magit--unnamed-upstream-p remote merge)
(concat u ", replacing unnamed"))
((magit--valid-upstream-p remote merge)
(concat u ", replacing non-existent"))
((or remote merge)
(concat u ", replacing invalid"))
((concat u ", setting that")))))))
((magit--unnamed-upstream-p remote merge)
(concat u ", replacing unnamed"))
((magit--valid-upstream-p remote merge)
(concat u ", replacing non-existent"))
((or remote merge)
(concat u ", replacing invalid"))
((concat u ", setting that")))))))
;;;###autoload
(defun magit-rebase-branch (target args)
@@ -823,25 +823,25 @@ In some cases this pops up a commit message buffer for you do
edit. With a prefix argument the old message is reused as-is."
(interactive "P")
(cond
((not (magit-rebase-in-progress-p))
(user-error "No rebase in progress"))
((magit-anything-unstaged-p t)
(user-error "Cannot continue rebase with unstaged changes"))
(t
(let ((dir (magit-gitdir)))
(when (and (magit-anything-staged-p)
(file-exists-p (expand-file-name "rebase-merge" dir))
(not (member (magit-toplevel)
magit--rebase-public-edit-confirmed)))
(magit-commit-amend-assert
(magit-file-line (expand-file-name "rebase-merge/orig-head" dir)))))
(if noedit
(with-environment-variables (("GIT_EDITOR" "true"))
(magit-run-git-async (magit--rebase-resume-command) "--continue")
(set-process-sentinel magit-this-process
#'magit-sequencer-process-sentinel)
magit-this-process)
(magit-run-git-sequencer (magit--rebase-resume-command) "--continue")))))
((not (magit-rebase-in-progress-p))
(user-error "No rebase in progress"))
((magit-anything-unstaged-p t)
(user-error "Cannot continue rebase with unstaged changes"))
(t
(let ((dir (magit-gitdir)))
(when (and (magit-anything-staged-p)
(file-exists-p (expand-file-name "rebase-merge" dir))
(not (member (magit-toplevel)
magit--rebase-public-edit-confirmed)))
(magit-commit-amend-assert
(magit-file-line (expand-file-name "rebase-merge/orig-head" dir)))))
(if noedit
(with-environment-variables (("GIT_EDITOR" "true"))
(magit-run-git-async (magit--rebase-resume-command) "--continue")
(set-process-sentinel magit-this-process
#'magit-sequencer-process-sentinel)
magit-this-process)
(magit-run-git-sequencer (magit--rebase-resume-command) "--continue")))))
;;;###autoload
(defun magit-rebase-skip ()
@@ -932,8 +932,9 @@ If no such sequence is in progress, do nothing."
patch commit)
(while (and patches (>= i cur))
(setq patch (pop patches))
(setq commit (magit-commit-p
(cadr (split-string (magit-file-line patch)))))
(setq commit
(magit-commit-oid (cadr (split-string (magit-file-line patch)))
t))
(cond ((and commit (= i cur))
(magit-sequence-insert-commit
"stop" commit 'magit-sequence-stop))
@@ -988,8 +989,8 @@ If no such sequence is in progress, do nothing."
(defun magit-rebase--todo ()
"Return `git-rebase-action' instances for remaining rebase actions.
These are ordered in that the same way they'll be sorted in the
status buffer (i.e., the reverse of how they will be applied)."
These are ordered the same way they'll be sorted in the status
buffer (i.e., the reverse of how they will be applied)."
(let ((comment-start (or (magit-get "core.commentChar") "#"))
(commits ())
(actions ()))
@@ -1066,37 +1067,37 @@ status buffer (i.e., the reverse of how they will be applied)."
(if-let ((matched (car (assoc (##equal (magit-patch-id %) id) done))))
(setq stop matched)
(cond
((assoc (##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...
((magit-anything-unmerged-p)
;; ...and time travel isn't for the faint of heart.
(magit-sequence-insert-commit "join" stop 'magit-sequence-part))
((magit-anything-modified-p t)
;; ...and the dust hasn't settled yet...
(magit-sequence-insert-commit
(let* ((magit--refresh-cache nil)
(staged (magit-commit-tree "oO" nil "HEAD"))
(unstaged (magit-commit-worktree "oO" "--reset")))
(cond
;; ...but we could end up at the same tree just by committing.
((or (magit-rev-equal staged stop)
(magit-rev-equal unstaged stop))
"goal")
;; ...but the changes are still there, untainted.
((or (equal (magit-patch-id staged) id)
(equal (magit-patch-id unstaged) id))
"same")
;; ...and some changes are gone and/or others were added.
("work")))
stop 'magit-sequence-part))
;; The commit is definitely gone...
((assoc (##magit-rev-equal % stop) done)
;; ...but all of its changes are still in effect.
(magit-sequence-insert-commit "poof" stop 'magit-sequence-drop))
;; ...and some changes are gone and/or other changes were added.
((magit-sequence-insert-commit "gone" stop 'magit-sequence-drop)))
((assoc (##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...
((magit-anything-unmerged-p)
;; ...and time travel isn't for the faint of heart.
(magit-sequence-insert-commit "join" stop 'magit-sequence-part))
((magit-anything-modified-p t)
;; ...and the dust hasn't settled yet...
(magit-sequence-insert-commit
(let* ((magit--refresh-cache nil)
(staged (magit-commit-tree "oO" nil "HEAD"))
(unstaged (magit-commit-worktree "oO" "--reset")))
(cond
;; ...but we could end up at the same tree just by committing.
((or (magit-rev-equal staged stop)
(magit-rev-equal unstaged stop))
"goal")
;; ...but the changes are still there, untainted.
((or (equal (magit-patch-id staged) id)
(equal (magit-patch-id unstaged) id))
"same")
;; ...and some changes are gone and/or others were added.
("work")))
stop 'magit-sequence-part))
;; The commit is definitely gone...
((assoc (##magit-rev-equal % stop) done)
;; ...but all of its changes are still in effect.
(magit-sequence-insert-commit "poof" stop 'magit-sequence-drop))
;; ...and some changes are gone and/or other changes were added.
((magit-sequence-insert-commit "gone" stop 'magit-sequence-drop)))
(setq stop nil))))
(pcase-dolist (`(,rev ,abbrev ,msg) done)
(apply #'magit-sequence-insert-commit
@@ -1144,6 +1145,7 @@ status buffer (i.e., the reverse of how they will be applied)."
;; ("and>" . "cond-let--and>")
;; ("and-let" . "cond-let--and-let")
;; ("if-let" . "cond-let--if-let")
;; ("when$" . "cond-let--when$")
;; ("when-let" . "cond-let--when-let")
;; ("while-let" . "cond-let--while-let")
;; ("match-string" . "match-string")

View File

@@ -1,6 +1,6 @@
;;; magit-sparse-checkout.el --- Sparse checkout support for Magit -*- lexical-binding:t -*-
;; Copyright (C) 2008-2025 The Magit Project Contributors
;; Copyright (C) 2008-2026 The Magit Project Contributors
;; Author: Kyle Meyer <kyle@kyleam.com>
;; Maintainer: Jonas Bernoulli <emacs.magit@jonas.bernoulli.dev>
@@ -82,12 +82,12 @@ See the `git sparse-checkout' manpage for details about
To extend rather than override the currently configured
directories, call `magit-sparse-checkout-add' instead."
(interactive
(list (magit-completing-read-multiple
"Include these directories: "
;; Note: Given that the appeal of sparse checkouts is
;; dealing with very large trees, listing all subdirectories
;; may need to be reconsidered.
(magit-revision-directories "HEAD"))))
(list (magit-completing-read-multiple
"Include these directories: "
;; Note: Given that the appeal of sparse checkouts is
;; dealing with very large trees, listing all subdirectories
;; may need to be reconsidered.
(magit-revision-directories "HEAD"))))
(magit-sparse-checkout--auto-enable)
(magit-run-git-async "sparse-checkout" "set" directories))
@@ -97,16 +97,16 @@ directories, call `magit-sparse-checkout-add' instead."
To override rather than extend the currently configured
directories, call `magit-sparse-checkout-set' instead."
(interactive
(list (magit-completing-read-multiple
"Add these directories: "
;; Same performance note as in `magit-sparse-checkout-set',
;; but even more so given the additional processing.
(seq-remove
(let ((re (concat
"\\`"
(regexp-opt (magit-sparse-checkout-directories)))))
(##string-match-p re %))
(magit-revision-directories "HEAD")))))
(list (magit-completing-read-multiple
"Add these directories: "
;; Same performance note as in `magit-sparse-checkout-set',
;; but even more so given the additional processing.
(seq-remove
(let ((re (concat
"\\`"
(regexp-opt (magit-sparse-checkout-directories)))))
(##string-match-p re %))
(magit-revision-directories "HEAD")))))
(magit-sparse-checkout--auto-enable)
(magit-run-git-async "sparse-checkout" "add" directories))
@@ -153,6 +153,7 @@ This header is not inserted by default. To enable it, add it to
;; ("and>" . "cond-let--and>")
;; ("and-let" . "cond-let--and-let")
;; ("if-let" . "cond-let--if-let")
;; ("when$" . "cond-let--when$")
;; ("when-let" . "cond-let--when-let")
;; ("while-let" . "cond-let--while-let")
;; ("match-string" . "match-string")

View File

@@ -1,6 +1,6 @@
;;; magit-stash.el --- Stash support for Magit -*- lexical-binding:t -*-
;; Copyright (C) 2008-2025 The Magit Project Contributors
;; Copyright (C) 2008-2026 The Magit Project Contributors
;; Author: Jonas Bernoulli <emacs.magit@jonas.bernoulli.dev>
;; Maintainer: Jonas Bernoulli <emacs.magit@jonas.bernoulli.dev>
@@ -127,13 +127,13 @@ Untracked files are included according to infix arguments.
One prefix argument is equivalent to `--include-untracked'
while two prefix arguments are equivalent to `--all'."
(interactive
(progn (when (and (magit-merge-in-progress-p)
(not (magit-y-or-n-p "\
(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. \
Proceed anyway? ")))
(user-error "Abort"))
(magit-stash-read-args)))
(user-error "Abort"))
(magit-stash-read-args)))
(magit-stash-save message t t include-untracked t))
;;;###autoload
@@ -370,9 +370,9 @@ want to fall back to using \"--3way\", without being prompted."
"Remove a stash from the stash list.
When the region is active offer to drop all contained stashes."
(interactive
(list (if-let ((values (magit-region-values 'stash)))
(magit-confirm 'drop-stashes nil "Drop %d stashes" nil values)
(magit-read-stash "Drop stash"))))
(list (if-let ((values (magit-region-values 'stash)))
(magit-confirm 'drop-stashes nil "Drop %d stashes" nil values)
(magit-read-stash "Drop stash"))))
(dolist (stash (if (listp stash)
(nreverse (prog1 stash (setq stash (car stash))))
(list stash)))
@@ -386,7 +386,8 @@ 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 (list "Drop all stashes in %s" ref))
(magit-confirm 'drop-stashes
(list "Drop all stashes in %s" ref))
(list ref)))
(magit-run-git "update-ref" "-d" ref))
@@ -619,7 +620,7 @@ See also info node `(magit)Section Movement'."
(defun magit-stash-setup-buffer (stash args files)
(magit-setup-buffer #'magit-stash-mode nil
(magit-buffer-revision stash)
(magit-buffer-range (format "%s^..%s" stash stash))
(magit-buffer-diff-range (format "%s^..%s" stash stash))
(magit-buffer-diff-args args)
(magit-buffer-diff-files files)))
@@ -630,7 +631,7 @@ See also info node `(magit)Section Movement'."
'font-lock-face
(list :weight 'normal :foreground
(face-attribute 'default :foreground)))))
(setq magit-buffer-revision-hash (magit-rev-parse magit-buffer-revision))
(setq magit-buffer-revision-oid (magit-commit-oid magit-buffer-revision))
(magit-insert-section (stash)
(magit-run-section-hook 'magit-stash-sections-hook)))
@@ -687,6 +688,7 @@ that make up the stash."
;; ("and>" . "cond-let--and>")
;; ("and-let" . "cond-let--and-let")
;; ("if-let" . "cond-let--if-let")
;; ("when$" . "cond-let--when$")
;; ("when-let" . "cond-let--when-let")
;; ("while-let" . "cond-let--while-let")
;; ("match-string" . "match-string")

View File

@@ -1,6 +1,6 @@
;;; magit-status.el --- The grand overview -*- lexical-binding:t -*-
;; Copyright (C) 2008-2025 The Magit Project Contributors
;; Copyright (C) 2008-2026 The Magit Project Contributors
;; Author: Jonas Bernoulli <emacs.magit@jonas.bernoulli.dev>
;; Maintainer: Jonas Bernoulli <emacs.magit@jonas.bernoulli.dev>
@@ -259,18 +259,18 @@ has to confirm that it should be reinitialized.
Non-interactively DIRECTORY is (re-)initialized unconditionally."
(interactive
(let ((directory (file-name-as-directory
(expand-file-name
(read-directory-name "Create repository in: ")))))
(when-let ((toplevel (magit-toplevel directory)))
(setq toplevel (expand-file-name toplevel))
(unless (y-or-n-p (if (file-equal-p toplevel directory)
(format "Reinitialize existing repository %s? "
directory)
(format "%s is a repository. Create another in %s? "
toplevel directory)))
(user-error "Abort")))
(list directory)))
(let ((directory (file-name-as-directory
(expand-file-name
(read-directory-name "Create repository in: ")))))
(when-let ((toplevel (magit-toplevel directory)))
(setq toplevel (expand-file-name toplevel))
(unless (y-or-n-p (if (file-equal-p toplevel directory)
(format "Reinitialize existing repository %s? "
directory)
(format "%s is a repository. Create another in %s? "
toplevel directory)))
(user-error "Abort")))
(list directory)))
;; `git init' does not understand the meaning of "~"!
(magit-call-git "init" (magit-convert-filename-for-git
(expand-file-name directory)))
@@ -310,12 +310,12 @@ prefix arguments:
then fall back to the same behavior as with two prefix
arguments."
(interactive
(let ((magit--refresh-cache (list (cons 0 0))))
(list (and (or current-prefix-arg (not (magit-toplevel)))
(progn (magit--assert-usable-git)
(magit-read-repository
(>= (prefix-numeric-value current-prefix-arg) 16))))
magit--refresh-cache)))
(let ((magit--refresh-cache (list (cons 0 0))))
(list (and (or current-prefix-arg (not (magit-toplevel)))
(progn (magit--assert-usable-git)
(magit-read-repository
(>= (prefix-numeric-value current-prefix-arg) 16))))
magit--refresh-cache)))
(let ((magit--refresh-cache (or cache (list (cons 0 0)))))
(if directory
(let ((toplevel (magit-toplevel directory)))
@@ -622,33 +622,33 @@ arguments are for internal use only."
(insert (format "%-10s" (or keyword (if rebase "Rebase: " "Merge: "))))
(insert
(cond
(upstream
(concat (and magit-status-show-hashes-in-headers
(concat (propertize (magit-rev-format "%h" upstream)
'font-lock-face 'magit-hash)
" "))
upstream " "
(magit-log--wash-summary
(or (magit-rev-format "%s" upstream)
"(no commit message)"))))
((magit--unnamed-upstream-p remote merge)
(concat (propertize merge 'font-lock-face 'magit-branch-remote)
" from "
(propertize remote 'font-lock-face 'bold)))
((magit--valid-upstream-p remote merge)
(if (equal remote ".")
(concat
(propertize merge 'font-lock-face 'magit-branch-local) " "
(propertize "does not exist"
'font-lock-face 'magit-branch-warning))
(format
"%s %s %s"
(propertize merge 'font-lock-face 'magit-branch-remote)
(propertize "does not exist on"
'font-lock-face 'magit-branch-warning)
(propertize remote 'font-lock-face 'magit-branch-remote))))
((propertize "invalid upstream configuration"
'font-lock-face 'magit-branch-warning))))
(upstream
(concat (and magit-status-show-hashes-in-headers
(concat (propertize (magit-rev-format "%h" upstream)
'font-lock-face 'magit-hash)
" "))
upstream " "
(magit-log--wash-summary
(or (magit-rev-format "%s" upstream)
"(no commit message)"))))
((magit--unnamed-upstream-p remote merge)
(concat (propertize merge 'font-lock-face 'magit-branch-remote)
" from "
(propertize remote 'font-lock-face 'bold)))
((magit--valid-upstream-p remote merge)
(if (equal remote ".")
(concat
(propertize merge 'font-lock-face 'magit-branch-local) " "
(propertize "does not exist"
'font-lock-face 'magit-branch-warning))
(format
"%s %s %s"
(propertize merge 'font-lock-face 'magit-branch-remote)
(propertize "does not exist on"
'font-lock-face 'magit-branch-warning)
(propertize remote 'font-lock-face 'magit-branch-remote))))
((propertize "invalid upstream configuration"
'font-lock-face 'magit-branch-warning))))
(insert ?\n))))))
(defun magit-insert-push-branch-header ()
@@ -826,6 +826,7 @@ Honor the buffer's file filter, which can be set using \"D - -\"."
;; ("and>" . "cond-let--and>")
;; ("and-let" . "cond-let--and-let")
;; ("if-let" . "cond-let--if-let")
;; ("when$" . "cond-let--when$")
;; ("when-let" . "cond-let--when-let")
;; ("while-let" . "cond-let--while-let")
;; ("match-string" . "match-string")

View File

@@ -1,6 +1,6 @@
;;; magit-submodule.el --- Submodule support for Magit -*- lexical-binding:t -*-
;; Copyright (C) 2008-2025 The Magit Project Contributors
;; Copyright (C) 2008-2026 The Magit Project Contributors
;; Author: Jonas Bernoulli <emacs.magit@jonas.bernoulli.dev>
;; Maintainer: Jonas Bernoulli <emacs.magit@jonas.bernoulli.dev>
@@ -216,13 +216,13 @@ it is nil, then PATH also becomes the name."
:class 'magit--git-submodule-suffix
:description "Add git submodule add [--force]"
(interactive
(magit-with-toplevel
(let* ((url (magit-read-string-ns "Add submodule (remote url)"))
(path (magit-submodule-read-path "Add submodules at path: " url)))
(list url
(directory-file-name path)
(magit-submodule-read-name-for-path path)
(magit-submodule-arguments "--force")))))
(magit-with-toplevel
(let* ((url (magit-read-string-ns "Add submodule (remote url)"))
(path (magit-submodule-read-path "Add submodules at path: " url)))
(list url
(directory-file-name path)
(magit-submodule-read-name-for-path path)
(magit-submodule-arguments "--force")))))
(magit-submodule-add-1 url path name args))
(defun magit-submodule-read-path (prompt url)
@@ -277,7 +277,7 @@ single module from the user."
;; the modules.
:description "Register git submodule init"
(interactive
(list (magit-module-confirm "Register" 'magit-module-no-worktree-p)))
(list (magit-module-confirm "Register" 'magit-module-no-worktree-p)))
(magit-with-toplevel
(magit-run-git-async "submodule" "init" "--" modules)))
@@ -295,8 +295,8 @@ single module from the user."
:class 'magit--git-submodule-suffix
:description "Populate git submodule update --init [--recursive]"
(interactive
(list (magit-module-confirm "Populate" 'magit-module-no-worktree-p)
(magit-submodule-arguments "--recursive")))
(list (magit-module-confirm "Populate" 'magit-module-no-worktree-p)
(magit-submodule-arguments "--recursive")))
(magit-with-toplevel
(magit-run-git-async "submodule" "update" "--init" args "--" modules)))
@@ -316,10 +316,10 @@ single module from the user."
:description "Update git submodule update [--force] [--no-fetch]
[--remote] [--recursive] [--checkout|--rebase|--merge]"
(interactive
(list (magit-module-confirm "Update" 'magit-module-worktree-p)
(magit-submodule-arguments
"--force" "--remote" "--recursive" "--checkout" "--rebase" "--merge"
"--no-fetch")))
(list (magit-module-confirm "Update" 'magit-module-worktree-p)
(magit-submodule-arguments
"--force" "--remote" "--recursive" "--checkout" "--rebase" "--merge"
"--no-fetch")))
(magit-with-toplevel
(magit-run-git-async "submodule" "update" args "--" modules)))
@@ -334,8 +334,8 @@ single module from the user."
:class 'magit--git-submodule-suffix
:description "Synchronize git submodule sync [--recursive]"
(interactive
(list (magit-module-confirm "Synchronize" 'magit-module-worktree-p)
(magit-submodule-arguments "--recursive")))
(list (magit-module-confirm "Synchronize" 'magit-module-worktree-p)
(magit-submodule-arguments "--recursive")))
(magit-with-toplevel
(magit-run-git-async "submodule" "sync" args "--" modules)))
@@ -357,8 +357,8 @@ single module from the user."
:class 'magit--git-submodule-suffix
:description "Unpopulate git submodule deinit [--force]"
(interactive
(list (magit-module-confirm "Unpopulate")
(magit-submodule-arguments "--force")))
(list (magit-module-confirm "Unpopulate")
(magit-submodule-arguments "--force")))
(magit-with-toplevel
(magit-run-git-async "submodule" "deinit" args "--" modules)))
@@ -377,11 +377,11 @@ Both actions are very dangerous and have to be confirmed. There
are additional safety precautions in place, so you might be able
to recover from making a mistake here, but don't count on it."
(interactive
(list (if-let ((modules (magit-region-values 'magit-module-section t)))
(magit-confirm 'remove-modules nil "Remove %d modules" nil modules)
(list (magit-read-module-path "Remove module")))
(magit-submodule-arguments "--force")
current-prefix-arg))
(list (if-let ((modules (magit-region-values 'magit-module-section t)))
(magit-confirm 'remove-modules nil "Remove %d modules" nil modules)
(list (magit-read-module-path "Remove module")))
(magit-submodule-arguments "--force")
current-prefix-arg))
(when magit-submodule-remove-trash-gitdirs
(setq trash-gitdirs t))
(magit-with-toplevel
@@ -539,20 +539,20 @@ With a prefix argument, visit in another window."
(magit-with-toplevel
(let ((path (expand-file-name module)))
(cond
((file-exists-p (expand-file-name ".git" module))
(magit-diff-visit-directory path other-window))
((y-or-n-p (format "Initialize submodule '%s' first?" module))
(magit-run-git-async "submodule" "update" "--init" "--" module)
(set-process-sentinel
magit-this-process
(lambda (process event)
(let ((magit-process-raise-error t))
(magit-process-sentinel process event))
(when (and (eq (process-status process) 'exit)
(= (process-exit-status process) 0))
(magit-diff-visit-directory path other-window)))))
((file-exists-p path)
(dired-jump other-window (concat path "/.")))))))
((file-exists-p (expand-file-name ".git" module))
(magit-diff-visit-directory path other-window))
((y-or-n-p (format "Initialize submodule '%s' first?" module))
(magit-run-git-async "submodule" "update" "--init" "--" module)
(set-process-sentinel
magit-this-process
(lambda (process event)
(let ((magit-process-raise-error t))
(magit-process-sentinel process event))
(when (and (eq (process-status process) 'exit)
(= (process-exit-status process) 0))
(magit-diff-visit-directory path other-window)))))
((file-exists-p path)
(dired-jump other-window (concat path "/.")))))))
;;;###autoload
(defun magit-insert-modules-unpulled-from-upstream ()
@@ -720,6 +720,7 @@ These sections can be expanded to show the respective commits."
;; ("and>" . "cond-let--and>")
;; ("and-let" . "cond-let--and-let")
;; ("if-let" . "cond-let--if-let")
;; ("when$" . "cond-let--when$")
;; ("when-let" . "cond-let--when-let")
;; ("while-let" . "cond-let--while-let")
;; ("match-string" . "match-string")

View File

@@ -1,6 +1,6 @@
;;; magit-subtree.el --- Subtree support for Magit -*- lexical-binding:t -*-
;; Copyright (C) 2008-2025 The Magit Project Contributors
;; Copyright (C) 2008-2026 The Magit Project Contributors
;; Author: Jonas Bernoulli <emacs.magit@jonas.bernoulli.dev>
;; Maintainer: Jonas Bernoulli <emacs.magit@jonas.bernoulli.dev>
@@ -129,40 +129,40 @@
(defun magit-subtree-add (prefix repository ref args)
"Add REF from REPOSITORY as a new subtree at PREFIX."
(interactive
(cons (magit-subtree-prefix 'magit-subtree-import "Add subtree")
(let ((remote (magit-read-remote-or-url "From repository")))
(list remote
(magit-read-refspec "Ref" remote)
(magit-subtree-arguments 'magit-subtree-import)))))
(cons (magit-subtree-prefix 'magit-subtree-import "Add subtree")
(let ((remote (magit-read-remote-or-url "From repository")))
(list remote
(magit-read-refspec "Ref" remote)
(magit-subtree-arguments 'magit-subtree-import)))))
(magit-git-subtree "add" prefix args repository ref))
;;;###autoload
(defun magit-subtree-add-commit (prefix commit args)
"Add COMMIT as a new subtree at PREFIX."
(interactive
(list (magit-subtree-prefix 'magit-subtree-import "Add subtree")
(magit-read-string-ns "Commit")
(magit-subtree-arguments 'magit-subtree-import)))
(list (magit-subtree-prefix 'magit-subtree-import "Add subtree")
(magit-read-string-ns "Commit")
(magit-subtree-arguments 'magit-subtree-import)))
(magit-git-subtree "add" prefix args commit))
;;;###autoload
(defun magit-subtree-merge (prefix commit args)
"Merge COMMIT into the PREFIX subtree."
(interactive
(list (magit-subtree-prefix 'magit-subtree-import "Merge into subtree")
(magit-read-string-ns "Commit")
(magit-subtree-arguments 'magit-subtree-import)))
(list (magit-subtree-prefix 'magit-subtree-import "Merge into subtree")
(magit-read-string-ns "Commit")
(magit-subtree-arguments 'magit-subtree-import)))
(magit-git-subtree "merge" prefix args commit))
;;;###autoload
(defun magit-subtree-pull (prefix repository ref args)
"Pull REF from REPOSITORY into the PREFIX subtree."
(interactive
(cons (magit-subtree-prefix 'magit-subtree-import "Pull into subtree")
(let ((remote (magit-read-remote-or-url "From repository")))
(list remote
(magit-read-refspec "Ref" remote)
(magit-subtree-arguments 'magit-subtree-import)))))
(cons (magit-subtree-prefix 'magit-subtree-import "Pull into subtree")
(let ((remote (magit-read-remote-or-url "From repository")))
(list remote
(magit-read-refspec "Ref" remote)
(magit-subtree-arguments 'magit-subtree-import)))))
(magit-git-subtree "pull" prefix args repository ref))
;;;###autoload
@@ -190,6 +190,7 @@
;; ("and>" . "cond-let--and>")
;; ("and-let" . "cond-let--and-let")
;; ("if-let" . "cond-let--if-let")
;; ("when$" . "cond-let--when$")
;; ("when-let" . "cond-let--when-let")
;; ("while-let" . "cond-let--while-let")
;; ("match-string" . "match-string")

View File

@@ -1,6 +1,6 @@
;;; magit-tag.el --- Tag functionality -*- lexical-binding:t -*-
;; Copyright (C) 2008-2025 The Magit Project Contributors
;; Copyright (C) 2008-2026 The Magit Project Contributors
;; Author: Jonas Bernoulli <emacs.magit@jonas.bernoulli.dev>
;; Maintainer: Jonas Bernoulli <emacs.magit@jonas.bernoulli.dev>
@@ -91,26 +91,26 @@ defaulting to the tag at point.
(defun magit-tag-prune (tags remote-tags remote)
"Offer to delete tags missing locally from REMOTE, and vice versa."
(interactive
(let* ((remote (magit-read-remote "Prune tags using remote"))
(tags (magit-list-tags))
(rtags (prog2 (message "Determining remote tags...")
(magit-remote-list-tags remote)
(message "Determining remote tags...done")))
(ltags (cl-set-difference tags rtags :test #'equal))
(rtags (cl-set-difference rtags tags :test #'equal)))
(unless (or ltags rtags)
(message "Same tags exist locally and remotely"))
(unless (magit-confirm t
"Delete %s locally"
"Delete %d tags locally"
'noabort ltags)
(setq ltags nil))
(unless (magit-confirm t
"Delete %s from remote"
"Delete %d tags from remote"
'noabort rtags)
(setq rtags nil))
(list ltags rtags remote)))
(let* ((remote (magit-read-remote "Prune tags using remote"))
(tags (magit-list-tags))
(rtags (prog2 (message "Determining remote tags...")
(magit-remote-list-tags remote)
(message "Determining remote tags...done")))
(ltags (cl-set-difference tags rtags :test #'equal))
(rtags (cl-set-difference rtags tags :test #'equal)))
(unless (or ltags rtags)
(message "Same tags exist locally and remotely"))
(unless (magit-confirm t
"Delete %s locally"
"Delete %d tags locally"
'noabort ltags)
(setq ltags nil))
(unless (magit-confirm t
"Delete %s from remote"
"Delete %d tags from remote"
'noabort rtags)
(setq rtags nil))
(list ltags rtags remote)))
(when tags
(magit-call-git "tag" "-d" tags))
(when remote-tags
@@ -161,52 +161,52 @@ 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*
((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-str 1 msg)))
(_ (and (not ver)
(require (quote sisyphus) nil t)
(string-match magit-release-commit-regexp
(magit-rev-format "%s" ptag))
(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)
ver)))
(ver
(concat (and (string-match magit-release-tag-regexp ptag)
(match-str 1 ptag))
ver))
((read-string (format "Create release tag (previous was %s): "
ptag)
ptag))))
(ver (and (string-match magit-release-tag-regexp tag)
(match-str 2 tag))))
(list tag
(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))))
(save-match-data
(pcase-let*
((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-str 1 msg)))
(_ (and (not ver)
(require (quote sisyphus) nil t)
(string-match magit-release-commit-regexp
(magit-rev-format "%s" ptag))
(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)
ver)))
(ver
(concat (and (string-match magit-release-tag-regexp ptag)
(match-str 1 ptag))
ver))
((read-string (format "Create release tag (previous was %s): "
ptag)
ptag))))
(ver (and (string-match magit-release-tag-regexp tag)
(match-str 2 tag))))
(list tag
(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-with-editor "tag" args (and msg (list "-m" msg)) tag)
(set-process-sentinel
magit-this-process
@@ -249,6 +249,7 @@ a tag qualifies as a release tag."
;; ("and>" . "cond-let--and>")
;; ("and-let" . "cond-let--and-let")
;; ("if-let" . "cond-let--if-let")
;; ("when$" . "cond-let--when$")
;; ("when-let" . "cond-let--when-let")
;; ("while-let" . "cond-let--while-let")
;; ("match-string" . "match-string")

View File

@@ -1,6 +1,6 @@
;;; magit-transient.el --- Support for transients -*- lexical-binding:t -*-
;; Copyright (C) 2008-2025 The Magit Project Contributors
;; Copyright (C) 2008-2026 The Magit Project Contributors
;; Author: Jonas Bernoulli <emacs.magit@jonas.bernoulli.dev>
;; Maintainer: Jonas Bernoulli <emacs.magit@jonas.bernoulli.dev>
@@ -38,7 +38,8 @@
(defclass magit--git-variable (transient-variable)
((scope :initarg :scope)
(global :initarg :global :initform nil)
(default :initarg :default :initform nil)))
(default :initarg :default :initform nil)
(accessible-format :initform "%i%k %d is %v")))
(defclass magit--git-variable:choices (magit--git-variable)
((choices :initarg :choices)
@@ -75,7 +76,9 @@
(oref obj scope)))
(arg (if (oref obj global) "--global" "--local")))
(oset obj variable variable)
(oset obj value (if (magit-get-boolean arg variable) "true" "false"))))
(oset obj value
(and (zerop (magit-process-git t "config" "--bool" arg variable))
(buffer-substring (point-min) (1- (point-max)))))))
;;;; Read
@@ -93,15 +96,18 @@
(when (functionp choices)
(setq choices (funcall choices)))
(cond-let
(current-prefix-arg
((or transient-prefer-reading-value current-prefix-arg)
(pcase-let*
((`(,fallback . ,choices)
((`(,unset . ,choices)
(magit--git-variable-list-choices obj))
(unset (or unset "(unset)"))
(choice (magit-completing-read
(format "Set `%s' to" (oref obj variable))
(if fallback (nconc choices (list fallback)) choices)
(nconc (mapcar #'magit--delete-text-properties choices)
(list (propertize unset 'face
'transient-inactive-value)))
nil t)))
(if (equal choice fallback) nil choice)))
(if (equal choice unset) nil choice)))
([value (oref obj value)]
(cadr (member value choices)))
((car choices)))))
@@ -167,36 +173,37 @@
'face 'transient-value)))
([default (oref obj default)]
[default (if (functionp default) (funcall default) default)]
(concat (propertize "default:" 'face 'transient-inactive-value)
(propertize default 'face 'transient-value)))
(if transient-prefer-reading-value
(format "unset, using default, which is %s"
(propertize default 'face 'transient-value))
(concat (propertize "default:" 'face 'transient-inactive-value)
(propertize default 'face 'transient-value))))
((propertize "unset" 'face 'transient-inactive-value))))
(cl-defmethod transient-format-value ((obj magit--git-variable:choices))
(pcase-let ((`(,fallback . ,choices) (magit--git-variable-list-choices obj)))
(concat
(propertize "[" 'face 'transient-inactive-value)
(mapconcat #'identity choices
(propertize "|" 'face 'transient-inactive-value))
(and fallback (propertize "|" 'face 'transient-inactive-value))
fallback
(propertize "]" 'face 'transient-inactive-value))))
(if transient-prefer-reading-value
(cl-call-next-method)
(pcase-let ((`(,fallback . ,choices) (magit--git-variable-list-choices obj)))
(concat
(propertize "[" 'face 'transient-inactive-value)
(mapconcat #'identity choices
(propertize "|" 'face 'transient-inactive-value))
(and fallback (propertize "|" 'face 'transient-inactive-value))
fallback
(propertize "]" 'face 'transient-inactive-value)))))
(defun magit--git-variable-list-choices (obj)
(let* ((variable (oref obj variable))
(choices (oref obj choices))
(globalp (oref obj global))
(value nil)
(global (magit-git-string "config" "--global" variable))
(value (oref obj value))
(global (and (not (oref obj global))
(magit-git-string "config" "--global" variable)))
(defaultp (oref obj default))
(default (if (functionp defaultp) (funcall defaultp obj) defaultp))
(fallback (oref obj fallback))
(fallback (and fallback
(and$ (magit-get fallback)
(concat fallback ":" $)))))
(if (not globalp)
(setq value (magit-git-string "config" "--local" variable))
(setq value global)
(setq global nil))
(when (functionp choices)
(setq choices (funcall choices)))
(cons (cond (global
@@ -236,6 +243,7 @@
;; ("and>" . "cond-let--and>")
;; ("and-let" . "cond-let--and-let")
;; ("if-let" . "cond-let--if-let")
;; ("when$" . "cond-let--when$")
;; ("when-let" . "cond-let--when-let")
;; ("while-let" . "cond-let--while-let")
;; ("match-string" . "match-string")

View File

@@ -1,6 +1,6 @@
;;; magit-version.el --- The Magit version you are using -*- lexical-binding:t -*-
(setq magit-version "4.4.2")
(setq magit-version "4.5.0")
(provide 'magit-version)

View File

@@ -1,6 +1,6 @@
;;; magit-wip.el --- Commit snapshots to work-in-progress refs -*- lexical-binding:t -*-
;; Copyright (C) 2008-2025 The Magit Project Contributors
;; Copyright (C) 2008-2026 The Magit Project Contributors
;; Author: Jonas Bernoulli <emacs.magit@jonas.bernoulli.dev>
;; Maintainer: Jonas Bernoulli <emacs.magit@jonas.bernoulli.dev>
@@ -70,6 +70,12 @@ Customize `magit-overriding-githook-directory' to enable use of
Git hooks."
:package-version '(magit . "2.90.0")
:group 'magit-wip
:set (lambda (symbol value)
(set-default-toplevel-value symbol value)
(when (bound-and-true-p magit-wip-mode)
(if (eq value 'immediately)
(add-hook 'git-commit-post-finish-hook #'magit-wip-commit)
(remove-hook 'git-commit-post-finish-hook #'magit-wip-commit))))
:type '(choice
(const :tag "Yes (safely, just in time)" t)
(const :tag "Yes (immediately, with race condition)" immediately)
@@ -104,29 +110,28 @@ buffer."
:package-version '(magit . "2.90.0")
:lighter magit-wip-mode-lighter
:global t
:set-after '(magit-wip-merge-branch)
(cond
(magit-wip-mode
(add-hook 'after-save-hook #'magit-wip-commit-buffer-file)
(add-hook 'magit-after-apply-functions #'magit-wip-commit)
(add-hook 'magit-before-change-functions #'magit-wip-commit)
(add-hook 'before-save-hook #'magit-wip-commit-initial-backup)
(add-hook 'magit-common-git-post-commit-functions #'magit-wip-post-commit)
(add-hook 'git-commit-post-finish-hook #'magit-wip-commit-post-editmsg))
(t
(remove-hook 'after-save-hook #'magit-wip-commit-buffer-file)
(remove-hook 'magit-after-apply-functions #'magit-wip-commit)
(remove-hook 'magit-before-change-functions #'magit-wip-commit)
(remove-hook 'before-save-hook #'magit-wip-commit-initial-backup)
(remove-hook 'magit-common-git-post-commit-functions #'magit-wip-post-commit)
(remove-hook 'git-commit-post-finish-hook #'magit-wip-commit-post-editmsg))))
(magit-wip-mode
(add-hook 'after-save-hook #'magit-wip-commit-buffer-file)
(add-hook 'magit-after-apply-functions #'magit-wip-commit)
(add-hook 'magit-before-change-functions #'magit-wip-commit)
(add-hook 'before-save-hook #'magit-wip-commit-initial-backup)
(add-hook 'magit-common-git-post-commit-functions #'magit-wip-post-commit)
(when (eq magit-wip-merge-branch 'immediately)
(add-hook 'git-commit-post-finish-hook #'magit-wip-commit)))
(t
(remove-hook 'after-save-hook #'magit-wip-commit-buffer-file)
(remove-hook 'magit-after-apply-functions #'magit-wip-commit)
(remove-hook 'magit-before-change-functions #'magit-wip-commit)
(remove-hook 'before-save-hook #'magit-wip-commit-initial-backup)
(remove-hook 'magit-common-git-post-commit-functions #'magit-wip-post-commit)
(remove-hook 'git-commit-post-finish-hook #'magit-wip-commit))))
(defun magit-wip-commit-buffer-file (&optional msg)
"Commit visited file to a worktree work-in-progress ref."
(interactive (list "save %s snapshot"))
(when (and (not magit--wip-inhibit-autosave)
buffer-file-name
(magit-inside-worktree-p t)
(magit-file-tracked-p buffer-file-name))
(when (magit-wip--commitable-p)
(magit-wip-commit-worktree
(magit-wip-get-ref)
(list buffer-file-name)
@@ -147,10 +152,7 @@ buffer."
(put 'magit-wip-buffer-backed-up 'permanent-local t)
(defun magit-wip-commit-initial-backup ()
(when (and (not magit-wip-buffer-backed-up)
buffer-file-name
(magit-inside-worktree-p t)
(magit-file-tracked-p buffer-file-name))
(when (magit-wip--commitable-p)
(let ((magit-save-repository-buffers nil))
(magit-wip-commit-buffer-file "autosave %s before save"))
(setq magit-wip-buffer-backed-up t)))
@@ -159,10 +161,6 @@ buffer."
(when (eq magit-wip-merge-branch 'githook)
(magit-wip-commit)))
(defun magit-wip-commit-post-editmsg ()
(when (eq magit-wip-merge-branch 'immediately)
(magit-wip-commit)))
;;; Core
(defun magit-wip-commit (&optional files msg)
@@ -189,24 +187,29 @@ commit message."
(defun magit-wip-commit-worktree (ref files msg)
(when (or (not files)
;; `update-index' will either ignore (before Git v2.32.0)
;; or fail when passed directories (relevant for the
;; untracked files code paths).
;; "git update-index" either ignores (before Git v2.32.0) or
;; fails, when passed directories. This is relevant for the
;; untracked files code paths.
(setq files (seq-remove #'file-directory-p files)))
(let* ((wipref (magit--wip-wtree-ref ref))
(parent (magit-wip-get-parent ref wipref))
(tree (magit-with-temp-index parent (list "--reset" "-i")
(if files
;; Note: `update-index' is used instead of `add'
;; because `add' will fail if a file is already
;; deleted in the temporary index.
(magit-wip--git "update-index" "--add" "--remove"
"--ignore-skip-worktree-entries"
"--" files)
(magit-with-toplevel
(magit-wip--git "add" "-u" ".")))
(magit-git-string "write-tree"))))
(magit-wip-update-wipref ref wipref tree parent files msg "worktree"))))
(tree (condition-case nil
(magit-with-temp-index parent (list "--reset" "-i")
(if files
;; Use "git update-index" instead of "git add"
;; because the latter fails if a file is already
;; deleted in the temporary index.
(magit-wip--git "update-index" "--add" "--remove"
"--ignore-skip-worktree-entries"
"--" files)
(magit-with-toplevel
(magit-wip--git "add" "-u" ".")))
(magit-git-string "write-tree"))
(error
(message "Index locked; no worktree wip commit created")))))
(when tree
(magit-wip-update-wipref ref wipref tree parent
files msg "worktree")))))
(defun magit-wip--git (&rest args)
(if magit-wip-debug
@@ -220,29 +223,29 @@ commit message."
(defun magit-wip-update-wipref (ref wipref tree parent files msg start-msg)
(cond
((and (not (equal parent wipref))
(or (not magit-wip-merge-branch)
(not (magit-rev-verify wipref))))
(setq start-msg (concat "start autosaving " start-msg))
(magit-wip--update-ref wipref start-msg
(magit-git-string "commit-tree" "--no-gpg-sign"
"-p" parent "-m" start-msg
(concat parent "^{tree}")))
(setq parent wipref))
((and magit-wip-merge-branch
(or (not (magit-rev-ancestor-p ref wipref))
(not (magit-rev-ancestor-p
(concat (magit-git-string "log" "--format=%H"
"-1" "--merges" wipref)
"^2")
ref))))
(setq start-msg (format "merge %s into %s" ref start-msg))
(magit-wip--update-ref wipref start-msg
(magit-git-string "commit-tree" "--no-gpg-sign"
"-p" wipref "-p" ref
"-m" start-msg
(concat ref "^{tree}")))
(setq parent wipref)))
((and (not (equal parent wipref))
(or (not magit-wip-merge-branch)
(not (magit-rev-verify wipref))))
(setq start-msg (concat "start autosaving " start-msg))
(magit-wip--update-ref wipref start-msg
(magit-git-string "commit-tree" "--no-gpg-sign"
"-p" parent "-m" start-msg
(concat parent "^{tree}")))
(setq parent wipref))
((and magit-wip-merge-branch
(or (not (magit-rev-ancestor-p ref wipref))
(not (magit-rev-ancestor-p
(concat (magit-git-string "log" "--format=%H"
"-1" "--merges" wipref)
"^2")
ref))))
(setq start-msg (format "merge %s into %s" ref start-msg))
(magit-wip--update-ref wipref start-msg
(magit-git-string "commit-tree" "--no-gpg-sign"
"-p" wipref "-p" ref
"-m" start-msg
(concat ref "^{tree}")))
(setq parent wipref)))
(when (magit-git-failure "diff-tree" "--quiet" parent tree "--" files)
(unless (and msg (not (= (aref msg 0) ?\s)))
(let ((len (length files)))
@@ -289,6 +292,13 @@ commit message."
(concat "refs/heads/" branch))
"HEAD")))
(defun magit-wip--commitable-p ()
(and (not magit--wip-inhibit-autosave)
buffer-file-name
(magit-inside-worktree-p t)
(magit-file-tracked-p buffer-file-name)
(magit-wip-get-ref)))
;;; Log
(defun magit-wip-log-index (args files)
@@ -307,9 +317,9 @@ With a negative prefix argument only show the worktree wip ref.
The absolute numeric value of the prefix argument controls how
many \"branches\" of each wip ref are shown."
(interactive
(nconc (list (or (magit-get-current-branch) "HEAD"))
(magit-log-arguments)
(list (prefix-numeric-value current-prefix-arg))))
(nconc (list (or (magit-get-current-branch) "HEAD"))
(magit-log-arguments)
(list (prefix-numeric-value current-prefix-arg))))
(magit-wip-log branch args files count))
(defun magit-wip-log (branch args files count)
@@ -318,16 +328,16 @@ With a negative prefix argument only show the worktree wip ref.
The absolute numeric value of the prefix argument controls how
many \"branches\" of each wip ref are shown."
(interactive
(nconc (list (magit-completing-read
"Log branch and its wip refs"
(nconc (magit-list-local-branch-names)
(list "HEAD"))
nil t nil 'magit-revision-history
(or (magit-branch-at-point)
(magit-get-current-branch)
"HEAD")))
(magit-log-arguments)
(list (prefix-numeric-value current-prefix-arg))))
(nconc (list (magit-completing-read
"Log branch and its wip refs"
(nconc (magit-list-local-branch-names)
(list "HEAD"))
nil t nil 'magit-revision-history
(or (magit-branch-at-point)
(magit-get-current-branch)
"HEAD")))
(magit-log-arguments)
(list (prefix-numeric-value current-prefix-arg))))
(magit-log-setup-buffer (nconc (list branch)
(magit-wip-log-get-tips
(magit--wip-wtree-ref branch)
@@ -383,6 +393,7 @@ many \"branches\" of each wip ref are shown."
;; ("and>" . "cond-let--and>")
;; ("and-let" . "cond-let--and-let")
;; ("if-let" . "cond-let--if-let")
;; ("when$" . "cond-let--when$")
;; ("when-let" . "cond-let--when-let")
;; ("while-let" . "cond-let--while-let")
;; ("match-string" . "match-string")

View File

@@ -1,6 +1,6 @@
;;; magit-worktree.el --- Worktree support -*- lexical-binding:t -*-
;; Copyright (C) 2008-2025 The Magit Project Contributors
;; Copyright (C) 2008-2026 The Magit Project Contributors
;; Author: Jonas Bernoulli <emacs.magit@jonas.bernoulli.dev>
;; Maintainer: Jonas Bernoulli <emacs.magit@jonas.bernoulli.dev>
@@ -143,11 +143,11 @@ just \"PREFIX_\". Always forward PROMPT as-is."
COMMIT may, but does not have to be, a local branch.
Interactively, use `magit-read-worktree-directory-function'."
(interactive
(let ((commit (magit-read-branch-or-commit
"In new worktree; checkout" nil
(mapcar #'caddr (magit-list-worktrees)))))
(list (magit--read-worktree-directory commit (magit-local-branch-p commit))
commit)))
(let ((commit (magit-read-branch-or-commit
"In new worktree; checkout" nil
(mapcar #'caddr (magit-list-worktrees)))))
(list (magit--read-worktree-directory commit (magit-local-branch-p commit))
commit)))
(when (zerop (magit-run-git "worktree" "add"
(magit--expand-worktree directory) commit))
(magit-diff-visit-directory directory)))
@@ -157,11 +157,11 @@ Interactively, use `magit-read-worktree-directory-function'."
"Create a new BRANCH and check it out in a new worktree at DIRECTORY.
Interactively, use `magit-read-worktree-directory-function'."
(interactive
(pcase-let
((`(,branch ,start-point)
(magit-branch-read-args "In new worktree; checkout new branch")))
(list (magit--read-worktree-directory branch t)
branch start-point)))
(pcase-let
((`(,branch ,start-point)
(magit-branch-read-args "In new worktree; checkout new branch")))
(list (magit--read-worktree-directory branch t)
branch start-point)))
(when (zerop (magit-run-git "worktree" "add" "-b" branch
(magit--expand-worktree directory) start-point))
(magit-diff-visit-directory directory)))
@@ -170,11 +170,11 @@ Interactively, use `magit-read-worktree-directory-function'."
(defun magit-worktree-move (worktree directory)
"Move existing WORKTREE directory to DIRECTORY."
(interactive
(list (magit-completing-read "Move worktree"
(cdr (magit-list-worktrees))
nil t nil nil
(magit-section-value-if 'worktree))
(read-directory-name "Move worktree to: ")))
(list (magit-completing-read "Move worktree"
(cdr (magit-list-worktrees))
nil t nil nil
(magit-section-value-if 'worktree))
(read-directory-name "Move worktree to: ")))
(if (file-directory-p (expand-file-name ".git" worktree))
(user-error "You may not move the main working tree")
(let ((preexisting-directory (file-directory-p directory)))
@@ -194,18 +194,31 @@ Interactively, use `magit-read-worktree-directory-function'."
"Delete a worktree, defaulting to the worktree at point.
The primary worktree cannot be deleted."
(interactive
(list (magit-completing-read "Delete worktree"
(mapcar #'car (cdr (magit-list-worktrees)))
nil t nil nil
(magit-section-value-if 'worktree))))
(list (magit-completing-read "Delete worktree"
(mapcar #'car (cdr (magit-list-worktrees)))
nil t nil nil
(magit-section-value-if 'worktree))))
(if (file-directory-p (expand-file-name ".git" worktree))
(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))
(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)))
(let (uncommitted)
(magit-confirm
(cond ((let ((default-directory worktree))
(or (magit-anything-modified-p)
(magit-untracked-files)))
(setq uncommitted 'danger))
(magit-delete-by-moving-to-trash 'trash)
('delete))
(format "%s worktree \"%s\"%s"
(if magit-delete-by-moving-to-trash "Trash" "Delete")
(file-name-nondirectory (directory-file-name worktree))
(if uncommitted " despite uncommitted changes" ""))
nil nil (list worktree)))
(if magit-delete-by-moving-to-trash
(let ((delete-by-moving-to-trash t))
(delete-directory worktree t t))
(magit-call-git "worktree" "remove" "--force" worktree)))
(if (file-exists-p default-directory)
(magit-run-git "worktree" "prune")
(let ((default-directory primary))
@@ -221,13 +234,13 @@ minibuffer. If the worktree at point is the one whose
status is already being displayed in the current buffer,
then show it in Dired instead."
(interactive
(list (or (magit-section-value-if 'worktree)
(magit-completing-read
"Show status for worktree"
(cl-delete (directory-file-name (magit-toplevel))
(magit-list-worktrees)
:test #'equal :key #'car)
nil t))))
(list (or (magit-section-value-if 'worktree)
(magit-completing-read
"Show status for worktree"
(cl-delete (directory-file-name (magit-toplevel))
(magit-list-worktrees)
:test #'equal :key #'car)
nil t))))
(magit-diff-visit-directory worktree))
(defun magit--expand-worktree (directory)
@@ -254,18 +267,21 @@ If there is only one worktree, then insert nothing."
(let* ((cols
(mapcar
(lambda (config)
(pcase-let ((`(,_ ,commit ,branch ,bare) config))
(pcase-let ((`(,directory ,commit ,branch ,bare) config))
(cons (cond
(branch
(propertize
branch 'font-lock-face
(if (equal branch (magit-get-current-branch))
'magit-branch-current
'magit-branch-local)))
(commit
(propertize (magit-rev-abbrev commit)
'font-lock-face 'magit-hash))
(bare "(bare)"))
(branch
(propertize
branch 'font-lock-face
(if (equal branch (magit-get-current-branch))
'magit-branch-current
'magit-branch-local)))
(commit
(propertize
(magit-rev-abbrev commit) 'font-lock-face
(if (file-equal-p default-directory directory)
'(magit-hash magit-branch-current)
'magit-hash)))
(bare "(bare)"))
config)))
worktrees))
(align (1+ (apply #'max (mapcar (##string-width (car %)) cols)))))
@@ -300,6 +316,7 @@ with padding for alignment."
;; ("and>" . "cond-let--and>")
;; ("and-let" . "cond-let--and-let")
;; ("if-let" . "cond-let--if-let")
;; ("when$" . "cond-let--when$")
;; ("when-let" . "cond-let--when-let")
;; ("while-let" . "cond-let--while-let")
;; ("match-string" . "match-string")

View File

@@ -1,6 +1,6 @@
;;; magit.el --- A Git porcelain inside Emacs -*- lexical-binding:t; coding:utf-8 -*-
;; Copyright (C) 2008-2025 The Magit Project Contributors
;; Copyright (C) 2008-2026 The Magit Project Contributors
;; Author: Marius Vollmer <marius.vollmer@gmail.com>
;; Jonas Bernoulli <emacs.magit@jonas.bernoulli.dev>
@@ -17,16 +17,16 @@
;; Homepage: https://github.com/magit/magit
;; Keywords: git tools vc
;; Package-Version: 20251217.1836
;; Package-Revision: 655bc502a3bd
;; Package-Version: 20260401.2251
;; Package-Revision: 6db34dc77d10
;; Package-Requires: (
;; (emacs "28.1")
;; (compat "30.1")
;; (cond-let "0.1")
;; (cond-let "0.2")
;; (llama "1.0")
;; (magit-section "4.4")
;; (magit-section "4.5")
;; (seq "2.24")
;; (transient "0.10")
;; (transient "0.12")
;; (with-editor "3.4"))
;; SPDX-License-Identifier: GPL-3.0-or-later
@@ -805,6 +805,7 @@ For X11 something like ~/.xinitrc should work.\n"
;; ("and>" . "cond-let--and>")
;; ("and-let" . "cond-let--and-let")
;; ("if-let" . "cond-let--if-let")
;; ("when$" . "cond-let--when$")
;; ("when-let" . "cond-let--when-let")
;; ("while-let" . "cond-let--while-let")
;; ("match-string" . "match-string")

File diff suppressed because it is too large Load Diff