1package main
2
3import (
4 "errors"
5 "os"
6 "sort"
7 "strings"
8
9 "github.com/go-git/go-git/v5"
10 "github.com/go-git/go-git/v5/plumbing"
11 "github.com/go-git/go-git/v5/plumbing/transport"
12)
13
14// listRemoteTags lists all tags in a remote repository, whether HTTP(S) or SSH.
15func listRemoteTags(url string) (tags []string, err error) {
16 // TODO: Implement listRemoteTags
17 // https://pkg.go.dev/github.com/go-git/go-git/v5@v5.8.0#NewRemote
18 return nil, nil
19}
20
21// fetchReleases fetches all releases in a remote repository, whether HTTP(S) or SSH.
22func getGitReleases(p project) (project, error) {
23 r, err := minimalClone(p.URL)
24 if err != nil {
25 return p, err
26 }
27 tagRefs, err := r.Tags()
28 if err != nil {
29 return p, err
30 }
31 err = tagRefs.ForEach(func(tagRef *plumbing.Reference) error {
32 obj, err := r.TagObject(tagRef.Hash())
33 switch err {
34 case plumbing.ErrObjectNotFound:
35 // This is a lightweight tag, not an annotated tag, skip it
36 return nil
37 case nil:
38 url := ""
39 tagName := bmStrict.Sanitize(tagRef.Name().Short())
40 switch p.Forge {
41 case "sourcehut":
42 url = p.URL + "/refs/" + tagName
43 case "gitlab":
44 url = p.URL + "/-/releases/" + tagName
45 default:
46 url = ""
47 }
48 p.Releases = append(p.Releases, release{
49 Tag: tagName,
50 Content: bmUGC.Sanitize(obj.Message),
51 URL: url,
52 Date: obj.Tagger.When,
53 })
54 default:
55 return err
56 }
57 return nil
58 })
59 if err != nil {
60 return p, err
61 }
62
63 sort.Slice(p.Releases, func(i, j int) bool { return p.Releases[i].Date.After(p.Releases[j].Date) })
64
65 return p, nil
66}
67
68// minimalClone clones a repository with a depth of 1 and no checkout.
69func minimalClone(url string) (r *git.Repository, err error) {
70 path, err := stringifyRepo(url)
71 if err != nil {
72 return nil, err
73 }
74
75 if _, err := os.Stat(path); err == nil {
76 r, err := git.PlainOpen(path)
77 if err != nil {
78 return nil, err
79 }
80 err = r.Fetch(&git.FetchOptions{
81 RemoteName: "origin",
82 Depth: 1,
83 Tags: git.AllTags,
84 })
85 if err == git.NoErrAlreadyUpToDate {
86 return r, nil
87 }
88 return r, err
89 }
90
91 r, err = git.PlainClone(path, false, &git.CloneOptions{
92 URL: url,
93 SingleBranch: true,
94 NoCheckout: true,
95 Depth: 1,
96 })
97 return r, err
98}
99
100// removeRepo removes a repository from the local filesystem.
101func removeRepo(url string) (err error) {
102 path, err := stringifyRepo(url)
103 if err != nil {
104 return err
105 }
106 err = os.RemoveAll(path)
107 return err
108}
109
110// stringifyRepo accepts a repository URI string and the corresponding local
111// filesystem path, whether the URI is HTTP, HTTPS, or SSH.
112func stringifyRepo(url string) (path string, err error) {
113 ep, err := transport.NewEndpoint(url)
114 if err != nil {
115 return "", err
116 }
117
118 if ep.Protocol == "http" || ep.Protocol == "https" {
119 return "data/" + strings.Split(url, "://")[1], nil
120 } else if ep.Protocol == "ssh" {
121 return "data/" + ep.Host + ep.Path, nil
122 } else {
123 return "", errors.New("unsupported protocol")
124 }
125}