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 · 24m ago 4b8c9e1d
history blame raw 4.0 KiB
1package hook
2
3// LFS policy at the gate.
4//
5// Two different rules, because they answer two different mistakes:
6//
7// - A pointer whose object is missing is a broken repository. The checkout
8// cannot be completed by anyone, so the push is refused, before the ref moves.
9// - A large binary committed straight into git is a repository that will get
10// slow, but it is not broken. The push is accepted and the user is told how
11// to move it to LFS.
12
13import (
14 "fmt"
15 "io"
16 "sort"
17 "strings"
18
19 "rodent.software/vole/core/git"
20 "rodent.software/vole/core/lfs"
21 "rodent.software/vole/internal/format"
22)
23
24// lfsFindings scans the blobs of the tree a pushed ref will serve. problems
25// reject the push, warnings are printed and the push proceeds.
26func lfsFindings(g *git.Repo, repo, new string, store *lfs.Store, warnBytes int64) (problems, warnings []string, err error) {
27 if store == nil {
28 return nil, nil, fmt.Errorf("no LFS store configured")
29 }
30 blobs, err := g.TreeBlobs(new)
31 if err != nil {
32 return nil, nil, err
33 }
34 shas := make([]string, 0, len(blobs))
35 for sha := range blobs {
36 shas = append(shas, sha)
37 }
38 sort.Strings(shas) // stable messages, so a test and a human see the same order
39
40 for _, sha := range shas {
41 b := blobs[sha]
42 if b.Size <= lfs.MaxPointerBytes {
43 head, err := g.BlobPrefix(sha, int(lfs.MaxPointerBytes))
44 if err != nil {
45 return nil, nil, err
46 }
47 p, ok := lfs.ParsePointer(head)
48 if !ok {
49 continue
50 }
51 problems = append(problems, pointerProblem(store, repo, p, b.Path)...)
52 continue
53 }
54 if warnBytes <= 0 || b.Size <= warnBytes {
55 continue
56 }
57 head, err := g.BlobPrefix(sha, 8000)
58 if err != nil {
59 return nil, nil, err
60 }
61 if !looksBinary(head) {
62 continue
63 }
64 warnings = append(warnings, fmt.Sprintf(
65 "%s is %s and stored in git. Track it with LFS instead:\n"+
66 " git lfs track %q && git add .gitattributes %s\n"+
67 " (a file already in history needs `git lfs migrate import --include=%q`)",
68 b.Path, format.Bytes(b.Size), trackPattern(b.Path), b.Path, trackPattern(b.Path)))
69 }
70 return problems, warnings, nil
71}
72
73func pointerProblem(store *lfs.Store, repo string, p lfs.Pointer, path string) []string {
74 size, err := store.Stat(repo, p.OID)
75 if err != nil {
76 return []string{fmt.Sprintf(
77 "%s points at LFS object %s (%s), which is not on this server. "+
78 "Upload it first: git lfs push origin <ref> for a tracked file, "+
79 "or vole release attach <tag> <file> for a release artifact",
80 path, oidShort(p.OID), format.Bytes(p.Size))}
81 }
82 if p.Size > 0 && size != p.Size {
83 return []string{fmt.Sprintf(
84 "%s points at LFS object %s and says it is %d bytes, but the stored object is %d",
85 path, oidShort(p.OID), p.Size, size)}
86 }
87 return nil
88}
89
90// oidShort is longer than short(): when the message tells someone which object to
91// upload, twelve hex characters are enough to find it in `git lfs ls-files`
92// without being a wall of hex.
93func oidShort(oid string) string {
94 if len(oid) > 12 {
95 return oid[:12]
96 }
97 return oid
98}
99
100// looksBinary is git's own heuristic, narrowed to "contains a NUL in the first
101// 8 KiB". A file that could be a pointer was already handled: only blobs larger
102// than the pointer cap reach here.
103func looksBinary(head []byte) bool {
104 for _, c := range head {
105 if c == 0 {
106 return true
107 }
108 }
109 return false
110}
111
112// trackPattern suggests the narrowest useful gitattributes pattern: the
113// extension when there is one, otherwise the path itself.
114func trackPattern(path string) string {
115 base := path
116 if i := strings.LastIndexByte(base, '/'); i >= 0 {
117 base = base[i+1:]
118 }
119 if i := strings.LastIndexByte(base, '.'); i > 0 && i < len(base)-1 {
120 return "*" + base[i:]
121 }
122 return path
123}
124
125// printWarnings writes findings the push accepts anyway. They go to the same
126// stream as rejections, which git forwards to the client as remote: lines.
127func printWarnings(w io.Writer, warnings []string) {
128 for _, warn := range warnings {
129 fmt.Fprintf(w, "vole: warning: %s\n", strings.TrimSpace(warn))
130 }
131}
132