Fast implementation of Git in pure Go
at master 133 lines 2.5 kB view raw
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}