Subject: [bug#75981] [PATCH v2 2/2] scripts: Add `guix git authenticate --branch`.

From: 45mg <[email-masked]>
Date: Sun, 23 Feb 2025 18:50:49 +0530

Add an option to make `guix git authenticate` configure branch-specific
introductions.  This is an improvement over users having to configure
them manually.

* guix/scripts/git/authenticate.scm
%options: Add --branch option.
(show-help): Mention it.
(guix-git-authenticate): Interpret and pass it to the below procedures.
(configured?): Accept it as an optional argument, to pass
to (config-value).
(config-value): Handle it as an optional argument.
(record-configuration): Likewise.
* tests/fork.sh: Test it.
* doc/guix.texi (Invoking guix git authenticate): Document it.

Change-Id: I0a64043448bec748be1117ffc24632279220ec7c
---
 doc/contributing.texi             | 10 +++-
 doc/guix.texi                     | 15 ++++--
 guix/scripts/git/authenticate.scm | 86 +++++++++++++++++++++----------
 tests/fork.sh                     | 31 +++++++++++
 4 files changed, 109 insertions(+), 33 deletions(-)

diff --git a/doc/contributing.texi b/doc/contributing.texi
index 121f7eea9d..d3a4129617 100644
--- a/doc/contributing.texi
+++ b/doc/contributing.texi
@@ -3145,8 +3145,9 @@ Using Your Own Patches
 
 @itemize
 @item
-Add your public key to the `keyring' branch of the repository. (This is
-the key that all future commits will be signed with).
+Add a commit to the `keyring' branch of the repository that adds your
+public key. (This is the key that all future commits will be signed
+with).
 
 @item
 Add a commit to the fork branch that modifies the
@@ -3158,6 +3159,11 @@ Using Your Own Patches
 a fork for you, performing these steps as well as taking care of other
 details. Run @samp{etc/fork.scm --help} for details.
 
+Additionally, the `--branch' option of @samp{guix git authenticate}
+allows you to specify the fork introduction as a branch-specific
+introduction, so that it will be used whenever you're on the fork
+branch. For more information, @ref{Invoking guix git authenticate}.
+
 @node Updating the Guix Package
 @section Updating the Guix Package
 
diff --git a/doc/guix.texi b/doc/guix.texi
index 59d9ae5dce..a121dd2a22 100644
--- a/doc/guix.texi
+++ b/doc/guix.texi
@@ -7490,9 +7490,10 @@ Invoking guix git authenticate
 @end example
 
 Should you have branches that require different introductions, you can
-specify them directly in @file{.git/config}.  For example, if the branch
-called @code{personal-fork} has a different introduction than other
-branches, you can extend @file{.git/config} along these lines:
+specify them using the @option{--branch} option, or directly in
+@file{.git/config}.  For example, if the branch called
+@code{personal-fork} has a different introduction than other branches,
+you can extend @file{.git/config} along these lines:
 
 @smallexample
 [guix "authentication-personal-fork"]
@@ -7541,6 +7542,14 @@ Invoking guix git authenticate
 commit that lacks @file{.guix-authorizations}.  The format of @var{file}
 is the same as that of @file{.guix-authorizations}
 (@pxref{channel-authorizations, @file{.guix-authorizations} format}).
+
+@item --branch[=@var{branch}]
+By default, the introduction is recorded under the @samp{[guix
+"authentication"]} section of the @file{.git/config} file of your
+checkout.  This option will cause it to be recorded under @samp{[guix
+"authentication-@var{branch}"]} instead, making it a
+@dfn{branch-specific introduction}.  @var{branch} defaults to the
+current branch.
 @end table

diff --git a/guix/scripts/git/authenticate.scm b/guix/scripts/git/authenticate.scm
index e3ecb67c89..82ecc6ee2f 100644
--- a/guix/scripts/git/authenticate.scm
+++ b/guix/scripts/git/authenticate.scm
@@ -1,5 +1,6 @@
 ;;; GNU Guix --- Functional package management for GNU
 ;;; Copyright © 2020, 2024 Ludovic Courtès <[email-masked]>
+;;; Copyright © 2025 45mg <[email-masked]>
 ;;;
 ;;; This file is part of GNU Guix.
 ;;;
@@ -69,6 +70,11 @@ (define %options
                 (lambda (opt name arg result)
                   (alist-cons 'historical-authorizations arg
                               result)))
+        (option '("branch") #f #t
+                (lambda (opt name arg result)
+                  (alist-cons 'branch
+                              (or arg #t)
+                              result)))
         (option '("stats") #f #f
                 (lambda (opt name arg result)
                   (alist-cons 'show-stats? #t result)))))
@@ -85,28 +91,36 @@ (define (current-branch repository)
          (and (string-prefix? "refs/heads/" name)
               (string-drop name (string-length "refs/heads/"))))))
 
-(define (config-value repository key)
-  "Return the config value associated with KEY in the 'guix.authentication' or
-'guix.authentication-BRANCH' name space in REPOSITORY, or #f if no such config
-was found."
+(define* (config-value repository key #:optional branch)
+  "Return the config value associated with KEY in the
+'guix.authentication-BRANCH' or 'guix.authentication' name space, in that
+order, in REPOSITORY; or #f if no such config was found.
+BRANCH defaults to the current branch; if this parameter is specified, look
+only in the 'guix.authentication-BRANCH' name space."
   (let-syntax ((false-if-git-error
                 (syntax-rules ()
                   ((_ exp)
                    (catch 'git-error (lambda () exp) (const #f))))))
     (let* ((config (repository-config repository))
-           (branch (current-branch repository)))
-      ;; First try the BRANCH-specific value, then the generic one.`
-      (or (and branch
-               (false-if-git-error
-                (config-entry-value
-                 (config-get-entry config
-                                   (string-append "guix.authentication-"
-                                                  branch "." key)))))
-          (false-if-git-error
-           (config-entry-value
-            (config-get-entry config
-                              (string-append "guix.authentication."
-                                             key))))))))
+           (search-branch (or branch
+                              (current-branch repository)))
+           (branch-specific
+            (and search-branch
+                 (false-if-git-error
+                  (config-entry-value
+                   (config-get-entry config
+                                     (string-append "guix.authentication-"
+                                                    search-branch
+                                                    "." key)))))))
+      (if branch
+          branch-specific
+          ;; First try the BRANCH-specific value, then the generic one.`
+          (or branch-specific
+              (false-if-git-error
+               (config-entry-value
+                (config-get-entry config
+                                  (string-append "guix.authentication."
+                                                 key)))))))))
 
 (define (configured-introduction repository)
   "Return two values: the commit and signer fingerprint (strings) as
@@ -121,27 +135,34 @@ (define (configured-keyring-reference repository)
   "Return the keyring reference configured in REPOSITORY or #f if missing."
   (config-value repository "keyring"))
 
-(define (configured? repository)
-  "Return true if REPOSITORY already container introduction info in its
-'config' file."
-  (and (config-value repository "introduction-commit")
-       (config-value repository "introduction-signer")))
+(define* (configured? repository #:optional branch)
+  "Return true if REPOSITORY already contains introduction info in its
+'config' file.
+If BRANCH is given, look only for the BRANCH-specific introduction."
+  (and (config-value repository "introduction-commit" branch)
+       (config-value repository "introduction-signer" branch)))
 
 (define* (record-configuration repository
+                               #:optional branch
                                #:key commit signer keyring-reference)
   "Record COMMIT, SIGNER, and KEYRING-REFERENCE in the 'config' file of
-REPOSITORY."
+REPOSITORY, under [guix \"authentication\"].
+If BRANCH is given, record them under [guix \"authentication-BRANCH\"]."
   (define config
     (repository-config repository))
+  (define section
+    (if branch
+        (string-append "guix.authentication-" branch)
+        "guix.authentication"))
 
   ;; Guile-Git < 0.7.0 lacks 'set-config-string'.
   (if (module-defined? (resolve-interface '(git)) 'set-config-string)
       (begin
-        (set-config-string config "guix.authentication.introduction-commit"
+        (set-config-string config (string-append section ".introduction-commit")
                            commit)
-        (set-config-string config "guix.authentication.introduction-signer"
+        (set-config-string config (string-append section ".introduction-signer")
                            signer)
-        (set-config-string config "guix.authentication.keyring"
+        (set-config-string config (string-append section ".keyring")
                            keyring-reference)
         (info (G_ "introduction and keyring recorded \
 in repository configuration file~%")))
@@ -227,6 +248,9 @@ (define (show-help)
   (display (G_ "
       --historical-authorizations=FILE
                          read historical authorizations from FILE"))
+  (display (G_ "
+      --branch[=BRANCH]  ensure BRANCH-specific introduction is used/configured
+                         (BRANCH defaults to the current branch)"))
   (newline)
   (display (G_ "
   -h, --help             display this help and exit"))
@@ -285,6 +309,12 @@ (define (guix-git-authenticate . args)
      (let* ((show-stats? (assoc-ref options 'show-stats?))
             (repository  (repository-open (or (assoc-ref options 'directory)
                                               (repository-discover "."))))
+            (branch      (match (assoc-ref options 'branch)
+                           (#t (or (current-branch repository)
+                                   (leave (G_ "\
+--branch specified, but cannot detect current branch; try --branch=BRANCH~%"))))
+                           (#f #f)
+                           (string string)))
             (commit signer (match (command-line-arguments options)
                              ((commit signer)
                               (values commit signer))
@@ -314,8 +344,8 @@ (define (guix-git-authenticate . args)
                                   #:cache-key cache-key
                                   #:make-reporter make-reporter))
 
-       (unless (configured? repository)
-         (record-configuration repository
+       (unless (configured? repository branch)
+         (record-configuration repository branch
                                #:commit commit #:signer signer
                                #:keyring-reference keyring)
          (install-hooks repository))
diff --git a/tests/fork.sh b/tests/fork.sh
index f6c72dba73..3791ccc838 100644
--- a/tests/fork.sh
+++ b/tests/fork.sh
@@ -133,3 +133,34 @@ guix git authenticate \
 # Test that the fork branch fails to authenticate with the upstream
 # introduction, as it is not signed by an authorized key.
 ! guix git authenticate
+
+# Test that the fork introduction itself can be authenticated using the --branch
+# argument (a rather trivial test).
+# This should configure the branch-specific introduction, without which the
+# next test will fail.
+FORK_INTRO_COMMIT=$(git rev-parse HEAD)
+guix git authenticate "$FORK_INTRO_COMMIT" "$TEST_KEY" --branch
+
+# The same test, but without any arguments, to see if the branch-specific
+# introduction was recorded in the repository config file and is read
+# correctly.
+guix git authenticate
+
+# Add a few empty commits onto the 'fork' branch, signed with our key.
+# Test that these can be authenticated.
+git config user.name test-user
+git config user.email [email-masked]
+for i in A B C D E; do
+    git commit --quiet --allow-empty --gpg-sign="$TEST_KEY" -m "$i"
+done
+guix git authenticate
+
+# Test that we can pass a parameter with --branch.
+guix git authenticate --branch=fork
+
+# Check out the previous commit, so that we're not on a branch (detached HEAD
+# state). Then test that we can still authenticate by passing --branch=fork.
+git checkout --quiet HEAD^
+guix git authenticate --branch=fork
+
+git checkout --quiet fork
-- 
2.48.1