Sign in

Search issue titles and bodies, change requests and comments. Private repositories you cannot read are not included.

vole
Clone

Read only, no credentials:

https://rodent.software/vole.git

Write over SSH with a registered key:

git clone ssh://git@rodent.software:2222/vole.git

A free and open-source forge that lives in your Git repo.

refactor

c5902451905acdaa478f8476844ccd4a510d9ca9 · Keenan Nemetz · 17d ago · parent ec2aba4b

internal/releases/releases.go → metadata/releases.go +28−18

@@ -1,4 +1,4 @@
1 // Package releases reads the artifacts attached to a release.
1 // Release artifacts are the files attached to a release.
2 2 //
3 3 // The artifact list is a directory on the metadata branch: releases/<tag>/<name>
4 4 // holds a standard git-lfs pointer blob, and the pointer is the whole record.
@@ -15,20 +15,17 @@
15 15 // A JSON manifest listing sha256 strings would have been none of those things: it
16 16 // parses as no pointer, so the sweep would treat every published artifact as
17 17 // garbage once the grace period passed.
18 package releases
18 package metadata
19 19
20 20 import (
21 21 "fmt"
22 22 "sort"
23 23 "strings"
24 24
25 "github.com/rodent-software/vole/internal/gitx"
26 "github.com/rodent-software/vole/internal/lfs"
25 "github.com/rodent-software/vole/core/git"
26 "github.com/rodent-software/vole/core/lfs"
27 27 )
28 28
29 // Dir is the metadata-branch directory releases live under.
30 const Dir = "releases"
31
32 29 // Artifact is one file attached to a release.
33 30 type Artifact struct {
34 31 Name string
@@ -37,13 +34,13 @@ type Artifact struct {
37 34 }
38 35
39 36 // Path is where an artifact's pointer lives on the metadata branch.
40 func Path(tag, name string) string { return Dir + "/" + tag + "/" + name }
37 func ReleasePath(tag, name string) string { return ReleasesDir + "/" + tag + "/" + name }
41 38
42 39 // ValidName rejects artifact names that would not be one path segment under the
43 40 // release's directory. A name is a filename, not a path: allowing "/" would let
44 41 // an attach write outside releases/<tag>/, and the download route could not name
45 42 // the result.
46 func ValidName(name string) error {
43 func ValidReleaseName(name string) error {
47 44 switch {
48 45 case name == "":
49 46 return fmt.Errorf("an artifact needs a name")
@@ -62,8 +59,8 @@ func ValidName(name string) error {
62 59 // List reads the artifacts of one tag, sorted by name. A tag with nothing
63 60 // attached is an empty list rather than an error: the directory only exists once
64 61 // something is attached to it.
65 func List(g *gitx.Repo, tag string) ([]Artifact, error) {
66 entries, err := g.LsTree("refs/heads/vole:" + Dir + "/" + tag)
62 func ListReleaseArtifacts(g *git.Repo, tag string) ([]Artifact, error) {
63 entries, err := g.LsTree("refs/heads/vole:" + ReleasesDir + "/" + tag)
67 64 if err != nil {
68 65 return nil, nil // no releases/<tag>/ directory: no artifacts
69 66 }
@@ -72,7 +69,7 @@ func List(g *gitx.Repo, tag string) ([]Artifact, error) {
72 69 if e.Type != "blob" {
73 70 continue
74 71 }
75 data, err := g.BlobAt("refs/heads/vole", Path(tag, e.Name))
72 data, err := g.BlobAt("refs/heads/vole", ReleasePath(tag, e.Name))
76 73 if err != nil {
77 74 return nil, fmt.Errorf("read artifact pointer %s: %w", e.Name, err)
78 75 }
@@ -94,7 +91,7 @@ func List(g *gitx.Repo, tag string) ([]Artifact, error) {
94 91 // that is not releases/<tag>/<name> pointing at an object nobody uploaded is
95 92 // either a mistake or a download that will 404, and both are cheaper to refuse
96 93 // here than to discover later.
97 func Problems(g *gitx.Repo, commit string) []string {
94 func ReleaseProblems(g *git.Repo, commit string) []string {
98 95 if commit == "" {
99 96 return nil
100 97 }
@@ -104,7 +101,7 @@ func Problems(g *gitx.Repo, commit string) []string {
104 101 }
105 102 var problems []string
106 103 for _, e := range blobs {
107 if !strings.HasPrefix(e.Name, Dir+"/") {
104 if !strings.HasPrefix(e.Name, ReleasesDir+"/") {
108 105 continue
109 106 }
110 107 parts := strings.Split(e.Name, "/")
@@ -112,13 +109,13 @@ func Problems(g *gitx.Repo, commit string) []string {
112 109 case len(parts) != 3:
113 110 problems = append(problems, fmt.Sprintf(
114 111 "%s: releases are %s/<tag>/<name>; a release directory holds files, not more directories",
115 e.Name, Dir))
112 e.Name, ReleasesDir))
116 113 continue
117 114 case parts[1] == "":
118 115 problems = append(problems, fmt.Sprintf("%s: missing tag name", e.Name))
119 116 continue
120 117 }
121 if err := ValidName(parts[2]); err != nil {
118 if err := ValidReleaseName(parts[2]); err != nil {
122 119 problems = append(problems, e.Name+": "+err.Error())
123 120 continue
124 121 }
@@ -136,7 +133,20 @@ func Problems(g *gitx.Repo, commit string) []string {
136 133
137 134 // TagWithArtifacts reports whether a tag has an artifacts directory. The release
138 135 // page uses it to say "no artifacts" without a second tree read.
139 func TagWithArtifacts(g *gitx.Repo, tag string) bool {
140 _, err := g.LsTree("refs/heads/vole:" + Dir + "/" + tag)
136 func TagWithArtifacts(g *git.Repo, tag string) bool {
137 _, err := g.LsTree("refs/heads/vole:" + ReleasesDir + "/" + tag)
141 138 return err == nil
142 139 }
140
141 // WritePointers records artifacts on the metadata branch, one literal LFS
142 // pointer per name. It starts from the branch's current tree, so applying it
143 // again after a lost push race is idempotent.
144 func WriteReleasePointers(g *git.Repo, ref, tag string, arts []Artifact, message, author string) error {
145 _, err := git.CommitFiles(g, ref, func(files map[string][]byte) error {
146 for _, a := range arts {
147 files[ReleasePath(tag, a.Name)] = lfs.Pointer{OID: a.OID, Size: a.Size}.Encode()
148 }
149 return nil
150 }, message, author)
151 return err
152 }