package system: change syntax of a GitHub package source to "git://..."

Besides changing the URL scheme, the tag or branch is optional and
specified as a fragment (inspired by npm). Also, any subpath is expressed
as a "path=..." query (which similarly avoids giving a different meaning
to URLs than `git' itself would). The repository name can have a ".git"
suffix.

The "github://..." format is still supported for compatibility, but
`--type github' adds "git://..." instead of "github://..." if
neither is already present (which is incompatible, since branches
and tags are handled differently for the two forms).

Closes PR 13656

(See the PR for a discussion and my rationale for this choice.)
This commit is contained in:
Matthew Flatt 2013-08-22 08:41:02 -06:00
parent 33c1a7349d
commit e1efd2d98f
8 changed files with 92 additions and 45 deletions

View File

@ -370,10 +370,10 @@ on GitHub, then
repository for your package}. After that, your @tech{package source} repository for your package}. After that, your @tech{package source}
is: is:
@inset{@exec{github://github.com/@nonterm{user}/@nonterm{package}/@nonterm{branch}}} @inset{@exec{git://github.com/@nonterm{user}/@nonterm{package}}}
Typically, @nonterm{branch} will be @exec{master}, but you may wish to use If you want the package to be @nonterm{branch} instead of @exec{master},
different branches for releases and development. then add @filepath{#@nonterm{branch}} to the end of the package source.
Whenever you Whenever you

View File

@ -187,25 +187,33 @@ is the directory name.}
@item{a remote URL naming a GitHub repository -- The format for such @item{a remote URL naming a GitHub repository -- The format for such
URLs is: URLs is:
@inset{@exec{github://github.com/}@nonterm{user}@exec{/}@nonterm{repo}@; @inset{@exec{git://github.com/}@nonterm{user}@exec{/}@nonterm{repo}@;
@exec{/}@nonterm{branch-or-tag}@exec{/}@nonterm{subpath}} @optional{@exec{.git}}@optional{@exec{/}}@optional{@exec{?path=}@nonterm{path}}@;
@optional{@exec{#}@nonterm{tag}}}
where @nonterm{subpath} is optional and can contain multiple where @nonterm{path} can contain multiple @litchar{/}-separated
@litchar{/}-separated elements. elements to form a path within the repository, and defaults to the
empty path. The @nonterm{tag} can be a branch or tag, and it
defaults to @exec{master}.
For example, @filepath{github://github.com/game/tic-tac-toe/master/} For example, @filepath{git://github.com/game/tic-tac-toe#master}
is a GitHub package source. is a GitHub package source.
For backward compatibility, an older format is also supported:
@inset{@exec{github://github.com/}@nonterm{user}@exec{/}@nonterm{repo}@;
@exec{/}@nonterm{tag}@optional{@exec{/}@nonterm{path}}}
The @exec{zip}-formatted archive for the repository (generated by The @exec{zip}-formatted archive for the repository (generated by
GitHub for every branch and tag) is used as a remote URL archive path, GitHub for every branch and tag) is used as a remote URL archive path,
except the @tech{checksum} is the hash identifying the branch (or except the @tech{checksum} is the hash identifying the branch (or
tag). tag).
A package source is inferred to be a GitHub reference when it A package source is inferred to be a GitHub reference when it
starts with @litchar{github://}; a package source that is otherwise starts with @litchar{git://} or @litchar{github://}; a package source that is otherwise
specified as a GitHub reference is automatically prefixed with specified as a GitHub reference is automatically prefixed with
@filepath{github://github.com/}. The inferred package name @filepath{git://github.com/}. The inferred package name
is the last element of @nonterm{subpath} if it is is the last element of @nonterm{path} if it is
non-empty, otherwise the inferred name is @nonterm{repo}.} non-empty, otherwise the inferred name is @nonterm{repo}.}
@item{a @tech{package name} -- A @tech{package catalog} is @item{a @tech{package name} -- A @tech{package catalog} is

View File

@ -37,10 +37,10 @@
(list (list
(pkg "p1" "http://a" "" "" "" ""))) (pkg "p1" "http://a" "" "" "" "")))
(set-pkg! "p1" "http://a" "github:a" "adam" "123" "the first package") (set-pkg! "p1" "http://a" "adam" "git:a" "123" "the first package")
(check-equal? (get-pkgs) (check-equal? (get-pkgs)
(list (list
(pkg "p1" "http://a" "github:a" "adam" "123" "the first package") (pkg "p1" "http://a" "adam" "git:a" "123" "the first package")
(pkg "p2" "http://b" "" "" "" ""))) (pkg "p2" "http://b" "" "" "" "")))
;; reverse order of catalogs: ;; reverse order of catalogs:
@ -50,7 +50,7 @@
(check-equal? (get-pkgs) (check-equal? (get-pkgs)
(list (list
(pkg "p2" "http://b" "" "" "" "") (pkg "p2" "http://b" "" "" "" "")
(pkg "p1" "http://a" "github:a" "adam" "123" "the first package"))) (pkg "p1" "http://a" "adam" "git:a" "123" "the first package")))
(check-equal? (get-pkg-tags "p2" "http://b") (check-equal? (get-pkg-tags "p2" "http://b")
'()) '())
@ -97,6 +97,6 @@
(check-equal? (get-pkgs) (check-equal? (get-pkgs)
(list (list
(pkg "p1" "http://a" "github:a" "adam" "123" "the first package"))) (pkg "p1" "http://a" "adam" "git:a" "123" "the first package")))
(delete-file (current-pkg-catalog-file))) (delete-file (current-pkg-catalog-file)))

View File

@ -91,10 +91,24 @@
(check-equal-values? (parse "github://github.com/fish/master" 'github #rx"three") (values #f 'github #f)) (check-equal-values? (parse "github://github.com/fish/master" 'github #rx"three") (values #f 'github #f))
(check-equal-values? (parse "github://github.com/racket/fish.more/release" 'github) (values #f 'github #t)) (check-equal-values? (parse "github://github.com/racket/fish.more/release" 'github) (values #f 'github #t))
(check-equal-values? (parse "racket/fish/master" 'github) (values "fish" 'github #t)) (check-equal-values? (parse "git://not-github.com/racket/fish" #f #rx"github.com") (values #f 'github #f))
(check-equal-values? (parse "racket/fish/master/" 'github) (values "fish" 'github #t)) (check-equal-values? (parse "git://github.com/racket/fish" #f) (values "fish" 'github #t))
(check-equal-values? (parse "racket/fish" 'github #rx"three") (values #f 'github #f)) (check-equal-values? (parse "git://github.com/racket/fish/" #f) (values "fish" 'github #t))
(check-equal-values? (parse "fish" 'github #rx"three") (values #f 'github #f)) (check-equal-values? (parse "git://github.com/racket/fish.git" #f) (values "fish" 'github #t))
(check-equal-values? (parse "git://github.com/racket/fish.git/" #f) (values "fish" 'github #t))
(check-equal-values? (parse "git://github.com/racket/fish.rkt" #f) (values #f 'github #t))
(check-equal-values? (parse "git://github.com/racket/fish#release" #f) (values "fish" 'github #t))
(check-equal-values? (parse "git://github.com/racket/fish?path=catfish#release" #f) (values "catfish" 'github #t))
(check-equal-values? (parse "git://github.com/racket/fish?path=catfish/" #f) (values "catfish" 'github #t))
(check-equal-values? (parse "git://github.com/racket/fish?path=catfish/bill" #f) (values "bill" 'github #t))
(check-equal-values? (parse "git://github.com/racket/fish/master" 'github #rx"two") (values #f 'github #f))
(check-equal-values? (parse "git://github.com/racket/fish.more" 'github) (values #f 'github #t))
(check-equal-values? (parse "racket/fish" 'github) (values "fish" 'github #t))
(check-equal-values? (parse "racket/fish.git" 'github) (values "fish" 'github #t))
(check-equal-values? (parse "racket/fish/" 'github) (values "fish" 'github #t))
(check-equal-values? (parse "racket/fish/x" 'github #rx"two") (values #f 'github #f))
(check-equal-values? (parse "fish" 'github #rx"two") (values #f 'github #f))
(check-equal-values? (parse "file://fish.plt" #f) (values "fish" 'file #t)) (check-equal-values? (parse "file://fish.plt" #f) (values "fish" 'file #t))
(check-equal-values? (parse "file:///root/fish.plt" #f) (values "fish" 'file #t)) (check-equal-values? (parse "file:///root/fish.plt" #f) (values "fish" 'file #t))

View File

@ -16,17 +16,17 @@
(pkg-tests (pkg-tests
(shelly-begin (shelly-begin
(shelly-install "remote/github" (shelly-install "remote/github"
"github://github.com/jeapostrophe/galaxy/master/tests/planet2/test-pkgs/planet2-test1") "git://github.com/jeapostrophe/galaxy?path=tests/planet2/test-pkgs/planet2-test1")
(shelly-install "remote/github with slash" (shelly-install "remote/github with slash"
"github://github.com/jeapostrophe/galaxy/master/tests/planet2/test-pkgs/planet2-test1/") "git://github.com/jeapostrophe/galaxy?path=tests/planet2/test-pkgs/planet2-test1/")
(shelly-install "remote/github with auto prefix" (shelly-install "remote/github with auto prefix"
"--type github jeapostrophe/galaxy/master/tests/planet2/test-pkgs/planet2-test1/") "--type github jeapostrophe/galaxy?path=tests/planet2/test-pkgs/planet2-test1/")
(hash-set! *index-ht-1* "planet2-test1-github-different-checksum" (hash-set! *index-ht-1* "planet2-test1-github-different-checksum"
(hasheq 'checksum (hasheq 'checksum
"23eeaee731e72a39bddbacdf1ed6cce3bcf423a5" "23eeaee731e72a39bddbacdf1ed6cce3bcf423a5"
'source 'source
"github://github.com/jeapostrophe/galaxy/master/tests/planet2/test-pkgs/planet2-test1/")) "git://github.com/jeapostrophe/galaxy?path=tests/planet2/test-pkgs/planet2-test1/"))
(with-fake-root (with-fake-root
(shelly-case (shelly-case

View File

@ -838,9 +838,9 @@
pkg)) pkg))
(cond (cond
[(and (eq? type 'github) [(and (eq? type 'github)
(not (regexp-match? #rx"^github://" pkg))) (not (regexp-match? #rx"^git(?:hub)?://" pkg)))
;; Add "github://github.com/" ;; Add "git://github.com/"
(stage-package/info (string-append "github://github.com/" pkg) type (stage-package/info (string-append "git://github.com/" pkg) type
pkg-name pkg-name
#:given-checksum given-checksum #:given-checksum given-checksum
check-sums? download-printf check-sums? download-printf

View File

@ -107,15 +107,15 @@
(validate-name s complain #f) (validate-name s complain #f)
(values (and (regexp-match? rx:package-name s) s) 'name)] (values (and (regexp-match? rx:package-name s) s) 'name)]
[(and (eq? type 'github) [(and (eq? type 'github)
(not (regexp-match? #rx"^github://" s))) (not (regexp-match? #rx"^git(?:hub)?://" s)))
(package-source->name+type (package-source->name+type
(string-append "github://github.com/" s) (string-append "git://github.com/" s)
'github)] 'github)]
[(if type [(if type
(or (eq? type 'github) (or (eq? type 'github)
(eq? type 'file-url) (eq? type 'file-url)
(eq? type 'dir-url)) (eq? type 'dir-url))
(regexp-match? #rx"^(https?|github)://" s)) (regexp-match? #rx"^(https?|github|git)://" s))
(define url (with-handlers ([exn:fail? (lambda (exn) #f)]) (define url (with-handlers ([exn:fail? (lambda (exn) #f)])
(string->url s))) (string->url s)))
(define-values (name name-type) (define-values (name name-type)
@ -124,25 +124,40 @@
(cond (cond
[(if type [(if type
(eq? type 'github) (eq? type 'github)
(equal? (url-scheme url) "github")) (or (equal? (url-scheme url) "github")
(unless (equal? (url-scheme url) "github") (equal? (url-scheme url) "git")))
(complain "URL scheme is not 'github'")) (unless (or (equal? (url-scheme url) "github")
(equal? (url-scheme url) "git"))
(complain "URL scheme is not 'git' or 'github'"))
(define name (define name
(and (cor (pair? p) (and (cor (pair? p)
(complain "URL path is empty")) (complain "URL path is empty"))
(cor (equal? "github.com" (url-host url)) (cor (equal? "github.com" (url-host url))
(complain "URL host is not 'github.com'")) (complain "URL host is not 'github.com'"))
(let ([p (if (equal? "" (path/param-path (last p))) (if (equal? (url-scheme url) "git")
(reverse (cdr (reverse p))) ;; git://
p)]) (and (cor (or (= (length p) 2)
(and (cor ((length p) . >= . 3) (and (= (length p) 3)
(complain "URL does not have at least three path elements")) (equal? "" (path/param-path (caddr p)))))
(validate-name (complain "URL does not have two path elements (name and repo)"))
(if (= (length p) 3) (let ([a (assoc 'path (url-query url))])
(path/param-path (second (reverse p))) (define sub (and a (cdr a) (string-split (cdr a) "/")))
(last-non-empty p)) (if (pair? sub)
complain-name (validate-name (last sub) complain-name #t)
#t))))) (let ([s (path/param-path (cadr p))])
(validate-name (regexp-replace #rx"[.]git$" s "") complain-name #t)))))
;; github://
(let ([p (if (equal? "" (path/param-path (last p)))
(reverse (cdr (reverse p)))
p)])
(and (cor ((length p) . >= . 3)
(complain "URL does not have at least three path elements"))
(validate-name
(if (= (length p) 3)
(path/param-path (second (reverse p)))
(last-non-empty p))
complain-name
#t))))))
(values name (or type 'github))] (values name (or type 'github))]
[(if type [(if type
(eq? type 'file-url) (eq? type 'file-url)

View File

@ -53,7 +53,17 @@
(define github-client_secret (make-parameter #f)) (define github-client_secret (make-parameter #f))
(define (split-github-url pkg-url) (define (split-github-url pkg-url)
(map path/param-path (url-path/no-slash pkg-url))) (if (equal? (url-scheme pkg-url) "github")
;; github://
(map path/param-path (url-path/no-slash pkg-url))
;; git://
(let* ([paths (map path/param-path (url-path/no-slash pkg-url))])
(list* (car paths)
(regexp-replace* #rx"[.]git$" (cadr paths) "")
(or (url-fragment pkg-url) "master")
(let ([a (assoc 'path (url-query pkg-url))])
(or (and a (cdr a) (string-split (cdr a) "/"))
null))))))
(define (package-url->checksum pkg-url-str [query empty] (define (package-url->checksum pkg-url-str [query empty]
#:download-printf [download-printf void] #:download-printf [download-printf void]
@ -61,7 +71,7 @@
(define pkg-url (define pkg-url
(string->url pkg-url-str)) (string->url pkg-url-str))
(match (url-scheme pkg-url) (match (url-scheme pkg-url)
["github" [(or "github" "git")
(match-define (list* user repo branch path) (match-define (list* user repo branch path)
(split-github-url pkg-url)) (split-github-url pkg-url))
(define api-u (define api-u