emacs: Add interface for comparing generations.
Suggested by Ludovic Courtès. * doc/emacs.texi (Emacs List buffer): Document new key bindings. * emacs/guix-base.el (guix-generation-packages-buffer-name-function, guix-generation-packages-update-buffer, guix-output-name-width): New variables. (guix-generation-file, guix-manifest-file, guix-generation-packages, guix-generation-packages-buffer-name-default, guix-generation-packages-buffer-name-long, guix-generation-packages-buffer-name, guix-generation-packages-buffer, guix-generation-insert-package, guix-generation-insert-packages, guix-profile-generation-manifest-file, guix-profile-generation-packages-buffer): New procedures. * emacs/guix-list.el: Add key bindings for comparing generations. (guix-generation-list-generations-to-compare, guix-generation-list-show-added-packages, guix-generation-list-show-removed-packages, guix-generation-list-compare, guix-generation-list-ediff-manifests, guix-generation-list-diff-manifests, guix-generation-list-ediff-packages, guix-generation-list-diff-packages, guix-generation-list-ediff, guix-generation-list-diff): New procedures. * emacs/guix-messages.el (guix-messages): Add 'generation-diff' search type. (guix-message-outputs-by-diff): New procedure. * emacs/guix-utils.el (guix-diff-switches): New variable. (guix-diff): New procedure. * emacs/guix-main.scm (package/output-sexps): Handle 'generation-diff' search type. (manifest-entry->package-specification, manifest-entries->package-specifications, generation-package-specifications, generation-package-specifications+paths, generation-difference): New procedures.
This commit is contained in:
parent
62f261d88c
commit
d38bd08c74
|
@ -239,6 +239,21 @@ Mark the current generation for deletion (with prefix, mark all
|
||||||
generations).
|
generations).
|
||||||
@item x
|
@item x
|
||||||
Execute actions on the marked generations---i.e., delete generations.
|
Execute actions on the marked generations---i.e., delete generations.
|
||||||
|
@item e
|
||||||
|
Run Ediff (@pxref{Top,,, ediff, The Ediff Manual}) on package outputs
|
||||||
|
installed in the 2 marked generations. With prefix argument, run Ediff
|
||||||
|
on manifests of the marked generations.
|
||||||
|
@item D
|
||||||
|
@itemx =
|
||||||
|
Run Diff (@pxref{Diff Mode,,, emacs, The GNU Emacs Manual}) on package
|
||||||
|
outputs installed in the 2 marked generations. With prefix argument,
|
||||||
|
run Diff on manifests of the marked generations.
|
||||||
|
@item +
|
||||||
|
List package outputs added to the latest marked generation comparing
|
||||||
|
with another marked generation.
|
||||||
|
@item -
|
||||||
|
List package outputs removed from the latest marked generation comparing
|
||||||
|
with another marked generation.
|
||||||
@end table
|
@end table
|
||||||
|
|
||||||
@node Emacs Info buffer
|
@node Emacs Info buffer
|
||||||
|
|
|
@ -649,6 +649,117 @@ This function will not update the information, use
|
||||||
(guix-result-message guix-profile guix-entries guix-entry-type
|
(guix-result-message guix-profile guix-entries guix-entry-type
|
||||||
guix-search-type guix-search-vals))
|
guix-search-type guix-search-vals))
|
||||||
|
|
||||||
|
|
||||||
|
;;; Generations
|
||||||
|
|
||||||
|
(defcustom guix-generation-packages-buffer-name-function
|
||||||
|
#'guix-generation-packages-buffer-name-default
|
||||||
|
"Function used to define name of a buffer with generation packages.
|
||||||
|
This function is called with 2 arguments: PROFILE (string) and
|
||||||
|
GENERATION (number)."
|
||||||
|
:type '(choice (function-item guix-generation-packages-buffer-name-default)
|
||||||
|
(function-item guix-generation-packages-buffer-name-long)
|
||||||
|
(function :tag "Other function"))
|
||||||
|
:group 'guix)
|
||||||
|
|
||||||
|
(defcustom guix-generation-packages-update-buffer t
|
||||||
|
"If non-nil, always update list of packages during comparing generations.
|
||||||
|
If nil, generation packages are received only once. So when you
|
||||||
|
compare generation 1 and generation 2, the packages for both
|
||||||
|
generations will be received. Then if you compare generation 1
|
||||||
|
and generation 3, only the packages for generation 3 will be
|
||||||
|
received. Thus if you use comparing of different generations a
|
||||||
|
lot, you may set this variable to nil to improve the
|
||||||
|
performance."
|
||||||
|
:type 'boolean
|
||||||
|
:group 'guix)
|
||||||
|
|
||||||
|
(defvar guix-output-name-width 30
|
||||||
|
"Width of an output name \"column\".
|
||||||
|
This variable is used in auxiliary buffers for comparing generations.")
|
||||||
|
|
||||||
|
(defun guix-generation-file (profile generation)
|
||||||
|
"Return the file name of a PROFILE's GENERATION."
|
||||||
|
(format "%s-%s-link" profile generation))
|
||||||
|
|
||||||
|
(defun guix-manifest-file (profile &optional generation)
|
||||||
|
"Return the file name of a PROFILE's manifest.
|
||||||
|
If GENERATION number is specified, return manifest file name for
|
||||||
|
this generation."
|
||||||
|
(expand-file-name "manifest"
|
||||||
|
(if generation
|
||||||
|
(guix-generation-file profile generation)
|
||||||
|
profile)))
|
||||||
|
|
||||||
|
(defun guix-generation-packages (profile generation)
|
||||||
|
"Return a list of sorted packages installed in PROFILE's GENERATION.
|
||||||
|
Each element of the list is a list of the package specification and its path."
|
||||||
|
(let ((names+paths (guix-eval-read
|
||||||
|
(guix-make-guile-expression
|
||||||
|
'generation-package-specifications+paths
|
||||||
|
profile generation))))
|
||||||
|
(sort names+paths
|
||||||
|
(lambda (a b)
|
||||||
|
(string< (car a) (car b))))))
|
||||||
|
|
||||||
|
(defun guix-generation-packages-buffer-name-default (profile generation)
|
||||||
|
"Return name of a buffer for displaying GENERATION's package outputs.
|
||||||
|
Use base name of PROFILE path."
|
||||||
|
(let ((profile-name (file-name-base (directory-file-name profile))))
|
||||||
|
(format "*Guix %s: generation %s*"
|
||||||
|
profile-name generation)))
|
||||||
|
|
||||||
|
(defun guix-generation-packages-buffer-name-long (profile generation)
|
||||||
|
"Return name of a buffer for displaying GENERATION's package outputs.
|
||||||
|
Use the full PROFILE path."
|
||||||
|
(format "*Guix generation %s (%s)*"
|
||||||
|
generation profile))
|
||||||
|
|
||||||
|
(defun guix-generation-packages-buffer-name (profile generation)
|
||||||
|
"Return name of a buffer for displaying GENERATION's package outputs."
|
||||||
|
(let ((fun (if (functionp guix-generation-packages-buffer-name-function)
|
||||||
|
guix-generation-packages-buffer-name-function
|
||||||
|
#'guix-generation-packages-buffer-name-default)))
|
||||||
|
(funcall fun profile generation)))
|
||||||
|
|
||||||
|
(defun guix-generation-insert-package (name path)
|
||||||
|
"Insert package output NAME and PATH at point."
|
||||||
|
(insert name)
|
||||||
|
(indent-to guix-output-name-width 2)
|
||||||
|
(insert path "\n"))
|
||||||
|
|
||||||
|
(defun guix-generation-insert-packages (buffer profile generation)
|
||||||
|
"Insert package outputs installed in PROFILE's GENERATION in BUFFER."
|
||||||
|
(with-current-buffer buffer
|
||||||
|
(setq buffer-read-only nil
|
||||||
|
indent-tabs-mode nil)
|
||||||
|
(erase-buffer)
|
||||||
|
(mapc (lambda (name+path)
|
||||||
|
(guix-generation-insert-package
|
||||||
|
(car name+path) (cadr name+path)))
|
||||||
|
(guix-generation-packages profile generation))))
|
||||||
|
|
||||||
|
(defun guix-generation-packages-buffer (profile generation)
|
||||||
|
"Return buffer with package outputs installed in PROFILE's GENERATION.
|
||||||
|
Create the buffer if needed."
|
||||||
|
(let ((buf-name (guix-generation-packages-buffer-name
|
||||||
|
profile generation)))
|
||||||
|
(or (and (null guix-generation-packages-update-buffer)
|
||||||
|
(get-buffer buf-name))
|
||||||
|
(let ((buf (get-buffer-create buf-name)))
|
||||||
|
(guix-generation-insert-packages buf profile generation)
|
||||||
|
buf))))
|
||||||
|
|
||||||
|
(defun guix-profile-generation-manifest-file (generation)
|
||||||
|
"Return the file name of a GENERATION's manifest.
|
||||||
|
GENERATION is a generation number of `guix-profile' profile."
|
||||||
|
(guix-manifest-file guix-profile generation))
|
||||||
|
|
||||||
|
(defun guix-profile-generation-packages-buffer (generation)
|
||||||
|
"Insert GENERATION's package outputs in a buffer and return it.
|
||||||
|
GENERATION is a generation number of `guix-profile' profile."
|
||||||
|
(guix-generation-packages-buffer guix-profile generation))
|
||||||
|
|
||||||
|
|
||||||
;;; Actions on packages and generations
|
;;; Actions on packages and generations
|
||||||
|
|
||||||
|
|
|
@ -27,7 +27,6 @@
|
||||||
(require 'cl-lib)
|
(require 'cl-lib)
|
||||||
(require 'tabulated-list)
|
(require 'tabulated-list)
|
||||||
(require 'guix-info)
|
(require 'guix-info)
|
||||||
(require 'guix-history)
|
|
||||||
(require 'guix-base)
|
(require 'guix-base)
|
||||||
(require 'guix-utils)
|
(require 'guix-utils)
|
||||||
|
|
||||||
|
@ -735,6 +734,11 @@ Also see `guix-package-info-type'."
|
||||||
|
|
||||||
(let ((map guix-generation-list-mode-map))
|
(let ((map guix-generation-list-mode-map))
|
||||||
(define-key map (kbd "RET") 'guix-generation-list-show-packages)
|
(define-key map (kbd "RET") 'guix-generation-list-show-packages)
|
||||||
|
(define-key map (kbd "+") 'guix-generation-list-show-added-packages)
|
||||||
|
(define-key map (kbd "-") 'guix-generation-list-show-removed-packages)
|
||||||
|
(define-key map (kbd "=") 'guix-generation-list-diff)
|
||||||
|
(define-key map (kbd "D") 'guix-generation-list-diff)
|
||||||
|
(define-key map (kbd "e") 'guix-generation-list-ediff)
|
||||||
(define-key map (kbd "x") 'guix-generation-list-execute)
|
(define-key map (kbd "x") 'guix-generation-list-execute)
|
||||||
(define-key map (kbd "i") 'guix-list-describe)
|
(define-key map (kbd "i") 'guix-list-describe)
|
||||||
(define-key map (kbd "s") 'guix-generation-list-switch)
|
(define-key map (kbd "s") 'guix-generation-list-switch)
|
||||||
|
@ -761,6 +765,85 @@ VAL is a boolean value."
|
||||||
(guix-get-show-entries guix-profile 'list guix-package-list-type
|
(guix-get-show-entries guix-profile 'list guix-package-list-type
|
||||||
'generation (guix-list-current-id)))
|
'generation (guix-list-current-id)))
|
||||||
|
|
||||||
|
(defun guix-generation-list-generations-to-compare ()
|
||||||
|
"Return a sorted list of 2 marked generations for comparing."
|
||||||
|
(let ((numbers (guix-list-get-marked-id-list 'general)))
|
||||||
|
(if (/= (length numbers) 2)
|
||||||
|
(user-error "2 generations should be marked for comparing")
|
||||||
|
(sort numbers #'<))))
|
||||||
|
|
||||||
|
(defun guix-generation-list-show-added-packages ()
|
||||||
|
"List package outputs added to the latest marked generation.
|
||||||
|
If 2 generations are marked with \\[guix-list-mark], display
|
||||||
|
outputs installed in the latest marked generation that were not
|
||||||
|
installed in the other one."
|
||||||
|
(interactive)
|
||||||
|
(apply #'guix-get-show-entries
|
||||||
|
guix-profile 'list 'output 'generation-diff
|
||||||
|
(reverse (guix-generation-list-generations-to-compare))))
|
||||||
|
|
||||||
|
(defun guix-generation-list-show-removed-packages ()
|
||||||
|
"List package outputs removed from the latest marked generation.
|
||||||
|
If 2 generations are marked with \\[guix-list-mark], display
|
||||||
|
outputs not installed in the latest marked generation that were
|
||||||
|
installed in the other one."
|
||||||
|
(interactive)
|
||||||
|
(apply #'guix-get-show-entries
|
||||||
|
guix-profile 'list 'output 'generation-diff
|
||||||
|
(guix-generation-list-generations-to-compare)))
|
||||||
|
|
||||||
|
(defun guix-generation-list-compare (diff-fun gen-fun)
|
||||||
|
"Run GEN-FUN on the 2 marked generations and run DIFF-FUN on the results."
|
||||||
|
(cl-multiple-value-bind (gen1 gen2)
|
||||||
|
(guix-generation-list-generations-to-compare)
|
||||||
|
(funcall diff-fun
|
||||||
|
(funcall gen-fun gen1)
|
||||||
|
(funcall gen-fun gen2))))
|
||||||
|
|
||||||
|
(defun guix-generation-list-ediff-manifests ()
|
||||||
|
"Run Ediff on manifests of the 2 marked generations."
|
||||||
|
(interactive)
|
||||||
|
(guix-generation-list-compare
|
||||||
|
#'ediff-files
|
||||||
|
#'guix-profile-generation-manifest-file))
|
||||||
|
|
||||||
|
(defun guix-generation-list-diff-manifests ()
|
||||||
|
"Run Diff on manifests of the 2 marked generations."
|
||||||
|
(interactive)
|
||||||
|
(guix-generation-list-compare
|
||||||
|
#'guix-diff
|
||||||
|
#'guix-profile-generation-manifest-file))
|
||||||
|
|
||||||
|
(defun guix-generation-list-ediff-packages ()
|
||||||
|
"Run Ediff on package outputs installed in the 2 marked generations."
|
||||||
|
(interactive)
|
||||||
|
(guix-generation-list-compare
|
||||||
|
#'ediff-buffers
|
||||||
|
#'guix-profile-generation-packages-buffer))
|
||||||
|
|
||||||
|
(defun guix-generation-list-diff-packages ()
|
||||||
|
"Run Diff on package outputs installed in the 2 marked generations."
|
||||||
|
(interactive)
|
||||||
|
(guix-generation-list-compare
|
||||||
|
#'guix-diff
|
||||||
|
#'guix-profile-generation-packages-buffer))
|
||||||
|
|
||||||
|
(defun guix-generation-list-ediff (arg)
|
||||||
|
"Run Ediff on package outputs installed in the 2 marked generations.
|
||||||
|
With ARG, run Ediff on manifests of the marked generations."
|
||||||
|
(interactive "P")
|
||||||
|
(if arg
|
||||||
|
(guix-generation-list-ediff-manifests)
|
||||||
|
(guix-generation-list-ediff-packages)))
|
||||||
|
|
||||||
|
(defun guix-generation-list-diff (arg)
|
||||||
|
"Run Diff on package outputs installed in the 2 marked generations.
|
||||||
|
With ARG, run Diff on manifests of the marked generations."
|
||||||
|
(interactive "P")
|
||||||
|
(if arg
|
||||||
|
(guix-generation-list-diff-manifests)
|
||||||
|
(guix-generation-list-diff-packages)))
|
||||||
|
|
||||||
(defun guix-generation-list-mark-delete (&optional arg)
|
(defun guix-generation-list-mark-delete (&optional arg)
|
||||||
"Mark the current generation for deletion and move to the next line.
|
"Mark the current generation for deletion and move to the next line.
|
||||||
With ARG, mark all generations for deletion."
|
With ARG, mark all generations for deletion."
|
||||||
|
|
|
@ -106,6 +106,38 @@
|
||||||
(manifest-entry-version entry)
|
(manifest-entry-version entry)
|
||||||
(manifest-entry-output entry)))
|
(manifest-entry-output entry)))
|
||||||
|
|
||||||
|
(define (manifest-entry->package-specification entry)
|
||||||
|
(call-with-values
|
||||||
|
(lambda () (manifest-entry->name+version+output entry))
|
||||||
|
make-package-specification))
|
||||||
|
|
||||||
|
(define (manifest-entries->package-specifications entries)
|
||||||
|
(map manifest-entry->package-specification entries))
|
||||||
|
|
||||||
|
(define (generation-package-specifications profile number)
|
||||||
|
"Return a list of package specifications for generation NUMBER."
|
||||||
|
(let ((manifest (profile-manifest
|
||||||
|
(generation-file-name profile number))))
|
||||||
|
(manifest-entries->package-specifications
|
||||||
|
(manifest-entries manifest))))
|
||||||
|
|
||||||
|
(define (generation-package-specifications+paths profile number)
|
||||||
|
"Return a list of package specifications and paths for generation NUMBER.
|
||||||
|
Each element of the list is a list of the package specification and its path."
|
||||||
|
(let ((manifest (profile-manifest
|
||||||
|
(generation-file-name profile number))))
|
||||||
|
(map (lambda (entry)
|
||||||
|
(list (manifest-entry->package-specification entry)
|
||||||
|
(manifest-entry-item entry)))
|
||||||
|
(manifest-entries manifest))))
|
||||||
|
|
||||||
|
(define (generation-difference profile number1 number2)
|
||||||
|
"Return a list of package specifications for outputs installed in generation
|
||||||
|
NUMBER1 and not installed in generation NUMBER2."
|
||||||
|
(let ((specs1 (generation-package-specifications profile number1))
|
||||||
|
(specs2 (generation-package-specifications profile number2)))
|
||||||
|
(lset-difference string=? specs1 specs2)))
|
||||||
|
|
||||||
(define (manifest-entries->hash-table entries)
|
(define (manifest-entries->hash-table entries)
|
||||||
"Return a hash table of name keys and lists of matching manifest ENTRIES."
|
"Return a hash table of name keys and lists of matching manifest ENTRIES."
|
||||||
(let ((table (make-hash-table (length entries))))
|
(let ((table (make-hash-table (length entries))))
|
||||||
|
@ -625,8 +657,15 @@ See 'entry-sexps' for details."
|
||||||
(generation-file-name profile (car search-vals))
|
(generation-file-name profile (car search-vals))
|
||||||
profile))
|
profile))
|
||||||
(manifest (profile-manifest profile))
|
(manifest (profile-manifest profile))
|
||||||
(patterns (apply (patterns-maker entry-type search-type)
|
(patterns (if (and (eq? entry-type 'output)
|
||||||
manifest search-vals))
|
(eq? search-type 'generation-diff))
|
||||||
|
(match search-vals
|
||||||
|
((g1 g2)
|
||||||
|
(map specification->output-pattern
|
||||||
|
(generation-difference profile g1 g2)))
|
||||||
|
(_ '()))
|
||||||
|
(apply (patterns-maker entry-type search-type)
|
||||||
|
manifest search-vals)))
|
||||||
(->sexps ((pattern-transformer entry-type) manifest params)))
|
(->sexps ((pattern-transformer entry-type) manifest params)))
|
||||||
(append-map ->sexps patterns)))
|
(append-map ->sexps patterns)))
|
||||||
|
|
||||||
|
|
|
@ -99,7 +99,9 @@
|
||||||
(1 "A single package output installed in generation %d of profile '%s'."
|
(1 "A single package output installed in generation %d of profile '%s'."
|
||||||
val profile)
|
val profile)
|
||||||
(many "%d package outputs installed in generation %d of profile '%s'."
|
(many "%d package outputs installed in generation %d of profile '%s'."
|
||||||
count val profile)))
|
count val profile))
|
||||||
|
(generation-diff
|
||||||
|
guix-message-outputs-by-diff))
|
||||||
|
|
||||||
(generation
|
(generation
|
||||||
(id
|
(id
|
||||||
|
@ -167,6 +169,20 @@
|
||||||
"matching time period '%s' - '%s'.")
|
"matching time period '%s' - '%s'.")
|
||||||
str-beg profile time-beg time-end)))
|
str-beg profile time-beg time-end)))
|
||||||
|
|
||||||
|
(defun guix-message-outputs-by-diff (profile entries generations)
|
||||||
|
"Display a message for outputs searched by GENERATIONS difference."
|
||||||
|
(let* ((count (length entries))
|
||||||
|
(str-beg (guix-message-string-entries count 'output))
|
||||||
|
(gen1 (car generations))
|
||||||
|
(gen2 (cadr generations)))
|
||||||
|
(cl-multiple-value-bind (new old str-action)
|
||||||
|
(if (> gen1 gen2)
|
||||||
|
(list gen1 gen2 "added to")
|
||||||
|
(list gen2 gen1 "removed from"))
|
||||||
|
(message (concat "%s %s generation %d comparing with "
|
||||||
|
"generation %d of profile '%s'.")
|
||||||
|
str-beg str-action new old profile))))
|
||||||
|
|
||||||
(defun guix-result-message (profile entries entry-type
|
(defun guix-result-message (profile entries entry-type
|
||||||
search-type search-vals)
|
search-type search-vals)
|
||||||
"Display an appropriate message after displaying ENTRIES."
|
"Display an appropriate message after displaying ENTRIES."
|
||||||
|
|
|
@ -154,6 +154,16 @@ accessed with KEYS."
|
||||||
(dolist (key keys val)
|
(dolist (key keys val)
|
||||||
(setq val (cdr (assq key val))))))
|
(setq val (cdr (assq key val))))))
|
||||||
|
|
||||||
|
|
||||||
|
;;; Diff
|
||||||
|
|
||||||
|
(defvar guix-diff-switches "-u"
|
||||||
|
"A string or list of strings specifying switches to be passed to diff.")
|
||||||
|
|
||||||
|
(defun guix-diff (old new &optional switches no-async)
|
||||||
|
"Same as `diff', but use `guix-diff-switches' as default."
|
||||||
|
(diff old new (or switches guix-diff-switches) no-async))
|
||||||
|
|
||||||
(provide 'guix-utils)
|
(provide 'guix-utils)
|
||||||
|
|
||||||
;;; guix-utils.el ends here
|
;;; guix-utils.el ends here
|
||||||
|
|
Loading…
Reference in New Issue