Merge branch 'master' into staging
[jackhill/guix/guix.git] / guix / channels.scm
CommitLineData
0d39a3b9 1;;; GNU Guix --- Functional package management for GNU
c9fbd407 2;;; Copyright © 2018-2022 Ludovic Courtès <ludo@gnu.org>
af12790b 3;;; Copyright © 2018 Ricardo Wurmus <rekado@elephly.net>
53f21642 4;;; Copyright © 2019 Jan (janneke) Nieuwenhuizen <janneke@gnu.org>
b891f851 5;;; Copyright © 2021 Brice Waegeneire <brice@waegenei.re>
0d39a3b9
LC
6;;;
7;;; This file is part of GNU Guix.
8;;;
9;;; GNU Guix is free software; you can redistribute it and/or modify it
10;;; under the terms of the GNU General Public License as published by
11;;; the Free Software Foundation; either version 3 of the License, or (at
12;;; your option) any later version.
13;;;
14;;; GNU Guix is distributed in the hope that it will be useful, but
15;;; WITHOUT ANY WARRANTY; without even the implied warranty of
16;;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17;;; GNU General Public License for more details.
18;;;
19;;; You should have received a copy of the GNU General Public License
20;;; along with GNU Guix. If not, see <http://www.gnu.org/licenses/>.
21
22(define-module (guix channels)
8ba7fd3c 23 #:use-module (git)
0d39a3b9 24 #:use-module (guix git)
43badf26
LC
25 #:use-module (guix git-authenticate)
26 #:use-module ((guix openpgp)
27 #:select (openpgp-public-key-fingerprint
28 openpgp-format-fingerprint))
29 #:use-module (guix base16)
0d39a3b9
LC
30 #:use-module (guix records)
31 #:use-module (guix gexp)
5fbdc9a5 32 #:use-module (guix modules)
0d39a3b9
LC
33 #:use-module (guix discovery)
34 #:use-module (guix monads)
35 #:use-module (guix profiles)
37c0d458 36 #:use-module (guix packages)
43badf26 37 #:use-module (guix progress)
0d39a3b9 38 #:use-module (guix derivations)
f58f676b 39 #:use-module (guix combinators)
69962ab7 40 #:use-module (guix diagnostics)
8ba7fd3c 41 #:use-module (guix sets)
0d39a3b9
LC
42 #:use-module (guix store)
43 #:use-module (guix i18n)
44 #:use-module (srfi srfi-1)
af12790b 45 #:use-module (srfi srfi-2)
0d39a3b9
LC
46 #:use-module (srfi srfi-9)
47 #:use-module (srfi srfi-11)
9719e8d3 48 #:use-module (srfi srfi-26)
ab6025b5
LC
49 #:use-module (srfi srfi-34)
50 #:use-module (srfi srfi-35)
29009fdb 51 #:autoload (guix describe) (current-channels) ;XXX: circular dep
5fbdc9a5
LC
52 #:autoload (guix self) (whole-package make-config.scm)
53 #:autoload (guix inferior) (gexp->derivation-in-inferior) ;FIXME: circular dep
20507347 54 #:autoload (guix quirks) (%quirks %patches applicable-patch? apply-patch)
43badf26 55 #:use-module (ice-9 format)
0d39a3b9 56 #:use-module (ice-9 match)
ed75bdf3 57 #:use-module (ice-9 vlist)
37c0d458 58 #:use-module ((ice-9 rdelim) #:select (read-string))
43badf26 59 #:use-module ((rnrs bytevectors) #:select (bytevector=?))
0d39a3b9
LC
60 #:export (channel
61 channel?
62 channel-name
63 channel-url
64 channel-branch
65 channel-commit
43badf26 66 channel-introduction
0d39a3b9
LC
67 channel-location
68
43badf26 69 channel-introduction?
8b7d982e
LC
70 make-channel-introduction
71 channel-introduction-first-signed-commit
72 channel-introduction-first-commit-signer
43badf26 73
6577682a
LC
74 openpgp-fingerprint->bytevector
75 openpgp-fingerprint
76
16c7472b 77 %default-guix-channel
0d39a3b9 78 %default-channels
72f749dc 79 guix-channel?
64a07071 80 repository->guix-channel
0d39a3b9
LC
81
82 channel-instance?
83 channel-instance-channel
84 channel-instance-commit
85 channel-instance-checkout
86
43badf26 87 authenticate-channel
0d39a3b9 88 latest-channel-instances
fe5db4eb 89 checkout->channel-instance
030f1367 90 latest-channel-derivation
4399b1cf 91 channel-instance->sexp
c37f38bd 92 channel-instances->manifest
5fbdc9a5 93 %channel-profile-hooks
a7c714d3 94 channel-instances->derivation
872898f7 95 ensure-forward-channel-update
a7c714d3 96
8ba7fd3c 97 profile-channels
1b88b7ba 98 manifest-entry-channel
aedbc5ff 99 sexp->channel
60d72f53 100 channel->code
8ba7fd3c
LC
101
102 channel-news-entry?
103 channel-news-entry-commit
9719e8d3 104 channel-news-entry-tag
8ba7fd3c
LC
105 channel-news-entry-title
106 channel-news-entry-body
107
108 channel-news-for-commit))
0d39a3b9
LC
109
110;;; Commentary:
111;;;
112;;; This module implements "channels." A channel is usually a source of
113;;; package definitions. There's a special channel, the 'guix' channel, that
114;;; provides all of Guix, including its commands and its documentation.
115;;; User-defined channels are expected to typically provide a bunch of .scm
116;;; files meant to be added to the '%package-search-path'.
117;;;
118;;; This module provides tools to fetch and update channels from a Git
119;;; repository and to build them.
120;;;
121;;; Code:
122
123(define-record-type* <channel> channel make-channel
124 channel?
125 (name channel-name)
126 (url channel-url)
127 (branch channel-branch (default "master"))
128 (commit channel-commit (default #f))
43badf26 129 (introduction channel-introduction (default #f))
0d39a3b9
LC
130 (location channel-location
131 (default (current-source-location)) (innate)))
0d39a3b9 132
43badf26
LC
133;; Channel introductions. A "channel introduction" provides a commit/signer
134;; pair that specifies the first commit of the authentication process as well
22a96992
LC
135;; as its signer's fingerprint. Introductions are used to bootstrap trust in
136;; a channel.
43badf26 137(define-record-type <channel-introduction>
22a96992 138 (%make-channel-introduction first-signed-commit first-commit-signer)
43badf26 139 channel-introduction?
22a96992
LC
140 (first-signed-commit channel-introduction-first-signed-commit) ;hex string
141 (first-commit-signer channel-introduction-first-commit-signer)) ;bytevector
43badf26 142
8b7d982e
LC
143(define (make-channel-introduction commit signer)
144 "Return a new channel introduction: COMMIT is the introductory where
145authentication starts, and SIGNER is the OpenPGP fingerprint (a bytevector) of
146the signer of that commit."
22a96992 147 (%make-channel-introduction commit signer))
8b7d982e 148
6577682a
LC
149(define (openpgp-fingerprint->bytevector str)
150 "Convert STR, an OpenPGP fingerprint (hexadecimal string with whitespace),
151to the corresponding bytevector."
152 (base16-string->bytevector
153 (string-downcase (string-filter char-set:hex-digit str))))
154
155(define-syntax openpgp-fingerprint
156 (lambda (s)
157 "Convert STR, an OpenPGP fingerprint (hexadecimal string with whitespace),
158to the corresponding bytevector."
159 (syntax-case s ()
160 ((_ str)
161 (string? (syntax->datum #'str))
162 (openpgp-fingerprint->bytevector (syntax->datum #'str)))
163 ((_ str)
164 #'(openpgp-fingerprint->bytevector str)))))
165
43badf26
LC
166(define %guix-channel-introduction
167 ;; Introduction of the official 'guix channel. The chosen commit is the
168 ;; first one that introduces '.guix-authorizations' on the 'staging'
169 ;; branch that was eventually merged in 'master'. Any branch starting
170 ;; before that commit cannot be merged or it will be rejected by 'guix pull'
171 ;; & co.
172 (make-channel-introduction
173 "9edb3f66fd807b096b48283debdcddccfea34bad" ;2020-05-26
6577682a
LC
174 (openpgp-fingerprint ;mbakke
175 "BBB0 2DDF 2CEA F6A8 0D1D E643 A2A0 6DF2 A33A 54FA")))
43badf26 176
c3f6f564
LC
177(define %default-channel-url
178 ;; URL of the default 'guix' channel.
179 "https://git.savannah.gnu.org/git/guix.git")
180
16c7472b
ZZ
181(define %default-guix-channel
182 (channel
183 (name 'guix)
184 (branch "master")
185 (url %default-channel-url)
186 (introduction %guix-channel-introduction)))
187
0d39a3b9
LC
188(define %default-channels
189 ;; Default list of channels.
16c7472b 190 (list %default-guix-channel))
0d39a3b9
LC
191
192(define (guix-channel? channel)
193 "Return true if CHANNEL is the 'guix' channel."
194 (eq? 'guix (channel-name channel)))
195
c3f6f564
LC
196(define (ensure-default-introduction chan)
197 "If CHAN represents the \"official\" 'guix' channel and lacks an
198introduction, add it."
199 (if (and (guix-channel? chan)
200 (not (channel-introduction chan))
201 (string=? (channel-url chan) %default-channel-url))
202 (channel (inherit chan)
203 (introduction %guix-channel-introduction))
204 chan))
205
64a07071
LC
206(define* (repository->guix-channel directory
207 #:key
208 (introduction %guix-channel-introduction))
209 "Look for a Git repository in DIRECTORY or its ancestors and return a
210channel that uses that repository and the commit HEAD currently points to; use
211INTRODUCTION as the channel's introduction. Return #f if no Git repository
212could be found at DIRECTORY or one of its ancestors."
213 (catch 'git-error
214 (lambda ()
215 (with-repository (repository-discover directory) repository
216 (let* ((head (repository-head repository))
217 (commit (oid->string (reference-target head))))
218 (channel
219 (inherit %default-guix-channel)
220 (url (repository-working-directory repository))
221 (commit commit)
222 (branch (reference-shorthand head))
223 (introduction introduction)))))
224 (const #f)))
225
0d39a3b9
LC
226(define-record-type <channel-instance>
227 (channel-instance channel commit checkout)
228 channel-instance?
229 (channel channel-instance-channel)
230 (commit channel-instance-commit)
231 (checkout channel-instance-checkout))
232
af12790b 233(define-record-type <channel-metadata>
4ae762af 234 (channel-metadata directory dependencies news-file keyring-reference url)
af12790b 235 channel-metadata?
ce5d9ec8 236 (directory channel-metadata-directory) ;string with leading slash
8ba7fd3c 237 (dependencies channel-metadata-dependencies) ;list of <channel>
43badf26 238 (news-file channel-metadata-news-file) ;string | #f
4ae762af
LC
239 (keyring-reference channel-metadata-keyring-reference) ;string
240 (url channel-metadata-url)) ;string | #f
43badf26
LC
241
242(define %default-keyring-reference
243 ;; Default value of the 'keyring-reference' field.
244 "keyring")
af12790b 245
0d39a3b9
LC
246(define (channel-reference channel)
247 "Return the \"reference\" for CHANNEL, an sexp suitable for
248'latest-repository-commit'."
249 (match (channel-commit channel)
250 (#f `(branch . ,(channel-branch channel)))
251 (commit `(commit . ,(channel-commit channel)))))
252
d774c7b1
LC
253(define sexp->channel-introduction
254 (match-lambda
255 (('channel-introduction ('version 0)
256 ('commit commit) ('signer signer)
257 _ ...)
258 (make-channel-introduction commit (openpgp-fingerprint signer)))
259 (x #f)))
260
45b90332
LC
261(define (read-channel-metadata port)
262 "Read from PORT channel metadata in the format expected for the
263'.guix-channel' file. Return a <channel-metadata> record, or raise an error
264if valid metadata could not be read from PORT."
265 (match (read port)
266 (('channel ('version 0) properties ...)
267 (let ((directory (and=> (assoc-ref properties 'directory) first))
8ba7fd3c 268 (dependencies (or (assoc-ref properties 'dependencies) '()))
43badf26 269 (news-file (and=> (assoc-ref properties 'news-file) first))
4ae762af 270 (url (and=> (assoc-ref properties 'url) first))
43badf26
LC
271 (keyring-reference
272 (or (and=> (assoc-ref properties 'keyring-reference) first)
273 %default-keyring-reference)))
45b90332 274 (channel-metadata
8ba7fd3c 275 (cond ((not directory) "/") ;directory
ce5d9ec8
LC
276 ((string-prefix? "/" directory) directory)
277 (else (string-append "/" directory)))
8ba7fd3c 278 (map (lambda (item) ;dependencies
45b90332
LC
279 (let ((get (lambda* (key #:optional default)
280 (or (and=> (assoc-ref item key) first) default))))
281 (and-let* ((name (get 'name))
282 (url (get 'url))
283 (branch (get 'branch "master")))
284 (channel
285 (name name)
286 (branch branch)
287 (url url)
d774c7b1
LC
288 (commit (get 'commit))
289 (introduction (and=> (get 'introduction)
290 sexp->channel-introduction))))))
8ba7fd3c 291 dependencies)
43badf26 292 news-file
4ae762af
LC
293 keyring-reference
294 url)))
45b90332
LC
295 ((and ('channel ('version version) _ ...) sexp)
296 (raise (condition
297 (&message (message "unsupported '.guix-channel' version"))
298 (&error-location
299 (location (source-properties->location
300 (source-properties sexp)))))))
301 (sexp
302 (raise (condition
303 (&message (message "invalid '.guix-channel' file"))
304 (&error-location
305 (location (source-properties->location
306 (source-properties sexp)))))))))
307
53f21642
JN
308(define (read-channel-metadata-from-source source)
309 "Return a channel-metadata record read from channel's SOURCE/.guix-channel
ce5d9ec8
LC
310description file, or return the default channel-metadata record if that file
311doesn't exist."
45b90332
LC
312 (catch 'system-error
313 (lambda ()
314 (call-with-input-file (string-append source "/.guix-channel")
315 read-channel-metadata))
316 (lambda args
317 (if (= ENOENT (system-error-errno args))
4ae762af 318 (channel-metadata "/" '() #f %default-keyring-reference #f)
45b90332
LC
319 (apply throw args)))))
320
321(define (channel-instance-metadata instance)
53f21642 322 "Return a channel-metadata record read from the channel INSTANCE's
ce5d9ec8 323description file or its default value."
53f21642
JN
324 (read-channel-metadata-from-source (channel-instance-checkout instance)))
325
af12790b
RW
326(define (channel-instance-dependencies instance)
327 "Return the list of channels that are declared as dependencies for the given
328channel INSTANCE."
ce5d9ec8 329 (channel-metadata-dependencies (channel-instance-metadata instance)))
af12790b 330
053b10c3
LC
331(define (apply-patches checkout commit patches)
332 "Apply the matching PATCHES to CHECKOUT, modifying files in place. The
333result is unspecified."
334 (let loop ((patches patches))
335 (match patches
336 (() #t)
20507347
LC
337 ((patch rest ...)
338 (when (applicable-patch? patch checkout commit)
339 (apply-patch patch checkout))
053b10c3
LC
340 (loop rest)))))
341
43badf26
LC
342(define commit-short-id
343 (compose (cut string-take <> 7) oid->string commit-id))
344
43badf26
LC
345(define* (authenticate-channel channel checkout commit
346 #:key (keyring-reference-prefix "origin/"))
347 "Authenticate the given COMMIT of CHANNEL, available at CHECKOUT, a
348directory containing a CHANNEL checkout. Raise an error if authentication
349fails."
838f2bdf
LC
350 (define intro
351 (channel-introduction channel))
352
353 (define cache-key
354 (string-append "channels/" (symbol->string (channel-name channel))))
355
356 (define keyring-reference
357 (channel-metadata-keyring-reference
358 (read-channel-metadata-from-source checkout)))
359
360 (define (make-reporter start-commit end-commit commits)
361 (format (current-error-port)
362 (G_ "Authenticating channel '~a', commits ~a to ~a (~h new \
363commits)...~%")
364 (channel-name channel)
365 (commit-short-id start-commit)
366 (commit-short-id end-commit)
367 (length commits))
368
369 (progress-reporter/bar (length commits)))
370
29009fdb
LC
371 (define authentic-commits
372 ;; Consider the currently-used commit of CHANNEL as authentic so
373 ;; authentication can skip it and all its closure.
374 (match (find (lambda (candidate)
375 (eq? (channel-name candidate) (channel-name channel)))
376 (current-channels))
377 (#f '())
378 (channel
379 (if (channel-commit channel)
380 (list (channel-commit channel))
381 '()))))
382
43badf26
LC
383 ;; XXX: Too bad we need to re-open CHECKOUT.
384 (with-repository checkout repository
838f2bdf
LC
385 (authenticate-repository repository
386 (string->oid
387 (channel-introduction-first-signed-commit intro))
388 (channel-introduction-first-commit-signer intro)
389 #:end (string->oid commit)
390 #:keyring-reference
391 (string-append keyring-reference-prefix
392 keyring-reference)
29009fdb 393 #:authentic-commits authentic-commits
838f2bdf
LC
394 #:make-reporter make-reporter
395 #:cache-key cache-key)))
43badf26 396
053b10c3 397(define* (latest-channel-instance store channel
872898f7 398 #:key (patches %patches)
5bafc70d 399 starting-commit
a9eeeaa6 400 (authenticate? #f)
5bafc70d
LC
401 (validate-pull
402 ensure-forward-channel-update))
403 "Return the latest channel instance for CHANNEL. When STARTING-COMMIT is
404true, call VALIDATE-PULL with CHANNEL, STARTING-COMMIT, the target commit, and
a9eeeaa6 405their relation. When AUTHENTICATE? is false, CHANNEL is not authenticated."
053b10c3
LC
406 (define (dot-git? file stat)
407 (and (string=? (basename file) ".git")
408 (eq? 'directory (stat:type stat))))
409
c3f6f564
LC
410 (let-values (((channel)
411 (ensure-default-introduction channel))
412 ((checkout commit relation)
053b10c3 413 (update-cached-checkout (channel-url channel)
872898f7
LC
414 #:ref (channel-reference channel)
415 #:starting-commit starting-commit)))
5bafc70d
LC
416 (when relation
417 (validate-pull channel starting-commit commit relation))
418
a9eeeaa6
LC
419 (if authenticate?
420 (if (channel-introduction channel)
421 (authenticate-channel channel checkout commit)
422 ;; TODO: Warn for all the channels once the authentication interface
423 ;; is public.
424 (when (guix-channel? channel)
d51bfe24
LC
425 (raise (make-compound-condition
426 (formatted-message (G_ "channel '~a' lacks an \
ead5c461 427introduction and cannot be authenticated~%")
d51bfe24
LC
428 (channel-name channel))
429 (condition
430 (&fix-hint
431 (hint (G_ "Add the missing introduction to your
ead5c461
LC
432channels file to address the issue. Alternatively, you can pass
433@option{--disable-authentication}, at the risk of running unauthenticated and
d51bfe24 434thus potentially malicious code."))))))))
a9eeeaa6 435 (warning (G_ "channel authentication disabled~%")))
43badf26 436
053b10c3
LC
437 (when (guix-channel? channel)
438 ;; Apply the relevant subset of PATCHES directly in CHECKOUT. This is
439 ;; safe to do because 'switch-to-ref' eventually does a hard reset.
440 (apply-patches checkout commit patches))
441
442 (let* ((name (url+commit->name (channel-url channel) commit))
443 (checkout (add-to-store store name #t "sha256" checkout
444 #:select? (negate dot-git?))))
5bafc70d 445 (channel-instance channel commit checkout))))
872898f7 446
5bafc70d 447(define (ensure-forward-channel-update channel start commit relation)
872898f7 448 "Raise an error if RELATION is not 'ancestor, meaning that START is not an
5bafc70d 449ancestor of COMMIT, unless CHANNEL specifies a commit.
872898f7
LC
450
451This procedure implements a channel update policy meant to be used as a
452#:validate-pull argument."
453 (match relation
454 ('ancestor #t)
455 ('self #t)
456 (_
9744cc7b
LC
457 (raise (make-compound-condition
458 (condition
459 (&message (message
460 (format #f (G_ "\
872898f7 461aborting update of channel '~a' to commit ~a, which is not a descendant of ~a")
9744cc7b 462 (channel-name channel)
5bafc70d 463 commit start))))
872898f7 464
9744cc7b
LC
465 ;; If the user asked for a specific commit, they might want
466 ;; that to happen nevertheless, so tell them about the
467 ;; relevant 'guix pull' option.
468 (if (channel-commit channel)
469 (condition
470 (&fix-hint
471 (hint (G_ "Use @option{--allow-downgrades} to force
472this downgrade."))))
473 (condition
474 (&fix-hint
475 (hint (G_ "This could indicate that the channel has
872898f7
LC
476been tampered with and is trying to force a roll-back, preventing you from
477getting the latest updates. If you think this is not the case, explicitly
9744cc7b 478allow non-forward updates."))))))))))
872898f7 479
4ae762af
LC
480(define (channel-instance-primary-url instance)
481 "Return the primary URL advertised for INSTANCE, or #f if there is no such
482information."
483 (channel-metadata-url (channel-instance-metadata instance)))
484
872898f7
LC
485(define* (latest-channel-instances store channels
486 #:key
487 (current-channels '())
a9eeeaa6 488 (authenticate? #t)
872898f7
LC
489 (validate-pull
490 ensure-forward-channel-update))
0d39a3b9 491 "Return a list of channel instances corresponding to the latest checkouts of
872898f7
LC
492CHANNELS and the channels on which they depend.
493
a9eeeaa6
LC
494When AUTHENTICATE? is true, authenticate the subset of CHANNELS that has a
495\"channel introduction\".
496
872898f7
LC
497CURRENT-CHANNELS is the list of currently used channels. It is compared
498against the newly-fetched instances of CHANNELS, and VALIDATE-PULL is called
499for each channel update and can choose to emit warnings or raise an error,
500depending on the policy it implements."
af12790b
RW
501 ;; Only process channels that are unique, or that are more specific than a
502 ;; previous channel specification.
503 (define (ignore? channel others)
504 (member channel others
505 (lambda (a b)
506 (and (eq? (channel-name a) (channel-name b))
507 (or (channel-commit b)
508 (not (or (channel-commit a)
509 (channel-commit b))))))))
f58f676b 510
872898f7
LC
511 (define (current-commit name)
512 ;; Return the current commit for channel NAME.
513 (any (lambda (channel)
514 (and (eq? (channel-name channel) name)
515 (channel-commit channel)))
516 current-channels))
517
9b049de8
LC
518 (let loop ((channels channels)
519 (previous-channels '()))
520 ;; Accumulate a list of instances. A list of processed channels is also
521 ;; accumulated to decide on duplicate channel specifications.
522 (define-values (resulting-channels instances)
523 (fold2 (lambda (channel previous-channels instances)
524 (if (ignore? channel previous-channels)
525 (values previous-channels instances)
526 (begin
527 (format (current-error-port)
528 (G_ "Updating channel '~a' from Git repository at '~a'...~%")
529 (channel-name channel)
530 (channel-url channel))
5bafc70d
LC
531 (let* ((current (current-commit (channel-name channel)))
532 (instance
533 (latest-channel-instance store channel
a9eeeaa6
LC
534 #:authenticate?
535 authenticate?
5bafc70d
LC
536 #:validate-pull
537 validate-pull
538 #:starting-commit
539 current)))
4ae762af
LC
540 (when authenticate?
541 ;; CHANNEL is authenticated so we can trust the
542 ;; primary URL advertised in its metadata and warn
543 ;; about possibly stale mirrors.
544 (let ((primary-url (channel-instance-primary-url
545 instance)))
546 (unless (or (not primary-url)
547 (channel-commit channel)
548 (string=? primary-url (channel-url channel)))
549 (warning (G_ "pulled channel '~a' from a mirror \
550of ~a, which might be stale~%")
551 (channel-name channel)
552 primary-url))))
872898f7 553
9b049de8
LC
554 (let-values (((new-instances new-channels)
555 (loop (channel-instance-dependencies instance)
556 previous-channels)))
557 (values (append (cons channel new-channels)
558 previous-channels)
559 (append (cons instance new-instances)
560 instances)))))))
561 previous-channels
562 '() ;instances
563 channels))
f58f676b 564
9b049de8
LC
565 (let ((instance-name (compose channel-name channel-instance-channel)))
566 ;; Remove all earlier channel specifications if they are followed by a
567 ;; more specific one.
568 (values (delete-duplicates instances
569 (lambda (a b)
570 (eq? (instance-name a) (instance-name b))))
571 resulting-channels))))
0d39a3b9 572
fe5db4eb
LC
573(define* (checkout->channel-instance checkout
574 #:key commit
575 (url checkout) (name 'guix))
576 "Return a channel instance for CHECKOUT, which is assumed to be a checkout
577of COMMIT at URL. Use NAME as the channel name."
578 (let* ((commit (or commit (make-string 40 #\0)))
579 (channel (channel (name name)
580 (commit commit)
581 (url url))))
582 (channel-instance channel commit checkout)))
583
0d39a3b9
LC
584(define %self-build-file
585 ;; The file containing code to build Guix. This serves the same purpose as
586 ;; a makefile, and, similarly, is intended to always keep this name.
587 "build-aux/build-self.scm")
588
589(define %pull-version
590 ;; This is the version of the 'guix pull' protocol. It specifies what's
591 ;; expected from %SELF-BUILD-FILE. The initial version ("0") was when we'd
592 ;; place a set of compiled Guile modules in ~/.config/guix/latest.
593 1)
594
acefa740
LC
595(define (standard-module-derivation name source core dependencies)
596 "Return a derivation that builds with CORE, a Guix instance, the Scheme
597modules in SOURCE and that depend on DEPENDENCIES, a list of lowerable
598objects. The assumption is that SOURCE contains package modules to be added
599to '%package-module-path'."
acefa740 600
53f21642 601 (let* ((metadata (read-channel-metadata-from-source source))
ce5d9ec8 602 (directory (channel-metadata-directory metadata)))
53f21642
JN
603
604 (define build
605 ;; This is code that we'll run in CORE, a Guix instance, with its own
606 ;; modules and so on. That way, we make sure these modules are built for
607 ;; the right Guile version, with the right dependencies, and that they get
608 ;; to see the right (gnu packages …) modules.
609 (with-extensions dependencies
610 #~(begin
611 (use-modules (guix build compile)
612 (guix build utils)
613 (srfi srfi-26))
614
615 (define go
616 (string-append #$output "/lib/guile/" (effective-version)
617 "/site-ccache"))
618 (define scm
619 (string-append #$output "/share/guile/site/"
620 (effective-version)))
acefa740 621
a514b4ab
LC
622 (define optimizations-for-level
623 ;; Guile 3.0 provides this procedure but Guile 2.2 didn't.
624 ;; Since this code may be executed by either version, we can't
625 ;; rely on its availability.
626 (or (and=> (false-if-exception
627 (resolve-interface '(system base optimize)))
628 (lambda (iface)
629 (module-ref iface 'optimizations-for-level)))
630 (const '())))
631
632 (define -O1
633 ;; Optimize for package module compilation speed.
634 (optimizations-for-level 1))
635
ce5d9ec8 636 (let* ((subdir #$directory)
53f21642 637 (source (string-append #$source subdir)))
a514b4ab
LC
638 (compile-files source go (find-files source "\\.scm$")
639 #:optimization-options (const -O1))
53f21642
JN
640 (mkdir-p (dirname scm))
641 (symlink (string-append #$source subdir) scm))
acefa740 642
53f21642 643 scm)))
acefa740 644
53f21642 645 (gexp->derivation-in-inferior name build core)))
0d39a3b9 646
37c0d458
LC
647(define* (guile-for-source source #:optional (quirks %quirks))
648 "Return the Guile package to use when building SOURCE or #f if the default
649'%guile-for-build' should be good enough."
650 (let loop ((quirks quirks))
651 (match quirks
652 (()
653 #f)
654 (((predicate . guile) rest ...)
655 (if (predicate source) (guile) (loop rest))))))
656
9db88369
LC
657(define (call-with-guile guile thunk)
658 (lambda (store)
659 (values (parameterize ((%guile-for-build
660 (if guile
661 (package-derivation store guile)
662 (%guile-for-build))))
663 (run-with-store store (thunk)))
664 store)))
665
666(define-syntax-rule (with-guile guile exp ...)
667 "Set GUILE as the '%guile-for-build' parameter for the dynamic extent of
668EXP, a series of monadic expressions."
669 (call-with-guile guile (lambda ()
670 (mbegin %store-monad exp ...))))
671
42a87136
LC
672(define (with-trivial-build-handler mvalue)
673 "Run MVALUE, a monadic value, with a \"trivial\" build handler installed
674that unconditionally resumes the continuation."
675 (lambda (store)
676 (with-build-handler (lambda (continue . _)
677 (continue #t))
678 (values (run-with-store store mvalue)
679 store))))
680
316fc2ac 681(define* (build-from-source instance
34985fb6 682 #:key core verbose? (dependencies '()) system)
316fc2ac
LC
683 "Return a derivation to build Guix from INSTANCE, using the self-build
684script contained therein. When CORE is true, build package modules under
34985fb6
CB
685SOURCE using CORE, an instance of Guix. By default, build for the current
686system, or SYSTEM if specified."
316fc2ac
LC
687 (define name
688 (symbol->string
689 (channel-name (channel-instance-channel instance))))
690 (define source
691 (channel-instance-checkout instance))
692 (define commit
693 (channel-instance-commit instance))
694
0d39a3b9
LC
695 ;; Running the self-build script makes it easier to update the build
696 ;; procedure: the self-build script of the Guix-to-be-installed contains the
697 ;; right dependencies, build procedure, etc., which the Guix-in-use may not
316fc2ac 698 ;; know.
0d39a3b9
LC
699 (define script
700 (string-append source "/" %self-build-file))
701
702 (if (file-exists? script)
703 (let ((build (save-module-excursion
704 (lambda ()
3a8c4860
LC
705 ;; Disable deprecation warnings; it's OK for SCRIPT to
706 ;; use deprecated APIs and the user doesn't have to know
707 ;; about it.
69962ab7 708 (parameterize ((guix-warning-port
3a8c4860 709 (%make-void-port "w")))
37c0d458
LC
710 (primitive-load script)))))
711 (guile (guile-for-source source)))
0d39a3b9
LC
712 ;; BUILD must be a monadic procedure of at least one argument: the
713 ;; source tree.
714 ;;
715 ;; Note: BUILD can return #f if it does not support %PULL-VERSION. In
716 ;; the future we'll fall back to a previous version of the protocol
717 ;; when that happens.
9db88369 718 (with-guile guile
42a87136
LC
719 ;; BUILD is usually quite costly. Install a "trivial" build handler
720 ;; so we don't bounce an outer build-accumulator handler that could
721 ;; cause us to redo half of the BUILD computation several times just
722 ;; to realize it gives the same result.
723 (with-trivial-build-handler
316fc2ac
LC
724 (build source
725 #:verbose? verbose? #:version commit
34985fb6 726 #:system system
316fc2ac 727 #:channel-metadata (channel-instance->sexp instance)
42a87136 728 #:pull-version %pull-version))))
0d39a3b9
LC
729
730 ;; Build a set of modules that extend Guix using the standard method.
acefa740 731 (standard-module-derivation name source core dependencies)))
0d39a3b9 732
34985fb6 733(define* (build-channel-instance instance system
acefa740 734 #:optional core (dependencies '()))
0d39a3b9 735 "Return, as a monadic value, the derivation for INSTANCE, a channel
34985fb6
CB
736instance, for SYSTEM. DEPENDENCIES is a list of extensions providing Guile
737modules that INSTANCE depends on."
316fc2ac 738 (build-from-source instance
acefa740 739 #:core core
34985fb6
CB
740 #:dependencies dependencies
741 #:system system))
0d39a3b9 742
ed75bdf3
LC
743(define (resolve-dependencies instances)
744 "Return a procedure that, given one of the elements of INSTANCES, returns
745list of instances it depends on."
746 (define channel-instance-name
747 (compose channel-name channel-instance-channel))
748
749 (define table ;map a name to an instance
750 (fold (lambda (instance table)
751 (vhash-consq (channel-instance-name instance)
752 instance table))
753 vlist-null
754 instances))
755
756 (define edges
757 (fold (lambda (instance edges)
758 (fold (lambda (channel edges)
759 (let ((name (channel-name channel)))
760 (match (vhash-assq name table)
761 ((_ . target)
762 (vhash-consq instance target edges)))))
763 edges
764 (channel-instance-dependencies instance)))
765 vlist-null
766 instances))
767
768 (lambda (instance)
769 (vhash-foldq* cons '() instance edges)))
770
34985fb6 771(define* (channel-instance-derivations instances #:key system)
0d39a3b9 772 "Return the list of derivations to build INSTANCES, in the same order as
34985fb6 773INSTANCES. Build for the current system by default, or SYSTEM if specified."
0d39a3b9
LC
774 (define core-instance
775 ;; The 'guix' channel is treated specially: it's an implicit dependency of
776 ;; all the other channels.
777 (find (lambda (instance)
778 (guix-channel? (channel-instance-channel instance)))
779 instances))
780
ed75bdf3
LC
781 (define edges
782 (resolve-dependencies instances))
783
784 (define (instance->derivation instance)
34985fb6 785 (mlet %store-monad ((system (if system (return system) (current-system))))
cdf68947 786 (mcached (if (eq? instance core-instance)
34985fb6 787 (build-channel-instance instance system)
cdf68947
LC
788 (mlet %store-monad ((core (instance->derivation core-instance))
789 (deps (mapm %store-monad instance->derivation
790 (edges instance))))
34985fb6 791 (build-channel-instance instance system core deps)))
cdf68947
LC
792 instance
793 system)))
ed75bdf3 794
ab6025b5
LC
795 (unless core-instance
796 (let ((loc (and=> (any (compose channel-location channel-instance-channel)
797 instances)
798 source-properties->location)))
799 (raise (apply make-compound-condition
800 (condition
801 (&message (message "'guix' channel is lacking")))
f75243e1
LC
802 (condition
803 (&fix-hint (hint (G_ "Make sure your list of channels
804contains one channel named @code{guix} providing the core of Guix."))))
ab6025b5
LC
805 (if loc
806 (list (condition (&error-location (location loc))))
807 '())))))
808
ed75bdf3 809 (mapm %store-monad instance->derivation instances))
0d39a3b9 810
0d39a3b9
LC
811(define (whole-package-for-legacy name modules)
812 "Return a full-blown Guix package for MODULES, a derivation that builds Guix
813modules in the old ~/.config/guix/latest style."
814 (define packages
815 (resolve-interface '(gnu packages guile)))
816
49c35bbb
LC
817 (define modules+compiled
818 ;; Since MODULES contains both .scm and .go files at its root, re-bundle
819 ;; it so that it has share/guile/site and lib/guile, which is what
820 ;; 'whole-package' expects.
821 (computed-file (derivation-name modules)
822 (with-imported-modules '((guix build utils))
823 #~(begin
824 (use-modules (guix build utils))
825
826 (define version
827 (effective-version))
828 (define share
829 (string-append #$output "/share/guile/site"))
830 (define lib
831 (string-append #$output "/lib/guile/" version))
832
833 (mkdir-p share) (mkdir-p lib)
834 (symlink #$modules (string-append share "/" version))
835 (symlink #$modules (string-append lib "/site-ccache"))))))
836
0d39a3b9
LC
837 (letrec-syntax ((list (syntax-rules (->)
838 ((_)
839 '())
840 ((_ (module -> variable) rest ...)
841 (cons (module-ref (resolve-interface
842 '(gnu packages module))
843 'variable)
844 (list rest ...)))
845 ((_ variable rest ...)
846 (cons (module-ref packages 'variable)
847 (list rest ...))))))
49c35bbb 848 (whole-package name modules+compiled
0d39a3b9
LC
849
850 ;; In the "old style", %SELF-BUILD-FILE would simply return a
851 ;; derivation that builds modules. We have to infer what the
852 ;; dependencies of these modules were.
b74ed909 853 (list guile-json-3 guile-git guile-bytestructures
6c46e477
LC
854 (ssh -> guile-ssh) (tls -> gnutls))
855 #:guile (default-guile))))
0d39a3b9
LC
856
857(define (old-style-guix? drv)
858 "Return true if DRV corresponds to a ~/.config/guix/latest style of
859derivation."
860 ;; Here we rely on a gross historical fact: that derivations produced by the
861 ;; "old style" (before commit 8a0d9bc8a3f153159d9e239a151c0fa98f1e12d8,
862 ;; dated May 30, 2018) did not depend on "guix-command.drv".
863 (not (find (lambda (input)
9af75a26
LC
864 (string=? "guix-command"
865 (derivation-name
866 (derivation-input-derivation input))))
0d39a3b9
LC
867 (derivation-inputs drv))))
868
9272cc70
LC
869(define (channel-instance->sexp instance)
870 "Return an sexp representation of INSTANCE, a channel instance."
871 (let* ((commit (channel-instance-commit instance))
872 (channel (channel-instance-channel instance))
873 (intro (channel-introduction channel)))
874 `(repository
875 (version 0)
876 (url ,(channel-url channel))
877 (branch ,(channel-branch channel))
878 (commit ,commit)
a47f16a8 879 (name ,(channel-name channel))
9272cc70
LC
880 ,@(if intro
881 `((introduction
882 (channel-introduction
883 (version 0)
884 (commit
885 ,(channel-introduction-first-signed-commit
886 intro))
887 (signer
888 ,(openpgp-format-fingerprint
889 (channel-introduction-first-commit-signer
890 intro))))))
891 '()))))
892
34985fb6 893(define* (channel-instances->manifest instances #:key system)
0d39a3b9 894 "Return a profile manifest with entries for all of INSTANCES, a list of
34985fb6
CB
895channel instances. By default, build for the current system, or SYSTEM if
896specified."
d9e6217f 897 (define (instance->entry instance drv)
9272cc70
LC
898 (let ((commit (channel-instance-commit instance))
899 (channel (channel-instance-channel instance)))
d9e6217f
LC
900 (manifest-entry
901 (name (symbol->string (channel-name channel)))
902 (version (string-take commit 7))
903 (item (if (guix-channel? channel)
904 (if (old-style-guix? drv)
905 (whole-package-for-legacy (string-append name "-" version)
906 drv)
907 drv)
908 drv))
909 (properties
9272cc70 910 `((source ,(channel-instance->sexp instance)))))))
0d39a3b9 911
34985fb6
CB
912 (mlet* %store-monad ((derivations (channel-instance-derivations instances
913 #:system system))
d9e6217f 914 (entries -> (map instance->entry instances derivations)))
0d39a3b9 915 (return (manifest entries))))
030f1367 916
5fbdc9a5
LC
917(define (package-cache-file manifest)
918 "Build a package cache file for the instance in MANIFEST. This is meant to
919be used as a profile hook."
c9fbd407
LC
920 ;; Note: Emit a profile in format version 3, which was introduced in 2017
921 ;; and is readable by Guix since before version 1.0. This ensures that the
922 ;; Guix in MANIFEST is able to read the manifest file created for its own
923 ;; profile below. See <https://issues.guix.gnu.org/56441>.
924 (let ((profile (profile (content manifest) (hooks '())
925 (format-version 3))))
5fbdc9a5
LC
926 (define build
927 #~(begin
928 (use-modules (gnu packages))
929
930 (if (defined? 'generate-package-cache)
931 (begin
932 ;; Delegate package cache generation to the inferior.
933 (format (current-error-port)
934 "Generating package cache for '~a'...~%"
935 #$profile)
f4041120
JP
936 ;; This script runs through (primitive-load), which by default
937 ;; doesn't print backtraces when it encounters an exception,
938 ;; so manually do it. Use with-throw-handler because it is
939 ;; supported by all Guile versions.
940 (with-throw-handler #t
941 (lambda () (generate-package-cache #$output))
942 (lambda (key . args)
943 (backtrace))))
5fbdc9a5
LC
944 (mkdir #$output))))
945
946 (gexp->derivation-in-inferior "guix-package-cache" build
947 profile
4035fcba
LC
948
949 ;; If the Guix in PROFILE is too old and
950 ;; lacks 'guix repl', don't build the cache
951 ;; instead of failing.
952 #:silent-failure? #t
953
5fbdc9a5 954 #:properties '((type . profile-hook)
f674bc66
LC
955 (hook . package-cache))
956 #:local-build? #t)))
5fbdc9a5
LC
957
958(define %channel-profile-hooks
959 ;; The default channel profile hooks.
960 (cons package-cache-file %default-profile-hooks))
961
c37f38bd
LC
962(define (channel-instances->derivation instances)
963 "Return the derivation of the profile containing INSTANCES, a list of
964channel instances."
965 (mlet %store-monad ((manifest (channel-instances->manifest instances)))
c9fbd407
LC
966 ;; Emit a profile in format version so that, if INSTANCES denotes an old
967 ;; Guix, it can still read that profile, for instance for the purposes of
968 ;; 'guix describe'.
5fbdc9a5 969 (profile-derivation manifest
c9fbd407
LC
970 #:hooks %channel-profile-hooks
971 #:format-version 3)))
c37f38bd 972
030f1367
LC
973(define latest-channel-instances*
974 (store-lift latest-channel-instances))
975
872898f7
LC
976(define* (latest-channel-derivation #:optional (channels %default-channels)
977 #:key
978 (current-channels '())
979 (validate-pull
980 ensure-forward-channel-update))
030f1367 981 "Return as a monadic value the derivation that builds the profile for the
872898f7
LC
982latest instances of CHANNELS. CURRENT-CHANNELS and VALIDATE-PULL are passed
983to 'latest-channel-instances'."
984 (mlet %store-monad ((instances
985 (latest-channel-instances* channels
986 #:current-channels
987 current-channels
988 #:validate-pull
989 validate-pull)))
c37f38bd 990 (channel-instances->derivation instances)))
a7c714d3 991
9272cc70 992(define* (sexp->channel sexp #:optional (name 'channel))
a47f16a8
LC
993 "Read SEXP, a provenance sexp as created by 'channel-instance->sexp'; use
994NAME as the channel name if SEXP does not specify it. Return #f if the sexp
995does not have the expected structure."
9272cc70
LC
996 (match sexp
997 (('repository ('version 0)
998 ('url url)
999 ('branch branch)
1000 ('commit commit)
1001 rest ...)
a47f16a8
LC
1002 ;; Historically channel sexps did not include the channel name. It's OK
1003 ;; for channels created by 'channel-instances->manifest' because the
1004 ;; entry name is the channel name, but it was missing for entries created
1005 ;; by 'manifest-entry-with-provenance'.
1006 (channel (name (match (assq 'name rest)
1007 (#f name)
1008 (('name name) name)))
9272cc70 1009 (url url)
8f999e1a 1010 (branch branch)
9272cc70
LC
1011 (commit commit)
1012 (introduction
1013 (match (assq 'introduction rest)
1014 (#f #f)
1015 (('introduction intro)
1016 (sexp->channel-introduction intro))))))
1017
1018 (_ #f)))
1019
1020(define (manifest-entry-channel entry)
1021 "Return the channel ENTRY corresponds to, or #f if that information is
1022missing or unreadable. ENTRY must be an entry created by
1023'channel-instances->manifest', with the 'source' property."
1024 (let ((name (string->symbol (manifest-entry-name entry))))
1025 (match (assq-ref (manifest-entry-properties entry) 'source)
1026 ((sexp)
1027 (sexp->channel sexp name))
1028 (_
1029 ;; No channel information for this manifest entry.
1030 ;; XXX: Pre-0.15.0 Guix did not provide that information,
1031 ;; but there's not much we can do in that case.
1032 #f))))
1033
a7c714d3
LC
1034(define (profile-channels profile)
1035 "Return the list of channels corresponding to entries in PROFILE. If
1036PROFILE is not a profile created by 'guix pull', return the empty list."
9272cc70 1037 (filter-map manifest-entry-channel
a7c714d3
LC
1038 ;; Show most recently installed packages last.
1039 (reverse
1040 (manifest-entries (profile-manifest profile)))))
8ba7fd3c 1041
60d72f53
LC
1042(define* (channel->code channel #:key (include-introduction? #t))
1043 "Return code (an sexp) to build CHANNEL. When INCLUDE-INTRODUCTION? is
1044true, include its introduction, if any."
1045 (let ((intro (and include-introduction?
1046 (channel-introduction channel))))
1047 `(channel
1048 (name ',(channel-name channel))
1049 (url ,(channel-url channel))
b891f851 1050 (branch ,(channel-branch channel))
60d72f53
LC
1051 (commit ,(channel-commit channel))
1052 ,@(if intro
1053 `((introduction (make-channel-introduction
1054 ,(channel-introduction-first-signed-commit intro)
1055 (openpgp-fingerprint
1056 ,(openpgp-format-fingerprint
1057 (channel-introduction-first-commit-signer
1058 intro))))))
1059 '()))))
1060
8ba7fd3c
LC
1061\f
1062;;;
1063;;; News.
1064;;;
1065
1066;; Channel news.
1067(define-record-type <channel-news>
1068 (channel-news entries)
1069 channel-news?
1070 (entries channel-news-entries)) ;list of <channel-news-entry>
1071
1072;; News entry, associated with a specific commit of the channel.
1073(define-record-type <channel-news-entry>
9719e8d3 1074 (channel-news-entry commit tag title body)
8ba7fd3c 1075 channel-news-entry?
9719e8d3
LC
1076 (commit channel-news-entry-commit) ;hex string | #f
1077 (tag channel-news-entry-tag) ;#f | string
8ba7fd3c
LC
1078 (title channel-news-entry-title) ;list of language tag/string pairs
1079 (body channel-news-entry-body)) ;list of language tag/string pairs
1080
1081(define (sexp->channel-news-entry entry)
1082 "Return the <channel-news-entry> record corresponding to ENTRY, an sexp."
1083 (define (pair language message)
1084 (cons (symbol->string language) message))
1085
1086 (match entry
9719e8d3 1087 (('entry ((and (or 'commit 'tag) type) commit-or-tag)
8ba7fd3c
LC
1088 ('title ((? symbol? title-tags) (? string? titles)) ...)
1089 ('body ((? symbol? body-tags) (? string? bodies)) ...)
1090 _ ...)
9719e8d3
LC
1091 (channel-news-entry (and (eq? type 'commit) commit-or-tag)
1092 (and (eq? type 'tag) commit-or-tag)
8ba7fd3c
LC
1093 (map pair title-tags titles)
1094 (map pair body-tags bodies)))
1095 (_
1096 (raise (condition
1097 (&message (message "invalid channel news entry"))
1098 (&error-location
1099 (location (source-properties->location
1100 (source-properties entry)))))))))
1101
1102(define (read-channel-news port)
1103 "Read a channel news feed from PORT and return it as a <channel-news>
1104record."
1105 (match (false-if-exception (read port))
1106 (('channel-news ('version 0) entries ...)
1107 (channel-news (map sexp->channel-news-entry entries)))
1108 (('channel-news ('version version) _ ...)
1109 ;; This is an unsupported version from the future. There's nothing wrong
1110 ;; with that (the user may simply need to upgrade the 'guix' channel to
1111 ;; be able to read it), so silently ignore it.
1112 (channel-news '()))
1113 (#f
1114 (raise (condition
1115 (&message (message "syntactically invalid channel news file")))))
1116 (sexp
1117 (raise (condition
1118 (&message (message "invalid channel news file"))
1119 (&error-location
1120 (location (source-properties->location
1121 (source-properties sexp)))))))))
1122
9719e8d3
LC
1123(define (resolve-channel-news-entry-tag repository entry)
1124 "If ENTRY has its 'commit' field set, return ENTRY. Otherwise, lookup
1125ENTRY's 'tag' in REPOSITORY and return ENTRY with its 'commit' field set to
1126the field its 'tag' refers to. A 'git-error' exception is raised if the tag
1127cannot be found."
1128 (if (channel-news-entry-commit entry)
1129 entry
1130 (let* ((tag (channel-news-entry-tag entry))
778c1fb4
LC
1131 (reference (reference-lookup repository
1132 (string-append "refs/tags/" tag)))
1133 (target (reference-target reference))
1134 (oid (let ((obj (object-lookup repository target)))
1135 (if (= OBJ-TAG (object-type obj)) ;annotated tag?
1136 (tag-target-id (tag-lookup repository target))
1137 target))))
9719e8d3
LC
1138 (channel-news-entry (oid->string oid) tag
1139 (channel-news-entry-title entry)
1140 (channel-news-entry-body entry)))))
1141
8ba7fd3c
LC
1142(define* (channel-news-for-commit channel new #:optional old)
1143 "Return a list of <channel-news-entry> for CHANNEL between commits OLD and
1144NEW. When OLD is omitted or is #f, return all the news entries of CHANNEL."
1145 (catch 'git-error
1146 (lambda ()
1147 (let* ((checkout (update-cached-checkout (channel-url channel)
1148 #:ref `(commit . ,new)))
1149 (metadata (read-channel-metadata-from-source checkout))
1150 (news-file (channel-metadata-news-file metadata))
1151 (news-file (and news-file
1152 (string-append checkout "/" news-file))))
1153 (if (and news-file (file-exists? news-file))
9719e8d3
LC
1154 (with-repository checkout repository
1155 (let* ((news (call-with-input-file news-file
60e0aae8
LC
1156 (lambda (port)
1157 (set-port-encoding! port
1158 (or (file-encoding port)
1159 "UTF-8"))
1160 (read-channel-news port))))
9719e8d3
LC
1161 (entries (map (lambda (entry)
1162 (resolve-channel-news-entry-tag repository
1163 entry))
1164 (channel-news-entries news))))
1165 (if old
8ba7fd3c
LC
1166 (let* ((new (commit-lookup repository (string->oid new)))
1167 (old (commit-lookup repository (string->oid old)))
1168 (commits (list->set
1169 (map (compose oid->string commit-id)
1170 (commit-difference new old)))))
1171 (filter (lambda (entry)
1172 (set-contains? commits
1173 (channel-news-entry-commit entry)))
9719e8d3
LC
1174 entries))
1175 entries)))
8ba7fd3c
LC
1176 '())))
1177 (lambda (key error . rest)
1178 ;; If commit NEW or commit OLD cannot be found, then something must be
1179 ;; wrong (for example, the history of CHANNEL was rewritten and these
1180 ;; commits no longer exist upstream), so quietly return the empty list.
1181 (if (= GIT_ENOTFOUND (git-error-code error))
1182 '()
1183 (apply throw key error rest)))))
9db88369
LC
1184
1185;;; Local Variables:
1186;;; eval: (put 'with-guile 'scheme-indent-function 1)
1187;;; End: