internal/gitutil/lfs.go

7cc95d1fc1115f22008751a05e9e07156d49d6b0
gitbay/internal/gitutil/lfs.go history · blame · raw

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}