1// SPDX-License-Identifier: AGPL-3.0-or-later
2
3package domain
4
5import (
6 "fmt"
7 "math"
8)
9
10// ValidateCommitObjects checks a request before any repository I/O. max is the
11// caller's commit-count limit.
12func ValidateCommitObjects(ids []ObjectID, max int) error {
13 if len(ids) > max {
14 return fmt.Errorf("%w: %d commit objects asked, at most %d", ErrInvalid, len(ids), max)
15 }
16 for _, id := range ids {
17 if id.IsZero() {
18 return fmt.Errorf("%w: empty commit id", ErrInvalidObjectID)
19 }
20 }
21 return nil
22}
23
24// CommitObjectBudget bounds a batch of raw commits. Its zero value is ready to
25// use. Admission is a preview; Charge records only successfully read content.
26type CommitObjectBudget struct {
27 total int64
28 seen map[ObjectID]bool
29}
30
31func (b *CommitObjectBudget) Seen(id ObjectID) bool { return b.seen[id] }
32
33func (b *CommitObjectBudget) Admit(id ObjectID, commit bool, size int64) (take, stop bool) {
34 if b.Seen(id) || !commit || size < 0 || size > MaxCommitObjectBytes {
35 return false, false
36 }
37 if size > MaxCommitObjectsBytes-b.total {
38 return false, true
39 }
40 return true, false
41}
42
43func (b *CommitObjectBudget) Charge(id ObjectID, size int64) {
44 if b.seen == nil {
45 b.seen = make(map[ObjectID]bool)
46 }
47 b.seen[id] = true
48 b.total += size
49}
50
51// DiffBudget owns file selection and retained hunk accounting. Omitted files
52// consume no budget; streaming readers may use a copy while attempting a file.
53type DiffBudget struct {
54 limits DiffLimits
55 linesLeft, bytesLeft int
56 // fileBytes is bytesLeft when the current file started (StartFile).
57 fileBytes int
58}
59
60func NewDiffBudget(limits DiffLimits) DiffBudget {
61 return DiffBudget{limits: limits, linesLeft: limits.MaxTotalLines, bytesLeft: limits.MaxTotalBytes, fileBytes: limits.MaxTotalBytes}
62}
63
64func (b DiffBudget) Window(files int) (kept, from, to int, truncated bool) {
65 kept = files
66 if b.limits.MaxFiles > 0 && kept > b.limits.MaxFiles {
67 kept = b.limits.MaxFiles
68 truncated = true
69 }
70 from, to = max(b.limits.From, 0), kept
71 if b.limits.To > 0 {
72 to = min(b.limits.To, kept)
73 }
74 return
75}
76
77// Admit decides whether the hunks of a file are read. read is false with
78// OmitNone for a binary file or one without changed lines (nothing to show).
79// Otherwise read is false with the reason the file is omitted. path is the path
80// a comment designates (see FileDiff.CommentPath).
81func (b DiffBudget) Admit(binary bool, path string, changed int) (read bool, why FileOmission) {
82 switch {
83 case binary || changed <= 0:
84 return false, OmitNone
85 case b.limits.SkipGenerated && IsGeneratedPath(path):
86 return false, OmitGenerated
87 case b.limits.MaxAloneLines > 0 && changed > b.limits.MaxAloneLines:
88 return false, OmitUnavailable
89 case b.limits.MaxFileLines > 0 && changed > b.limits.MaxFileLines:
90 return false, OmitLarge
91 case b.limits.MaxTotalLines > 0 && changed > b.linesLeft, b.bytesLeft <= 0:
92 return false, OmitBudget
93 }
94 return true, OmitNone
95}
96
97// FileLines is the most changed lines the next file may have; math.MaxInt when
98// neither MaxFileLines nor MaxTotalLines limits it.
99func (b DiffBudget) FileLines() int {
100 n := math.MaxInt
101 if b.limits.MaxFileLines > 0 {
102 n = b.limits.MaxFileLines
103 }
104 if b.limits.MaxTotalLines > 0 {
105 n = min(n, b.linesLeft)
106 }
107 return n
108}
109
110// StartFile marks the start of a file's lines, for ChargeLine to tell a file
111// over the byte limit on its own from one that met the bytes of earlier files.
112func (b *DiffBudget) StartFile() { b.fileBytes = b.bytesLeft }
113
114// ChargeLine accounts for decoded text bytes and, for additions or deletions,
115// one changed line. Context consumes bytes only. It returns OmitNone when the
116// line fits and OmitBudget when the changed lines ran out. When the bytes run
117// out it returns OmitBudget if files read before this one (since StartFile)
118// spent some of them, as this file may still fit on its own, and
119// OmitUnavailable if this file alone is over MaxTotalBytes.
120func (b *DiffBudget) ChargeLine(bytes int, changed bool) FileOmission {
121 if changed && b.limits.MaxTotalLines > 0 {
122 b.linesLeft--
123 }
124 b.bytesLeft -= bytes
125 switch {
126 case b.limits.MaxTotalLines > 0 && b.linesLeft < 0:
127 return OmitBudget
128 case b.bytesLeft < 0 && b.fileBytes < b.limits.MaxTotalBytes:
129 return OmitBudget
130 case b.bytesLeft < 0:
131 return OmitUnavailable
132 }
133 return OmitNone
134}