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/git/vole.git

Write over SSH with a registered key:

git clone ssh://git@0.0.0.0:2222/vole.git

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

Code

392 commit(s) on main

Merge change request 01M70WES5ZZDJ6RP226WB11BBZ: Let people rename and remove their own passkeys keenan · 1h ago 4b8c9e1d
history blame raw 3.3 KiB
1package hook
2
3// Published releases are immutable, and this is the only place that can enforce
4// it: the tag and the pointers arrive in ordinary pushes, and nothing else sees
5// both. A download link whose bytes can change is worse than no link — the ETag
6// moves with the bytes, so a cache tells the truth while the human who saved the
7// old file has no way to notice.
8
9import (
10 "fmt"
11 "sort"
12 "strings"
13
14 "rodent.software/vole/core/git"
15 "rodent.software/vole/metadata"
16)
17
18// releaseFindings reports the changes that would detach a published artifact from
19// the bytes its link serves:
20//
21// - moving or deleting a tag that releases/<tag>/ holds artifacts for;
22// - deleting a pointer, or re-pointing one at different bytes.
23//
24// Adding artifacts is allowed: an artifact is published when its pointer lands, so
25// there is nothing for an addition to invalidate.
26func releaseFindings(g *git.Repo, c change) []string {
27 if strings.HasPrefix(c.ref, "refs/tags/") {
28 name := strings.TrimPrefix(c.ref, "refs/tags/")
29 if !tagHasArtifacts(g, name) {
30 return nil
31 }
32 verb := "moved"
33 if c.new == git.ZeroSHA || c.new == "" {
34 verb = "deleted"
35 }
36 return []string{fmt.Sprintf(
37 "%s: this tag has release artifacts attached, so it cannot be %s; "+
38 "a release is immutable once published. Tag the new commit with a new name",
39 c.ref, verb)}
40 }
41 if c.ref != metadata.Ref {
42 return nil
43 }
44 if c.old == "" || c.old == git.ZeroSHA {
45 return nil // a new metadata branch publishes nothing old
46 }
47 before := releaseBlobs(g, c.old)
48 if len(before) == 0 {
49 return nil
50 }
51 after := releaseBlobs(g, c.new) // an empty map when the branch is deleted
52 var problems []string
53 for path, sha := range before {
54 now, ok := after[path]
55 switch {
56 case !ok:
57 problems = append(problems, fmt.Sprintf(
58 "%s: a published artifact cannot be removed; publish a new release instead", path))
59 case now != sha:
60 problems = append(problems, fmt.Sprintf(
61 "%s: a published artifact cannot be re-pointed at different bytes; "+
62 "attach the new file under a new name", path))
63 }
64 }
65 sort.Strings(problems)
66 return problems
67}
68
69// releaseBlobs maps every path under releases/ in a commit to its blob id. The
70// blob id is the pointer text, so an unchanged artifact has an unchanged id: the
71// comparison needs no parsing and cannot be fooled by a reformatted pointer.
72func releaseBlobs(g *git.Repo, commit string) map[string]string {
73 if commit == "" || commit == git.ZeroSHA {
74 return nil
75 }
76 entries, err := g.LsTreeR(commit)
77 if err != nil {
78 return nil
79 }
80 out := map[string]string{}
81 for _, e := range entries {
82 if strings.HasPrefix(e.Name, metadata.ReleasesDir+"/") {
83 out[e.Name] = e.SHA
84 }
85 }
86 return out
87}
88
89// tagHasArtifacts reports whether the metadata branch has a releases/<tag>/
90// directory. It reads the current tip rather than the pushed tree: a tag push and
91// a pointer push can arrive in either order, and a tag is only bound to artifacts
92// that already exist.
93func tagHasArtifacts(g *git.Repo, tag string) bool {
94 tip, err := g.RevParse(metadata.Ref)
95 if err != nil || tip == "" {
96 return false
97 }
98 entries, err := g.LsTreeR(tip)
99 if err != nil {
100 return false
101 }
102 prefix := metadata.ReleasesDir + "/" + tag + "/"
103 for _, e := range entries {
104 if strings.HasPrefix(e.Name, prefix) {
105 return true
106 }
107 }
108 return false
109}
110