aboutsummary
aboutsummaryrefslogtreecommitdiff #!@GUILE@ \ --no-auto-compile -s !# ;;; GNU Guix --- Functional package management for GNU ;;; Copyright © 2020, 2021, 2022, 2023 Ricardo Wurmus <rekado@elephly.net> ;;; Copyright © 2021 Sarah Morgensen <iskarian@mgsn.dev> ;;; Copyright © 2021 Xinglu Chen <public@yoctocell.xyz> ;;; Copyright © 2022 Maxim Cournoyer <maxim.cournoyer@gmail.com> ;;; ;;; This file is part of GNU Guix. ;;; ;;; GNU Guix is free software; you can redistribute it and/or modify it ;;; under the terms of the GNU General Public License as published by ;;; the Free Software Foundation; either version 3 of the License, or (at ;;; your option) any later version. ;;; ;;; GNU Guix is distributed in the hope that it will be useful, but ;;; WITHOUT ANY WARRANTY; without even the implied warranty of ;;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ;;; GNU General Public License for more details. ;;; ;;; You should have received a copy of the GNU General Public License ;;; along with GNU Guix. If not, see <http://www.gnu.org/licenses/>. ;;; Commentary: ;; This script stages and commits changes to package definitions. ;;; Code: (use-modules ((sxml xpath) #:prefix xpath:) (srfi srfi-1) (srfi srfi-2) (srfi srfi-9) (srfi srfi-11) (srfi srfi-26) (ice-9 format) (ice-9 popen) (ice-9 match) (ice-9 rdelim) (ice-9 regex) (ice-9 textual-ports) (guix gexp)) (define* (break-string str #:optional (max-line-length 70)) "Break the string STR into lines that are no longer than MAX-LINE-LENGTH. Return a single string." (define (restore-line words) (string-join (reverse words) " ")) (if (<= (string-length str) max-line-length) str (let ((words+lengths (map (lambda (word) (cons word (string-length word))) (string-tokenize str)))) (match (fold (match-lambda* (((word . length) (count current lines)) (let ((new-count (+ count length 1))) (if (< new-count max-line-length) (list new-count (cons word current) lines) (list length (list word) (cons (restore-line current) lines)))))) '(0 () ()) words+lengths) ((_ last-words lines) (string-join (reverse (cons (restore-line last-words) lines)) "\n")))))) (define* (break-string-with-newlines str #:optional (max-line-length 70)) "Break the lines of string STR into lines that are no longer than MAX-LINE-LENGTH. Return a single string." (string-join (map (cut break-string <> max-line-length) (string-split str #\newline)) "\n")) (define (read-excursion port) "Read an expression from PORT and reset the port position before returning the expression." (let ((start (ftell port)) (result (read port))) (seek port start SEEK_SET) result)) (define (lines+offsets-with-opening-parens port) "Record all line numbers (and their offsets) where an opening parenthesis is found in column 0. The resulting list is in reverse order." (let loop ((acc '()) (number 0)) (let ((line (read-line port))) (cond ((eof-object? line) acc) ((string-prefix? "(" line) (loop (cons (cons number ;line number (- (ftell port) (string-length line) 1)) ;offset acc) (1+ number))) (else (loop acc (1+ number))))))) (define (surrounding-sexp port target-line-no) "Return the top-level S-expression surrounding the change at line number TARGET-LINE-NO in PORT." (let* ((line-numbers+offsets (lines+offsets-with-opening-parens port)) (closest-offset (or (and=> (list-index (match-lambda ((line-number . offset) (< line-number target-line-no))) line-numbers+offsets) (lambda (index) (match (list-ref line-numbers+offsets index) ((line-number . offset) offset)))) (error "Could not find surrounding S-expression for line" target-line-no)))) (seek port closest-offset SEEK_SET) (read port))) ;;; Whether the hunk contains a newly added package (definition), a removed ;;; package (removal) or something else (#false). (define hunk-types '(addition removal #false)) (define-record-type <hunk> (make-hunk file-name old-line-number new-line-number diff-lines type) hunk? (file-name hunk-file-name) ;; Line number before the change (old-line-number hunk-old-line-number) ;; Line number after the change (new-line-number hunk-new-line-number) ;; The full diff to be used with "git apply --cached" (diff-lines hunk-diff-lines) ;; Does this hunk add or remove a package? (type hunk-type)) ;one of 'hunk-types' (define* (hunk->patch hunk #:optional (port (current-output-port))) (let ((file-name (hunk-file-name hunk))) (format port "diff --git a/~a b/~a~%--- a/~a~%+++ b/~a~%~a" file-name file-name file-name file-name (string-join (hunk-diff-lines hunk) "")))) (define (diff-info) "Read the diff and return a list of <hunk> values." (let ((port (open-pipe* OPEN_READ "git" "diff-files" "--no-prefix" ;; Only include one context line to avoid lumping in ;; new definitions with changes to existing ;; definitions. "--unified=1" "--" "gnu"))) (define (extract-line-number line-tag) (abs (string->number (car (string-split line-tag #\,))))) (define (read-hunk) (let loop ((lines '())