@@ -23,9 +23,12 @@ type GitRef struct {
2323 // e.g., "bar" for "https://github.com/foo/bar.git"
2424 ShortName string
2525
26- // Commit is a commit hash, a tag, or branch name.
27- // Commit is optional.
28- Commit string
26+ // Ref is a commit hash, a tag, or branch name.
27+ // Ref is optional.
28+ Ref string
29+
30+ // Checksum is a commit hash.
31+ Checksum string
2932
3033 // SubDir is a directory path inside the repo.
3134 // SubDir is optional.
@@ -48,10 +51,8 @@ type GitRef struct {
4851 UnencryptedTCP bool
4952}
5053
51- // var gitURLPathWithFragmentSuffix = regexp.MustCompile(`\.git(?:#.+)?$`)
52-
5354// ParseGitRef parses a git ref.
54- func ParseGitRef (ref string ) (* GitRef , error ) {
55+ func ParseGitRef (ref string ) (* GitRef , bool , error ) {
5556 res := & GitRef {}
5657
5758 var (
@@ -60,21 +61,25 @@ func ParseGitRef(ref string) (*GitRef, error) {
6061 )
6162
6263 if strings .HasPrefix (ref , "./" ) || strings .HasPrefix (ref , "../" ) {
63- return nil , cerrdefs .ErrInvalidArgument
64+ return nil , false , errors . WithStack ( cerrdefs .ErrInvalidArgument )
6465 } else if strings .HasPrefix (ref , "github.com/" ) {
6566 res .IndistinguishableFromLocal = true // Deprecated
66- remote = gitutil .FromURL (& url.URL {
67- Scheme : "https" ,
68- Host : "github.com" ,
69- Path : strings .TrimPrefix (ref , "github.com/" ),
70- })
67+ u , err := url .Parse (ref )
68+ if err != nil {
69+ return nil , false , err
70+ }
71+ u .Scheme = "https"
72+ remote , err = gitutil .FromURL (u )
73+ if err != nil {
74+ return nil , false , err
75+ }
7176 } else {
7277 remote , err = gitutil .ParseURL (ref )
7378 if errors .Is (err , gitutil .ErrUnknownProtocol ) {
74- return nil , err
79+ return nil , false , err
7580 }
7681 if err != nil {
77- return nil , err
82+ return nil , false , err
7883 }
7984
8085 switch remote .Scheme {
@@ -86,7 +91,7 @@ func ParseGitRef(ref string) (*GitRef, error) {
8691 // An HTTP(S) URL is considered to be a valid git ref only when it has the ".git[...]" suffix.
8792 case gitutil .HTTPProtocol , gitutil .HTTPSProtocol :
8893 if ! strings .HasSuffix (remote .Path , ".git" ) {
89- return nil , cerrdefs .ErrInvalidArgument
94+ return nil , false , errors . WithStack ( cerrdefs .ErrInvalidArgument )
9095 }
9196 }
9297 }
@@ -96,11 +101,83 @@ func ParseGitRef(ref string) (*GitRef, error) {
96101 _ , res .Remote , _ = strings .Cut (res .Remote , "://" )
97102 }
98103 if remote .Opts != nil {
99- res .Commit , res .SubDir = remote .Opts .Ref , remote .Opts .Subdir
104+ res .Ref , res .SubDir = remote .Opts .Ref , remote .Opts .Subdir
100105 }
101106
102107 repoSplitBySlash := strings .Split (res .Remote , "/" )
103108 res .ShortName = strings .TrimSuffix (repoSplitBySlash [len (repoSplitBySlash )- 1 ], ".git" )
104109
105- return res , nil
110+ if err := res .loadQuery (remote .Query ); err != nil {
111+ return nil , true , err
112+ }
113+
114+ return res , true , nil
115+ }
116+
117+ func (gf * GitRef ) loadQuery (query url.Values ) error {
118+ if len (query ) == 0 {
119+ return nil
120+ }
121+ var tag , branch string
122+ for k , v := range query {
123+ switch len (v ) {
124+ case 0 :
125+ return errors .Errorf ("query %q has no value" , k )
126+ case 1 :
127+ if v [0 ] == "" {
128+ return errors .Errorf ("query %q has no value" , k )
129+ }
130+ // NOP
131+ default :
132+ return errors .Errorf ("query %q has multiple values" , k )
133+ }
134+ switch k {
135+ case "ref" :
136+ if gf .Ref != "" && gf .Ref != v [0 ] {
137+ return errors .Errorf ("ref conflicts: %q vs %q" , gf .Ref , v [0 ])
138+ }
139+ gf .Ref = v [0 ]
140+ case "tag" :
141+ tag = v [0 ]
142+ case "branch" :
143+ branch = v [0 ]
144+ case "subdir" :
145+ if gf .SubDir != "" && gf .SubDir != v [0 ] {
146+ return errors .Errorf ("subdir conflicts: %q vs %q" , gf .SubDir , v [0 ])
147+ }
148+ gf .SubDir = v [0 ]
149+ case "checksum" , "commit" :
150+ gf .Checksum = v [0 ]
151+ default :
152+ return errors .Errorf ("unexpected query %q" , k )
153+ }
154+ }
155+ if tag != "" {
156+ const tagPrefix = "refs/tags/"
157+ if ! strings .HasPrefix (tag , tagPrefix ) {
158+ tag = tagPrefix + tag
159+ }
160+ if gf .Ref != "" && gf .Ref != tag {
161+ return errors .Errorf ("ref conflicts: %q vs %q" , gf .Ref , tag )
162+ }
163+ gf .Ref = tag
164+ }
165+ if branch != "" {
166+ if tag != "" {
167+ // TODO: consider allowing this, when the tag actually exists on the branch
168+ return errors .New ("branch conflicts with tag" )
169+ }
170+ const branchPrefix = "refs/heads/"
171+ if ! strings .HasPrefix (branch , branchPrefix ) {
172+ branch = branchPrefix + branch
173+ }
174+ if gf .Ref != "" && gf .Ref != branch {
175+ return errors .Errorf ("ref conflicts: %q vs %q" , gf .Ref , branch )
176+ }
177+ gf .Ref = branch
178+ }
179+ if gf .Checksum != "" && gf .Ref == "" {
180+ gf .Ref = gf .Checksum
181+ }
182+ return nil
106183}
0 commit comments