GNU bug report logs - #75981
[PATCH (WIP) v1 0/4] Add 'guix fork'.

Previous Next

Package: guix-patches;

Reported by: 45mg <45mg.writes <at> gmail.com>

Date: Fri, 31 Jan 2025 21:11:02 UTC

Severity: normal

Tags: patch

To reply to this bug, email your comments to 75981 AT debbugs.gnu.org.

Toggle the display of automated, internal messages from the tracker.

View this report as an mbox folder, status mbox, maintainer mbox


Report forwarded to ludo <at> gnu.org, maxim.cournoyer <at> gmail.com, guix-patches <at> gnu.org:
bug#75981; Package guix-patches. (Fri, 31 Jan 2025 21:11:02 GMT) Full text and rfc822 format available.

Acknowledgement sent to 45mg <45mg.writes <at> gmail.com>:
New bug report received and forwarded. Copy sent to ludo <at> gnu.org, maxim.cournoyer <at> gmail.com, guix-patches <at> gnu.org. (Fri, 31 Jan 2025 21:11:02 GMT) Full text and rfc822 format available.

Message #5 received at submit <at> debbugs.gnu.org (full text, mbox):

From: 45mg <45mg.writes <at> gmail.com>
To: guix-patches <at> gnu.org
Cc: Nicolas Graves <ngraves <at> ngraves.fr>, Tomas Volf <~@wolfsden.cz>,
 45mg <45mg.writes <at> gmail.com>,
 Liliana Marie Prikler <liliana.prikler <at> gmail.com>,
 Ricardo Wurmus <rekado <at> elephly.net>, Attila Lendvai <attila <at> lendvai.name>
Subject: [PATCH (WIP) v1 0/4] Add 'guix fork'.
Date: Sat,  1 Feb 2025 02:40:08 +0530
Please ignore bugs #75973 and #75975. Those were both attempts to send this
patch series, but I sent the first one out before it was done and somehow
messed up the 'CC' and 'In-Reply-To' fields of the second. The best I can do
is close them and try again. Hopefully I'll get it right this time. (The 'v1'
in the subject line here is to differentiate this attempt from those ones.)

Hello Guix,

This patch series aims to enable and automate the creation and management of
authenticated local forks of Guix. The purpose of this work is to allow
contributors to use their own patches before they're applied to
upstream Guix, so that their own use of Guix is not hindered by the slow and
erratic pace of patch review.

This is a solution to bug #75552 [1], in whose discussion thread the design
was conceived and refined. Credit goes to Tomas for being the first person (to
my knowledge) to share their solution to this problem [2], which provided a
blueprint for 'guix fork create'; to Liliana for the idea behind the way 'guix
fork update' works [3]; and to Ricardo for the idea behind 'guix fork
identify' [4]. I've also CC'ed Attila and Nicolas since they replied in the
original thread (apologies in advance if I shouldn't have).

As I mentioned in the original thread [5], this solution aims to satisfy four
conditions which are not met by any existing method to my knowledge:

1. Allows authenticating both upstream and fork commits.
2. Does not require bumping the channel introduction (as distributing channel
introductions is sensitive)
3. Keeps fork history intact (to avoid force pulls).
4. Keeps upstream history intact (to avoid confusion).

Despite the '(WIP)' subject prefix, this patch series should be perfectly
usable in its current state. The easiest way to try it out would be as follows:

1. Apply it to your local clone of Guix (eg. in a branch) and build it.
2. 'cp -r' your local clone to another location.
3. Run the following command:
./pre-inst-env guix fork create <fingerprint-of-your-key> path/to/copy/of/local/clone --use-existing

Now you have the setup needed for an authenticated local fork. From here, you
can create and 'guix pull' (with authentication) from branches starting from
the initial fork commit. You can authenticate both fork and upstream using
'guix fork authenticate', even if the key used to create your fork is not
authorized upstream. You can update your fork with new commits from upstream
using 'guix fork update'.

The documentation (additions to doc/guix.texi and doc/contributing.texi)
should provide a proper overview of these commands and their usage. Easiest
way to view it could be to run 'make doc/guix.html' and then open it in a
browser.

The '(WIP)' subject prefix is there because the following things are yet to be
implemented:
1. The 'guix fork identify' command.
2. Tests, along the lines of tests/guix-git-authenticate.sh.

The code here adapts certain procedures from Tomas Volf's original 'fork-guix'
script [6]; namely: '-->', 'invoke/c', 'create-keyring-branch', 'git-C', and
'git-C/c'. That script is licensed under AGPL, so my understanding is that it,
or the procedures I used from it, would need to be relicensed under GPLv3 to
be included into Guix. Tomas - could you confirm here that you're willing to
do so, as we discussed earlier? (Note that I didn't ask you about the last two
of the five procedures above, since I hadn't used them yet at the time.)

Regards,
45mg

P.S It was helpfully explained to me [8] (in a reply to one of my previous
botched attempts to send this out) that since this patch series modifies (guix
build utils), it will result in almost every derivation changing and almost
everything needing to be rebuilt. I may send a v1.5 that moves those changes
elsewhere, so that it's feasible to 'guix pull' from a clone with this series
applied. Until then, you can test things via pre-inst-env.

[1] https://issues.guix.gnu.org/75552
[2] https://lists.gnu.org/archive/html/help-guix/2023-09/msg00078.html
[3] https://lists.nongnu.org/archive/html/bug-guix/2025-01/msg00139.html
[4] https://lists.nongnu.org/archive/html/bug-guix/2025-01/msg00130.html
[5] https://lists.nongnu.org/archive/html/bug-guix/2025-01/msg00135.html
[6] https://git.wolfsden.cz/guix/tree/etc/fork-guix
[7] https://ci.guix.gnu.org/eval/2036099
[8] https://lists.gnu.org/archive/html/guix-patches/2025-01/msg02844.html

45mg (4):
  Add 'guix fork create'.
  Add 'guix fork authenticate'.
  Add 'guix fork update'.
  Document 'guix fork'.

 Makefile.am                        |   4 +
 doc/contributing.texi              |  50 +++++
 doc/guix.texi                      | 150 +++++++++++++
 guix/build/utils.scm               |  20 ++
 guix/channels.scm                  |  13 ++
 guix/git-authenticate.scm          |  17 ++
 guix/git.scm                       |  10 +
 guix/scripts/fork.scm              |  71 +++++++
 guix/scripts/fork/authenticate.scm | 331 +++++++++++++++++++++++++++++
 guix/scripts/fork/create.scm       | 257 ++++++++++++++++++++++
 guix/scripts/fork/update.scm       | 181 ++++++++++++++++
 guix/scripts/git/authenticate.scm  |  45 +---
 guix/utils.scm                     |  33 +++
 13 files changed, 1141 insertions(+), 41 deletions(-)
 create mode 100644 guix/scripts/fork.scm
 create mode 100644 guix/scripts/fork/authenticate.scm
 create mode 100644 guix/scripts/fork/create.scm
 create mode 100644 guix/scripts/fork/update.scm


base-commit: b85d20e853192a92093cd8d6a5756ec80e94c658
-- 
2.48.1





Information forwarded to guix-patches <at> gnu.org:
bug#75981; Package guix-patches. (Fri, 31 Jan 2025 21:20:02 GMT) Full text and rfc822 format available.

Message #8 received at 75981 <at> debbugs.gnu.org (full text, mbox):

From: 45mg <45mg.writes <at> gmail.com>
To: 75981 <at> debbugs.gnu.org
Cc: Nicolas Graves <ngraves <at> ngraves.fr>, Tomas Volf <~@wolfsden.cz>,
 45mg <45mg.writes <at> gmail.com>,
 Liliana Marie Prikler <liliana.prikler <at> gmail.com>,
 Ricardo Wurmus <rekado <at> elephly.net>, Attila Lendvai <attila <at> lendvai.name>
Subject: [PATCH (WIP) v1 1/4] Add 'guix fork create'.
Date: Sat,  1 Feb 2025 02:48:44 +0530
* guix/scripts/fork.scm, guix/scripts/fork/create.scm: New files.
* Makefile.am (MODULES): Add the new files.
* guix/build/utils.scm (invoke/stdout): New procedure.
* guix/utils.scm (chain-cut): New procedure.
* guix/scripts/git/authenticate.scm
(commit-short-id): Remove procedure, and use its existing duplicate in
guix/channels.scm.
(openpgp-fingerprint*, current-branch, show-stats): Move procedures to
the files below.
* guix/channels.scm (openpgp-fingerprint*): Moved here.
* guix/git.scm (repository-current-branch): Moved here and renamed from
'current-branch'.
* guix/git-authenticate.scm (show-authentication-stats): Moved here and
renamed from 'show-stats'.

Change-Id: I45ba37f434e136f6d496c741d9a933280f9ccf88
---
 Makefile.am                       |   2 +
 guix/build/utils.scm              |  20 +++
 guix/channels.scm                 |  13 ++
 guix/git-authenticate.scm         |  17 ++
 guix/git.scm                      |  10 ++
 guix/scripts/fork.scm             |  67 ++++++++
 guix/scripts/fork/create.scm      | 257 ++++++++++++++++++++++++++++++
 guix/scripts/git/authenticate.scm |  45 +-----
 guix/utils.scm                    |  33 ++++
 9 files changed, 423 insertions(+), 41 deletions(-)
 create mode 100644 guix/scripts/fork.scm
 create mode 100644 guix/scripts/fork/create.scm

diff --git a/Makefile.am b/Makefile.am
index f759803b8b..c628450a5a 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -377,6 +377,8 @@ MODULES =					\
   guix/scripts/size.scm				\
   guix/scripts/git.scm				\
   guix/scripts/git/authenticate.scm		\
+  guix/scripts/fork.scm			\
+  guix/scripts/fork/create.scm			\
   guix/scripts/graph.scm			\
   guix/scripts/weather.scm			\
   guix/scripts/container.scm			\
diff --git a/guix/build/utils.scm b/guix/build/utils.scm
index 94714bf397..e8bd39f5de 100644
--- a/guix/build/utils.scm
+++ b/guix/build/utils.scm
@@ -10,6 +10,8 @@
 ;;; Copyright © 2021, 2022 Maxime Devos <maximedevos <at> telenet.be>
 ;;; Copyright © 2021 Brendan Tildesley <mail <at> brendan.scot>
 ;;; Copyright © 2023 Carlo Zancanaro <carlo <at> zancanaro.id.au>
+;;; Copyright © 2025 Tomas Volf <~@wolfsden.cz>
+;;; Copyright © 2025 45mg <45mg.writes <at> gmail.com>
 ;;;
 ;;; This file is part of GNU Guix.
 ;;;
@@ -39,6 +41,7 @@ (define-module (guix build utils)
   #:use-module (ice-9 rdelim)
   #:use-module (ice-9 format)
   #:use-module (ice-9 threads)
+  #:use-module (ice-9 popen)
   #:use-module (rnrs bytevectors)
   #:use-module (rnrs io ports)
   #:re-export (alist-cons
@@ -128,6 +131,7 @@ (define-module (guix build utils)
             report-invoke-error
 
             invoke/quiet
+            invoke/stdout
 
             make-desktop-entry-file
 
@@ -889,6 +893,22 @@ (define (invoke/quiet program . args)
         (line
          (loop (cons line lines)))))))
 
+(define (invoke/stdout program . args)
+  "Invoke PROGRAM with ARGS and capture PROGRAM's standard output.  If PROGRAM
+succeeds, return its standard output as a string.  Otherwise, raise an
+'&invoke-error' condition."
+  (let* ((port (apply open-pipe* OPEN_READ program args))
+         (data (get-string-all port))
+         (code (close-pipe port)))
+    (unless (zero? code)
+      (raise (condition (&invoke-error
+                         (program program)
+                         (arguments args)
+                         (exit-status (status:exit-val code))
+                         (term-signal (status:term-sig code))
+                         (stop-signal (status:stop-sig code))))))
+    data))
+
 
 ;;;
 ;;; Text substitution (aka. sed).
diff --git a/guix/channels.scm b/guix/channels.scm
index 4700f7a45d..6ca8e64881 100644
--- a/guix/channels.scm
+++ b/guix/channels.scm
@@ -47,6 +47,7 @@ (define-module (guix channels)
   #:use-module (guix packages)
   #:use-module (guix progress)
   #:use-module (guix derivations)
+  #:autoload   (rnrs bytevectors) (bytevector-length)
   #:use-module (guix diagnostics)
   #:use-module (guix sets)
   #:use-module (guix store)
@@ -81,6 +82,7 @@ (define-module (guix channels)
 
             openpgp-fingerprint->bytevector
             openpgp-fingerprint
+            openpgp-fingerprint*
 
             %default-guix-channel
             %default-channels
@@ -171,6 +173,17 @@ (define-syntax openpgp-fingerprint
       ((_ str)
        #'(openpgp-fingerprint->bytevector str)))))
 
+(define (openpgp-fingerprint* str)
+  "Like openpgp-fingerprint, but with error handling from (guix diagnostics)."
+    (unless (string-every (char-set-union char-set:hex-digit
+                                          char-set:whitespace)
+                          str)
+      (leave (G_ "~a: invalid OpenPGP fingerprint~%") str))
+    (let ((fingerprint (openpgp-fingerprint str)))
+      (unless (= 20 (bytevector-length fingerprint))
+        (leave (G_ "~a: wrong length for OpenPGP fingerprint~%") str))
+      fingerprint))
+
 (define %guix-channel-introduction
   ;; Introduction of the official 'guix channel.  The chosen commit is the
   ;; first one that introduces '.guix-authorizations' on the 'staging'
diff --git a/guix/git-authenticate.scm b/guix/git-authenticate.scm
index 37c69d0880..8bc7fb6fb3 100644
--- a/guix/git-authenticate.scm
+++ b/guix/git-authenticate.scm
@@ -40,6 +40,7 @@ (define-module (guix git-authenticate)
   #:use-module (rnrs bytevectors)
   #:use-module (rnrs io ports)
   #:use-module (ice-9 match)
+  #:use-module (ice-9 format)
   #:autoload   (ice-9 pretty-print) (pretty-print)
   #:export (read-authorizations
             commit-signing-key
@@ -52,6 +53,7 @@ (define-module (guix git-authenticate)
 
             repository-cache-key
             authenticate-repository
+            show-authentication-stats
 
             git-authentication-error?
             git-authentication-error-commit
@@ -449,3 +451,18 @@ (define* (authenticate-repository repository start signer
                                       (oid->string (commit-id end-commit)))
 
           stats))))
+
+(define (show-authentication-stats stats)
+  "Display STATS, an alist containing commit signing stats as returned by
+'authenticate-repository'."
+  (format #t (G_ "Signing statistics:~%"))
+  (for-each (match-lambda
+              ((signer . count)
+               (format #t "  ~a ~10d~%"
+                       (openpgp-format-fingerprint
+                        (openpgp-public-key-fingerprint signer))
+                       count)))
+            (sort stats
+                  (match-lambda*
+                    (((_ . count1) (_ . count2))
+                     (> count1 count2))))))
diff --git a/guix/git.scm b/guix/git.scm
index 6ac6e4e3a2..afeacb53aa 100644
--- a/guix/git.scm
+++ b/guix/git.scm
@@ -59,6 +59,7 @@ (define-module (guix git)
             with-git-error-handling
             false-if-git-not-found
             repository-info
+            repository-current-branch
             update-cached-checkout
             url+commit->name
             latest-repository-commit
@@ -401,6 +402,15 @@ (define (repository-info directory)
     (lambda _
       (values #f #f #f))))
 
+(define (repository-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* (update-submodules repository
                             #:key (log-port (current-error-port))
                             (fetch-options #f))
diff --git a/guix/scripts/fork.scm b/guix/scripts/fork.scm
new file mode 100644
index 0000000000..2d97bcb93f
--- /dev/null
+++ b/guix/scripts/fork.scm
@@ -0,0 +1,67 @@
+;;; GNU Guix --- Functional package management for GNU
+;;; Copyright © 2025 45mg <45mg.writes <at> gmail.com>
+;;;
+;;; This file is part of GNU Guix.
+;;;
+;;; GNU Guix is free software; you can redistribute it and/or modify it
+;;; under the terms of the GNU General Public License as published by
+;;; the Free Software Foundation; either version 3 of the License, or (at
+;;; your option) any later version.
+;;;
+;;; GNU Guix is distributed in the hope that it will be useful, but
+;;; WITHOUT ANY WARRANTY; without even the implied warranty of
+;;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+;;; GNU General Public License for more details.
+;;;
+;;; You should have received a copy of the GNU General Public License
+;;; along with GNU Guix.  If not, see <http://www.gnu.org/licenses/>.
+
+(define-module (guix scripts fork)
+  #:use-module (ice-9 match)
+  #:use-module (guix ui)
+  #:use-module (guix scripts)
+  #:export (guix-fork))
+
+(define (show-help)
+  (display (G_ "Usage: guix fork ACTION ARGS...
+Create and manage authenticated forks of Guix.\n"))
+  (newline)
+  (display (G_ "The valid values for ACTION are:\n"))
+  (newline)
+  (display (G_ "\
+   create    set up a fork of Guix\n"))
+  (newline)
+  (display (G_ "
+  -h, --help             display this help and exit"))
+  (display (G_ "
+  -V, --version          display version information and exit"))
+  (newline)
+  (show-bug-report-information))
+
+(define %sub-commands '("create"))
+
+(define (resolve-sub-command name)
+  (let ((module (resolve-interface
+                 `(guix scripts fork ,(string->symbol name))))
+        (proc (string->symbol (string-append "guix-fork-" name))))
+    (module-ref module proc)))
+
+(define-command (guix-fork . args)
+  (category plumbing)
+  (synopsis "operate on Guix forks")
+
+  (with-error-handling
+    (match args
+      (()
+       (format (current-error-port)
+               (G_ "guix fork: missing sub-command~%")))
+      ((or ("-h") ("--help"))
+       (leave-on-EPIPE (show-help))
+       (exit 0))
+      ((or ("-V") ("--version"))
+       (show-version-and-exit "guix fork"))
+      ((sub-command args ...)
+       (if (member sub-command %sub-commands)
+           (apply (resolve-sub-command sub-command) args)
+           (format (current-error-port)
+                   (G_ "guix fork: invalid sub-command~%")))))))
diff --git a/guix/scripts/fork/create.scm b/guix/scripts/fork/create.scm
new file mode 100644
index 0000000000..8b5555947b
--- /dev/null
+++ b/guix/scripts/fork/create.scm
@@ -0,0 +1,257 @@
+;;; GNU Guix --- Functional package management for GNU
+;;; Copyright © 2025 Tomas Volf <~@wolfsden.cz>
+;;; Copyright © 2025 45mg <45mg.writes <at> gmail.com>
+;;;
+;;; This file is part of GNU Guix.
+;;;
+;;; GNU Guix is free software; you can redistribute it and/or modify it
+;;; under the terms of the GNU General Public License as published by
+;;; the Free Software Foundation; either version 3 of the License, or (at
+;;; your option) any later version.
+;;;
+;;; GNU Guix is distributed in the hope that it will be useful, but
+;;; WITHOUT ANY WARRANTY; without even the implied warranty of
+;;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+;;; GNU General Public License for more details.
+;;;
+;;; You should have received a copy of the GNU General Public License
+;;; along with GNU Guix.  If not, see <http://www.gnu.org/licenses/>.
+
+(define-module (guix scripts fork create)
+  #:use-module (guix ui)
+  #:use-module (guix scripts)
+  #:use-module ((guix utils) #:select (chain-cut))
+  #:use-module (guix build utils)
+  #:use-module (guix channels)
+  #:use-module (ice-9 exceptions)
+  #:use-module (ice-9 match)
+  #:use-module (ice-9 popen)
+  #:use-module (ice-9 pretty-print)
+  #:use-module (ice-9 string-fun)
+  #:use-module (ice-9 textual-ports)
+  #:use-module (srfi srfi-1)
+  #:use-module (srfi srfi-13)
+  #:use-module (srfi srfi-26)
+  #:use-module (srfi srfi-37)
+  #:use-module (srfi srfi-71)
+  #:export (guix-fork-create))
+
+;;; Commentary:
+;;;
+;;; Create a fork of Guix, by running a series of git commands.
+;;;
+;;; Code:
+
+(define %options
+  ;; Specifications of the command-line options.
+  (list (option '(#\h "help") #f #f
+                (lambda args
+                  (show-help)
+                  (exit 0)))
+        (option '(#\V "version") #f #f
+                (lambda args
+                  (show-version-and-exit "guix fork create")))
+        (option '("upstream") #t #f
+                (lambda (opt name arg result)
+                  (alist-cons 'upstream arg result)))
+        (option '("channel-url") #t #f
+                (lambda (opt name arg result)
+                  (alist-cons 'channel-url arg result)))
+        (option '("use-existing") #f #f
+                (lambda (opt name arg result)
+                  (alist-cons 'use-existing? #t result)))
+        (option '("git-parameter") #t #f
+                (lambda (opt name arg result)
+                  (let ((git-parameters (assoc-ref result 'git-parameters)))
+                    (if git-parameters
+                        (alist-cons 'git-parameters (cons arg git-parameters) result)
+                        (alist-cons 'git-parameters (list arg) result)))))))
+
+(define %default-options
+  `((upstream . ,(channel-url %default-guix-channel))))
+
+(define %usage
+  (format #f (G_ "Usage: guix fork create SIGNING_KEY [DIRECTORY OPTIONS...]
+Create a fork of Guix in DIRECTORY, using SIGNING_KEY to sign the introductory
+commit.
+DIRECTORY defaults to ./guix.
+
+      --upstream=URI     the repository to clone from
+                         (defaults to ~a)
+      --channel-url=URI  optional URI, used to replace the channel URL
+                         and the existing 'origin' remote (which is
+                         renamed to 'upstream')
+      --use-existing     Use existing clone of Guix in DIRECTORY
+      --git-parameter PARAMETER
+                         Specify configuration PARAMETER for git, via
+                         '-c' option (can pass multiple times)
+
+  -h, --help             display this help and exit
+  -V, --version          display version information and exit
+")
+      (channel-url %default-guix-channel)))
+
+(define (show-help)
+  (display %usage)
+  (newline)
+  (show-bug-report-information))
+
+(define (missing-arguments)
+    (leave (G_ "wrong number of arguments; \
+required SIGNING_KEY~%")))
+
+
+;;;
+;;; Helper prodecures.
+;;;
+
+(define (fingerprint->key-file-name fingerprint)
+  (let* ((listing (invoke/stdout "gpg" "--list-key" "--with-colons" fingerprint))
+         (uid (chain-cut listing
+                           (string-split <> #\newline)
+                           (filter (cut string-prefix? "uid:" <>) <>)
+                           first
+                           (string-split <> #\:)
+                           tenth))
+         (email-name (string-delete
+                      (cut eq? <> #\.)
+                      (substring uid
+                                 (1+ (or (string-index-right uid #\<)
+                                         -1))  ;no name in uid
+                                 (string-index uid #\@))))
+         (key-id (chain-cut listing
+                      (string-split <> #\newline)
+                      (filter (cut string-prefix? "pub:" <>) <>)
+                      car
+                      (string-split <> #\:)
+                      fifth
+                      (string-take-right <> 8))))
+    (string-append email-name "-" key-id ".key")))
+
+(define (update-channel-url file channel-url)
+  "Modify .guix_channel FILE.
+Change the channel url to CHANNEL-URL."
+  (let ((channel-data (call-with-input-file file read)))
+    (assq-set! (cdr channel-data) 'url (list channel-url))
+    (call-with-output-file file
+      (lambda (file)
+        (display ";; This is a Guix channel.\n\n" file)
+        (pretty-print channel-data file)))))
+
+(define (rewrite-authorizations file name fingerprint)
+  "Rewrite .guix-authorizations FILE to contain a single authorization
+consisting of NAME and FINGERPRINT."
+  (let ((auth-data (call-with-input-file file read)))
+    (list-set! auth-data (1- (length auth-data))
+               `((,fingerprint (name ,name))))
+    (call-with-output-file file
+      (lambda (file)
+        (display ";; This file, which is best viewed as -*- Scheme -*-, lists the OpenPGP keys
+;; currently authorized to sign commits in this fork branch.
+
+" file)
+        (pretty-print auth-data file)))))
+
+
+;;;
+;;; Entry point.
+;;;
+
+(define (guix-fork-create . args)
+  (define options
+    (parse-command-line args %options (list %default-options)
+                        #:build-options? #f))
+
+  (define (command-line-arguments lst)
+    (reverse (filter-map (match-lambda
+                           (('argument . arg) arg)
+                           (_ #f))
+                         lst)))
+
+  (with-error-handling
+    (let* ((signing-key directory (match (command-line-arguments options)
+                                    ((signing-key directory)
+                                     (values signing-key directory))
+                                    ((signing-key)
+                                     (values signing-key "guix"))
+                                    (_ (missing-arguments))))
+           (upstream (assoc-ref options 'upstream))
+           (channel-url (assoc-ref options 'channel-url))
+           (use-existing? (assoc-ref options 'use-existing?))
+           (git-parameters (assoc-ref options 'git-parameters))
+           (git-c-options  ;'("-c" "param1" "-c" "param2" ...)
+            (let loop ((opts '()) (params git-parameters))
+              (if (or (not params) (null-list? params))
+                  opts
+                  (loop (append
+                         opts (list "-c" (first params)))
+                        (drop params 1)))))
+
+           (key-file-name (fingerprint->key-file-name signing-key))
+           (introduction-name (car (string-split key-file-name #\-)))
+
+           (upstream-branch-name "master"))
+
+      (define (invoke-git . args)
+        (apply invoke `("git" ,@git-c-options "-C" ,directory ,@args)))
+
+      (unless use-existing?
+        (info (G_ "Cloning from upstream ~a...~%") upstream)
+        (invoke "git" "clone" upstream directory))
+
+      (info (G_ "Authenticating upstream commits...~%"))
+
+      (when channel-url
+        (info (G_ "Renaming existing 'origin' remote to 'upstream'...~%"))
+        (invoke-git "remote" "rename" "origin" "upstream")
+        (info (G_ "Using provided channel URL for new 'origin' remote...~%"))
+        (invoke-git "remote" "add" "origin" channel-url))
+
+      (set! upstream-branch-name
+            (chain-cut
+             (invoke/stdout "git"
+                            "-C" directory
+                            "symbolic-ref"
+                            (string-append "refs/remotes/"
+                                           (if channel-url "upstream" "origin")
+                                           "/HEAD"))
+             string-trim-right
+             (string-split <> #\/)
+             last))
+
+      (info (G_ "Adding key to keyring branch...~%"))
+      (invoke-git "switch" "keyring")
+      (invoke "gpg"
+              "--armor" "--export"
+              "-o" (string-append directory "/" key-file-name)
+              signing-key)
+      (invoke-git "add" "--" key-file-name)
+      (invoke-git "commit" "-m" "Add key for fork introduction.")
+
+      (info (G_ "Setting up fork branch...~%"))
+      (invoke-git "switch" "--create" "fork" "master")
+      (when channel-url
+        (update-channel-url (string-append directory "/.guix-channel")
+                            channel-url))
+      (rewrite-authorizations (string-append directory "/.guix-authorizations")
+                              introduction-name signing-key)
+      (invoke-git "add" "--"
+                  (string-append directory "/.guix-authorizations")
+                  (string-append directory "/.guix-channel"))
+      (invoke-git "commit"
+                  (string-append "--gpg-sign=" signing-key)
+                  "-m"
+                  (string-append
+                   "Initial fork commit.\n\n"
+                   ".guix-authorizations: Allow only " introduction-name "'s key."
+                   (if channel-url
+                       "\n.guix-channels: Update channel URL."
+                       "")))
+
+      (info (G_ "Successfully created Guix fork in ~a.
+You should run the following command next:
+guix fork authenticate ~a ~a ~a~%")
+            directory
+            upstream-branch-name
+            (string-trim-right (invoke/stdout "git" "-C" directory "rev-parse" "HEAD"))
+            signing-key))))
diff --git a/guix/scripts/git/authenticate.scm b/guix/scripts/git/authenticate.scm
index e3ecb67c89..154aae9b14 100644
--- a/guix/scripts/git/authenticate.scm
+++ b/guix/scripts/git/authenticate.scm
@@ -23,8 +23,8 @@ (define-module (guix scripts git authenticate)
   #:use-module (guix git-authenticate)
   #:autoload   (guix openpgp) (openpgp-format-fingerprint
                                openpgp-public-key-fingerprint)
-  #:use-module ((guix channels) #:select (openpgp-fingerprint))
-  #:use-module ((guix git) #:select (with-git-error-handling))
+  #:use-module ((guix channels) #:select (openpgp-fingerprint*))
+  #:use-module ((guix git) #:select (with-git-error-handling commit-short-id repository-current-branch))
   #:use-module (guix progress)
   #:use-module (guix base64)
   #:autoload   (rnrs bytevectors) (bytevector-length)
@@ -76,15 +76,6 @@ (define %options
 (define %default-options
   '())
 
-(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
@@ -94,7 +85,7 @@ (define (config-value repository key)
                   ((_ exp)
                    (catch 'git-error (lambda () exp) (const #f))))))
     (let* ((config (repository-config repository))
-           (branch (current-branch repository)))
+           (branch (repository-current-branch repository)))
       ;; First try the BRANCH-specific value, then the generic one.`
       (or (and branch
                (false-if-git-error
@@ -194,21 +185,6 @@ (define (install-hooks repository)
       (warning (G_ "cannot determine where to install hooks\
  (Guile-Git too old?)~%"))))
 
-(define (show-stats stats)
-  "Display STATS, an alist containing commit signing stats as returned by
-'authenticate-repository'."
-  (format #t (G_ "Signing statistics:~%"))
-  (for-each (match-lambda
-              ((signer . count)
-               (format #t "  ~a ~10d~%"
-                       (openpgp-format-fingerprint
-                        (openpgp-public-key-fingerprint signer))
-                       count)))
-            (sort stats
-                  (match-lambda*
-                    (((_ . count1) (_ . count2))
-                     (> count1 count2))))))
-
 (define (show-help)
   (display (G_ "Usage: guix git authenticate COMMIT SIGNER [OPTIONS...]
 Authenticate the given Git checkout using COMMIT/SIGNER as its introduction.\n"))
@@ -251,19 +227,6 @@ (define (guix-git-authenticate . args)
                            (_ #f))
                          lst)))
 
-  (define commit-short-id
-    (compose (cut string-take <> 7) oid->string commit-id))
-
-  (define (openpgp-fingerprint* str)
-    (unless (string-every (char-set-union char-set:hex-digit
-                                          char-set:whitespace)
-                          str)
-      (leave (G_ "~a: invalid OpenPGP fingerprint~%") str))
-    (let ((fingerprint (openpgp-fingerprint str)))
-      (unless (= 20 (bytevector-length fingerprint))
-        (leave (G_ "~a: wrong length for OpenPGP fingerprint~%") str))
-      fingerprint))
-
   (define (make-reporter start-commit end-commit commits)
     (format (current-error-port)
             (G_ "Authenticating commits ~a to ~a (~h new \
@@ -321,7 +284,7 @@ (define (guix-git-authenticate . args)
          (install-hooks repository))
 
        (when (and show-stats? (not (null? stats)))
-         (show-stats stats))
+         (show-authentication-stats stats))
 
        (info (G_ "successfully authenticated commit ~a~%")
              (oid->string end))))))
diff --git a/guix/utils.scm b/guix/utils.scm
index b6cf5aea4f..e07e89c321 100644
--- a/guix/utils.scm
+++ b/guix/utils.scm
@@ -21,6 +21,8 @@
 ;;; Copyright © 2023 Zheng Junjie <873216071 <at> qq.com>
 ;;; Copyright © 2023 Foundation Devices, Inc. <hello <at> foundationdevices.com>
 ;;; Copyright © 2024 Herman Rimm <herman <at> rimm.ee>
+;;; Copyright © 2025 Tomas Volf <~@wolfsden.cz>
+;;; Copyright © 2025 45mg <45mg.writes <at> gmail.com>
 ;;;
 ;;; This file is part of GNU Guix.
 ;;;
@@ -163,6 +165,8 @@ (define-module (guix utils)
             call-with-compressed-output-port
             canonical-newline-port
 
+            chain-cut
+
             string-distance
             string-closest
 
@@ -1193,6 +1197,35 @@ (define-syntax current-source-directory
           ;; raising an error would upset Geiser users
           #f))))))
 
+
+;;;
+;;; Higher-order functions.
+;;;
+
+(define-syntax chain-cut
+  (lambda (x)
+    "Apply each successive form to the result of evaluating the previous one.
+Before applying, expand each form (op ...) to (cut op ...).
+
+Examples:
+
+    (chain-cut '(1 2 3) cdr car)
+ => (car (cdr '(1 2 3)))
+
+    (chain-cut 2 (- 3 <>) 1+)
+ => (1+ ((cut - 3 <>) 2))
+ => (1+ (- 3 2))
+"
+    (syntax-case x ()
+      ((chain-cut init op) (identifier? #'op)
+       #'(op init))
+      ((chain-cut init (op ...))
+       #'((cut op ...) init))
+      ((chain-cut init op op* ...) (identifier? #'op)
+       #'(chain-cut (op init) op* ...))
+      ((chain-cut init (op ...) op* ...)
+       #'(chain-cut ((cut op ...) init) op* ...)))))
+
 
 ;;;
 ;;; String comparison.
-- 
2.48.1





Information forwarded to guix-patches <at> gnu.org:
bug#75981; Package guix-patches. (Fri, 31 Jan 2025 21:21:02 GMT) Full text and rfc822 format available.

Message #11 received at 75981 <at> debbugs.gnu.org (full text, mbox):

From: 45mg <45mg.writes <at> gmail.com>
To: 75981 <at> debbugs.gnu.org
Cc: Nicolas Graves <ngraves <at> ngraves.fr>, Tomas Volf <~@wolfsden.cz>,
 45mg <45mg.writes <at> gmail.com>,
 Liliana Marie Prikler <liliana.prikler <at> gmail.com>,
 Ricardo Wurmus <rekado <at> elephly.net>, Attila Lendvai <attila <at> lendvai.name>
Subject: [PATCH (WIP) v1 2/4] Add 'guix fork authenticate'.
Date: Sat,  1 Feb 2025 02:48:45 +0530
* guix/scripts/fork/authenticate.scm: New file.
* Makefile.am (MODULES): Add the new file.
* guix/scripts/fork.scm
(show-help): Mention new command.
(%sub-commands): Add new command.

Change-Id: Ic34a1b3d1642cedce8d1ff5bae825df30e47755c
---
 Makefile.am                        |   1 +
 guix/scripts/fork.scm              |   6 +-
 guix/scripts/fork/authenticate.scm | 331 +++++++++++++++++++++++++++++
 3 files changed, 336 insertions(+), 2 deletions(-)
 create mode 100644 guix/scripts/fork/authenticate.scm

diff --git a/Makefile.am b/Makefile.am
index c628450a5a..1c1f5d84fd 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -379,6 +379,7 @@ MODULES =					\
   guix/scripts/git/authenticate.scm		\
   guix/scripts/fork.scm			\
   guix/scripts/fork/create.scm			\
+  guix/scripts/fork/authenticate.scm			\
   guix/scripts/graph.scm			\
   guix/scripts/weather.scm			\
   guix/scripts/container.scm			\
diff --git a/guix/scripts/fork.scm b/guix/scripts/fork.scm
index 2d97bcb93f..c5c7a59ba7 100644
--- a/guix/scripts/fork.scm
+++ b/guix/scripts/fork.scm
@@ -29,7 +29,9 @@ (define (show-help)
   (display (G_ "The valid values for ACTION are:\n"))
   (newline)
   (display (G_ "\
-   create    set up a fork of Guix\n"))
+   create          set up a fork of Guix\n"))
+  (display (G_ "\
+   authenticate    authenticate a fork of Guix\n"))
   (newline)
   (display (G_ "
   -h, --help             display this help and exit"))
@@ -38,7 +40,7 @@ (define (show-help)
   (newline)
   (show-bug-report-information))
 
-(define %sub-commands '("create"))
+(define %sub-commands '("create" "authenticate"))
 
 (define (resolve-sub-command name)
   (let ((module (resolve-interface
diff --git a/guix/scripts/fork/authenticate.scm b/guix/scripts/fork/authenticate.scm
new file mode 100644
index 0000000000..83d9d87d44
--- /dev/null
+++ b/guix/scripts/fork/authenticate.scm
@@ -0,0 +1,331 @@
+;;; GNU Guix --- Functional package management for GNU
+;;; Copyright © 2025 45mg <45mg.writes <at> gmail.com>
+;;;
+;;; This file is part of GNU Guix.
+;;;
+;;; GNU Guix is free software; you can redistribute it and/or modify it
+;;; under the terms of the GNU General Public License as published by
+;;; the Free Software Foundation; either version 3 of the License, or (at
+;;; your option) any later version.
+;;;
+;;; GNU Guix is distributed in the hope that it will be useful, but
+;;; WITHOUT ANY WARRANTY; without even the implied warranty of
+;;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+;;; GNU General Public License for more details.
+;;;
+;;; You should have received a copy of the GNU General Public License
+;;; along with GNU Guix.  If not, see <http://www.gnu.org/licenses/>.
+
+(define-module (guix scripts fork authenticate)
+  #:use-module (git)
+  #:use-module (guix git)
+  #:use-module (guix git-authenticate)
+  #:use-module (guix base16)
+  #:use-module (guix ui)
+  #:use-module (guix progress)
+  #:use-module (guix scripts)
+  #:use-module (guix build utils)
+  #:use-module (guix channels)
+  #:use-module (ice-9 exceptions)
+  #:use-module (ice-9 match)
+  #:use-module (ice-9 receive)
+  #:use-module (ice-9 popen)
+  #:use-module (ice-9 format)
+  #:use-module (ice-9 pretty-print)
+  #:use-module (ice-9 string-fun)
+  #:use-module (ice-9 textual-ports)
+  #:use-module (srfi srfi-1)
+  #:use-module (srfi srfi-13)
+  #:use-module (srfi srfi-26)
+  #:use-module (srfi srfi-37)
+  #:use-module (srfi srfi-71)
+  #:export (guix-fork-authenticate
+
+            fork-config-value
+            fork-configured?
+            fork-configured-keyring-reference
+            fork-configured-introduction))
+
+;;; Commentary:
+;;;
+;;; Authenticate a fork of Guix, in the same manner as `guix git
+;;; authenticate`.
+;;;
+;;; Code:
+
+(define %options
+  ;; Specifications of the command-line options.
+  (list (option '(#\h "help") #f #f
+                (lambda args
+                  (show-help)
+                  (exit 0)))
+        (option '(#\V "version") #f #f
+                (lambda args
+                  (show-version-and-exit "guix fork authenticate")))
+
+        (option '(#\r "repository") #t #f
+                (lambda (opt name arg result)
+                  (alist-cons 'directory arg result)))
+        (option '("upstream-commit") #f #f
+                (lambda (opt name arg result)
+                  (alist-cons 'upstream-commit (string->oid arg) result)))
+        (option '("upstream-signer") #f #f
+                (lambda (opt name arg result)
+                  (alist-cons 'upstream-signer (openpgp-fingerprint* arg) result)))
+
+        (option '(#\e "end") #t #f
+                (lambda (opt name arg result)
+                  (alist-cons 'end-commit (string->oid arg) result)))
+        (option '("upstream-end") #t #f
+                (lambda (opt name arg result)
+                  (alist-cons 'upstream-end-commit (string->oid arg) result)))
+        (option '(#\k "keyring") #t #f
+                (lambda (opt name arg result)
+                  (alist-cons 'keyring-reference arg result)))
+        (option '("upstream-keyring") #t #f
+                (lambda (opt name arg result)
+                  (alist-cons 'upstream-keyring arg result)))
+        (option '("cache-key") #t #f
+                (lambda (opt name arg result)
+                  (alist-cons 'cache-key arg result)))
+        (option '("historical-authorizations") #t #f
+                (lambda (opt name arg result)
+                  (alist-cons 'historical-authorizations arg
+                              result)))
+        (option '("stats") #f #f
+                (lambda (opt name arg result)
+                  (alist-cons 'show-stats? #t result)))))
+
+(define %default-options
+  (let ((introduction (channel-introduction %default-guix-channel)))
+    `((upstream-commit
+       . ,(string->oid (channel-introduction-first-signed-commit introduction)))
+      (upstream-signer
+       . ,(openpgp-fingerprint
+           (string-upcase
+            (bytevector->base16-string
+             (channel-introduction-first-commit-signer introduction)))))
+      (upstream-keyring
+       . "keyring"))))
+
+(define %usage
+  (format #f (G_ "Usage: guix fork authenticate UPSTREAM COMMIT SIGNER [OPTIONS...]
+Authenticate a fork of Guix, using COMMIT/SIGNER as the fork introduction.
+
+First, authenticate new commits from UPSTREAM, using Guix's default
+introduction. Then authenticate the remaining commits using the fork
+introduction.
+
+  -r, --repository=DIRECTORY
+                         Authenticate the Git repository in DIRECTORY
+
+      --upstream-commit=COMMIT
+      --upstream-signer=SIGNER
+                         Use COMMIT/SIGNER as the introduction for upstream
+                         Guix, overriding the default values
+                         ~a
+                        /~a
+                         (Guix's default introduction).
+
+  -k, --keyring=REFERENCE
+                         load keyring for fork commits from REFERENCE, a Git
+                         branch (default \"keyring\")
+      --upstream-keyring=REFERENCE
+                         load keyring for upstream commits from REFERENCE, a
+                         Git branch (default \"keyring\")
+      --end=COMMIT       authenticate fork commits up to COMMIT
+      --cache-key=KEY    cache authenticated commits under KEY
+      --historical-authorizations=FILE
+                         read historical authorizations from FILE
+      --stats            Display commit signing statistics upon completion
+
+  -h, --help             display this help and exit
+  -V, --version          display version information and exit
+")
+          (assoc-ref %default-options 'upstream-commit)
+          (assoc-ref %default-options 'upstream-signer)))
+
+(define (show-help)
+  (display %usage)
+  (newline)
+  (show-bug-report-information))
+
+(define (missing-arguments)
+  (leave (G_ "wrong number of arguments; \
+required UPSTREAM, COMMIT and SIGNER~%")))
+
+
+;;;
+;;; Helper prodecures.
+;;;
+
+(define (fork-config-value repository key)
+  "Return the config value associated with KEY in the
+'guix.fork-authentication' namespace in REPOSITORY, or #f if no such config
+was found."
+  (let* ((config (repository-config repository))
+         (branch (repository-current-branch repository)))
+    (catch 'git-error
+      (lambda ()
+        (config-entry-value
+         (config-get-entry config
+                           (string-append "guix.fork-authentication."
+                                          key))))
+      (const #f))))
+
+(define (fork-configured-introduction repository)
+  "Return three values: the upstream branch name, introductory commit, and
+signer fingerprint (strings) for this fork, as configured in REPOSITORY.
+Error out if any were missing."
+  (let* ((upstream-branch (fork-config-value repository "upstream-branch"))
+         (commit (fork-config-value repository "introduction-commit"))
+         (signer (fork-config-value repository "introduction-signer")))
+    (unless (and upstream-branch commit signer)
+      (leave (G_ "fork information in .git/config is incomplete;
+missing at least one of
+introduction-commit, introduction-signer, upstream-branch
+under [guix \"fork-authentication\"]")))
+    (values upstream-branch commit signer)))
+
+(define (fork-configured-keyring-reference repository)
+  "Return the keyring reference configured in REPOSITORY or #f if missing."
+  (fork-config-value repository "keyring"))
+
+(define (fork-configured? repository)
+  "Return true if REPOSITORY already contains fork introduction info in its
+'config' file."
+  (and (fork-config-value repository "upstream-branch")
+       (fork-config-value repository "introduction-commit")
+       (fork-config-value repository "introduction-signer")))
+
+(define* (record-fork-configuration
+          repository
+          #:key commit signer upstream-branch keyring-reference)
+  "Record COMMIT, SIGNER, UPSTREAM-BRANCH 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.fork-authentication.introduction-commit"
+                           commit)
+        (set-config-string config "guix.fork-authentication.introduction-signer"
+                           signer)
+        (set-config-string config "guix.fork-authentication.upstream-branch"
+                           upstream-branch)
+        (set-config-string config "guix.fork-authentication.keyring"
+                           keyring-reference)
+        (info (G_ "introduction, upstream branch and keyring recorded \
+in repository configuration file~%")))
+      (warning (G_ "could not record introduction and keyring configuration\
+ (Guile-Git too old?)~%"))))
+
+
+(define (guix-fork-authenticate . args)
+  (define options
+    (parse-command-line args %options (list %default-options)
+                        #:build-options? #f))
+
+  (define (command-line-arguments lst)
+    (reverse (filter-map (match-lambda
+                           (('argument . arg) arg)
+                           (_ #f))
+                         lst)))
+
+  (define (make-reporter start-commit end-commit commits)
+    (format (current-error-port)
+            (G_ "Authenticating commits ~a to ~a (~h new \
+commits)...~%")
+            (commit-short-id start-commit)
+            (commit-short-id end-commit)
+            (length commits))
+    (if (isatty? (current-error-port))
+        (progress-reporter/bar (length commits))
+        progress-reporter/silent))
+
+  (with-error-handling
+    (with-git-error-handling
+     ;; TODO: BUG: it doesn't recognize '~' in paths
+     ;; How to do 'realpath' in Guile?
+     (let* ((repository (repository-open (or (assoc-ref options 'directory)
+                                             (repository-discover "."))))
+            (upstream commit signer (match (command-line-arguments options)
+                                      ((upstream commit signer)
+                                       (values
+                                        (branch-lookup repository upstream)
+                                        (string->oid commit)
+                                        (openpgp-fingerprint* signer)))
+                                      (()
+                                       (receive (upstream commit signer)
+                                           (fork-configured-introduction repository)
+                                         (values
+                                          (branch-lookup repository upstream)
+                                          (string->oid commit)
+                                          (openpgp-fingerprint* signer))))
+                                      (_
+                                       (missing-arguments))))
+            (upstream-commit (assoc-ref options 'upstream-commit))
+            (upstream-signer (assoc-ref options 'upstream-signer))
+            (history (match (assoc-ref options 'historical-authorizations)
+                       (#f '())
+                       (file (call-with-input-file file
+                               read-authorizations))))
+            (keyring (or (assoc-ref options 'keyring-reference)
+                         (fork-configured-keyring-reference repository)
+                         "keyring"))
+            (upstream-keyring (assoc-ref options 'upstream-keyring))
+            (end (match (assoc-ref options 'end-commit)
+                   (#f  (reference-target
+                         (repository-head repository)))
+                   (oid oid)))
+            (upstream-end (match (assoc-ref options 'upstream-end-commit)
+                            (#f
+                             (reference-target upstream))
+                            (oid oid)))
+            (cache-key (or (assoc-ref options 'cache-key)
+                           (repository-cache-key repository)))
+            (show-stats? (assoc-ref options 'show-stats?)))
+
+       (define upstream-authentication-args
+         (filter identity
+                 (list
+                  (oid->string upstream-commit)
+                  (bytevector->base16-string upstream-signer)
+                  (string-append "--repository="
+                                 (repository-directory repository))
+                  (string-append "--end="
+                                 (oid->string upstream-end))
+                  (and upstream-keyring
+                       (string-append "--keyring="
+                                      upstream-keyring))
+                  (and show-stats? "--stats"))))
+
+       (info (G_ "calling `guix git authenticate` for branch ~a...~%")
+             (branch-name upstream))
+
+       (apply run-guix-command 'git "authenticate"
+              upstream-authentication-args)
+
+       (define fork-stats
+         (authenticate-repository
+          repository commit signer
+          #:end end
+          #:keyring-reference keyring
+          #:historical-authorizations history
+          #:cache-key cache-key
+          #:make-reporter make-reporter))
+
+       (unless (fork-configured? repository)
+         (record-fork-configuration repository
+                               #:commit (oid->string commit)
+                               #:signer (bytevector->base16-string signer)
+                               #:upstream-branch (branch-name upstream)
+                               #:keyring-reference keyring))
+
+       (when (and show-stats? (not (null? fork-stats)))
+         (show-authentication-stats fork-stats))
+
+       (info (G_ "successfully authenticated commit ~a~%")
+             (oid->string end))))))
-- 
2.48.1





Information forwarded to guix-patches <at> gnu.org:
bug#75981; Package guix-patches. (Fri, 31 Jan 2025 21:21:02 GMT) Full text and rfc822 format available.

Message #14 received at 75981 <at> debbugs.gnu.org (full text, mbox):

From: 45mg <45mg.writes <at> gmail.com>
To: 75981 <at> debbugs.gnu.org
Cc: Nicolas Graves <ngraves <at> ngraves.fr>, Tomas Volf <~@wolfsden.cz>,
 45mg <45mg.writes <at> gmail.com>,
 Liliana Marie Prikler <liliana.prikler <at> gmail.com>,
 Ricardo Wurmus <rekado <at> elephly.net>, Attila Lendvai <attila <at> lendvai.name>
Subject: [PATCH (WIP) v1 3/4] Add 'guix fork update'.
Date: Sat,  1 Feb 2025 02:48:46 +0530
* guix/scripts/fork/update.scm: New file.
* Makefile.am (MODULES): Add the new file.
* guix/scripts/fork.scm
(show-help): Mention new command.
(%sub-commands): Add new command.

Change-Id: I2017eb9a9286c02ca8bdf962bcbfe89d7607c413
---
 Makefile.am                  |   1 +
 guix/scripts/fork.scm        |   4 +-
 guix/scripts/fork/update.scm | 181 +++++++++++++++++++++++++++++++++++
 3 files changed, 185 insertions(+), 1 deletion(-)
 create mode 100644 guix/scripts/fork/update.scm

diff --git a/Makefile.am b/Makefile.am
index 1c1f5d84fd..8edd371ccd 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -380,6 +380,7 @@ MODULES =					\
   guix/scripts/fork.scm			\
   guix/scripts/fork/create.scm			\
   guix/scripts/fork/authenticate.scm			\
+  guix/scripts/fork/update.scm			\
   guix/scripts/graph.scm			\
   guix/scripts/weather.scm			\
   guix/scripts/container.scm			\
diff --git a/guix/scripts/fork.scm b/guix/scripts/fork.scm
index c5c7a59ba7..bf9c86e0aa 100644
--- a/guix/scripts/fork.scm
+++ b/guix/scripts/fork.scm
@@ -32,6 +32,8 @@ (define (show-help)
    create          set up a fork of Guix\n"))
   (display (G_ "\
    authenticate    authenticate a fork of Guix\n"))
+  (display (G_ "\
+   update          update a fork of Guix\n"))
   (newline)
   (display (G_ "
   -h, --help             display this help and exit"))
@@ -40,7 +42,7 @@ (define (show-help)
   (newline)
   (show-bug-report-information))
 
-(define %sub-commands '("create" "authenticate"))
+(define %sub-commands '("create" "authenticate" "update"))
 
 (define (resolve-sub-command name)
   (let ((module (resolve-interface
diff --git a/guix/scripts/fork/update.scm b/guix/scripts/fork/update.scm
new file mode 100644
index 0000000000..5aed337b85
--- /dev/null
+++ b/guix/scripts/fork/update.scm
@@ -0,0 +1,181 @@
+;;; GNU Guix --- Functional package management for GNU
+;;; Copyright © 2025 Tomas Volf <~@wolfsden.cz>
+;;; Copyright © 2025 45mg <45mg.writes <at> gmail.com>
+;;;
+;;; This file is part of GNU Guix.
+;;;
+;;; GNU Guix is free software; you can redistribute it and/or modify it
+;;; under the terms of the GNU General Public License as published by
+;;; the Free Software Foundation; either version 3 of the License, or (at
+;;; your option) any later version.
+;;;
+;;; GNU Guix is distributed in the hope that it will be useful, but
+;;; WITHOUT ANY WARRANTY; without even the implied warranty of
+;;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+;;; GNU General Public License for more details.
+;;;
+;;; You should have received a copy of the GNU General Public License
+;;; along with GNU Guix.  If not, see <http://www.gnu.org/licenses/>.
+
+(define-module (guix scripts fork update)
+  #:use-module (guix scripts fork authenticate)
+  #:use-module (git repository)
+  #:use-module (git structs)
+  #:use-module (git config)
+  #:use-module (guix ui)
+  #:use-module (guix scripts)
+  #:use-module (guix build utils)
+  #:use-module (guix channels)
+  #:use-module (ice-9 exceptions)
+  #:use-module (ice-9 match)
+  #:use-module (ice-9 popen)
+  #:use-module (ice-9 pretty-print)
+  #:use-module (ice-9 string-fun)
+  #:use-module (ice-9 textual-ports)
+  #:use-module (srfi srfi-1)
+  #:use-module (srfi srfi-13)
+  #:use-module (srfi srfi-26)
+  #:use-module (srfi srfi-37)
+  #:use-module (srfi srfi-71)
+  #:export (guix-fork-update))
+
+;;; Commentary:
+;;;
+;;; Update a fork of Guix created via `guix fork create` and authenticated via
+;;; `guix fork authenticate`, by applying new commits from the upstream branch
+;;; onto it.
+;;;
+;;; Code:
+
+(define %options
+  ;; Specifications of the command-line options.
+  (list (option '(#\h "help") #f #f
+                (lambda args
+                  (show-help)
+                  (exit 0)))
+        (option '(#\V "version") #f #f
+                (lambda args
+                  (show-version-and-exit "guix fork create")))
+
+        (option '( "fork-branch") #t #f
+                (lambda (opt name arg result)
+                  (alist-cons 'fork-branch-name arg result)))
+        (option '(#\r "repository") #t #f
+                (lambda (opt name arg result)
+                  (alist-cons 'directory arg result)))))
+
+(define %default-options
+  '())
+
+(define %usage
+  (G_ "Usage: guix fork update [OPTIONS...]
+Pull into this Guix fork's configured upstream branch, then apply new commits
+onto the current branch.
+
+  -r, --repository=DIRECTORY
+                         Act in the Git repository in DIRECTORY
+      --fork-branch=BRANCH
+                         Apply new commits onto BRANCH instead of the current
+                         branch
+
+  -h, --help             display this help and exit
+  -V, --version          display version information and exit
+"))
+
+(define (show-help)
+  (display %usage)
+  (newline)
+  (show-bug-report-information))
+
+(define (missing-arguments)
+    (leave (G_ "wrong number of arguments; \
+required ~%")))
+
+
+;;;
+;;; Entry point.
+;;;
+
+(define (guix-fork-update . args)
+
+  (define options
+    (parse-command-line args %options (list %default-options)
+                        #:build-options? #f))
+
+  (define (command-line-arguments lst)
+    (reverse (filter-map (match-lambda
+                           (('argument . arg) arg)
+                           (_ #f))
+                         lst)))
+
+  (define-syntax invoke-git
+    (lambda (x)
+      (syntax-case x ()
+        ((_ args ...)
+         #`(invoke "git" "-C" #,(datum->syntax x 'directory) args ...)))))
+
+  (define-syntax invoke-git/stdout
+    (lambda (x)
+      (syntax-case x ()
+        ((_ args ...)
+         #`(string-trim-right
+            (invoke/stdout "git" "-C" #,(datum->syntax x 'directory) args ...))))))
+
+  (with-error-handling
+    (let* ((directory (or (assoc-ref options 'directory) "."))
+           (current-branch-name (invoke-git/stdout
+                                 "branch"
+                                 "--show-current"))
+           (current-head-location (invoke-git/stdout
+                                   "rev-parse"
+                                   "HEAD"))
+           (fork-branch-name (or (assoc-ref options 'fork-branch-name)
+                                 (if (string= current-branch-name "")
+                                     (leave (G_ "no current branch and --fork-branch not given"))
+                                     current-branch-name)))
+
+           (repository (repository-open directory))
+           (upstream-branch-name introduction-commit introduction-signer
+                                 (if (fork-configured? repository)
+                                     (fork-configured-introduction
+                                      (repository-open directory))
+                                     (leave (G_ "fork not fully configured.
+(Did you remember to run `guix fork authenticate` first?)%~"))))
+           (upstream-branch-commit
+            (invoke-git/stdout "rev-parse" upstream-branch-name))
+           (new-upstream-branch-commit "")
+           (config (repository-config repository))
+           (signing-key
+            (or
+             (catch 'git-error
+               (lambda ()
+                 (config-entry-value
+                  (config-get-entry config "user.signingkey")))
+               (const #f))
+             (begin
+               (info (G_ "user.signingkey not set for this repository.~%"))
+               (info (G_ "Will attempt to sign commits with fork introduction key.~%"))
+               introduction-signer))))
+
+      (info (G_ "Pulling into '~a'...~%") upstream-branch-name)
+      (invoke-git "switch" upstream-branch-name)
+      (invoke-git "pull")
+      (set! new-upstream-branch-commit
+            (invoke-git/stdout "rev-parse" upstream-branch-name))
+
+      (info (G_ "Rebasing commits from '~a' to '~a' onto fork branch '~a'...~%")
+            upstream-branch-commit
+            new-upstream-branch-commit
+            fork-branch-name)
+      (invoke-git "rebase" "--rebase-merges"
+                  (string-append "--gpg-sign=" signing-key)
+                  fork-branch-name new-upstream-branch-commit)
+
+      (info (G_ "Resetting fork branch '~a' to latest rebased commit...~%")
+            fork-branch-name)
+      (invoke-git "branch" "--force" fork-branch-name "HEAD")
+
+      (invoke-git "checkout" (or current-branch-name current-head-location))
+
+      (info (G_ "Successfully updated Guix fork in ~a~%")
+            directory))))
-- 
2.48.1





Information forwarded to guix-patches <at> gnu.org:
bug#75981; Package guix-patches. (Fri, 31 Jan 2025 21:21:03 GMT) Full text and rfc822 format available.

Message #17 received at 75981 <at> debbugs.gnu.org (full text, mbox):

From: 45mg <45mg.writes <at> gmail.com>
To: 75981 <at> debbugs.gnu.org
Cc: Nicolas Graves <ngraves <at> ngraves.fr>, Tomas Volf <~@wolfsden.cz>,
 45mg <45mg.writes <at> gmail.com>,
 Liliana Marie Prikler <liliana.prikler <at> gmail.com>,
 Ricardo Wurmus <rekado <at> elephly.net>, Attila Lendvai <attila <at> lendvai.name>
Subject: [PATCH (WIP) v1 4/4] Document 'guix fork'.
Date: Sat,  1 Feb 2025 02:48:47 +0530
* doc/guix.texi (Invoking guix fork): New node.
* doc/contributing.texi (Using Your Own Patches): New node.

Change-Id: I06240f0fe8d1fe39f27130a72f5d0d92949c99da
---
 doc/contributing.texi |  50 ++++++++++++++
 doc/guix.texi         | 150 ++++++++++++++++++++++++++++++++++++++++++
 2 files changed, 200 insertions(+)

diff --git a/doc/contributing.texi b/doc/contributing.texi
index c94ae940fa..bd4fd6c2ac 100644
--- a/doc/contributing.texi
+++ b/doc/contributing.texi
@@ -35,6 +35,7 @@ Contributing
 * Making Decisions::            Collectively choosing the way forward.
 * Commit Access::               Pushing to the official repository.
 * Reviewing the Work of Others::  Some guidelines for sharing reviews.
+* Using Your Own Patches::      Using your own work before it's accepted.
 * Updating the Guix Package::   Updating the Guix package definition.
 * Deprecation Policy::          Commitments and tools for deprecation.
 * Writing Documentation::       Improving documentation in GNU Guix.
@@ -3095,6 +3096,55 @@ Reviewing the Work of Others
 have reviewed more easily by adding a @code{reviewed-looks-good} usertag
 for the @code{guix} user (@pxref{Debbugs Usertags}).
 
+@node Using Your Own Patches
+@section Using Your Own Patches
+
+If you've taken the time to contribute code to Guix, chances are that
+you want the changes you've made to be reflected in your own Guix
+installation as soon as possible. Maybe you've added a package you want,
+and you want to start using it @emph{right now}. Or you've fixed a bug
+that affects you, and you want it to @emph{go away}.
+
+As described in the preceding sections, all contributions to Guix first
+go through a review process to ensure code quality. Sometimes, this can
+take longer than one would like. Ideally, the pace of the review process
+should not prevent you from benefiting from your own work.
+
+One way to work around this issue is to create an additional channel of
+your own (@pxref{Creating a Channel}), and add your code to it. For
+certain kinds of contributions, such as adding a new package, this is
+fairly straightforward - simply copy your new package definition(s) into
+a new file in the channel, and remove them when your contribution is
+accepted.
+
+However, there may be cases where this is not convenient. Certain kinds
+of changes, such as those that need to modify existing Guix internals,
+may be more challenging to incorporate into a channel. Moreoever, the
+more substantial your contribution is, the more work it will be to do
+so.
+
+@cindex fork, of Guix
+For such cases, there is another option. Recall that the patch series
+that you sent (@pxref{Sending a Patch Series}) was created from a one or
+more commits on a checkout of the Guix repository (@pxref{Building from
+Git}). You could simply specify this repository (referred to as your
+`Guix fork', or simply `fork', from here onwards), and its relevant
+branch, as your `@code{guix}' channel (@pxref{Using a Custom Guix
+Channel}). Now `@code{guix pull}' will fetch your new commits, and
+you'll see the changes you made reflected in your Guix installation!
+
+However, there's a potential complication to this approach - the issue
+of authentication (@pxref{Channel Authentication}). If your fork only
+exists on your local filesystem (a `local fork'), then you probably
+don't need to worry about this, and can pull without authentication
+(@pxref{Invoking guix pull}). But other situations, such as a remotely
+hosted fork, may make it important for your fork to be authenticated, in
+the same way that all channels are expected to be.
+
+Guix provides a @command{guix fork} command in order to simplify and
+automate many details of creating and managing and authenticated
+fork. For more information, @pxref{Invoking guix fork}.
+
 @node Updating the Guix Package
 @section Updating the Guix Package
 
diff --git a/doc/guix.texi b/doc/guix.texi
index b1b6d98e74..bbb5666d0a 100644
--- a/doc/guix.texi
+++ b/doc/guix.texi
@@ -311,6 +311,7 @@ Top
 * Invoking guix pack::          Creating software bundles.
 * The GCC toolchain::           Working with languages supported by GCC.
 * Invoking guix git authenticate::  Authenticating Git repositories.
+* Invoking guix fork::          Creating and managing authenticated forks of Guix.
 
 Programming Interface
 
@@ -5930,6 +5931,7 @@ Development
 * Invoking guix pack::          Creating software bundles.
 * The GCC toolchain::           Working with languages supported by GCC.
 * Invoking guix git authenticate::  Authenticating Git repositories.
+* Invoking guix fork::          Creating and managing authenticated forks of Guix.
 @end menu
 
 @node Invoking guix shell
@@ -7534,6 +7536,154 @@ Invoking guix git authenticate
 @end table
 
 
+@node Invoking guix fork
+@section Invoking @command{guix fork}
+
+@cindex @command{guix fork}
+
+The @command{guix fork} command provides the means to quickly set up,
+authenticate, and keep up-to-date an authenticated fork of Guix. For
+more information on authentication of a Guix checkout, @pxref{Invoking
+guix git authenticate}.
+
+Its syntax is:
+
+guix fork ACTION ARGS...
+
+ACTION specifies the fork-related action to perform. Currently, the
+following values are supported:
+
+@table @code
+@item create SIGNING_KEY [DIRECTORY OPTIONS...]
+Create a fork of Guix in DIRECTORY, using SIGNING_KEY to sign the introductory
+commit.
+DIRECTORY defaults to ./guix.
+
+First, clone Guix into DIRECTORY, unless @code{--use-existing} is
+given. Then, add SIGNING_KEY to the `@code{keyring}' branch of the
+repository. Finally, create a new `@code{fork}' branch based starting
+from the default branch, whose initial commit authorizes SIGNING_KEY
+alone (by adding it to @file{.guix-authorizations}) and is signed by it.
+
+The new `@code{fork}' branch is intended to mirror upstream
+Guix. Updating the fork amounts to applying all new commits to it (see
+the `@code{update}' command below for further explanation). You can work
+on patches in branches based off of this one, in much the same way as
+you would base them on Guix's default branch - every commit from the
+latter will be present in the former.
+
+To @command{guix pull} your changes, you could create a `build' branch
+starting from the initial fork commit, onto which you can cherry-pick or
+rebase commits from patch branches. This branch can then be specified
+for the `@code{guix}' channel (@pxref{Using a Custom Guix Channel}).
+Updating this channel can be done by merging the `@code{fork}' branch
+into it.
+
+OPTIONS can be one or more of the following:
+
+@table @code
+@item --use-existing
+Use existing clone of Guix in DIRECTORY. This is useful if you've
+already created commits for a patch series (@pxref{Using Your Own
+Patches}). However, all commits to the default branch, as well as any
+branches that may be merged into it in the future, must have been signed
+with an authorized key; otherwise, authentication will fail later.
+@item --upstream=URI
+The repository to clone from. This defaults to the default URL for the
+Guix repository.
+@item --channel-url=URI
+Optional URI, which if given, will be used to replace the channel URL.
+Furthermore, the existing `origin' remote (which tracks
+`@code{upstream}') is renamed to `upstream', and a new `origin' remote
+is created to track URI.
+@item --git-parameter PARAMETER
+Specify configuration PARAMETER for git, via `-c' option. You can pass
+this option multiple times.
+@end table
+
+@cindex authentication, of Guix forks
+@item authenticate UPSTREAM COMMIT SIGNER [OPTIONS...]
+Authenticate a Guix fork, using COMMIT and SIGNER as the fork
+introduction.
+
+First, authenticate new commits from UPSTREAM, using Guix's default
+introduction. Then authenticate the remaining commits using the fork
+introduction.
+
+As with @code{guix git authenticate}, all three of UPSTREAM, COMMIT and
+SIGNER will be cached in .git/config, so that you don't need to specify
+them after the first time.
+
+OPTIONS can be one or more of the following:
+
+@table @code
+@item --repository=DIRECTORY
+@itemx -r DIRECTORY
+Authenticate the git repository in DIRECTORY, instead of the current
+directory.
+@item --upstream-commit=COMMIT
+@itemx --upstream-signer=SIGNER
+Use COMMIT/SIGNER as the introduction for upstream
+Guix, instead of Guix's default channel introduction.
+@item --keyring=REFERENCE
+@itemx -k REFERENCE
+Load keyring for fork commits from REFERENCE, a Git branch (default
+`@code{keyring}').
+@item --upstream-keyring=REFERENCE
+Load keyring for upstream commits from REFERENCE, a Git branch (default
+`@code{keyring}').
+@item --end=COMMIT
+Authenticate fork commits up to COMMIT.
+@item --upstream-end=COMMIT
+Authenticate upstream commits up to COMMIT.
+
+@item --cache-key=KEY
+@itemx --historical-authorizations=FILE
+@itemx --stats
+Identical to the correponding options in @command{guix git authenticate}
+(@pxref{Invoking guix git authenticate}).
+@end table
+
+@item update [OPTIONS...]
+Pull into this Guix fork's configured upstream branch (from running
+@command{guix fork authenticate}), then apply new commits onto the
+current branch.
+
+This approach may seem less convenient than simply merging the upstream
+branch into the fork branch. Indeed, it duplicates every upstream commit
+under a different commit hash, and applying a large number of commits
+can be slow. However, this is currently the only feasible approach due
+to the nature of Guix's authentication mechanism. Namely, merge commits
+can only be authenticated if both their parents are signed by an
+authorized key, meaning that you can only use the merge workflow if
+you're authorized to commit to upstream Guix.
+
+For mapping commits on the fork branch to their equivalents on the
+upstream branch, you can use @command{guix fork identify} (see below).
+
+OPTIONS can be one or more of the following:
+
+@table @code
+@item --repository=DIRECTORY
+@itemx -r DIRECTORY
+Act in the Git repository in DIRECTORY.
+@item --fork-branch=BRANCH
+Apply new commits onto BRANCH instead of the current branch.
+@end table
+
+@item identify
+Coming soon!
+
+Given a commit hash from upstream Guix, print its equivalent on the fork
+branch, or vice versa.
+This uses the 'Change-Id:' line added to commit messages by Guix's
+'commit-msg' hook.
+The first invocation of this command will be slow, as the entire set of
+corresponding commits is built up as a hash table, and then
+cached. Subsequent invocations should be nearly instant.
+
+@end table
+
 @c *********************************************************************
 @node Programming Interface
 @chapter Programming Interface
-- 
2.48.1





Information forwarded to guix-patches <at> gnu.org:
bug#75981; Package guix-patches. (Fri, 31 Jan 2025 21:25:02 GMT) Full text and rfc822 format available.

Message #20 received at 75981 <at> debbugs.gnu.org (full text, mbox):

From: 45mg <45mg.writes <at> gmail.com>
To: 75981 <at> debbugs.gnu.org 
Subject: [Tomas Volf] Re: [PATCH (WIP) 0/4] Add 'guix fork'.
Date: Fri, 31 Jan 2025 21:22:50 +0000
[Message part 1 (text/plain, inline)]
Forwarding this from the previous thread.

-------------------- Start of forwarded message --------------------
From: Tomas Volf <~@wolfsden.cz>
To: 45mg <45mg.writes <at> gmail.com>
Cc: 75975 <at> debbugs.gnu.org,  Liliana Marie Prikler
 <liliana.prikler <at> gmail.com>,  Ricardo Wurmus <rekado <at> elephly.net>,  Attila
 Lendvai <attila <at> lendvai.name>,  Nicolas Graves <ngraves <at> ngraves.fr>
Subject: Re: [PATCH (WIP) 0/4] Add 'guix fork'.
Date: Fri, 31 Jan 2025 21:51:29 +0100

[Message part 2 (text/plain, inline)]
45mg <45mg.writes <at> gmail.com> writes:

> The code here adapts certain procedures from Tomas Volf's original 'fork-guix'
> script [6]; namely: '-->', 'invoke/c', 'create-keyring-branch', 'git-C', and
> 'git-C/c'. That script is licensed under AGPL, so my understanding is that it,
> or the procedures I used from it, would need to be relicensed under GPLv3 to
> be included into Guix. Tomas - could you confirm here that you're willing to
> do so, as we discussed earlier? (Note that I didn't ask you about the last two
> of the five procedures above, since I hadn't used them yet at the
> time.)

I hereby declare the above mentioned procedures to be dual licensed
under AGPL-3.0-only and GPL-3.0-or-later.

That should remove any possible licensing issues for merging this
patch. ^_^

Tomas

-- 
There are only two hard things in Computer Science:
cache invalidation, naming things and off-by-one errors.
[signature.asc (application/pgp-signature, inline)]
[Message part 4 (text/plain, inline)]
-------------------- End of forwarded message --------------------

Information forwarded to ludo <at> gnu.org, maxim.cournoyer <at> gmail.com, guix-patches <at> gnu.org:
bug#75981; Package guix-patches. (Sat, 01 Feb 2025 11:45:01 GMT) Full text and rfc822 format available.

Message #23 received at 75981 <at> debbugs.gnu.org (full text, mbox):

From: 45mg <45mg.writes <at> gmail.com>
To: 75981 <at> debbugs.gnu.org
Cc: Nicolas Graves <ngraves <at> ngraves.fr>, Tomas Volf <~@wolfsden.cz>,
 45mg <45mg.writes <at> gmail.com>,
 Liliana Marie Prikler <liliana.prikler <at> gmail.com>,
 Ricardo Wurmus <rekado <at> elephly.net>, Attila Lendvai <attila <at> lendvai.name>
Subject: [PATCH (WIP) v1.5 0/4] Add 'guix fork'.
Date: Sat,  1 Feb 2025 17:13:21 +0530
Hi Guix,

This revision is the same as v1, with one difference - the procedure
'invoke/stdout' was moved from (guix build utils) to (guix utils). While it
probably belongs in the former file, the fact is that nearly every gexp starts
with `(with-imported-modules ((guix build utils)) #~(begin ...))` or something
similar, so changing that file will cause most derivations to change, which
will result in a world rebuild (I think that's what it's called? That term is
not in the manual...). So I moved it, added some TODO comments pointing out
where it should go, and didn't update the commit message changelogs.

This revision only exists so that I can apply it to my fork, `guix pull`, and
thereby have access to these commands in my CLI. So if anyone else wants to
use this patch - as opposed to just test it via pre-inst-env - then this is
what you should apply.

45mg (4):
  Add 'guix fork create'.
  Add 'guix fork authenticate'.
  Add 'guix fork update'.
  Document 'guix fork'.

 Makefile.am                        |   4 +
 doc/contributing.texi              |  50 +++++
 doc/guix.texi                      | 150 +++++++++++++
 guix/channels.scm                  |  13 ++
 guix/git-authenticate.scm          |  17 ++
 guix/git.scm                       |  10 +
 guix/scripts/fork.scm              |  71 +++++++
 guix/scripts/fork/authenticate.scm | 331 +++++++++++++++++++++++++++++
 guix/scripts/fork/create.scm       | 258 ++++++++++++++++++++++
 guix/scripts/fork/update.scm       | 182 ++++++++++++++++
 guix/scripts/git/authenticate.scm  |  45 +---
 guix/utils.scm                     |  61 ++++++
 12 files changed, 1151 insertions(+), 41 deletions(-)
 create mode 100644 guix/scripts/fork.scm
 create mode 100644 guix/scripts/fork/authenticate.scm
 create mode 100644 guix/scripts/fork/create.scm
 create mode 100644 guix/scripts/fork/update.scm


base-commit: b85d20e853192a92093cd8d6a5756ec80e94c658
-- 
2.48.1





Information forwarded to guix <at> cbaines.net, dev <at> jpoiret.xyz, ludo <at> gnu.org, othacehe <at> gnu.org, zimon.toutoune <at> gmail.com, me <at> tobias.gr, guix-patches <at> gnu.org:
bug#75981; Package guix-patches. (Sat, 01 Feb 2025 11:46:02 GMT) Full text and rfc822 format available.

Message #26 received at 75981 <at> debbugs.gnu.org (full text, mbox):

From: 45mg <45mg.writes <at> gmail.com>
To: 75981 <at> debbugs.gnu.org
Cc: Nicolas Graves <ngraves <at> ngraves.fr>, Tomas Volf <~@wolfsden.cz>,
 45mg <45mg.writes <at> gmail.com>,
 Liliana Marie Prikler <liliana.prikler <at> gmail.com>,
 Ricardo Wurmus <rekado <at> elephly.net>, Attila Lendvai <attila <at> lendvai.name>
Subject: [PATCH (WIP) v1.5 1/4] Add 'guix fork create'.
Date: Sat,  1 Feb 2025 17:13:23 +0530
* guix/scripts/fork.scm, guix/scripts/fork/create.scm: New files.
* Makefile.am (MODULES): Add the new files.
* guix/build/utils.scm (invoke/stdout): New procedure.
* guix/utils.scm (chain-cut): New procedure.
* guix/scripts/git/authenticate.scm
(commit-short-id): Remove procedure, and use its existing duplicate in
guix/channels.scm.
(openpgp-fingerprint*, current-branch, show-stats): Move procedures to
the files below.
* guix/channels.scm (openpgp-fingerprint*): Moved here.
* guix/git.scm (repository-current-branch): Moved here and renamed from
'current-branch'.
* guix/git-authenticate.scm (show-authentication-stats): Moved here and
renamed from 'show-stats'.

Change-Id: I45ba37f434e136f6d496c741d9a933280f9ccf88
---
 Makefile.am                       |   2 +
 guix/channels.scm                 |  13 ++
 guix/git-authenticate.scm         |  17 ++
 guix/git.scm                      |  10 ++
 guix/scripts/fork.scm             |  67 ++++++++
 guix/scripts/fork/create.scm      | 258 ++++++++++++++++++++++++++++++
 guix/scripts/git/authenticate.scm |  45 +-----
 guix/utils.scm                    |  61 +++++++
 8 files changed, 432 insertions(+), 41 deletions(-)
 create mode 100644 guix/scripts/fork.scm
 create mode 100644 guix/scripts/fork/create.scm

diff --git a/Makefile.am b/Makefile.am
index f759803b8b..c628450a5a 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -377,6 +377,8 @@ MODULES =					\
   guix/scripts/size.scm				\
   guix/scripts/git.scm				\
   guix/scripts/git/authenticate.scm		\
+  guix/scripts/fork.scm			\
+  guix/scripts/fork/create.scm			\
   guix/scripts/graph.scm			\
   guix/scripts/weather.scm			\
   guix/scripts/container.scm			\
diff --git a/guix/channels.scm b/guix/channels.scm
index 4700f7a45d..6ca8e64881 100644
--- a/guix/channels.scm
+++ b/guix/channels.scm
@@ -47,6 +47,7 @@ (define-module (guix channels)
   #:use-module (guix packages)
   #:use-module (guix progress)
   #:use-module (guix derivations)
+  #:autoload   (rnrs bytevectors) (bytevector-length)
   #:use-module (guix diagnostics)
   #:use-module (guix sets)
   #:use-module (guix store)
@@ -81,6 +82,7 @@ (define-module (guix channels)
 
             openpgp-fingerprint->bytevector
             openpgp-fingerprint
+            openpgp-fingerprint*
 
             %default-guix-channel
             %default-channels
@@ -171,6 +173,17 @@ (define-syntax openpgp-fingerprint
       ((_ str)
        #'(openpgp-fingerprint->bytevector str)))))
 
+(define (openpgp-fingerprint* str)
+  "Like openpgp-fingerprint, but with error handling from (guix diagnostics)."
+    (unless (string-every (char-set-union char-set:hex-digit
+                                          char-set:whitespace)
+                          str)
+      (leave (G_ "~a: invalid OpenPGP fingerprint~%") str))
+    (let ((fingerprint (openpgp-fingerprint str)))
+      (unless (= 20 (bytevector-length fingerprint))
+        (leave (G_ "~a: wrong length for OpenPGP fingerprint~%") str))
+      fingerprint))
+
 (define %guix-channel-introduction
   ;; Introduction of the official 'guix channel.  The chosen commit is the
   ;; first one that introduces '.guix-authorizations' on the 'staging'
diff --git a/guix/git-authenticate.scm b/guix/git-authenticate.scm
index 37c69d0880..8bc7fb6fb3 100644
--- a/guix/git-authenticate.scm
+++ b/guix/git-authenticate.scm
@@ -40,6 +40,7 @@ (define-module (guix git-authenticate)
   #:use-module (rnrs bytevectors)
   #:use-module (rnrs io ports)
   #:use-module (ice-9 match)
+  #:use-module (ice-9 format)
   #:autoload   (ice-9 pretty-print) (pretty-print)
   #:export (read-authorizations
             commit-signing-key
@@ -52,6 +53,7 @@ (define-module (guix git-authenticate)
 
             repository-cache-key
             authenticate-repository
+            show-authentication-stats
 
             git-authentication-error?
             git-authentication-error-commit
@@ -449,3 +451,18 @@ (define* (authenticate-repository repository start signer
                                       (oid->string (commit-id end-commit)))
 
           stats))))
+
+(define (show-authentication-stats stats)
+  "Display STATS, an alist containing commit signing stats as returned by
+'authenticate-repository'."
+  (format #t (G_ "Signing statistics:~%"))
+  (for-each (match-lambda
+              ((signer . count)
+               (format #t "  ~a ~10d~%"
+                       (openpgp-format-fingerprint
+                        (openpgp-public-key-fingerprint signer))
+                       count)))
+            (sort stats
+                  (match-lambda*
+                    (((_ . count1) (_ . count2))
+                     (> count1 count2))))))
diff --git a/guix/git.scm b/guix/git.scm
index 6ac6e4e3a2..afeacb53aa 100644
--- a/guix/git.scm
+++ b/guix/git.scm
@@ -59,6 +59,7 @@ (define-module (guix git)
             with-git-error-handling
             false-if-git-not-found
             repository-info
+            repository-current-branch
             update-cached-checkout
             url+commit->name
             latest-repository-commit
@@ -401,6 +402,15 @@ (define (repository-info directory)
     (lambda _
       (values #f #f #f))))
 
+(define (repository-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* (update-submodules repository
                             #:key (log-port (current-error-port))
                             (fetch-options #f))
diff --git a/guix/scripts/fork.scm b/guix/scripts/fork.scm
new file mode 100644
index 0000000000..2d97bcb93f
--- /dev/null
+++ b/guix/scripts/fork.scm
@@ -0,0 +1,67 @@
+;;; GNU Guix --- Functional package management for GNU
+;;; Copyright © 2025 45mg <45mg.writes <at> gmail.com>
+;;;
+;;; This file is part of GNU Guix.
+;;;
+;;; GNU Guix is free software; you can redistribute it and/or modify it
+;;; under the terms of the GNU General Public License as published by
+;;; the Free Software Foundation; either version 3 of the License, or (at
+;;; your option) any later version.
+;;;
+;;; GNU Guix is distributed in the hope that it will be useful, but
+;;; WITHOUT ANY WARRANTY; without even the implied warranty of
+;;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+;;; GNU General Public License for more details.
+;;;
+;;; You should have received a copy of the GNU General Public License
+;;; along with GNU Guix.  If not, see <http://www.gnu.org/licenses/>.
+
+(define-module (guix scripts fork)
+  #:use-module (ice-9 match)
+  #:use-module (guix ui)
+  #:use-module (guix scripts)
+  #:export (guix-fork))
+
+(define (show-help)
+  (display (G_ "Usage: guix fork ACTION ARGS...
+Create and manage authenticated forks of Guix.\n"))
+  (newline)
+  (display (G_ "The valid values for ACTION are:\n"))
+  (newline)
+  (display (G_ "\
+   create    set up a fork of Guix\n"))
+  (newline)
+  (display (G_ "
+  -h, --help             display this help and exit"))
+  (display (G_ "
+  -V, --version          display version information and exit"))
+  (newline)
+  (show-bug-report-information))
+
+(define %sub-commands '("create"))
+
+(define (resolve-sub-command name)
+  (let ((module (resolve-interface
+                 `(guix scripts fork ,(string->symbol name))))
+        (proc (string->symbol (string-append "guix-fork-" name))))
+    (module-ref module proc)))
+
+(define-command (guix-fork . args)
+  (category plumbing)
+  (synopsis "operate on Guix forks")
+
+  (with-error-handling
+    (match args
+      (()
+       (format (current-error-port)
+               (G_ "guix fork: missing sub-command~%")))
+      ((or ("-h") ("--help"))
+       (leave-on-EPIPE (show-help))
+       (exit 0))
+      ((or ("-V") ("--version"))
+       (show-version-and-exit "guix fork"))
+      ((sub-command args ...)
+       (if (member sub-command %sub-commands)
+           (apply (resolve-sub-command sub-command) args)
+           (format (current-error-port)
+                   (G_ "guix fork: invalid sub-command~%")))))))
diff --git a/guix/scripts/fork/create.scm b/guix/scripts/fork/create.scm
new file mode 100644
index 0000000000..a9de204f23
--- /dev/null
+++ b/guix/scripts/fork/create.scm
@@ -0,0 +1,258 @@
+;;; GNU Guix --- Functional package management for GNU
+;;; Copyright © 2025 Tomas Volf <~@wolfsden.cz>
+;;; Copyright © 2025 45mg <45mg.writes <at> gmail.com>
+;;;
+;;; This file is part of GNU Guix.
+;;;
+;;; GNU Guix is free software; you can redistribute it and/or modify it
+;;; under the terms of the GNU General Public License as published by
+;;; the Free Software Foundation; either version 3 of the License, or (at
+;;; your option) any later version.
+;;;
+;;; GNU Guix is distributed in the hope that it will be useful, but
+;;; WITHOUT ANY WARRANTY; without even the implied warranty of
+;;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+;;; GNU General Public License for more details.
+;;;
+;;; You should have received a copy of the GNU General Public License
+;;; along with GNU Guix.  If not, see <http://www.gnu.org/licenses/>.
+
+(define-module (guix scripts fork create)
+  #:use-module (guix ui)
+  #:use-module (guix scripts)
+  #:use-module ((guix utils) #:select (chain-cut
+                                       invoke/stdout))  ;TODO move to (guix build utils)
+  #:use-module (guix build utils)
+  #:use-module (guix channels)
+  #:use-module (ice-9 exceptions)
+  #:use-module (ice-9 match)
+  #:use-module (ice-9 popen)
+  #:use-module (ice-9 pretty-print)
+  #:use-module (ice-9 string-fun)
+  #:use-module (ice-9 textual-ports)
+  #:use-module (srfi srfi-1)
+  #:use-module (srfi srfi-13)
+  #:use-module (srfi srfi-26)
+  #:use-module (srfi srfi-37)
+  #:use-module (srfi srfi-71)
+  #:export (guix-fork-create))
+
+;;; Commentary:
+;;;
+;;; Create a fork of Guix, by running a series of git commands.
+;;;
+;;; Code:
+
+(define %options
+  ;; Specifications of the command-line options.
+  (list (option '(#\h "help") #f #f
+                (lambda args
+                  (show-help)
+                  (exit 0)))
+        (option '(#\V "version") #f #f
+                (lambda args
+                  (show-version-and-exit "guix fork create")))
+        (option '("upstream") #t #f
+                (lambda (opt name arg result)
+                  (alist-cons 'upstream arg result)))
+        (option '("channel-url") #t #f
+                (lambda (opt name arg result)
+                  (alist-cons 'channel-url arg result)))
+        (option '("use-existing") #f #f
+                (lambda (opt name arg result)
+                  (alist-cons 'use-existing? #t result)))
+        (option '("git-parameter") #t #f
+                (lambda (opt name arg result)
+                  (let ((git-parameters (assoc-ref result 'git-parameters)))
+                    (if git-parameters
+                        (alist-cons 'git-parameters (cons arg git-parameters) result)
+                        (alist-cons 'git-parameters (list arg) result)))))))
+
+(define %default-options
+  `((upstream . ,(channel-url %default-guix-channel))))
+
+(define %usage
+  (format #f (G_ "Usage: guix fork create SIGNING_KEY [DIRECTORY OPTIONS...]
+Create a fork of Guix in DIRECTORY, using SIGNING_KEY to sign the introductory
+commit.
+DIRECTORY defaults to ./guix.
+
+      --upstream=URI     the repository to clone from
+                         (defaults to ~a)
+      --channel-url=URI  optional URI, used to replace the channel URL
+                         and the existing 'origin' remote (which is
+                         renamed to 'upstream')
+      --use-existing     Use existing clone of Guix in DIRECTORY
+      --git-parameter PARAMETER
+                         Specify configuration PARAMETER for git, via
+                         '-c' option (can pass multiple times)
+
+  -h, --help             display this help and exit
+  -V, --version          display version information and exit
+")
+      (channel-url %default-guix-channel)))
+
+(define (show-help)
+  (display %usage)
+  (newline)
+  (show-bug-report-information))
+
+(define (missing-arguments)
+    (leave (G_ "wrong number of arguments; \
+required SIGNING_KEY~%")))
+
+
+;;;
+;;; Helper prodecures.
+;;;
+
+(define (fingerprint->key-file-name fingerprint)
+  (let* ((listing (invoke/stdout "gpg" "--list-key" "--with-colons" fingerprint))
+         (uid (chain-cut listing
+                           (string-split <> #\newline)
+                           (filter (cut string-prefix? "uid:" <>) <>)
+                           first
+                           (string-split <> #\:)
+                           tenth))
+         (email-name (string-delete
+                      (cut eq? <> #\.)
+                      (substring uid
+                                 (1+ (or (string-index-right uid #\<)
+                                         -1))  ;no name in uid
+                                 (string-index uid #\@))))
+         (key-id (chain-cut listing
+                      (string-split <> #\newline)
+                      (filter (cut string-prefix? "pub:" <>) <>)
+                      car
+                      (string-split <> #\:)
+                      fifth
+                      (string-take-right <> 8))))
+    (string-append email-name "-" key-id ".key")))
+
+(define (update-channel-url file channel-url)
+  "Modify .guix_channel FILE.
+Change the channel url to CHANNEL-URL."
+  (let ((channel-data (call-with-input-file file read)))
+    (assq-set! (cdr channel-data) 'url (list channel-url))
+    (call-with-output-file file
+      (lambda (file)
+        (display ";; This is a Guix channel.\n\n" file)
+        (pretty-print channel-data file)))))
+
+(define (rewrite-authorizations file name fingerprint)
+  "Rewrite .guix-authorizations FILE to contain a single authorization
+consisting of NAME and FINGERPRINT."
+  (let ((auth-data (call-with-input-file file read)))
+    (list-set! auth-data (1- (length auth-data))
+               `((,fingerprint (name ,name))))
+    (call-with-output-file file
+      (lambda (file)
+        (display ";; This file, which is best viewed as -*- Scheme -*-, lists the OpenPGP keys
+;; currently authorized to sign commits in this fork branch.
+
+" file)
+        (pretty-print auth-data file)))))
+
+
+;;;
+;;; Entry point.
+;;;
+
+(define (guix-fork-create . args)
+  (define options
+    (parse-command-line args %options (list %default-options)
+                        #:build-options? #f))
+
+  (define (command-line-arguments lst)
+    (reverse (filter-map (match-lambda
+                           (('argument . arg) arg)
+                           (_ #f))
+                         lst)))
+
+  (with-error-handling
+    (let* ((signing-key directory (match (command-line-arguments options)
+                                    ((signing-key directory)
+                                     (values signing-key directory))
+                                    ((signing-key)
+                                     (values signing-key "guix"))
+                                    (_ (missing-arguments))))
+           (upstream (assoc-ref options 'upstream))
+           (channel-url (assoc-ref options 'channel-url))
+           (use-existing? (assoc-ref options 'use-existing?))
+           (git-parameters (assoc-ref options 'git-parameters))
+           (git-c-options  ;'("-c" "param1" "-c" "param2" ...)
+            (let loop ((opts '()) (params git-parameters))
+              (if (or (not params) (null-list? params))
+                  opts
+                  (loop (append
+                         opts (list "-c" (first params)))
+                        (drop params 1)))))
+
+           (key-file-name (fingerprint->key-file-name signing-key))
+           (introduction-name (car (string-split key-file-name #\-)))
+
+           (upstream-branch-name "master"))
+
+      (define (invoke-git . args)
+        (apply invoke `("git" ,@git-c-options "-C" ,directory ,@args)))
+
+      (unless use-existing?
+        (info (G_ "Cloning from upstream ~a...~%") upstream)
+        (invoke "git" "clone" upstream directory))
+
+      (info (G_ "Authenticating upstream commits...~%"))
+
+      (when channel-url
+        (info (G_ "Renaming existing 'origin' remote to 'upstream'...~%"))
+        (invoke-git "remote" "rename" "origin" "upstream")
+        (info (G_ "Using provided channel URL for new 'origin' remote...~%"))
+        (invoke-git "remote" "add" "origin" channel-url))
+
+      (set! upstream-branch-name
+            (chain-cut
+             (invoke/stdout "git"
+                            "-C" directory
+                            "symbolic-ref"
+                            (string-append "refs/remotes/"
+                                           (if channel-url "upstream" "origin")
+                                           "/HEAD"))
+             string-trim-right
+             (string-split <> #\/)
+             last))
+
+      (info (G_ "Adding key to keyring branch...~%"))
+      (invoke-git "switch" "keyring")
+      (invoke "gpg"
+              "--armor" "--export"
+              "-o" (string-append directory "/" key-file-name)
+              signing-key)
+      (invoke-git "add" "--" key-file-name)
+      (invoke-git "commit" "-m" "Add key for fork introduction.")
+
+      (info (G_ "Setting up fork branch...~%"))
+      (invoke-git "switch" "--create" "fork" "master")
+      (when channel-url
+        (update-channel-url (string-append directory "/.guix-channel")
+                            channel-url))
+      (rewrite-authorizations (string-append directory "/.guix-authorizations")
+                              introduction-name signing-key)
+      (invoke-git "add" "--"
+                  (string-append directory "/.guix-authorizations")
+                  (string-append directory "/.guix-channel"))
+      (invoke-git "commit"
+                  (string-append "--gpg-sign=" signing-key)
+                  "-m"
+                  (string-append
+                   "Initial fork commit.\n\n"
+                   ".guix-authorizations: Allow only " introduction-name "'s key."
+                   (if channel-url
+                       "\n.guix-channels: Update channel URL."
+                       "")))
+
+      (info (G_ "Successfully created Guix fork in ~a.
+You should run the following command next:
+guix fork authenticate ~a ~a ~a~%")
+            directory
+            upstream-branch-name
+            (string-trim-right (invoke/stdout "git" "-C" directory "rev-parse" "HEAD"))
+            signing-key))))
diff --git a/guix/scripts/git/authenticate.scm b/guix/scripts/git/authenticate.scm
index e3ecb67c89..154aae9b14 100644
--- a/guix/scripts/git/authenticate.scm
+++ b/guix/scripts/git/authenticate.scm
@@ -23,8 +23,8 @@ (define-module (guix scripts git authenticate)
   #:use-module (guix git-authenticate)
   #:autoload   (guix openpgp) (openpgp-format-fingerprint
                                openpgp-public-key-fingerprint)
-  #:use-module ((guix channels) #:select (openpgp-fingerprint))
-  #:use-module ((guix git) #:select (with-git-error-handling))
+  #:use-module ((guix channels) #:select (openpgp-fingerprint*))
+  #:use-module ((guix git) #:select (with-git-error-handling commit-short-id repository-current-branch))
   #:use-module (guix progress)
   #:use-module (guix base64)
   #:autoload   (rnrs bytevectors) (bytevector-length)
@@ -76,15 +76,6 @@ (define %options
 (define %default-options
   '())
 
-(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
@@ -94,7 +85,7 @@ (define (config-value repository key)
                   ((_ exp)
                    (catch 'git-error (lambda () exp) (const #f))))))
     (let* ((config (repository-config repository))
-           (branch (current-branch repository)))
+           (branch (repository-current-branch repository)))
       ;; First try the BRANCH-specific value, then the generic one.`
       (or (and branch
                (false-if-git-error
@@ -194,21 +185,6 @@ (define (install-hooks repository)
       (warning (G_ "cannot determine where to install hooks\
  (Guile-Git too old?)~%"))))
 
-(define (show-stats stats)
-  "Display STATS, an alist containing commit signing stats as returned by
-'authenticate-repository'."
-  (format #t (G_ "Signing statistics:~%"))
-  (for-each (match-lambda
-              ((signer . count)
-               (format #t "  ~a ~10d~%"
-                       (openpgp-format-fingerprint
-                        (openpgp-public-key-fingerprint signer))
-                       count)))
-            (sort stats
-                  (match-lambda*
-                    (((_ . count1) (_ . count2))
-                     (> count1 count2))))))
-
 (define (show-help)
   (display (G_ "Usage: guix git authenticate COMMIT SIGNER [OPTIONS...]
 Authenticate the given Git checkout using COMMIT/SIGNER as its introduction.\n"))
@@ -251,19 +227,6 @@ (define (guix-git-authenticate . args)
                            (_ #f))
                          lst)))
 
-  (define commit-short-id
-    (compose (cut string-take <> 7) oid->string commit-id))
-
-  (define (openpgp-fingerprint* str)
-    (unless (string-every (char-set-union char-set:hex-digit
-                                          char-set:whitespace)
-                          str)
-      (leave (G_ "~a: invalid OpenPGP fingerprint~%") str))
-    (let ((fingerprint (openpgp-fingerprint str)))
-      (unless (= 20 (bytevector-length fingerprint))
-        (leave (G_ "~a: wrong length for OpenPGP fingerprint~%") str))
-      fingerprint))
-
   (define (make-reporter start-commit end-commit commits)
     (format (current-error-port)
             (G_ "Authenticating commits ~a to ~a (~h new \
@@ -321,7 +284,7 @@ (define (guix-git-authenticate . args)
          (install-hooks repository))
 
        (when (and show-stats? (not (null? stats)))
-         (show-stats stats))
+         (show-authentication-stats stats))
 
        (info (G_ "successfully authenticated commit ~a~%")
              (oid->string end))))))
diff --git a/guix/utils.scm b/guix/utils.scm
index b6cf5aea4f..0d023e7729 100644
--- a/guix/utils.scm
+++ b/guix/utils.scm
@@ -21,6 +21,8 @@
 ;;; Copyright © 2023 Zheng Junjie <873216071 <at> qq.com>
 ;;; Copyright © 2023 Foundation Devices, Inc. <hello <at> foundationdevices.com>
 ;;; Copyright © 2024 Herman Rimm <herman <at> rimm.ee>
+;;; Copyright © 2025 Tomas Volf <~@wolfsden.cz>
+;;; Copyright © 2025 45mg <45mg.writes <at> gmail.com>
 ;;;
 ;;; This file is part of GNU Guix.
 ;;;
@@ -44,6 +46,8 @@ (define-module (guix utils)
   #:use-module (srfi srfi-11)
   #:use-module (srfi srfi-26)
   #:use-module (srfi srfi-71)
+  #:use-module (srfi srfi-35)  ;TODO remove after moving invoke/stdout
+  #:use-module (ice-9 popen)  ;TODO remove after moving invoke/stdout
   #:use-module (rnrs io ports)                    ;need 'port-position' etc.
   #:use-module ((rnrs bytevectors) #:select (bytevector-u8-set!))
   #:use-module (guix memoization)
@@ -163,6 +167,9 @@ (define-module (guix utils)
             call-with-compressed-output-port
             canonical-newline-port
 
+            chain-cut
+            invoke/stdout  ;TODO move to (guix build utils)
+
             string-distance
             string-closest
 
@@ -1193,6 +1200,60 @@ (define-syntax current-source-directory
           ;; raising an error would upset Geiser users
           #f))))))
 
+
+;;;
+;;; Higher-order functions.
+;;;
+
+(define-syntax chain-cut
+  (lambda (x)
+    "Apply each successive form to the result of evaluating the previous one.
+Before applying, expand each form (op ...) to (cut op ...).
+
+Examples:
+
+    (chain-cut '(1 2 3) cdr car)
+ => (car (cdr '(1 2 3)))
+
+    (chain-cut 2 (- 3 <>) 1+)
+ => (1+ ((cut - 3 <>) 2))
+ => (1+ (- 3 2))
+"
+    (syntax-case x ()
+      ((chain-cut init op) (identifier? #'op)
+       #'(op init))
+      ((chain-cut init (op ...))
+       #'((cut op ...) init))
+      ((chain-cut init op op* ...) (identifier? #'op)
+       #'(chain-cut (op init) op* ...))
+      ((chain-cut init (op ...) op* ...)
+       #'(chain-cut ((cut op ...) init) op* ...)))))
+
+;; Copied from (guix build utils); remove
+(define-condition-type &invoke-error &error
+  invoke-error?
+  (program      invoke-error-program)
+  (arguments    invoke-error-arguments)
+  (exit-status  invoke-error-exit-status)
+  (term-signal  invoke-error-term-signal)
+  (stop-signal  invoke-error-stop-signal))
+;; TODO move to (guix build utils)
+(define (invoke/stdout program . args)
+  "Invoke PROGRAM with ARGS and capture PROGRAM's standard output.  If PROGRAM
+succeeds, return its standard output as a string.  Otherwise, raise an
+'&invoke-error' condition."
+  (let* ((port (apply open-pipe* OPEN_READ program args))
+         (data (get-string-all port))
+         (code (close-pipe port)))
+    (unless (zero? code)
+      (raise (condition (&invoke-error
+                         (program program)
+                         (arguments args)
+                         (exit-status (status:exit-val code))
+                         (term-signal (status:term-sig code))
+                         (stop-signal (status:stop-sig code))))))
+    data))
+
 
 ;;;
 ;;; String comparison.
-- 
2.48.1





Information forwarded to guix <at> cbaines.net, dev <at> jpoiret.xyz, ludo <at> gnu.org, othacehe <at> gnu.org, zimon.toutoune <at> gmail.com, me <at> tobias.gr, guix-patches <at> gnu.org:
bug#75981; Package guix-patches. (Sat, 01 Feb 2025 11:46:02 GMT) Full text and rfc822 format available.

Message #29 received at 75981 <at> debbugs.gnu.org (full text, mbox):

From: 45mg <45mg.writes <at> gmail.com>
To: 75981 <at> debbugs.gnu.org
Cc: Nicolas Graves <ngraves <at> ngraves.fr>, Tomas Volf <~@wolfsden.cz>,
 45mg <45mg.writes <at> gmail.com>,
 Liliana Marie Prikler <liliana.prikler <at> gmail.com>,
 Ricardo Wurmus <rekado <at> elephly.net>, Attila Lendvai <attila <at> lendvai.name>
Subject: [PATCH (WIP) v1.5 2/4] Add 'guix fork authenticate'.
Date: Sat,  1 Feb 2025 17:13:24 +0530
* guix/scripts/fork/authenticate.scm: New file.
* Makefile.am (MODULES): Add the new file.
* guix/scripts/fork.scm
(show-help): Mention new command.
(%sub-commands): Add new command.

Change-Id: Ic34a1b3d1642cedce8d1ff5bae825df30e47755c
---
 Makefile.am                        |   1 +
 guix/scripts/fork.scm              |   6 +-
 guix/scripts/fork/authenticate.scm | 331 +++++++++++++++++++++++++++++
 3 files changed, 336 insertions(+), 2 deletions(-)
 create mode 100644 guix/scripts/fork/authenticate.scm

diff --git a/Makefile.am b/Makefile.am
index c628450a5a..1c1f5d84fd 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -379,6 +379,7 @@ MODULES =					\
   guix/scripts/git/authenticate.scm		\
   guix/scripts/fork.scm			\
   guix/scripts/fork/create.scm			\
+  guix/scripts/fork/authenticate.scm			\
   guix/scripts/graph.scm			\
   guix/scripts/weather.scm			\
   guix/scripts/container.scm			\
diff --git a/guix/scripts/fork.scm b/guix/scripts/fork.scm
index 2d97bcb93f..c5c7a59ba7 100644
--- a/guix/scripts/fork.scm
+++ b/guix/scripts/fork.scm
@@ -29,7 +29,9 @@ (define (show-help)
   (display (G_ "The valid values for ACTION are:\n"))
   (newline)
   (display (G_ "\
-   create    set up a fork of Guix\n"))
+   create          set up a fork of Guix\n"))
+  (display (G_ "\
+   authenticate    authenticate a fork of Guix\n"))
   (newline)
   (display (G_ "
   -h, --help             display this help and exit"))
@@ -38,7 +40,7 @@ (define (show-help)
   (newline)
   (show-bug-report-information))
 
-(define %sub-commands '("create"))
+(define %sub-commands '("create" "authenticate"))
 
 (define (resolve-sub-command name)
   (let ((module (resolve-interface
diff --git a/guix/scripts/fork/authenticate.scm b/guix/scripts/fork/authenticate.scm
new file mode 100644
index 0000000000..83d9d87d44
--- /dev/null
+++ b/guix/scripts/fork/authenticate.scm
@@ -0,0 +1,331 @@
+;;; GNU Guix --- Functional package management for GNU
+;;; Copyright © 2025 45mg <45mg.writes <at> gmail.com>
+;;;
+;;; This file is part of GNU Guix.
+;;;
+;;; GNU Guix is free software; you can redistribute it and/or modify it
+;;; under the terms of the GNU General Public License as published by
+;;; the Free Software Foundation; either version 3 of the License, or (at
+;;; your option) any later version.
+;;;
+;;; GNU Guix is distributed in the hope that it will be useful, but
+;;; WITHOUT ANY WARRANTY; without even the implied warranty of
+;;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+;;; GNU General Public License for more details.
+;;;
+;;; You should have received a copy of the GNU General Public License
+;;; along with GNU Guix.  If not, see <http://www.gnu.org/licenses/>.
+
+(define-module (guix scripts fork authenticate)
+  #:use-module (git)
+  #:use-module (guix git)
+  #:use-module (guix git-authenticate)
+  #:use-module (guix base16)
+  #:use-module (guix ui)
+  #:use-module (guix progress)
+  #:use-module (guix scripts)
+  #:use-module (guix build utils)
+  #:use-module (guix channels)
+  #:use-module (ice-9 exceptions)
+  #:use-module (ice-9 match)
+  #:use-module (ice-9 receive)
+  #:use-module (ice-9 popen)
+  #:use-module (ice-9 format)
+  #:use-module (ice-9 pretty-print)
+  #:use-module (ice-9 string-fun)
+  #:use-module (ice-9 textual-ports)
+  #:use-module (srfi srfi-1)
+  #:use-module (srfi srfi-13)
+  #:use-module (srfi srfi-26)
+  #:use-module (srfi srfi-37)
+  #:use-module (srfi srfi-71)
+  #:export (guix-fork-authenticate
+
+            fork-config-value
+            fork-configured?
+            fork-configured-keyring-reference
+            fork-configured-introduction))
+
+;;; Commentary:
+;;;
+;;; Authenticate a fork of Guix, in the same manner as `guix git
+;;; authenticate`.
+;;;
+;;; Code:
+
+(define %options
+  ;; Specifications of the command-line options.
+  (list (option '(#\h "help") #f #f
+                (lambda args
+                  (show-help)
+                  (exit 0)))
+        (option '(#\V "version") #f #f
+                (lambda args
+                  (show-version-and-exit "guix fork authenticate")))
+
+        (option '(#\r "repository") #t #f
+                (lambda (opt name arg result)
+                  (alist-cons 'directory arg result)))
+        (option '("upstream-commit") #f #f
+                (lambda (opt name arg result)
+                  (alist-cons 'upstream-commit (string->oid arg) result)))
+        (option '("upstream-signer") #f #f
+                (lambda (opt name arg result)
+                  (alist-cons 'upstream-signer (openpgp-fingerprint* arg) result)))
+
+        (option '(#\e "end") #t #f
+                (lambda (opt name arg result)
+                  (alist-cons 'end-commit (string->oid arg) result)))
+        (option '("upstream-end") #t #f
+                (lambda (opt name arg result)
+                  (alist-cons 'upstream-end-commit (string->oid arg) result)))
+        (option '(#\k "keyring") #t #f
+                (lambda (opt name arg result)
+                  (alist-cons 'keyring-reference arg result)))
+        (option '("upstream-keyring") #t #f
+                (lambda (opt name arg result)
+                  (alist-cons 'upstream-keyring arg result)))
+        (option '("cache-key") #t #f
+                (lambda (opt name arg result)
+                  (alist-cons 'cache-key arg result)))
+        (option '("historical-authorizations") #t #f
+                (lambda (opt name arg result)
+                  (alist-cons 'historical-authorizations arg
+                              result)))
+        (option '("stats") #f #f
+                (lambda (opt name arg result)
+                  (alist-cons 'show-stats? #t result)))))
+
+(define %default-options
+  (let ((introduction (channel-introduction %default-guix-channel)))
+    `((upstream-commit
+       . ,(string->oid (channel-introduction-first-signed-commit introduction)))
+      (upstream-signer
+       . ,(openpgp-fingerprint
+           (string-upcase
+            (bytevector->base16-string
+             (channel-introduction-first-commit-signer introduction)))))
+      (upstream-keyring
+       . "keyring"))))
+
+(define %usage
+  (format #f (G_ "Usage: guix fork authenticate UPSTREAM COMMIT SIGNER [OPTIONS...]
+Authenticate a fork of Guix, using COMMIT/SIGNER as the fork introduction.
+
+First, authenticate new commits from UPSTREAM, using Guix's default
+introduction. Then authenticate the remaining commits using the fork
+introduction.
+
+  -r, --repository=DIRECTORY
+                         Authenticate the Git repository in DIRECTORY
+
+      --upstream-commit=COMMIT
+      --upstream-signer=SIGNER
+                         Use COMMIT/SIGNER as the introduction for upstream
+                         Guix, overriding the default values
+                         ~a
+                        /~a
+                         (Guix's default introduction).
+
+  -k, --keyring=REFERENCE
+                         load keyring for fork commits from REFERENCE, a Git
+                         branch (default \"keyring\")
+      --upstream-keyring=REFERENCE
+                         load keyring for upstream commits from REFERENCE, a
+                         Git branch (default \"keyring\")
+      --end=COMMIT       authenticate fork commits up to COMMIT
+      --cache-key=KEY    cache authenticated commits under KEY
+      --historical-authorizations=FILE
+                         read historical authorizations from FILE
+      --stats            Display commit signing statistics upon completion
+
+  -h, --help             display this help and exit
+  -V, --version          display version information and exit
+")
+          (assoc-ref %default-options 'upstream-commit)
+          (assoc-ref %default-options 'upstream-signer)))
+
+(define (show-help)
+  (display %usage)
+  (newline)
+  (show-bug-report-information))
+
+(define (missing-arguments)
+  (leave (G_ "wrong number of arguments; \
+required UPSTREAM, COMMIT and SIGNER~%")))
+
+
+;;;
+;;; Helper prodecures.
+;;;
+
+(define (fork-config-value repository key)
+  "Return the config value associated with KEY in the
+'guix.fork-authentication' namespace in REPOSITORY, or #f if no such config
+was found."
+  (let* ((config (repository-config repository))
+         (branch (repository-current-branch repository)))
+    (catch 'git-error
+      (lambda ()
+        (config-entry-value
+         (config-get-entry config
+                           (string-append "guix.fork-authentication."
+                                          key))))
+      (const #f))))
+
+(define (fork-configured-introduction repository)
+  "Return three values: the upstream branch name, introductory commit, and
+signer fingerprint (strings) for this fork, as configured in REPOSITORY.
+Error out if any were missing."
+  (let* ((upstream-branch (fork-config-value repository "upstream-branch"))
+         (commit (fork-config-value repository "introduction-commit"))
+         (signer (fork-config-value repository "introduction-signer")))
+    (unless (and upstream-branch commit signer)
+      (leave (G_ "fork information in .git/config is incomplete;
+missing at least one of
+introduction-commit, introduction-signer, upstream-branch
+under [guix \"fork-authentication\"]")))
+    (values upstream-branch commit signer)))
+
+(define (fork-configured-keyring-reference repository)
+  "Return the keyring reference configured in REPOSITORY or #f if missing."
+  (fork-config-value repository "keyring"))
+
+(define (fork-configured? repository)
+  "Return true if REPOSITORY already contains fork introduction info in its
+'config' file."
+  (and (fork-config-value repository "upstream-branch")
+       (fork-config-value repository "introduction-commit")
+       (fork-config-value repository "introduction-signer")))
+
+(define* (record-fork-configuration
+          repository
+          #:key commit signer upstream-branch keyring-reference)
+  "Record COMMIT, SIGNER, UPSTREAM-BRANCH 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.fork-authentication.introduction-commit"
+                           commit)
+        (set-config-string config "guix.fork-authentication.introduction-signer"
+                           signer)
+        (set-config-string config "guix.fork-authentication.upstream-branch"
+                           upstream-branch)
+        (set-config-string config "guix.fork-authentication.keyring"
+                           keyring-reference)
+        (info (G_ "introduction, upstream branch and keyring recorded \
+in repository configuration file~%")))
+      (warning (G_ "could not record introduction and keyring configuration\
+ (Guile-Git too old?)~%"))))
+
+
+(define (guix-fork-authenticate . args)
+  (define options
+    (parse-command-line args %options (list %default-options)
+                        #:build-options? #f))
+
+  (define (command-line-arguments lst)
+    (reverse (filter-map (match-lambda
+                           (('argument . arg) arg)
+                           (_ #f))
+                         lst)))
+
+  (define (make-reporter start-commit end-commit commits)
+    (format (current-error-port)
+            (G_ "Authenticating commits ~a to ~a (~h new \
+commits)...~%")
+            (commit-short-id start-commit)
+            (commit-short-id end-commit)
+            (length commits))
+    (if (isatty? (current-error-port))
+        (progress-reporter/bar (length commits))
+        progress-reporter/silent))
+
+  (with-error-handling
+    (with-git-error-handling
+     ;; TODO: BUG: it doesn't recognize '~' in paths
+     ;; How to do 'realpath' in Guile?
+     (let* ((repository (repository-open (or (assoc-ref options 'directory)
+                                             (repository-discover "."))))
+            (upstream commit signer (match (command-line-arguments options)
+                                      ((upstream commit signer)
+                                       (values
+                                        (branch-lookup repository upstream)
+                                        (string->oid commit)
+                                        (openpgp-fingerprint* signer)))
+                                      (()
+                                       (receive (upstream commit signer)
+                                           (fork-configured-introduction repository)
+                                         (values
+                                          (branch-lookup repository upstream)
+                                          (string->oid commit)
+                                          (openpgp-fingerprint* signer))))
+                                      (_
+                                       (missing-arguments))))
+            (upstream-commit (assoc-ref options 'upstream-commit))
+            (upstream-signer (assoc-ref options 'upstream-signer))
+            (history (match (assoc-ref options 'historical-authorizations)
+                       (#f '())
+                       (file (call-with-input-file file
+                               read-authorizations))))
+            (keyring (or (assoc-ref options 'keyring-reference)
+                         (fork-configured-keyring-reference repository)
+                         "keyring"))
+            (upstream-keyring (assoc-ref options 'upstream-keyring))
+            (end (match (assoc-ref options 'end-commit)
+                   (#f  (reference-target
+                         (repository-head repository)))
+                   (oid oid)))
+            (upstream-end (match (assoc-ref options 'upstream-end-commit)
+                            (#f
+                             (reference-target upstream))
+                            (oid oid)))
+            (cache-key (or (assoc-ref options 'cache-key)
+                           (repository-cache-key repository)))
+            (show-stats? (assoc-ref options 'show-stats?)))
+
+       (define upstream-authentication-args
+         (filter identity
+                 (list
+                  (oid->string upstream-commit)
+                  (bytevector->base16-string upstream-signer)
+                  (string-append "--repository="
+                                 (repository-directory repository))
+                  (string-append "--end="
+                                 (oid->string upstream-end))
+                  (and upstream-keyring
+                       (string-append "--keyring="
+                                      upstream-keyring))
+                  (and show-stats? "--stats"))))
+
+       (info (G_ "calling `guix git authenticate` for branch ~a...~%")
+             (branch-name upstream))
+
+       (apply run-guix-command 'git "authenticate"
+              upstream-authentication-args)
+
+       (define fork-stats
+         (authenticate-repository
+          repository commit signer
+          #:end end
+          #:keyring-reference keyring
+          #:historical-authorizations history
+          #:cache-key cache-key
+          #:make-reporter make-reporter))
+
+       (unless (fork-configured? repository)
+         (record-fork-configuration repository
+                               #:commit (oid->string commit)
+                               #:signer (bytevector->base16-string signer)
+                               #:upstream-branch (branch-name upstream)
+                               #:keyring-reference keyring))
+
+       (when (and show-stats? (not (null? fork-stats)))
+         (show-authentication-stats fork-stats))
+
+       (info (G_ "successfully authenticated commit ~a~%")
+             (oid->string end))))))
-- 
2.48.1





Information forwarded to guix <at> cbaines.net, dev <at> jpoiret.xyz, ludo <at> gnu.org, othacehe <at> gnu.org, zimon.toutoune <at> gmail.com, me <at> tobias.gr, guix-patches <at> gnu.org:
bug#75981; Package guix-patches. (Sat, 01 Feb 2025 11:46:03 GMT) Full text and rfc822 format available.

Message #32 received at 75981 <at> debbugs.gnu.org (full text, mbox):

From: 45mg <45mg.writes <at> gmail.com>
To: 75981 <at> debbugs.gnu.org
Cc: Nicolas Graves <ngraves <at> ngraves.fr>, Tomas Volf <~@wolfsden.cz>,
 45mg <45mg.writes <at> gmail.com>,
 Liliana Marie Prikler <liliana.prikler <at> gmail.com>,
 Ricardo Wurmus <rekado <at> elephly.net>, Attila Lendvai <attila <at> lendvai.name>
Subject: [PATCH (WIP) v1.5 3/4] Add 'guix fork update'.
Date: Sat,  1 Feb 2025 17:13:25 +0530
* guix/scripts/fork/update.scm: New file.
* Makefile.am (MODULES): Add the new file.
* guix/scripts/fork.scm
(show-help): Mention new command.
(%sub-commands): Add new command.

Change-Id: I2017eb9a9286c02ca8bdf962bcbfe89d7607c413
---
 Makefile.am                  |   1 +
 guix/scripts/fork.scm        |   4 +-
 guix/scripts/fork/update.scm | 182 +++++++++++++++++++++++++++++++++++
 3 files changed, 186 insertions(+), 1 deletion(-)
 create mode 100644 guix/scripts/fork/update.scm

diff --git a/Makefile.am b/Makefile.am
index 1c1f5d84fd..8edd371ccd 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -380,6 +380,7 @@ MODULES =					\
   guix/scripts/fork.scm			\
   guix/scripts/fork/create.scm			\
   guix/scripts/fork/authenticate.scm			\
+  guix/scripts/fork/update.scm			\
   guix/scripts/graph.scm			\
   guix/scripts/weather.scm			\
   guix/scripts/container.scm			\
diff --git a/guix/scripts/fork.scm b/guix/scripts/fork.scm
index c5c7a59ba7..bf9c86e0aa 100644
--- a/guix/scripts/fork.scm
+++ b/guix/scripts/fork.scm
@@ -32,6 +32,8 @@ (define (show-help)
    create          set up a fork of Guix\n"))
   (display (G_ "\
    authenticate    authenticate a fork of Guix\n"))
+  (display (G_ "\
+   update          update a fork of Guix\n"))
   (newline)
   (display (G_ "
   -h, --help             display this help and exit"))
@@ -40,7 +42,7 @@ (define (show-help)
   (newline)
   (show-bug-report-information))
 
-(define %sub-commands '("create" "authenticate"))
+(define %sub-commands '("create" "authenticate" "update"))
 
 (define (resolve-sub-command name)
   (let ((module (resolve-interface
diff --git a/guix/scripts/fork/update.scm b/guix/scripts/fork/update.scm
new file mode 100644
index 0000000000..4223b9855c
--- /dev/null
+++ b/guix/scripts/fork/update.scm
@@ -0,0 +1,182 @@
+;;; GNU Guix --- Functional package management for GNU
+;;; Copyright © 2025 Tomas Volf <~@wolfsden.cz>
+;;; Copyright © 2025 45mg <45mg.writes <at> gmail.com>
+;;;
+;;; This file is part of GNU Guix.
+;;;
+;;; GNU Guix is free software; you can redistribute it and/or modify it
+;;; under the terms of the GNU General Public License as published by
+;;; the Free Software Foundation; either version 3 of the License, or (at
+;;; your option) any later version.
+;;;
+;;; GNU Guix is distributed in the hope that it will be useful, but
+;;; WITHOUT ANY WARRANTY; without even the implied warranty of
+;;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+;;; GNU General Public License for more details.
+;;;
+;;; You should have received a copy of the GNU General Public License
+;;; along with GNU Guix.  If not, see <http://www.gnu.org/licenses/>.
+
+(define-module (guix scripts fork update)
+  #:use-module (guix scripts fork authenticate)
+  #:use-module (git repository)
+  #:use-module (git structs)
+  #:use-module (git config)
+  #:use-module (guix ui)
+  #:use-module (guix scripts)
+  #:use-module ((guix utils) #:select (invoke/stdout))  ;TODO move invoke/stdout to (guix build utils)
+  #:use-module (guix build utils)
+  #:use-module (guix channels)
+  #:use-module (ice-9 exceptions)
+  #:use-module (ice-9 match)
+  #:use-module (ice-9 popen)
+  #:use-module (ice-9 pretty-print)
+  #:use-module (ice-9 string-fun)
+  #:use-module (ice-9 textual-ports)
+  #:use-module (srfi srfi-1)
+  #:use-module (srfi srfi-13)
+  #:use-module (srfi srfi-26)
+  #:use-module (srfi srfi-37)
+  #:use-module (srfi srfi-71)
+  #:export (guix-fork-update))
+
+;;; Commentary:
+;;;
+;;; Update a fork of Guix created via `guix fork create` and authenticated via
+;;; `guix fork authenticate`, by applying new commits from the upstream branch
+;;; onto it.
+;;;
+;;; Code:
+
+(define %options
+  ;; Specifications of the command-line options.
+  (list (option '(#\h "help") #f #f
+                (lambda args
+                  (show-help)
+                  (exit 0)))
+        (option '(#\V "version") #f #f
+                (lambda args
+                  (show-version-and-exit "guix fork create")))
+
+        (option '( "fork-branch") #t #f
+                (lambda (opt name arg result)
+                  (alist-cons 'fork-branch-name arg result)))
+        (option '(#\r "repository") #t #f
+                (lambda (opt name arg result)
+                  (alist-cons 'directory arg result)))))
+
+(define %default-options
+  '())
+
+(define %usage
+  (G_ "Usage: guix fork update [OPTIONS...]
+Pull into this Guix fork's configured upstream branch, then apply new commits
+onto the current branch.
+
+  -r, --repository=DIRECTORY
+                         Act in the Git repository in DIRECTORY
+      --fork-branch=BRANCH
+                         Apply new commits onto BRANCH instead of the current
+                         branch
+
+  -h, --help             display this help and exit
+  -V, --version          display version information and exit
+"))
+
+(define (show-help)
+  (display %usage)
+  (newline)
+  (show-bug-report-information))
+
+(define (missing-arguments)
+    (leave (G_ "wrong number of arguments; \
+required ~%")))
+
+
+;;;
+;;; Entry point.
+;;;
+
+(define (guix-fork-update . args)
+
+  (define options
+    (parse-command-line args %options (list %default-options)
+                        #:build-options? #f))
+
+  (define (command-line-arguments lst)
+    (reverse (filter-map (match-lambda
+                           (('argument . arg) arg)
+                           (_ #f))
+                         lst)))
+
+  (define-syntax invoke-git
+    (lambda (x)
+      (syntax-case x ()
+        ((_ args ...)
+         #`(invoke "git" "-C" #,(datum->syntax x 'directory) args ...)))))
+
+  (define-syntax invoke-git/stdout
+    (lambda (x)
+      (syntax-case x ()
+        ((_ args ...)
+         #`(string-trim-right
+            (invoke/stdout "git" "-C" #,(datum->syntax x 'directory) args ...))))))
+
+  (with-error-handling
+    (let* ((directory (or (assoc-ref options 'directory) "."))
+           (current-branch-name (invoke-git/stdout
+                                 "branch"
+                                 "--show-current"))
+           (current-head-location (invoke-git/stdout
+                                   "rev-parse"
+                                   "HEAD"))
+           (fork-branch-name (or (assoc-ref options 'fork-branch-name)
+                                 (if (string= current-branch-name "")
+                                     (leave (G_ "no current branch and --fork-branch not given"))
+                                     current-branch-name)))
+
+           (repository (repository-open directory))
+           (upstream-branch-name introduction-commit introduction-signer
+                                 (if (fork-configured? repository)
+                                     (fork-configured-introduction
+                                      (repository-open directory))
+                                     (leave (G_ "fork not fully configured.
+(Did you remember to run `guix fork authenticate` first?)%~"))))
+           (upstream-branch-commit
+            (invoke-git/stdout "rev-parse" upstream-branch-name))
+           (new-upstream-branch-commit "")
+           (config (repository-config repository))
+           (signing-key
+            (or
+             (catch 'git-error
+               (lambda ()
+                 (config-entry-value
+                  (config-get-entry config "user.signingkey")))
+               (const #f))
+             (begin
+               (info (G_ "user.signingkey not set for this repository.~%"))
+               (info (G_ "Will attempt to sign commits with fork introduction key.~%"))
+               introduction-signer))))
+
+      (info (G_ "Pulling into '~a'...~%") upstream-branch-name)
+      (invoke-git "switch" upstream-branch-name)
+      (invoke-git "pull")
+      (set! new-upstream-branch-commit
+            (invoke-git/stdout "rev-parse" upstream-branch-name))
+
+      (info (G_ "Rebasing commits from '~a' to '~a' onto fork branch '~a'...~%")
+            upstream-branch-commit
+            new-upstream-branch-commit
+            fork-branch-name)
+      (invoke-git "rebase" "--rebase-merges"
+                  (string-append "--gpg-sign=" signing-key)
+                  fork-branch-name new-upstream-branch-commit)
+
+      (info (G_ "Resetting fork branch '~a' to latest rebased commit...~%")
+            fork-branch-name)
+      (invoke-git "branch" "--force" fork-branch-name "HEAD")
+
+      (invoke-git "checkout" (or current-branch-name current-head-location))
+
+      (info (G_ "Successfully updated Guix fork in ~a~%")
+            directory))))
-- 
2.48.1





Information forwarded to ludo <at> gnu.org, maxim.cournoyer <at> gmail.com, guix-patches <at> gnu.org:
bug#75981; Package guix-patches. (Sat, 01 Feb 2025 11:46:03 GMT) Full text and rfc822 format available.

Message #35 received at 75981 <at> debbugs.gnu.org (full text, mbox):

From: 45mg <45mg.writes <at> gmail.com>
To: 75981 <at> debbugs.gnu.org
Cc: Nicolas Graves <ngraves <at> ngraves.fr>, Tomas Volf <~@wolfsden.cz>,
 45mg <45mg.writes <at> gmail.com>,
 Liliana Marie Prikler <liliana.prikler <at> gmail.com>,
 Ricardo Wurmus <rekado <at> elephly.net>, Attila Lendvai <attila <at> lendvai.name>
Subject: [PATCH (WIP) v1.5 4/4] Document 'guix fork'.
Date: Sat,  1 Feb 2025 17:13:26 +0530
* doc/guix.texi (Invoking guix fork): New node.
* doc/contributing.texi (Using Your Own Patches): New node.

Change-Id: I06240f0fe8d1fe39f27130a72f5d0d92949c99da
---
 doc/contributing.texi |  50 ++++++++++++++
 doc/guix.texi         | 150 ++++++++++++++++++++++++++++++++++++++++++
 2 files changed, 200 insertions(+)

diff --git a/doc/contributing.texi b/doc/contributing.texi
index c94ae940fa..bd4fd6c2ac 100644
--- a/doc/contributing.texi
+++ b/doc/contributing.texi
@@ -35,6 +35,7 @@ Contributing
 * Making Decisions::            Collectively choosing the way forward.
 * Commit Access::               Pushing to the official repository.
 * Reviewing the Work of Others::  Some guidelines for sharing reviews.
+* Using Your Own Patches::      Using your own work before it's accepted.
 * Updating the Guix Package::   Updating the Guix package definition.
 * Deprecation Policy::          Commitments and tools for deprecation.
 * Writing Documentation::       Improving documentation in GNU Guix.
@@ -3095,6 +3096,55 @@ Reviewing the Work of Others
 have reviewed more easily by adding a @code{reviewed-looks-good} usertag
 for the @code{guix} user (@pxref{Debbugs Usertags}).
 
+@node Using Your Own Patches
+@section Using Your Own Patches
+
+If you've taken the time to contribute code to Guix, chances are that
+you want the changes you've made to be reflected in your own Guix
+installation as soon as possible. Maybe you've added a package you want,
+and you want to start using it @emph{right now}. Or you've fixed a bug
+that affects you, and you want it to @emph{go away}.
+
+As described in the preceding sections, all contributions to Guix first
+go through a review process to ensure code quality. Sometimes, this can
+take longer than one would like. Ideally, the pace of the review process
+should not prevent you from benefiting from your own work.
+
+One way to work around this issue is to create an additional channel of
+your own (@pxref{Creating a Channel}), and add your code to it. For
+certain kinds of contributions, such as adding a new package, this is
+fairly straightforward - simply copy your new package definition(s) into
+a new file in the channel, and remove them when your contribution is
+accepted.
+
+However, there may be cases where this is not convenient. Certain kinds
+of changes, such as those that need to modify existing Guix internals,
+may be more challenging to incorporate into a channel. Moreoever, the
+more substantial your contribution is, the more work it will be to do
+so.
+
+@cindex fork, of Guix
+For such cases, there is another option. Recall that the patch series
+that you sent (@pxref{Sending a Patch Series}) was created from a one or
+more commits on a checkout of the Guix repository (@pxref{Building from
+Git}). You could simply specify this repository (referred to as your
+`Guix fork', or simply `fork', from here onwards), and its relevant
+branch, as your `@code{guix}' channel (@pxref{Using a Custom Guix
+Channel}). Now `@code{guix pull}' will fetch your new commits, and
+you'll see the changes you made reflected in your Guix installation!
+
+However, there's a potential complication to this approach - the issue
+of authentication (@pxref{Channel Authentication}). If your fork only
+exists on your local filesystem (a `local fork'), then you probably
+don't need to worry about this, and can pull without authentication
+(@pxref{Invoking guix pull}). But other situations, such as a remotely
+hosted fork, may make it important for your fork to be authenticated, in
+the same way that all channels are expected to be.
+
+Guix provides a @command{guix fork} command in order to simplify and
+automate many details of creating and managing and authenticated
+fork. For more information, @pxref{Invoking guix fork}.
+
 @node Updating the Guix Package
 @section Updating the Guix Package
 
diff --git a/doc/guix.texi b/doc/guix.texi
index b1b6d98e74..bbb5666d0a 100644
--- a/doc/guix.texi
+++ b/doc/guix.texi
@@ -311,6 +311,7 @@ Top
 * Invoking guix pack::          Creating software bundles.
 * The GCC toolchain::           Working with languages supported by GCC.
 * Invoking guix git authenticate::  Authenticating Git repositories.
+* Invoking guix fork::          Creating and managing authenticated forks of Guix.
 
 Programming Interface
 
@@ -5930,6 +5931,7 @@ Development
 * Invoking guix pack::          Creating software bundles.
 * The GCC toolchain::           Working with languages supported by GCC.
 * Invoking guix git authenticate::  Authenticating Git repositories.
+* Invoking guix fork::          Creating and managing authenticated forks of Guix.
 @end menu
 
 @node Invoking guix shell
@@ -7534,6 +7536,154 @@ Invoking guix git authenticate
 @end table
 
 
+@node Invoking guix fork
+@section Invoking @command{guix fork}
+
+@cindex @command{guix fork}
+
+The @command{guix fork} command provides the means to quickly set up,
+authenticate, and keep up-to-date an authenticated fork of Guix. For
+more information on authentication of a Guix checkout, @pxref{Invoking
+guix git authenticate}.
+
+Its syntax is:
+
+guix fork ACTION ARGS...
+
+ACTION specifies the fork-related action to perform. Currently, the
+following values are supported:
+
+@table @code
+@item create SIGNING_KEY [DIRECTORY OPTIONS...]
+Create a fork of Guix in DIRECTORY, using SIGNING_KEY to sign the introductory
+commit.
+DIRECTORY defaults to ./guix.
+
+First, clone Guix into DIRECTORY, unless @code{--use-existing} is
+given. Then, add SIGNING_KEY to the `@code{keyring}' branch of the
+repository. Finally, create a new `@code{fork}' branch based starting
+from the default branch, whose initial commit authorizes SIGNING_KEY
+alone (by adding it to @file{.guix-authorizations}) and is signed by it.
+
+The new `@code{fork}' branch is intended to mirror upstream
+Guix. Updating the fork amounts to applying all new commits to it (see
+the `@code{update}' command below for further explanation). You can work
+on patches in branches based off of this one, in much the same way as
+you would base them on Guix's default branch - every commit from the
+latter will be present in the former.
+
+To @command{guix pull} your changes, you could create a `build' branch
+starting from the initial fork commit, onto which you can cherry-pick or
+rebase commits from patch branches. This branch can then be specified
+for the `@code{guix}' channel (@pxref{Using a Custom Guix Channel}).
+Updating this channel can be done by merging the `@code{fork}' branch
+into it.
+
+OPTIONS can be one or more of the following:
+
+@table @code
+@item --use-existing
+Use existing clone of Guix in DIRECTORY. This is useful if you've
+already created commits for a patch series (@pxref{Using Your Own
+Patches}). However, all commits to the default branch, as well as any
+branches that may be merged into it in the future, must have been signed
+with an authorized key; otherwise, authentication will fail later.
+@item --upstream=URI
+The repository to clone from. This defaults to the default URL for the
+Guix repository.
+@item --channel-url=URI
+Optional URI, which if given, will be used to replace the channel URL.
+Furthermore, the existing `origin' remote (which tracks
+`@code{upstream}') is renamed to `upstream', and a new `origin' remote
+is created to track URI.
+@item --git-parameter PARAMETER
+Specify configuration PARAMETER for git, via `-c' option. You can pass
+this option multiple times.
+@end table
+
+@cindex authentication, of Guix forks
+@item authenticate UPSTREAM COMMIT SIGNER [OPTIONS...]
+Authenticate a Guix fork, using COMMIT and SIGNER as the fork
+introduction.
+
+First, authenticate new commits from UPSTREAM, using Guix's default
+introduction. Then authenticate the remaining commits using the fork
+introduction.
+
+As with @code{guix git authenticate}, all three of UPSTREAM, COMMIT and
+SIGNER will be cached in .git/config, so that you don't need to specify
+them after the first time.
+
+OPTIONS can be one or more of the following:
+
+@table @code
+@item --repository=DIRECTORY
+@itemx -r DIRECTORY
+Authenticate the git repository in DIRECTORY, instead of the current
+directory.
+@item --upstream-commit=COMMIT
+@itemx --upstream-signer=SIGNER
+Use COMMIT/SIGNER as the introduction for upstream
+Guix, instead of Guix's default channel introduction.
+@item --keyring=REFERENCE
+@itemx -k REFERENCE
+Load keyring for fork commits from REFERENCE, a Git branch (default
+`@code{keyring}').
+@item --upstream-keyring=REFERENCE
+Load keyring for upstream commits from REFERENCE, a Git branch (default
+`@code{keyring}').
+@item --end=COMMIT
+Authenticate fork commits up to COMMIT.
+@item --upstream-end=COMMIT
+Authenticate upstream commits up to COMMIT.
+
+@item --cache-key=KEY
+@itemx --historical-authorizations=FILE
+@itemx --stats
+Identical to the correponding options in @command{guix git authenticate}
+(@pxref{Invoking guix git authenticate}).
+@end table
+
+@item update [OPTIONS...]
+Pull into this Guix fork's configured upstream branch (from running
+@command{guix fork authenticate}), then apply new commits onto the
+current branch.
+
+This approach may seem less convenient than simply merging the upstream
+branch into the fork branch. Indeed, it duplicates every upstream commit
+under a different commit hash, and applying a large number of commits
+can be slow. However, this is currently the only feasible approach due
+to the nature of Guix's authentication mechanism. Namely, merge commits
+can only be authenticated if both their parents are signed by an
+authorized key, meaning that you can only use the merge workflow if
+you're authorized to commit to upstream Guix.
+
+For mapping commits on the fork branch to their equivalents on the
+upstream branch, you can use @command{guix fork identify} (see below).
+
+OPTIONS can be one or more of the following:
+
+@table @code
+@item --repository=DIRECTORY
+@itemx -r DIRECTORY
+Act in the Git repository in DIRECTORY.
+@item --fork-branch=BRANCH
+Apply new commits onto BRANCH instead of the current branch.
+@end table
+
+@item identify
+Coming soon!
+
+Given a commit hash from upstream Guix, print its equivalent on the fork
+branch, or vice versa.
+This uses the 'Change-Id:' line added to commit messages by Guix's
+'commit-msg' hook.
+The first invocation of this command will be slow, as the entire set of
+corresponding commits is built up as a hash table, and then
+cached. Subsequent invocations should be nearly instant.
+
+@end table
+
 @c *********************************************************************
 @node Programming Interface
 @chapter Programming Interface
-- 
2.48.1





Information forwarded to guix-patches <at> gnu.org:
bug#75981; Package guix-patches. (Sun, 02 Feb 2025 15:02:01 GMT) Full text and rfc822 format available.

Message #38 received at 75981 <at> debbugs.gnu.org (full text, mbox):

From: Maxim Cournoyer <maxim.cournoyer <at> gmail.com>
To: 45mg <45mg.writes <at> gmail.com>
Cc: Josselin Poiret <dev <at> jpoiret.xyz>, Nicolas Graves <ngraves <at> ngraves.fr>,
 Simon Tournier <zimon.toutoune <at> gmail.com>, Mathieu Othacehe <othacehe <at> gnu.org>,
 Tomas Volf <~@wolfsden.cz>, Tobias Geerinckx-Rice <me <at> tobias.gr>,
 Liliana Marie Prikler <liliana.prikler <at> gmail.com>, 75981 <at> debbugs.gnu.org,
 Ricardo Wurmus <rekado <at> elephly.net>, Christopher Baines <guix <at> cbaines.net>,
 Attila Lendvai <attila <at> lendvai.name>,
 Ludovic Courtès <ludo <at> gnu.org>
Subject: Re: [bug#75981] [PATCH (WIP) v1.5 1/4] Add 'guix fork create'.
Date: Mon, 03 Feb 2025 00:01:07 +0900
Hi,

My first thought was similar to Liliana's reply in the other
issue thread: putting lots of energy into making it convenient to fork
Guix instead of contributing to the review process (described as slow
and erratic, which appears to be the motivation here), appears
counter-productive.

So I'm not even sure this should be incorporated in Guix, especially if
it does touch the sensitive guix authentication mechanism.

I'll still offer a review, given the code looks rather good, and perhaps
being a committer I'm missing part of the picture on why such a
mechanism improves on the status quo of using extensions or channels, or
local unauthenticated forks (for personal use, that was enough for me
when one of my changes didn't make it for a year).  It was rather
inconvenient, but that was a good motivator to keep nudging it into Guix
proper.

And I disagree with your assessment that it takes years to become a Guix
committer.  I think 6 months to a year would be a reasonable time frame
for a dedicated individual.  It's also not the only way to be useful to
the project.  Reviewing the work of others help a lot too (those appear
at https://qa.guix.gnu.org/patches).

Below are some comments on the code.

45mg <45mg.writes <at> gmail.com> writes:

> * guix/scripts/fork.scm, guix/scripts/fork/create.scm: New files.
> * Makefile.am (MODULES): Add the new files.
> * guix/build/utils.scm (invoke/stdout): New procedure.

Touching (guix build utils) is a world rebuild (the module is included
in every build system, including the gnu-build-system).  Perhaps start
its life in (guix utils) with a TODO to move it to (guix build utils)
later along another world-rebuilding change.

Later: I see you changed that in this v1.5 revision: in this case just
update the change log message.

> * guix/utils.scm (chain-cut): New procedure.

Could use 's/New procedure./Likewise./' to avoid repetition.

> * guix/scripts/git/authenticate.scm
> (commit-short-id): Remove procedure, and use its existing duplicate in
> guix/channels.scm.
> (openpgp-fingerprint*, current-branch, show-stats): Move procedures to
> the files below.

You can use the ellipsis trick: (openpgp-fingerprint*): "Move to..."
* guix/channels.scm (openpgp-fingerprint*): ... here.

and likewise for the other procedures.

There's a missing entry for adjusting the renamed current-branch
procedure inside the config-value proc.

[...]

> diff --git a/guix/channels.scm b/guix/channels.scm
> index 4700f7a45d..6ca8e64881 100644
> --- a/guix/channels.scm
> +++ b/guix/channels.scm
> @@ -47,6 +47,7 @@ (define-module (guix channels)
>    #:use-module (guix packages)
>    #:use-module (guix progress)
>    #:use-module (guix derivations)
> +  #:autoload   (rnrs bytevectors) (bytevector-length)
>    #:use-module (guix diagnostics)
>    #:use-module (guix sets)
>    #:use-module (guix store)
> @@ -81,6 +82,7 @@ (define-module (guix channels)
>  
>              openpgp-fingerprint->bytevector
>              openpgp-fingerprint
> +            openpgp-fingerprint*
>  
>              %default-guix-channel
>              %default-channels
> @@ -171,6 +173,17 @@ (define-syntax openpgp-fingerprint
>        ((_ str)
>         #'(openpgp-fingerprint->bytevector str)))))
>  
> +(define (openpgp-fingerprint* str)
> +  "Like openpgp-fingerprint, but with error handling from (guix diagnostics)."
> +    (unless (string-every (char-set-union char-set:hex-digit
> +                                          char-set:whitespace)
> +                          str)
> +      (leave (G_ "~a: invalid OpenPGP fingerprint~%") str))
> +    (let ((fingerprint (openpgp-fingerprint str)))
> +      (unless (= 20 (bytevector-length fingerprint))
> +        (leave (G_ "~a: wrong length for OpenPGP fingerprint~%") str))
> +      fingerprint))
> +

I'm not convinced having a program-exiting procedure in the public API
makes sense (and these are annoying at the REPL!)  Returning a proper
exception would be better.

[...]

> --- /dev/null
> +++ b/guix/scripts/fork.scm

[...]

> +(define-module (guix scripts fork)
> +  #:use-module (ice-9 match)
> +  #:use-module (guix ui)
> +  #:use-module (guix scripts)

Please list modules in lexicographic order.

[...]

> diff --git a/guix/scripts/fork/create.scm b/guix/scripts/fork/create.scm
> new file mode 100644
> index 0000000000..a9de204f23
> --- /dev/null
> +++ b/guix/scripts/fork/create.scm

[...]

> +
> +;;;
> +;;; Helper prodecures.
> +;;;
> +
> +(define (fingerprint->key-file-name fingerprint)
> +  (let* ((listing (invoke/stdout "gpg" "--list-key" "--with-colons" fingerprint))
> +         (uid (chain-cut listing
> +                           (string-split <> #\newline)
> +                           (filter (cut string-prefix? "uid:" <>) <>)
> +                           first

If there are no key for FINGERPRINT, `first' will fail with a cryptic
error here.  It should ideally throw a useful exception.

[...]

> +
> +;;;
> +;;; Entry point.
> +;;;
> +
> +(define (guix-fork-create . args)
> +  (define options
> +    (parse-command-line args %options (list %default-options)
> +                        #:build-options? #f))

I think you could provide a proc to set the default value of the
DIRECTORY positional argument via #:argument-handler...

> +
> +  (define (command-line-arguments lst)
> +    (reverse (filter-map (match-lambda
> +                           (('argument . arg) arg)
> +                           (_ #f))
> +                         lst)))
> +
> +  (with-error-handling
> +    (let* ((signing-key directory (match (command-line-arguments options)
> +                                    ((signing-key directory)
> +                                     (values signing-key directory))
> +                                    ((signing-key)
> +                                     (values signing-key "guix"))
> +                                    (_ (missing-arguments))))

Avoiding the command-line-arguments proc as well as the match above.

> +           (upstream (assoc-ref options 'upstream))
> +           (channel-url (assoc-ref options 'channel-url))
> +           (use-existing? (assoc-ref options 'use-existing?))
> +           (git-parameters (assoc-ref options 'git-parameters))
> +           (git-c-options  ;'("-c" "param1" "-c" "param2" ...)
> +            (let loop ((opts '()) (params git-parameters))
> +              (if (or (not params) (null-list? params))
> +                  opts
> +                  (loop (append
> +                         opts (list "-c" (first params)))

You have enough horizontal space to (append opts ...) on a single line I
think.

> +                        (drop params 1)))))
> +
> +           (key-file-name (fingerprint->key-file-name signing-key))
> +           (introduction-name (car (string-split key-file-name #\-)))
> +
> +           (upstream-branch-name "master"))
> +
> +      (define (invoke-git . args)
> +        (apply invoke `("git" ,@git-c-options "-C" ,directory ,@args)))

We prefer to use guile-git throughout Guix, as it has a proper Scheme
interface.  Have you tried using it instead of shelling out to git?
Perhaps it was missing some features you needed?

> +      (unless use-existing?
> +        (info (G_ "Cloning from upstream ~a...~%") upstream)
> +        (invoke "git" "clone" upstream directory))

Why not using the above defined invoke-git here?

> +
> +      (info (G_ "Authenticating upstream commits...~%"))
> +
> +      (when channel-url
> +        (info (G_ "Renaming existing 'origin' remote to 'upstream'...~%"))
> +        (invoke-git "remote" "rename" "origin" "upstream")
> +        (info (G_ "Using provided channel URL for new 'origin' remote...~%"))
> +        (invoke-git "remote" "add" "origin" channel-url))
> +
> +      (set! upstream-branch-name
> +            (chain-cut
> +             (invoke/stdout "git"

Break the line to place "git" below invoke/stdout, to avoid busting our
80 columns max convention a bit below.

> +                            "-C" directory
> +                            "symbolic-ref"
> +                            (string-append "refs/remotes/"
> +                                           (if channel-url "upstream" "origin")
> +                                           "/HEAD"))
> +             string-trim-right
> +             (string-split <> #\/)
> +             last))
> +
> +      (info (G_ "Adding key to keyring branch...~%"))
> +      (invoke-git "switch" "keyring")
> +      (invoke "gpg"
> +              "--armor" "--export"
> +              "-o" (string-append directory "/" key-file-name)
> +              signing-key)
> +      (invoke-git "add" "--" key-file-name)
> +      (invoke-git "commit" "-m" "Add key for fork introduction.")
> +
> +      (info (G_ "Setting up fork branch...~%"))
> +      (invoke-git "switch" "--create" "fork" "master")
> +      (when channel-url
> +        (update-channel-url (string-append directory "/.guix-channel")
> +                            channel-url))
> +      (rewrite-authorizations (string-append directory "/.guix-authorizations")
> +                              introduction-name signing-key)
> +      (invoke-git "add" "--"
> +                  (string-append directory "/.guix-authorizations")
> +                  (string-append directory "/.guix-channel"))
> +      (invoke-git "commit"
> +                  (string-append "--gpg-sign=" signing-key)
> +                  "-m"
> +                  (string-append
> +                   "Initial fork commit.\n\n"
> +                   ".guix-authorizations: Allow only " introduction-name "'s key."
> +                   (if channel-url
> +                       "\n.guix-channels: Update channel URL."
> +                       "")))
> +      (info (G_ "Successfully created Guix fork in ~a.

Phew!

> +You should run the following command next:
> +guix fork authenticate ~a ~a ~a~%")
> +            directory
> +            upstream-branch-name
> +            (string-trim-right (invoke/stdout "git" "-C" directory "rev-parse" "HEAD"))
> +            signing-key))))
> diff --git a/guix/scripts/git/authenticate.scm b/guix/scripts/git/authenticate.scm
> index e3ecb67c89..154aae9b14 100644
> --- a/guix/scripts/git/authenticate.scm
> +++ b/guix/scripts/git/authenticate.scm
> @@ -23,8 +23,8 @@ (define-module (guix scripts git authenticate)
>    #:use-module (guix git-authenticate)
>    #:autoload   (guix openpgp) (openpgp-format-fingerprint
>                                 openpgp-public-key-fingerprint)
> -  #:use-module ((guix channels) #:select (openpgp-fingerprint))
> -  #:use-module ((guix git) #:select (with-git-error-handling))
> +  #:use-module ((guix channels) #:select (openpgp-fingerprint*))
> +  #:use-module ((guix git) #:select (with-git-error-handling commit-short-id repository-current-branch))

Please watch the 80 columns limit :-).

>    #:use-module (guix progress)
>    #:use-module (guix base64)
>    #:autoload   (rnrs bytevectors) (bytevector-length)
> @@ -76,15 +76,6 @@ (define %options
>  (define %default-options
>    '())
>  
> -(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
> @@ -94,7 +85,7 @@ (define (config-value repository key)
>                    ((_ exp)
>                     (catch 'git-error (lambda () exp) (const #f))))))
>      (let* ((config (repository-config repository))
> -           (branch (current-branch repository)))
> +           (branch (repository-current-branch repository)))
>        ;; First try the BRANCH-specific value, then the generic one.`
>        (or (and branch
>                 (false-if-git-error
> @@ -194,21 +185,6 @@ (define (install-hooks repository)
>        (warning (G_ "cannot determine where to install hooks\
>   (Guile-Git too old?)~%"))))
>  
> -(define (show-stats stats)
> -  "Display STATS, an alist containing commit signing stats as returned by
> -'authenticate-repository'."
> -  (format #t (G_ "Signing statistics:~%"))
> -  (for-each (match-lambda
> -              ((signer . count)
> -               (format #t "  ~a ~10d~%"
> -                       (openpgp-format-fingerprint
> -                        (openpgp-public-key-fingerprint signer))
> -                       count)))
> -            (sort stats
> -                  (match-lambda*
> -                    (((_ . count1) (_ . count2))
> -                     (> count1 count2))))))
> -
>  (define (show-help)
>    (display (G_ "Usage: guix git authenticate COMMIT SIGNER [OPTIONS...]
>  Authenticate the given Git checkout using COMMIT/SIGNER as its introduction.\n"))
> @@ -251,19 +227,6 @@ (define (guix-git-authenticate . args)
>                             (_ #f))
>                           lst)))
>  
> -  (define commit-short-id
> -    (compose (cut string-take <> 7) oid->string commit-id))
> -
> -  (define (openpgp-fingerprint* str)
> -    (unless (string-every (char-set-union char-set:hex-digit
> -                                          char-set:whitespace)
> -                          str)
> -      (leave (G_ "~a: invalid OpenPGP fingerprint~%") str))
> -    (let ((fingerprint (openpgp-fingerprint str)))
> -      (unless (= 20 (bytevector-length fingerprint))
> -        (leave (G_ "~a: wrong length for OpenPGP fingerprint~%") str))
> -      fingerprint))
> -

If that's only ever used here, I'd leave it here, as as I said earlier,
it's not a great API (and having confusing asterisk suffixes variants in
the public API should be limited to cases that truly matter, in my
opinion).

[...]

>  
> @@ -1193,6 +1200,60 @@ (define-syntax current-source-directory
>            ;; raising an error would upset Geiser users
>            #f))))))
>  
> +
> +;;;
> +;;; Higher-order functions.
> +;;;
> +
> +(define-syntax chain-cut
> +  (lambda (x)
> +    "Apply each successive form to the result of evaluating the previous one.
> +Before applying, expand each form (op ...) to (cut op ...).
> +
> +Examples:
> +
> +    (chain-cut '(1 2 3) cdr car)
> + => (car (cdr '(1 2 3)))
> +
> +    (chain-cut 2 (- 3 <>) 1+)
> + => (1+ ((cut - 3 <>) 2))
> + => (1+ (- 3 2))
> +"
> +    (syntax-case x ()
> +      ((chain-cut init op) (identifier? #'op)
> +       #'(op init))
> +      ((chain-cut init (op ...))
> +       #'((cut op ...) init))
> +      ((chain-cut init op op* ...) (identifier? #'op)
> +       #'(chain-cut (op init) op* ...))
> +      ((chain-cut init (op ...) op* ...)
> +       #'(chain-cut ((cut op ...) init) op* ...)))))

I'm not 100% convince on the above, as it seems it leads to bunching a
whole lot of procedures together and not paying attention to potential
exceptions/errors returned.  But maybe that's OK if the whole form is
wrapped in an error handler.

That's it!  This adding a whole new command line, and to get everyone
aware, I think going through the new GCD (Guix Common Document/RFC)
process is warranted before it is to be accepted/included in
Guix.

-- 
Thanks,
Maxim




Information forwarded to guix-patches <at> gnu.org:
bug#75981; Package guix-patches. (Sun, 02 Feb 2025 15:25:01 GMT) Full text and rfc822 format available.

Message #41 received at 75981 <at> debbugs.gnu.org (full text, mbox):

From: Maxim Cournoyer <maxim.cournoyer <at> gmail.com>
To: 45mg <45mg.writes <at> gmail.com>
Cc: Josselin Poiret <dev <at> jpoiret.xyz>, Nicolas Graves <ngraves <at> ngraves.fr>,
 Simon Tournier <zimon.toutoune <at> gmail.com>, Mathieu Othacehe <othacehe <at> gnu.org>,
 Tomas Volf <~@wolfsden.cz>, Tobias Geerinckx-Rice <me <at> tobias.gr>,
 Liliana Marie Prikler <liliana.prikler <at> gmail.com>, 75981 <at> debbugs.gnu.org,
 Ricardo Wurmus <rekado <at> elephly.net>, Christopher Baines <guix <at> cbaines.net>,
 Attila Lendvai <attila <at> lendvai.name>,
 Ludovic Courtès <ludo <at> gnu.org>
Subject: Re: [bug#75981] [PATCH (WIP) v1.5 2/4] Add 'guix fork authenticate'.
Date: Mon, 03 Feb 2025 00:23:41 +0900
Hi,

45mg <45mg.writes <at> gmail.com> writes:

> * guix/scripts/fork/authenticate.scm: New file.
> * Makefile.am (MODULES): Add the new file.
> * guix/scripts/fork.scm
> (show-help): Mention new command.
> (%sub-commands): Add new command.

OK.

[...]

> diff --git a/guix/scripts/fork/authenticate.scm b/guix/scripts/fork/authenticate.scm

[...]

> +(define-module (guix scripts fork authenticate)
> +  #:use-module (git)
> +  #:use-module (guix git)
> +  #:use-module (guix git-authenticate)
> +  #:use-module (guix base16)
> +  #:use-module (guix ui)
> +  #:use-module (guix progress)
> +  #:use-module (guix scripts)
> +  #:use-module (guix build utils)
> +  #:use-module (guix channels)
> +  #:use-module (ice-9 exceptions)
> +  #:use-module (ice-9 match)
> +  #:use-module (ice-9 receive)
> +  #:use-module (ice-9 popen)
> +  #:use-module (ice-9 format)
> +  #:use-module (ice-9 pretty-print)
> +  #:use-module (ice-9 string-fun)
> +  #:use-module (ice-9 textual-ports)

Please sort lexicographically.

[...]

> +(define %default-options
> +  (let ((introduction (channel-introduction %default-guix-channel)))
> +    `((upstream-commit
> +       . ,(string->oid (channel-introduction-first-signed-commit introduction)))

nitpick: a bit wide (83 chars)

> +      (upstream-signer
> +       . ,(openpgp-fingerprint
> +           (string-upcase
> +            (bytevector->base16-string
> +             (channel-introduction-first-commit-signer introduction)))))
> +      (upstream-keyring
> +       . "keyring"))))
> +
> +(define %usage
> +  (format #f (G_ "Usage: guix fork authenticate UPSTREAM COMMIT SIGNER [OPTIONS...]
> +Authenticate a fork of Guix, using COMMIT/SIGNER as the fork introduction.
> +
> +First, authenticate new commits from UPSTREAM, using Guix's default
> +introduction. Then authenticate the remaining commits using the fork
> +introduction.
> +
> +  -r, --repository=DIRECTORY
> +                         Authenticate the Git repository in DIRECTORY
> +
> +      --upstream-commit=COMMIT
> +      --upstream-signer=SIGNER
> +                         Use COMMIT/SIGNER as the introduction for upstream
> +                         Guix, overriding the default values
> +                         ~a
> +                        /~a
> +                         (Guix's default introduction).
> +
> +  -k, --keyring=REFERENCE
> +                         load keyring for fork commits from REFERENCE, a Git
> +                         branch (default \"keyring\")
> +      --upstream-keyring=REFERENCE
> +                         load keyring for upstream commits from REFERENCE, a
> +                         Git branch (default \"keyring\")
> +      --end=COMMIT       authenticate fork commits up to COMMIT
> +      --cache-key=KEY    cache authenticated commits under KEY
> +      --historical-authorizations=FILE
> +                         read historical authorizations from FILE
> +      --stats            Display commit signing statistics upon completion
> +
> +  -h, --help             display this help and exit
> +  -V, --version          display version information and exit
> +")
> +          (assoc-ref %default-options 'upstream-commit)
> +          (assoc-ref %default-options 'upstream-signer)))
> +
> +(define (show-help)
> +  (display %usage)
> +  (newline)
> +  (show-bug-report-information))
> +
> +(define (missing-arguments)
> +  (leave (G_ "wrong number of arguments; \
> +required UPSTREAM, COMMIT and SIGNER~%")))
> +
> +
> +;;;
> +;;; Helper prodecures.
> +;;;
> +
> +(define (fork-config-value repository key)
> +  "Return the config value associated with KEY in the
> +'guix.fork-authentication' namespace in REPOSITORY, or #f if no such config
> +was found."
> +  (let* ((config (repository-config repository))
> +         (branch (repository-current-branch repository)))
> +    (catch 'git-error
> +      (lambda ()
> +        (config-entry-value
> +         (config-get-entry config
> +                           (string-append "guix.fork-authentication."
> +                                          key))))
> +      (const #f))))
> +
> +(define (fork-configured-introduction repository)
> +  "Return three values: the upstream branch name, introductory commit, and
> +signer fingerprint (strings) for this fork, as configured in REPOSITORY.
> +Error out if any were missing."

s/were/are/

> +  (let* ((upstream-branch (fork-config-value repository "upstream-branch"))
> +         (commit (fork-config-value repository "introduction-commit"))
> +         (signer (fork-config-value repository "introduction-signer")))
> +    (unless (and upstream-branch commit signer)
> +      (leave (G_ "fork information in .git/config is incomplete;
> +missing at least one of
> +introduction-commit, introduction-signer, upstream-branch
> +under [guix \"fork-authentication\"]")))
> +    (values upstream-branch commit signer)))
> +
> +(define (fork-configured-keyring-reference repository)
> +  "Return the keyring reference configured in REPOSITORY or #f if missing."
> +  (fork-config-value repository "keyring"))
> +
> +(define (fork-configured? repository)
> +  "Return true if REPOSITORY already contains fork introduction info in its
> +'config' file."
> +  (and (fork-config-value repository "upstream-branch")
> +       (fork-config-value repository "introduction-commit")
> +       (fork-config-value repository "introduction-signer")))
> +
> +(define* (record-fork-configuration
> +          repository
> +          #:key commit signer upstream-branch keyring-reference)

nitpick: I'd leave the first arg on the same line, and put the keywords
on separate lines below.

> +  "Record COMMIT, SIGNER, UPSTREAM-BRANCH and KEYRING-REFERENCE in the
> +'config' file of REPOSITORY."

Should it say, .git/config file, for extra clarity?

> +  (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.fork-authentication.introduction-commit"
> +                           commit)
> +        (set-config-string config "guix.fork-authentication.introduction-signer"
> +                           signer)
> +        (set-config-string config "guix.fork-authentication.upstream-branch"
> +                           upstream-branch)
> +        (set-config-string config "guix.fork-authentication.keyring"
> +                           keyring-reference)
> +        (info (G_ "introduction, upstream branch and keyring recorded \
> +in repository configuration file~%")))
> +      (warning (G_ "could not record introduction and keyring configuration\
> + (Guile-Git too old?)~%"))))

That should be an error, not a warning, no?  Unless you think it's not
critical to what this tool is trying to achieve.

> +
> +(define (guix-fork-authenticate . args)
> +  (define options
> +    (parse-command-line args %options (list %default-options)
> +                        #:build-options? #f))
> +
> +  (define (command-line-arguments lst)
> +    (reverse (filter-map (match-lambda
> +                           (('argument . arg) arg)
> +                           (_ #f))
> +                         lst)))
> +
> +  (define (make-reporter start-commit end-commit commits)
> +    (format (current-error-port)
> +            (G_ "Authenticating commits ~a to ~a (~h new \
> +commits)...~%")
> +            (commit-short-id start-commit)
> +            (commit-short-id end-commit)
> +            (length commits))
> +    (if (isatty? (current-error-port))
> +        (progress-reporter/bar (length commits))
> +        progress-reporter/silent))
> +
> +  (with-error-handling
> +    (with-git-error-handling
> +     ;; TODO: BUG: it doesn't recognize '~' in paths
> +     ;; How to do 'realpath' in Guile?

It doesn't exist yet, as far as I know.  We have readlink* which
recursively expands links (guix utils).

> +     (let* ((repository (repository-open (or (assoc-ref options 'directory)
> +                                             (repository-discover "."))))
> +            (upstream commit signer (match (command-line-arguments options)

Perhaps break the line here to reduce horizontal indent.

> +                                      ((upstream commit signer)
> +                                       (values
> +                                        (branch-lookup repository upstream)
> +                                        (string->oid commit)
> +                                        (openpgp-fingerprint* signer)))
> +                                      (()
> +                                       (receive (upstream commit signer)
> +                                           (fork-configured-introduction repository)
> +                                         (values
> +                                          (branch-lookup repository upstream)
> +                                          (string->oid commit)
> +                                          (openpgp-fingerprint* signer))))
> +                                      (_
> +                                       (missing-arguments))))

Hm, I've looked at argument-handler again, and I guess it could be used,
though I'm not sure it'd simplify things by much.  Give it a try, if you want!

> +            (upstream-commit (assoc-ref options 'upstream-commit))
> +            (upstream-signer (assoc-ref options 'upstream-signer))
> +            (history (match (assoc-ref options 'historical-authorizations)
> +                       (#f '())
> +                       (file (call-with-input-file file
> +                               read-authorizations))))
> +            (keyring (or (assoc-ref options 'keyring-reference)
> +                         (fork-configured-keyring-reference repository)
> +                         "keyring"))
> +            (upstream-keyring (assoc-ref options 'upstream-keyring))
> +            (end (match (assoc-ref options 'end-commit)
> +                   (#f  (reference-target
> +                         (repository-head repository)))
> +                   (oid oid)))
> +            (upstream-end (match (assoc-ref options 'upstream-end-commit)
> +                            (#f
> +                             (reference-target upstream))
> +                            (oid oid)))
> +            (cache-key (or (assoc-ref options 'cache-key)
> +                           (repository-cache-key repository)))
> +            (show-stats? (assoc-ref options 'show-stats?)))
> +
> +       (define upstream-authentication-args
> +         (filter identity
> +                 (list
> +                  (oid->string upstream-commit)
> +                  (bytevector->base16-string upstream-signer)
> +                  (string-append "--repository="
> +                                 (repository-directory repository))
> +                  (string-append "--end="
> +                                 (oid->string upstream-end))
> +                  (and upstream-keyring
> +                       (string-append "--keyring="
> +                                      upstream-keyring))
> +                  (and show-stats? "--stats"))))
> +
> +       (info (G_ "calling `guix git authenticate` for branch ~a...~%")
> +             (branch-name upstream))
> +
> +       (apply run-guix-command 'git "authenticate"
> +              upstream-authentication-args)
> +
> +       (define fork-stats
> +         (authenticate-repository
> +          repository commit signer
> +          #:end end
> +          #:keyring-reference keyring
> +          #:historical-authorizations history
> +          #:cache-key cache-key
> +          #:make-reporter make-reporter))
> +
> +       (unless (fork-configured? repository)
> +         (record-fork-configuration repository
> +                               #:commit (oid->string commit)
> +                               #:signer (bytevector->base16-string signer)
> +                               #:upstream-branch (branch-name upstream)
> +                               #:keyring-reference keyring))
> +
> +       (when (and show-stats? (not (null? fork-stats)))
> +         (show-authentication-stats fork-stats))
> +
> +       (info (G_ "successfully authenticated commit ~a~%")
> +             (oid->string end))))))

The rest LGTM, from a cursory review.

-- 
Thanks,
Maxim




Information forwarded to guix-patches <at> gnu.org:
bug#75981; Package guix-patches. (Sun, 02 Feb 2025 16:23:02 GMT) Full text and rfc822 format available.

Message #44 received at 75981 <at> debbugs.gnu.org (full text, mbox):

From: Maxim Cournoyer <maxim.cournoyer <at> gmail.com>
To: 45mg <45mg.writes <at> gmail.com>
Cc: Josselin Poiret <dev <at> jpoiret.xyz>, Nicolas Graves <ngraves <at> ngraves.fr>,
 Simon Tournier <zimon.toutoune <at> gmail.com>, Mathieu Othacehe <othacehe <at> gnu.org>,
 Tomas Volf <~@wolfsden.cz>, Tobias Geerinckx-Rice <me <at> tobias.gr>,
 Liliana Marie Prikler <liliana.prikler <at> gmail.com>, 75981 <at> debbugs.gnu.org,
 Ricardo Wurmus <rekado <at> elephly.net>, Christopher Baines <guix <at> cbaines.net>,
 Attila Lendvai <attila <at> lendvai.name>,
 Ludovic Courtès <ludo <at> gnu.org>
Subject: Re: [bug#75981] [PATCH (WIP) v1.5 3/4] Add 'guix fork update'.
Date: Mon, 03 Feb 2025 01:21:38 +0900
Hi,

45mg <45mg.writes <at> gmail.com> writes:

> * guix/scripts/fork/update.scm: New file.
> * Makefile.am (MODULES): Add the new file.

Or, "Register it."

> * guix/scripts/fork.scm
> (show-help): Mention new command.
> (%sub-commands): Add new command.

OK.

[...]

> +(define %options
> +  ;; Specifications of the command-line options.
> +  (list (option '(#\h "help") #f #f
> +                (lambda args
> +                  (show-help)
> +                  (exit 0)))
> +        (option '(#\V "version") #f #f
> +                (lambda args
> +                  (show-version-and-exit "guix fork create")))
> +
> +        (option '( "fork-branch") #t #f

Extraneous space in list.

> +                (lambda (opt name arg result)
> +                  (alist-cons 'fork-branch-name arg result)))
> +        (option '(#\r "repository") #t #f
> +                (lambda (opt name arg result)
> +                  (alist-cons 'directory arg result)))))
> +
> +(define %default-options
> +  '())
> +
> +(define %usage
> +  (G_ "Usage: guix fork update [OPTIONS...]
> +Pull into this Guix fork's configured upstream branch, then apply new commits
> +onto the current branch.

I'd reword the beginning to "Pull into this Guix fork its configured
upstream branch [...]"

> +
> +  -r, --repository=DIRECTORY
> +                         Act in the Git repository in DIRECTORY

Maybe, "Work on the Git repository in DIRECTORY"

> +      --fork-branch=BRANCH
> +                         Apply new commits onto BRANCH instead of the current
> +                         branch
> +
> +  -h, --help             display this help and exit
> +  -V, --version          display version information and exit
> +"))
> +
> +(define (show-help)
> +  (display %usage)
> +  (newline)
> +  (show-bug-report-information))
> +
> +(define (missing-arguments)
> +    (leave (G_ "wrong number of arguments; \
> +required ~%")))
> +
> +
> +;;;
> +;;; Entry point.
> +;;;
> +
> +(define (guix-fork-update . args)
> +
> +  (define options
> +    (parse-command-line args %options (list %default-options)
> +                        #:build-options? #f))
> +
> +  (define (command-line-arguments lst)
> +    (reverse (filter-map (match-lambda
> +                           (('argument . arg) arg)
> +                           (_ #f))
> +                         lst)))
> +
> +  (define-syntax invoke-git
> +    (lambda (x)
> +      (syntax-case x ()
> +        ((_ args ...)
> +         #`(invoke "git" "-C" #,(datum->syntax x 'directory) args ...)))))
> +
> +  (define-syntax invoke-git/stdout
> +    (lambda (x)
> +      (syntax-case x ()
> +        ((_ args ...)
> +         #`(string-trim-right
> +            (invoke/stdout "git" "-C" #,(datum->syntax x 'directory) args ...))))))
> +
> +  (with-error-handling
> +    (let* ((directory (or (assoc-ref options 'directory) "."))
> +           (current-branch-name (invoke-git/stdout
> +                                 "branch"
> +                                 "--show-current"))
> +           (current-head-location (invoke-git/stdout
> +                                   "rev-parse"
> +                                   "HEAD"))
> +           (fork-branch-name (or (assoc-ref options 'fork-branch-name)
> +                                 (if (string= current-branch-name "")
> +                                     (leave (G_ "no current branch and --fork-branch not given"))

Too wide.  You can always break a string with a \ escape.

> +                                     current-branch-name)))
> +
> +           (repository (repository-open directory))
> +           (upstream-branch-name introduction-commit introduction-signer
> +                                 (if (fork-configured? repository)
> +                                     (fork-configured-introduction
> +                                      (repository-open directory))
> +                                     (leave (G_ "fork not fully configured.
> +(Did you remember to run `guix fork authenticate` first?)%~"))))

'leave' prints errors, which conventionally should be brief and not
complete sentence. I think you could get a nicer result by using a
compound condition combining a &message and &fix-hint conditions; which
the `with-error-handling' handler will correcly format with colors and
all.

> +           (upstream-branch-commit
> +            (invoke-git/stdout "rev-parse" upstream-branch-name))
> +           (new-upstream-branch-commit "")
> +           (config (repository-config repository))
> +           (signing-key
> +            (or
> +             (catch 'git-error
> +               (lambda ()
> +                 (config-entry-value
> +                  (config-get-entry config "user.signingkey")))
> +               (const #f))
> +             (begin
> +               (info (G_ "user.signingkey not set for this repository.~%"))
> +               (info (G_ "Will attempt to sign commits with fork introduction key.~%"))

Max width busted :-)

> +               introduction-signer))))
> +
> +      (info (G_ "Pulling into '~a'...~%") upstream-branch-name)
> +      (invoke-git "switch" upstream-branch-name)
> +      (invoke-git "pull")
> +      (set! new-upstream-branch-commit
> +            (invoke-git/stdout "rev-parse" upstream-branch-name))

I think you can use (define new-upstream-branch-commit ...) and avoid
its let-bound variable (set to the empty string).

> +
> +      (info (G_ "Rebasing commits from '~a' to '~a' onto fork branch '~a'...~%")
> +            upstream-branch-commit
> +            new-upstream-branch-commit
> +            fork-branch-name)
> +      (invoke-git "rebase" "--rebase-merges"
> +                  (string-append "--gpg-sign=" signing-key)
> +                  fork-branch-name new-upstream-branch-commit)
> +
> +      (info (G_ "Resetting fork branch '~a' to latest rebased commit...~%")
> +            fork-branch-name)
> +      (invoke-git "branch" "--force" fork-branch-name "HEAD")
> +
> +      (invoke-git "checkout" (or current-branch-name current-head-location))
> +
> +      (info (G_ "Successfully updated Guix fork in ~a~%")
> +            directory))))

Phew!  LGTM.  So the idea is to avoid rewriting the fork's introductory
commit and instead rewriting (rebasing) the Guix upstream commits on
top, which will resign them with the fork's authorized key, IIUC?

That's clever, but personally I much prefer to keep any work I've done
*rebased* on upstream so they are easily (re-)submitted, and it's clear
what extra work my fork has.  Seems like a good way for "forks" to hide
potentially bad commits hidden under thousands of rust commits,
obscuring them.

I think Liliana had that remark as well in the associated issue.

-- 
Thanks,
Maxim




Information forwarded to guix-patches <at> gnu.org:
bug#75981; Package guix-patches. (Sun, 02 Feb 2025 18:24:01 GMT) Full text and rfc822 format available.

Message #47 received at 75981 <at> debbugs.gnu.org (full text, mbox):

From: Liliana Marie Prikler <liliana.prikler <at> gmail.com>
To: Maxim Cournoyer <maxim.cournoyer <at> gmail.com>, 45mg <45mg.writes <at> gmail.com>
Cc: Josselin Poiret <dev <at> jpoiret.xyz>, Nicolas Graves <ngraves <at> ngraves.fr>,
 Simon Tournier <zimon.toutoune <at> gmail.com>, Mathieu Othacehe <othacehe <at> gnu.org>,
 Tomas Volf <~@wolfsden.cz>, Tobias Geerinckx-Rice <me <at> tobias.gr>,
 75981 <at> debbugs.gnu.org, Ricardo Wurmus <rekado <at> elephly.net>,
 Christopher Baines <guix <at> cbaines.net>, Attila Lendvai <attila <at> lendvai.name>,
 Ludovic Courtès <ludo <at> gnu.org>
Subject: Re: [bug#75981] [PATCH (WIP) v1.5 3/4] Add 'guix fork update'.
Date: Sun, 02 Feb 2025 19:23:47 +0100
Am Montag, dem 03.02.2025 um 01:21 +0900 schrieb Maxim Cournoyer:
> So the idea is to avoid rewriting the fork's introductory
> commit and instead rewriting (rebasing) the Guix upstream commits on
> top, which will resign them with the fork's authorized key, IIUC?
> 
> That's clever, but personally I much prefer to keep any work I've
> done *rebased* on upstream so they are easily (re-)submitted, and
> it's clear what extra work my fork has.  Seems like a good way for
> "forks" to hide potentially bad commits hidden under thousands of
> rust commits, obscuring them.
> 
> I think Liliana had that remark as well in the associated issue.
I did remark that, yet :)

The problem with rebasing on Guix is that you will have to update the
introduction on each rebase (or indeed use an unauthenticated fork). 
If you do record the introduction, say, in your own channels.scm, `guix
pull` will break, which 45mg wants to avoid.

As you wrote in your first message, it appears somewhat counter-
productive to offer `guix fork` as a means of authoring such long-lived
forks, but sentiments aside, that's precisely the goal of this series.

Cheers




Information forwarded to guix-patches <at> gnu.org:
bug#75981; Package guix-patches. (Sun, 02 Feb 2025 18:25:02 GMT) Full text and rfc822 format available.

Message #50 received at 75981 <at> debbugs.gnu.org (full text, mbox):

From: Liliana Marie Prikler <liliana.prikler <at> gmail.com>
To: Maxim Cournoyer <maxim.cournoyer <at> gmail.com>, 45mg <45mg.writes <at> gmail.com>
Cc: Josselin Poiret <dev <at> jpoiret.xyz>, Nicolas Graves <ngraves <at> ngraves.fr>,
 Simon Tournier <zimon.toutoune <at> gmail.com>, Mathieu Othacehe <othacehe <at> gnu.org>,
 Tomas Volf <~@wolfsden.cz>, Tobias Geerinckx-Rice <me <at> tobias.gr>,
 75981 <at> debbugs.gnu.org, Ricardo Wurmus <rekado <at> elephly.net>,
 Christopher Baines <guix <at> cbaines.net>, Attila Lendvai <attila <at> lendvai.name>,
 Ludovic Courtès <ludo <at> gnu.org>
Subject: Re: [bug#75981] [PATCH (WIP) v1.5 3/4] Add 'guix fork update'.
Date: Sun, 02 Feb 2025 19:24:48 +0100
Am Sonntag, dem 02.02.2025 um 19:23 +0100 schrieb Liliana Marie
Prikler:
> > I think Liliana had that remark as well in the associated issue.
> I did remark that, yet :)
s/yet/yes/




Information forwarded to guix-patches <at> gnu.org:
bug#75981; Package guix-patches. (Sun, 02 Feb 2025 20:57:02 GMT) Full text and rfc822 format available.

Message #53 received at 75981 <at> debbugs.gnu.org (full text, mbox):

From: Attila Lendvai <attila <at> lendvai.name>
To: Maxim Cournoyer <maxim.cournoyer <at> gmail.com>
Cc: Josselin Poiret <dev <at> jpoiret.xyz>, Tobias Geerinckx-Rice <me <at> tobias.gr>,
 Nicolas Graves <ngraves <at> ngraves.fr>, Simon Tournier <zimon.toutoune <at> gmail.com>,
 Mathieu Othacehe <othacehe <at> gnu.org>, Tomas Volf <~@wolfsden.cz>,
 45mg <45mg.writes <at> gmail.com>,
 Liliana Marie Prikler <liliana.prikler <at> gmail.com>, 75981 <at> debbugs.gnu.org,
 Ricardo Wurmus <rekado <at> elephly.net>, Christopher Baines <guix <at> cbaines.net>,
 Ludovic Courtès <ludo <at> gnu.org>
Subject: Re: [bug#75981] [PATCH (WIP) v1.5 1/4] Add 'guix fork create'.
Date: Sun, 02 Feb 2025 20:56:07 +0000
> My first thought was similar to Liliana's reply in the other
> issue thread: putting lots of energy into making it convenient to fork
> Guix instead of contributing to the review process (described as slow
> and erratic, which appears to be the motivation here), appears
> counter-productive.


FWIW, i have long-lived patches that will never be incorporated into guix proper. some are simply kludges that enable me to do proceed, while some others are rejected by the maintainers.

none of the above is solved by a better review process.

for the curious, here are the patches that i'm currently dragging along guix HEAD:

https://codeberg.org/attila-lendvai-patches/guix/commits/branch/attila

--
• attila lendvai
• PGP: 963F 5D5F 45C7 DFCD 0A39
--
“To eliminate statism is not to physically subdue the rulers, but to mentally liberate the ruled.”
	— Jakub Bożydar Wiśniewski





Information forwarded to guix-patches <at> gnu.org:
bug#75981; Package guix-patches. (Sun, 02 Feb 2025 22:25:01 GMT) Full text and rfc822 format available.

Message #56 received at 75981 <at> debbugs.gnu.org (full text, mbox):

From: Simon Streit <simon <at> netpanic.org>
To: Maxim Cournoyer <maxim.cournoyer <at> gmail.com>
Cc: Josselin Poiret <dev <at> jpoiret.xyz>, Tobias Geerinckx-Rice <me <at> tobias.gr>,
 Nicolas Graves <ngraves <at> ngraves.fr>, Simon Tournier <zimon.toutoune <at> gmail.com>,
 Mathieu Othacehe <othacehe <at> gnu.org>, Tomas Volf <~@wolfsden.cz>,
 45mg <45mg.writes <at> gmail.com>,
 Liliana Marie Prikler <liliana.prikler <at> gmail.com>, 75981 <at> debbugs.gnu.org,
 Ricardo Wurmus <rekado <at> elephly.net>, Christopher Baines <guix <at> cbaines.net>,
 Attila Lendvai <attila <at> lendvai.name>,
 Ludovic Courtès <ludo <at> gnu.org>
Subject: Re: [bug#75981] [PATCH (WIP) v1.5 1/4] Add 'guix fork create'.
Date: Sun, 02 Feb 2025 23:24:46 +0100
Hello Maxim,

Maxim Cournoyer <maxim.cournoyer <at> gmail.com> writes:

> My first thought was similar to Liliana’s reply in the other issue
> thread: putting lots of energy into making it convenient to fork Guix
> instead of contributing to the review process (described as slow and
> erratic, which appears to be the motivation here), appears
> counter-productive.

I am all for contributing to the review process.  It is only through
recent discussions on this subject that I am forcing myself to be a bit
more active within the community again.  Thanks for getting me back in.
I am also at fault my self.  I have a personal channel running and the
list is getting longer on patches that rather be submitted.

I am nowhere close to be a contributor (yet).  I simply don’t have time
and resources to be more active at the moment.  At the same time I also
don’t want to wait for months until certain patches – which have been
submitted for review – are pushed upstream.

I do keep patches running on top of local branches that are constantly
being re-based from upstream.  While time consuming, it seems to be the
most convenient at the moment.

I don’t even want maintain a local fork.  It is not that I really need
one.  I use it for development, thus many branches are just dead ends
that are kept for archival reasons.  I have a local central repository
where I usually push my work to be more independent from my devices –
which is my issue.  And here I only recently realised that I can’t even
push these branches to my central repository any more.

Then I tried it the other day to set up a modified keyring and
authenticate with my key and push it to my local repository as described
in the manual.  I failed for some reason and probably missed something.
This time I felt it: The bar is now seriously high to work on Guix at
the moment.

While the authentication mechanism is useful and necessary to prove what
is from Guix, it defeats the point to use Git as a decentralised tool.
It should be possible to allow local modifications for personal use,
also as unauthorised contributors.

I am for it.  Including a warning that I am pulling an unauthenticated
fork.


Kind regards

-- 
Simon




Information forwarded to guix-patches <at> gnu.org:
bug#75981; Package guix-patches. (Mon, 03 Feb 2025 01:15:02 GMT) Full text and rfc822 format available.

Message #59 received at 75981 <at> debbugs.gnu.org (full text, mbox):

From: Maxim Cournoyer <maxim.cournoyer <at> gmail.com>
To: 45mg <45mg.writes <at> gmail.com>
Cc: Nicolas Graves <ngraves <at> ngraves.fr>, Tomas Volf <~@wolfsden.cz>,
 Liliana Marie Prikler <liliana.prikler <at> gmail.com>, 75981 <at> debbugs.gnu.org,
 Ricardo Wurmus <rekado <at> elephly.net>, Attila Lendvai <attila <at> lendvai.name>,
 Ludovic Courtès <ludo <at> gnu.org>
Subject: Re: [bug#75981] [PATCH (WIP) v1.5 4/4] Document 'guix fork'.
Date: Mon, 03 Feb 2025 10:14:26 +0900
Hello!

45mg <45mg.writes <at> gmail.com> writes:

> * doc/guix.texi (Invoking guix fork): New node.
> * doc/contributing.texi (Using Your Own Patches): New node.
>
> Change-Id: I06240f0fe8d1fe39f27130a72f5d0d92949c99da
> ---
>  doc/contributing.texi |  50 ++++++++++++++
>  doc/guix.texi         | 150 ++++++++++++++++++++++++++++++++++++++++++
>  2 files changed, 200 insertions(+)
>
> diff --git a/doc/contributing.texi b/doc/contributing.texi
> index c94ae940fa..bd4fd6c2ac 100644
> --- a/doc/contributing.texi
> +++ b/doc/contributing.texi
> @@ -35,6 +35,7 @@ Contributing
>  * Making Decisions::            Collectively choosing the way forward.
>  * Commit Access::               Pushing to the official repository.
>  * Reviewing the Work of Others::  Some guidelines for sharing reviews.
> +* Using Your Own Patches::      Using your own work before it's accepted.
>  * Updating the Guix Package::   Updating the Guix package definition.
>  * Deprecation Policy::          Commitments and tools for deprecation.
>  * Writing Documentation::       Improving documentation in GNU Guix.
> @@ -3095,6 +3096,55 @@ Reviewing the Work of Others
>  have reviewed more easily by adding a @code{reviewed-looks-good} usertag
>  for the @code{guix} user (@pxref{Debbugs Usertags}).
>
> +@node Using Your Own Patches
> +@section Using Your Own Patches
> +
> +If you've taken the time to contribute code to Guix, chances are that
> +you want the changes you've made to be reflected in your own Guix
> +installation as soon as possible. Maybe you've added a package you want,
> +and you want to start using it @emph{right now}. Or you've fixed a bug
> +that affects you, and you want it to @emph{go away}.

Eh :-).  Please use double space between the sentences in doc and
comments (including doc strings); that is a GNU convention we follow,
and it makes sentence separation unambiguous, allowing editors such as
Emacs to navigate between sentences.

> +As described in the preceding sections, all contributions to Guix first
> +go through a review process to ensure code quality. Sometimes, this can
> +take longer than one would like. Ideally, the pace of the review process
> +should not prevent you from benefiting from your own work.
> +
> +One way to work around this issue is to create an additional channel of
> +your own (@pxref{Creating a Channel}), and add your code to it. For
> +certain kinds of contributions, such as adding a new package, this is
> +fairly straightforward - simply copy your new package definition(s) into

Use triple hyphen for a em dash (longer variant), as described in info
'(texinfo) Conventions':

     Use three hyphens in a row, ‘---’, to produce a long dash--like
     this (called an “em dash”), used for punctuation in sentences.  Use
     two hyphens, ‘--’, to produce a medium dash (called an “en dash”),
     used primarily for numeric ranges, as in "June 25-26".  Use a
     single hyphen, ‘-’, to produce a standard hyphen used in compound
     words.

> +a new file in the channel, and remove them when your contribution is
> +accepted.
> +
> +However, there may be cases where this is not convenient. Certain kinds
> +of changes, such as those that need to modify existing Guix internals,
> +may be more challenging to incorporate into a channel. Moreoever, the

s/Moveoever/Moreover/

> +more substantial your contribution is, the more work it will be to do
> +so.
> +
> +@cindex fork, of Guix
> +For such cases, there is another option. Recall that the patch series
> +that you sent (@pxref{Sending a Patch Series}) was created from a one or

s/a one/one/

> +more commits on a checkout of the Guix repository (@pxref{Building from
> +Git}). You could simply specify this repository (referred to as your
> +`Guix fork', or simply `fork', from here onwards), and its relevant
> +branch, as your `@code{guix}' channel (@pxref{Using a Custom Guix
> +Channel}). Now `@code{guix pull}' will fetch your new commits, and

It'd be more correct to use @samp{guix pull}, as that is not code (it's
also not a @command per Texinfo, as these should only be used with the
name of the command, without arguments).

> +you'll see the changes you made reflected in your Guix installation!
> +
> +However, there's a potential complication to this approach - the issue
> +of authentication (@pxref{Channel Authentication}). If your fork only
> +exists on your local filesystem (a `local fork'), then you probably

The chosen convention is 'file system' in Guix, as two words.

> +don't need to worry about this, and can pull without authentication
> +(@pxref{Invoking guix pull}). But other situations, such as a remotely
> +hosted fork, may make it important for your fork to be authenticated, in
> +the same way that all channels are expected to be.
> +
> +Guix provides a @command{guix fork} command in order to simplify and
> +automate many details of creating and managing and authenticated

s/and/an/

> +fork. For more information, @pxref{Invoking guix fork}.

This should be @ref, since not used inside parentheses, and used at the
end of a sentence (info '(texinfo) Cross Reference Commands').

>  @node Updating the Guix Package
>  @section Updating the Guix Package
>
> diff --git a/doc/guix.texi b/doc/guix.texi
> index b1b6d98e74..bbb5666d0a 100644
> --- a/doc/guix.texi
> +++ b/doc/guix.texi
> @@ -311,6 +311,7 @@ Top
>  * Invoking guix pack::          Creating software bundles.
>  * The GCC toolchain::           Working with languages supported by GCC.
>  * Invoking guix git authenticate::  Authenticating Git repositories.
> +* Invoking guix fork::          Creating and managing authenticated forks of Guix.
>
>  Programming Interface
>
> @@ -5930,6 +5931,7 @@ Development
>  * Invoking guix pack::          Creating software bundles.
>  * The GCC toolchain::           Working with languages supported by GCC.
>  * Invoking guix git authenticate::  Authenticating Git repositories.
> +* Invoking guix fork::          Creating and managing authenticated forks of Guix.
>  @end menu
>
>  @node Invoking guix shell
> @@ -7534,6 +7536,154 @@ Invoking guix git authenticate
>  @end table
>
>
> +@node Invoking guix fork
> +@section Invoking @command{guix fork}
> +
> +@cindex @command{guix fork}
> +
> +The @command{guix fork} command provides the means to quickly set up,
> +authenticate, and keep up-to-date an authenticated fork of Guix. For

keep up to date, without hyphens (not a compound adjective here; c.f.:
https://idioms.thefreedictionary.com/keep+up+to+date).

> +more information on authentication of a Guix checkout, @pxref{Invoking
> +guix git authenticate}.

s/@pxref/@ref/

> +Its syntax is:
> +
> +guix fork ACTION ARGS...
> +
> +ACTION specifies the fork-related action to perform. Currently, the
> +following values are supported:
> +
> +@table @code
> +@item create SIGNING_KEY [DIRECTORY OPTIONS...]
> +Create a fork of Guix in DIRECTORY, using SIGNING_KEY to sign the introductory
> +commit.
> +DIRECTORY defaults to ./guix.
> +
> +First, clone Guix into DIRECTORY, unless @code{--use-existing} is
> +given.

is given, in which case an existing Git checkout is expected to already
exist in DIRECTORY.

>Then, add SIGNING_KEY to the `@code{keyring}' branch of the
> +repository. Finally, create a new `@code{fork}' branch based starting
> +from the default branch, whose initial commit authorizes SIGNING_KEY

s/starting from/based on/

> +alone (by adding it to @file{.guix-authorizations}) and is signed by it.

to the @file{.guix-authorizations} file

> +
> +The new `@code{fork}' branch is intended to mirror upstream
> +Guix. Updating the fork amounts to applying all new commits to it (see
> +the `@code{update}' command below for further explanation). You can work

/further explanation/more details/

> +on patches in branches based off of this one, in much the same way as

I'd use 's/based off of/based on/'

> +you would base them on Guix's default branch - every commit from the
> +latter will be present in the former.
> +
> +To @command{guix pull} your changes, you could create a `build' branch

s/@command/@samp/ and s/`build'/``build''/

Double quoting is to be used sparringly, using `` '' in Texinfo.

> +starting from the initial fork commit, onto which you can cherry-pick or
> +rebase commits from patch branches. This branch can then be specified
> +for the `@code{guix}' channel (@pxref{Using a Custom Guix Channel}).

Remove the ` ' quotes.

> +Updating this channel can be done by merging the `@code{fork}' branch
> +into it.

Ditto.

> +OPTIONS can be one or more of the following:
> +
> +@table @code
> +@item --use-existing
> +Use existing clone of Guix in DIRECTORY. This is useful if you've
> +already created commits for a patch series (@pxref{Using Your Own
> +Patches}). However, all commits to the default branch, as well as any
> +branches that may be merged into it in the future, must have been signed
> +with an authorized key; otherwise, authentication will fail later.
> +@item --upstream=URI
> +The repository to clone from. This defaults to the default URL for the
> +Guix repository.
> +@item --channel-url=URI
> +Optional URI, which if given, will be used to replace the channel URL.
> +Furthermore, the existing `origin' remote (which tracks

s/`origin'/@code{remote}/

> +`@code{upstream}') is renamed to `upstream', and a new `origin' remote

Use @code instead of ` '.  Do not use both together.

> +is created to track URI.
> +@item --git-parameter PARAMETER
> +Specify configuration PARAMETER for git, via `-c' option. You can pass

s/`-c'/@samp{-c}/

> +this option multiple times.
> +@end table
> +
> +@cindex authentication, of Guix forks
> +@item authenticate UPSTREAM COMMIT SIGNER [OPTIONS...]
> +Authenticate a Guix fork, using COMMIT and SIGNER as the fork
> +introduction.
> +
> +First, authenticate new commits from UPSTREAM, using Guix's default
> +introduction. Then authenticate the remaining commits using the fork
> +introduction.
> +
> +As with @code{guix git authenticate}, all three of UPSTREAM, COMMIT and
> +SIGNER will be cached in .git/config, so that you don't need to specify
> +them after the first time.

Instead of 'cached', I'd use 'persisted' or 'written', which sounds more
accurate to me.

> +
> +OPTIONS can be one or more of the following:
> +
> +@table @code
> +@item --repository=DIRECTORY
> +@itemx -r DIRECTORY
> +Authenticate the git repository in DIRECTORY, instead of the current
> +directory.
> +@item --upstream-commit=COMMIT
> +@itemx --upstream-signer=SIGNER
> +Use COMMIT/SIGNER as the introduction for upstream
> +Guix, instead of Guix's default channel introduction.
> +@item --keyring=REFERENCE
> +@itemx -k REFERENCE
> +Load keyring for fork commits from REFERENCE, a Git branch (default
> +`@code{keyring}').

Remove quotes.

> +@item --upstream-keyring=REFERENCE
> +Load keyring for upstream commits from REFERENCE, a Git branch (default
> +`@code{keyring}').

Ditto.  Perhaps this could be renamed '--keyring-branch', which is more descriptive?

> +@item --end=COMMIT
> +Authenticate fork commits up to COMMIT.
> +@item --upstream-end=COMMIT
> +Authenticate upstream commits up to COMMIT.
> +@item --cache-key=KEY
> +@itemx --historical-authorizations=FILE
> +@itemx --stats
> +Identical to the correponding options in @command{guix git authenticate}
> +(@pxref{Invoking guix git authenticate}).
> +@end table
> +
> +@item update [OPTIONS...]
> +Pull into this Guix fork's configured upstream branch (from running
> +@command{guix fork authenticate}), then apply new commits onto the
> +current branch.
> +
> +This approach may seem less convenient than simply merging the upstream
> +branch into the fork branch. Indeed, it duplicates every upstream commit
> +under a different commit hash, and applying a large number of commits
> +can be slow. However, this is currently the only feasible approach due
> +to the nature of Guix's authentication mechanism. Namely, merge commits
> +can only be authenticated if both their parents are signed by an
> +authorized key, meaning that you can only use the merge workflow if
> +you're authorized to commit to upstream Guix.

Idea for a refinement: detect if the users's key is authorized by
upstream Guix, and use a merge in this situation?  Perhaps offer a
switch to force one flow or another, but error out when the user uses
--merge-strategy=merge and their key is not authorized in upstream Guix
(merge-strategy would default to rebase).

> +For mapping commits on the fork branch to their equivalents on the
> +upstream branch, you can use @command{guix fork identify} (see below).
> +
> +OPTIONS can be one or more of the following:
> +
> +@table @code
> +@item --repository=DIRECTORY
> +@itemx -r DIRECTORY
> +Act in the Git repository in DIRECTORY.
> +@item --fork-branch=BRANCH
> +Apply new commits onto BRANCH instead of the current branch.
> +@end table
> +
> +@item identify
> +Coming soon!
> +
> +Given a commit hash from upstream Guix, print its equivalent on the fork
> +branch, or vice versa.
> +This uses the 'Change-Id:' line added to commit messages by Guix's

@samp{Change-Id} git trailer (see 'man git-interpret-trailers').

> +'commit-msg' hook.

I'd use @samp{commit-msg} or @code.

> +The first invocation of this command will be slow, as the entire set of
> +corresponding commits is built up as a hash table, and then
> +cached. Subsequent invocations should be nearly instant.

Apart from my above comment and the double period thing, this LGTM.
It's obvious you've taken a lot of care/effort into producing this.

I'm warming up to the idea.

Thanks for the contribution.

-- 
Maxim




Information forwarded to guix-patches <at> gnu.org:
bug#75981; Package guix-patches. (Mon, 03 Feb 2025 16:05:03 GMT) Full text and rfc822 format available.

Message #62 received at 75981 <at> debbugs.gnu.org (full text, mbox):

From: Simon Tournier <zimon.toutoune <at> gmail.com>
To: 45mg <45mg.writes <at> gmail.com>, 75981 <at> debbugs.gnu.org
Cc: Josselin Poiret <dev <at> jpoiret.xyz>, Tobias Geerinckx-Rice <me <at> tobias.gr>,
 Nicolas Graves <ngraves <at> ngraves.fr>, Mathieu Othacehe <othacehe <at> gnu.org>,
 Tomas Volf <~@wolfsden.cz>, 45mg <45mg.writes <at> gmail.com>,
 Liliana Marie Prikler <liliana.prikler <at> gmail.com>,
 Ricardo Wurmus <rekado <at> elephly.net>, Christopher Baines <guix <at> cbaines.net>,
 Attila Lendvai <attila <at> lendvai.name>,
 Ludovic Courtès <ludo <at> gnu.org>
Subject: Re: [bug#75981] [PATCH (WIP) v1.5 1/4] Add 'guix fork create'.
Date: Mon, 03 Feb 2025 16:15:22 +0100
Hi,

On Sat, 01 Feb 2025 at 17:13, 45mg <45mg.writes <at> gmail.com> wrote:
> * guix/scripts/fork.scm, guix/scripts/fork/create.scm: New files.

[...]

> * guix/scripts/git/authenticate.scm

I think this fork “feature” should not be yet another subcommand but
this must be another subsubcommand: ’guix git fork’.

It would make more sense, IMHO.

Cheers,
simon




Information forwarded to guix-patches <at> gnu.org:
bug#75981; Package guix-patches. (Tue, 04 Feb 2025 05:39:01 GMT) Full text and rfc822 format available.

Message #65 received at 75981 <at> debbugs.gnu.org (full text, mbox):

From: Maxim Cournoyer <maxim.cournoyer <at> gmail.com>
To: Simon Streit <simon <at> netpanic.org>
Cc: Josselin Poiret <dev <at> jpoiret.xyz>, Tobias Geerinckx-Rice <me <at> tobias.gr>,
 Nicolas Graves <ngraves <at> ngraves.fr>, Simon Tournier <zimon.toutoune <at> gmail.com>,
 Mathieu Othacehe <othacehe <at> gnu.org>, Tomas Volf <~@wolfsden.cz>,
 45mg <45mg.writes <at> gmail.com>,
 Liliana Marie Prikler <liliana.prikler <at> gmail.com>, 75981 <at> debbugs.gnu.org,
 Ricardo Wurmus <rekado <at> elephly.net>, Christopher Baines <guix <at> cbaines.net>,
 Attila Lendvai <attila <at> lendvai.name>,
 Ludovic Courtès <ludo <at> gnu.org>
Subject: Re: [bug#75981] [PATCH (WIP) v1.5 1/4] Add 'guix fork create'.
Date: Tue, 04 Feb 2025 14:38:08 +0900
Hi Simon,

Simon Streit <simon <at> netpanic.org> writes:

> Hello Maxim,
>
> Maxim Cournoyer <maxim.cournoyer <at> gmail.com> writes:
>
>> My first thought was similar to Liliana’s reply in the other issue
>> thread: putting lots of energy into making it convenient to fork Guix
>> instead of contributing to the review process (described as slow and
>> erratic, which appears to be the motivation here), appears
>> counter-productive.
>
> I am all for contributing to the review process.  It is only through
> recent discussions on this subject that I am forcing myself to be a bit
> more active within the community again.  Thanks for getting me back in.
> I am also at fault my self.  I have a personal channel running and the
> list is getting longer on patches that rather be submitted.

I didn't mean to make anyone feel bad for having a channel, just to
state that if someone wants to have an impact on the slow review
process, the direction should be contributing toward that goal by
providing more eyes and hands, not providing more tools to more
comfortably doing our own things in our sandbox without interacting.  So
I'm glad if the result was to nudge you toward joining the review party ;-).

> I am nowhere close to be a contributor (yet).  I simply don’t have time
> and resources to be more active at the moment.  At the same time I also
> don’t want to wait for months until certain patches – which have been
> submitted for review – are pushed upstream.

There's no hiding it: reviewing is a (very) time consuming process, and
is currently done by volunteers, so on their own limited time they
probably would rather use to hack on things that personally matter more
to them :-).  The more hands we throw at it, the less time individual
reviewers have to spend on it to keep the community happy and running
smoothly.

> I do keep patches running on top of local branches that are constantly
> being re-based from upstream.  While time consuming, it seems to be the
> most convenient at the moment.

> I don’t even want maintain a local fork.  It is not that I really need
> one.  I use it for development, thus many branches are just dead ends
> that are kept for archival reasons.  I have a local central repository
> where I usually push my work to be more independent from my devices –
> which is my issue.  And here I only recently realised that I can’t even
> push these branches to my central repository any more.

For development, I simply use git checkouts and force-push them around
when I have to, or use './pre-inst-env guix deploy'.  It's not as
seamless as simply using 'guix', but it did the job when I needed it.  I
feel this feature here caters to more long-term forks that could have
multiple users, thus requiring authentication.

> Then I tried it the other day to set up a modified keyring and
> authenticate with my key and push it to my local repository as described
> in the manual.  I failed for some reason and probably missed something.
> This time I felt it: The bar is now seriously high to work on Guix at
> the moment.

I feel perhaps people are trying to replace Git by Guix :-).  Or are
operating outside what I'd call 'development', and want some
fancier/better integrated distribution means for Guix as a whole.

> While the authentication mechanism is useful and necessary to prove what
> is from Guix, it defeats the point to use Git as a decentralised tool.
> It should be possible to allow local modifications for personal use,
> also as unauthorised contributors.
>
> I am for it.  Including a warning that I am pulling an unauthenticated
> fork.

What do you mean unauthenticated?  The point of this feature is to make
authenticated forks easier to setup/work with, so you wouldn't get any
warning, unless I'm missing something.

Thanks for sharing your thoughts.

-- 
Maxim




Information forwarded to guix-patches <at> gnu.org:
bug#75981; Package guix-patches. (Wed, 05 Feb 2025 03:23:02 GMT) Full text and rfc822 format available.

Message #68 received at 75981 <at> debbugs.gnu.org (full text, mbox):

From: 45mg <45mg.writes <at> gmail.com>
To: 75981 <at> debbugs.gnu.org
Cc: Nicolas Graves <ngraves <at> ngraves.fr>,
 Maxim Cournoyer <maxim.cournoyer <at> gmail.com>,
 Simon Tournier <zimon.toutoune <at> gmail.com>, Tomas Volf <~@wolfsden.cz>,
 45mg <45mg.writes <at> gmail.com>,
 Liliana Marie Prikler <liliana.prikler <at> gmail.com>,
 Ricardo Wurmus <rekado <at> elephly.net>, Attila Lendvai <attila <at> lendvai.name>,
 Simon Streit <simon <at> netpanic.org>
Subject: Re: [PATCH (WIP) v1.5 0/4] Add 'guix fork'.
Date: Wed, 05 Feb 2025 03:21:52 +0000
Hi all,

First of all, thanks to Maxim for the patch review! There are a lot of
things that I'll need to address in there. And thanks to everyone else
who's replied here. It's encouraging to see that people are paying
attention to my work; I was worried there wouldn't be enough people who
care about this issue.

So far, my approach to Guix stuff in general has been to just dive in
and work on things until they're done. But there's just so much work
here that if I dive in right now, I don't know when I'll resurface. And
I'm way too busy for that right now.

So, rather than doing a typical inline reply to each message in this
thread, I'm going to try to list out all the feedback I've gotten, and
everything that's pending. Then I'll talk about how I plan to work on
it. Feel free to comment on anything here.

I will try to organize the feedback I've gotten so far below:

1. The argument that this doesn't belong upstream. The main arguments
   seem to be that we should improve the review process instead, or that
   channels are sufficient for anything you'd use a fork for.
2. Feature suggestions:
   a. If the user has commit access, then allow them to use a merge
      rather than rebase upstream commits.
      I hadn't thought of this before because I was thinking entirely
      from a non-committer's perspective. But I guess even committers
      might want a personal authenticated fork in some cases. As Attila
      pointed out, some things may be rejected from upstream and some
      may be temporary kludges to get things working until you can
      implement a proper solution.
3. Larger code corrections - things I need to actually spend some
   thought on. Here's a list:
   a. I've used a mix of Guile-Git and just shelling out to the Git CLI.
      The idea behind this was to provide transparency about what the
      commands are doing. For example, we could implement a `--dry-run`
      option for `create` and `update` that will just output the git
      commands that will be run rather than executing them.
      With that said, however, there are some places where it would
      clearly be cleaner to use Guile-Git, and doing so would not make
      `--dry-run` output less useful. For example, the `git
      symbolic-ref` invocations to get branch names, etc. So I need to
      make some judgement calls in that regard.
   b. The use of the `#:argument-handler` keyword of
      `parse-command-line`. It's not clear to me how this would simplify
      things, and I need to put some thought into it.
   c. Error handling. Replacing `leave` with proper exceptions (in
      `openpgp-fingerprint*` and `guix-fork-update`), handling possible
      exceptions (from gpg in `openpgp-fingerprint*`). I'm not familiar
      with exceptions, etc. in Guile, and it'll take some time for me to
      figure things out.
   d. Whether `openpgp-fingerprint*` belongs in guix/channels.scm.
4. Minor code corrections, eg. formatting. These don't really need
   further discussion, and I can just address them sequentially as I
   work on the v2 of this patch series.
      
And here are the other things that are still pending:

5. Tests. These will be the first thing to work on, as it will likely
   speed up the development feedback loop a lot.
6. Implement `guix fork identify`.
7. Figure out how to make the existing git hooks 'fork-aware' -
   currently the post-merge hook runs where it shouldn't and fails,
   because it invokes `guix git authenticate` where it should be calling
   `guix fork authenticate`.
      
Now, on to the plan of action.

First of all, let's talk about '1.'. I think I may have addressed this
in the original thread, but going by the responses here, clearly I
didn't do so well enough.

Now, as Maxim pointed out, I will probably need to submit a GCD to get
this merged upstream. I think that would be the best place for me to
state my argument. That way, we can discuss whether this patch series
should be accepted at all, as well as the broader design - items '1.'
and '2.' from the list above - in a separate thread.

Simultaneously, we can use this thread for the concrete implementation -
items '3.' to '7.'.

That way, even when I don't have the time or energy to work on the code,
I can still keep the discussion going, and collect feedback and opinions
for when I do.

Thoughts? I've never had to juggle so much discussion and feedback with
implementation work like this (is this what a software career is going
to feel like?), so I'm open to suggestions here.

Thanks,
45mg




Information forwarded to guix-patches <at> gnu.org:
bug#75981; Package guix-patches. (Thu, 06 Feb 2025 04:47:01 GMT) Full text and rfc822 format available.

Message #71 received at 75981 <at> debbugs.gnu.org (full text, mbox):

From: Maxim Cournoyer <maxim.cournoyer <at> gmail.com>
To: 45mg <45mg.writes <at> gmail.com>
Cc: Nicolas Graves <ngraves <at> ngraves.fr>,
 Simon Tournier <zimon.toutoune <at> gmail.com>, Tomas Volf <~@wolfsden.cz>,
 Liliana Marie Prikler <liliana.prikler <at> gmail.com>, 75981 <at> debbugs.gnu.org,
 Ricardo Wurmus <rekado <at> elephly.net>, Attila Lendvai <attila <at> lendvai.name>,
 Simon Streit <simon <at> netpanic.org>
Subject: Re: [PATCH (WIP) v1.5 0/4] Add 'guix fork'.
Date: Thu, 06 Feb 2025 13:46:13 +0900
Hi 45mg,

45mg <45mg.writes <at> gmail.com> writes:

> Hi all,
>
> First of all, thanks to Maxim for the patch review! There are a lot of
> things that I'll need to address in there. And thanks to everyone else
> who's replied here. It's encouraging to see that people are paying
> attention to my work; I was worried there wouldn't be enough people who
> care about this issue.

[...]

> So, rather than doing a typical inline reply to each message in this
> thread, I'm going to try to list out all the feedback I've gotten, and
> everything that's pending. Then I'll talk about how I plan to work on
> it. Feel free to comment on anything here.

Before you go further, I'd propose you to explore whether the
GUIX_EXTENSIONS_PATH mechanism could work for your new command.  If it
does, then that's even nicer, as I think in general it'd be preferable
for something as particular as 'guix fork' to not be advertised as a top
level guix command.  A note could be added to the manual pointing to
this extension, perhaps in a subsection of the section documenting
channels, for the rarer cases where this is useful/necessary.

We'd also need to document the GUIX_EXTENSIONS_PATH environment
variable, and some usage guidance (I've never used an extension myself).

-- 
Thanks,
Maxim




Information forwarded to guix-patches <at> gnu.org:
bug#75981; Package guix-patches. (Thu, 06 Feb 2025 17:01:01 GMT) Full text and rfc822 format available.

Message #74 received at 75981 <at> debbugs.gnu.org (full text, mbox):

From: 45mg <45mg.writes <at> gmail.com>
To: Maxim Cournoyer <maxim.cournoyer <at> gmail.com>, Simon Tournier
 <zimon.toutoune <at> gmail.com>, 45mg <45mg.writes <at> gmail.com>
Cc: Nicolas Graves <ngraves <at> ngraves.fr>,
 Simon Tournier <zimon.toutoune <at> gmail.com>, Tomas Volf <~@wolfsden.cz>,
 Liliana Marie Prikler <liliana.prikler <at> gmail.com>, 75981 <at> debbugs.gnu.org,
 Ricardo Wurmus <rekado <at> elephly.net>, Attila Lendvai <attila <at> lendvai.name>,
 Simon Streit <simon <at> netpanic.org>
Subject: Re: [PATCH (WIP) v1.5 0/4] Add 'guix fork'.
Date: Thu, 06 Feb 2025 17:00:33 +0000
Hi Maxim,

Maxim Cournoyer <maxim.cournoyer <at> gmail.com> writes:

> Before you go further, I'd propose you to explore whether the
> GUIX_EXTENSIONS_PATH mechanism could work for your new command.  If it
> does, then that's even nicer,

If I understand correctly how GUIX_EXTENSIONS_PATH is supposed to work,
then yes, I could use it to get the Guix CLI to find my `fork`
subcommand without it being in upstream Guix. But I'm trying to get it
into upstream Guix, hence this patch series :)

Regarding /why/ I want it upstream - I've started a draft of a GCD in
which I intend to make a comprehensive argument for its inclusion; I
believe it's more useful and important than you'd think, and I hope I'll
be able to convince you and everyone else of this.

(May take me a while to finish the GCD, though.)

> as I think in general it'd be preferable for something as particular
> as 'guix fork' to not be advertised as a top level guix command.

If that's specifically what you're worried about - Simon was of the
opinion (upthread) that I should have made this a subcommand of `guix
git`. So, we'd have `guix git fork create`, `guix git fork update`, etc.
That would mean it wouldn't show up under top-level `guix --help`. WDYT?
Would that work for you?

I chose `guix fork` because AFAIK all our commands so far are at most 3
'verbs' long (eg. `guix system list-generations`), and 4 verbs felt a
bit too much. But I'm flexible on this point.

> A note could be added to the manual pointing to this extension,
> perhaps in a subsection of the section documenting channels, for the
> rarer cases where this is useful/necessary.

I don't really understand what you're proposing. Are you suggesting that
we move guix/scripts/fork.scm and guix/scripts/fork/* to a separate
directory from the other scripts, then ask people who want to use it to
set GUIX_EXTENSIONS_PATH with that directory?

IMO, that'd just make it needlessly difficult to use it. And honestly,
what exactly is the harm in having a lesser-used top-level subcommand?
We have `guix refresh`, which the manual explicitly states is mostly
only relevant for packagers.

> We'd also need to document the GUIX_EXTENSIONS_PATH environment
> variable, and some usage guidance (I've never used an extension myself).

Yup. Actually, this should be done regardless of what happens with my
proposal. I had to search the mailing lists and track down the patch in
which it was implemented [1] just to figure out what it was supposed to do
(and even then I'm not entirely clear).

> -- 
> Thanks,
> Maxim

[1] https://yhetil.org/guix/20210105101817.7576-1-rekado <at> elephly.net/
    I /think/ this is the one?




Information forwarded to guix-patches <at> gnu.org:
bug#75981; Package guix-patches. (Fri, 07 Feb 2025 10:35:02 GMT) Full text and rfc822 format available.

Message #77 received at 75981 <at> debbugs.gnu.org (full text, mbox):

From: Maxim Cournoyer <maxim.cournoyer <at> gmail.com>
To: 45mg <45mg.writes <at> gmail.com>
Cc: Nicolas Graves <ngraves <at> ngraves.fr>,
 Simon Tournier <zimon.toutoune <at> gmail.com>, Tomas Volf <~@wolfsden.cz>,
 Liliana Marie Prikler <liliana.prikler <at> gmail.com>, 75981 <at> debbugs.gnu.org,
 Ricardo Wurmus <rekado <at> elephly.net>, Attila Lendvai <attila <at> lendvai.name>,
 Simon Streit <simon <at> netpanic.org>
Subject: Re: [PATCH (WIP) v1.5 0/4] Add 'guix fork'.
Date: Fri, 07 Feb 2025 19:34:20 +0900
Hello!

45mg <45mg.writes <at> gmail.com> writes:

> Hi Maxim,
>
> Maxim Cournoyer <maxim.cournoyer <at> gmail.com> writes:
>
>> Before you go further, I'd propose you to explore whether the
>> GUIX_EXTENSIONS_PATH mechanism could work for your new command.  If it
>> does, then that's even nicer,
>
> If I understand correctly how GUIX_EXTENSIONS_PATH is supposed to work,
> then yes, I could use it to get the Guix CLI to find my `fork`
> subcommand without it being in upstream Guix. But I'm trying to get it
> into upstream Guix, hence this patch series :)

Yes, that's the idea of extensions; they live as a separate
package/project that can be combined with Guix to extend it.

> Regarding /why/ I want it upstream - I've started a draft of a GCD in
> which I intend to make a comprehensive argument for its inclusion; I
> believe it's more useful and important than you'd think, and I hope I'll
> be able to convince you and everyone else of this.
>
> (May take me a while to finish the GCD, though.)

OK, I look forward to read it (and be convinced ;-)).

>> as I think in general it'd be preferable for something as particular
>> as 'guix fork' to not be advertised as a top level guix command.
>
> If that's specifically what you're worried about - Simon was of the
> opinion (upthread) that I should have made this a subcommand of `guix
> git`. So, we'd have `guix git fork create`, `guix git fork update`, etc.
> That would mean it wouldn't show up under top-level `guix --help`. WDYT?
> Would that work for you?
>
> I chose `guix fork` because AFAIK all our commands so far are at most 3
> 'verbs' long (eg. `guix system list-generations`), and 4 verbs felt a
> bit too much. But I'm flexible on this point.

If a Guix extension can do (and I think it should, since IIRC, extending
the available commands of the command line was the original goal of the
mechanism), then that's the best way to add a feature that, in my
opinion, should be the very last resort for users to extend Guix, as it:

1. Obfuscate the actual changes in the fork by rebasing upstream Guix
commits on top (bad for auditing it/security).

2. Can be more easily abused into doing anything nefarious to users
since it can touch any area of the Guix code (duh, it's a fork).

3. Could be easily (?) mistaken as the canonical/better way to
distribute changes made to Guix additions/improvements (instead of
contributing them upstream or via the extension mechanism of the use of
channels), especially if a prominent CLI command exists for it.

>> A note could be added to the manual pointing to this extension,
>> perhaps in a subsection of the section documenting channels, for the
>> rarer cases where this is useful/necessary.
>
> I don't really understand what you're proposing. Are you suggesting that
> we move guix/scripts/fork.scm and guix/scripts/fork/* to a separate
> directory from the other scripts, then ask people who want to use it to
> set GUIX_EXTENSIONS_PATH with that directory?

If this work here becomes a Guix extension, it'd live outside of Guix
itself in its main repository and be added as a package in Guix.  For
users, it'd be seemlessly picked up via the GUIX_EXTENSIONS_PATH search
path, which *should* be automatically set in a profile, according to
commit bbc1735be26 ("profiles: Implicitly set GUIX_EXTENSIONS_PATH.")).

> IMO, that'd just make it needlessly difficult to use it. And honestly,
> what exactly is the harm in having a lesser-used top-level subcommand?
> We have `guix refresh`, which the manual explicitly states is mostly
> only relevant for packagers.

Maintenance, crowding the manual, crowding the command line, and the
arguments I've mentioned above.

>> We'd also need to document the GUIX_EXTENSIONS_PATH environment
>> variable, and some usage guidance (I've never used an extension myself).
>
> Yup. Actually, this should be done regardless of what happens with my
> proposal. I had to search the mailing lists and track down the patch in
> which it was implemented [1] just to figure out what it was supposed to do
> (and even then I'm not entirely clear).

I think a good way to document it would be to show an example.  There's
already a 'guix-modules' Guix extension package available in Guix.

--8<---------------cut here---------------start------------->8---
$ guix shell guix guix-modules -- guix module --help
Usage: guix module COMMAND [OPTION]...
Provide an "environment module" interface for Guix.

Available commands:

    create    convert packages to module files
    
[...]
--8<---------------cut here---------------end--------------->8---

It seems the commit I've referenced above is not enough for the
GUIX_EXTENSIONS_PATH to be present, for some reason, which is why you
need to include the 'guix' package, whose package defines a search path
for it.  That's not optimal, but you can see how easy it is to use Guix
extensions.

-- 
Thanks,
Maxim




Information forwarded to guix-patches <at> gnu.org:
bug#75981; Package guix-patches. (Fri, 14 Feb 2025 23:53:02 GMT) Full text and rfc822 format available.

Message #80 received at 75981 <at> debbugs.gnu.org (full text, mbox):

From: Ludovic Courtès <ludo <at> gnu.org>
To: 45mg <45mg.writes <at> gmail.com>
Cc: Nicolas Graves <ngraves <at> ngraves.fr>, Tomas Volf <~@wolfsden.cz>,
 Liliana Marie Prikler <liliana.prikler <at> gmail.com>, 75981 <at> debbugs.gnu.org,
 Ricardo Wurmus <rekado <at> elephly.net>, Attila Lendvai <attila <at> lendvai.name>
Subject: Re: bug#75981: [PATCH (WIP) v1 0/4] Add 'guix fork'.
Date: Sat, 15 Feb 2025 00:51:45 +0100
Hello,

One quick comment…

45mg <45mg.writes <at> gmail.com> skribis:

> +      (info (G_ "Adding key to keyring branch...~%"))
> +      (invoke-git "switch" "keyring")
> +      (invoke "gpg"
> +              "--armor" "--export"
> +              "-o" (string-append directory "/" key-file-name)
> +              signing-key)
> +      (invoke-git "add" "--" key-file-name)
> +      (invoke-git "commit" "-m" "Add key for fork introduction.")
> +
> +      (info (G_ "Setting up fork branch...~%"))
> +      (invoke-git "switch" "--create" "fork" "master")
> +      (when channel-url
> +        (update-channel-url (string-append directory "/.guix-channel")
> +                            channel-url))
> +      (rewrite-authorizations (string-append directory "/.guix-authorizations")
> +                              introduction-name signing-key)
> +      (invoke-git "add" "--"
> +                  (string-append directory "/.guix-authorizations")
> +                  (string-append directory "/.guix-channel"))
> +      (invoke-git "commit"
> +                  (string-append "--gpg-sign=" signing-key)
> +                  "-m"
> +                  (string-append
> +                   "Initial fork commit.\n\n"
> +                   ".guix-authorizations: Allow only " introduction-name "'s key."
> +                   (if channel-url
> +                       "\n.guix-channels: Update channel URL."
> +                       "")))

Apologies for not following the initial discussions that led to this
design.  To make sure I understand, what this does is create
‘.guix-authorizations’ with a single key and then makes that commit the
introduction of the fork, right?

The idea being that one would keep rebasing their fork and ‘guix fork’
would take care of updating the introduction in ‘.git/config’ so you can
keep authenticating it, right?

This looks interesting and a much needed improvement.  There’s quite a
bit to discuss about the implementation; in particular, it would be cool
if we could avoid duplicating most of (guix scripts git authenticate)
and if we could avoid shelling out to various commands, as in the
snippet above, to the extent possible (for instance using Guile-Git).

Thanks for your work!

Ludo’.




Information forwarded to guix-patches <at> gnu.org:
bug#75981; Package guix-patches. (Fri, 21 Feb 2025 07:48:02 GMT) Full text and rfc822 format available.

Message #83 received at 75981 <at> debbugs.gnu.org (full text, mbox):

From: Nicolas Graves <ngraves <at> ngraves.fr>
To: Maxim Cournoyer <maxim.cournoyer <at> gmail.com>, 45mg <45mg.writes <at> gmail.com>
Cc: Simon Tournier <zimon.toutoune <at> gmail.com>, Tomas Volf <~@wolfsden.cz>,
 Liliana Marie Prikler <liliana.prikler <at> gmail.com>, 75981 <at> debbugs.gnu.org,
 Ricardo Wurmus <rekado <at> elephly.net>, Attila Lendvai <attila <at> lendvai.name>,
 Simon Streit <simon <at> netpanic.org>
Subject: Re: [bug#75981] [PATCH (WIP) v1.5 0/4] Add 'guix fork'.
Date: Fri, 21 Feb 2025 08:47:33 +0100
Adding my grain of salt, a bit late, but since I'm CCed :)

I think having an extension is also a way to solidify and tests things
before thinking of moving CLI functionalities upstream, when it will
make sense for users.  Like git or emacs extensions, when the idea is
inevitable and sound enough, extensions or similar functionality
eventually gets merged upstream.

I also work on an extension of guix for working more easily on local
repos.  I do not advertise it now because it isn't polished.  It doesn't
have the same goal as`guix fork`, although it probably targets the same
audience.  The focus is on
1) reproducibility of guix with unmerged patches when you don't want a
fork online (precisely to avoid accumulating unmerged patches in a fork,
but still have your dozen patches when you need them),
2) performance gains from having profiles generated from compiled
bytecode instead of recompiled from scratch)

I find the format of extensions very adapted to that kind of need (for
some users with a particular need, but building upon Guix).  Guix
extensions are still undocumented and missing some integration options
(but being worked on in 74633).

I probably won't try to upstream it, except if it makes sense down the
line. 

For those curious, it's in the unlisted repo
https://git.sr.ht/~ngraves/guix-stack

I will announce it when I'll be happy with it myself.

-- 
Best regards,
Nicolas Graves




Information forwarded to guix <at> cbaines.net, dev <at> jpoiret.xyz, ludo <at> gnu.org, othacehe <at> gnu.org, maxim.cournoyer <at> gmail.com, zimon.toutoune <at> gmail.com, me <at> tobias.gr, guix-patches <at> gnu.org:
bug#75981; Package guix-patches. (Sun, 23 Feb 2025 13:25:02 GMT) Full text and rfc822 format available.

Message #86 received at 75981 <at> debbugs.gnu.org (full text, mbox):

From: 45mg <45mg.writes <at> gmail.com>
To: 75981 <at> debbugs.gnu.org
Cc: Nicolas Graves <ngraves <at> ngraves.fr>,
 Maxim Cournoyer <maxim.cournoyer <at> gmail.com>,
 Simon Tournier <zimon.toutoune <at> gmail.com>, Tomas Volf <~@wolfsden.cz>,
 45mg <45mg.writes <at> gmail.com>,
 Ludovic Courtès <ludo <at> gnu.org>,
 Liliana Marie Prikler <liliana.prikler <at> gmail.com>,
 Ricardo Wurmus <rekado <at> elephly.net>, Attila Lendvai <attila <at> lendvai.name>,
 Simon Streit <simon <at> netpanic.org>
Subject: [PATCH v2 0/2] Support authenticated forks.
Date: Sun, 23 Feb 2025 18:50:47 +0530
Hi Guix,

This revision is a major refactor of the previous one, v1.5 [1]. If you
were one of those who felt that adding a entire command line was
excessive, this one will make you happy :)

The changes are as follows:

* `guix fork create` is now `etc/fork.scm`

I realized that Maxim was right when he pointed out that this command
will rarely be used, so it shouldn't be a top-level command.

Other changes made to the script:
- Adds a `--dry-run` option that displays the commands that would be
  executed.
- Adds a `--from` option to specify the base commit of the fork branch.
- Use %default-guix-channel to determine the name of the default branch,
  instead of the horrific `git symbolic-ref` incantation that was used
  earlier.
- We now create the fork in ../guix-fork, since the script will likely
  be run from the toplevel of a Guix checkout.
- Addressed feedback from Maxim's review.

* `guix fork authenticate` is now `guix git authenticate --branch`

It turns out that `guix git authenticate` already supports
branch-specific introductions. Quoting from "(guix) Invoking guix git
authenticate":

> Should you have branches that require different introductions, you
> can specify them directly in ‘.git/config’.  For example, if the branch
> called ‘personal-fork’ has a different introduction than other branches,
> you can extend ‘.git/config’ along these lines:
>
> [guix "authentication-personal-fork"]
> introduction-commit = cabba936fd807b096b48283debdcddccfea3900d
> introduction-signer = C0FF EECA BBA9 E6A8 0D1D  E643 A2A0 6DF2 A33A 54FA
> keyring = keyring

It turns out this is all we really need for fork authentication. `guix
fork authenticate` ended up duplicating a lot of code from `guix git
authenticate` (as Ludovic pointed out), and all it really did
differently was
a. Authenticate a configured 'upstream' branch of the current branch
   first
b. Configure a separate introduction

Guix already has support for b., and in retrospect a. isn't really
necessary because the existing post-merge hook should take care of it.

So all we need is a way to configure a branch-specific introduction
without having to manually edit .git/config. This is what patch 2/2 in
this series provides, via the `--branch` option.

* `guix fork update` has been removed

There are two aspects of a solution to handle forks - fork
/authentication/, which is what we've discussed so far; and fork
/management/, which includes keeping a fork updated, rebasing patch
branches onto the latest master, etc. I would like to keep this patch
series restricted to the former, and avoid addressing the latter, for
two main reasons.

The first reason is that fork management is a fairly opinionated
subject. For example, non-committers have to rebase upstream commits
onto their fork branches to update them, but committers are able to and
may prefer to merge.

The second reason is that fork management bleeds into patch management,
which is already addressed by tools like `b4` and the like; and we
already have people working on it even within the Guix community [2].

As `guix fork update` falls more into fork management, I've removed it.
All it really did anyway was rebase commits onto the fork branch, which
is not that hard to to via the Git CLI.

My aim is to have code related to fork authentication upstreamed, while
code related to fork management can be developed as extensions (or
better yet, as standalone tools). Prior experiments [3] have
demonstrated that code which touches the authentication mechanism in any
way has a high chance of introducing security issues, and the only real
way to mitigate this is to have as many eyes on it as possible. The
alternative, which we're already seeing [4], is that people will come up
with their own solutions for fork authentication, which may or may not
be secure, and may never be reviewed by anyone else.

* Other changes

- I've added tests, in tests/fork.sh.
- I've removed the plans for `guix fork identify`, as it falls more into
  fork management, and anyway I haven't actually found a use-case for it
  so far.



I will probably need to submit a GCD for this change, even though it no
longer adds a new command line, given that last time some people weren't
convinced that fork authentication should be supported. That'll probably
take a while. In the meantime, as always, any feedback is appreciated.

Regards,
45mg

[1] https://yhetil.org/guix/cover.1738408683.git.45mg.writes <at> gmail.com/
[2] https://git.sr.ht/~ngraves/guix-stack
[3] https://yhetil.org/guix/87bjwdpmyh.fsf <at> wolfsden.cz/
[4] Please do not take the inclusion of these solutions here as a judgement on
    their quality or on their originators. I am merely trying to demonstrate
    that people need a solution to this issue, and if we do not implement one
    upstream, we will end up with a bunch of unofficial solutions anyway. No
    disrespect is intended.
    https://yhetil.org/guix/871pw77ryg.fsf <at> lease-up.com/
    https://yhetil.org/guix/D7QG7XS56NR6.2IWYZPP7TMEI1 <at> disroot.org/
    https://yhetil.org/guix/87a5bpqmpu.fsf <at> wolfsden.cz/



45mg (2):
  etc: Add fork.scm.
  scripts: Add `guix git authenticate --branch`.

 Makefile.am                       |   3 +-
 doc/contributing.texi             |  69 +++++++
 doc/guix.texi                     |  15 +-
 etc/fork.scm                      | 286 ++++++++++++++++++++++++++++++
 guix/scripts/git/authenticate.scm |  86 ++++++---
 guix/utils.scm                    |  61 +++++++
 tests/fork.sh                     | 166 +++++++++++++++++
 7 files changed, 654 insertions(+), 32 deletions(-)
 create mode 100755 etc/fork.scm
 create mode 100644 tests/fork.sh


base-commit: 38b4358f4d84cafa54deff77dee6fafac3e03864
-- 
2.48.1




Information forwarded to guix <at> cbaines.net, dev <at> jpoiret.xyz, ludo <at> gnu.org, othacehe <at> gnu.org, maxim.cournoyer <at> gmail.com, zimon.toutoune <at> gmail.com, me <at> tobias.gr, guix-patches <at> gnu.org:
bug#75981; Package guix-patches. (Sun, 23 Feb 2025 13:26:02 GMT) Full text and rfc822 format available.

Message #89 received at 75981 <at> debbugs.gnu.org (full text, mbox):

From: 45mg <45mg.writes <at> gmail.com>
To: 75981 <at> debbugs.gnu.org
Cc: Nicolas Graves <ngraves <at> ngraves.fr>,
 Maxim Cournoyer <maxim.cournoyer <at> gmail.com>,
 Simon Tournier <zimon.toutoune <at> gmail.com>, Tomas Volf <~@wolfsden.cz>,
 45mg <45mg.writes <at> gmail.com>,
 Ludovic Courtès <ludo <at> gnu.org>,
 Liliana Marie Prikler <liliana.prikler <at> gmail.com>,
 Ricardo Wurmus <rekado <at> elephly.net>, Attila Lendvai <attila <at> lendvai.name>,
 Simon Streit <simon <at> netpanic.org>
Subject: [PATCH v2 1/2] etc: Add fork.scm.
Date: Sun, 23 Feb 2025 18:50:48 +0530
* guix/utils.scm (chain-cut, invoke/stdout): New procedures.
* etc/fork.scm: New script.
* tests/etc-fork.sh: Test it.
* Makefile.am: Add etc-fork.sh.
* doc/contributing.texi (Using Your Own Patches): New node.

Change-Id: Ifcba59ac6b6a330056bcfd51f7b1b7f790f95cc8
---
 Makefile.am           |   3 +-
 doc/contributing.texi |  63 ++++++++++
 etc/fork.scm          | 286 ++++++++++++++++++++++++++++++++++++++++++
 guix/utils.scm        |  61 +++++++++
 tests/fork.sh         | 135 ++++++++++++++++++++
 5 files changed, 547 insertions(+), 1 deletion(-)
 create mode 100755 etc/fork.scm
 create mode 100644 tests/fork.sh

diff --git a/Makefile.am b/Makefile.am
index 8e142a4002..dc4da74334 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -664,7 +664,8 @@ SH_TESTS =					\
   tests/guix-graph.sh				\
   tests/guix-describe.sh			\
   tests/guix-repl.sh     			\
-  tests/guix-lint.sh
+  tests/guix-lint.sh     			\
+  tests/etc-fork.sh
 
 TESTS = $(SCM_TESTS) $(SH_TESTS)
 
diff --git a/doc/contributing.texi b/doc/contributing.texi
index ab4f30d54b..121f7eea9d 100644
--- a/doc/contributing.texi
+++ b/doc/contributing.texi
@@ -35,6 +35,7 @@ Contributing
 * Making Decisions::            Collectively choosing the way forward.
 * Commit Access::               Pushing to the official repository.
 * Reviewing the Work of Others::  Some guidelines for sharing reviews.
+* Using Your Own Patches::      Using your own work before it's accepted.
 * Updating the Guix Package::   Updating the Guix package definition.
 * Deprecation Policy::          Commitments and tools for deprecation.
 * Writing Documentation::       Improving documentation in GNU Guix.
@@ -3095,6 +3096,68 @@ Reviewing the Work of Others
 have reviewed more easily by adding a @code{reviewed-looks-good} usertag
 for the @code{guix} user (@pxref{Debbugs Usertags}).
 
+@node Using Your Own Patches
+@section Using Your Own Patches
+
+If you've taken the time to contribute code to Guix, chances are that
+you want the changes you've made to be reflected in your own Guix
+installation as soon as possible.  Maybe you've added a package you want,
+and you want to start using it @emph{right now}.  Or you've fixed a bug
+that affects you, and you want it to @emph{go away}.
+
+As described in the preceding sections, all contributions to Guix first
+go through a review process to ensure code quality.  Sometimes, this can
+take longer than one would like.  Ideally, the pace of the review process
+should not prevent you from benefiting from your own work.
+
+One way to work around this issue is to create an additional channel of
+your own (@pxref{Creating a Channel}), and add your code to it.  For
+certain kinds of contributions, such as adding a new package, this is
+fairly straightforward --- simply copy your new package definition(s)
+into a new file in the channel, and remove them when your contribution
+is accepted.
+
+However, there may be cases where this is not convenient.  Certain kinds
+of changes, such as those that need to modify existing Guix internals,
+may be more challenging to incorporate into a channel.  Moreover, the
+more substantial your contribution is, the more work it will be to do
+so.
+
+@cindex fork, of Guix
+For such cases, there is another option.  Recall that the patch series
+that you sent (@pxref{Sending a Patch Series}) was created from one or
+more commits on a checkout of the Guix repository (@pxref{Building from
+Git}).  You could simply specify this repository (referred to as your
+``Guix fork'', or simply ``fork'', from here onwards), and its relevant
+branch, as your `@code{guix}' channel (@pxref{Using a Custom Guix
+Channel}).  Now `@samp{guix pull}' will fetch your new commits, and
+you'll see the changes you made reflected in your Guix installation!
+
+However, there's a potential complication to this approach - the issue
+of authentication (@pxref{Channel Authentication}).  If your fork only
+exists on your local filesystem (a `local fork'), then you probably
+don't need to worry about this, and can pull without authentication
+(@pxref{Invoking guix pull}).  But other situations, such as a remotely
+hosted fork, may make it important for your fork to be authenticated, in
+the same way that all channels are expected to be.
+
+The steps needed to enable the authentication of a fork branch are:
+
+@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).
+
+@item
+Add a commit to the fork branch that modifies the
+@file{.guix_authorizations} file to add your public key. This commit
+will serve as the @dfn{fork introduction}.
+@end itemize
+
+Guix provides a @file{etc/fork.scm} script that can automatically create
+a fork for you, performing these steps as well as taking care of other
+details. Run @samp{etc/fork.scm --help} for details.
+
 @node Updating the Guix Package
 @section Updating the Guix Package
 
diff --git a/etc/fork.scm b/etc/fork.scm
new file mode 100755
index 0000000000..a31300072b
--- /dev/null
+++ b/etc/fork.scm
@@ -0,0 +1,286 @@
+#!/bin/sh
+# -*- mode: scheme; -*-
+# Ensure that this script can run without guix being installed.
+pre_inst_env_maybe=
+command -v guix > /dev/null || pre_inst_env_maybe=./pre-inst-env
+exec $pre_inst_env_maybe guix repl -- "$0" "$@"
+!#
+
+;;; GNU Guix --- Functional package management for GNU
+;;; Copyright © 2025 Tomas Volf <~@wolfsden.cz>
+;;; Copyright © 2025 45mg <45mg.writes <at> gmail.com>
+;;;
+;;; This file is part of GNU Guix.
+;;;
+;;; GNU Guix is free software; you can redistribute it and/or modify it
+;;; under the terms of the GNU General Public License as published by
+;;; the Free Software Foundation; either version 3 of the License, or (at
+;;; your option) any later version.
+;;;
+;;; GNU Guix is distributed in the hope that it will be useful, but
+;;; WITHOUT ANY WARRANTY; without even the implied warranty of
+;;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+;;; GNU General Public License for more details.
+;;;
+;;; You should have received a copy of the GNU General Public License
+;;; along with GNU Guix.  If not, see <http://www.gnu.org/licenses/>.
+
+;;; Commentary:
+;;;
+;;; Create a fork of Guix, by running a series of git commands.
+;;;
+;;; Code:
+
+(use-modules (guix build utils)
+             (guix channels)
+             (guix scripts)
+             (guix ui)
+             ((guix utils) #:select (chain-cut
+                                     invoke/stdout))  ;TODO move to (guix build utils)
+             (ice-9 exceptions)
+             (ice-9 match)
+             (ice-9 popen)
+             (ice-9 pretty-print)
+             (ice-9 string-fun)
+             (ice-9 textual-ports)
+             (srfi srfi-1)
+             (srfi srfi-13)
+             (srfi srfi-26)
+             (srfi srfi-37)
+             (srfi srfi-71))
+
+(define %options
+  ;; Specifications of the command-line options.
+  (list (option '(#\h "help") #f #f
+                (lambda args
+                  (show-help)
+                  (exit 0)))
+        (option '("upstream") #t #f
+                (lambda (opt name arg result)
+                  (alist-cons 'upstream arg result)))
+        (option '("from") #t #f
+                (lambda (opt name arg result)
+                  (alist-cons 'start arg result)))
+        (option '("channel-url") #t #f
+                (lambda (opt name arg result)
+                  (alist-cons 'channel-url arg result)))
+        (option '("use-existing") #f #f
+                (lambda (opt name arg result)
+                  (alist-cons 'use-existing? #t result)))
+        (option '("git-parameter") #t #f
+                (lambda (opt name arg result)
+                  (let ((git-parameters (assoc-ref result 'git-parameters)))
+                    (if git-parameters
+                        (alist-cons 'git-parameters (cons arg git-parameters) result)
+                        (alist-cons 'git-parameters (list arg) result)))))
+        (option '("dry-run") #f #f
+                (lambda (opt name arg result)
+                  (alist-cons 'dry-run? #t result)))))
+
+(define %default-options
+  `((upstream . ,(channel-url %default-guix-channel))
+    (start    . ,(channel-branch %default-guix-channel))))
+
+(define %usage
+  (format #f (G_ "Usage: etc/fork.scm SIGNING_KEY [DIRECTORY OPTIONS...]
+Create a fork of Guix in DIRECTORY, using SIGNING_KEY to sign the introductory
+commit.
+DIRECTORY defaults to ../guix-fork.
+
+      --upstream=URI     the repository to clone from
+                         (defaults to ~a)
+      --from=START       create the fork branch starting from START,
+                         a branch or commit
+                         (defaults to starting from the '~a' branch)
+      --channel-url=URI  optional URI, used to replace the channel URL
+                         and the existing 'origin' remote (which is
+                         renamed to 'upstream')
+      --use-existing     Use existing clone of Guix in DIRECTORY
+      --git-parameter=PARAMETER
+                         Specify configuration PARAMETER for git, via
+                         '-c' option (can pass multiple times)
+      --dry-run          Display what would be done, without doing it
+
+  -h, --help             display this help and exit
+")
+          (assoc-ref %default-options 'upstream)
+          (assoc-ref %default-options 'start)))
+
+(define (show-help)
+  (display %usage)
+  (newline)
+  (show-bug-report-information))
+
+(define (missing-arguments)
+  (info (G_ "hint: try etc/fork.scm --help~%"))
+  (leave (G_ "wrong number of arguments; \
+required SIGNING_KEY~%")))
+
+
+;;;
+;;; Helper prodecures.
+;;;
+
+(define (fingerprint->key-file-name fingerprint)
+  (let* ((listing (invoke/stdout "gpg" "--list-key" "--with-colons"
+                                 fingerprint))
+         (uid (chain-cut listing
+                         (string-split <> #\newline)
+                         (filter (cut string-prefix? "uid:" <>) <>)
+                         first
+                         (string-split <> #\:)
+                         tenth))
+         (email-name (string-delete
+                      (cut eq? <> #\.)
+                      (substring uid
+                                 (1+ (or (string-index-right uid #\<)
+                                         -1))  ;no name in uid
+                                 (string-index uid #\@))))
+         (key-id (chain-cut listing
+                            (string-split <> #\newline)
+                            (filter (cut string-prefix? "pub:" <>) <>)
+                            car
+                            (string-split <> #\:)
+                            fifth
+                            (string-take-right <> 8))))
+    (string-append email-name "-" key-id ".key")))
+
+(define (update-channel-url file channel-url dry-run?)
+  "Modify .guix_channel FILE.
+Change the channel url to CHANNEL-URL.
+If DRY-RUN? is true, only display what would be done."
+  (let ((channel-data (call-with-input-file file read)))
+    (assq-set! (cdr channel-data) 'url (list channel-url))
+
+    (define (writer port)
+      (display ";; This is a Guix channel.\n\n" port)
+      (pretty-print channel-data port))
+
+    (if dry-run?
+        (begin
+          (display "Modified .guix_channel:\n")
+          (writer (current-output-port)))
+        (call-with-output-file file writer))))
+
+(define (rewrite-authorizations file name fingerprint dry-run?)
+  "Rewrite .guix-authorizations FILE to contain a single authorization
+consisting of NAME and FINGERPRINT.
+If DRY-RUN? is true, only display what would be done."
+  (let ((auth-data (call-with-input-file file read)))
+    (list-set! auth-data (1- (length auth-data))
+               `((,fingerprint (name ,name))))
+
+    (define (writer port)
+      (display "\
+;; This file, which is best viewed as -*- Scheme -*-, lists the OpenPGP keys
+;; currently authorized to sign commits in this fork branch.
+
+" port)
+      (pretty-print auth-data port))
+
+    (if dry-run?
+        (begin
+          (display "Rewritten .guix_authorizations:\n")
+          (writer (current-output-port)))
+        (call-with-output-file file writer))))
+
+
+;;;
+;;; Entry point.
+;;;
+
+(define (main . args)
+  (define options
+    (parse-command-line args %options (list %default-options)
+                        #:build-options? #f))
+
+  (define (command-line-arguments lst)
+    (reverse (filter-map (match-lambda
+                           (('argument . arg) arg)
+                           (_ #f))
+                         lst)))
+
+  (with-error-handling
+    (let* ((signing-key directory (match (command-line-arguments options)
+                                    ((signing-key directory)
+                                     (values signing-key directory))
+                                    ((signing-key)
+                                     (values signing-key "../guix-fork"))
+                                    (_ (missing-arguments))))
+           (upstream (assoc-ref options 'upstream))
+           (start (assoc-ref options 'start))
+           (channel-url (assoc-ref options 'channel-url))
+           (use-existing? (assoc-ref options 'use-existing?))
+           (dry-run? (assoc-ref options 'dry-run?))
+           (git-parameters (assoc-ref options 'git-parameters))
+           (git-c-options  ;'("-c" "param1" "-c" "param2" ...)
+            (let loop ((opts '()) (params git-parameters))
+              (if (or (not params) (null-list? params))
+                  opts
+                  (loop (append opts (list "-c" (first params)))
+                        (drop params 1)))))
+
+           (key-file-name (fingerprint->key-file-name signing-key))
+           (introduction-name (car (string-split key-file-name #\-))))
+
+      (define (invoke* . args)
+        (if dry-run?
+            (display (string-append
+                      (string-join (map (cut format #f "~s" <>)
+                                        args))
+                      "\n"))
+            (apply invoke args)))
+
+      (define (invoke-git . args)
+        (apply invoke* `("git" ,@git-c-options "-C" ,directory ,@args)))
+
+      (unless use-existing?
+        (info (G_ "Cloning from upstream ~a...~%") upstream)
+        (invoke* "git" "clone" upstream directory))
+
+      (when channel-url
+        (info (G_ "Renaming existing 'origin' remote to 'upstream'...~%"))
+        (invoke-git "remote" "rename" "origin" "upstream")
+        (info (G_ "Using provided channel URL for new 'origin' remote...~%"))
+        (invoke-git "remote" "add" "origin" channel-url))
+
+      (info (G_ "Adding key to keyring branch...~%"))
+      (invoke-git "switch" "keyring")
+      (invoke* "gpg"
+               "--armor" "--export"
+               "-o" (string-append directory "/" key-file-name)
+               signing-key)
+      (invoke-git "add" "--" key-file-name)
+      (invoke-git "commit"
+                  ;; This commit does not need to be signed, but we might as
+                  ;; well use the signing key we were given.
+                  (string-append "--gpg-sign=" signing-key)
+                  "-m" "Add key for fork introduction.")
+
+      (info (G_ "Setting up fork branch...~%"))
+      (invoke-git "switch" "--create" "fork" start)
+      (when channel-url
+        (update-channel-url (string-append directory "/.guix-channel")
+                            channel-url
+                            dry-run?))
+      (rewrite-authorizations (string-append directory "/.guix-authorizations")
+                              introduction-name signing-key
+                              dry-run?)
+      (invoke-git "add" "--"
+                  (string-append directory "/.guix-authorizations")
+                  (string-append directory "/.guix-channel"))
+      (invoke-git
+       "commit"
+       (string-append "--gpg-sign=" signing-key)
+       "-m"
+       (string-append
+        "Initial fork commit.\n\n"
+        ".guix-authorizations: Allow only " introduction-name "'s key."
+        (if channel-url
+            "\n.guix-channels: Update channel URL."
+            "")))
+
+      (info (G_ "Successfully created Guix fork in ~a.~%")
+            directory))))
+
+(apply main (cdr (command-line)))
diff --git a/guix/utils.scm b/guix/utils.scm
index c7c23d9d5b..191d17c570 100644
--- a/guix/utils.scm
+++ b/guix/utils.scm
@@ -21,6 +21,8 @@
 ;;; Copyright © 2023 Zheng Junjie <873216071 <at> qq.com>
 ;;; Copyright © 2023 Foundation Devices, Inc. <hello <at> foundationdevices.com>
 ;;; Copyright © 2024 Herman Rimm <herman <at> rimm.ee>
+;;; Copyright © 2025 Tomas Volf <~@wolfsden.cz>
+;;; Copyright © 2025 45mg <45mg.writes <at> gmail.com>
 ;;;
 ;;; This file is part of GNU Guix.
 ;;;
@@ -44,6 +46,8 @@ (define-module (guix utils)
   #:use-module (srfi srfi-11)
   #:use-module (srfi srfi-26)
   #:use-module (srfi srfi-71)
+  #:use-module (srfi srfi-35)  ;TODO remove after moving invoke/stdout
+  #:use-module (ice-9 popen)  ;TODO remove after moving invoke/stdout
   #:use-module (rnrs io ports)                    ;need 'port-position' etc.
   #:use-module ((rnrs bytevectors) #:select (bytevector-u8-set!))
   #:use-module (guix memoization)
@@ -163,6 +167,9 @@ (define-module (guix utils)
             call-with-compressed-output-port
             canonical-newline-port
 
+            chain-cut
+            invoke/stdout  ;TODO move to (guix build utils)
+
             string-distance
             string-closest
 
@@ -1193,6 +1200,60 @@ (define-syntax current-source-directory
           ;; raising an error would upset Geiser users
           #f))))))
 
+
+;;;
+;;; Higher-order functions.
+;;;
+
+(define-syntax chain-cut
+  (lambda (x)
+    "Apply each successive form to the result of evaluating the previous one.
+Before applying, expand each form (op ...) to (cut op ...).
+
+Examples:
+
+    (chain-cut '(1 2 3) cdr car)
+ => (car (cdr '(1 2 3)))
+
+    (chain-cut 2 (- 3 <>) 1+)
+ => (1+ ((cut - 3 <>) 2))
+ => (1+ (- 3 2))
+"
+    (syntax-case x ()
+      ((chain-cut init op) (identifier? #'op)
+       #'(op init))
+      ((chain-cut init (op ...))
+       #'((cut op ...) init))
+      ((chain-cut init op op* ...) (identifier? #'op)
+       #'(chain-cut (op init) op* ...))
+      ((chain-cut init (op ...) op* ...)
+       #'(chain-cut ((cut op ...) init) op* ...)))))
+
+;; Copied from (guix build utils); remove
+(define-condition-type &invoke-error &error
+  invoke-error?
+  (program      invoke-error-program)
+  (arguments    invoke-error-arguments)
+  (exit-status  invoke-error-exit-status)
+  (term-signal  invoke-error-term-signal)
+  (stop-signal  invoke-error-stop-signal))
+;; TODO move to (guix build utils)
+(define (invoke/stdout program . args)
+  "Invoke PROGRAM with ARGS and capture PROGRAM's standard output.  If PROGRAM
+succeeds, return its standard output as a string.  Otherwise, raise an
+'&invoke-error' condition."
+  (let* ((port (apply open-pipe* OPEN_READ program args))
+         (data (get-string-all port))
+         (code (close-pipe port)))
+    (unless (zero? code)
+      (raise (condition (&invoke-error
+                         (program program)
+                         (arguments args)
+                         (exit-status (status:exit-val code))
+                         (term-signal (status:term-sig code))
+                         (stop-signal (status:stop-sig code))))))
+    data))
+
 
 ;;;
 ;;; String comparison.
diff --git a/tests/fork.sh b/tests/fork.sh
new file mode 100644
index 0000000000..f6c72dba73
--- /dev/null
+++ b/tests/fork.sh
@@ -0,0 +1,135 @@
+# GNU Guix --- Functional package management for GNU
+# Copyright © 2020, 2022, 2024 Ludovic Courtès <ludo <at> gnu.org>
+#
+# This file is part of GNU Guix.
+#
+# GNU Guix is free software; you can redistribute it and/or modify it
+# under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 3 of the License, or (at
+# your option) any later version.
+#
+# GNU Guix is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with GNU Guix.  If not, see <http://www.gnu.org/licenses/>.
+
+#
+# Test forking Guix using the etc/fork.scm script, and fork authentication via
+# `guix git authenticate --branch`.
+#
+
+set -e
+
+gpg_fingerprint() {
+    flag=--list-keys
+    [ "$2" == --file ] && flag=--show-keys
+    gpg --quiet --with-colons "$flag" --with-fingerprint "$1" | awk -F: '$1 == "fpr" {print $10;}'
+}
+
+test_directory="$(mktemp -d)"
+trap 'chmod -Rf +w "$test_directory"; rm -rf "$test_directory"' EXIT
+
+### Sanity checks
+
+# Skip if git is not available.
+command -v git > /dev/null || exit 77
+
+# Skip if gpg is not available.
+command -v gpg > /dev/null || exit 77
+
+# Skip if we're not in a Git checkout.
+[ -d "$abs_top_srcdir/.git" ] || exit 77
+
+# Skip if there's no 'keyring' branch.
+guile -c '(use-modules (git))
+  (member "refs/heads/keyring" (branch-list (repository-open ".")))' || \
+    exit 77
+
+### Create a dummy test key to sign commits with
+
+# Create a temporary substitute for ~/.gnupg
+export GNUPGHOME="$test_directory"/.gnupg
+mkdir -p $GNUPGHOME
+# gpg expects correct perms
+chmod 700 $GNUPGHOME
+chown $(whoami) $GNUPGHOME
+
+# Generate a test key
+gpg --quiet --batch --gen-key <<EOF
+%echo Generating test OpenPGP key...
+Key-Type: DSA
+Name-Real: testuser <at> no.mail
+%no-protection
+%commit
+%echo done.
+EOF
+
+# Get the test key fingerprint
+TEST_KEY=$(gpg_fingerprint testuser)
+
+### Clone this repository to the test directory
+git clone --branch master "$abs_top_srcdir" "$test_directory"/guix
+
+### Create the repository.
+# Create the fork branch from a commit that's not too far from Guix's
+# introduction, so that the authentication doesn't take too long.
+FORK_BASE_COMMIT=90c4298eede94cd353fc3dd0e3099af7b5ba7283
+"$abs_top_srcdir"/etc/fork.scm "$TEST_KEY" "$test_directory"/guix --use-existing \
+    --from="$FORK_BASE_COMMIT" \
+    --channel-url=/foo/bar \
+    --git-parameter=user.name=test-user --git-parameter=user.email=test-user <at> no.mail
+
+### Tests
+
+cd "$test_directory"/guix || exit 77
+
+#### remotes
+
+# Test that the 'upstream' remote is the one corresponding to where the
+# repository was cloned from.
+test "$(git remote get-url upstream)" == "$abs_top_srcdir"
+
+# Test that the 'origin' remote has the given --channel-url.
+test "$(git remote get-url origin)" == /foo/bar
+
+#### keyring branch
+
+git switch --quiet keyring
+
+# Test that only one new file has been added, and there have been no other
+# changes.
+KEY_FILE_NAME="$(git diff --diff-filter=A --name-only upstream/keyring keyring)"
+test "$KEY_FILE_NAME" = "$(git diff --name-only upstream/keyring keyring)"
+
+# Test that the added file is correctly named.
+CORRECT_KEY_FILE_NAME=testuser-"${TEST_KEY:(-8)}".key
+test "$KEY_FILE_NAME" = "$CORRECT_KEY_FILE_NAME"
+
+# Test that the added file contains the correct key.
+test "$(gpg_fingerprint "$KEY_FILE_NAME" --file)" = "$TEST_KEY"
+
+#### fork authentication
+
+# Test that the 'fork' branch exists.
+git show-ref --verify --quiet refs/heads/fork
+
+git switch --quiet fork
+
+# Test that only .guix_authorizations and .guix_channel have been modified.
+test "$(git diff --name-only "$FORK_BASE_COMMIT" fork)" = "\
+.guix-authorizations
+.guix-channel"
+
+# test that the fork's base commit can be authenticated
+# (authenticating upstream)
+guix git authenticate \
+    9edb3f66fd807b096b48283debdcddccfea34bad \
+    "BBB0 2DDF 2CEA F6A8 0D1D  E643 A2A0 6DF2 A33A 54FA" \
+    --end="$FORK_BASE_COMMIT"
+
+# Test that the fork branch fails to authenticate with the upstream
+# introduction, as it is not signed by an authorized key.
+! guix git authenticate
-- 
2.48.1





Information forwarded to guix <at> cbaines.net, dev <at> jpoiret.xyz, ludo <at> gnu.org, othacehe <at> gnu.org, maxim.cournoyer <at> gmail.com, zimon.toutoune <at> gmail.com, me <at> tobias.gr, guix-patches <at> gnu.org:
bug#75981; Package guix-patches. (Sun, 23 Feb 2025 13:26:02 GMT) Full text and rfc822 format available.

Message #92 received at 75981 <at> debbugs.gnu.org (full text, mbox):

From: 45mg <45mg.writes <at> gmail.com>
To: 75981 <at> debbugs.gnu.org
Cc: Nicolas Graves <ngraves <at> ngraves.fr>,
 Maxim Cournoyer <maxim.cournoyer <at> gmail.com>,
 Simon Tournier <zimon.toutoune <at> gmail.com>, Tomas Volf <~@wolfsden.cz>,
 45mg <45mg.writes <at> gmail.com>,
 Ludovic Courtès <ludo <at> gnu.org>,
 Liliana Marie Prikler <liliana.prikler <at> gmail.com>,
 Ricardo Wurmus <rekado <at> elephly.net>, Attila Lendvai <attila <at> lendvai.name>,
 Simon Streit <simon <at> netpanic.org>
Subject: [PATCH v2 2/2] scripts: Add `guix git authenticate --branch`.
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 <ludo <at> gnu.org>
+;;; Copyright © 2025 45mg <45mg.writes <at> gmail.com>
 ;;;
 ;;; 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 test-user <at> no.mail
+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





Information forwarded to guix-patches <at> gnu.org:
bug#75981; Package guix-patches. (Sun, 23 Feb 2025 13:29:02 GMT) Full text and rfc822 format available.

Message #95 received at 75981 <at> debbugs.gnu.org (full text, mbox):

From: 45mg <45mg.writes <at> gmail.com>
To: Maxim Cournoyer <maxim.cournoyer <at> gmail.com>, 45mg <45mg.writes <at> gmail.com>
Cc: Josselin Poiret <dev <at> jpoiret.xyz>, Nicolas Graves <ngraves <at> ngraves.fr>,
 Simon Tournier <zimon.toutoune <at> gmail.com>, Mathieu Othacehe <othacehe <at> gnu.org>,
 Tomas Volf <~@wolfsden.cz>, Tobias Geerinckx-Rice <me <at> tobias.gr>,
 Liliana Marie Prikler <liliana.prikler <at> gmail.com>, 75981 <at> debbugs.gnu.org,
 Ricardo Wurmus <rekado <at> elephly.net>, Christopher Baines <guix <at> cbaines.net>,
 Attila Lendvai <attila <at> lendvai.name>,
 Ludovic Courtès <ludo <at> gnu.org>
Subject: Re: [bug#75981] [PATCH (WIP) v1.5 1/4] Add 'guix fork create'.
Date: Sun, 23 Feb 2025 13:27:58 +0000
Hi Maxim,

I've implemented most of your feedback in the v2 - at least, the
feedback that still applies, since v2 is a pretty big refactor. I'm
replying here to the things I /didn't/ address, to explain why.

Maxim Cournoyer <maxim.cournoyer <at> gmail.com> writes:

>> +
>> +;;;
>> +;;; Helper prodecures.
>> +;;;
>> +
>> +(define (fingerprint->key-file-name fingerprint)
>> +  (let* ((listing (invoke/stdout "gpg" "--list-key" "--with-colons" fingerprint))
>> +         (uid (chain-cut listing
>> +                           (string-split <> #\newline)
>> +                           (filter (cut string-prefix? "uid:" <>) <>)
>> +                           first
>
> If there are no key for FINGERPRINT, `first' will fail with a cryptic
> error here.  It should ideally throw a useful exception.

I tested it, and it doesn't, because 'invoke/stdout' has error handling:

--8<---------------cut here---------------start------------->8---
scheme@(guile-user)> (fingerprint->key-file-name "3CE464558A84FDC69DB40CFB090B11993D9AEBB5")  ;Ludo's key, which I have since I needed it to verify the Guix installer .iso
$1 = "ludo-3D9AEBB5.key"
scheme@(guile-user)> (fingerprint->key-file-name "abcdef")
gpg: error reading key: No public key
ice-9/boot-9.scm:1685:16: In procedure raise-exception:
Wrong type (expecting exact integer): #<&invoke-error program: "gpg" arguments: ("--list-key" "--with-colons" "abcdef") exit-status: 2 term-signal: #f stop-signal: #f>
--8<---------------cut here---------------end--------------->8---

I think this sufficiently indicates what the error is.

> We prefer to use guile-git throughout Guix, as it has a proper Scheme
> interface.  Have you tried using it instead of shelling out to git?
> Perhaps it was missing some features you needed?

Yes, IIRC renaming remotes wasn't supported. But the main reason was
that I wanted to implement a --dry-run option that would print the
commands to be executed, which would help clarify what the script is
doing. I've done that in v2.

>> +      (unless use-existing?
>> +        (info (G_ "Cloning from upstream ~a...~%") upstream)
>> +        (invoke "git" "clone" upstream directory))
>
> Why not using the above defined invoke-git here?

'invoke-git' acts on the git repository in 'directory'. Here, we are
creating that repository.

>>  
>> @@ -1193,6 +1200,60 @@ (define-syntax current-source-directory
>>            ;; raising an error would upset Geiser users
>>            #f))))))
>>  
>> +
>> +;;;
>> +;;; Higher-order functions.
>> +;;;
>> +
>> +(define-syntax chain-cut
>> +  (lambda (x)
>> +    "Apply each successive form to the result of evaluating the previous one.
>> +Before applying, expand each form (op ...) to (cut op ...).
>> +
>> +Examples:
>> +
>> +    (chain-cut '(1 2 3) cdr car)
>> + => (car (cdr '(1 2 3)))
>> +
>> +    (chain-cut 2 (- 3 <>) 1+)
>> + => (1+ ((cut - 3 <>) 2))
>> + => (1+ (- 3 2))
>> +"
>> +    (syntax-case x ()
>> +      ((chain-cut init op) (identifier? #'op)
>> +       #'(op init))
>> +      ((chain-cut init (op ...))
>> +       #'((cut op ...) init))
>> +      ((chain-cut init op op* ...) (identifier? #'op)
>> +       #'(chain-cut (op init) op* ...))
>> +      ((chain-cut init (op ...) op* ...)
>> +       #'(chain-cut ((cut op ...) init) op* ...)))))
>
> I'm not 100% convince on the above, as it seems it leads to bunching a
> whole lot of procedures together and not paying attention to potential
> exceptions/errors returned.  But maybe that's OK if the whole form is
> wrapped in an error handler.

That's a fair point, but I think we're always going to have this
tradeoff when we do a lot of function composition. For example:

--8<---------------cut here---------------start------------->8---
scheme@(guile-user)> ((compose car car cdr) '(0 (1)))
$1 = 1
scheme@(guile-user)> ((compose car car cdr) '(0 1))
ice-9/boot-9.scm:1685:16: In procedure raise-exception:
In procedure car: Wrong type (expecting pair): 1
--8<---------------cut here---------------end--------------->8---
Here it's not immediately clear which 'car' failed.

Ideally, we'd be able to make Guile give us a proper backtrace showing
that the error originates in 'chain-cut'... but given my experience with
Guile backtraces I doubt that's even possible :_)

> That's it!  This adding a whole new command line, and to get everyone
> aware, I think going through the new GCD (Guix Common Document/RFC)
> process is warranted before it is to be accepted/included in
> Guix.
>
> -- 
> Thanks,
> Maxim




Information forwarded to guix-patches <at> gnu.org:
bug#75981; Package guix-patches. (Sun, 23 Feb 2025 13:42:02 GMT) Full text and rfc822 format available.

Message #98 received at 75981 <at> debbugs.gnu.org (full text, mbox):

From: Liliana Marie Prikler <liliana.prikler <at> gmail.com>
To: 45mg <45mg.writes <at> gmail.com>, 75981 <at> debbugs.gnu.org
Cc: Nicolas Graves <ngraves <at> ngraves.fr>,
 Maxim Cournoyer <maxim.cournoyer <at> gmail.com>,
 Simon Tournier <zimon.toutoune <at> gmail.com>, Tomas Volf <~@wolfsden.cz>,
 Ludovic Courtès <ludo <at> gnu.org>,
 Ricardo Wurmus <rekado <at> elephly.net>, Attila Lendvai <attila <at> lendvai.name>,
 Simon Streit <simon <at> netpanic.org>
Subject: Re: [PATCH v2 1/2] etc: Add fork.scm.
Date: Sun, 23 Feb 2025 14:42:04 +0100
Am Sonntag, dem 23.02.2025 um 18:50 +0530 schrieb 45mg:
> +(define %usage
> +  (format #f (G_ "Usage: etc/fork.scm SIGNING_KEY [DIRECTORY
> OPTIONS...]
> +Create a fork of Guix in DIRECTORY, using SIGNING_KEY to sign the
> introductory
> +commit.
> +DIRECTORY defaults to ../guix-fork.
I would make DIRECTORY a mandatory argument and SIGNING_KEY an option
(-S/--gpg-sign=KEY).  The default value can be fetched from git config.

Cheers




Information forwarded to guix-patches <at> gnu.org:
bug#75981; Package guix-patches. (Sun, 23 Feb 2025 13:56:02 GMT) Full text and rfc822 format available.

Message #101 received at 75981 <at> debbugs.gnu.org (full text, mbox):

From: Liliana Marie Prikler <liliana.prikler <at> gmail.com>
To: 45mg <45mg.writes <at> gmail.com>, 75981 <at> debbugs.gnu.org
Cc: Nicolas Graves <ngraves <at> ngraves.fr>,
 Maxim Cournoyer <maxim.cournoyer <at> gmail.com>,
 Simon Tournier <zimon.toutoune <at> gmail.com>, Tomas Volf <~@wolfsden.cz>,
 Ludovic Courtès <ludo <at> gnu.org>,
 Ricardo Wurmus <rekado <at> elephly.net>, Attila Lendvai <attila <at> lendvai.name>,
 Simon Streit <simon <at> netpanic.org>
Subject: Re: [PATCH v2 2/2] scripts: Add `guix git authenticate --branch`.
Date: Sun, 23 Feb 2025 14:56:20 +0100
Am Sonntag, dem 23.02.2025 um 18:50 +0530 schrieb 45mg:
>  (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~%")))
I would use guix.authentication[.BRANCH].KEY for consistency.  Yes, you
can use periods in Git config subsection names as per [1]:
> Subsection names are case sensitive and can contain any characters
> except newline and the null byte. 

Cheers

[1] https://git-scm.com/docs/git-config

Information forwarded to guix-patches <at> gnu.org:
bug#75981; Package guix-patches. (Mon, 24 Feb 2025 12:43:02 GMT) Full text and rfc822 format available.

Message #104 received at 75981 <at> debbugs.gnu.org (full text, mbox):

From: 45mg <45mg.writes <at> gmail.com>
To: 45mg <45mg.writes <at> gmail.com>, 75981 <at> debbugs.gnu.org
Cc: Nicolas Graves <ngraves <at> ngraves.fr>,
 Maxim Cournoyer <maxim.cournoyer <at> gmail.com>,
 Simon Tournier <zimon.toutoune <at> gmail.com>, Tomas Volf <~@wolfsden.cz>,
 45mg <45mg.writes <at> gmail.com>, Ludovic Courtès <ludo <at> gnu.org>,
 Liliana Marie Prikler <liliana.prikler <at> gmail.com>,
 Ricardo Wurmus <rekado <at> elephly.net>, Attila Lendvai <attila <at> lendvai.name>,
 Simon Streit <simon <at> netpanic.org>
Subject: Re: [PATCH v2 0/2] Support authenticated forks.
Date: Mon, 24 Feb 2025 12:41:46 +0000
45mg <45mg.writes <at> gmail.com> writes:

> There are two aspects of a solution to handle forks - fork
> /authentication/, which is what we've discussed so far; and fork
> /management/, which includes keeping a fork updated, rebasing patch
> branches onto the latest master, etc. I would like to keep this patch
> series restricted to the former, and avoid addressing the latter, for
> two main reasons.

It just occurred to me that I should still have documented some of the
issues that come up with fork management in Guix, even if I don't want
to include any fancy tooling for it in this patch series. For instance,
I should have mentioned how non-committers need to rebase upstream
commits onto their fork branch instead of merging them in, and
demonstrated how this can be done via the Git CLI. And in general, I
should have walked users through the complexities of updating to new
versions of patches, etc. without altering history (as altering history
requires you to pull with `--allow-downgrades`).

I guess that's something to address with v3, whenever I get around to
it...




Information forwarded to guix-patches <at> gnu.org:
bug#75981; Package guix-patches. (Mon, 24 Feb 2025 12:46:02 GMT) Full text and rfc822 format available.

Message #107 received at 75981 <at> debbugs.gnu.org (full text, mbox):

From: 45mg <45mg.writes <at> gmail.com>
To: Liliana Marie Prikler <liliana.prikler <at> gmail.com>, 45mg
 <45mg.writes <at> gmail.com>, 75981 <at> debbugs.gnu.org
Cc: Nicolas Graves <ngraves <at> ngraves.fr>,
 Maxim Cournoyer <maxim.cournoyer <at> gmail.com>,
 Simon Tournier <zimon.toutoune <at> gmail.com>, Tomas Volf <~@wolfsden.cz>,
 Ludovic Courtès <ludo <at> gnu.org>,
 Ricardo Wurmus <rekado <at> elephly.net>, Attila Lendvai <attila <at> lendvai.name>,
 Simon Streit <simon <at> netpanic.org>
Subject: Re: [PATCH v2 2/2] scripts: Add `guix git authenticate --branch`.
Date: Mon, 24 Feb 2025 12:45:23 +0000
Liliana Marie Prikler <liliana.prikler <at> gmail.com> writes:

> Am Sonntag, dem 23.02.2025 um 18:50 +0530 schrieb 45mg:
>>  (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~%")))
> I would use guix.authentication[.BRANCH].KEY for consistency.  Yes, you
> can use periods in Git config subsection names as per [1]:
>> Subsection names are case sensitive and can contain any characters
>> except newline and the null byte. 

This would indeed be cleaner. But as you can see in the diffs for
`config-value` and guix.texi, 'guix.authentication-BRANCH' was already
chosen, and I didn't want to break anyone's workflow by changing it.

> Cheers
>
> [1] https://git-scm.com/docs/git-config

Information forwarded to guix-patches <at> gnu.org:
bug#75981; Package guix-patches. (Mon, 24 Feb 2025 12:55:02 GMT) Full text and rfc822 format available.

Message #110 received at 75981 <at> debbugs.gnu.org (full text, mbox):

From: 45mg <45mg.writes <at> gmail.com>
To: Liliana Marie Prikler <liliana.prikler <at> gmail.com>, 45mg
 <45mg.writes <at> gmail.com>, 75981 <at> debbugs.gnu.org
Cc: Nicolas Graves <ngraves <at> ngraves.fr>,
 Maxim Cournoyer <maxim.cournoyer <at> gmail.com>,
 Simon Tournier <zimon.toutoune <at> gmail.com>, Tomas Volf <~@wolfsden.cz>,
 Ludovic Courtès <ludo <at> gnu.org>,
 Ricardo Wurmus <rekado <at> elephly.net>, Attila Lendvai <attila <at> lendvai.name>,
 Simon Streit <simon <at> netpanic.org>
Subject: Re: [PATCH v2 1/2] etc: Add fork.scm.
Date: Mon, 24 Feb 2025 12:54:04 +0000
Liliana Marie Prikler <liliana.prikler <at> gmail.com> writes:

> Am Sonntag, dem 23.02.2025 um 18:50 +0530 schrieb 45mg:
>> +(define %usage
>> +  (format #f (G_ "Usage: etc/fork.scm SIGNING_KEY [DIRECTORY
>> OPTIONS...]
>> +Create a fork of Guix in DIRECTORY, using SIGNING_KEY to sign the
>> introductory
>> +commit.
>> +DIRECTORY defaults to ../guix-fork.
> I would make DIRECTORY a mandatory argument and SIGNING_KEY an option
> (-S/--gpg-sign=KEY).  The default value can be fetched from git config.

While that could be convenient, I think a lot of people may not have set
up automatic commit signing before creating their fork (I was one of
those people).

> Cheers




Information forwarded to guix-patches <at> gnu.org:
bug#75981; Package guix-patches. (Mon, 24 Feb 2025 20:22:02 GMT) Full text and rfc822 format available.

Message #113 received at 75981 <at> debbugs.gnu.org (full text, mbox):

From: Liliana Marie Prikler <liliana.prikler <at> gmail.com>
To: 45mg <45mg.writes <at> gmail.com>, 75981 <at> debbugs.gnu.org
Subject: Re: [PATCH v2 2/2] scripts: Add `guix git authenticate --branch`.
Date: Mon, 24 Feb 2025 21:22:17 +0100
Am Montag, dem 24.02.2025 um 12:45 +0000 schrieb 45mg:
> Liliana Marie Prikler <liliana.prikler <at> gmail.com> writes:
> 
> > Am Sonntag, dem 23.02.2025 um 18:50 +0530 schrieb 45mg:
> > >  (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~%")))
> > I would use guix.authentication[.BRANCH].KEY for consistency.  Yes,
> > you
> > can use periods in Git config subsection names as per [1]:
> > > Subsection names are case sensitive and can contain any
> > > characters
> > > except newline and the null byte. 
> 
> This would indeed be cleaner. But as you can see in the diffs for
> `config-value` and guix.texi, 'guix.authentication-BRANCH' was
> already chosen, and I didn't want to break anyone's workflow by
> changing it.
Uhm… if I read this correctly, this is still a patch in progress and
not *that* widely used, no?  Is there prior art to go by?

Cheers

Information forwarded to guix-patches <at> gnu.org:
bug#75981; Package guix-patches. (Tue, 25 Feb 2025 06:46:01 GMT) Full text and rfc822 format available.

Message #116 received at 75981 <at> debbugs.gnu.org (full text, mbox):

From: 45mg <45mg.writes <at> gmail.com>
To: Liliana Marie Prikler <liliana.prikler <at> gmail.com>, 45mg
 <45mg.writes <at> gmail.com>, 75981 <at> debbugs.gnu.org
Subject: Re: [PATCH v2 2/2] scripts: Add `guix git authenticate --branch`.
Date: Tue, 25 Feb 2025 06:45:12 +0000
Hi Liliana,

Liliana Marie Prikler <liliana.prikler <at> gmail.com> writes:

> Am Montag, dem 24.02.2025 um 12:45 +0000 schrieb 45mg:

> Uhm… if I read this correctly, this is still a patch in progress and
> not *that* widely used, no?  Is there prior art to go by?
>

Sorry if it wasn't clear - guix.authentication-BRANCH is already
supported /in upstream Guix/, and has been since long before I started
work on v1.

Here's the bit from "(guix) Invoking guix git authenticate" that I
quoted in the cover letter:

> Should you have branches that require different introductions, you
> can specify them directly in ‘.git/config’.  For example, if the branch
> called ‘personal-fork’ has a different introduction than other branches,
> you can extend ‘.git/config’ along these lines:
>
> [guix "authentication-personal-fork"]
> introduction-commit = cabba936fd807b096b48283debdcddccfea3900d
> introduction-signer = C0FF EECA BBA9 E6A8 0D1D  E643 A2A0 6DF2 A33A 54FA
> keyring = keyring




Information forwarded to guix-patches <at> gnu.org:
bug#75981; Package guix-patches. (Tue, 25 Feb 2025 19:08:02 GMT) Full text and rfc822 format available.

Message #119 received at 75981 <at> debbugs.gnu.org (full text, mbox):

From: Liliana Marie Prikler <liliana.prikler <at> gmail.com>
To: 45mg <45mg.writes <at> gmail.com>, 75981 <at> debbugs.gnu.org
Subject: Re: [PATCH v2 2/2] scripts: Add `guix git authenticate --branch`.
Date: Tue, 25 Feb 2025 20:08:03 +0100
Am Dienstag, dem 25.02.2025 um 06:45 +0000 schrieb 45mg:
> Hi Liliana,
> 
> Liliana Marie Prikler <liliana.prikler <at> gmail.com> writes:
> 
> > Am Montag, dem 24.02.2025 um 12:45 +0000 schrieb 45mg:
> 
> > Uhm… if I read this correctly, this is still a patch in progress
> > and
> > not *that* widely used, no?  Is there prior art to go by?
> > 
> 
> Sorry if it wasn't clear - guix.authentication-BRANCH is already
> supported /in upstream Guix/, and has been since long before I
> started work on v1.
> 
> Here's the bit from "(guix) Invoking guix git authenticate" that I
> quoted in the cover letter:
> 
> > Should you have branches that require different introductions, you
> > can specify them directly in ‘.git/config’.  For example, if the
> > branch
> > called ‘personal-fork’ has a different introduction than other
> > branches,
> > you can extend ‘.git/config’ along these lines:
> > 
> > [guix "authentication-personal-fork"]
> > introduction-commit = cabba936fd807b096b48283debdcddccfea3900d
> > introduction-signer = C0FF EECA BBA9 E6A8 0D1D  E643 A2A0 6DF2 A33A
> > 54FA
> > keyring = keyring
Ahh, thanks.  In that case never mind.

Cheers




Information forwarded to guix-patches <at> gnu.org:
bug#75981; Package guix-patches. (Thu, 27 Feb 2025 15:17:02 GMT) Full text and rfc822 format available.

Message #122 received at 75981 <at> debbugs.gnu.org (full text, mbox):

From: Maxim Cournoyer <maxim.cournoyer <at> gmail.com>
To: 45mg <45mg.writes <at> gmail.com>
Cc: Nicolas Graves <ngraves <at> ngraves.fr>,
 Simon Tournier <zimon.toutoune <at> gmail.com>, Tomas Volf <~@wolfsden.cz>,
 Ludovic Courtès <ludo <at> gnu.org>,
 Liliana Marie Prikler <liliana.prikler <at> gmail.com>, 75981 <at> debbugs.gnu.org,
 Ricardo Wurmus <rekado <at> elephly.net>, Attila Lendvai <attila <at> lendvai.name>,
 Simon Streit <simon <at> netpanic.org>
Subject: Re: [PATCH v2 0/2] Support authenticated forks.
Date: Fri, 28 Feb 2025 00:16:11 +0900
Hi 45mg,

45mg <45mg.writes <at> gmail.com> writes:

> Hi Guix,
>
> This revision is a major refactor of the previous one, v1.5 [1]. If you
> were one of those who felt that adding a entire command line was
> excessive, this one will make you happy :)
>
> The changes are as follows:
>
> * `guix fork create` is now `etc/fork.scm`

That's a good stop gap solution, but even better would be to have it
work as a proper Guix extension; this way it could find its place as a
top level Guix command for its users, without burdening upstream Guix.
Could you please take a look and see if that would be workable?

The extension could be referred to in the manual if you worry about
discoverability.

-- 
Thanks,
Maxim




This bug report was last modified 109 days ago.

Previous Next


GNU bug tracking system
Copyright (C) 1999 Darren O. Benham, 1997,2003 nCipher Corporation Ltd, 1994-97 Ian Jackson.