From 0ca70400965a000db0ab5b22d455d006e0b43b03 Mon Sep 17 00:00:00 2001 From: Jeremy Bryant Date: Sun, 8 Feb 2026 23:07:33 +0000 Subject: [PATCH] Add option to keep previous package versions on upgrade This change adds a conditional to keep old package versions on upgrade. This allows users to keep running multiple Emacsen without a package upgrade in one Emacs breaking another running Emacs. * lisp/emacs-lisp/package.el (package-upgrade-keep-previous): New boolean user option. (package-upgrade): Use it. (package-menu--mark-upgrades-1): Use it. (package-menu--find-upgrades): Make consistent with new option. * etc/NEWS: Announce it. (Bug#79957) --- etc/NEWS | 5 +++++ lisp/emacs-lisp/package.el | 41 +++++++++++++++++--------------------- 2 files changed, 23 insertions(+), 23 deletions(-) diff --git a/etc/NEWS b/etc/NEWS index 7367e3ccbd9..4662fea58b5 100644 --- a/etc/NEWS +++ b/etc/NEWS @@ -673,6 +673,11 @@ will change its appearance if the tab buffer has been modified. This user option controls which buffers should appear in the tab line. By default, this is set to not filter the buffers. +*** New user option 'package-upgrade-keep-previous'. +This user option controls whether to keep previous packages versions +on upgrade. By default, this is set to nil, to keep the previous +behavior. + ** Project --- diff --git a/lisp/emacs-lisp/package.el b/lisp/emacs-lisp/package.el index 0536f45f911..ea04a648adb 100644 --- a/lisp/emacs-lisp/package.el +++ b/lisp/emacs-lisp/package.el @@ -427,6 +427,11 @@ synchronously." :type 'natnum :version "28.1") +(defcustom package-upgrade-keep-previous nil + "If non-nil, keep previous packages versions on upgrade." + :type 'boolean + :version "31.1") + ;;; `package-desc' object definition ;; This is the struct used internally to represent packages. @@ -2082,7 +2087,7 @@ NAME should be a symbol." ;; An active built-in has never been "selected" ;; before. Mark it as installed explicitly. (and pkg-desc 'dont-select)) - (when pkg-desc + (when (and (not package-upgrade-keep-previous) pkg-desc) (package-delete pkg-desc 'force 'dont-unselect)))))) (defun package--upgradeable-packages (&optional include-builtins) @@ -3846,27 +3851,16 @@ consideration." The alist has the same form as `package-alist', namely a list of elements of the form (PKG . DESCS), but where DESCS is the `package-desc' object corresponding to the newer version." - (let (installed available upgrades) - ;; Build list of installed/available packages in this buffer. - (dolist (entry tabulated-list-entries) - ;; ENTRY is (PKG-DESC [NAME VERSION STATUS DOC]) - (let ((pkg-desc (car entry)) - (status (aref (cadr entry) 2))) - (cond ((member status '("installed" "dependency" "unsigned" "external" "built-in")) - (push pkg-desc installed)) - ((member status '("available" "new")) - (setq available (package--append-to-alist pkg-desc available)))))) - ;; Loop through list of installed packages, finding upgrades. - (dolist (pkg-desc installed) - (let* ((name (package-desc-name pkg-desc)) - (avail-pkg (cadr (assq name available)))) - (and avail-pkg - (version-list-< (package-desc-priority-version pkg-desc) - (package-desc-priority-version avail-pkg)) - (or (not (package--active-built-in-p pkg-desc)) - package-install-upgrade-built-in) - (push (cons name avail-pkg) upgrades)))) - upgrades)) + (mapcar + (lambda (pkg-name) + (cons pkg-name + (seq-find + (let ((curr (package-desc-version + (cadr (assq pkg-name package-alist))))) + (lambda (pkg-desc) + (version-list-< curr (package-desc-version pkg-desc)))) + (cdr (assq pkg-name package-archive-contents))))) + (package--upgradeable-packages))) (defvar package-menu--mark-upgrades-pending nil "Whether mark-upgrades is waiting for a refresh to finish.") @@ -3889,7 +3883,8 @@ Implementation of `package-menu-mark-upgrades'." ((equal pkg-desc upgrade) (package-menu-mark-install)) (t - (package-menu-mark-delete)))))) + (when (not package-upgrade-keep-previous) + (package-menu-mark-delete))))))) (message "Packages marked for upgrading: %d" (length upgrades)))))