diff mbox series

[bug#69780,v2,1/5] git authenticate: Record introduction and keyring in ‘.git/config’.

Message ID 4d37936c1b3aed95ca72de2ba112033bafefc2de.1712522118.git.ludo@gnu.org
State New
Headers show
Series [bug#69780,v2,1/5] git authenticate: Record introduction and keyring in ‘.git/config’. | expand

Commit Message

Ludovic Courtès April 7, 2024, 8:38 p.m. UTC
* guix/scripts/git/authenticate.scm (%default-options): Remove
‘keyring-reference’.
(config-value, configured-introduction, configured-keyring-reference)
(configured?, record-configuration, current-branch): New procedures.
(guix-git-authenticate)[missing-arguments]: New procedure.
Use ‘configured-introduction’ when zero arguments are given.
Use ‘configured-keyring-reference’ when ‘-k’ is not passed.  Add call to
‘record-configuration’.
* doc/guix.texi (Invoking guix git authenticate): Document it.

Change-Id: I66e111a83f50407b52da71662629947f83a78bbc
---
 doc/guix.texi                     |  28 +++++-
 guix/scripts/git/authenticate.scm | 147 +++++++++++++++++++++++-------
 tests/guix-git-authenticate.sh    |   9 +-
 3 files changed, 150 insertions(+), 34 deletions(-)
diff mbox series

Patch

diff --git a/doc/guix.texi b/doc/guix.texi
index acfe60b47a..6ff0e76d97 100644
--- a/doc/guix.texi
+++ b/doc/guix.texi
@@ -7667,6 +7667,8 @@  Invoking guix git authenticate
 @section Invoking @command{guix git authenticate}
 
 @cindex @command{guix git authenticate}
+@cindex authentication, of Git checkouts
+@cindex Git checkout authentication
 
 The @command{guix git authenticate} command authenticates a Git checkout
 following the same rule as for channels (@pxref{channel-authentication,
@@ -7686,13 +7688,35 @@  Invoking guix git authenticate
 guix git authenticate @var{commit} @var{signer} [@var{options}@dots{}]
 @end example
 
+@cindex introduction, for Git authentication
 By default, this command authenticates the Git checkout in the current
 directory; it outputs nothing and exits with exit code zero on success
 and non-zero on failure.  @var{commit} above denotes the first commit
 where authentication takes place, and @var{signer} is the OpenPGP
 fingerprint of public key used to sign @var{commit}.  Together, they
-form a ``channel introduction'' (@pxref{channel-authentication, channel
-introduction}).  The options below allow you to fine-tune the process.
+form a @dfn{channel introduction} (@pxref{channel-authentication, channel
+introduction}).  On your first successful run, the introduction is
+recorded in the @file{.git/config} file of your checkout, allowing you
+to omit them from subsequent invocations:
+
+@example
+guix git authenticate [@var{options}@dots{}]
+@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:
+
+@smallexample
+[guix "authentication-personal-fork"]
+	introduction-commit = cabba936fd807b096b48283debdcddccfea3900d
+	introduction-signer = C0FF EECA BBA9 E6A8 0D1D  E643 A2A0 6DF2 A33A 54FA
+	keyring = keyring
+@end smallexample
+
+The command-line options described below allow you to fine-tune the
+process.
 
 @table @code
 @item --repository=@var{directory}
diff --git a/guix/scripts/git/authenticate.scm b/guix/scripts/git/authenticate.scm
index def4879e96..a606f1c146 100644
--- a/guix/scripts/git/authenticate.scm
+++ b/guix/scripts/git/authenticate.scm
@@ -31,6 +31,7 @@  (define-module (guix scripts git authenticate)
   #:use-module (srfi srfi-1)
   #:use-module (srfi srfi-26)
   #:use-module (srfi srfi-37)
+  #:use-module (srfi srfi-71)
   #:use-module (ice-9 format)
   #:use-module (ice-9 match)
   #:export (guix-git-authenticate))
@@ -73,8 +74,79 @@  (define %options
                   (alist-cons 'show-stats? #t result)))))
 
 (define %default-options
-  '((directory . ".")
-    (keyring-reference . "keyring")))
+  '((directory . ".")))
+
+(define (current-branch repository)
+  "Return the name of the checked out branch of REPOSITORY or #f if it could
+not be determined."
+  (and (not (repository-head-detached? repository))
+       (let* ((head (repository-head repository))
+              (name (reference-name head)))
+         (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."
+  (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))))))))
+
+(define (configured-introduction repository)
+  "Return two values: the commit and signer fingerprint (strings) as
+configured in REPOSITORY.  Error out if one or both were missing."
+  (let* ((commit (config-value repository "introduction-commit"))
+         (signer (config-value repository "introduction-signer")))
+    (unless (and commit signer)
+      (leave (G_ "unknown introductory commit and signer~%")))
+    (values commit signer)))
+
+(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* (record-configuration repository
+                               #:key commit signer keyring-reference)
+  "Record COMMIT, SIGNER, and KEYRING-REFERENCE in the 'config' file of
+REPOSITORY."
+  (define config
+    (repository-config repository))
+
+  ;; 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"
+                           commit)
+        (set-config-string config "guix.authentication.introduction-signer"
+                           signer)
+        (set-config-string config "guix.authentication.keyring"
+                           keyring-reference)
+        (info (G_ "introduction and keyring recorded \
+in repository configuration file~%")))
+      (warning (G_ "could not record introduction and keyring configuration\
+ (Guile-Git too old?)~%"))))
 
 (define (show-stats stats)
   "Display STATS, an alist containing commit signing stats as returned by
@@ -158,35 +230,48 @@  (define (guix-git-authenticate . args)
         (progress-reporter/bar (length commits))
         progress-reporter/silent))
 
+  (define (missing-arguments)
+    (leave (G_ "wrong number of arguments; \
+expected COMMIT and SIGNER~%")))
+
   (with-error-handling
     (with-git-error-handling
-     (match (command-line-arguments options)
-       ((commit signer)
-        (let* ((directory   (assoc-ref options 'directory))
-               (show-stats? (assoc-ref options 'show-stats?))
-               (keyring     (assoc-ref options 'keyring-reference))
-               (repository  (repository-open directory))
-               (end         (match (assoc-ref options 'end-commit)
-                              (#f  (reference-target
-                                    (repository-head repository)))
-                              (oid oid)))
-               (history     (match (assoc-ref options 'historical-authorizations)
-                              (#f '())
-                              (file (call-with-input-file file
-                                      read-authorizations))))
-               (cache-key   (or (assoc-ref options 'cache-key)
-                                (repository-cache-key repository))))
-          (define stats
-            (authenticate-repository repository (string->oid commit)
-                                     (openpgp-fingerprint* signer)
-                                     #:end end
-                                     #:keyring-reference keyring
-                                     #:historical-authorizations history
-                                     #:cache-key cache-key
-                                     #:make-reporter make-reporter))
+     (let* ((directory   (assoc-ref options 'directory))
+            (show-stats? (assoc-ref options 'show-stats?))
+            (repository  (repository-open directory))
+            (commit signer (match (command-line-arguments options)
+                             ((commit signer)
+                              (values commit signer))
+                             (()
+                              (configured-introduction repository))
+                             (_
+                              (missing-arguments))))
+            (keyring     (or (assoc-ref options 'keyring-reference)
+                             (configured-keyring-reference repository)
+                             "keyring"))
+            (end         (match (assoc-ref options 'end-commit)
+                           (#f  (reference-target
+                                 (repository-head repository)))
+                           (oid oid)))
+            (history     (match (assoc-ref options 'historical-authorizations)
+                           (#f '())
+                           (file (call-with-input-file file
+                                   read-authorizations))))
+            (cache-key   (or (assoc-ref options 'cache-key)
+                             (repository-cache-key repository))))
+       (define stats
+         (authenticate-repository repository (string->oid commit)
+                                  (openpgp-fingerprint* signer)
+                                  #:end end
+                                  #:keyring-reference keyring
+                                  #:historical-authorizations history
+                                  #:cache-key cache-key
+                                  #:make-reporter make-reporter))
 
-          (when (and show-stats? (not (null? stats)))
-            (show-stats stats))))
-       (_
-        (leave (G_ "wrong number of arguments; \
-expected COMMIT and SIGNER~%")))))))
+       (unless (configured? repository)
+         (record-configuration repository
+                               #:commit commit #:signer signer
+                               #:keyring-reference keyring))
+
+       (when (and show-stats? (not (null? stats)))
+         (show-stats stats))))))
diff --git a/tests/guix-git-authenticate.sh b/tests/guix-git-authenticate.sh
index ec89f941e6..7b8951b9aa 100644
--- a/tests/guix-git-authenticate.sh
+++ b/tests/guix-git-authenticate.sh
@@ -1,5 +1,5 @@ 
 # GNU Guix --- Functional package management for GNU
-# Copyright © 2020, 2022 Ludovic Courtès <ludo@gnu.org>
+# Copyright © 2020, 2022, 2024 Ludovic Courtès <ludo@gnu.org>
 #
 # This file is part of GNU Guix.
 #
@@ -46,6 +46,13 @@  guix git authenticate "$intro_commit" "$intro_signer"	\
      --cache-key="$cache_key" --stats			\
      --end="$v1_2_0_commit"
 
+# Check a commit that came soon after v1.2.0.  No need to repeat $intro_commit
+# and $intro_signer because it should have been recorded in '.git/config'.
+after_v1_2_0="be4d9527b55b6829e33a6e0727496af25927a786"
+guix git authenticate				\
+     --cache-key="$cache_key" --stats		\
+     --end="$v1_2_0_commit"
+
 rm "$XDG_CACHE_HOME/guix/authentication/$cache_key"
 
 # Commit and signer of the 'v1.0.0' tag.