internal/gitutil/lfs.go

bd5cf5d7d1f34fa780660fd7562b9ffd9746ee27
gitbay/internal/gitutil/lfs.go history · blame · raw

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}