* [bug#75973] [PATCH (WIP) 0/4] Add 'guix fork'.
@ 2025-01-31 18:09 45mg
2025-01-31 18:15 ` bug#75973: close (sent by mistake) 45mg
` (4 more replies)
0 siblings, 5 replies; 36+ messages in thread
From: 45mg @ 2025-01-31 18:09 UTC (permalink / raw)
To: 75973
Cc: Nicolas Graves, Tomas Volf, 45mg, Liliana Marie Prikler,
Ricardo Wurmus, Attila Lendvai, Ludovic Courtès,
Maxim Cournoyer
***DRAFT***
If you're seeing this, it was sent early by mistake. Sorry!
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.
Finally - 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)
[1] https://issues.guix.gnu.org/75552
[2] https://lists.gnu.org/archive/html/help-guix/2023-09/msg00078.html
[3] https://lists.nongnu.org/archive/html/bug-guix/2025-01/msg00139.html
[4] https://lists.nongnu.org/archive/html/bug-guix/2025-01/msg00130.html
[5] https://lists.nongnu.org/archive/html/bug-guix/2025-01/msg00135.html
[6] https://git.wolfsden.cz/guix/tree/etc/fork-guix
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 | 149 +++++++++++++
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 | 330 +++++++++++++++++++++++++++++
guix/scripts/fork/create.scm | 253 ++++++++++++++++++++++
guix/scripts/fork/update.scm | 181 ++++++++++++++++
guix/scripts/git/authenticate.scm | 45 +---
guix/utils.scm | 33 +++
13 files changed, 1135 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.47.1
^ permalink raw reply [flat|nested] 36+ messages in thread
* bug#75973: close (sent by mistake)
2025-01-31 18:09 [bug#75973] [PATCH (WIP) 0/4] Add 'guix fork' 45mg
@ 2025-01-31 18:15 ` 45mg
2025-01-31 18:16 ` [bug#75973] " 45mg
` (3 subsequent siblings)
4 siblings, 0 replies; 36+ messages in thread
From: 45mg @ 2025-01-31 18:15 UTC (permalink / raw)
To: 75973-done
Closing; will send this again when it's done.
^ permalink raw reply [flat|nested] 36+ messages in thread
* [bug#75973] close (sent by mistake)
2025-01-31 18:09 [bug#75973] [PATCH (WIP) 0/4] Add 'guix fork' 45mg
2025-01-31 18:15 ` bug#75973: close (sent by mistake) 45mg
@ 2025-01-31 18:16 ` 45mg
2025-01-31 18:32 ` [bug#75975] [PATCH (WIP) 1/4] Add 'guix fork create' 45mg
` (2 subsequent siblings)
4 siblings, 0 replies; 36+ messages in thread
From: 45mg @ 2025-01-31 18:16 UTC (permalink / raw)
To: 75973, 75973-done
Closing; will send this again when it's done.
^ permalink raw reply [flat|nested] 36+ messages in thread
* [bug#75975] [PATCH (WIP) 1/4] Add 'guix fork create'.
2025-01-31 18:09 [bug#75973] [PATCH (WIP) 0/4] Add 'guix fork' 45mg
2025-01-31 18:15 ` bug#75973: close (sent by mistake) 45mg
2025-01-31 18:16 ` [bug#75973] " 45mg
@ 2025-01-31 18:32 ` 45mg
2025-01-31 18:32 ` [bug#75975] [PATCH (WIP) 2/4] Add 'guix fork authenticate' 45mg
` (2 more replies)
2025-01-31 20:10 ` [bug#75975] [PATCH (WIP) 0/4] Add " vicvbcun
2025-01-31 20:51 ` Tomas Volf
4 siblings, 3 replies; 36+ messages in thread
From: 45mg @ 2025-01-31 18:32 UTC (permalink / raw)
To: 75975
Cc: 45mg, Andreas Enge, Christopher Baines, Janneke Nieuwenhuizen,
Josselin Poiret, Ludovic Courtès, Mathieu Othacehe,
Simon Tournier, Tobias Geerinckx-Rice
* guix/scripts/fork.scm, guix/scripts/fork/create.scm: New files.
* Makefile.am (MODULES): Add the new files.
* guix/build/utils.scm (invoke/stdout): New procedure.
* guix/utils.scm (chain-cut): New procedure.
* guix/scripts/git/authenticate.scm
(commit-short-id): Remove procedure, and use its existing duplicate in
guix/channels.scm.
(openpgp-fingerprint*, current-branch, show-stats): Move procedures to
the files below.
* guix/channels.scm (openpgp-fingerprint*): Moved here.
* guix/git.scm (repository-current-branch): Moved here and renamed from
'current-branch'.
* guix/git-authenticate.scm (show-authentication-stats): Moved here and
renamed from 'show-stats'.
Change-Id: I45ba37f434e136f6d496c741d9a933280f9ccf88
---
Makefile.am | 2 +
guix/build/utils.scm | 20 +++
guix/channels.scm | 13 ++
guix/git-authenticate.scm | 17 ++
guix/git.scm | 10 ++
guix/scripts/fork.scm | 67 ++++++++
guix/scripts/fork/create.scm | 257 ++++++++++++++++++++++++++++++
guix/scripts/git/authenticate.scm | 45 +-----
guix/utils.scm | 33 ++++
9 files changed, 423 insertions(+), 41 deletions(-)
create mode 100644 guix/scripts/fork.scm
create mode 100644 guix/scripts/fork/create.scm
diff --git a/Makefile.am b/Makefile.am
index f759803b8b..c628450a5a 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -377,6 +377,8 @@ MODULES = \
guix/scripts/size.scm \
guix/scripts/git.scm \
guix/scripts/git/authenticate.scm \
+ guix/scripts/fork.scm \
+ guix/scripts/fork/create.scm \
guix/scripts/graph.scm \
guix/scripts/weather.scm \
guix/scripts/container.scm \
diff --git a/guix/build/utils.scm b/guix/build/utils.scm
index 94714bf397..e8bd39f5de 100644
--- a/guix/build/utils.scm
+++ b/guix/build/utils.scm
@@ -10,6 +10,8 @@
;;; Copyright © 2021, 2022 Maxime Devos <maximedevos@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))
+
\f
;;;
;;; 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~%")))
+
+\f
+;;;
+;;; 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)))))
+
+\f
+;;;
+;;; Entry point.
+;;;
+
+(define (guix-fork-create . args)
+ (define options
+ (parse-command-line args %options (list %default-options)
+ #:build-options? #f))
+
+ (define (command-line-arguments lst)
+ (reverse (filter-map (match-lambda
+ (('argument . arg) arg)
+ (_ #f))
+ lst)))
+
+ (with-error-handling
+ (let* ((signing-key directory (match (command-line-arguments options)
+ ((signing-key directory)
+ (values signing-key directory))
+ ((signing-key)
+ (values signing-key "guix"))
+ (_ (missing-arguments))))
+ (upstream (assoc-ref options 'upstream))
+ (channel-url (assoc-ref options 'channel-url))
+ (use-existing? (assoc-ref options 'use-existing?))
+ (git-parameters (assoc-ref options 'git-parameters))
+ (git-c-options ;'("-c" "param1" "-c" "param2" ...)
+ (let loop ((opts '()) (params git-parameters))
+ (if (or (not params) (null-list? params))
+ opts
+ (loop (append
+ opts (list "-c" (first params)))
+ (drop params 1)))))
+
+ (key-file-name (fingerprint->key-file-name signing-key))
+ (introduction-name (car (string-split key-file-name #\-)))
+
+ (upstream-branch-name "master"))
+
+ (define (invoke-git . args)
+ (apply invoke `("git" ,@git-c-options "-C" ,directory ,@args)))
+
+ (unless use-existing?
+ (info (G_ "Cloning from upstream ~a...~%") upstream)
+ (invoke "git" "clone" upstream directory))
+
+ (info (G_ "Authenticating upstream commits...~%"))
+
+ (when channel-url
+ (info (G_ "Renaming existing 'origin' remote to 'upstream'...~%"))
+ (invoke-git "remote" "rename" "origin" "upstream")
+ (info (G_ "Using provided channel URL for new 'origin' remote...~%"))
+ (invoke-git "remote" "add" "origin" channel-url))
+
+ (set! upstream-branch-name
+ (chain-cut
+ (invoke/stdout "git"
+ "-C" directory
+ "symbolic-ref"
+ (string-append "refs/remotes/"
+ (if channel-url "upstream" "origin")
+ "/HEAD"))
+ string-trim-right
+ (string-split <> #\/)
+ last))
+
+ (info (G_ "Adding key to keyring branch...~%"))
+ (invoke-git "switch" "keyring")
+ (invoke "gpg"
+ "--armor" "--export"
+ "-o" (string-append directory "/" key-file-name)
+ signing-key)
+ (invoke-git "add" "--" key-file-name)
+ (invoke-git "commit" "-m" "Add key for fork introduction.")
+
+ (info (G_ "Setting up fork branch...~%"))
+ (invoke-git "switch" "--create" "fork" "master")
+ (when channel-url
+ (update-channel-url (string-append directory "/.guix-channel")
+ channel-url))
+ (rewrite-authorizations (string-append directory "/.guix-authorizations")
+ introduction-name signing-key)
+ (invoke-git "add" "--"
+ (string-append directory "/.guix-authorizations")
+ (string-append directory "/.guix-channel"))
+ (invoke-git "commit"
+ (string-append "--gpg-sign=" signing-key)
+ "-m"
+ (string-append
+ "Initial fork commit.\n\n"
+ ".guix-authorizations: Allow only " introduction-name "'s key."
+ (if channel-url
+ "\n.guix-channels: Update channel URL."
+ "")))
+
+ (info (G_ "Successfully created Guix fork in ~a.
+You should run the following command next:
+guix fork authenticate ~a ~a ~a~%")
+ directory
+ upstream-branch-name
+ (string-trim-right (invoke/stdout "git" "-C" directory "rev-parse" "HEAD"))
+ signing-key))))
diff --git a/guix/scripts/git/authenticate.scm b/guix/scripts/git/authenticate.scm
index e3ecb67c89..154aae9b14 100644
--- a/guix/scripts/git/authenticate.scm
+++ b/guix/scripts/git/authenticate.scm
@@ -23,8 +23,8 @@ (define-module (guix scripts git authenticate)
#:use-module (guix git-authenticate)
#:autoload (guix openpgp) (openpgp-format-fingerprint
openpgp-public-key-fingerprint)
- #:use-module ((guix channels) #:select (openpgp-fingerprint))
- #:use-module ((guix git) #:select (with-git-error-handling))
+ #:use-module ((guix channels) #:select (openpgp-fingerprint*))
+ #:use-module ((guix git) #:select (with-git-error-handling commit-short-id repository-current-branch))
#:use-module (guix progress)
#:use-module (guix base64)
#:autoload (rnrs bytevectors) (bytevector-length)
@@ -76,15 +76,6 @@ (define %options
(define %default-options
'())
-(define (current-branch repository)
- "Return the name of the checked out branch of REPOSITORY or #f if it could
-not be determined."
- (and (not (repository-head-detached? repository))
- (let* ((head (repository-head repository))
- (name (reference-name head)))
- (and (string-prefix? "refs/heads/" name)
- (string-drop name (string-length "refs/heads/"))))))
-
(define (config-value repository key)
"Return the config value associated with KEY in the 'guix.authentication' or
'guix.authentication-BRANCH' name space in REPOSITORY, or #f if no such config
@@ -94,7 +85,7 @@ (define (config-value repository key)
((_ exp)
(catch 'git-error (lambda () exp) (const #f))))))
(let* ((config (repository-config repository))
- (branch (current-branch repository)))
+ (branch (repository-current-branch repository)))
;; First try the BRANCH-specific value, then the generic one.`
(or (and branch
(false-if-git-error
@@ -194,21 +185,6 @@ (define (install-hooks repository)
(warning (G_ "cannot determine where to install hooks\
(Guile-Git too old?)~%"))))
-(define (show-stats stats)
- "Display STATS, an alist containing commit signing stats as returned by
-'authenticate-repository'."
- (format #t (G_ "Signing statistics:~%"))
- (for-each (match-lambda
- ((signer . count)
- (format #t " ~a ~10d~%"
- (openpgp-format-fingerprint
- (openpgp-public-key-fingerprint signer))
- count)))
- (sort stats
- (match-lambda*
- (((_ . count1) (_ . count2))
- (> count1 count2))))))
-
(define (show-help)
(display (G_ "Usage: guix git authenticate COMMIT SIGNER [OPTIONS...]
Authenticate the given Git checkout using COMMIT/SIGNER as its introduction.\n"))
@@ -251,19 +227,6 @@ (define (guix-git-authenticate . args)
(_ #f))
lst)))
- (define commit-short-id
- (compose (cut string-take <> 7) oid->string commit-id))
-
- (define (openpgp-fingerprint* str)
- (unless (string-every (char-set-union char-set:hex-digit
- char-set:whitespace)
- str)
- (leave (G_ "~a: invalid OpenPGP fingerprint~%") str))
- (let ((fingerprint (openpgp-fingerprint str)))
- (unless (= 20 (bytevector-length fingerprint))
- (leave (G_ "~a: wrong length for OpenPGP fingerprint~%") str))
- fingerprint))
-
(define (make-reporter start-commit end-commit commits)
(format (current-error-port)
(G_ "Authenticating commits ~a to ~a (~h new \
@@ -321,7 +284,7 @@ (define (guix-git-authenticate . args)
(install-hooks repository))
(when (and show-stats? (not (null? stats)))
- (show-stats stats))
+ (show-authentication-stats stats))
(info (G_ "successfully authenticated commit ~a~%")
(oid->string end))))))
diff --git a/guix/utils.scm b/guix/utils.scm
index b6cf5aea4f..e07e89c321 100644
--- a/guix/utils.scm
+++ b/guix/utils.scm
@@ -21,6 +21,8 @@
;;; Copyright © 2023 Zheng Junjie <873216071@qq.com>
;;; Copyright © 2023 Foundation Devices, Inc. <hello@foundationdevices.com>
;;; Copyright © 2024 Herman Rimm <herman@rimm.ee>
+;;; Copyright © 2025 Tomas Volf <~@wolfsden.cz>
+;;; Copyright © 2025 45mg <45mg.writes@gmail.com>
;;;
;;; This file is part of GNU Guix.
;;;
@@ -163,6 +165,8 @@ (define-module (guix utils)
call-with-compressed-output-port
canonical-newline-port
+ chain-cut
+
string-distance
string-closest
@@ -1193,6 +1197,35 @@ (define-syntax current-source-directory
;; raising an error would upset Geiser users
#f))))))
+\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* ...)))))
+
\f
;;;
;;; String comparison.
base-commit: b85d20e853192a92093cd8d6a5756ec80e94c658
prerequisite-patch-id: 2d660c1f16c3ac553886bc5a0bbdc6b66fcdfe4f
--
2.47.1
^ permalink raw reply related [flat|nested] 36+ messages in thread
* [bug#75975] [PATCH (WIP) 2/4] Add 'guix fork authenticate'.
2025-01-31 18:32 ` [bug#75975] [PATCH (WIP) 1/4] Add 'guix fork create' 45mg
@ 2025-01-31 18:32 ` 45mg
2025-01-31 18:32 ` [bug#75975] [PATCH (WIP) 3/4] Add 'guix fork update' 45mg
2025-01-31 18:32 ` [bug#75975] [PATCH (WIP) 4/4] Document 'guix fork' 45mg
2 siblings, 0 replies; 36+ messages in thread
From: 45mg @ 2025-01-31 18:32 UTC (permalink / raw)
To: 75975
Cc: 45mg, Christopher Baines, Josselin Poiret, Ludovic Courtès,
Mathieu Othacehe, Simon Tournier, Tobias Geerinckx-Rice
* guix/scripts/fork/authenticate.scm: New file.
* Makefile.am (MODULES): Add the new file.
* guix/scripts/fork.scm
(show-help): Mention new command.
(%sub-commands): Add new command.
Change-Id: Ic34a1b3d1642cedce8d1ff5bae825df30e47755c
---
Makefile.am | 1 +
guix/scripts/fork.scm | 6 +-
guix/scripts/fork/authenticate.scm | 331 +++++++++++++++++++++++++++++
3 files changed, 336 insertions(+), 2 deletions(-)
create mode 100644 guix/scripts/fork/authenticate.scm
diff --git a/Makefile.am b/Makefile.am
index c628450a5a..1c1f5d84fd 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -379,6 +379,7 @@ MODULES = \
guix/scripts/git/authenticate.scm \
guix/scripts/fork.scm \
guix/scripts/fork/create.scm \
+ guix/scripts/fork/authenticate.scm \
guix/scripts/graph.scm \
guix/scripts/weather.scm \
guix/scripts/container.scm \
diff --git a/guix/scripts/fork.scm b/guix/scripts/fork.scm
index 2d97bcb93f..c5c7a59ba7 100644
--- a/guix/scripts/fork.scm
+++ b/guix/scripts/fork.scm
@@ -29,7 +29,9 @@ (define (show-help)
(display (G_ "The valid values for ACTION are:\n"))
(newline)
(display (G_ "\
- create set up a fork of Guix\n"))
+ create set up a fork of Guix\n"))
+ (display (G_ "\
+ authenticate authenticate a fork of Guix\n"))
(newline)
(display (G_ "
-h, --help display this help and exit"))
@@ -38,7 +40,7 @@ (define (show-help)
(newline)
(show-bug-report-information))
-(define %sub-commands '("create"))
+(define %sub-commands '("create" "authenticate"))
(define (resolve-sub-command name)
(let ((module (resolve-interface
diff --git a/guix/scripts/fork/authenticate.scm b/guix/scripts/fork/authenticate.scm
new file mode 100644
index 0000000000..83d9d87d44
--- /dev/null
+++ b/guix/scripts/fork/authenticate.scm
@@ -0,0 +1,331 @@
+;;; GNU Guix --- Functional package management for GNU
+;;; Copyright © 2025 45mg <45mg.writes@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~%")))
+
+\f
+;;;
+;;; 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.47.1
^ permalink raw reply related [flat|nested] 36+ messages in thread
* [bug#75975] [PATCH (WIP) 3/4] Add 'guix fork update'.
2025-01-31 18:32 ` [bug#75975] [PATCH (WIP) 1/4] Add 'guix fork create' 45mg
2025-01-31 18:32 ` [bug#75975] [PATCH (WIP) 2/4] Add 'guix fork authenticate' 45mg
@ 2025-01-31 18:32 ` 45mg
2025-01-31 18:32 ` [bug#75975] [PATCH (WIP) 4/4] Document 'guix fork' 45mg
2 siblings, 0 replies; 36+ messages in thread
From: 45mg @ 2025-01-31 18:32 UTC (permalink / raw)
To: 75975
Cc: 45mg, Christopher Baines, Josselin Poiret, Ludovic Courtès,
Mathieu Othacehe, Simon Tournier, Tobias Geerinckx-Rice
* guix/scripts/fork/update.scm: New file.
* Makefile.am (MODULES): Add the new file.
* guix/scripts/fork.scm
(show-help): Mention new command.
(%sub-commands): Add new command.
Change-Id: I2017eb9a9286c02ca8bdf962bcbfe89d7607c413
---
Makefile.am | 1 +
guix/scripts/fork.scm | 4 +-
guix/scripts/fork/update.scm | 181 +++++++++++++++++++++++++++++++++++
3 files changed, 185 insertions(+), 1 deletion(-)
create mode 100644 guix/scripts/fork/update.scm
diff --git a/Makefile.am b/Makefile.am
index 1c1f5d84fd..8edd371ccd 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -380,6 +380,7 @@ MODULES = \
guix/scripts/fork.scm \
guix/scripts/fork/create.scm \
guix/scripts/fork/authenticate.scm \
+ guix/scripts/fork/update.scm \
guix/scripts/graph.scm \
guix/scripts/weather.scm \
guix/scripts/container.scm \
diff --git a/guix/scripts/fork.scm b/guix/scripts/fork.scm
index c5c7a59ba7..bf9c86e0aa 100644
--- a/guix/scripts/fork.scm
+++ b/guix/scripts/fork.scm
@@ -32,6 +32,8 @@ (define (show-help)
create set up a fork of Guix\n"))
(display (G_ "\
authenticate authenticate a fork of Guix\n"))
+ (display (G_ "\
+ update update a fork of Guix\n"))
(newline)
(display (G_ "
-h, --help display this help and exit"))
@@ -40,7 +42,7 @@ (define (show-help)
(newline)
(show-bug-report-information))
-(define %sub-commands '("create" "authenticate"))
+(define %sub-commands '("create" "authenticate" "update"))
(define (resolve-sub-command name)
(let ((module (resolve-interface
diff --git a/guix/scripts/fork/update.scm b/guix/scripts/fork/update.scm
new file mode 100644
index 0000000000..5aed337b85
--- /dev/null
+++ b/guix/scripts/fork/update.scm
@@ -0,0 +1,181 @@
+;;; GNU Guix --- Functional package management for GNU
+;;; Copyright © 2025 Tomas Volf <~@wolfsden.cz>
+;;; Copyright © 2025 45mg <45mg.writes@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 ~%")))
+
+\f
+;;;
+;;; 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.47.1
^ permalink raw reply related [flat|nested] 36+ messages in thread
* [bug#75975] [PATCH (WIP) 4/4] Document 'guix fork'.
2025-01-31 18:32 ` [bug#75975] [PATCH (WIP) 1/4] Add 'guix fork create' 45mg
2025-01-31 18:32 ` [bug#75975] [PATCH (WIP) 2/4] Add 'guix fork authenticate' 45mg
2025-01-31 18:32 ` [bug#75975] [PATCH (WIP) 3/4] Add 'guix fork update' 45mg
@ 2025-01-31 18:32 ` 45mg
2 siblings, 0 replies; 36+ messages in thread
From: 45mg @ 2025-01-31 18:32 UTC (permalink / raw)
To: 75975; +Cc: 45mg, Ludovic Courtès, Maxim Cournoyer
* doc/guix.texi (Invoking guix fork): New node.
* doc/contributing.texi (Using Your Own Patches): New node.
Change-Id: I06240f0fe8d1fe39f27130a72f5d0d92949c99da
---
doc/contributing.texi | 50 ++++++++++++++
doc/guix.texi | 150 ++++++++++++++++++++++++++++++++++++++++++
2 files changed, 200 insertions(+)
diff --git a/doc/contributing.texi b/doc/contributing.texi
index c94ae940fa..bd4fd6c2ac 100644
--- a/doc/contributing.texi
+++ b/doc/contributing.texi
@@ -35,6 +35,7 @@ Contributing
* Making Decisions:: Collectively choosing the way forward.
* Commit Access:: Pushing to the official repository.
* Reviewing the Work of Others:: Some guidelines for sharing reviews.
+* Using Your Own Patches:: Using your own work before it's accepted.
* Updating the Guix Package:: Updating the Guix package definition.
* Deprecation Policy:: Commitments and tools for deprecation.
* Writing Documentation:: Improving documentation in GNU Guix.
@@ -3095,6 +3096,55 @@ Reviewing the Work of Others
have reviewed more easily by adding a @code{reviewed-looks-good} usertag
for the @code{guix} user (@pxref{Debbugs Usertags}).
+@node Using Your Own Patches
+@section Using Your Own Patches
+
+If you've taken the time to contribute code to Guix, chances are that
+you want the changes you've made to be reflected in your own Guix
+installation as soon as possible. Maybe you've added a package you want,
+and you want to start using it @emph{right now}. Or you've fixed a bug
+that affects you, and you want it to @emph{go away}.
+
+As described in the preceding sections, all contributions to Guix first
+go through a review process to ensure code quality. Sometimes, this can
+take longer than one would like. Ideally, the pace of the review process
+should not prevent you from benefiting from your own work.
+
+One way to work around this issue is to create an additional channel of
+your own (@pxref{Creating a Channel}), and add your code to it. For
+certain kinds of contributions, such as adding a new package, this is
+fairly straightforward - simply copy your new package definition(s) into
+a new file in the channel, and remove them when your contribution is
+accepted.
+
+However, there may be cases where this is not convenient. Certain kinds
+of changes, such as those that need to modify existing Guix internals,
+may be more challenging to incorporate into a channel. Moreoever, the
+more substantial your contribution is, the more work it will be to do
+so.
+
+@cindex fork, of Guix
+For such cases, there is another option. Recall that the patch series
+that you sent (@pxref{Sending a Patch Series}) was created from a one or
+more commits on a checkout of the Guix repository (@pxref{Building from
+Git}). You could simply specify this repository (referred to as your
+`Guix fork', or simply `fork', from here onwards), and its relevant
+branch, as your `@code{guix}' channel (@pxref{Using a Custom Guix
+Channel}). Now `@code{guix pull}' will fetch your new commits, and
+you'll see the changes you made reflected in your Guix installation!
+
+However, there's a potential complication to this approach - the issue
+of authentication (@pxref{Channel Authentication}). If your fork only
+exists on your local filesystem (a `local fork'), then you probably
+don't need to worry about this, and can pull without authentication
+(@pxref{Invoking guix pull}). But other situations, such as a remotely
+hosted fork, may make it important for your fork to be authenticated, in
+the same way that all channels are expected to be.
+
+Guix provides a @command{guix fork} command in order to simplify and
+automate many details of creating and managing and authenticated
+fork. For more information, @pxref{Invoking guix fork}.
+
@node Updating the Guix Package
@section Updating the Guix Package
diff --git a/doc/guix.texi b/doc/guix.texi
index b1b6d98e74..bbb5666d0a 100644
--- a/doc/guix.texi
+++ b/doc/guix.texi
@@ -311,6 +311,7 @@ Top
* Invoking guix pack:: Creating software bundles.
* The GCC toolchain:: Working with languages supported by GCC.
* Invoking guix git authenticate:: Authenticating Git repositories.
+* Invoking guix fork:: Creating and managing authenticated forks of Guix.
Programming Interface
@@ -5930,6 +5931,7 @@ Development
* Invoking guix pack:: Creating software bundles.
* The GCC toolchain:: Working with languages supported by GCC.
* Invoking guix git authenticate:: Authenticating Git repositories.
+* Invoking guix fork:: Creating and managing authenticated forks of Guix.
@end menu
@node Invoking guix shell
@@ -7534,6 +7536,154 @@ Invoking guix git authenticate
@end table
+@node Invoking guix fork
+@section Invoking @command{guix fork}
+
+@cindex @command{guix fork}
+
+The @command{guix fork} command provides the means to quickly set up,
+authenticate, and keep up-to-date an authenticated fork of Guix. For
+more information on authentication of a Guix checkout, @pxref{Invoking
+guix git authenticate}.
+
+Its syntax is:
+
+guix fork ACTION ARGS...
+
+ACTION specifies the fork-related action to perform. Currently, the
+following values are supported:
+
+@table @code
+@item create SIGNING_KEY [DIRECTORY OPTIONS...]
+Create a fork of Guix in DIRECTORY, using SIGNING_KEY to sign the introductory
+commit.
+DIRECTORY defaults to ./guix.
+
+First, clone Guix into DIRECTORY, unless @code{--use-existing} is
+given. Then, add SIGNING_KEY to the `@code{keyring}' branch of the
+repository. Finally, create a new `@code{fork}' branch based starting
+from the default branch, whose initial commit authorizes SIGNING_KEY
+alone (by adding it to @file{.guix-authorizations}) and is signed by it.
+
+The new `@code{fork}' branch is intended to mirror upstream
+Guix. Updating the fork amounts to applying all new commits to it (see
+the `@code{update}' command below for further explanation). You can work
+on patches in branches based off of this one, in much the same way as
+you would base them on Guix's default branch - every commit from the
+latter will be present in the former.
+
+To @command{guix pull} your changes, you could create a `build' branch
+starting from the initial fork commit, onto which you can cherry-pick or
+rebase commits from patch branches. This branch can then be specified
+for the `@code{guix}' channel (@pxref{Using a Custom Guix Channel}).
+Updating this channel can be done by merging the `@code{fork}' branch
+into it.
+
+OPTIONS can be one or more of the following:
+
+@table @code
+@item --use-existing
+Use existing clone of Guix in DIRECTORY. This is useful if you've
+already created commits for a patch series (@pxref{Using Your Own
+Patches}). However, all commits to the default branch, as well as any
+branches that may be merged into it in the future, must have been signed
+with an authorized key; otherwise, authentication will fail later.
+@item --upstream=URI
+The repository to clone from. This defaults to the default URL for the
+Guix repository.
+@item --channel-url=URI
+Optional URI, which if given, will be used to replace the channel URL.
+Furthermore, the existing `origin' remote (which tracks
+`@code{upstream}') is renamed to `upstream', and a new `origin' remote
+is created to track URI.
+@item --git-parameter PARAMETER
+Specify configuration PARAMETER for git, via `-c' option. You can pass
+this option multiple times.
+@end table
+
+@cindex authentication, of Guix forks
+@item authenticate UPSTREAM COMMIT SIGNER [OPTIONS...]
+Authenticate a Guix fork, using COMMIT and SIGNER as the fork
+introduction.
+
+First, authenticate new commits from UPSTREAM, using Guix's default
+introduction. Then authenticate the remaining commits using the fork
+introduction.
+
+As with @code{guix git authenticate}, all three of UPSTREAM, COMMIT and
+SIGNER will be cached in .git/config, so that you don't need to specify
+them after the first time.
+
+OPTIONS can be one or more of the following:
+
+@table @code
+@item --repository=DIRECTORY
+@itemx -r DIRECTORY
+Authenticate the git repository in DIRECTORY, instead of the current
+directory.
+@item --upstream-commit=COMMIT
+@itemx --upstream-signer=SIGNER
+Use COMMIT/SIGNER as the introduction for upstream
+Guix, instead of Guix's default channel introduction.
+@item --keyring=REFERENCE
+@itemx -k REFERENCE
+Load keyring for fork commits from REFERENCE, a Git branch (default
+`@code{keyring}').
+@item --upstream-keyring=REFERENCE
+Load keyring for upstream commits from REFERENCE, a Git branch (default
+`@code{keyring}').
+@item --end=COMMIT
+Authenticate fork commits up to COMMIT.
+@item --upstream-end=COMMIT
+Authenticate upstream commits up to COMMIT.
+
+@item --cache-key=KEY
+@itemx --historical-authorizations=FILE
+@itemx --stats
+Identical to the correponding options in @command{guix git authenticate}
+(@pxref{Invoking guix git authenticate}).
+@end table
+
+@item update [OPTIONS...]
+Pull into this Guix fork's configured upstream branch (from running
+@command{guix fork authenticate}), then apply new commits onto the
+current branch.
+
+This approach may seem less convenient than simply merging the upstream
+branch into the fork branch. Indeed, it duplicates every upstream commit
+under a different commit hash, and applying a large number of commits
+can be slow. However, this is currently the only feasible approach due
+to the nature of Guix's authentication mechanism. Namely, merge commits
+can only be authenticated if both their parents are signed by an
+authorized key, meaning that you can only use the merge workflow if
+you're authorized to commit to upstream Guix.
+
+For mapping commits on the fork branch to their equivalents on the
+upstream branch, you can use @command{guix fork identify} (see below).
+
+OPTIONS can be one or more of the following:
+
+@table @code
+@item --repository=DIRECTORY
+@itemx -r DIRECTORY
+Act in the Git repository in DIRECTORY.
+@item --fork-branch=BRANCH
+Apply new commits onto BRANCH instead of the current branch.
+@end table
+
+@item identify
+Coming soon!
+
+Given a commit hash from upstream Guix, print its equivalent on the fork
+branch, or vice versa.
+This uses the 'Change-Id:' line added to commit messages by Guix's
+'commit-msg' hook.
+The first invocation of this command will be slow, as the entire set of
+corresponding commits is built up as a hash table, and then
+cached. Subsequent invocations should be nearly instant.
+
+@end table
+
@c *********************************************************************
@node Programming Interface
@chapter Programming Interface
--
2.47.1
^ permalink raw reply related [flat|nested] 36+ messages in thread
* [bug#75975] [PATCH (WIP) 0/4] Add 'guix fork'.
2025-01-31 18:09 [bug#75973] [PATCH (WIP) 0/4] Add 'guix fork' 45mg
` (2 preceding siblings ...)
2025-01-31 18:32 ` [bug#75975] [PATCH (WIP) 1/4] Add 'guix fork create' 45mg
@ 2025-01-31 20:10 ` vicvbcun
2025-01-31 21:35 ` 45mg
2025-01-31 20:51 ` Tomas Volf
4 siblings, 1 reply; 36+ messages in thread
From: vicvbcun @ 2025-01-31 20:10 UTC (permalink / raw)
To: 45mg
Cc: Nicolas Graves, Maxim Cournoyer, Tomas Volf,
Liliana Marie Prikler, Ricardo Wurmus, Attila Lendvai,
Ludovic Courtès, 75975
Hello,
On 2025-01-31T23:58:44+0530, 45mg wrote:
>[...]
>P.S For some reason, if I `guix pull` from a local clone with these patches
>applied, Guix tries to build stuff from (gnu packages commencement) from
>source, even though I applied them to a commit for which CI has already built
>substitutes [7]. It's been stuck on that for hours, so I don't even know the
>extent of the rebuild it's trying to do. I am unable to figure out why this is
>happening. Any ideas are welcome, since I'd like to start daily-driving this
>patch series as soon as possible.
I think this is because you modified the (guix build utils) module which
just about any package will depend on. In other words, every derivation
changes and you will need to build every package yourself. I'd
recommend putting `invoke/stdout' somewhere else — at least as long as
this is not in upstream Guix.
vicvbcun
^ permalink raw reply [flat|nested] 36+ messages in thread
* [bug#75975] [PATCH (WIP) 0/4] Add 'guix fork'.
2025-01-31 18:09 [bug#75973] [PATCH (WIP) 0/4] Add 'guix fork' 45mg
` (3 preceding siblings ...)
2025-01-31 20:10 ` [bug#75975] [PATCH (WIP) 0/4] Add " vicvbcun
@ 2025-01-31 20:51 ` Tomas Volf
2025-01-31 21:10 ` [bug#75981] [PATCH (WIP) v1 " 45mg
2025-01-31 21:28 ` bug#75975: [PATCH (WIP) " 45mg
4 siblings, 2 replies; 36+ messages in thread
From: Tomas Volf @ 2025-01-31 20:51 UTC (permalink / raw)
To: 45mg
Cc: Ricardo Wurmus, Attila Lendvai, Nicolas Graves,
Liliana Marie Prikler, 75975
[-- Attachment #1: Type: text/plain, Size: 927 bytes --]
45mg <45mg.writes@gmail.com> writes:
> The code here adapts certain procedures from Tomas Volf's original 'fork-guix'
> script [6]; namely: '-->', 'invoke/c', 'create-keyring-branch', 'git-C', and
> 'git-C/c'. That script is licensed under AGPL, so my understanding is that it,
> or the procedures I used from it, would need to be relicensed under GPLv3 to
> be included into Guix. Tomas - could you confirm here that you're willing to
> do so, as we discussed earlier? (Note that I didn't ask you about the last two
> of the five procedures above, since I hadn't used them yet at the
> time.)
I hereby declare the above mentioned procedures to be dual licensed
under AGPL-3.0-only and GPL-3.0-or-later.
That should remove any possible licensing issues for merging this
patch. ^_^
Tomas
--
There are only two hard things in Computer Science:
cache invalidation, naming things and off-by-one errors.
[-- Attachment #2: signature.asc --]
[-- Type: application/pgp-signature, Size: 853 bytes --]
^ permalink raw reply [flat|nested] 36+ messages in thread
* [bug#75981] [PATCH (WIP) v1 0/4] Add 'guix fork'.
@ 2025-01-31 21:10 ` 45mg
2025-01-31 21:18 ` [bug#75981] [PATCH (WIP) v1 1/4] Add 'guix fork create' 45mg
` (5 more replies)
0 siblings, 6 replies; 36+ messages in thread
From: 45mg @ 2025-01-31 21:10 UTC (permalink / raw)
To: 75981
Cc: Nicolas Graves, Tomas Volf, 45mg, Liliana Marie Prikler,
Ricardo Wurmus, Attila Lendvai, Ludovic Courtès,
Maxim Cournoyer
Please ignore bugs #75973 and #75975. Those were both attempts to send this
patch series, but I sent the first one out before it was done and somehow
messed up the 'CC' and 'In-Reply-To' fields of the second. The best I can do
is close them and try again. Hopefully I'll get it right this time. (The 'v1'
in the subject line here is to differentiate this attempt from those ones.)
Hello Guix,
This patch series aims to enable and automate the creation and management of
authenticated local forks of Guix. The purpose of this work is to allow
contributors to use their own patches before they're applied to
upstream Guix, so that their own use of Guix is not hindered by the slow and
erratic pace of patch review.
This is a solution to bug #75552 [1], in whose discussion thread the design
was conceived and refined. Credit goes to Tomas for being the first person (to
my knowledge) to share their solution to this problem [2], which provided a
blueprint for 'guix fork create'; to Liliana for the idea behind the way 'guix
fork update' works [3]; and to Ricardo for the idea behind 'guix fork
identify' [4]. I've also CC'ed Attila and Nicolas since they replied in the
original thread (apologies in advance if I shouldn't have).
As I mentioned in the original thread [5], this solution aims to satisfy four
conditions which are not met by any existing method to my knowledge:
1. Allows authenticating both upstream and fork commits.
2. Does not require bumping the channel introduction (as distributing channel
introductions is sensitive)
3. Keeps fork history intact (to avoid force pulls).
4. Keeps upstream history intact (to avoid confusion).
Despite the '(WIP)' subject prefix, this patch series should be perfectly
usable in its current state. The easiest way to try it out would be as follows:
1. Apply it to your local clone of Guix (eg. in a branch) and build it.
2. 'cp -r' your local clone to another location.
3. Run the following command:
./pre-inst-env guix fork create <fingerprint-of-your-key> path/to/copy/of/local/clone --use-existing
Now you have the setup needed for an authenticated local fork. From here, you
can create and 'guix pull' (with authentication) from branches starting from
the initial fork commit. You can authenticate both fork and upstream using
'guix fork authenticate', even if the key used to create your fork is not
authorized upstream. You can update your fork with new commits from upstream
using 'guix fork update'.
The documentation (additions to doc/guix.texi and doc/contributing.texi)
should provide a proper overview of these commands and their usage. Easiest
way to view it could be to run 'make doc/guix.html' and then open it in a
browser.
The '(WIP)' subject prefix is there because the following things are yet to be
implemented:
1. The 'guix fork identify' command.
2. Tests, along the lines of tests/guix-git-authenticate.sh.
The code here adapts certain procedures from Tomas Volf's original 'fork-guix'
script [6]; namely: '-->', 'invoke/c', 'create-keyring-branch', 'git-C', and
'git-C/c'. That script is licensed under AGPL, so my understanding is that it,
or the procedures I used from it, would need to be relicensed under GPLv3 to
be included into Guix. Tomas - could you confirm here that you're willing to
do so, as we discussed earlier? (Note that I didn't ask you about the last two
of the five procedures above, since I hadn't used them yet at the time.)
Regards,
45mg
P.S It was helpfully explained to me [8] (in a reply to one of my previous
botched attempts to send this out) that since this patch series modifies (guix
build utils), it will result in almost every derivation changing and almost
everything needing to be rebuilt. I may send a v1.5 that moves those changes
elsewhere, so that it's feasible to 'guix pull' from a clone with this series
applied. Until then, you can test things via pre-inst-env.
[1] https://issues.guix.gnu.org/75552
[2] https://lists.gnu.org/archive/html/help-guix/2023-09/msg00078.html
[3] https://lists.nongnu.org/archive/html/bug-guix/2025-01/msg00139.html
[4] https://lists.nongnu.org/archive/html/bug-guix/2025-01/msg00130.html
[5] https://lists.nongnu.org/archive/html/bug-guix/2025-01/msg00135.html
[6] https://git.wolfsden.cz/guix/tree/etc/fork-guix
[7] https://ci.guix.gnu.org/eval/2036099
[8] https://lists.gnu.org/archive/html/guix-patches/2025-01/msg02844.html
45mg (4):
Add 'guix fork create'.
Add 'guix fork authenticate'.
Add 'guix fork update'.
Document 'guix fork'.
Makefile.am | 4 +
doc/contributing.texi | 50 +++++
doc/guix.texi | 150 +++++++++++++
guix/build/utils.scm | 20 ++
guix/channels.scm | 13 ++
guix/git-authenticate.scm | 17 ++
guix/git.scm | 10 +
guix/scripts/fork.scm | 71 +++++++
guix/scripts/fork/authenticate.scm | 331 +++++++++++++++++++++++++++++
guix/scripts/fork/create.scm | 257 ++++++++++++++++++++++
guix/scripts/fork/update.scm | 181 ++++++++++++++++
guix/scripts/git/authenticate.scm | 45 +---
guix/utils.scm | 33 +++
13 files changed, 1141 insertions(+), 41 deletions(-)
create mode 100644 guix/scripts/fork.scm
create mode 100644 guix/scripts/fork/authenticate.scm
create mode 100644 guix/scripts/fork/create.scm
create mode 100644 guix/scripts/fork/update.scm
base-commit: b85d20e853192a92093cd8d6a5756ec80e94c658
--
2.48.1
^ permalink raw reply [flat|nested] 36+ messages in thread
* [bug#75981] [PATCH (WIP) v1 1/4] Add 'guix fork create'.
2025-01-31 21:10 ` [bug#75981] [PATCH (WIP) v1 " 45mg
@ 2025-01-31 21:18 ` 45mg
2025-01-31 21:18 ` [bug#75981] [PATCH (WIP) v1 2/4] Add 'guix fork authenticate' 45mg
` (4 subsequent siblings)
5 siblings, 0 replies; 36+ messages in thread
From: 45mg @ 2025-01-31 21:18 UTC (permalink / raw)
To: 75981
Cc: Nicolas Graves, Tomas Volf, 45mg, Liliana Marie Prikler,
Ricardo Wurmus, Attila Lendvai
* guix/scripts/fork.scm, guix/scripts/fork/create.scm: New files.
* Makefile.am (MODULES): Add the new files.
* guix/build/utils.scm (invoke/stdout): New procedure.
* guix/utils.scm (chain-cut): New procedure.
* guix/scripts/git/authenticate.scm
(commit-short-id): Remove procedure, and use its existing duplicate in
guix/channels.scm.
(openpgp-fingerprint*, current-branch, show-stats): Move procedures to
the files below.
* guix/channels.scm (openpgp-fingerprint*): Moved here.
* guix/git.scm (repository-current-branch): Moved here and renamed from
'current-branch'.
* guix/git-authenticate.scm (show-authentication-stats): Moved here and
renamed from 'show-stats'.
Change-Id: I45ba37f434e136f6d496c741d9a933280f9ccf88
---
Makefile.am | 2 +
guix/build/utils.scm | 20 +++
guix/channels.scm | 13 ++
guix/git-authenticate.scm | 17 ++
guix/git.scm | 10 ++
guix/scripts/fork.scm | 67 ++++++++
guix/scripts/fork/create.scm | 257 ++++++++++++++++++++++++++++++
guix/scripts/git/authenticate.scm | 45 +-----
guix/utils.scm | 33 ++++
9 files changed, 423 insertions(+), 41 deletions(-)
create mode 100644 guix/scripts/fork.scm
create mode 100644 guix/scripts/fork/create.scm
diff --git a/Makefile.am b/Makefile.am
index f759803b8b..c628450a5a 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -377,6 +377,8 @@ MODULES = \
guix/scripts/size.scm \
guix/scripts/git.scm \
guix/scripts/git/authenticate.scm \
+ guix/scripts/fork.scm \
+ guix/scripts/fork/create.scm \
guix/scripts/graph.scm \
guix/scripts/weather.scm \
guix/scripts/container.scm \
diff --git a/guix/build/utils.scm b/guix/build/utils.scm
index 94714bf397..e8bd39f5de 100644
--- a/guix/build/utils.scm
+++ b/guix/build/utils.scm
@@ -10,6 +10,8 @@
;;; Copyright © 2021, 2022 Maxime Devos <maximedevos@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))
+
\f
;;;
;;; 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~%")))
+
+\f
+;;;
+;;; 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)))))
+
+\f
+;;;
+;;; Entry point.
+;;;
+
+(define (guix-fork-create . args)
+ (define options
+ (parse-command-line args %options (list %default-options)
+ #:build-options? #f))
+
+ (define (command-line-arguments lst)
+ (reverse (filter-map (match-lambda
+ (('argument . arg) arg)
+ (_ #f))
+ lst)))
+
+ (with-error-handling
+ (let* ((signing-key directory (match (command-line-arguments options)
+ ((signing-key directory)
+ (values signing-key directory))
+ ((signing-key)
+ (values signing-key "guix"))
+ (_ (missing-arguments))))
+ (upstream (assoc-ref options 'upstream))
+ (channel-url (assoc-ref options 'channel-url))
+ (use-existing? (assoc-ref options 'use-existing?))
+ (git-parameters (assoc-ref options 'git-parameters))
+ (git-c-options ;'("-c" "param1" "-c" "param2" ...)
+ (let loop ((opts '()) (params git-parameters))
+ (if (or (not params) (null-list? params))
+ opts
+ (loop (append
+ opts (list "-c" (first params)))
+ (drop params 1)))))
+
+ (key-file-name (fingerprint->key-file-name signing-key))
+ (introduction-name (car (string-split key-file-name #\-)))
+
+ (upstream-branch-name "master"))
+
+ (define (invoke-git . args)
+ (apply invoke `("git" ,@git-c-options "-C" ,directory ,@args)))
+
+ (unless use-existing?
+ (info (G_ "Cloning from upstream ~a...~%") upstream)
+ (invoke "git" "clone" upstream directory))
+
+ (info (G_ "Authenticating upstream commits...~%"))
+
+ (when channel-url
+ (info (G_ "Renaming existing 'origin' remote to 'upstream'...~%"))
+ (invoke-git "remote" "rename" "origin" "upstream")
+ (info (G_ "Using provided channel URL for new 'origin' remote...~%"))
+ (invoke-git "remote" "add" "origin" channel-url))
+
+ (set! upstream-branch-name
+ (chain-cut
+ (invoke/stdout "git"
+ "-C" directory
+ "symbolic-ref"
+ (string-append "refs/remotes/"
+ (if channel-url "upstream" "origin")
+ "/HEAD"))
+ string-trim-right
+ (string-split <> #\/)
+ last))
+
+ (info (G_ "Adding key to keyring branch...~%"))
+ (invoke-git "switch" "keyring")
+ (invoke "gpg"
+ "--armor" "--export"
+ "-o" (string-append directory "/" key-file-name)
+ signing-key)
+ (invoke-git "add" "--" key-file-name)
+ (invoke-git "commit" "-m" "Add key for fork introduction.")
+
+ (info (G_ "Setting up fork branch...~%"))
+ (invoke-git "switch" "--create" "fork" "master")
+ (when channel-url
+ (update-channel-url (string-append directory "/.guix-channel")
+ channel-url))
+ (rewrite-authorizations (string-append directory "/.guix-authorizations")
+ introduction-name signing-key)
+ (invoke-git "add" "--"
+ (string-append directory "/.guix-authorizations")
+ (string-append directory "/.guix-channel"))
+ (invoke-git "commit"
+ (string-append "--gpg-sign=" signing-key)
+ "-m"
+ (string-append
+ "Initial fork commit.\n\n"
+ ".guix-authorizations: Allow only " introduction-name "'s key."
+ (if channel-url
+ "\n.guix-channels: Update channel URL."
+ "")))
+
+ (info (G_ "Successfully created Guix fork in ~a.
+You should run the following command next:
+guix fork authenticate ~a ~a ~a~%")
+ directory
+ upstream-branch-name
+ (string-trim-right (invoke/stdout "git" "-C" directory "rev-parse" "HEAD"))
+ signing-key))))
diff --git a/guix/scripts/git/authenticate.scm b/guix/scripts/git/authenticate.scm
index e3ecb67c89..154aae9b14 100644
--- a/guix/scripts/git/authenticate.scm
+++ b/guix/scripts/git/authenticate.scm
@@ -23,8 +23,8 @@ (define-module (guix scripts git authenticate)
#:use-module (guix git-authenticate)
#:autoload (guix openpgp) (openpgp-format-fingerprint
openpgp-public-key-fingerprint)
- #:use-module ((guix channels) #:select (openpgp-fingerprint))
- #:use-module ((guix git) #:select (with-git-error-handling))
+ #:use-module ((guix channels) #:select (openpgp-fingerprint*))
+ #:use-module ((guix git) #:select (with-git-error-handling commit-short-id repository-current-branch))
#:use-module (guix progress)
#:use-module (guix base64)
#:autoload (rnrs bytevectors) (bytevector-length)
@@ -76,15 +76,6 @@ (define %options
(define %default-options
'())
-(define (current-branch repository)
- "Return the name of the checked out branch of REPOSITORY or #f if it could
-not be determined."
- (and (not (repository-head-detached? repository))
- (let* ((head (repository-head repository))
- (name (reference-name head)))
- (and (string-prefix? "refs/heads/" name)
- (string-drop name (string-length "refs/heads/"))))))
-
(define (config-value repository key)
"Return the config value associated with KEY in the 'guix.authentication' or
'guix.authentication-BRANCH' name space in REPOSITORY, or #f if no such config
@@ -94,7 +85,7 @@ (define (config-value repository key)
((_ exp)
(catch 'git-error (lambda () exp) (const #f))))))
(let* ((config (repository-config repository))
- (branch (current-branch repository)))
+ (branch (repository-current-branch repository)))
;; First try the BRANCH-specific value, then the generic one.`
(or (and branch
(false-if-git-error
@@ -194,21 +185,6 @@ (define (install-hooks repository)
(warning (G_ "cannot determine where to install hooks\
(Guile-Git too old?)~%"))))
-(define (show-stats stats)
- "Display STATS, an alist containing commit signing stats as returned by
-'authenticate-repository'."
- (format #t (G_ "Signing statistics:~%"))
- (for-each (match-lambda
- ((signer . count)
- (format #t " ~a ~10d~%"
- (openpgp-format-fingerprint
- (openpgp-public-key-fingerprint signer))
- count)))
- (sort stats
- (match-lambda*
- (((_ . count1) (_ . count2))
- (> count1 count2))))))
-
(define (show-help)
(display (G_ "Usage: guix git authenticate COMMIT SIGNER [OPTIONS...]
Authenticate the given Git checkout using COMMIT/SIGNER as its introduction.\n"))
@@ -251,19 +227,6 @@ (define (guix-git-authenticate . args)
(_ #f))
lst)))
- (define commit-short-id
- (compose (cut string-take <> 7) oid->string commit-id))
-
- (define (openpgp-fingerprint* str)
- (unless (string-every (char-set-union char-set:hex-digit
- char-set:whitespace)
- str)
- (leave (G_ "~a: invalid OpenPGP fingerprint~%") str))
- (let ((fingerprint (openpgp-fingerprint str)))
- (unless (= 20 (bytevector-length fingerprint))
- (leave (G_ "~a: wrong length for OpenPGP fingerprint~%") str))
- fingerprint))
-
(define (make-reporter start-commit end-commit commits)
(format (current-error-port)
(G_ "Authenticating commits ~a to ~a (~h new \
@@ -321,7 +284,7 @@ (define (guix-git-authenticate . args)
(install-hooks repository))
(when (and show-stats? (not (null? stats)))
- (show-stats stats))
+ (show-authentication-stats stats))
(info (G_ "successfully authenticated commit ~a~%")
(oid->string end))))))
diff --git a/guix/utils.scm b/guix/utils.scm
index b6cf5aea4f..e07e89c321 100644
--- a/guix/utils.scm
+++ b/guix/utils.scm
@@ -21,6 +21,8 @@
;;; Copyright © 2023 Zheng Junjie <873216071@qq.com>
;;; Copyright © 2023 Foundation Devices, Inc. <hello@foundationdevices.com>
;;; Copyright © 2024 Herman Rimm <herman@rimm.ee>
+;;; Copyright © 2025 Tomas Volf <~@wolfsden.cz>
+;;; Copyright © 2025 45mg <45mg.writes@gmail.com>
;;;
;;; This file is part of GNU Guix.
;;;
@@ -163,6 +165,8 @@ (define-module (guix utils)
call-with-compressed-output-port
canonical-newline-port
+ chain-cut
+
string-distance
string-closest
@@ -1193,6 +1197,35 @@ (define-syntax current-source-directory
;; raising an error would upset Geiser users
#f))))))
+\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* ...)))))
+
\f
;;;
;;; String comparison.
--
2.48.1
^ permalink raw reply related [flat|nested] 36+ messages in thread
* [bug#75981] [PATCH (WIP) v1 2/4] Add 'guix fork authenticate'.
2025-01-31 21:10 ` [bug#75981] [PATCH (WIP) v1 " 45mg
2025-01-31 21:18 ` [bug#75981] [PATCH (WIP) v1 1/4] Add 'guix fork create' 45mg
@ 2025-01-31 21:18 ` 45mg
2025-01-31 21:18 ` [bug#75981] [PATCH (WIP) v1 3/4] Add 'guix fork update' 45mg
` (3 subsequent siblings)
5 siblings, 0 replies; 36+ messages in thread
From: 45mg @ 2025-01-31 21:18 UTC (permalink / raw)
To: 75981
Cc: Nicolas Graves, Tomas Volf, 45mg, Liliana Marie Prikler,
Ricardo Wurmus, Attila Lendvai
* guix/scripts/fork/authenticate.scm: New file.
* Makefile.am (MODULES): Add the new file.
* guix/scripts/fork.scm
(show-help): Mention new command.
(%sub-commands): Add new command.
Change-Id: Ic34a1b3d1642cedce8d1ff5bae825df30e47755c
---
Makefile.am | 1 +
guix/scripts/fork.scm | 6 +-
guix/scripts/fork/authenticate.scm | 331 +++++++++++++++++++++++++++++
3 files changed, 336 insertions(+), 2 deletions(-)
create mode 100644 guix/scripts/fork/authenticate.scm
diff --git a/Makefile.am b/Makefile.am
index c628450a5a..1c1f5d84fd 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -379,6 +379,7 @@ MODULES = \
guix/scripts/git/authenticate.scm \
guix/scripts/fork.scm \
guix/scripts/fork/create.scm \
+ guix/scripts/fork/authenticate.scm \
guix/scripts/graph.scm \
guix/scripts/weather.scm \
guix/scripts/container.scm \
diff --git a/guix/scripts/fork.scm b/guix/scripts/fork.scm
index 2d97bcb93f..c5c7a59ba7 100644
--- a/guix/scripts/fork.scm
+++ b/guix/scripts/fork.scm
@@ -29,7 +29,9 @@ (define (show-help)
(display (G_ "The valid values for ACTION are:\n"))
(newline)
(display (G_ "\
- create set up a fork of Guix\n"))
+ create set up a fork of Guix\n"))
+ (display (G_ "\
+ authenticate authenticate a fork of Guix\n"))
(newline)
(display (G_ "
-h, --help display this help and exit"))
@@ -38,7 +40,7 @@ (define (show-help)
(newline)
(show-bug-report-information))
-(define %sub-commands '("create"))
+(define %sub-commands '("create" "authenticate"))
(define (resolve-sub-command name)
(let ((module (resolve-interface
diff --git a/guix/scripts/fork/authenticate.scm b/guix/scripts/fork/authenticate.scm
new file mode 100644
index 0000000000..83d9d87d44
--- /dev/null
+++ b/guix/scripts/fork/authenticate.scm
@@ -0,0 +1,331 @@
+;;; GNU Guix --- Functional package management for GNU
+;;; Copyright © 2025 45mg <45mg.writes@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~%")))
+
+\f
+;;;
+;;; 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
^ permalink raw reply related [flat|nested] 36+ messages in thread
* [bug#75981] [PATCH (WIP) v1 3/4] Add 'guix fork update'.
2025-01-31 21:10 ` [bug#75981] [PATCH (WIP) v1 " 45mg
2025-01-31 21:18 ` [bug#75981] [PATCH (WIP) v1 1/4] Add 'guix fork create' 45mg
2025-01-31 21:18 ` [bug#75981] [PATCH (WIP) v1 2/4] Add 'guix fork authenticate' 45mg
@ 2025-01-31 21:18 ` 45mg
2025-01-31 21:18 ` [bug#75981] [PATCH (WIP) v1 4/4] Document 'guix fork' 45mg
` (2 subsequent siblings)
5 siblings, 0 replies; 36+ messages in thread
From: 45mg @ 2025-01-31 21:18 UTC (permalink / raw)
To: 75981
Cc: Nicolas Graves, Tomas Volf, 45mg, Liliana Marie Prikler,
Ricardo Wurmus, Attila Lendvai
* guix/scripts/fork/update.scm: New file.
* Makefile.am (MODULES): Add the new file.
* guix/scripts/fork.scm
(show-help): Mention new command.
(%sub-commands): Add new command.
Change-Id: I2017eb9a9286c02ca8bdf962bcbfe89d7607c413
---
Makefile.am | 1 +
guix/scripts/fork.scm | 4 +-
guix/scripts/fork/update.scm | 181 +++++++++++++++++++++++++++++++++++
3 files changed, 185 insertions(+), 1 deletion(-)
create mode 100644 guix/scripts/fork/update.scm
diff --git a/Makefile.am b/Makefile.am
index 1c1f5d84fd..8edd371ccd 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -380,6 +380,7 @@ MODULES = \
guix/scripts/fork.scm \
guix/scripts/fork/create.scm \
guix/scripts/fork/authenticate.scm \
+ guix/scripts/fork/update.scm \
guix/scripts/graph.scm \
guix/scripts/weather.scm \
guix/scripts/container.scm \
diff --git a/guix/scripts/fork.scm b/guix/scripts/fork.scm
index c5c7a59ba7..bf9c86e0aa 100644
--- a/guix/scripts/fork.scm
+++ b/guix/scripts/fork.scm
@@ -32,6 +32,8 @@ (define (show-help)
create set up a fork of Guix\n"))
(display (G_ "\
authenticate authenticate a fork of Guix\n"))
+ (display (G_ "\
+ update update a fork of Guix\n"))
(newline)
(display (G_ "
-h, --help display this help and exit"))
@@ -40,7 +42,7 @@ (define (show-help)
(newline)
(show-bug-report-information))
-(define %sub-commands '("create" "authenticate"))
+(define %sub-commands '("create" "authenticate" "update"))
(define (resolve-sub-command name)
(let ((module (resolve-interface
diff --git a/guix/scripts/fork/update.scm b/guix/scripts/fork/update.scm
new file mode 100644
index 0000000000..5aed337b85
--- /dev/null
+++ b/guix/scripts/fork/update.scm
@@ -0,0 +1,181 @@
+;;; GNU Guix --- Functional package management for GNU
+;;; Copyright © 2025 Tomas Volf <~@wolfsden.cz>
+;;; Copyright © 2025 45mg <45mg.writes@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 ~%")))
+
+\f
+;;;
+;;; 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
^ permalink raw reply related [flat|nested] 36+ messages in thread
* [bug#75981] [PATCH (WIP) v1 4/4] Document 'guix fork'.
2025-01-31 21:10 ` [bug#75981] [PATCH (WIP) v1 " 45mg
` (2 preceding siblings ...)
2025-01-31 21:18 ` [bug#75981] [PATCH (WIP) v1 3/4] Add 'guix fork update' 45mg
@ 2025-01-31 21:18 ` 45mg
2025-01-31 21:22 ` [bug#75981] [Tomas Volf] Re: [PATCH (WIP) 0/4] Add " 45mg
2025-02-01 11:43 ` [bug#75981] [PATCH (WIP) v1.5 " 45mg
5 siblings, 0 replies; 36+ messages in thread
From: 45mg @ 2025-01-31 21:18 UTC (permalink / raw)
To: 75981
Cc: Nicolas Graves, Tomas Volf, 45mg, Liliana Marie Prikler,
Ricardo Wurmus, Attila Lendvai
* doc/guix.texi (Invoking guix fork): New node.
* doc/contributing.texi (Using Your Own Patches): New node.
Change-Id: I06240f0fe8d1fe39f27130a72f5d0d92949c99da
---
doc/contributing.texi | 50 ++++++++++++++
doc/guix.texi | 150 ++++++++++++++++++++++++++++++++++++++++++
2 files changed, 200 insertions(+)
diff --git a/doc/contributing.texi b/doc/contributing.texi
index c94ae940fa..bd4fd6c2ac 100644
--- a/doc/contributing.texi
+++ b/doc/contributing.texi
@@ -35,6 +35,7 @@ Contributing
* Making Decisions:: Collectively choosing the way forward.
* Commit Access:: Pushing to the official repository.
* Reviewing the Work of Others:: Some guidelines for sharing reviews.
+* Using Your Own Patches:: Using your own work before it's accepted.
* Updating the Guix Package:: Updating the Guix package definition.
* Deprecation Policy:: Commitments and tools for deprecation.
* Writing Documentation:: Improving documentation in GNU Guix.
@@ -3095,6 +3096,55 @@ Reviewing the Work of Others
have reviewed more easily by adding a @code{reviewed-looks-good} usertag
for the @code{guix} user (@pxref{Debbugs Usertags}).
+@node Using Your Own Patches
+@section Using Your Own Patches
+
+If you've taken the time to contribute code to Guix, chances are that
+you want the changes you've made to be reflected in your own Guix
+installation as soon as possible. Maybe you've added a package you want,
+and you want to start using it @emph{right now}. Or you've fixed a bug
+that affects you, and you want it to @emph{go away}.
+
+As described in the preceding sections, all contributions to Guix first
+go through a review process to ensure code quality. Sometimes, this can
+take longer than one would like. Ideally, the pace of the review process
+should not prevent you from benefiting from your own work.
+
+One way to work around this issue is to create an additional channel of
+your own (@pxref{Creating a Channel}), and add your code to it. For
+certain kinds of contributions, such as adding a new package, this is
+fairly straightforward - simply copy your new package definition(s) into
+a new file in the channel, and remove them when your contribution is
+accepted.
+
+However, there may be cases where this is not convenient. Certain kinds
+of changes, such as those that need to modify existing Guix internals,
+may be more challenging to incorporate into a channel. Moreoever, the
+more substantial your contribution is, the more work it will be to do
+so.
+
+@cindex fork, of Guix
+For such cases, there is another option. Recall that the patch series
+that you sent (@pxref{Sending a Patch Series}) was created from a one or
+more commits on a checkout of the Guix repository (@pxref{Building from
+Git}). You could simply specify this repository (referred to as your
+`Guix fork', or simply `fork', from here onwards), and its relevant
+branch, as your `@code{guix}' channel (@pxref{Using a Custom Guix
+Channel}). Now `@code{guix pull}' will fetch your new commits, and
+you'll see the changes you made reflected in your Guix installation!
+
+However, there's a potential complication to this approach - the issue
+of authentication (@pxref{Channel Authentication}). If your fork only
+exists on your local filesystem (a `local fork'), then you probably
+don't need to worry about this, and can pull without authentication
+(@pxref{Invoking guix pull}). But other situations, such as a remotely
+hosted fork, may make it important for your fork to be authenticated, in
+the same way that all channels are expected to be.
+
+Guix provides a @command{guix fork} command in order to simplify and
+automate many details of creating and managing and authenticated
+fork. For more information, @pxref{Invoking guix fork}.
+
@node Updating the Guix Package
@section Updating the Guix Package
diff --git a/doc/guix.texi b/doc/guix.texi
index b1b6d98e74..bbb5666d0a 100644
--- a/doc/guix.texi
+++ b/doc/guix.texi
@@ -311,6 +311,7 @@ Top
* Invoking guix pack:: Creating software bundles.
* The GCC toolchain:: Working with languages supported by GCC.
* Invoking guix git authenticate:: Authenticating Git repositories.
+* Invoking guix fork:: Creating and managing authenticated forks of Guix.
Programming Interface
@@ -5930,6 +5931,7 @@ Development
* Invoking guix pack:: Creating software bundles.
* The GCC toolchain:: Working with languages supported by GCC.
* Invoking guix git authenticate:: Authenticating Git repositories.
+* Invoking guix fork:: Creating and managing authenticated forks of Guix.
@end menu
@node Invoking guix shell
@@ -7534,6 +7536,154 @@ Invoking guix git authenticate
@end table
+@node Invoking guix fork
+@section Invoking @command{guix fork}
+
+@cindex @command{guix fork}
+
+The @command{guix fork} command provides the means to quickly set up,
+authenticate, and keep up-to-date an authenticated fork of Guix. For
+more information on authentication of a Guix checkout, @pxref{Invoking
+guix git authenticate}.
+
+Its syntax is:
+
+guix fork ACTION ARGS...
+
+ACTION specifies the fork-related action to perform. Currently, the
+following values are supported:
+
+@table @code
+@item create SIGNING_KEY [DIRECTORY OPTIONS...]
+Create a fork of Guix in DIRECTORY, using SIGNING_KEY to sign the introductory
+commit.
+DIRECTORY defaults to ./guix.
+
+First, clone Guix into DIRECTORY, unless @code{--use-existing} is
+given. Then, add SIGNING_KEY to the `@code{keyring}' branch of the
+repository. Finally, create a new `@code{fork}' branch based starting
+from the default branch, whose initial commit authorizes SIGNING_KEY
+alone (by adding it to @file{.guix-authorizations}) and is signed by it.
+
+The new `@code{fork}' branch is intended to mirror upstream
+Guix. Updating the fork amounts to applying all new commits to it (see
+the `@code{update}' command below for further explanation). You can work
+on patches in branches based off of this one, in much the same way as
+you would base them on Guix's default branch - every commit from the
+latter will be present in the former.
+
+To @command{guix pull} your changes, you could create a `build' branch
+starting from the initial fork commit, onto which you can cherry-pick or
+rebase commits from patch branches. This branch can then be specified
+for the `@code{guix}' channel (@pxref{Using a Custom Guix Channel}).
+Updating this channel can be done by merging the `@code{fork}' branch
+into it.
+
+OPTIONS can be one or more of the following:
+
+@table @code
+@item --use-existing
+Use existing clone of Guix in DIRECTORY. This is useful if you've
+already created commits for a patch series (@pxref{Using Your Own
+Patches}). However, all commits to the default branch, as well as any
+branches that may be merged into it in the future, must have been signed
+with an authorized key; otherwise, authentication will fail later.
+@item --upstream=URI
+The repository to clone from. This defaults to the default URL for the
+Guix repository.
+@item --channel-url=URI
+Optional URI, which if given, will be used to replace the channel URL.
+Furthermore, the existing `origin' remote (which tracks
+`@code{upstream}') is renamed to `upstream', and a new `origin' remote
+is created to track URI.
+@item --git-parameter PARAMETER
+Specify configuration PARAMETER for git, via `-c' option. You can pass
+this option multiple times.
+@end table
+
+@cindex authentication, of Guix forks
+@item authenticate UPSTREAM COMMIT SIGNER [OPTIONS...]
+Authenticate a Guix fork, using COMMIT and SIGNER as the fork
+introduction.
+
+First, authenticate new commits from UPSTREAM, using Guix's default
+introduction. Then authenticate the remaining commits using the fork
+introduction.
+
+As with @code{guix git authenticate}, all three of UPSTREAM, COMMIT and
+SIGNER will be cached in .git/config, so that you don't need to specify
+them after the first time.
+
+OPTIONS can be one or more of the following:
+
+@table @code
+@item --repository=DIRECTORY
+@itemx -r DIRECTORY
+Authenticate the git repository in DIRECTORY, instead of the current
+directory.
+@item --upstream-commit=COMMIT
+@itemx --upstream-signer=SIGNER
+Use COMMIT/SIGNER as the introduction for upstream
+Guix, instead of Guix's default channel introduction.
+@item --keyring=REFERENCE
+@itemx -k REFERENCE
+Load keyring for fork commits from REFERENCE, a Git branch (default
+`@code{keyring}').
+@item --upstream-keyring=REFERENCE
+Load keyring for upstream commits from REFERENCE, a Git branch (default
+`@code{keyring}').
+@item --end=COMMIT
+Authenticate fork commits up to COMMIT.
+@item --upstream-end=COMMIT
+Authenticate upstream commits up to COMMIT.
+
+@item --cache-key=KEY
+@itemx --historical-authorizations=FILE
+@itemx --stats
+Identical to the correponding options in @command{guix git authenticate}
+(@pxref{Invoking guix git authenticate}).
+@end table
+
+@item update [OPTIONS...]
+Pull into this Guix fork's configured upstream branch (from running
+@command{guix fork authenticate}), then apply new commits onto the
+current branch.
+
+This approach may seem less convenient than simply merging the upstream
+branch into the fork branch. Indeed, it duplicates every upstream commit
+under a different commit hash, and applying a large number of commits
+can be slow. However, this is currently the only feasible approach due
+to the nature of Guix's authentication mechanism. Namely, merge commits
+can only be authenticated if both their parents are signed by an
+authorized key, meaning that you can only use the merge workflow if
+you're authorized to commit to upstream Guix.
+
+For mapping commits on the fork branch to their equivalents on the
+upstream branch, you can use @command{guix fork identify} (see below).
+
+OPTIONS can be one or more of the following:
+
+@table @code
+@item --repository=DIRECTORY
+@itemx -r DIRECTORY
+Act in the Git repository in DIRECTORY.
+@item --fork-branch=BRANCH
+Apply new commits onto BRANCH instead of the current branch.
+@end table
+
+@item identify
+Coming soon!
+
+Given a commit hash from upstream Guix, print its equivalent on the fork
+branch, or vice versa.
+This uses the 'Change-Id:' line added to commit messages by Guix's
+'commit-msg' hook.
+The first invocation of this command will be slow, as the entire set of
+corresponding commits is built up as a hash table, and then
+cached. Subsequent invocations should be nearly instant.
+
+@end table
+
@c *********************************************************************
@node Programming Interface
@chapter Programming Interface
--
2.48.1
^ permalink raw reply related [flat|nested] 36+ messages in thread
* [bug#75981] [Tomas Volf] Re: [PATCH (WIP) 0/4] Add 'guix fork'.
2025-01-31 21:10 ` [bug#75981] [PATCH (WIP) v1 " 45mg
` (3 preceding siblings ...)
2025-01-31 21:18 ` [bug#75981] [PATCH (WIP) v1 4/4] Document 'guix fork' 45mg
@ 2025-01-31 21:22 ` 45mg
2025-02-01 11:43 ` [bug#75981] [PATCH (WIP) v1.5 " 45mg
5 siblings, 0 replies; 36+ messages in thread
From: 45mg @ 2025-01-31 21:22 UTC (permalink / raw)
To: 75981
[-- Attachment #1: Type: text/plain, Size: 460 bytes --]
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
[-- Attachment #2.1: Type: text/plain, Size: 905 bytes --]
45mg <45mg.writes@gmail.com> writes:
> The code here adapts certain procedures from Tomas Volf's original 'fork-guix'
> script [6]; namely: '-->', 'invoke/c', 'create-keyring-branch', 'git-C', and
> 'git-C/c'. That script is licensed under AGPL, so my understanding is that it,
> or the procedures I used from it, would need to be relicensed under GPLv3 to
> be included into Guix. Tomas - could you confirm here that you're willing to
> do so, as we discussed earlier? (Note that I didn't ask you about the last two
> of the five procedures above, since I hadn't used them yet at the
> time.)
I hereby declare the above mentioned procedures to be dual licensed
under AGPL-3.0-only and GPL-3.0-or-later.
That should remove any possible licensing issues for merging this
patch. ^_^
Tomas
--
There are only two hard things in Computer Science:
cache invalidation, naming things and off-by-one errors.
[-- Attachment #2.2: signature.asc --]
[-- Type: application/pgp-signature, Size: 853 bytes --]
[-- Attachment #3: Type: text/plain, Size: 67 bytes --]
-------------------- End of forwarded message --------------------
^ permalink raw reply [flat|nested] 36+ messages in thread
* bug#75975: [PATCH (WIP) 0/4] Add 'guix fork'.
2025-01-31 20:51 ` Tomas Volf
2025-01-31 21:10 ` [bug#75981] [PATCH (WIP) v1 " 45mg
@ 2025-01-31 21:28 ` 45mg
1 sibling, 0 replies; 36+ messages in thread
From: 45mg @ 2025-01-31 21:28 UTC (permalink / raw)
To: Tomas Volf, 45mg, 75975-done, 75975
Tomas Volf <~@wolfsden.cz> writes:
> 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. ^_^
Thank you, Tomas. I forwarded your message to #75981. Closing this one,
since I messed up sending the patches, based on how it looks on the
bug-guix archives.
Closing this.
^ permalink raw reply [flat|nested] 36+ messages in thread
* [bug#75975] [PATCH (WIP) 0/4] Add 'guix fork'.
2025-01-31 20:10 ` [bug#75975] [PATCH (WIP) 0/4] Add " vicvbcun
@ 2025-01-31 21:35 ` 45mg
0 siblings, 0 replies; 36+ messages in thread
From: 45mg @ 2025-01-31 21:35 UTC (permalink / raw)
To: vicvbcun, 45mg; +Cc: 75975
vicvbcun <guix@ikherbers.com> writes:
> I think this is because you modified the (guix build utils) module which
> just about any package will depend on. In other words, every derivation
> changes and you will need to build every package yourself. I'd
> recommend putting `invoke/stdout' somewhere else — at least as long as
> this is not in upstream Guix.
That seems likely. Thanks a ton for pointing it out; I was spinning my
wheels for hours trying to figure out what was causing it. I resubmitted
this series to bug #75981; I may send a v1.5 addressing this there.
^ permalink raw reply [flat|nested] 36+ messages in thread
* [bug#75981] [PATCH (WIP) v1.5 0/4] Add 'guix fork'.
2025-01-31 21:10 ` [bug#75981] [PATCH (WIP) v1 " 45mg
` (4 preceding siblings ...)
2025-01-31 21:22 ` [bug#75981] [Tomas Volf] Re: [PATCH (WIP) 0/4] Add " 45mg
@ 2025-02-01 11:43 ` 45mg
2025-02-01 11:43 ` [bug#75981] [PATCH (WIP) v1.5 1/4] Add 'guix fork create' 45mg
` (4 more replies)
5 siblings, 5 replies; 36+ messages in thread
From: 45mg @ 2025-02-01 11:43 UTC (permalink / raw)
To: 75981
Cc: Nicolas Graves, Tomas Volf, 45mg, Liliana Marie Prikler,
Ricardo Wurmus, Attila Lendvai, Ludovic Courtès,
Maxim Cournoyer
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
^ permalink raw reply [flat|nested] 36+ messages in thread
* [bug#75981] [PATCH (WIP) v1.5 1/4] Add 'guix fork create'.
2025-02-01 11:43 ` [bug#75981] [PATCH (WIP) v1.5 " 45mg
@ 2025-02-01 11:43 ` 45mg
2025-02-02 15:01 ` Maxim Cournoyer
2025-02-03 15:15 ` Simon Tournier
2025-02-01 11:43 ` [bug#75981] [PATCH (WIP) v1.5 2/4] Add 'guix fork authenticate' 45mg
` (3 subsequent siblings)
4 siblings, 2 replies; 36+ messages in thread
From: 45mg @ 2025-02-01 11:43 UTC (permalink / raw)
To: 75981
Cc: Nicolas Graves, Tomas Volf, 45mg, Liliana Marie Prikler,
Ricardo Wurmus, Attila Lendvai, Christopher Baines,
Josselin Poiret, Ludovic Courtès, Mathieu Othacehe,
Simon Tournier, Tobias Geerinckx-Rice
* guix/scripts/fork.scm, guix/scripts/fork/create.scm: New files.
* Makefile.am (MODULES): Add the new files.
* guix/build/utils.scm (invoke/stdout): New procedure.
* guix/utils.scm (chain-cut): New procedure.
* guix/scripts/git/authenticate.scm
(commit-short-id): Remove procedure, and use its existing duplicate in
guix/channels.scm.
(openpgp-fingerprint*, current-branch, show-stats): Move procedures to
the files below.
* guix/channels.scm (openpgp-fingerprint*): Moved here.
* guix/git.scm (repository-current-branch): Moved here and renamed from
'current-branch'.
* guix/git-authenticate.scm (show-authentication-stats): Moved here and
renamed from 'show-stats'.
Change-Id: I45ba37f434e136f6d496c741d9a933280f9ccf88
---
Makefile.am | 2 +
guix/channels.scm | 13 ++
guix/git-authenticate.scm | 17 ++
guix/git.scm | 10 ++
guix/scripts/fork.scm | 67 ++++++++
guix/scripts/fork/create.scm | 258 ++++++++++++++++++++++++++++++
guix/scripts/git/authenticate.scm | 45 +-----
guix/utils.scm | 61 +++++++
8 files changed, 432 insertions(+), 41 deletions(-)
create mode 100644 guix/scripts/fork.scm
create mode 100644 guix/scripts/fork/create.scm
diff --git a/Makefile.am b/Makefile.am
index f759803b8b..c628450a5a 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -377,6 +377,8 @@ MODULES = \
guix/scripts/size.scm \
guix/scripts/git.scm \
guix/scripts/git/authenticate.scm \
+ guix/scripts/fork.scm \
+ guix/scripts/fork/create.scm \
guix/scripts/graph.scm \
guix/scripts/weather.scm \
guix/scripts/container.scm \
diff --git a/guix/channels.scm b/guix/channels.scm
index 4700f7a45d..6ca8e64881 100644
--- a/guix/channels.scm
+++ b/guix/channels.scm
@@ -47,6 +47,7 @@ (define-module (guix channels)
#:use-module (guix packages)
#:use-module (guix progress)
#:use-module (guix derivations)
+ #:autoload (rnrs bytevectors) (bytevector-length)
#:use-module (guix diagnostics)
#:use-module (guix sets)
#:use-module (guix store)
@@ -81,6 +82,7 @@ (define-module (guix channels)
openpgp-fingerprint->bytevector
openpgp-fingerprint
+ openpgp-fingerprint*
%default-guix-channel
%default-channels
@@ -171,6 +173,17 @@ (define-syntax openpgp-fingerprint
((_ str)
#'(openpgp-fingerprint->bytevector str)))))
+(define (openpgp-fingerprint* str)
+ "Like openpgp-fingerprint, but with error handling from (guix diagnostics)."
+ (unless (string-every (char-set-union char-set:hex-digit
+ char-set:whitespace)
+ str)
+ (leave (G_ "~a: invalid OpenPGP fingerprint~%") str))
+ (let ((fingerprint (openpgp-fingerprint str)))
+ (unless (= 20 (bytevector-length fingerprint))
+ (leave (G_ "~a: wrong length for OpenPGP fingerprint~%") str))
+ fingerprint))
+
(define %guix-channel-introduction
;; Introduction of the official 'guix channel. The chosen commit is the
;; first one that introduces '.guix-authorizations' on the 'staging'
diff --git a/guix/git-authenticate.scm b/guix/git-authenticate.scm
index 37c69d0880..8bc7fb6fb3 100644
--- a/guix/git-authenticate.scm
+++ b/guix/git-authenticate.scm
@@ -40,6 +40,7 @@ (define-module (guix git-authenticate)
#:use-module (rnrs bytevectors)
#:use-module (rnrs io ports)
#:use-module (ice-9 match)
+ #:use-module (ice-9 format)
#:autoload (ice-9 pretty-print) (pretty-print)
#:export (read-authorizations
commit-signing-key
@@ -52,6 +53,7 @@ (define-module (guix git-authenticate)
repository-cache-key
authenticate-repository
+ show-authentication-stats
git-authentication-error?
git-authentication-error-commit
@@ -449,3 +451,18 @@ (define* (authenticate-repository repository start signer
(oid->string (commit-id end-commit)))
stats))))
+
+(define (show-authentication-stats stats)
+ "Display STATS, an alist containing commit signing stats as returned by
+'authenticate-repository'."
+ (format #t (G_ "Signing statistics:~%"))
+ (for-each (match-lambda
+ ((signer . count)
+ (format #t " ~a ~10d~%"
+ (openpgp-format-fingerprint
+ (openpgp-public-key-fingerprint signer))
+ count)))
+ (sort stats
+ (match-lambda*
+ (((_ . count1) (_ . count2))
+ (> count1 count2))))))
diff --git a/guix/git.scm b/guix/git.scm
index 6ac6e4e3a2..afeacb53aa 100644
--- a/guix/git.scm
+++ b/guix/git.scm
@@ -59,6 +59,7 @@ (define-module (guix git)
with-git-error-handling
false-if-git-not-found
repository-info
+ repository-current-branch
update-cached-checkout
url+commit->name
latest-repository-commit
@@ -401,6 +402,15 @@ (define (repository-info directory)
(lambda _
(values #f #f #f))))
+(define (repository-current-branch repository)
+ "Return the name of the checked out branch of REPOSITORY or #f if it could
+not be determined."
+ (and (not (repository-head-detached? repository))
+ (let* ((head (repository-head repository))
+ (name (reference-name head)))
+ (and (string-prefix? "refs/heads/" name)
+ (string-drop name (string-length "refs/heads/"))))))
+
(define* (update-submodules repository
#:key (log-port (current-error-port))
(fetch-options #f))
diff --git a/guix/scripts/fork.scm b/guix/scripts/fork.scm
new file mode 100644
index 0000000000..2d97bcb93f
--- /dev/null
+++ b/guix/scripts/fork.scm
@@ -0,0 +1,67 @@
+;;; GNU Guix --- Functional package management for GNU
+;;; Copyright © 2025 45mg <45mg.writes@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~%")))
+
+\f
+;;;
+;;; 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)))))
+
+\f
+;;;
+;;; Entry point.
+;;;
+
+(define (guix-fork-create . args)
+ (define options
+ (parse-command-line args %options (list %default-options)
+ #:build-options? #f))
+
+ (define (command-line-arguments lst)
+ (reverse (filter-map (match-lambda
+ (('argument . arg) arg)
+ (_ #f))
+ lst)))
+
+ (with-error-handling
+ (let* ((signing-key directory (match (command-line-arguments options)
+ ((signing-key directory)
+ (values signing-key directory))
+ ((signing-key)
+ (values signing-key "guix"))
+ (_ (missing-arguments))))
+ (upstream (assoc-ref options 'upstream))
+ (channel-url (assoc-ref options 'channel-url))
+ (use-existing? (assoc-ref options 'use-existing?))
+ (git-parameters (assoc-ref options 'git-parameters))
+ (git-c-options ;'("-c" "param1" "-c" "param2" ...)
+ (let loop ((opts '()) (params git-parameters))
+ (if (or (not params) (null-list? params))
+ opts
+ (loop (append
+ opts (list "-c" (first params)))
+ (drop params 1)))))
+
+ (key-file-name (fingerprint->key-file-name signing-key))
+ (introduction-name (car (string-split key-file-name #\-)))
+
+ (upstream-branch-name "master"))
+
+ (define (invoke-git . args)
+ (apply invoke `("git" ,@git-c-options "-C" ,directory ,@args)))
+
+ (unless use-existing?
+ (info (G_ "Cloning from upstream ~a...~%") upstream)
+ (invoke "git" "clone" upstream directory))
+
+ (info (G_ "Authenticating upstream commits...~%"))
+
+ (when channel-url
+ (info (G_ "Renaming existing 'origin' remote to 'upstream'...~%"))
+ (invoke-git "remote" "rename" "origin" "upstream")
+ (info (G_ "Using provided channel URL for new 'origin' remote...~%"))
+ (invoke-git "remote" "add" "origin" channel-url))
+
+ (set! upstream-branch-name
+ (chain-cut
+ (invoke/stdout "git"
+ "-C" directory
+ "symbolic-ref"
+ (string-append "refs/remotes/"
+ (if channel-url "upstream" "origin")
+ "/HEAD"))
+ string-trim-right
+ (string-split <> #\/)
+ last))
+
+ (info (G_ "Adding key to keyring branch...~%"))
+ (invoke-git "switch" "keyring")
+ (invoke "gpg"
+ "--armor" "--export"
+ "-o" (string-append directory "/" key-file-name)
+ signing-key)
+ (invoke-git "add" "--" key-file-name)
+ (invoke-git "commit" "-m" "Add key for fork introduction.")
+
+ (info (G_ "Setting up fork branch...~%"))
+ (invoke-git "switch" "--create" "fork" "master")
+ (when channel-url
+ (update-channel-url (string-append directory "/.guix-channel")
+ channel-url))
+ (rewrite-authorizations (string-append directory "/.guix-authorizations")
+ introduction-name signing-key)
+ (invoke-git "add" "--"
+ (string-append directory "/.guix-authorizations")
+ (string-append directory "/.guix-channel"))
+ (invoke-git "commit"
+ (string-append "--gpg-sign=" signing-key)
+ "-m"
+ (string-append
+ "Initial fork commit.\n\n"
+ ".guix-authorizations: Allow only " introduction-name "'s key."
+ (if channel-url
+ "\n.guix-channels: Update channel URL."
+ "")))
+
+ (info (G_ "Successfully created Guix fork in ~a.
+You should run the following command next:
+guix fork authenticate ~a ~a ~a~%")
+ directory
+ upstream-branch-name
+ (string-trim-right (invoke/stdout "git" "-C" directory "rev-parse" "HEAD"))
+ signing-key))))
diff --git a/guix/scripts/git/authenticate.scm b/guix/scripts/git/authenticate.scm
index e3ecb67c89..154aae9b14 100644
--- a/guix/scripts/git/authenticate.scm
+++ b/guix/scripts/git/authenticate.scm
@@ -23,8 +23,8 @@ (define-module (guix scripts git authenticate)
#:use-module (guix git-authenticate)
#:autoload (guix openpgp) (openpgp-format-fingerprint
openpgp-public-key-fingerprint)
- #:use-module ((guix channels) #:select (openpgp-fingerprint))
- #:use-module ((guix git) #:select (with-git-error-handling))
+ #:use-module ((guix channels) #:select (openpgp-fingerprint*))
+ #:use-module ((guix git) #:select (with-git-error-handling commit-short-id repository-current-branch))
#:use-module (guix progress)
#:use-module (guix base64)
#:autoload (rnrs bytevectors) (bytevector-length)
@@ -76,15 +76,6 @@ (define %options
(define %default-options
'())
-(define (current-branch repository)
- "Return the name of the checked out branch of REPOSITORY or #f if it could
-not be determined."
- (and (not (repository-head-detached? repository))
- (let* ((head (repository-head repository))
- (name (reference-name head)))
- (and (string-prefix? "refs/heads/" name)
- (string-drop name (string-length "refs/heads/"))))))
-
(define (config-value repository key)
"Return the config value associated with KEY in the 'guix.authentication' or
'guix.authentication-BRANCH' name space in REPOSITORY, or #f if no such config
@@ -94,7 +85,7 @@ (define (config-value repository key)
((_ exp)
(catch 'git-error (lambda () exp) (const #f))))))
(let* ((config (repository-config repository))
- (branch (current-branch repository)))
+ (branch (repository-current-branch repository)))
;; First try the BRANCH-specific value, then the generic one.`
(or (and branch
(false-if-git-error
@@ -194,21 +185,6 @@ (define (install-hooks repository)
(warning (G_ "cannot determine where to install hooks\
(Guile-Git too old?)~%"))))
-(define (show-stats stats)
- "Display STATS, an alist containing commit signing stats as returned by
-'authenticate-repository'."
- (format #t (G_ "Signing statistics:~%"))
- (for-each (match-lambda
- ((signer . count)
- (format #t " ~a ~10d~%"
- (openpgp-format-fingerprint
- (openpgp-public-key-fingerprint signer))
- count)))
- (sort stats
- (match-lambda*
- (((_ . count1) (_ . count2))
- (> count1 count2))))))
-
(define (show-help)
(display (G_ "Usage: guix git authenticate COMMIT SIGNER [OPTIONS...]
Authenticate the given Git checkout using COMMIT/SIGNER as its introduction.\n"))
@@ -251,19 +227,6 @@ (define (guix-git-authenticate . args)
(_ #f))
lst)))
- (define commit-short-id
- (compose (cut string-take <> 7) oid->string commit-id))
-
- (define (openpgp-fingerprint* str)
- (unless (string-every (char-set-union char-set:hex-digit
- char-set:whitespace)
- str)
- (leave (G_ "~a: invalid OpenPGP fingerprint~%") str))
- (let ((fingerprint (openpgp-fingerprint str)))
- (unless (= 20 (bytevector-length fingerprint))
- (leave (G_ "~a: wrong length for OpenPGP fingerprint~%") str))
- fingerprint))
-
(define (make-reporter start-commit end-commit commits)
(format (current-error-port)
(G_ "Authenticating commits ~a to ~a (~h new \
@@ -321,7 +284,7 @@ (define (guix-git-authenticate . args)
(install-hooks repository))
(when (and show-stats? (not (null? stats)))
- (show-stats stats))
+ (show-authentication-stats stats))
(info (G_ "successfully authenticated commit ~a~%")
(oid->string end))))))
diff --git a/guix/utils.scm b/guix/utils.scm
index b6cf5aea4f..0d023e7729 100644
--- a/guix/utils.scm
+++ b/guix/utils.scm
@@ -21,6 +21,8 @@
;;; Copyright © 2023 Zheng Junjie <873216071@qq.com>
;;; Copyright © 2023 Foundation Devices, Inc. <hello@foundationdevices.com>
;;; Copyright © 2024 Herman Rimm <herman@rimm.ee>
+;;; Copyright © 2025 Tomas Volf <~@wolfsden.cz>
+;;; Copyright © 2025 45mg <45mg.writes@gmail.com>
;;;
;;; This file is part of GNU Guix.
;;;
@@ -44,6 +46,8 @@ (define-module (guix utils)
#:use-module (srfi srfi-11)
#:use-module (srfi srfi-26)
#:use-module (srfi srfi-71)
+ #:use-module (srfi srfi-35) ;TODO remove after moving invoke/stdout
+ #:use-module (ice-9 popen) ;TODO remove after moving invoke/stdout
#:use-module (rnrs io ports) ;need 'port-position' etc.
#:use-module ((rnrs bytevectors) #:select (bytevector-u8-set!))
#:use-module (guix memoization)
@@ -163,6 +167,9 @@ (define-module (guix utils)
call-with-compressed-output-port
canonical-newline-port
+ chain-cut
+ invoke/stdout ;TODO move to (guix build utils)
+
string-distance
string-closest
@@ -1193,6 +1200,60 @@ (define-syntax current-source-directory
;; raising an error would upset Geiser users
#f))))))
+\f
+;;;
+;;; Higher-order functions.
+;;;
+
+(define-syntax chain-cut
+ (lambda (x)
+ "Apply each successive form to the result of evaluating the previous one.
+Before applying, expand each form (op ...) to (cut op ...).
+
+Examples:
+
+ (chain-cut '(1 2 3) cdr car)
+ => (car (cdr '(1 2 3)))
+
+ (chain-cut 2 (- 3 <>) 1+)
+ => (1+ ((cut - 3 <>) 2))
+ => (1+ (- 3 2))
+"
+ (syntax-case x ()
+ ((chain-cut init op) (identifier? #'op)
+ #'(op init))
+ ((chain-cut init (op ...))
+ #'((cut op ...) init))
+ ((chain-cut init op op* ...) (identifier? #'op)
+ #'(chain-cut (op init) op* ...))
+ ((chain-cut init (op ...) op* ...)
+ #'(chain-cut ((cut op ...) init) op* ...)))))
+
+;; Copied from (guix build utils); remove
+(define-condition-type &invoke-error &error
+ invoke-error?
+ (program invoke-error-program)
+ (arguments invoke-error-arguments)
+ (exit-status invoke-error-exit-status)
+ (term-signal invoke-error-term-signal)
+ (stop-signal invoke-error-stop-signal))
+;; TODO move to (guix build utils)
+(define (invoke/stdout program . args)
+ "Invoke PROGRAM with ARGS and capture PROGRAM's standard output. If PROGRAM
+succeeds, return its standard output as a string. Otherwise, raise an
+'&invoke-error' condition."
+ (let* ((port (apply open-pipe* OPEN_READ program args))
+ (data (get-string-all port))
+ (code (close-pipe port)))
+ (unless (zero? code)
+ (raise (condition (&invoke-error
+ (program program)
+ (arguments args)
+ (exit-status (status:exit-val code))
+ (term-signal (status:term-sig code))
+ (stop-signal (status:stop-sig code))))))
+ data))
+
\f
;;;
;;; String comparison.
--
2.48.1
^ permalink raw reply related [flat|nested] 36+ messages in thread
* [bug#75981] [PATCH (WIP) v1.5 2/4] Add 'guix fork authenticate'.
2025-02-01 11:43 ` [bug#75981] [PATCH (WIP) v1.5 " 45mg
2025-02-01 11:43 ` [bug#75981] [PATCH (WIP) v1.5 1/4] Add 'guix fork create' 45mg
@ 2025-02-01 11:43 ` 45mg
2025-02-02 15:23 ` Maxim Cournoyer
2025-02-01 11:43 ` [bug#75981] [PATCH (WIP) v1.5 3/4] Add 'guix fork update' 45mg
` (2 subsequent siblings)
4 siblings, 1 reply; 36+ messages in thread
From: 45mg @ 2025-02-01 11:43 UTC (permalink / raw)
To: 75981
Cc: Nicolas Graves, Tomas Volf, 45mg, Liliana Marie Prikler,
Ricardo Wurmus, Attila Lendvai, Christopher Baines,
Josselin Poiret, Ludovic Courtès, Mathieu Othacehe,
Simon Tournier, Tobias Geerinckx-Rice
* guix/scripts/fork/authenticate.scm: New file.
* Makefile.am (MODULES): Add the new file.
* guix/scripts/fork.scm
(show-help): Mention new command.
(%sub-commands): Add new command.
Change-Id: Ic34a1b3d1642cedce8d1ff5bae825df30e47755c
---
Makefile.am | 1 +
guix/scripts/fork.scm | 6 +-
guix/scripts/fork/authenticate.scm | 331 +++++++++++++++++++++++++++++
3 files changed, 336 insertions(+), 2 deletions(-)
create mode 100644 guix/scripts/fork/authenticate.scm
diff --git a/Makefile.am b/Makefile.am
index c628450a5a..1c1f5d84fd 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -379,6 +379,7 @@ MODULES = \
guix/scripts/git/authenticate.scm \
guix/scripts/fork.scm \
guix/scripts/fork/create.scm \
+ guix/scripts/fork/authenticate.scm \
guix/scripts/graph.scm \
guix/scripts/weather.scm \
guix/scripts/container.scm \
diff --git a/guix/scripts/fork.scm b/guix/scripts/fork.scm
index 2d97bcb93f..c5c7a59ba7 100644
--- a/guix/scripts/fork.scm
+++ b/guix/scripts/fork.scm
@@ -29,7 +29,9 @@ (define (show-help)
(display (G_ "The valid values for ACTION are:\n"))
(newline)
(display (G_ "\
- create set up a fork of Guix\n"))
+ create set up a fork of Guix\n"))
+ (display (G_ "\
+ authenticate authenticate a fork of Guix\n"))
(newline)
(display (G_ "
-h, --help display this help and exit"))
@@ -38,7 +40,7 @@ (define (show-help)
(newline)
(show-bug-report-information))
-(define %sub-commands '("create"))
+(define %sub-commands '("create" "authenticate"))
(define (resolve-sub-command name)
(let ((module (resolve-interface
diff --git a/guix/scripts/fork/authenticate.scm b/guix/scripts/fork/authenticate.scm
new file mode 100644
index 0000000000..83d9d87d44
--- /dev/null
+++ b/guix/scripts/fork/authenticate.scm
@@ -0,0 +1,331 @@
+;;; GNU Guix --- Functional package management for GNU
+;;; Copyright © 2025 45mg <45mg.writes@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~%")))
+
+\f
+;;;
+;;; 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
^ permalink raw reply related [flat|nested] 36+ messages in thread
* [bug#75981] [PATCH (WIP) v1.5 3/4] Add 'guix fork update'.
2025-02-01 11:43 ` [bug#75981] [PATCH (WIP) v1.5 " 45mg
2025-02-01 11:43 ` [bug#75981] [PATCH (WIP) v1.5 1/4] Add 'guix fork create' 45mg
2025-02-01 11:43 ` [bug#75981] [PATCH (WIP) v1.5 2/4] Add 'guix fork authenticate' 45mg
@ 2025-02-01 11:43 ` 45mg
2025-02-02 16:21 ` Maxim Cournoyer
2025-02-01 11:43 ` [bug#75981] [PATCH (WIP) v1.5 4/4] Document 'guix fork' 45mg
2025-02-05 3:21 ` [bug#75981] [PATCH (WIP) v1.5 0/4] Add " 45mg
4 siblings, 1 reply; 36+ messages in thread
From: 45mg @ 2025-02-01 11:43 UTC (permalink / raw)
To: 75981
Cc: Nicolas Graves, Tomas Volf, 45mg, Liliana Marie Prikler,
Ricardo Wurmus, Attila Lendvai, Christopher Baines,
Josselin Poiret, Ludovic Courtès, Mathieu Othacehe,
Simon Tournier, Tobias Geerinckx-Rice
* guix/scripts/fork/update.scm: New file.
* Makefile.am (MODULES): Add the new file.
* guix/scripts/fork.scm
(show-help): Mention new command.
(%sub-commands): Add new command.
Change-Id: I2017eb9a9286c02ca8bdf962bcbfe89d7607c413
---
Makefile.am | 1 +
guix/scripts/fork.scm | 4 +-
guix/scripts/fork/update.scm | 182 +++++++++++++++++++++++++++++++++++
3 files changed, 186 insertions(+), 1 deletion(-)
create mode 100644 guix/scripts/fork/update.scm
diff --git a/Makefile.am b/Makefile.am
index 1c1f5d84fd..8edd371ccd 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -380,6 +380,7 @@ MODULES = \
guix/scripts/fork.scm \
guix/scripts/fork/create.scm \
guix/scripts/fork/authenticate.scm \
+ guix/scripts/fork/update.scm \
guix/scripts/graph.scm \
guix/scripts/weather.scm \
guix/scripts/container.scm \
diff --git a/guix/scripts/fork.scm b/guix/scripts/fork.scm
index c5c7a59ba7..bf9c86e0aa 100644
--- a/guix/scripts/fork.scm
+++ b/guix/scripts/fork.scm
@@ -32,6 +32,8 @@ (define (show-help)
create set up a fork of Guix\n"))
(display (G_ "\
authenticate authenticate a fork of Guix\n"))
+ (display (G_ "\
+ update update a fork of Guix\n"))
(newline)
(display (G_ "
-h, --help display this help and exit"))
@@ -40,7 +42,7 @@ (define (show-help)
(newline)
(show-bug-report-information))
-(define %sub-commands '("create" "authenticate"))
+(define %sub-commands '("create" "authenticate" "update"))
(define (resolve-sub-command name)
(let ((module (resolve-interface
diff --git a/guix/scripts/fork/update.scm b/guix/scripts/fork/update.scm
new file mode 100644
index 0000000000..4223b9855c
--- /dev/null
+++ b/guix/scripts/fork/update.scm
@@ -0,0 +1,182 @@
+;;; GNU Guix --- Functional package management for GNU
+;;; Copyright © 2025 Tomas Volf <~@wolfsden.cz>
+;;; Copyright © 2025 45mg <45mg.writes@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 ~%")))
+
+\f
+;;;
+;;; 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
^ permalink raw reply related [flat|nested] 36+ messages in thread
* [bug#75981] [PATCH (WIP) v1.5 4/4] Document 'guix fork'.
2025-02-01 11:43 ` [bug#75981] [PATCH (WIP) v1.5 " 45mg
` (2 preceding siblings ...)
2025-02-01 11:43 ` [bug#75981] [PATCH (WIP) v1.5 3/4] Add 'guix fork update' 45mg
@ 2025-02-01 11:43 ` 45mg
2025-02-03 1:14 ` Maxim Cournoyer
2025-02-05 3:21 ` [bug#75981] [PATCH (WIP) v1.5 0/4] Add " 45mg
4 siblings, 1 reply; 36+ messages in thread
From: 45mg @ 2025-02-01 11:43 UTC (permalink / raw)
To: 75981
Cc: Nicolas Graves, Tomas Volf, 45mg, Liliana Marie Prikler,
Ricardo Wurmus, Attila Lendvai, Ludovic Courtès,
Maxim Cournoyer
* doc/guix.texi (Invoking guix fork): New node.
* doc/contributing.texi (Using Your Own Patches): New node.
Change-Id: I06240f0fe8d1fe39f27130a72f5d0d92949c99da
---
doc/contributing.texi | 50 ++++++++++++++
doc/guix.texi | 150 ++++++++++++++++++++++++++++++++++++++++++
2 files changed, 200 insertions(+)
diff --git a/doc/contributing.texi b/doc/contributing.texi
index c94ae940fa..bd4fd6c2ac 100644
--- a/doc/contributing.texi
+++ b/doc/contributing.texi
@@ -35,6 +35,7 @@ Contributing
* Making Decisions:: Collectively choosing the way forward.
* Commit Access:: Pushing to the official repository.
* Reviewing the Work of Others:: Some guidelines for sharing reviews.
+* Using Your Own Patches:: Using your own work before it's accepted.
* Updating the Guix Package:: Updating the Guix package definition.
* Deprecation Policy:: Commitments and tools for deprecation.
* Writing Documentation:: Improving documentation in GNU Guix.
@@ -3095,6 +3096,55 @@ Reviewing the Work of Others
have reviewed more easily by adding a @code{reviewed-looks-good} usertag
for the @code{guix} user (@pxref{Debbugs Usertags}).
+@node Using Your Own Patches
+@section Using Your Own Patches
+
+If you've taken the time to contribute code to Guix, chances are that
+you want the changes you've made to be reflected in your own Guix
+installation as soon as possible. Maybe you've added a package you want,
+and you want to start using it @emph{right now}. Or you've fixed a bug
+that affects you, and you want it to @emph{go away}.
+
+As described in the preceding sections, all contributions to Guix first
+go through a review process to ensure code quality. Sometimes, this can
+take longer than one would like. Ideally, the pace of the review process
+should not prevent you from benefiting from your own work.
+
+One way to work around this issue is to create an additional channel of
+your own (@pxref{Creating a Channel}), and add your code to it. For
+certain kinds of contributions, such as adding a new package, this is
+fairly straightforward - simply copy your new package definition(s) into
+a new file in the channel, and remove them when your contribution is
+accepted.
+
+However, there may be cases where this is not convenient. Certain kinds
+of changes, such as those that need to modify existing Guix internals,
+may be more challenging to incorporate into a channel. Moreoever, the
+more substantial your contribution is, the more work it will be to do
+so.
+
+@cindex fork, of Guix
+For such cases, there is another option. Recall that the patch series
+that you sent (@pxref{Sending a Patch Series}) was created from a one or
+more commits on a checkout of the Guix repository (@pxref{Building from
+Git}). You could simply specify this repository (referred to as your
+`Guix fork', or simply `fork', from here onwards), and its relevant
+branch, as your `@code{guix}' channel (@pxref{Using a Custom Guix
+Channel}). Now `@code{guix pull}' will fetch your new commits, and
+you'll see the changes you made reflected in your Guix installation!
+
+However, there's a potential complication to this approach - the issue
+of authentication (@pxref{Channel Authentication}). If your fork only
+exists on your local filesystem (a `local fork'), then you probably
+don't need to worry about this, and can pull without authentication
+(@pxref{Invoking guix pull}). But other situations, such as a remotely
+hosted fork, may make it important for your fork to be authenticated, in
+the same way that all channels are expected to be.
+
+Guix provides a @command{guix fork} command in order to simplify and
+automate many details of creating and managing and authenticated
+fork. For more information, @pxref{Invoking guix fork}.
+
@node Updating the Guix Package
@section Updating the Guix Package
diff --git a/doc/guix.texi b/doc/guix.texi
index b1b6d98e74..bbb5666d0a 100644
--- a/doc/guix.texi
+++ b/doc/guix.texi
@@ -311,6 +311,7 @@ Top
* Invoking guix pack:: Creating software bundles.
* The GCC toolchain:: Working with languages supported by GCC.
* Invoking guix git authenticate:: Authenticating Git repositories.
+* Invoking guix fork:: Creating and managing authenticated forks of Guix.
Programming Interface
@@ -5930,6 +5931,7 @@ Development
* Invoking guix pack:: Creating software bundles.
* The GCC toolchain:: Working with languages supported by GCC.
* Invoking guix git authenticate:: Authenticating Git repositories.
+* Invoking guix fork:: Creating and managing authenticated forks of Guix.
@end menu
@node Invoking guix shell
@@ -7534,6 +7536,154 @@ Invoking guix git authenticate
@end table
+@node Invoking guix fork
+@section Invoking @command{guix fork}
+
+@cindex @command{guix fork}
+
+The @command{guix fork} command provides the means to quickly set up,
+authenticate, and keep up-to-date an authenticated fork of Guix. For
+more information on authentication of a Guix checkout, @pxref{Invoking
+guix git authenticate}.
+
+Its syntax is:
+
+guix fork ACTION ARGS...
+
+ACTION specifies the fork-related action to perform. Currently, the
+following values are supported:
+
+@table @code
+@item create SIGNING_KEY [DIRECTORY OPTIONS...]
+Create a fork of Guix in DIRECTORY, using SIGNING_KEY to sign the introductory
+commit.
+DIRECTORY defaults to ./guix.
+
+First, clone Guix into DIRECTORY, unless @code{--use-existing} is
+given. Then, add SIGNING_KEY to the `@code{keyring}' branch of the
+repository. Finally, create a new `@code{fork}' branch based starting
+from the default branch, whose initial commit authorizes SIGNING_KEY
+alone (by adding it to @file{.guix-authorizations}) and is signed by it.
+
+The new `@code{fork}' branch is intended to mirror upstream
+Guix. Updating the fork amounts to applying all new commits to it (see
+the `@code{update}' command below for further explanation). You can work
+on patches in branches based off of this one, in much the same way as
+you would base them on Guix's default branch - every commit from the
+latter will be present in the former.
+
+To @command{guix pull} your changes, you could create a `build' branch
+starting from the initial fork commit, onto which you can cherry-pick or
+rebase commits from patch branches. This branch can then be specified
+for the `@code{guix}' channel (@pxref{Using a Custom Guix Channel}).
+Updating this channel can be done by merging the `@code{fork}' branch
+into it.
+
+OPTIONS can be one or more of the following:
+
+@table @code
+@item --use-existing
+Use existing clone of Guix in DIRECTORY. This is useful if you've
+already created commits for a patch series (@pxref{Using Your Own
+Patches}). However, all commits to the default branch, as well as any
+branches that may be merged into it in the future, must have been signed
+with an authorized key; otherwise, authentication will fail later.
+@item --upstream=URI
+The repository to clone from. This defaults to the default URL for the
+Guix repository.
+@item --channel-url=URI
+Optional URI, which if given, will be used to replace the channel URL.
+Furthermore, the existing `origin' remote (which tracks
+`@code{upstream}') is renamed to `upstream', and a new `origin' remote
+is created to track URI.
+@item --git-parameter PARAMETER
+Specify configuration PARAMETER for git, via `-c' option. You can pass
+this option multiple times.
+@end table
+
+@cindex authentication, of Guix forks
+@item authenticate UPSTREAM COMMIT SIGNER [OPTIONS...]
+Authenticate a Guix fork, using COMMIT and SIGNER as the fork
+introduction.
+
+First, authenticate new commits from UPSTREAM, using Guix's default
+introduction. Then authenticate the remaining commits using the fork
+introduction.
+
+As with @code{guix git authenticate}, all three of UPSTREAM, COMMIT and
+SIGNER will be cached in .git/config, so that you don't need to specify
+them after the first time.
+
+OPTIONS can be one or more of the following:
+
+@table @code
+@item --repository=DIRECTORY
+@itemx -r DIRECTORY
+Authenticate the git repository in DIRECTORY, instead of the current
+directory.
+@item --upstream-commit=COMMIT
+@itemx --upstream-signer=SIGNER
+Use COMMIT/SIGNER as the introduction for upstream
+Guix, instead of Guix's default channel introduction.
+@item --keyring=REFERENCE
+@itemx -k REFERENCE
+Load keyring for fork commits from REFERENCE, a Git branch (default
+`@code{keyring}').
+@item --upstream-keyring=REFERENCE
+Load keyring for upstream commits from REFERENCE, a Git branch (default
+`@code{keyring}').
+@item --end=COMMIT
+Authenticate fork commits up to COMMIT.
+@item --upstream-end=COMMIT
+Authenticate upstream commits up to COMMIT.
+
+@item --cache-key=KEY
+@itemx --historical-authorizations=FILE
+@itemx --stats
+Identical to the correponding options in @command{guix git authenticate}
+(@pxref{Invoking guix git authenticate}).
+@end table
+
+@item update [OPTIONS...]
+Pull into this Guix fork's configured upstream branch (from running
+@command{guix fork authenticate}), then apply new commits onto the
+current branch.
+
+This approach may seem less convenient than simply merging the upstream
+branch into the fork branch. Indeed, it duplicates every upstream commit
+under a different commit hash, and applying a large number of commits
+can be slow. However, this is currently the only feasible approach due
+to the nature of Guix's authentication mechanism. Namely, merge commits
+can only be authenticated if both their parents are signed by an
+authorized key, meaning that you can only use the merge workflow if
+you're authorized to commit to upstream Guix.
+
+For mapping commits on the fork branch to their equivalents on the
+upstream branch, you can use @command{guix fork identify} (see below).
+
+OPTIONS can be one or more of the following:
+
+@table @code
+@item --repository=DIRECTORY
+@itemx -r DIRECTORY
+Act in the Git repository in DIRECTORY.
+@item --fork-branch=BRANCH
+Apply new commits onto BRANCH instead of the current branch.
+@end table
+
+@item identify
+Coming soon!
+
+Given a commit hash from upstream Guix, print its equivalent on the fork
+branch, or vice versa.
+This uses the 'Change-Id:' line added to commit messages by Guix's
+'commit-msg' hook.
+The first invocation of this command will be slow, as the entire set of
+corresponding commits is built up as a hash table, and then
+cached. Subsequent invocations should be nearly instant.
+
+@end table
+
@c *********************************************************************
@node Programming Interface
@chapter Programming Interface
--
2.48.1
^ permalink raw reply related [flat|nested] 36+ messages in thread
* [bug#75981] [PATCH (WIP) v1.5 1/4] Add 'guix fork create'.
2025-02-01 11:43 ` [bug#75981] [PATCH (WIP) v1.5 1/4] Add 'guix fork create' 45mg
@ 2025-02-02 15:01 ` Maxim Cournoyer
2025-02-02 20:56 ` Attila Lendvai
2025-02-02 22:24 ` Simon Streit
2025-02-03 15:15 ` Simon Tournier
1 sibling, 2 replies; 36+ messages in thread
From: Maxim Cournoyer @ 2025-02-02 15:01 UTC (permalink / raw)
To: 45mg
Cc: Josselin Poiret, Nicolas Graves, Simon Tournier, Mathieu Othacehe,
Tomas Volf, Tobias Geerinckx-Rice, Liliana Marie Prikler, 75981,
Ricardo Wurmus, Christopher Baines, Attila Lendvai,
Ludovic Courtès
Hi,
My first thought was similar to Liliana's reply in the other
issue thread: putting lots of energy into making it convenient to fork
Guix instead of contributing to the review process (described as slow
and erratic, which appears to be the motivation here), appears
counter-productive.
So I'm not even sure this should be incorporated in Guix, especially if
it does touch the sensitive guix authentication mechanism.
I'll still offer a review, given the code looks rather good, and perhaps
being a committer I'm missing part of the picture on why such a
mechanism improves on the status quo of using extensions or channels, or
local unauthenticated forks (for personal use, that was enough for me
when one of my changes didn't make it for a year). It was rather
inconvenient, but that was a good motivator to keep nudging it into Guix
proper.
And I disagree with your assessment that it takes years to become a Guix
committer. I think 6 months to a year would be a reasonable time frame
for a dedicated individual. It's also not the only way to be useful to
the project. Reviewing the work of others help a lot too (those appear
at https://qa.guix.gnu.org/patches).
Below are some comments on the code.
45mg <45mg.writes@gmail.com> writes:
> * guix/scripts/fork.scm, guix/scripts/fork/create.scm: New files.
> * Makefile.am (MODULES): Add the new files.
> * guix/build/utils.scm (invoke/stdout): New procedure.
Touching (guix build utils) is a world rebuild (the module is included
in every build system, including the gnu-build-system). Perhaps start
its life in (guix utils) with a TODO to move it to (guix build utils)
later along another world-rebuilding change.
Later: I see you changed that in this v1.5 revision: in this case just
update the change log message.
> * guix/utils.scm (chain-cut): New procedure.
Could use 's/New procedure./Likewise./' to avoid repetition.
> * guix/scripts/git/authenticate.scm
> (commit-short-id): Remove procedure, and use its existing duplicate in
> guix/channels.scm.
> (openpgp-fingerprint*, current-branch, show-stats): Move procedures to
> the files below.
You can use the ellipsis trick: (openpgp-fingerprint*): "Move to..."
* guix/channels.scm (openpgp-fingerprint*): ... here.
and likewise for the other procedures.
There's a missing entry for adjusting the renamed current-branch
procedure inside the config-value proc.
[...]
> diff --git a/guix/channels.scm b/guix/channels.scm
> index 4700f7a45d..6ca8e64881 100644
> --- a/guix/channels.scm
> +++ b/guix/channels.scm
> @@ -47,6 +47,7 @@ (define-module (guix channels)
> #:use-module (guix packages)
> #:use-module (guix progress)
> #:use-module (guix derivations)
> + #:autoload (rnrs bytevectors) (bytevector-length)
> #:use-module (guix diagnostics)
> #:use-module (guix sets)
> #:use-module (guix store)
> @@ -81,6 +82,7 @@ (define-module (guix channels)
>
> openpgp-fingerprint->bytevector
> openpgp-fingerprint
> + openpgp-fingerprint*
>
> %default-guix-channel
> %default-channels
> @@ -171,6 +173,17 @@ (define-syntax openpgp-fingerprint
> ((_ str)
> #'(openpgp-fingerprint->bytevector str)))))
>
> +(define (openpgp-fingerprint* str)
> + "Like openpgp-fingerprint, but with error handling from (guix diagnostics)."
> + (unless (string-every (char-set-union char-set:hex-digit
> + char-set:whitespace)
> + str)
> + (leave (G_ "~a: invalid OpenPGP fingerprint~%") str))
> + (let ((fingerprint (openpgp-fingerprint str)))
> + (unless (= 20 (bytevector-length fingerprint))
> + (leave (G_ "~a: wrong length for OpenPGP fingerprint~%") str))
> + fingerprint))
> +
I'm not convinced having a program-exiting procedure in the public API
makes sense (and these are annoying at the REPL!) Returning a proper
exception would be better.
[...]
> --- /dev/null
> +++ b/guix/scripts/fork.scm
[...]
> +(define-module (guix scripts fork)
> + #:use-module (ice-9 match)
> + #:use-module (guix ui)
> + #:use-module (guix scripts)
Please list modules in lexicographic order.
[...]
> diff --git a/guix/scripts/fork/create.scm b/guix/scripts/fork/create.scm
> new file mode 100644
> index 0000000000..a9de204f23
> --- /dev/null
> +++ b/guix/scripts/fork/create.scm
[...]
> +\f
> +;;;
> +;;; 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.
[...]
> +\f
> +;;;
> +;;; Entry point.
> +;;;
> +
> +(define (guix-fork-create . args)
> + (define options
> + (parse-command-line args %options (list %default-options)
> + #:build-options? #f))
I think you could provide a proc to set the default value of the
DIRECTORY positional argument via #:argument-handler...
> +
> + (define (command-line-arguments lst)
> + (reverse (filter-map (match-lambda
> + (('argument . arg) arg)
> + (_ #f))
> + lst)))
> +
> + (with-error-handling
> + (let* ((signing-key directory (match (command-line-arguments options)
> + ((signing-key directory)
> + (values signing-key directory))
> + ((signing-key)
> + (values signing-key "guix"))
> + (_ (missing-arguments))))
Avoiding the command-line-arguments proc as well as the match above.
> + (upstream (assoc-ref options 'upstream))
> + (channel-url (assoc-ref options 'channel-url))
> + (use-existing? (assoc-ref options 'use-existing?))
> + (git-parameters (assoc-ref options 'git-parameters))
> + (git-c-options ;'("-c" "param1" "-c" "param2" ...)
> + (let loop ((opts '()) (params git-parameters))
> + (if (or (not params) (null-list? params))
> + opts
> + (loop (append
> + opts (list "-c" (first params)))
You have enough horizontal space to (append opts ...) on a single line I
think.
> + (drop params 1)))))
> +
> + (key-file-name (fingerprint->key-file-name signing-key))
> + (introduction-name (car (string-split key-file-name #\-)))
> +
> + (upstream-branch-name "master"))
> +
> + (define (invoke-git . args)
> + (apply invoke `("git" ,@git-c-options "-C" ,directory ,@args)))
We prefer to use guile-git throughout Guix, as it has a proper Scheme
interface. Have you tried using it instead of shelling out to git?
Perhaps it was missing some features you needed?
> + (unless use-existing?
> + (info (G_ "Cloning from upstream ~a...~%") upstream)
> + (invoke "git" "clone" upstream directory))
Why not using the above defined invoke-git here?
> +
> + (info (G_ "Authenticating upstream commits...~%"))
> +
> + (when channel-url
> + (info (G_ "Renaming existing 'origin' remote to 'upstream'...~%"))
> + (invoke-git "remote" "rename" "origin" "upstream")
> + (info (G_ "Using provided channel URL for new 'origin' remote...~%"))
> + (invoke-git "remote" "add" "origin" channel-url))
> +
> + (set! upstream-branch-name
> + (chain-cut
> + (invoke/stdout "git"
Break the line to place "git" below invoke/stdout, to avoid busting our
80 columns max convention a bit below.
> + "-C" directory
> + "symbolic-ref"
> + (string-append "refs/remotes/"
> + (if channel-url "upstream" "origin")
> + "/HEAD"))
> + string-trim-right
> + (string-split <> #\/)
> + last))
> +
> + (info (G_ "Adding key to keyring branch...~%"))
> + (invoke-git "switch" "keyring")
> + (invoke "gpg"
> + "--armor" "--export"
> + "-o" (string-append directory "/" key-file-name)
> + signing-key)
> + (invoke-git "add" "--" key-file-name)
> + (invoke-git "commit" "-m" "Add key for fork introduction.")
> +
> + (info (G_ "Setting up fork branch...~%"))
> + (invoke-git "switch" "--create" "fork" "master")
> + (when channel-url
> + (update-channel-url (string-append directory "/.guix-channel")
> + channel-url))
> + (rewrite-authorizations (string-append directory "/.guix-authorizations")
> + introduction-name signing-key)
> + (invoke-git "add" "--"
> + (string-append directory "/.guix-authorizations")
> + (string-append directory "/.guix-channel"))
> + (invoke-git "commit"
> + (string-append "--gpg-sign=" signing-key)
> + "-m"
> + (string-append
> + "Initial fork commit.\n\n"
> + ".guix-authorizations: Allow only " introduction-name "'s key."
> + (if channel-url
> + "\n.guix-channels: Update channel URL."
> + "")))
> + (info (G_ "Successfully created Guix fork in ~a.
Phew!
> +You should run the following command next:
> +guix fork authenticate ~a ~a ~a~%")
> + directory
> + upstream-branch-name
> + (string-trim-right (invoke/stdout "git" "-C" directory "rev-parse" "HEAD"))
> + signing-key))))
> diff --git a/guix/scripts/git/authenticate.scm b/guix/scripts/git/authenticate.scm
> index e3ecb67c89..154aae9b14 100644
> --- a/guix/scripts/git/authenticate.scm
> +++ b/guix/scripts/git/authenticate.scm
> @@ -23,8 +23,8 @@ (define-module (guix scripts git authenticate)
> #:use-module (guix git-authenticate)
> #:autoload (guix openpgp) (openpgp-format-fingerprint
> openpgp-public-key-fingerprint)
> - #:use-module ((guix channels) #:select (openpgp-fingerprint))
> - #:use-module ((guix git) #:select (with-git-error-handling))
> + #:use-module ((guix channels) #:select (openpgp-fingerprint*))
> + #:use-module ((guix git) #:select (with-git-error-handling commit-short-id repository-current-branch))
Please watch the 80 columns limit :-).
> #:use-module (guix progress)
> #:use-module (guix base64)
> #:autoload (rnrs bytevectors) (bytevector-length)
> @@ -76,15 +76,6 @@ (define %options
> (define %default-options
> '())
>
> -(define (current-branch repository)
> - "Return the name of the checked out branch of REPOSITORY or #f if it could
> -not be determined."
> - (and (not (repository-head-detached? repository))
> - (let* ((head (repository-head repository))
> - (name (reference-name head)))
> - (and (string-prefix? "refs/heads/" name)
> - (string-drop name (string-length "refs/heads/"))))))
> -
> (define (config-value repository key)
> "Return the config value associated with KEY in the 'guix.authentication' or
> 'guix.authentication-BRANCH' name space in REPOSITORY, or #f if no such config
> @@ -94,7 +85,7 @@ (define (config-value repository key)
> ((_ exp)
> (catch 'git-error (lambda () exp) (const #f))))))
> (let* ((config (repository-config repository))
> - (branch (current-branch repository)))
> + (branch (repository-current-branch repository)))
> ;; First try the BRANCH-specific value, then the generic one.`
> (or (and branch
> (false-if-git-error
> @@ -194,21 +185,6 @@ (define (install-hooks repository)
> (warning (G_ "cannot determine where to install hooks\
> (Guile-Git too old?)~%"))))
>
> -(define (show-stats stats)
> - "Display STATS, an alist containing commit signing stats as returned by
> -'authenticate-repository'."
> - (format #t (G_ "Signing statistics:~%"))
> - (for-each (match-lambda
> - ((signer . count)
> - (format #t " ~a ~10d~%"
> - (openpgp-format-fingerprint
> - (openpgp-public-key-fingerprint signer))
> - count)))
> - (sort stats
> - (match-lambda*
> - (((_ . count1) (_ . count2))
> - (> count1 count2))))))
> -
> (define (show-help)
> (display (G_ "Usage: guix git authenticate COMMIT SIGNER [OPTIONS...]
> Authenticate the given Git checkout using COMMIT/SIGNER as its introduction.\n"))
> @@ -251,19 +227,6 @@ (define (guix-git-authenticate . args)
> (_ #f))
> lst)))
>
> - (define commit-short-id
> - (compose (cut string-take <> 7) oid->string commit-id))
> -
> - (define (openpgp-fingerprint* str)
> - (unless (string-every (char-set-union char-set:hex-digit
> - char-set:whitespace)
> - str)
> - (leave (G_ "~a: invalid OpenPGP fingerprint~%") str))
> - (let ((fingerprint (openpgp-fingerprint str)))
> - (unless (= 20 (bytevector-length fingerprint))
> - (leave (G_ "~a: wrong length for OpenPGP fingerprint~%") str))
> - fingerprint))
> -
If that's only ever used here, I'd leave it here, as as I said earlier,
it's not a great API (and having confusing asterisk suffixes variants in
the public API should be limited to cases that truly matter, in my
opinion).
[...]
>
> @@ -1193,6 +1200,60 @@ (define-syntax current-source-directory
> ;; raising an error would upset Geiser users
> #f))))))
>
> +\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
^ permalink raw reply [flat|nested] 36+ messages in thread
* [bug#75981] [PATCH (WIP) v1.5 2/4] Add 'guix fork authenticate'.
2025-02-01 11:43 ` [bug#75981] [PATCH (WIP) v1.5 2/4] Add 'guix fork authenticate' 45mg
@ 2025-02-02 15:23 ` Maxim Cournoyer
0 siblings, 0 replies; 36+ messages in thread
From: Maxim Cournoyer @ 2025-02-02 15:23 UTC (permalink / raw)
To: 45mg
Cc: Josselin Poiret, Nicolas Graves, Simon Tournier, Mathieu Othacehe,
Tomas Volf, Tobias Geerinckx-Rice, Liliana Marie Prikler, 75981,
Ricardo Wurmus, Christopher Baines, Attila Lendvai,
Ludovic Courtès
Hi,
45mg <45mg.writes@gmail.com> writes:
> * guix/scripts/fork/authenticate.scm: New file.
> * Makefile.am (MODULES): Add the new file.
> * guix/scripts/fork.scm
> (show-help): Mention new command.
> (%sub-commands): Add new command.
OK.
[...]
> diff --git a/guix/scripts/fork/authenticate.scm b/guix/scripts/fork/authenticate.scm
[...]
> +(define-module (guix scripts fork authenticate)
> + #:use-module (git)
> + #:use-module (guix git)
> + #:use-module (guix git-authenticate)
> + #:use-module (guix base16)
> + #:use-module (guix ui)
> + #:use-module (guix progress)
> + #:use-module (guix scripts)
> + #:use-module (guix build utils)
> + #:use-module (guix channels)
> + #:use-module (ice-9 exceptions)
> + #:use-module (ice-9 match)
> + #:use-module (ice-9 receive)
> + #:use-module (ice-9 popen)
> + #:use-module (ice-9 format)
> + #:use-module (ice-9 pretty-print)
> + #:use-module (ice-9 string-fun)
> + #:use-module (ice-9 textual-ports)
Please sort lexicographically.
[...]
> +(define %default-options
> + (let ((introduction (channel-introduction %default-guix-channel)))
> + `((upstream-commit
> + . ,(string->oid (channel-introduction-first-signed-commit introduction)))
nitpick: a bit wide (83 chars)
> + (upstream-signer
> + . ,(openpgp-fingerprint
> + (string-upcase
> + (bytevector->base16-string
> + (channel-introduction-first-commit-signer introduction)))))
> + (upstream-keyring
> + . "keyring"))))
> +
> +(define %usage
> + (format #f (G_ "Usage: guix fork authenticate UPSTREAM COMMIT SIGNER [OPTIONS...]
> +Authenticate a fork of Guix, using COMMIT/SIGNER as the fork introduction.
> +
> +First, authenticate new commits from UPSTREAM, using Guix's default
> +introduction. Then authenticate the remaining commits using the fork
> +introduction.
> +
> + -r, --repository=DIRECTORY
> + Authenticate the Git repository in DIRECTORY
> +
> + --upstream-commit=COMMIT
> + --upstream-signer=SIGNER
> + Use COMMIT/SIGNER as the introduction for upstream
> + Guix, overriding the default values
> + ~a
> + /~a
> + (Guix's default introduction).
> +
> + -k, --keyring=REFERENCE
> + load keyring for fork commits from REFERENCE, a Git
> + branch (default \"keyring\")
> + --upstream-keyring=REFERENCE
> + load keyring for upstream commits from REFERENCE, a
> + Git branch (default \"keyring\")
> + --end=COMMIT authenticate fork commits up to COMMIT
> + --cache-key=KEY cache authenticated commits under KEY
> + --historical-authorizations=FILE
> + read historical authorizations from FILE
> + --stats Display commit signing statistics upon completion
> +
> + -h, --help display this help and exit
> + -V, --version display version information and exit
> +")
> + (assoc-ref %default-options 'upstream-commit)
> + (assoc-ref %default-options 'upstream-signer)))
> +
> +(define (show-help)
> + (display %usage)
> + (newline)
> + (show-bug-report-information))
> +
> +(define (missing-arguments)
> + (leave (G_ "wrong number of arguments; \
> +required UPSTREAM, COMMIT and SIGNER~%")))
> +
> +\f
> +;;;
> +;;; Helper prodecures.
> +;;;
> +
> +(define (fork-config-value repository key)
> + "Return the config value associated with KEY in the
> +'guix.fork-authentication' namespace in REPOSITORY, or #f if no such config
> +was found."
> + (let* ((config (repository-config repository))
> + (branch (repository-current-branch repository)))
> + (catch 'git-error
> + (lambda ()
> + (config-entry-value
> + (config-get-entry config
> + (string-append "guix.fork-authentication."
> + key))))
> + (const #f))))
> +
> +(define (fork-configured-introduction repository)
> + "Return three values: the upstream branch name, introductory commit, and
> +signer fingerprint (strings) for this fork, as configured in REPOSITORY.
> +Error out if any were missing."
s/were/are/
> + (let* ((upstream-branch (fork-config-value repository "upstream-branch"))
> + (commit (fork-config-value repository "introduction-commit"))
> + (signer (fork-config-value repository "introduction-signer")))
> + (unless (and upstream-branch commit signer)
> + (leave (G_ "fork information in .git/config is incomplete;
> +missing at least one of
> +introduction-commit, introduction-signer, upstream-branch
> +under [guix \"fork-authentication\"]")))
> + (values upstream-branch commit signer)))
> +
> +(define (fork-configured-keyring-reference repository)
> + "Return the keyring reference configured in REPOSITORY or #f if missing."
> + (fork-config-value repository "keyring"))
> +
> +(define (fork-configured? repository)
> + "Return true if REPOSITORY already contains fork introduction info in its
> +'config' file."
> + (and (fork-config-value repository "upstream-branch")
> + (fork-config-value repository "introduction-commit")
> + (fork-config-value repository "introduction-signer")))
> +
> +(define* (record-fork-configuration
> + repository
> + #:key commit signer upstream-branch keyring-reference)
nitpick: I'd leave the first arg on the same line, and put the keywords
on separate lines below.
> + "Record COMMIT, SIGNER, UPSTREAM-BRANCH and KEYRING-REFERENCE in the
> +'config' file of REPOSITORY."
Should it say, .git/config file, for extra clarity?
> + (define config
> + (repository-config repository))
> +
> + ;; Guile-Git < 0.7.0 lacks 'set-config-string'.
> + (if (module-defined? (resolve-interface '(git)) 'set-config-string)
> + (begin
> + (set-config-string config "guix.fork-authentication.introduction-commit"
> + commit)
> + (set-config-string config "guix.fork-authentication.introduction-signer"
> + signer)
> + (set-config-string config "guix.fork-authentication.upstream-branch"
> + upstream-branch)
> + (set-config-string config "guix.fork-authentication.keyring"
> + keyring-reference)
> + (info (G_ "introduction, upstream branch and keyring recorded \
> +in repository configuration file~%")))
> + (warning (G_ "could not record introduction and keyring configuration\
> + (Guile-Git too old?)~%"))))
That should be an error, not a warning, no? Unless you think it's not
critical to what this tool is trying to achieve.
> +
> +(define (guix-fork-authenticate . args)
> + (define options
> + (parse-command-line args %options (list %default-options)
> + #:build-options? #f))
> +
> + (define (command-line-arguments lst)
> + (reverse (filter-map (match-lambda
> + (('argument . arg) arg)
> + (_ #f))
> + lst)))
> +
> + (define (make-reporter start-commit end-commit commits)
> + (format (current-error-port)
> + (G_ "Authenticating commits ~a to ~a (~h new \
> +commits)...~%")
> + (commit-short-id start-commit)
> + (commit-short-id end-commit)
> + (length commits))
> + (if (isatty? (current-error-port))
> + (progress-reporter/bar (length commits))
> + progress-reporter/silent))
> +
> + (with-error-handling
> + (with-git-error-handling
> + ;; TODO: BUG: it doesn't recognize '~' in paths
> + ;; How to do 'realpath' in Guile?
It doesn't exist yet, as far as I know. We have readlink* which
recursively expands links (guix utils).
> + (let* ((repository (repository-open (or (assoc-ref options 'directory)
> + (repository-discover "."))))
> + (upstream commit signer (match (command-line-arguments options)
Perhaps break the line here to reduce horizontal indent.
> + ((upstream commit signer)
> + (values
> + (branch-lookup repository upstream)
> + (string->oid commit)
> + (openpgp-fingerprint* signer)))
> + (()
> + (receive (upstream commit signer)
> + (fork-configured-introduction repository)
> + (values
> + (branch-lookup repository upstream)
> + (string->oid commit)
> + (openpgp-fingerprint* signer))))
> + (_
> + (missing-arguments))))
Hm, I've looked at argument-handler again, and I guess it could be used,
though I'm not sure it'd simplify things by much. Give it a try, if you want!
> + (upstream-commit (assoc-ref options 'upstream-commit))
> + (upstream-signer (assoc-ref options 'upstream-signer))
> + (history (match (assoc-ref options 'historical-authorizations)
> + (#f '())
> + (file (call-with-input-file file
> + read-authorizations))))
> + (keyring (or (assoc-ref options 'keyring-reference)
> + (fork-configured-keyring-reference repository)
> + "keyring"))
> + (upstream-keyring (assoc-ref options 'upstream-keyring))
> + (end (match (assoc-ref options 'end-commit)
> + (#f (reference-target
> + (repository-head repository)))
> + (oid oid)))
> + (upstream-end (match (assoc-ref options 'upstream-end-commit)
> + (#f
> + (reference-target upstream))
> + (oid oid)))
> + (cache-key (or (assoc-ref options 'cache-key)
> + (repository-cache-key repository)))
> + (show-stats? (assoc-ref options 'show-stats?)))
> +
> + (define upstream-authentication-args
> + (filter identity
> + (list
> + (oid->string upstream-commit)
> + (bytevector->base16-string upstream-signer)
> + (string-append "--repository="
> + (repository-directory repository))
> + (string-append "--end="
> + (oid->string upstream-end))
> + (and upstream-keyring
> + (string-append "--keyring="
> + upstream-keyring))
> + (and show-stats? "--stats"))))
> +
> + (info (G_ "calling `guix git authenticate` for branch ~a...~%")
> + (branch-name upstream))
> +
> + (apply run-guix-command 'git "authenticate"
> + upstream-authentication-args)
> +
> + (define fork-stats
> + (authenticate-repository
> + repository commit signer
> + #:end end
> + #:keyring-reference keyring
> + #:historical-authorizations history
> + #:cache-key cache-key
> + #:make-reporter make-reporter))
> +
> + (unless (fork-configured? repository)
> + (record-fork-configuration repository
> + #:commit (oid->string commit)
> + #:signer (bytevector->base16-string signer)
> + #:upstream-branch (branch-name upstream)
> + #:keyring-reference keyring))
> +
> + (when (and show-stats? (not (null? fork-stats)))
> + (show-authentication-stats fork-stats))
> +
> + (info (G_ "successfully authenticated commit ~a~%")
> + (oid->string end))))))
The rest LGTM, from a cursory review.
--
Thanks,
Maxim
^ permalink raw reply [flat|nested] 36+ messages in thread
* [bug#75981] [PATCH (WIP) v1.5 3/4] Add 'guix fork update'.
2025-02-01 11:43 ` [bug#75981] [PATCH (WIP) v1.5 3/4] Add 'guix fork update' 45mg
@ 2025-02-02 16:21 ` Maxim Cournoyer
2025-02-02 18:23 ` Liliana Marie Prikler
0 siblings, 1 reply; 36+ messages in thread
From: Maxim Cournoyer @ 2025-02-02 16:21 UTC (permalink / raw)
To: 45mg
Cc: Josselin Poiret, Nicolas Graves, Simon Tournier, Mathieu Othacehe,
Tomas Volf, Tobias Geerinckx-Rice, Liliana Marie Prikler, 75981,
Ricardo Wurmus, Christopher Baines, Attila Lendvai,
Ludovic Courtès
Hi,
45mg <45mg.writes@gmail.com> writes:
> * guix/scripts/fork/update.scm: New file.
> * Makefile.am (MODULES): Add the new file.
Or, "Register it."
> * guix/scripts/fork.scm
> (show-help): Mention new command.
> (%sub-commands): Add new command.
OK.
[...]
> +(define %options
> + ;; Specifications of the command-line options.
> + (list (option '(#\h "help") #f #f
> + (lambda args
> + (show-help)
> + (exit 0)))
> + (option '(#\V "version") #f #f
> + (lambda args
> + (show-version-and-exit "guix fork create")))
> +
> + (option '( "fork-branch") #t #f
Extraneous space in list.
> + (lambda (opt name arg result)
> + (alist-cons 'fork-branch-name arg result)))
> + (option '(#\r "repository") #t #f
> + (lambda (opt name arg result)
> + (alist-cons 'directory arg result)))))
> +
> +(define %default-options
> + '())
> +
> +(define %usage
> + (G_ "Usage: guix fork update [OPTIONS...]
> +Pull into this Guix fork's configured upstream branch, then apply new commits
> +onto the current branch.
I'd reword the beginning to "Pull into this Guix fork its configured
upstream branch [...]"
> +
> + -r, --repository=DIRECTORY
> + Act in the Git repository in DIRECTORY
Maybe, "Work on the Git repository in DIRECTORY"
> + --fork-branch=BRANCH
> + Apply new commits onto BRANCH instead of the current
> + branch
> +
> + -h, --help display this help and exit
> + -V, --version display version information and exit
> +"))
> +
> +(define (show-help)
> + (display %usage)
> + (newline)
> + (show-bug-report-information))
> +
> +(define (missing-arguments)
> + (leave (G_ "wrong number of arguments; \
> +required ~%")))
> +
> +\f
> +;;;
> +;;; Entry point.
> +;;;
> +
> +(define (guix-fork-update . args)
> +
> + (define options
> + (parse-command-line args %options (list %default-options)
> + #:build-options? #f))
> +
> + (define (command-line-arguments lst)
> + (reverse (filter-map (match-lambda
> + (('argument . arg) arg)
> + (_ #f))
> + lst)))
> +
> + (define-syntax invoke-git
> + (lambda (x)
> + (syntax-case x ()
> + ((_ args ...)
> + #`(invoke "git" "-C" #,(datum->syntax x 'directory) args ...)))))
> +
> + (define-syntax invoke-git/stdout
> + (lambda (x)
> + (syntax-case x ()
> + ((_ args ...)
> + #`(string-trim-right
> + (invoke/stdout "git" "-C" #,(datum->syntax x 'directory) args ...))))))
> +
> + (with-error-handling
> + (let* ((directory (or (assoc-ref options 'directory) "."))
> + (current-branch-name (invoke-git/stdout
> + "branch"
> + "--show-current"))
> + (current-head-location (invoke-git/stdout
> + "rev-parse"
> + "HEAD"))
> + (fork-branch-name (or (assoc-ref options 'fork-branch-name)
> + (if (string= current-branch-name "")
> + (leave (G_ "no current branch and --fork-branch not given"))
Too wide. You can always break a string with a \ escape.
> + current-branch-name)))
> +
> + (repository (repository-open directory))
> + (upstream-branch-name introduction-commit introduction-signer
> + (if (fork-configured? repository)
> + (fork-configured-introduction
> + (repository-open directory))
> + (leave (G_ "fork not fully configured.
> +(Did you remember to run `guix fork authenticate` first?)%~"))))
'leave' prints errors, which conventionally should be brief and not
complete sentence. I think you could get a nicer result by using a
compound condition combining a &message and &fix-hint conditions; which
the `with-error-handling' handler will correcly format with colors and
all.
> + (upstream-branch-commit
> + (invoke-git/stdout "rev-parse" upstream-branch-name))
> + (new-upstream-branch-commit "")
> + (config (repository-config repository))
> + (signing-key
> + (or
> + (catch 'git-error
> + (lambda ()
> + (config-entry-value
> + (config-get-entry config "user.signingkey")))
> + (const #f))
> + (begin
> + (info (G_ "user.signingkey not set for this repository.~%"))
> + (info (G_ "Will attempt to sign commits with fork introduction key.~%"))
Max width busted :-)
> + introduction-signer))))
> +
> + (info (G_ "Pulling into '~a'...~%") upstream-branch-name)
> + (invoke-git "switch" upstream-branch-name)
> + (invoke-git "pull")
> + (set! new-upstream-branch-commit
> + (invoke-git/stdout "rev-parse" upstream-branch-name))
I think you can use (define new-upstream-branch-commit ...) and avoid
its let-bound variable (set to the empty string).
> +
> + (info (G_ "Rebasing commits from '~a' to '~a' onto fork branch '~a'...~%")
> + upstream-branch-commit
> + new-upstream-branch-commit
> + fork-branch-name)
> + (invoke-git "rebase" "--rebase-merges"
> + (string-append "--gpg-sign=" signing-key)
> + fork-branch-name new-upstream-branch-commit)
> +
> + (info (G_ "Resetting fork branch '~a' to latest rebased commit...~%")
> + fork-branch-name)
> + (invoke-git "branch" "--force" fork-branch-name "HEAD")
> +
> + (invoke-git "checkout" (or current-branch-name current-head-location))
> +
> + (info (G_ "Successfully updated Guix fork in ~a~%")
> + directory))))
Phew! LGTM. So the idea is to avoid rewriting the fork's introductory
commit and instead rewriting (rebasing) the Guix upstream commits on
top, which will resign them with the fork's authorized key, IIUC?
That's clever, but personally I much prefer to keep any work I've done
*rebased* on upstream so they are easily (re-)submitted, and it's clear
what extra work my fork has. Seems like a good way for "forks" to hide
potentially bad commits hidden under thousands of rust commits,
obscuring them.
I think Liliana had that remark as well in the associated issue.
--
Thanks,
Maxim
^ permalink raw reply [flat|nested] 36+ messages in thread
* [bug#75981] [PATCH (WIP) v1.5 3/4] Add 'guix fork update'.
2025-02-02 16:21 ` Maxim Cournoyer
@ 2025-02-02 18:23 ` Liliana Marie Prikler
2025-02-02 18:24 ` Liliana Marie Prikler
0 siblings, 1 reply; 36+ messages in thread
From: Liliana Marie Prikler @ 2025-02-02 18:23 UTC (permalink / raw)
To: Maxim Cournoyer, 45mg
Cc: Josselin Poiret, Nicolas Graves, Simon Tournier, Mathieu Othacehe,
Tomas Volf, Tobias Geerinckx-Rice, 75981, Ricardo Wurmus,
Christopher Baines, Attila Lendvai, Ludovic Courtès
Am Montag, dem 03.02.2025 um 01:21 +0900 schrieb Maxim Cournoyer:
> So the idea is to avoid rewriting the fork's introductory
> commit and instead rewriting (rebasing) the Guix upstream commits on
> top, which will resign them with the fork's authorized key, IIUC?
>
> That's clever, but personally I much prefer to keep any work I've
> done *rebased* on upstream so they are easily (re-)submitted, and
> it's clear what extra work my fork has. Seems like a good way for
> "forks" to hide potentially bad commits hidden under thousands of
> rust commits, obscuring them.
>
> I think Liliana had that remark as well in the associated issue.
I did remark that, yet :)
The problem with rebasing on Guix is that you will have to update the
introduction on each rebase (or indeed use an unauthenticated fork).
If you do record the introduction, say, in your own channels.scm, `guix
pull` will break, which 45mg wants to avoid.
As you wrote in your first message, it appears somewhat counter-
productive to offer `guix fork` as a means of authoring such long-lived
forks, but sentiments aside, that's precisely the goal of this series.
Cheers
^ permalink raw reply [flat|nested] 36+ messages in thread
* [bug#75981] [PATCH (WIP) v1.5 3/4] Add 'guix fork update'.
2025-02-02 18:23 ` Liliana Marie Prikler
@ 2025-02-02 18:24 ` Liliana Marie Prikler
0 siblings, 0 replies; 36+ messages in thread
From: Liliana Marie Prikler @ 2025-02-02 18:24 UTC (permalink / raw)
To: Maxim Cournoyer, 45mg
Cc: Josselin Poiret, Nicolas Graves, Simon Tournier, Mathieu Othacehe,
Tomas Volf, Tobias Geerinckx-Rice, 75981, Ricardo Wurmus,
Christopher Baines, Attila Lendvai, Ludovic Courtès
Am Sonntag, dem 02.02.2025 um 19:23 +0100 schrieb Liliana Marie
Prikler:
> > I think Liliana had that remark as well in the associated issue.
> I did remark that, yet :)
s/yet/yes/
^ permalink raw reply [flat|nested] 36+ messages in thread
* [bug#75981] [PATCH (WIP) v1.5 1/4] Add 'guix fork create'.
2025-02-02 15:01 ` Maxim Cournoyer
@ 2025-02-02 20:56 ` Attila Lendvai
2025-02-02 22:24 ` Simon Streit
1 sibling, 0 replies; 36+ messages in thread
From: Attila Lendvai @ 2025-02-02 20:56 UTC (permalink / raw)
To: Maxim Cournoyer
Cc: Josselin Poiret, Tobias Geerinckx-Rice, Nicolas Graves,
Simon Tournier, Mathieu Othacehe, Tomas Volf, 45mg,
Liliana Marie Prikler, 75981, Ricardo Wurmus, Christopher Baines,
Ludovic Courtès
> My first thought was similar to Liliana's reply in the other
> issue thread: putting lots of energy into making it convenient to fork
> Guix instead of contributing to the review process (described as slow
> and erratic, which appears to be the motivation here), appears
> counter-productive.
FWIW, i have long-lived patches that will never be incorporated into guix proper. some are simply kludges that enable me to do proceed, while some others are rejected by the maintainers.
none of the above is solved by a better review process.
for the curious, here are the patches that i'm currently dragging along guix HEAD:
https://codeberg.org/attila-lendvai-patches/guix/commits/branch/attila
--
• attila lendvai
• PGP: 963F 5D5F 45C7 DFCD 0A39
--
“To eliminate statism is not to physically subdue the rulers, but to mentally liberate the ruled.”
— Jakub Bożydar Wiśniewski
^ permalink raw reply [flat|nested] 36+ messages in thread
* [bug#75981] [PATCH (WIP) v1.5 1/4] Add 'guix fork create'.
2025-02-02 15:01 ` Maxim Cournoyer
2025-02-02 20:56 ` Attila Lendvai
@ 2025-02-02 22:24 ` Simon Streit
2025-02-04 5:38 ` Maxim Cournoyer
1 sibling, 1 reply; 36+ messages in thread
From: Simon Streit @ 2025-02-02 22:24 UTC (permalink / raw)
To: Maxim Cournoyer
Cc: Josselin Poiret, Tobias Geerinckx-Rice, Nicolas Graves,
Simon Tournier, Mathieu Othacehe, Tomas Volf, 45mg,
Liliana Marie Prikler, 75981, Ricardo Wurmus, Christopher Baines,
Attila Lendvai, Ludovic Courtès
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 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
^ permalink raw reply [flat|nested] 36+ messages in thread
* [bug#75981] [PATCH (WIP) v1.5 4/4] Document 'guix fork'.
2025-02-01 11:43 ` [bug#75981] [PATCH (WIP) v1.5 4/4] Document 'guix fork' 45mg
@ 2025-02-03 1:14 ` Maxim Cournoyer
0 siblings, 0 replies; 36+ messages in thread
From: Maxim Cournoyer @ 2025-02-03 1:14 UTC (permalink / raw)
To: 45mg
Cc: Nicolas Graves, Tomas Volf, Liliana Marie Prikler, 75981,
Ricardo Wurmus, Attila Lendvai, Ludovic Courtès
Hello!
45mg <45mg.writes@gmail.com> writes:
> * doc/guix.texi (Invoking guix fork): New node.
> * doc/contributing.texi (Using Your Own Patches): New node.
>
> Change-Id: I06240f0fe8d1fe39f27130a72f5d0d92949c99da
> ---
> doc/contributing.texi | 50 ++++++++++++++
> doc/guix.texi | 150 ++++++++++++++++++++++++++++++++++++++++++
> 2 files changed, 200 insertions(+)
>
> diff --git a/doc/contributing.texi b/doc/contributing.texi
> index c94ae940fa..bd4fd6c2ac 100644
> --- a/doc/contributing.texi
> +++ b/doc/contributing.texi
> @@ -35,6 +35,7 @@ Contributing
> * Making Decisions:: Collectively choosing the way forward.
> * Commit Access:: Pushing to the official repository.
> * Reviewing the Work of Others:: Some guidelines for sharing reviews.
> +* Using Your Own Patches:: Using your own work before it's accepted.
> * Updating the Guix Package:: Updating the Guix package definition.
> * Deprecation Policy:: Commitments and tools for deprecation.
> * Writing Documentation:: Improving documentation in GNU Guix.
> @@ -3095,6 +3096,55 @@ Reviewing the Work of Others
> have reviewed more easily by adding a @code{reviewed-looks-good} usertag
> for the @code{guix} user (@pxref{Debbugs Usertags}).
>
> +@node Using Your Own Patches
> +@section Using Your Own Patches
> +
> +If you've taken the time to contribute code to Guix, chances are that
> +you want the changes you've made to be reflected in your own Guix
> +installation as soon as possible. Maybe you've added a package you want,
> +and you want to start using it @emph{right now}. Or you've fixed a bug
> +that affects you, and you want it to @emph{go away}.
Eh :-). Please use double space between the sentences in doc and
comments (including doc strings); that is a GNU convention we follow,
and it makes sentence separation unambiguous, allowing editors such as
Emacs to navigate between sentences.
> +As described in the preceding sections, all contributions to Guix first
> +go through a review process to ensure code quality. Sometimes, this can
> +take longer than one would like. Ideally, the pace of the review process
> +should not prevent you from benefiting from your own work.
> +
> +One way to work around this issue is to create an additional channel of
> +your own (@pxref{Creating a Channel}), and add your code to it. For
> +certain kinds of contributions, such as adding a new package, this is
> +fairly straightforward - simply copy your new package definition(s) into
Use triple hyphen for a em dash (longer variant), as described in info
'(texinfo) Conventions':
Use three hyphens in a row, ‘---’, to produce a long dash--like
this (called an “em dash”), used for punctuation in sentences. Use
two hyphens, ‘--’, to produce a medium dash (called an “en dash”),
used primarily for numeric ranges, as in "June 25-26". Use a
single hyphen, ‘-’, to produce a standard hyphen used in compound
words.
> +a new file in the channel, and remove them when your contribution is
> +accepted.
> +
> +However, there may be cases where this is not convenient. Certain kinds
> +of changes, such as those that need to modify existing Guix internals,
> +may be more challenging to incorporate into a channel. Moreoever, the
s/Moveoever/Moreover/
> +more substantial your contribution is, the more work it will be to do
> +so.
> +
> +@cindex fork, of Guix
> +For such cases, there is another option. Recall that the patch series
> +that you sent (@pxref{Sending a Patch Series}) was created from a one or
s/a one/one/
> +more commits on a checkout of the Guix repository (@pxref{Building from
> +Git}). You could simply specify this repository (referred to as your
> +`Guix fork', or simply `fork', from here onwards), and its relevant
> +branch, as your `@code{guix}' channel (@pxref{Using a Custom Guix
> +Channel}). Now `@code{guix pull}' will fetch your new commits, and
It'd be more correct to use @samp{guix pull}, as that is not code (it's
also not a @command per Texinfo, as these should only be used with the
name of the command, without arguments).
> +you'll see the changes you made reflected in your Guix installation!
> +
> +However, there's a potential complication to this approach - the issue
> +of authentication (@pxref{Channel Authentication}). If your fork only
> +exists on your local filesystem (a `local fork'), then you probably
The chosen convention is 'file system' in Guix, as two words.
> +don't need to worry about this, and can pull without authentication
> +(@pxref{Invoking guix pull}). But other situations, such as a remotely
> +hosted fork, may make it important for your fork to be authenticated, in
> +the same way that all channels are expected to be.
> +
> +Guix provides a @command{guix fork} command in order to simplify and
> +automate many details of creating and managing and authenticated
s/and/an/
> +fork. For more information, @pxref{Invoking guix fork}.
This should be @ref, since not used inside parentheses, and used at the
end of a sentence (info '(texinfo) Cross Reference Commands').
> @node Updating the Guix Package
> @section Updating the Guix Package
>
> diff --git a/doc/guix.texi b/doc/guix.texi
> index b1b6d98e74..bbb5666d0a 100644
> --- a/doc/guix.texi
> +++ b/doc/guix.texi
> @@ -311,6 +311,7 @@ Top
> * Invoking guix pack:: Creating software bundles.
> * The GCC toolchain:: Working with languages supported by GCC.
> * Invoking guix git authenticate:: Authenticating Git repositories.
> +* Invoking guix fork:: Creating and managing authenticated forks of Guix.
>
> Programming Interface
>
> @@ -5930,6 +5931,7 @@ Development
> * Invoking guix pack:: Creating software bundles.
> * The GCC toolchain:: Working with languages supported by GCC.
> * Invoking guix git authenticate:: Authenticating Git repositories.
> +* Invoking guix fork:: Creating and managing authenticated forks of Guix.
> @end menu
>
> @node Invoking guix shell
> @@ -7534,6 +7536,154 @@ Invoking guix git authenticate
> @end table
>
>
> +@node Invoking guix fork
> +@section Invoking @command{guix fork}
> +
> +@cindex @command{guix fork}
> +
> +The @command{guix fork} command provides the means to quickly set up,
> +authenticate, and keep up-to-date an authenticated fork of Guix. For
keep up to date, without hyphens (not a compound adjective here; c.f.:
https://idioms.thefreedictionary.com/keep+up+to+date).
> +more information on authentication of a Guix checkout, @pxref{Invoking
> +guix git authenticate}.
s/@pxref/@ref/
> +Its syntax is:
> +
> +guix fork ACTION ARGS...
> +
> +ACTION specifies the fork-related action to perform. Currently, the
> +following values are supported:
> +
> +@table @code
> +@item create SIGNING_KEY [DIRECTORY OPTIONS...]
> +Create a fork of Guix in DIRECTORY, using SIGNING_KEY to sign the introductory
> +commit.
> +DIRECTORY defaults to ./guix.
> +
> +First, clone Guix into DIRECTORY, unless @code{--use-existing} is
> +given.
is given, in which case an existing Git checkout is expected to already
exist in DIRECTORY.
>Then, add SIGNING_KEY to the `@code{keyring}' branch of the
> +repository. Finally, create a new `@code{fork}' branch based starting
> +from the default branch, whose initial commit authorizes SIGNING_KEY
s/starting from/based on/
> +alone (by adding it to @file{.guix-authorizations}) and is signed by it.
to the @file{.guix-authorizations} file
> +
> +The new `@code{fork}' branch is intended to mirror upstream
> +Guix. Updating the fork amounts to applying all new commits to it (see
> +the `@code{update}' command below for further explanation). You can work
/further explanation/more details/
> +on patches in branches based off of this one, in much the same way as
I'd use 's/based off of/based on/'
> +you would base them on Guix's default branch - every commit from the
> +latter will be present in the former.
> +
> +To @command{guix pull} your changes, you could create a `build' branch
s/@command/@samp/ and s/`build'/``build''/
Double quoting is to be used sparringly, using `` '' in Texinfo.
> +starting from the initial fork commit, onto which you can cherry-pick or
> +rebase commits from patch branches. This branch can then be specified
> +for the `@code{guix}' channel (@pxref{Using a Custom Guix Channel}).
Remove the ` ' quotes.
> +Updating this channel can be done by merging the `@code{fork}' branch
> +into it.
Ditto.
> +OPTIONS can be one or more of the following:
> +
> +@table @code
> +@item --use-existing
> +Use existing clone of Guix in DIRECTORY. This is useful if you've
> +already created commits for a patch series (@pxref{Using Your Own
> +Patches}). However, all commits to the default branch, as well as any
> +branches that may be merged into it in the future, must have been signed
> +with an authorized key; otherwise, authentication will fail later.
> +@item --upstream=URI
> +The repository to clone from. This defaults to the default URL for the
> +Guix repository.
> +@item --channel-url=URI
> +Optional URI, which if given, will be used to replace the channel URL.
> +Furthermore, the existing `origin' remote (which tracks
s/`origin'/@code{remote}/
> +`@code{upstream}') is renamed to `upstream', and a new `origin' remote
Use @code instead of ` '. Do not use both together.
> +is created to track URI.
> +@item --git-parameter PARAMETER
> +Specify configuration PARAMETER for git, via `-c' option. You can pass
s/`-c'/@samp{-c}/
> +this option multiple times.
> +@end table
> +
> +@cindex authentication, of Guix forks
> +@item authenticate UPSTREAM COMMIT SIGNER [OPTIONS...]
> +Authenticate a Guix fork, using COMMIT and SIGNER as the fork
> +introduction.
> +
> +First, authenticate new commits from UPSTREAM, using Guix's default
> +introduction. Then authenticate the remaining commits using the fork
> +introduction.
> +
> +As with @code{guix git authenticate}, all three of UPSTREAM, COMMIT and
> +SIGNER will be cached in .git/config, so that you don't need to specify
> +them after the first time.
Instead of 'cached', I'd use 'persisted' or 'written', which sounds more
accurate to me.
> +
> +OPTIONS can be one or more of the following:
> +
> +@table @code
> +@item --repository=DIRECTORY
> +@itemx -r DIRECTORY
> +Authenticate the git repository in DIRECTORY, instead of the current
> +directory.
> +@item --upstream-commit=COMMIT
> +@itemx --upstream-signer=SIGNER
> +Use COMMIT/SIGNER as the introduction for upstream
> +Guix, instead of Guix's default channel introduction.
> +@item --keyring=REFERENCE
> +@itemx -k REFERENCE
> +Load keyring for fork commits from REFERENCE, a Git branch (default
> +`@code{keyring}').
Remove quotes.
> +@item --upstream-keyring=REFERENCE
> +Load keyring for upstream commits from REFERENCE, a Git branch (default
> +`@code{keyring}').
Ditto. Perhaps this could be renamed '--keyring-branch', which is more descriptive?
> +@item --end=COMMIT
> +Authenticate fork commits up to COMMIT.
> +@item --upstream-end=COMMIT
> +Authenticate upstream commits up to COMMIT.
> +@item --cache-key=KEY
> +@itemx --historical-authorizations=FILE
> +@itemx --stats
> +Identical to the correponding options in @command{guix git authenticate}
> +(@pxref{Invoking guix git authenticate}).
> +@end table
> +
> +@item update [OPTIONS...]
> +Pull into this Guix fork's configured upstream branch (from running
> +@command{guix fork authenticate}), then apply new commits onto the
> +current branch.
> +
> +This approach may seem less convenient than simply merging the upstream
> +branch into the fork branch. Indeed, it duplicates every upstream commit
> +under a different commit hash, and applying a large number of commits
> +can be slow. However, this is currently the only feasible approach due
> +to the nature of Guix's authentication mechanism. Namely, merge commits
> +can only be authenticated if both their parents are signed by an
> +authorized key, meaning that you can only use the merge workflow if
> +you're authorized to commit to upstream Guix.
Idea for a refinement: detect if the users's key is authorized by
upstream Guix, and use a merge in this situation? Perhaps offer a
switch to force one flow or another, but error out when the user uses
--merge-strategy=merge and their key is not authorized in upstream Guix
(merge-strategy would default to rebase).
> +For mapping commits on the fork branch to their equivalents on the
> +upstream branch, you can use @command{guix fork identify} (see below).
> +
> +OPTIONS can be one or more of the following:
> +
> +@table @code
> +@item --repository=DIRECTORY
> +@itemx -r DIRECTORY
> +Act in the Git repository in DIRECTORY.
> +@item --fork-branch=BRANCH
> +Apply new commits onto BRANCH instead of the current branch.
> +@end table
> +
> +@item identify
> +Coming soon!
> +
> +Given a commit hash from upstream Guix, print its equivalent on the fork
> +branch, or vice versa.
> +This uses the 'Change-Id:' line added to commit messages by Guix's
@samp{Change-Id} git trailer (see 'man git-interpret-trailers').
> +'commit-msg' hook.
I'd use @samp{commit-msg} or @code.
> +The first invocation of this command will be slow, as the entire set of
> +corresponding commits is built up as a hash table, and then
> +cached. Subsequent invocations should be nearly instant.
Apart from my above comment and the double period thing, this LGTM.
It's obvious you've taken a lot of care/effort into producing this.
I'm warming up to the idea.
Thanks for the contribution.
--
Maxim
^ permalink raw reply [flat|nested] 36+ messages in thread
* [bug#75981] [PATCH (WIP) v1.5 1/4] Add 'guix fork create'.
2025-02-01 11:43 ` [bug#75981] [PATCH (WIP) v1.5 1/4] Add 'guix fork create' 45mg
2025-02-02 15:01 ` Maxim Cournoyer
@ 2025-02-03 15:15 ` Simon Tournier
1 sibling, 0 replies; 36+ messages in thread
From: Simon Tournier @ 2025-02-03 15:15 UTC (permalink / raw)
To: 45mg, 75981
Cc: Josselin Poiret, Tobias Geerinckx-Rice, Nicolas Graves,
Mathieu Othacehe, Tomas Volf, 45mg, Liliana Marie Prikler,
Ricardo Wurmus, Christopher Baines, Attila Lendvai,
Ludovic Courtès
Hi,
On Sat, 01 Feb 2025 at 17:13, 45mg <45mg.writes@gmail.com> wrote:
> * guix/scripts/fork.scm, guix/scripts/fork/create.scm: New files.
[...]
> * guix/scripts/git/authenticate.scm
I think this fork “feature” should not be yet another subcommand but
this must be another subsubcommand: ’guix git fork’.
It would make more sense, IMHO.
Cheers,
simon
^ permalink raw reply [flat|nested] 36+ messages in thread
* [bug#75981] [PATCH (WIP) v1.5 1/4] Add 'guix fork create'.
2025-02-02 22:24 ` Simon Streit
@ 2025-02-04 5:38 ` Maxim Cournoyer
0 siblings, 0 replies; 36+ messages in thread
From: Maxim Cournoyer @ 2025-02-04 5:38 UTC (permalink / raw)
To: Simon Streit
Cc: Josselin Poiret, Tobias Geerinckx-Rice, Nicolas Graves,
Simon Tournier, Mathieu Othacehe, Tomas Volf, 45mg,
Liliana Marie Prikler, 75981, Ricardo Wurmus, Christopher Baines,
Attila Lendvai, Ludovic Courtès
Hi Simon,
Simon Streit <simon@netpanic.org> writes:
> 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 ;-).
> I am nowhere close to be a contributor (yet). I simply don’t have time
> and resources to be more active at the moment. At the same time I also
> don’t want to wait for months until certain patches – which have been
> submitted for review – are pushed upstream.
There's no hiding it: reviewing is a (very) time consuming process, and
is currently done by volunteers, so on their own limited time they
probably would rather use to hack on things that personally matter more
to them :-). The more hands we throw at it, the less time individual
reviewers have to spend on it to keep the community happy and running
smoothly.
> I do keep patches running on top of local branches that are constantly
> being re-based from upstream. While time consuming, it seems to be the
> most convenient at the moment.
> I don’t even want maintain a local fork. It is not that I really need
> one. I use it for development, thus many branches are just dead ends
> that are kept for archival reasons. I have a local central repository
> where I usually push my work to be more independent from my devices –
> which is my issue. And here I only recently realised that I can’t even
> push these branches to my central repository any more.
For development, I simply use git checkouts and force-push them around
when I have to, or use './pre-inst-env guix deploy'. It's not as
seamless as simply using 'guix', but it did the job when I needed it. I
feel this feature here caters to more long-term forks that could have
multiple users, thus requiring authentication.
> Then I tried it the other day to set up a modified keyring and
> authenticate with my key and push it to my local repository as described
> in the manual. I failed for some reason and probably missed something.
> This time I felt it: The bar is now seriously high to work on Guix at
> the moment.
I feel perhaps people are trying to replace Git by Guix :-). Or are
operating outside what I'd call 'development', and want some
fancier/better integrated distribution means for Guix as a whole.
> While the authentication mechanism is useful and necessary to prove what
> is from Guix, it defeats the point to use Git as a decentralised tool.
> It should be possible to allow local modifications for personal use,
> also as unauthorised contributors.
>
> I am for it. Including a warning that I am pulling an unauthenticated
> fork.
What do you mean unauthenticated? The point of this feature is to make
authenticated forks easier to setup/work with, so you wouldn't get any
warning, unless I'm missing something.
Thanks for sharing your thoughts.
--
Maxim
^ permalink raw reply [flat|nested] 36+ messages in thread
* [bug#75981] [PATCH (WIP) v1.5 0/4] Add 'guix fork'.
2025-02-01 11:43 ` [bug#75981] [PATCH (WIP) v1.5 " 45mg
` (3 preceding siblings ...)
2025-02-01 11:43 ` [bug#75981] [PATCH (WIP) v1.5 4/4] Document 'guix fork' 45mg
@ 2025-02-05 3:21 ` 45mg
2025-02-06 4:46 ` Maxim Cournoyer
4 siblings, 1 reply; 36+ messages in thread
From: 45mg @ 2025-02-05 3:21 UTC (permalink / raw)
To: 75981
Cc: Nicolas Graves, Maxim Cournoyer, Simon Tournier, Tomas Volf, 45mg,
Liliana Marie Prikler, Ricardo Wurmus, Attila Lendvai,
Simon Streit
Hi all,
First of all, thanks to Maxim for the patch review! There are a lot of
things that I'll need to address in there. And thanks to everyone else
who's replied here. It's encouraging to see that people are paying
attention to my work; I was worried there wouldn't be enough people who
care about this issue.
So far, my approach to Guix stuff in general has been to just dive in
and work on things until they're done. But there's just so much work
here that if I dive in right now, I don't know when I'll resurface. And
I'm way too busy for that right now.
So, rather than doing a typical inline reply to each message in this
thread, I'm going to try to list out all the feedback I've gotten, and
everything that's pending. Then I'll talk about how I plan to work on
it. Feel free to comment on anything here.
I will try to organize the feedback I've gotten so far below:
1. The argument that this doesn't belong upstream. The main arguments
seem to be that we should improve the review process instead, or that
channels are sufficient for anything you'd use a fork for.
2. Feature suggestions:
a. If the user has commit access, then allow them to use a merge
rather than rebase upstream commits.
I hadn't thought of this before because I was thinking entirely
from a non-committer's perspective. But I guess even committers
might want a personal authenticated fork in some cases. As Attila
pointed out, some things may be rejected from upstream and some
may be temporary kludges to get things working until you can
implement a proper solution.
3. Larger code corrections - things I need to actually spend some
thought on. Here's a list:
a. I've used a mix of Guile-Git and just shelling out to the Git CLI.
The idea behind this was to provide transparency about what the
commands are doing. For example, we could implement a `--dry-run`
option for `create` and `update` that will just output the git
commands that will be run rather than executing them.
With that said, however, there are some places where it would
clearly be cleaner to use Guile-Git, and doing so would not make
`--dry-run` output less useful. For example, the `git
symbolic-ref` invocations to get branch names, etc. So I need to
make some judgement calls in that regard.
b. The use of the `#:argument-handler` keyword of
`parse-command-line`. It's not clear to me how this would simplify
things, and I need to put some thought into it.
c. Error handling. Replacing `leave` with proper exceptions (in
`openpgp-fingerprint*` and `guix-fork-update`), handling possible
exceptions (from gpg in `openpgp-fingerprint*`). I'm not familiar
with exceptions, etc. in Guile, and it'll take some time for me to
figure things out.
d. Whether `openpgp-fingerprint*` belongs in guix/channels.scm.
4. Minor code corrections, eg. formatting. These don't really need
further discussion, and I can just address them sequentially as I
work on the v2 of this patch series.
And here are the other things that are still pending:
5. Tests. These will be the first thing to work on, as it will likely
speed up the development feedback loop a lot.
6. Implement `guix fork identify`.
7. Figure out how to make the existing git hooks 'fork-aware' -
currently the post-merge hook runs where it shouldn't and fails,
because it invokes `guix git authenticate` where it should be calling
`guix fork authenticate`.
Now, on to the plan of action.
First of all, let's talk about '1.'. I think I may have addressed this
in the original thread, but going by the responses here, clearly I
didn't do so well enough.
Now, as Maxim pointed out, I will probably need to submit a GCD to get
this merged upstream. I think that would be the best place for me to
state my argument. That way, we can discuss whether this patch series
should be accepted at all, as well as the broader design - items '1.'
and '2.' from the list above - in a separate thread.
Simultaneously, we can use this thread for the concrete implementation -
items '3.' to '7.'.
That way, even when I don't have the time or energy to work on the code,
I can still keep the discussion going, and collect feedback and opinions
for when I do.
Thoughts? I've never had to juggle so much discussion and feedback with
implementation work like this (is this what a software career is going
to feel like?), so I'm open to suggestions here.
Thanks,
45mg
^ permalink raw reply [flat|nested] 36+ messages in thread
* [bug#75981] [PATCH (WIP) v1.5 0/4] Add 'guix fork'.
2025-02-05 3:21 ` [bug#75981] [PATCH (WIP) v1.5 0/4] Add " 45mg
@ 2025-02-06 4:46 ` Maxim Cournoyer
2025-02-06 17:00 ` 45mg
0 siblings, 1 reply; 36+ messages in thread
From: Maxim Cournoyer @ 2025-02-06 4:46 UTC (permalink / raw)
To: 45mg
Cc: Nicolas Graves, Simon Tournier, Tomas Volf, Liliana Marie Prikler,
75981, Ricardo Wurmus, Attila Lendvai, Simon Streit
Hi 45mg,
45mg <45mg.writes@gmail.com> writes:
> Hi all,
>
> First of all, thanks to Maxim for the patch review! There are a lot of
> things that I'll need to address in there. And thanks to everyone else
> who's replied here. It's encouraging to see that people are paying
> attention to my work; I was worried there wouldn't be enough people who
> care about this issue.
[...]
> So, rather than doing a typical inline reply to each message in this
> thread, I'm going to try to list out all the feedback I've gotten, and
> everything that's pending. Then I'll talk about how I plan to work on
> it. Feel free to comment on anything here.
Before you go further, I'd propose you to explore whether the
GUIX_EXTENSIONS_PATH mechanism could work for your new command. If it
does, then that's even nicer, as I think in general it'd be preferable
for something as particular as 'guix fork' to not be advertised as a top
level guix command. A note could be added to the manual pointing to
this extension, perhaps in a subsection of the section documenting
channels, for the rarer cases where this is useful/necessary.
We'd also need to document the GUIX_EXTENSIONS_PATH environment
variable, and some usage guidance (I've never used an extension myself).
--
Thanks,
Maxim
^ permalink raw reply [flat|nested] 36+ messages in thread
* [bug#75981] [PATCH (WIP) v1.5 0/4] Add 'guix fork'.
2025-02-06 4:46 ` Maxim Cournoyer
@ 2025-02-06 17:00 ` 45mg
2025-02-07 10:34 ` Maxim Cournoyer
0 siblings, 1 reply; 36+ messages in thread
From: 45mg @ 2025-02-06 17:00 UTC (permalink / raw)
To: Maxim Cournoyer, Simon Tournier, 45mg
Cc: Nicolas Graves, Simon Tournier, Tomas Volf, Liliana Marie Prikler,
75981, Ricardo Wurmus, Attila Lendvai, Simon Streit
Hi Maxim,
Maxim Cournoyer <maxim.cournoyer@gmail.com> writes:
> Before you go further, I'd propose you to explore whether the
> GUIX_EXTENSIONS_PATH mechanism could work for your new command. If it
> does, then that's even nicer,
If I understand correctly how GUIX_EXTENSIONS_PATH is supposed to work,
then yes, I could use it to get the Guix CLI to find my `fork`
subcommand without it being in upstream Guix. But I'm trying to get it
into upstream Guix, hence this patch series :)
Regarding /why/ I want it upstream - I've started a draft of a GCD in
which I intend to make a comprehensive argument for its inclusion; I
believe it's more useful and important than you'd think, and I hope I'll
be able to convince you and everyone else of this.
(May take me a while to finish the GCD, though.)
> as I think in general it'd be preferable for something as particular
> as 'guix fork' to not be advertised as a top level guix command.
If that's specifically what you're worried about - Simon was of the
opinion (upthread) that I should have made this a subcommand of `guix
git`. So, we'd have `guix git fork create`, `guix git fork update`, etc.
That would mean it wouldn't show up under top-level `guix --help`. WDYT?
Would that work for you?
I chose `guix fork` because AFAIK all our commands so far are at most 3
'verbs' long (eg. `guix system list-generations`), and 4 verbs felt a
bit too much. But I'm flexible on this point.
> A note could be added to the manual pointing to this extension,
> perhaps in a subsection of the section documenting channels, for the
> rarer cases where this is useful/necessary.
I don't really understand what you're proposing. Are you suggesting that
we move guix/scripts/fork.scm and guix/scripts/fork/* to a separate
directory from the other scripts, then ask people who want to use it to
set GUIX_EXTENSIONS_PATH with that directory?
IMO, that'd just make it needlessly difficult to use it. And honestly,
what exactly is the harm in having a lesser-used top-level subcommand?
We have `guix refresh`, which the manual explicitly states is mostly
only relevant for packagers.
> We'd also need to document the GUIX_EXTENSIONS_PATH environment
> variable, and some usage guidance (I've never used an extension myself).
Yup. Actually, this should be done regardless of what happens with my
proposal. I had to search the mailing lists and track down the patch in
which it was implemented [1] just to figure out what it was supposed to do
(and even then I'm not entirely clear).
> --
> Thanks,
> Maxim
[1] https://yhetil.org/guix/20210105101817.7576-1-rekado@elephly.net/
I /think/ this is the one?
^ permalink raw reply [flat|nested] 36+ messages in thread
* [bug#75981] [PATCH (WIP) v1.5 0/4] Add 'guix fork'.
2025-02-06 17:00 ` 45mg
@ 2025-02-07 10:34 ` Maxim Cournoyer
0 siblings, 0 replies; 36+ messages in thread
From: Maxim Cournoyer @ 2025-02-07 10:34 UTC (permalink / raw)
To: 45mg
Cc: Nicolas Graves, Simon Tournier, Tomas Volf, Liliana Marie Prikler,
75981, Ricardo Wurmus, Attila Lendvai, Simon Streit
Hello!
45mg <45mg.writes@gmail.com> writes:
> Hi Maxim,
>
> Maxim Cournoyer <maxim.cournoyer@gmail.com> writes:
>
>> Before you go further, I'd propose you to explore whether the
>> GUIX_EXTENSIONS_PATH mechanism could work for your new command. If it
>> does, then that's even nicer,
>
> If I understand correctly how GUIX_EXTENSIONS_PATH is supposed to work,
> then yes, I could use it to get the Guix CLI to find my `fork`
> subcommand without it being in upstream Guix. But I'm trying to get it
> into upstream Guix, hence this patch series :)
Yes, that's the idea of extensions; they live as a separate
package/project that can be combined with Guix to extend it.
> Regarding /why/ I want it upstream - I've started a draft of a GCD in
> which I intend to make a comprehensive argument for its inclusion; I
> believe it's more useful and important than you'd think, and I hope I'll
> be able to convince you and everyone else of this.
>
> (May take me a while to finish the GCD, though.)
OK, I look forward to read it (and be convinced ;-)).
>> as I think in general it'd be preferable for something as particular
>> as 'guix fork' to not be advertised as a top level guix command.
>
> If that's specifically what you're worried about - Simon was of the
> opinion (upthread) that I should have made this a subcommand of `guix
> git`. So, we'd have `guix git fork create`, `guix git fork update`, etc.
> That would mean it wouldn't show up under top-level `guix --help`. WDYT?
> Would that work for you?
>
> I chose `guix fork` because AFAIK all our commands so far are at most 3
> 'verbs' long (eg. `guix system list-generations`), and 4 verbs felt a
> bit too much. But I'm flexible on this point.
If a Guix extension can do (and I think it should, since IIRC, extending
the available commands of the command line was the original goal of the
mechanism), then that's the best way to add a feature that, in my
opinion, should be the very last resort for users to extend Guix, as it:
1. Obfuscate the actual changes in the fork by rebasing upstream Guix
commits on top (bad for auditing it/security).
2. Can be more easily abused into doing anything nefarious to users
since it can touch any area of the Guix code (duh, it's a fork).
3. Could be easily (?) mistaken as the canonical/better way to
distribute changes made to Guix additions/improvements (instead of
contributing them upstream or via the extension mechanism of the use of
channels), especially if a prominent CLI command exists for it.
>> A note could be added to the manual pointing to this extension,
>> perhaps in a subsection of the section documenting channels, for the
>> rarer cases where this is useful/necessary.
>
> I don't really understand what you're proposing. Are you suggesting that
> we move guix/scripts/fork.scm and guix/scripts/fork/* to a separate
> directory from the other scripts, then ask people who want to use it to
> set GUIX_EXTENSIONS_PATH with that directory?
If this work here becomes a Guix extension, it'd live outside of Guix
itself in its main repository and be added as a package in Guix. For
users, it'd be seemlessly picked up via the GUIX_EXTENSIONS_PATH search
path, which *should* be automatically set in a profile, according to
commit bbc1735be26 ("profiles: Implicitly set GUIX_EXTENSIONS_PATH.")).
> IMO, that'd just make it needlessly difficult to use it. And honestly,
> what exactly is the harm in having a lesser-used top-level subcommand?
> We have `guix refresh`, which the manual explicitly states is mostly
> only relevant for packagers.
Maintenance, crowding the manual, crowding the command line, and the
arguments I've mentioned above.
>> We'd also need to document the GUIX_EXTENSIONS_PATH environment
>> variable, and some usage guidance (I've never used an extension myself).
>
> Yup. Actually, this should be done regardless of what happens with my
> proposal. I had to search the mailing lists and track down the patch in
> which it was implemented [1] just to figure out what it was supposed to do
> (and even then I'm not entirely clear).
I think a good way to document it would be to show an example. There's
already a 'guix-modules' Guix extension package available in Guix.
--8<---------------cut here---------------start------------->8---
$ guix shell guix guix-modules -- guix module --help
Usage: guix module COMMAND [OPTION]...
Provide an "environment module" interface for Guix.
Available commands:
create convert packages to module files
[...]
--8<---------------cut here---------------end--------------->8---
It seems the commit I've referenced above is not enough for the
GUIX_EXTENSIONS_PATH to be present, for some reason, which is why you
need to include the 'guix' package, whose package defines a search path
for it. That's not optimal, but you can see how easy it is to use Guix
extensions.
--
Thanks,
Maxim
^ permalink raw reply [flat|nested] 36+ messages in thread
end of thread, other threads:[~2025-02-07 10:35 UTC | newest]
Thread overview: 36+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2025-01-31 18:09 [bug#75973] [PATCH (WIP) 0/4] Add 'guix fork' 45mg
2025-01-31 18:15 ` bug#75973: close (sent by mistake) 45mg
2025-01-31 18:16 ` [bug#75973] " 45mg
2025-01-31 18:32 ` [bug#75975] [PATCH (WIP) 1/4] Add 'guix fork create' 45mg
2025-01-31 18:32 ` [bug#75975] [PATCH (WIP) 2/4] Add 'guix fork authenticate' 45mg
2025-01-31 18:32 ` [bug#75975] [PATCH (WIP) 3/4] Add 'guix fork update' 45mg
2025-01-31 18:32 ` [bug#75975] [PATCH (WIP) 4/4] Document 'guix fork' 45mg
2025-01-31 20:10 ` [bug#75975] [PATCH (WIP) 0/4] Add " vicvbcun
2025-01-31 21:35 ` 45mg
2025-01-31 20:51 ` Tomas Volf
2025-01-31 21:10 ` [bug#75981] [PATCH (WIP) v1 " 45mg
2025-01-31 21:18 ` [bug#75981] [PATCH (WIP) v1 1/4] Add 'guix fork create' 45mg
2025-01-31 21:18 ` [bug#75981] [PATCH (WIP) v1 2/4] Add 'guix fork authenticate' 45mg
2025-01-31 21:18 ` [bug#75981] [PATCH (WIP) v1 3/4] Add 'guix fork update' 45mg
2025-01-31 21:18 ` [bug#75981] [PATCH (WIP) v1 4/4] Document 'guix fork' 45mg
2025-01-31 21:22 ` [bug#75981] [Tomas Volf] Re: [PATCH (WIP) 0/4] Add " 45mg
2025-02-01 11:43 ` [bug#75981] [PATCH (WIP) v1.5 " 45mg
2025-02-01 11:43 ` [bug#75981] [PATCH (WIP) v1.5 1/4] Add 'guix fork create' 45mg
2025-02-02 15:01 ` Maxim Cournoyer
2025-02-02 20:56 ` Attila Lendvai
2025-02-02 22:24 ` Simon Streit
2025-02-04 5:38 ` Maxim Cournoyer
2025-02-03 15:15 ` Simon Tournier
2025-02-01 11:43 ` [bug#75981] [PATCH (WIP) v1.5 2/4] Add 'guix fork authenticate' 45mg
2025-02-02 15:23 ` Maxim Cournoyer
2025-02-01 11:43 ` [bug#75981] [PATCH (WIP) v1.5 3/4] Add 'guix fork update' 45mg
2025-02-02 16:21 ` Maxim Cournoyer
2025-02-02 18:23 ` Liliana Marie Prikler
2025-02-02 18:24 ` Liliana Marie Prikler
2025-02-01 11:43 ` [bug#75981] [PATCH (WIP) v1.5 4/4] Document 'guix fork' 45mg
2025-02-03 1:14 ` Maxim Cournoyer
2025-02-05 3:21 ` [bug#75981] [PATCH (WIP) v1.5 0/4] Add " 45mg
2025-02-06 4:46 ` Maxim Cournoyer
2025-02-06 17:00 ` 45mg
2025-02-07 10:34 ` Maxim Cournoyer
2025-01-31 21:28 ` bug#75975: [PATCH (WIP) " 45mg
Code repositories for project(s) associated with this public inbox
https://git.savannah.gnu.org/cgit/guix.git
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for read-only IMAP folder(s) and NNTP newsgroup(s).