diff --git a/doc/guix.texi b/doc/guix.texi
index 3b7fa50d814602a23cc1b0c61ad885ea24236223..d410f2a5506323549d704d15fef6b20752af059b 100644
--- a/doc/guix.texi
+++ b/doc/guix.texi
@@ -2844,9 +2844,6 @@ The @command{guix pull} command is usually invoked with no arguments,
 but it supports the following options:
 
 @table @code
-@item --verbose
-Produce verbose output, writing build logs to the standard error output.
-
 @item --url=@var{url}
 @itemx --commit=@var{commit}
 @itemx --branch=@var{branch}
@@ -2880,6 +2877,14 @@ current generation only.
 @itemx -p @var{profile}
 Use @var{profile} instead of @file{~/.config/guix/current}.
 
+@item --dry-run
+@itemx -n
+Show which channel commit(s) would be used and what would be built or
+substituted but do not actually do it.
+
+@item --verbose
+Produce verbose output, writing build logs to the standard error output.
+
 @item --bootstrap
 Use the bootstrap Guile to build the latest Guix.  This option is only
 useful to Guix developers.
diff --git a/guix/scripts/pull.scm b/guix/scripts/pull.scm
index 188237aa9046e22d7605a333c5c51c1dd2618d5a..aff4f378bed211252ba4c7d7d3b775d25a9cafca 100644
--- a/guix/scripts/pull.scm
+++ b/guix/scripts/pull.scm
@@ -86,6 +86,8 @@ (define (show-help)
                          list generations matching PATTERN"))
   (display (G_ "
   -p, --profile=PROFILE  use PROFILE instead of ~/.config/guix/current"))
+  (display (G_ "
+  -n, --dry-run          show what would be pulled and built"))
   (display (G_ "
       --bootstrap        use the bootstrap Guile to build the new Guix"))
   (newline)
@@ -164,15 +166,18 @@ (define (display-profile-news profile)
     (_ #t)))
 
 (define* (build-and-install instances profile
-                            #:key verbose?)
-  "Build the tool from SOURCE, and install it in PROFILE."
+                            #:key verbose? dry-run?)
+  "Build the tool from SOURCE, and install it in PROFILE.  When DRY-RUN? is
+true, display what would be built without actually building it."
   (define update-profile
     (store-lift build-and-use-profile))
 
   (mlet %store-monad ((manifest (channel-instances->manifest instances)))
     (mbegin %store-monad
-      (update-profile profile manifest)
-      (return (display-profile-news profile)))))
+      (update-profile profile manifest
+                      #:dry-run? dry-run?)
+      (munless dry-run?
+        (display-profile-news profile)))))
 
 (define (honor-lets-encrypt-certificates! store)
   "Tell Guile-Git to use the Let's Encrypt certificates."
@@ -497,8 +502,6 @@ (define (guix-pull . args)
        (ensure-default-profile)
        (cond ((assoc-ref opts 'query)
               (process-query opts profile))
-             ((assoc-ref opts 'dry-run?)
-              #t)                                 ;XXX: not very useful
              (else
               (with-store store
                 (with-status-report print-build-event
@@ -531,6 +534,8 @@ (define (guix-pull . args)
                                            (canonical-package guile-2.2)))))
                         (run-with-store store
                           (build-and-install instances profile
+                                             #:dry-run?
+                                             (assoc-ref opts 'dry-run?)
                                              #:verbose?
                                              (assoc-ref opts 'verbose?))))))))))))))