Subject: [PATCH] scripts: pull: Add options for generation management

From: Konrad Hinsen <[email-masked]>
Date: Wed, 18 Sep 2019 09:52:18 +0200

* guix/scripts/pull.scm (%options) Add --roll-back, --switch-generation,
--delete-generations
(process-generation-change): New function
(guix-pull): Execute generation management operations

* doc/guix.texi: Document the generation management operations
---
 doc/guix.texi         | 47 +++++++++++++++++++++++++++++++++++++++++--
 guix/scripts/pull.scm | 41 ++++++++++++++++++++++++++++++++++++-
 2 files changed, 85 insertions(+), 3 deletions(-)

diff --git a/doc/guix.texi b/doc/guix.texi
index 55935b3794..27b58b37e5 100644
--- a/doc/guix.texi
+++ b/doc/guix.texi
@@ -3673,11 +3673,20 @@ Generation 3	Jun 13 2018 23:31:07	(current)
 @xref{Invoking guix describe, @command{guix describe}}, for other ways to
 describe the current status of Guix.
 
-This @code{~/.config/guix/current} profile works like any other profile
-created by @command{guix package} (@pxref{Invoking guix package}).  That
+This @code{~/.config/guix/current} profile works exactly like the profiles
+created by @command{guix package} (@pxref{Invoking guix package}). That
 is, you can list generations, roll back to the previous
 generation---i.e., the previous Guix---and so on:
 
+@example
+$ guix pull --roll-back
+switched from generation 3 to 2
+$ guix pull --delete-generations=1
+deleting /var/guix/profiles/per-user/charlie/current-guix-1-link
+@end example
+
+You can also use @command{guix package} (@pxref{Invoking guix package})
+to manage the profile by naming it explicitly:
 @example
 $ guix package -p ~/.config/guix/current --roll-back
 switched from generation 3 to 2
@@ -3724,6 +3733,40 @@ is provided, the subset of generations that match @var{pattern}.
 The syntax of @var{pattern} is the same as with @code{guix package
 --list-generations} (@pxref{Invoking guix package}).
 
+@item --roll-back
+@cindex rolling back
+@cindex undoing transactions
+@cindex transactions, undoing
+Roll back to the previous @dfn{generation} of @file{~/.config/guix/current}---i.e.,
+undo the last transaction.
+
+@item --switch-generation=@var{pattern}
+@itemx -S @var{pattern}
+@cindex generations
+Switch to a particular generation defined by @var{pattern}.
+
+@var{pattern} may be either a generation number or a number prefixed
+with ``+'' or ``-''.  The latter means: move forward/backward by a
+specified number of generations.  For example, if you want to return to
+the latest generation after @code{--roll-back}, use
+@code{--switch-generation=+1}.
+
+@item --delete-generations[=@var{pattern}]
+@itemx -d [@var{pattern}]
+When @var{pattern} is omitted, delete all generations except the current
+one.
+
+This command accepts the same patterns as @option{--list-generations}.
+When @var{pattern} is specified, delete the matching generations.  When
+@var{pattern} specifies a duration, generations @emph{older} than the
+specified duration match.  For instance, @code{--delete-generations=1m}
+deletes generations that are more than one month old.
+
+If the current generation matches, it is @emph{not} deleted.
+
+Note that deleting generations prevents rolling back to them.
+Consequently, this command must be used with care.
+
 @xref{Invoking guix describe}, for a way to display information about the
 current generation only.
 
diff --git a/guix/scripts/pull.scm b/guix/scripts/pull.scm
index 54bbaddf30..a111f3616d 100644
--- a/guix/scripts/pull.scm
+++ b/guix/scripts/pull.scm
@@ -38,7 +38,8 @@
   #:use-module (guix git)
   #:use-module (git)
   #:use-module (gnu packages)
-  #:use-module ((guix scripts package) #:select (build-and-use-profile))
+  #:use-module ((guix scripts package) #:select (build-and-use-profile
+                                                 delete-matching-generations))
   #:use-module ((gnu packages base) #:select (canonical-package))
   #:use-module (gnu packages guile)
   #:use-module ((gnu packages bootstrap)
@@ -91,6 +92,14 @@ Download and deploy the latest version of Guix.\n"))
   (display (G_ "
   -l, --list-generations[=PATTERN]
                          list generations matching PATTERN"))
+  (display (G_ "
+      --roll-back        roll back to the previous generation"))
+  (display (G_ "
+  -d, --delete-generations[=PATTERN]
+                         delete generations matching PATTERN"))
+  (display (G_ "
+  -S, --switch-generation=PATTERN
+                         switch to a generation matching PATTERN"))
   (display (G_ "
   -p, --profile=PROFILE  use PROFILE instead of ~/.config/guix/current"))
   (display (G_ "
@@ -120,6 +129,18 @@ Download and deploy the latest version of Guix.\n"))
                  (lambda (opt name arg result)
                    (cons `(query list-generations ,arg)
                          result)))
+         (option '("roll-back") #f #f
+                 (lambda (opt name arg result)
+                   (cons '(generation roll-back)
+                         result)))
+         (option '(#\S "switch-generation") #t #f
+                 (lambda (opt name arg result)
+                   (cons `(generation switch ,arg)
+                         result)))
+         (option '(#\d "delete-generations") #f #t
+                 (lambda (opt name arg result)
+                   (cons `(generation delete ,arg)
+                         result)))
          (option '(#\N "news") #f #f
                  (lambda (opt name arg result)
                    (cons '(query display-news) result)))
@@ -498,6 +519,22 @@ list of package changes.")))))
      (display-profile-news profile
                            #:current-is-newer? #t))))
 
+(define (process-generation-change opts profile)
+  "Process a request to change the current generation (roll-back, switch, delete)."
+  (unless (assoc-ref opts 'dry-run?)
+    (match (assoc-ref opts 'generation)
+      (('roll-back)
+       (with-store store
+         (roll-back* store profile)))
+      (('switch pattern)
+       (let ((number (relative-generation-spec->number profile pattern)))
+         (if number
+             (switch-to-generation* profile number)
+             (leave (G_ "cannot switch to generation '~a'~%") pattern))))
+      (('delete pattern)
+       (with-store store
+         (delete-matching-generations store profile pattern))))))
+
 (define (channel-list opts)
   "Return the list of channels to use.  If OPTS specify a channel file,
 channels are read from there; otherwise, if ~/.config/guix/channels.scm
@@ -565,6 +602,8 @@ Use '~/.config/guix/channels.scm' instead."))
             (profile  (or (assoc-ref opts 'profile) %current-profile)))
        (cond ((assoc-ref opts 'query)
               (process-query opts profile))
+             ((assoc-ref opts 'generation)
+              (process-generation-change opts profile))
              (else
               (with-store store
                 (ensure-default-profile)
-- 
2.22.1