[PATCH (WIP) v1 0/4] Add 'guix fork'.

  • Open
  • quality assurance status badge
Details
6 participants
  • 45mg
  • Attila Lendvai
  • Liliana Marie Prikler
  • Maxim Cournoyer
  • Simon Streit
  • Simon Tournier
Owner
unassigned
Submitted by
45mg
Severity
normal
4
4
45mg wrote 4 days ago
(address . guix-patches@gnu.org)
cover.1738357415.git.45mg.writes@gmail.com
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.


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
4
4
45mg wrote 4 days ago
[PATCH (WIP) v1 1/4] Add 'guix fork create'.
(address . 75981@debbugs.gnu.org)
2a950d7e5c42768724d1c8fe3bcea3ff54fb81bd.1738357415.git.45mg.writes@gmail.com
* 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

Toggle diff (491 lines)
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@telenet.be>
;;; Copyright © 2021 Brendan Tildesley <mail@brendan.scot>
;;; Copyright © 2023 Carlo Zancanaro <carlo@zancanaro.id.au>
+;;; Copyright © 2025 Tomas Volf <~@wolfsden.cz>
+;;; Copyright © 2025 45mg <45mg.writes@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@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@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"
+ (
This message was truncated. Download the full message here.
4
4
45mg wrote 4 days ago
[PATCH (WIP) v1 2/4] Add 'guix fork authenticate'.
(address . 75981@debbugs.gnu.org)
97662f19dd262168c9d8c5d76bc4bfee20d9695a.1738357415.git.45mg.writes@gmail.com
* 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

Toggle diff (375 lines)
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@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
4
4
45mg wrote 4 days ago
[PATCH (WIP) v1 3/4] Add 'guix fork update'.
(address . 75981@debbugs.gnu.org)
a8900889db07c887b8863fa774e7e38b29ea716e.1738357415.git.45mg.writes@gmail.com
* 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

Toggle diff (223 lines)
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@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
4
4
45mg wrote 4 days ago
[PATCH (WIP) v1 4/4] Document 'guix fork'.
(address . 75981@debbugs.gnu.org)
b526ba9ce6b830a6c39796c640bae984c22236d0.1738357415.git.45mg.writes@gmail.com
* 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(+)

Toggle diff (245 lines)
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
4
4
45mg wrote 4 days ago
[Tomas Volf] Re: [PATCH (WIP) 0/4] Add 'guix fork'.
(address . 75981@debbugs.gnu.org)
87ed0ivfth.fsf@gmail.com
Forwarding this from the previous thread.

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

Toggle quote (9 lines)
> 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.
-----BEGIN PGP SIGNATURE-----

iQJCBAEBCgAsFiEEt4NJs4wUfTYpiGikL7/ufbZ/wakFAmedN9EOHH5Ad29sZnNk
ZW4uY3oACgkQL7/ufbZ/wakVHg/8DFujsIYYNFJbq6BFHs472Haq38AdkZSs4jv3
tahxG/aXfxV/6BxUXtApAIBJ6XTx4rrF21WXAiP2LzchzBa+2Xy+IkjSkpaASKzv
ZENFeLJDmkb6ln9QeIvnkgGNMn/BLpWxbVqiuj/IavoXLvmVuv0n1aKpFUjAiY4C
iVPY/vJQJ2pLLxL6Zy6NRlqs2WDloV6HLB3wEX6ujRT+EykESMaBoalssfv5fKBu
RSp6zmiSK0z9D1tdz4VRsQ68+VbVFAtugrVl/1mWO5+eegr23MdVib+WH3o8zYhP
2dXubNkSzXqANgyHU2igF537/x9ZP5uZa090DIAXDifNHTLammDJaONUcPKMqVH2
PKEH/QS2ZZqkqhN//BxGNpDoXX/GUEGigzKAiZGhWYXf8PlX2aHKXpJ/zWDNu9pT
sQa/tDDoE40xodcPo6kcStEtw+sK08wKvXW7y5/pGkbKghMJvtk9tnE6xDjA18Pj
JK4vkqXL950u06iH6LBSz0Tqupauk0nIP/qcZfZ0vQG95HXomN/Kjh/9ozRDG318
mYMTPVbjRNAwoe3zYLmrE4Rnq1nN5ElnE0F4TYZSsQvQufzZxaUeBlwmkDDkg2a5
7VM2H8dbhNZw6TGZrQh3YtjaI75m0LYQq2Rg3o4DWKcyfTYlc9T8gz0Sc7XQ5SsX
eS3ZS/I=
=T9ks
-----END PGP SIGNATURE-----
-------------------- End of forwarded message --------------------
4
4
45mg wrote 3 days ago
[PATCH (WIP) v1.5 0/4] Add 'guix fork'.
(address . 75981@debbugs.gnu.org)
cover.1738408683.git.45mg.writes@gmail.com
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
4
4
45mg wrote 3 days ago
[PATCH (WIP) v1.5 1/4] Add 'guix fork create'.
(address . 75981@debbugs.gnu.org)
590b269995eb83d8fe2b584a40a58fa9ed473c54.1738408683.git.45mg.writes@gmail.com
* 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

Toggle diff (479 lines)
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@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@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))

This message was truncated. Download the full message here.
4
4
45mg wrote 3 days ago
[PATCH (WIP) v1.5 2/4] Add 'guix fork authenticate'.
(address . 75981@debbugs.gnu.org)
10c11dfc090e48aa6a3f4b1fd67543ec2bab7b40.1738408683.git.45mg.writes@gmail.com
* 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

Toggle diff (375 lines)
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@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
4
4
45mg wrote 3 days ago
[PATCH (WIP) v1.5 3/4] Add 'guix fork update'.
(address . 75981@debbugs.gnu.org)
20c828d43d189914c7a5a3de58831f74b134e796.1738408683.git.45mg.writes@gmail.com
* 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

Toggle diff (224 lines)
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@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
4
4
45mg wrote 3 days ago
[PATCH (WIP) v1.5 4/4] Document 'guix fork'.
(address . 75981@debbugs.gnu.org)
49cb491b107b5f0899209905d7679ba389bc65e6.1738408683.git.45mg.writes@gmail.com
* 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(+)

Toggle diff (245 lines)
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
M
M
Maxim Cournoyer wrote 2 days ago
Re: [bug#75981] [PATCH (WIP) v1.5 1/4] Add 'guix fork create'.
(name . 45mg)(address . 45mg.writes@gmail.com)
877c68jsr0.fsf@gmail.com
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

Below are some comments on the code.

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

Toggle quote (4 lines)
> * 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.

Toggle quote (2 lines)
> * guix/utils.scm (chain-cut): New procedure.

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

Toggle quote (6 lines)
> * 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.

[...]

Toggle quote (36 lines)
> 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.

[...]

Toggle quote (3 lines)
> --- /dev/null
> +++ b/guix/scripts/fork.scm

[...]

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

Please list modules in lexicographic order.

[...]

Toggle quote (6 lines)
> 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

[...]

Toggle quote (12 lines)
> +
> +;;;
> +;;; 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.

[...]

Toggle quote (10 lines)
> +
> +;;;
> +;;; 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...

Toggle quote (15 lines)
> +
> + (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.

Toggle quote (11 lines)
> + (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.

Toggle quote (10 lines)
> + (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?

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

Why not using the above defined invoke-git here?

Toggle quote (13 lines)
> +
> + (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.

Toggle quote (39 lines)
> + "-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!

Toggle quote (19 lines)
> +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 :-).

Toggle quote (68 lines)
> #: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).

[...]

Toggle quote (34 lines)
>
> @@ -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
M
M
Maxim Cournoyer wrote 2 days ago
Re: [bug#75981] [PATCH (WIP) v1.5 2/4] Add 'guix fork authenticate'.
(name . 45mg)(address . 45mg.writes@gmail.com)
871pwgjrpe.fsf@gmail.com
Hi,

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

Toggle quote (6 lines)
> * 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.

[...]

Toggle quote (2 lines)
> diff --git a/guix/scripts/fork/authenticate.scm b/guix/scripts/fork/authenticate.scm

[...]

Toggle quote (19 lines)
> +(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.

[...]

Toggle quote (5 lines)
> +(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)

Toggle quote (78 lines)
> + (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/

Toggle quote (25 lines)
> + (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.

Toggle quote (3 lines)
> + "Record COMMIT, SIGNER, UPSTREAM-BRANCH and KEYRING-REFERENCE in the
> +'config' file of REPOSITORY."

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

Toggle quote (19 lines)
> + (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.

Toggle quote (28 lines)
> +
> +(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).

Toggle quote (4 lines)
> + (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.

Toggle quote (15 lines)
> + ((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!

Toggle quote (64 lines)
> + (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
M
M
Maxim Cournoyer wrote 2 days ago
Re: [bug#75981] [PATCH (WIP) v1.5 3/4] Add 'guix fork update'.
(name . 45mg)(address . 45mg.writes@gmail.com)
87wme8iagd.fsf@gmail.com
Hi,

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

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

Or, "Register it."

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

OK.

[...]

Toggle quote (12 lines)
> +(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.

Toggle quote (14 lines)
> + (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 [...]"

Toggle quote (4 lines)
> +
> + -r, --repository=DIRECTORY
> + Act in the Git repository in DIRECTORY

Maybe, "Work on the Git repository in DIRECTORY"

Toggle quote (59 lines)
> + --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.

Toggle quote (10 lines)
> + 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.

Toggle quote (15 lines)
> + (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 :-)

Toggle quote (8 lines)
> + 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).

Toggle quote (18 lines)
> +
> + (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
L
L
Liliana Marie Prikler wrote 2 days ago
cb9ef50d4c30654b92ba0943d5e764965de5e214.camel@gmail.com
Am Montag, dem 03.02.2025 um 01:21 +0900 schrieb Maxim Cournoyer:
Toggle quote (11 lines)
> 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
L
L
Liliana Marie Prikler wrote 2 days ago
92af9e3af160d3ed49030e1961b98f7b9e2e857f.camel@gmail.com
Am Sonntag, dem 02.02.2025 um 19:23 +0100 schrieb Liliana Marie
Prikler:
Toggle quote (2 lines)
> > I think Liliana had that remark as well in the associated issue.
> I did remark that, yet :)
s/yet/yes/
A
A
Attila Lendvai wrote 2 days ago
Re: [bug#75981] [PATCH (WIP) v1.5 1/4] Add 'guix fork create'.
(name . Maxim Cournoyer)(address . maxim.cournoyer@gmail.com)
0lJyKFTNrG_gWbzcwgMn1ygmvYDbrcGQm9KZbFkiFBCR5dcPIwfXIdwk6LWmzfh-HZ5aLkaZkol4qmN9LdHlkXZz2OU1Vjq0QIJQti2uXUQ=@lendvai.name
Toggle quote (7 lines)
> 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:


--
• 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
S
S
Simon Streit wrote 48 hours ago
(name . Maxim Cournoyer)(address . maxim.cournoyer@gmail.com)
ygu4j1cnfwx.fsf@netpanic.org
Hello Maxim,

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

Toggle quote (6 lines)
> 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
M
M
Maxim Cournoyer wrote 45 hours ago
Re: [bug#75981] [PATCH (WIP) v1.5 4/4] Document 'guix fork'.
(name . 45mg)(address . 45mg.writes@gmail.com)
87r04fj0ct.fsf@gmail.com
Hello!

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

Toggle quote (34 lines)
> * 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.

Toggle quote (10 lines)
> +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.

Toggle quote (7 lines)
> +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/

Toggle quote (7 lines)
> +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/

Toggle quote (6 lines)
> +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).

Toggle quote (6 lines)
> +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.

Toggle quote (8 lines)
> +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/

Toggle quote (2 lines)
> +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').

Toggle quote (35 lines)
> @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.:

Toggle quote (3 lines)
> +more information on authentication of a Guix checkout, @pxref{Invoking
> +guix git authenticate}.

s/@pxref/@ref/

Toggle quote (16 lines)
> +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.

Toggle quote (4 lines)
>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/

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

to the @file{.guix-authorizations} file

Toggle quote (5 lines)
> +
> +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/

Toggle quote (2 lines)
> +on patches in branches based off of this one, in much the same way as

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

Toggle quote (5 lines)
> +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.

Toggle quote (4 lines)
> +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.

Toggle quote (3 lines)
> +Updating this channel can be done by merging the `@code{fork}' branch
> +into it.

Ditto.

Toggle quote (16 lines)
> +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}/

Toggle quote (2 lines)
> +`@code{upstream}') is renamed to `upstream', and a new `origin' remote

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

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

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

Toggle quote (16 lines)
> +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.

Toggle quote (17 lines)
> +
> +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.

Toggle quote (4 lines)
> +@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?

Toggle quote (25 lines)
> +@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).

Toggle quote (20 lines)
> +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').

Toggle quote (2 lines)
> +'commit-msg' hook.

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

Toggle quote (4 lines)
> +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
S
S
Simon Tournier wrote 31 hours ago
Re: [bug#75981] [PATCH (WIP) v1.5 1/4] Add 'guix fork create'.
87a5b39i0l.fsf@gmail.com
Hi,

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

[...]

Toggle quote (2 lines)
> * 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
M
M
Maxim Cournoyer wrote 16 hours ago
(name . Simon Streit)(address . simon@netpanic.org)
87v7tqb77j.fsf@gmail.com
Hi Simon,

Simon Streit <simon@netpanic.org> writes:

Toggle quote (16 lines)
> Hello Maxim,
>
> Maxim Cournoyer <maxim.cournoyer@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 ;-).

Toggle quote (5 lines)
> 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.

Toggle quote (11 lines)
> 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.

Toggle quote (6 lines)
> 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.

Toggle quote (8 lines)
> 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
?
Your comment

Commenting via the web interface is currently disabled.

To comment on this conversation send an email to 75981@debbugs.gnu.org

To respond to this issue using the mumi CLI, first switch to it
mumi current 75981
Then, you may apply the latest patchset in this issue (with sign off)
mumi am -- -s
Or, compose a reply to this issue
mumi compose
Or, send patches to this issue
mumi send-email *.patch