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