git: Support recursive updates of submodules.

* guix/git.scm: Autoload (git submodule).
(url-cache-directory): Add #:recursive? and honor it.
(call-with-repository): New procedure.
(with-repository): New macro.
(update-submodules): New procedure.
(update-cached-checkout): Add #:recursive? and #:log-port and honor
them.
(latest-repository-commit): Add #:recursive? and honor it.
[dot-git?]: Recognize ".git" regular files when RECURSIVE? is true.
This commit is contained in:
Ludovic Courtès 2019-02-08 09:12:07 +01:00 committed by Ludovic Courtès
parent 92becc3f15
commit 60cbc6a8df
No known key found for this signature in database
GPG Key ID: 090B11993D9AEBB5
1 changed files with 79 additions and 7 deletions

View File

@ -1,6 +1,6 @@
;;; GNU Guix --- Functional package management for GNU ;;; GNU Guix --- Functional package management for GNU
;;; Copyright © 2017 Mathieu Othacehe <m.othacehe@gmail.com> ;;; Copyright © 2017 Mathieu Othacehe <m.othacehe@gmail.com>
;;; Copyright © 2018 Ludovic Courtès <ludo@gnu.org> ;;; Copyright © 2018, 2019 Ludovic Courtès <ludo@gnu.org>
;;; ;;;
;;; This file is part of GNU Guix. ;;; This file is part of GNU Guix.
;;; ;;;
@ -43,6 +43,11 @@
git-checkout-url git-checkout-url
git-checkout-branch)) git-checkout-branch))
;; XXX: Use this hack instead of #:autoload to avoid compilation errors.
;; See <http://bugs.gnu.org/12202>.
(module-autoload! (current-module)
'(git submodule) '(repository-submodules))
(define %repository-cache-directory (define %repository-cache-directory
(make-parameter (string-append (cache-directory #:ensure? #f) (make-parameter (string-append (cache-directory #:ensure? #f)
"/checkouts"))) "/checkouts")))
@ -57,11 +62,15 @@
(define* (url-cache-directory url (define* (url-cache-directory url
#:optional (cache-directory #:optional (cache-directory
(%repository-cache-directory))) (%repository-cache-directory))
#:key recursive?)
"Return the directory associated to URL in %repository-cache-directory." "Return the directory associated to URL in %repository-cache-directory."
(string-append (string-append
cache-directory "/" cache-directory "/"
(bytevector->base32-string (sha256 (string->utf8 url))))) (bytevector->base32-string
(sha256 (string->utf8 (if recursive?
(string-append "R:" url)
url))))))
(define (clone* url directory) (define (clone* url directory)
"Clone git repository at URL into DIRECTORY. Upon failure, "Clone git repository at URL into DIRECTORY. Upon failure,
@ -119,18 +128,62 @@ OID (roughly the commit hash) corresponding to REF."
(reset repository obj RESET_HARD) (reset repository obj RESET_HARD)
(object-id obj)) (object-id obj))
(define (call-with-repository directory proc)
(let ((repository #f))
(dynamic-wind
(lambda ()
(set! repository (repository-open directory)))
(lambda ()
(proc repository))
(lambda ()
(repository-close! repository)))))
(define-syntax-rule (with-repository directory repository exp ...)
"Open the repository at DIRECTORY and bind REPOSITORY to it within the
dynamic extent of EXP."
(call-with-repository directory
(lambda (repository) exp ...)))
(define* (update-submodules repository
#:key (log-port (current-error-port)))
"Update the submodules of REPOSITORY, a Git repository object."
;; Guile-Git < 0.2.0 did not have (git submodule).
(if (false-if-exception (resolve-interface '(git submodule)))
(for-each (lambda (name)
(let ((submodule (submodule-lookup repository name)))
(format log-port (G_ "updating submodule '~a'...~%")
name)
(submodule-update submodule)
;; Recurse in SUBMODULE.
(let ((directory (string-append
(repository-working-directory repository)
"/" (submodule-path submodule))))
(with-repository directory repository
(update-submodules repository
#:log-port log-port)))))
(repository-submodules repository))
(format (current-error-port)
(G_ "Support for submodules is missing; \
please upgrade Guile-Git.~%"))))
(define* (update-cached-checkout url (define* (update-cached-checkout url
#:key #:key
(ref '(branch . "master")) (ref '(branch . "master"))
recursive?
(log-port (%make-void-port "w"))
(cache-directory (cache-directory
(url-cache-directory (url-cache-directory
url (%repository-cache-directory)))) url (%repository-cache-directory)
#:recursive? recursive?)))
"Update the cached checkout of URL to REF in CACHE-DIRECTORY. Return two "Update the cached checkout of URL to REF in CACHE-DIRECTORY. Return two
values: the cache directory name, and the SHA1 commit (a string) corresponding values: the cache directory name, and the SHA1 commit (a string) corresponding
to REF. to REF.
REF is pair whose key is [branch | commit | tag] and value the associated REF is pair whose key is [branch | commit | tag] and value the associated
data, respectively [<branch name> | <sha1> | <tag name>]." data, respectively [<branch name> | <sha1> | <tag name>].
When RECURSIVE? is true, check out submodules as well, if any."
(define canonical-ref (define canonical-ref
;; We used to require callers to specify "origin/" for each branch, which ;; We used to require callers to specify "origin/" for each branch, which
;; made little sense since the cache should be transparent to them. So ;; made little sense since the cache should be transparent to them. So
@ -150,6 +203,8 @@ data, respectively [<branch name> | <sha1> | <tag name>]."
;; Only fetch remote if it has not been cloned just before. ;; Only fetch remote if it has not been cloned just before.
(when cache-exists? (when cache-exists?
(remote-fetch (remote-lookup repository "origin"))) (remote-fetch (remote-lookup repository "origin")))
(when recursive?
(update-submodules repository #:log-port log-port))
(let ((oid (switch-to-ref repository canonical-ref))) (let ((oid (switch-to-ref repository canonical-ref)))
;; Reclaim file descriptors and memory mappings associated with ;; Reclaim file descriptors and memory mappings associated with
@ -162,6 +217,7 @@ data, respectively [<branch name> | <sha1> | <tag name>]."
(define* (latest-repository-commit store url (define* (latest-repository-commit store url
#:key #:key
recursive?
(log-port (%make-void-port "w")) (log-port (%make-void-port "w"))
(cache-directory (cache-directory
(%repository-cache-directory)) (%repository-cache-directory))
@ -172,21 +228,33 @@ reference to be checkout, once the repository is fetched, is specified by REF.
REF is pair whose key is [branch | commit | tag] and value the associated REF is pair whose key is [branch | commit | tag] and value the associated
data, respectively [<branch name> | <sha1> | <tag name>]. data, respectively [<branch name> | <sha1> | <tag name>].
When RECURSIVE? is true, check out submodules as well, if any.
Git repositories are kept in the cache directory specified by Git repositories are kept in the cache directory specified by
%repository-cache-directory parameter. %repository-cache-directory parameter.
Log progress and checkout info to LOG-PORT." Log progress and checkout info to LOG-PORT."
(define (dot-git? file stat) (define (dot-git? file stat)
(and (string=? (basename file) ".git") (and (string=? (basename file) ".git")
(eq? 'directory (stat:type stat)))) (or (eq? 'directory (stat:type stat))
;; Submodule checkouts end up with a '.git' regular file that
;; contains metadata about where their actual '.git' directory
;; lives.
(and recursive?
(eq? 'regular (stat:type stat))))))
(format log-port "updating checkout of '~a'...~%" url) (format log-port "updating checkout of '~a'...~%" url)
(let*-values (let*-values
(((checkout commit) (((checkout commit)
(update-cached-checkout url (update-cached-checkout url
#:recursive? recursive?
#:ref ref #:ref ref
#:cache-directory #:cache-directory
(url-cache-directory url cache-directory))) (url-cache-directory url cache-directory
#:recursive?
recursive?)
#:log-port log-port))
((name) ((name)
(url+commit->name url commit))) (url+commit->name url commit)))
(format log-port "retrieved commit ~a~%" commit) (format log-port "retrieved commit ~a~%" commit)
@ -244,3 +312,7 @@ Log progress and checkout info to LOG-PORT."
`(commit . ,commit) `(commit . ,commit)
`(branch . ,branch)) `(branch . ,branch))
#:log-port (current-error-port))))) #:log-port (current-error-port)))))
;; Local Variables:
;; eval: (put 'with-repository 'scheme-indent-function 2)
;; End: