aboutsummaryrefslogtreecommitdiff
path: root/object/store/packed/internal/ingest/record_resolve.go
blob: 7a9471dcd85b0ad97ab5f01618b8a4c599b2635d (about) (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
package ingest

import (
	"fmt"

	objecttype "codeberg.org/lindenii/furgit/object/type"
)

// resolveRecord resolves one record and returns canonical type/content.
func resolveRecord(state *ingestState, idx int, visiting map[int]struct{}) (objecttype.Type, []byte, error) {
	if idx < 0 || idx >= len(state.records) {
		return objecttype.TypeInvalid, nil, fmt.Errorf("packfile/ingest: record index out of bounds")
	}

	if _, ok := visiting[idx]; ok {
		return objecttype.TypeInvalid, nil, &DeltaCycleError{Offset: state.records[idx].offset}
	}

	visiting[idx] = struct{}{}
	defer delete(visiting, idx)

	record := &state.records[idx]
	if ty, content, ok := state.baseCache.get(idx); ok {
		return ty, content, nil
	}

	if record.packedType.IsBaseObject() {
		ty, content, err := readBaseRecordContent(state, idx)
		if err != nil {
			return objecttype.TypeInvalid, nil, err
		}

		if record.resolved {
			state.baseCache.add(idx, record.realType, content)

			return record.realType, content, nil
		}

		id, err := hashCanonicalObject(state.algo, ty, content)
		if err != nil {
			return objecttype.TypeInvalid, nil, err
		}

		record.objectID = id
		record.realType = ty
		record.resolved = true
		state.objectToRecord[id] = idx
		state.baseCache.add(idx, ty, content)

		return ty, content, nil
	}

	var (
		baseType    objecttype.Type
		baseContent []byte
		err         error
	)
	switch record.packedType {
	case objecttype.TypeOfsDelta:
		baseIdx, ok := state.offsetToRecord[record.baseOffset]
		if !ok {
			return objecttype.TypeInvalid, nil, &MalformedPackEntryError{
				Offset: record.offset,
				Reason: "missing ofs-delta base entry",
			}
		}

		baseType, baseContent, err = resolveRecord(state, baseIdx, visiting)
		if err != nil {
			return objecttype.TypeInvalid, nil, err
		}
	case objecttype.TypeRefDelta:
		baseIdx, ok := state.objectToRecord[record.baseObject]
		if ok {
			baseType, baseContent, err = resolveRecord(state, baseIdx, visiting)
			if err != nil {
				return objecttype.TypeInvalid, nil, err
			}
		} else {
			return objecttype.TypeInvalid, nil, errExternalThinBase
		}
	case objecttype.TypeInvalid,
		objecttype.TypeCommit,
		objecttype.TypeTree,
		objecttype.TypeBlob,
		objecttype.TypeTag,
		objecttype.TypeFuture:
		return objecttype.TypeInvalid, nil, &MalformedPackEntryError{
			Offset: record.offset,
			Reason: "unsupported delta type",
		}
	default:
		return objecttype.TypeInvalid, nil, &MalformedPackEntryError{
			Offset: record.offset,
			Reason: "unsupported delta type",
		}
	}

	ty, content, err := applyDeltaRecord(state, idx, baseType, baseContent)
	if err != nil {
		return objecttype.TypeInvalid, nil, err
	}

	id, err := hashCanonicalObject(state.algo, ty, content)
	if err != nil {
		return objecttype.TypeInvalid, nil, err
	}

	record.objectID = id
	record.realType = ty
	record.resolved = true
	state.objectToRecord[id] = idx
	state.baseCache.add(idx, ty, content)

	return ty, content, nil
}