guix package: Add '--switch-generation' option.

* guix/scripts/package.scm (switch-to-generation): New procedure.
  (switch-to-previous-generation): Use it.
  (guix-package): Adjust for '--switch-generation' option.
* tests/guix-package.sh: Test it.
* doc/guix.texi (Invoking guix package): Document it.
This commit is contained in:
Alex Kost 2014-10-10 17:58:43 +04:00
parent 3ccde08752
commit b3bb82f154
3 changed files with 76 additions and 12 deletions

View File

@ -806,6 +806,21 @@ Installing, removing, or upgrading packages from a generation that has
been rolled back to overwrites previous future generations. Thus, the
history of a profile's generations is always linear.
@item --switch-generation=@var{pattern}
@itemx -S @var{pattern}
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}.
The difference between @code{--roll-back} and
@code{--switch-generation=-1} is that @code{--switch-generation} will
not make a zeroth generation, so if a specified generation does not
exist, the current generation will not be changed.
@item --search-paths
@cindex search paths
Report environment variable definitions, in Bash syntax, that may be

View File

@ -46,6 +46,8 @@
#:use-module (gnu packages guile)
#:use-module ((gnu packages bootstrap) #:select (%bootstrap-guile))
#:export (specification->package+output
switch-to-generation
switch-to-previous-generation
roll-back
delete-generation
delete-generations
@ -96,14 +98,26 @@ return PROFILE unchanged. The goal is to treat '-p ~/.guix-profile' as if
(switch-symlinks generation prof)))
(define (switch-to-generation profile number)
"Atomically switch PROFILE to the generation NUMBER."
(let ((current (generation-number profile))
(generation (generation-file-name profile number)))
(cond ((not (file-exists? profile))
(raise (condition (&profile-not-found-error
(profile profile)))))
((not (file-exists? generation))
(raise (condition (&missing-generation-error
(profile profile)
(generation number)))))
(else
(format #t (_ "switching from generation ~a to ~a~%")
current number)
(switch-symlinks profile generation)))))
(define (switch-to-previous-generation profile)
"Atomically switch PROFILE to the previous generation."
(let* ((number (generation-number profile))
(previous-number (previous-generation-number profile number))
(previous-generation (generation-file-name profile previous-number)))
(format #t (_ "switching from generation ~a to ~a~%")
number previous-number)
(switch-symlinks profile previous-generation)))
(switch-to-generation profile
(previous-generation-number profile)))
(define (roll-back store profile)
"Roll back to the previous generation of PROFILE."
@ -411,6 +425,9 @@ Install, remove, or upgrade PACKAGES in a single transaction.\n"))
-d, --delete-generations[=PATTERN]
delete generations matching PATTERN"))
(display (_ "
-S, --switch-generation=PATTERN
switch to a generation matching PATTERN"))
(display (_ "
-p, --profile=PROFILE use PROFILE instead of the user's default profile"))
(newline)
(display (_ "
@ -490,6 +507,10 @@ Install, remove, or upgrade PACKAGES in a single transaction.\n"))
(values (alist-cons 'delete-generations (or arg "")
result)
#f)))
(option '(#\S "switch-generation") #t #f
(lambda (opt name arg result arg-handler)
(values (alist-cons 'switch-generation arg result)
#f)))
(option '("search-paths") #f #f
(lambda (opt name arg result arg-handler)
(values (cons `(query search-paths) result)
@ -715,13 +736,31 @@ more information.~%"))
(generation-number profile))
;; First roll back if asked to.
(cond ((and (assoc-ref opts 'roll-back?) (not dry-run?))
(begin
(roll-back (%store) profile)
(process-actions (alist-delete 'roll-back? opts))))
(cond ((and (assoc-ref opts 'roll-back?)
(not dry-run?))
(roll-back (%store) profile)
(process-actions (alist-delete 'roll-back? opts)))
((and (assoc-ref opts 'switch-generation)
(not dry-run?))
(for-each
(match-lambda
(('switch-generation . pattern)
(let* ((number (string->number pattern))
(number (and number
(case (string-ref pattern 0)
((#\+ #\-)
(relative-generation profile number))
(else number)))))
(if number
(switch-to-generation profile number)
(leave (_ "cannot switch to generation '~a'~%")
pattern)))
(process-actions (alist-delete 'switch-generation opts)))
(_ #f))
opts))
((and (assoc-ref opts 'delete-generations)
(not dry-run?))
(filter-map
(for-each
(match-lambda
(('delete-generations . pattern)
(cond ((not (file-exists? profile)) ; XXX: race condition

View File

@ -87,6 +87,8 @@ then
# Exit with 1 when a generation does not exist.
if guix package -p "$profile" --list-generations=42;
then false; else true; fi
if guix package -p "$profile" --switch-generation=99;
then false; else true; fi
# Remove a package.
guix package --bootstrap -p "$profile" -r "guile-bootstrap"
@ -101,6 +103,12 @@ then
test "`readlink_base "$profile"`" = "$profile-1-link"
test -x "$profile/bin/guile" && ! test -x "$profile/bin/make"
# Switch to the rolled generation and switch back.
guix package -p "$profile" --switch-generation=2
test "`readlink_base "$profile"`" = "$profile-2-link"
guix package -p "$profile" --switch-generation=-1
test "`readlink_base "$profile"`" = "$profile-1-link"
# Move to the empty profile.
for i in `seq 1 3`
do
@ -133,10 +141,12 @@ then
grep "`guix build -e "$boot_make"`" "$profile/manifest"
# Make a "hole" in the list of generations, and make sure we can
# roll back "over" it.
# roll back and switch "over" it.
rm "$profile-1-link"
guix package --bootstrap -p "$profile" --roll-back
test "`readlink_base "$profile"`" = "$profile-0-link"
guix package -p "$profile" --switch-generation=+1
test "`readlink_base "$profile"`" = "$profile-2-link"
# Make sure LIBRARY_PATH gets listed by `--search-paths'.
guix package --bootstrap -p "$profile" -i guile-bootstrap -i gcc-bootstrap