diff options
| author | 2026-03-06 11:54:21 +0800 | |
|---|---|---|
| committer | 2026-03-06 11:55:56 +0800 | |
| commit | c62c5544fa23378843a3383a9dcd4494e5ea33bc (patch) | |
| tree | 8b825a36767fe0ba3fb44f27cb634047c4c0318f /format/commitgraph/read/open_chain.go | |
| parent | format/pack/ingest: Fix delta apply import (diff) | |
| signature | No signature | |
format/commitgraph: Split into ./read and ./ v0.1.60
Diffstat (limited to 'format/commitgraph/read/open_chain.go')
| -rw-r--r-- | format/commitgraph/read/open_chain.go | 133 |
1 files changed, 133 insertions, 0 deletions
diff --git a/format/commitgraph/read/open_chain.go b/format/commitgraph/read/open_chain.go new file mode 100644 index 00000000..f64040bc --- /dev/null +++ b/format/commitgraph/read/open_chain.go @@ -0,0 +1,133 @@ +package read + +import ( + "bufio" + "errors" + "fmt" + "os" + "strings" + + "codeberg.org/lindenii/furgit/internal/intconv" + "codeberg.org/lindenii/furgit/objectid" +) + +func openChain(root *os.Root, algo objectid.Algorithm) (*Reader, error) { + chainPath := "info/commit-graphs/commit-graph-chain" + + file, err := root.Open(chainPath) + if err != nil { + if errors.Is(err, os.ErrNotExist) { + return nil, &ErrMalformed{Path: chainPath, Reason: "missing commit-graph-chain"} + } + + return nil, err + } + + scanner := bufio.NewScanner(file) + hashes := make([]string, 0) + + for scanner.Scan() { + line := strings.TrimSpace(scanner.Text()) + if line == "" { + continue + } + + hashes = append(hashes, line) + } + + scanErr := scanner.Err() + closeErr := file.Close() + + if scanErr != nil { + return nil, scanErr + } + + if closeErr != nil { + return nil, closeErr + } + + if len(hashes) == 0 { + return nil, &ErrMalformed{Path: chainPath, Reason: "empty chain"} + } + + layers := make([]layer, 0, len(hashes)) + + var total uint32 + + hashVersion, err := intconv.Uint32ToUint8(algo.PackHashID()) + if err != nil { + return nil, err + } + + for i, hashHex := range hashes { + expectedBaseCount, err := intconv.IntToUint32(i) + if err != nil { + closeLayers(layers) + + return nil, err + } + + if len(hashHex) != algo.HexLen() { + closeLayers(layers) + + return nil, &ErrMalformed{ + Path: chainPath, + Reason: fmt.Sprintf("invalid graph hash length at line %d", i+1), + } + } + + relPath := fmt.Sprintf("info/commit-graphs/graph-%s.graph", hashHex) + + loaded, loadErr := openLayer(root, relPath, algo) + if loadErr != nil { + closeLayers(layers) + + return nil, loadErr + } + + if loaded.baseCount != expectedBaseCount { + _ = loaded.close() + + closeLayers(layers) + + return nil, &ErrMalformed{ + Path: relPath, + Reason: fmt.Sprintf("BASE count %d does not match chain depth %d", loaded.baseCount, i), + } + } + + validateErr := validateChainBaseHashes(algo, hashes, i, loaded) + if validateErr != nil { + _ = loaded.close() + + closeLayers(layers) + + return nil, validateErr + } + + loaded.globalFrom = total + loaded.baseCount = expectedBaseCount + + totalNext := total + loaded.numCommits + if totalNext < total { + _ = loaded.close() + + closeLayers(layers) + + return nil, &ErrMalformed{Path: relPath, Reason: "total commit count overflow"} + } + + total = totalNext + + layers = append(layers, *loaded) + } + + out := &Reader{ + algo: algo, + hashVersion: hashVersion, + layers: layers, + total: total, + } + + return out, nil +} |
