Subject: [bug#78580] [PATCH v5] pull: allow filtering which channels to pull from the CLI

From: Sergio Pastor Pérez <[email-masked]>
Date: Wed, 4 Jun 2025 23:23:32 +0200

* guix/scripts/pull.scm (guix-pull): Treat non-prefix CLI arguments as a list
of channels to pull.
* doc/guix.texi (guix pull): Document new command syntax and behavior for
filtering pulled channels by name.

Change-Id: I5d08c4b1cc84ab58a9c4e7600eb86468f92d10f0
---
 doc/guix.texi         | 18 ++++++++++++++--
 guix/scripts/pull.scm | 50 ++++++++++++++++++++++++++++++++++++-------
 2 files changed, 58 insertions(+), 10 deletions(-)

diff --git a/doc/guix.texi b/doc/guix.texi
index 5fefba3634..b6d7461f34 100644
--- a/doc/guix.texi
+++ b/doc/guix.texi
@@ -4656,8 +4656,22 @@ Invoking guix pull
 deleting /var/guix/profiles/per-user/charlie/current-guix-1-link
 @end example
 
-The @command{guix pull} command is usually invoked with no arguments,
-but it supports the following options:
+The general syntax is:
+
+@example
+guix pull [@var{options}] [@var{channels}@dots{}]
+@end example
+
+The optional @var{channels} argument filters the list of pulled channels
+to those whose names match the given list.
+
+For example, given a list of channels with names A B C:
+
+@example
+guix pull A B
+@end example
+
+would update channels A and B while keeping C unchanged.
 
 @table @code
 @item --url=@var{url}
diff --git a/guix/scripts/pull.scm b/guix/scripts/pull.scm
index 76aed0b5cc..3d8e613171 100644
--- a/guix/scripts/pull.scm
+++ b/guix/scripts/pull.scm
@@ -2,6 +2,7 @@
 ;;; Copyright © 2013-2015, 2017-2024 Ludovic Courtès <[email-masked]>
 ;;; Copyright © 2017 Marius Bakke <[email-masked]>
 ;;; Copyright © 2020, 2021 Tobias Geerinckx-Rice <[email-masked]>
+;;; Copyright © 2025 Sergio Pastor Pérez <[email-masked]>
 ;;;
 ;;; This file is part of GNU Guix.
 ;;;
@@ -81,8 +82,8 @@ (define %default-options
     (validate-pull . ,ensure-forward-channel-update)))
 
 (define (show-help)
-  (display (G_ "Usage: guix pull [OPTION]...
-Download and deploy the latest version of Guix.\n"))
+  (display (G_ "Usage: guix pull [OPTION]... [CHANNELS...]
+Download and deploy the latest version of Guix, possibly limited to CHANNELS.\n"))
   (display (G_ "
   -C, --channels=FILE    deploy the channels defined in FILE"))
   (display (G_ "
@@ -839,21 +840,52 @@ (define (validate-cache-directory-ownership)
 (define-command (guix-pull . args)
   (synopsis "pull the latest revision of Guix")
 
-  (define (no-arguments arg _)
-    (leave (G_ "~A: extraneous argument~%") arg))
+  ;; Unpin CHANNELS whose name symbol is present in the NAMES list. If NAMES
+  ;; is an empty list, do not filter anything. Warn if a name is not available
+  ;; in the channels list.
+  (define (unpin-channels channels current-channels names)
+    (if (null? names)
+        channels
+        (let ((available-names (map channel-name
+                                    channels))
+              (selected-channels (filter (lambda (channel)
+                                           (member (channel-name channel)
+                                                   names))
+                                         channels)))
+          (for-each (lambda (name)
+                      (unless (member name available-names)
+                        (warning (G_ "channel '~a~' selected but missing from channel list~%")
+                                 name)))
+                    names)
+
+          (map (lambda (channel)
+                 (let ((selected-channel (find (lambda (ch)
+                                                 (eq? (channel-name ch)
+                                                      (channel-name channel)))
+                                               selected-channels)))
+                   ;; If the user selected this channel, follow channel file
+                   ;; specification. Otherwise, leave the channel pinned as
+                   ;; defined by the current profile.
+                   (or selected-channel
+                       channel)))
+               current-channels))))
 
   (with-error-handling
     (with-git-error-handling
      (let* ((opts         (parse-command-line args %options
-                                              (list %default-options)
-                                              #:argument-handler no-arguments))
+                                              (list %default-options)))
             (substitutes? (assoc-ref opts 'substitutes?))
             (dry-run?     (assoc-ref opts 'dry-run?))
             (profile      (or (assoc-ref opts 'profile) %current-profile))
             (current-channels (profile-channels profile))
             (validate-pull    (assoc-ref opts 'validate-pull))
             (authenticate?    (assoc-ref opts 'authenticate-channels?))
-            (verify-certificate? (assoc-ref opts 'verify-certificate?)))
+            (verify-certificate? (assoc-ref opts 'verify-certificate?))
+            (selected-channels (filter-map
+                                (match-lambda
+                                  (('argument . name) (string->symbol name))
+                                  (_ #f))
+                                opts)))
        (cond
         ((assoc-ref opts 'query)
          (process-query opts profile))
@@ -877,7 +909,9 @@ (define-command (guix-pull . args)
                  (ensure-default-profile)
                  (honor-x509-certificates store)
 
-                 (let* ((channels (channel-list opts))
+                 (let* ((channels (unpin-channels (channel-list opts)
+                                                  current-channels
+                                                  selected-channels))
                         (instances
                          (latest-channel-instances store channels
                                                    #:current-channels

base-commit: ed5988f0d2cf14e3cc35a32e6ad91d7cbf535e2f
-- 
2.49.0