Fast implementation of Git in pure Go
1package read
2
3import (
4 "bufio"
5 "errors"
6 "fmt"
7 "os"
8 "strings"
9
10 "codeberg.org/lindenii/furgit/internal/intconv"
11 "codeberg.org/lindenii/furgit/objectid"
12)
13
14func openChain(root *os.Root, algo objectid.Algorithm) (*Reader, error) {
15 chainPath := "info/commit-graphs/commit-graph-chain"
16
17 file, err := root.Open(chainPath)
18 if err != nil {
19 if errors.Is(err, os.ErrNotExist) {
20 return nil, &MalformedError{Path: chainPath, Reason: "missing commit-graph-chain"}
21 }
22
23 return nil, err
24 }
25
26 scanner := bufio.NewScanner(file)
27 hashes := make([]string, 0)
28
29 for scanner.Scan() {
30 line := strings.TrimSpace(scanner.Text())
31 if line == "" {
32 continue
33 }
34
35 hashes = append(hashes, line)
36 }
37
38 scanErr := scanner.Err()
39 closeErr := file.Close()
40
41 if scanErr != nil {
42 return nil, scanErr
43 }
44
45 if closeErr != nil {
46 return nil, closeErr
47 }
48
49 if len(hashes) == 0 {
50 return nil, &MalformedError{Path: chainPath, Reason: "empty chain"}
51 }
52
53 layers := make([]layer, 0, len(hashes))
54
55 var total uint32
56
57 hashVersion, err := intconv.Uint32ToUint8(algo.PackHashID())
58 if err != nil {
59 return nil, err
60 }
61
62 for i, hashHex := range hashes {
63 expectedBaseCount, err := intconv.IntToUint32(i)
64 if err != nil {
65 closeLayers(layers)
66
67 return nil, err
68 }
69
70 if len(hashHex) != algo.HexLen() {
71 closeLayers(layers)
72
73 return nil, &MalformedError{
74 Path: chainPath,
75 Reason: fmt.Sprintf("invalid graph hash length at line %d", i+1),
76 }
77 }
78
79 relPath := fmt.Sprintf("info/commit-graphs/graph-%s.graph", hashHex)
80
81 loaded, loadErr := openLayer(root, relPath, algo)
82 if loadErr != nil {
83 closeLayers(layers)
84
85 return nil, loadErr
86 }
87
88 if loaded.baseCount != expectedBaseCount {
89 _ = loaded.close()
90
91 closeLayers(layers)
92
93 return nil, &MalformedError{
94 Path: relPath,
95 Reason: fmt.Sprintf("BASE count %d does not match chain depth %d", loaded.baseCount, i),
96 }
97 }
98
99 validateErr := validateChainBaseHashes(algo, hashes, i, loaded)
100 if validateErr != nil {
101 _ = loaded.close()
102
103 closeLayers(layers)
104
105 return nil, validateErr
106 }
107
108 loaded.globalFrom = total
109 loaded.baseCount = expectedBaseCount
110
111 totalNext := total + loaded.numCommits
112 if totalNext < total {
113 _ = loaded.close()
114
115 closeLayers(layers)
116
117 return nil, &MalformedError{Path: relPath, Reason: "total commit count overflow"}
118 }
119
120 total = totalNext
121
122 layers = append(layers, *loaded)
123 }
124
125 out := &Reader{
126 algo: algo,
127 hashVersion: hashVersion,
128 layers: layers,
129 total: total,
130 }
131
132 return out, nil
133}