internal/gitutil/lfs.go
79 lines · 1982 bytes
1package gitutil
2
3import (
4 "bufio"
5 "bytes"
6 "os/exec"
7 "regexp"
8 "strconv"
9 "strings"
10
11 "gitbay.org/gitbay/internal/toolpath"
12)
13
14// lfsPointerMax bounds a pointer file; real ones are around 130 bytes.
15const lfsPointerMax = 1024
16
17var lfsOIDLine = regexp.MustCompile(`(?m)^oid sha256:([0-9a-f]{64})$`)
18
19// LFSPointerOIDs returns every LFS object id referenced by a pointer blob
20// anywhere in the repository: every object, not just the reachable ones,
21// since an unreachable blob is still an object gc has not removed.
22func LFSPointerOIDs(dir string) ([]string, error) {
23 list := exec.Command(toolpath.Look("git"), "-C", dir, "cat-file", "--batch-all-objects",
24 "--batch-check=%(objecttype) %(objectsize) %(objectname)")
25 out, err := list.Output()
26 if err != nil {
27 return nil, err
28 }
29 var small []string
30 sc := bufio.NewScanner(bytes.NewReader(out))
31 for sc.Scan() {
32 f := strings.Fields(sc.Text())
33 if len(f) != 3 || f[0] != "blob" {
34 continue
35 }
36 if n, err := strconv.Atoi(f[1]); err == nil && n <= lfsPointerMax {
37 small = append(small, f[2])
38 }
39 }
40 if len(small) == 0 {
41 return nil, nil
42 }
43 cat := exec.Command(toolpath.Look("git"), "-C", dir, "cat-file", "--batch")
44 cat.Stdin = strings.NewReader(strings.Join(small, "\n") + "\n")
45 out, err = cat.Output()
46 if err != nil {
47 return nil, err
48 }
49 // --batch output: "<sha> blob <size>\n<content>\n" per object.
50 var oids []string
51 rest := out
52 for len(rest) > 0 {
53 nl := bytes.IndexByte(rest, '\n')
54 if nl < 0 {
55 break
56 }
57 hdr := strings.Fields(string(rest[:nl]))
58 rest = rest[nl+1:]
59 if len(hdr) != 3 {
60 break
61 }
62 size, _ := strconv.Atoi(hdr[2])
63 if size > len(rest) {
64 break
65 }
66 body := rest[:size]
67 rest = rest[size:]
68 if len(rest) > 0 && rest[0] == '\n' {
69 rest = rest[1:]
70 }
71 if !bytes.HasPrefix(body, []byte("version https://git-lfs.github.com/spec/")) {
72 continue
73 }
74 if m := lfsOIDLine.FindSubmatch(body); m != nil {
75 oids = append(oids, string(m[1]))
76 }
77 }
78 return oids, nil
79}