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