ci_workflow_guard.go RawHistoryFolder
1// SPDX-License-Identifier: AGPL-3.0-or-later
2
3package domain
4
5import "fmt"
6
7// PushObjectType is the type of a peeled Git object.
8type PushObjectType string
9
10// Peeled object types accepted by the push tree reader.
11const (
12 PushObjectCommit PushObjectType = "commit"
13 PushObjectTree PushObjectType = "tree"
14 PushObjectBlob PushObjectType = "blob"
15)
16
17// PushWalkBounds bounds the walk over the commits of a push.
18type PushWalkBounds struct{ Commits, IDs int }
19
20// Bounds fail closed and count repeated parent IDs too.
21const (
22 MaxPushedCommits = 10000
23 MaxPushedIDs = 4 * MaxPushedCommits
24)
25
26// DefaultPushWalkBounds returns the job-token push walk limits.
27func DefaultPushWalkBounds() PushWalkBounds { return PushWalkBounds{MaxPushedCommits, MaxPushedIDs} }
28
29// Subtree is the kind and identity of an entry at a tree path.
30type Subtree struct {
31 Kind EntryKind
32 ID ObjectID
33}
34
35// PushedCommit is a newly reachable commit with its ordered parents.
36type PushedCommit struct {
37 ID ObjectID
38 Parents []ObjectID
39}
40
41// HeadKey identifies the default branch head in PushSubtrees.Subtrees.
42const HeadKey = "HEAD"
43
44// PushSubtrees contains endpoint types and entries keyed by original object ID
45// (or HeadKey), and newly reachable commits in rev-list order. Absent entries
46// mean there is no subtree at the requested path.
47type PushSubtrees struct {
48 Ends map[string]PushObjectType
49 Commits []PushedCommit
50 Subtrees map[string]Subtree
51}
52
53// WorkflowTreeChange is the first forbidden workflow-tree replacement.
54// A zero Old denotes no workflow directory.
55type WorkflowTreeChange struct{ Old, New ObjectID }
56
57// WorkflowChangeOf applies the job-token workflow policy. Every new commit must
58// keep the workflows of at least one parent; then each ref must keep its old
59// workflows (HEAD's for a new ref). Disappearance and ref deletion are allowed.
60// New commits are judged before ref ends, in their supplied order. Missing ends
61// are a reader contract breach and fail closed with ErrInvalid.
62func WorkflowChangeOf(updates []RefUpdate, t PushSubtrees) (WorkflowTreeChange, bool, error) {
63 for _, up := range updates {
64 if up.IsDelete() {
65 continue
66 }
67 ids := []string{up.New}
68 if !up.IsCreate() {
69 ids = append(ids, up.Old)
70 }
71 for _, id := range ids {
72 if _, ok := t.Ends[id]; !ok {
73 return WorkflowTreeChange{}, false, fmt.Errorf("%w: missing push end %s", ErrInvalid, id)
74 }
75 }
76 }
77 dirOf := func(key string) ObjectID {
78 s := t.Subtrees[key]
79 if s.Kind != EntryDirectory {
80 return ObjectID{}
81 }
82 return s.ID
83 }
84 for _, c := range t.Commits {
85 cur := dirOf(c.ID.String())
86 if cur.IsZero() {
87 continue
88 }
89 var first ObjectID
90 same := false
91 for i, p := range c.Parents {
92 w := dirOf(p.String())
93 if i == 0 {
94 first = w
95 }
96 if w == cur {
97 same = true
98 }
99 }
100 if !same {
101 return WorkflowTreeChange{first, cur}, true, nil
102 }
103 }
104 for _, up := range updates {
105 if up.IsDelete() || t.Ends[up.New] == PushObjectBlob {
106 continue
107 }
108 cur := dirOf(up.New)
109 if cur.IsZero() {
110 continue
111 }
112 var base ObjectID
113 if up.IsCreate() {
114 base = dirOf(HeadKey)
115 } else if t.Ends[up.Old] != PushObjectBlob {
116 base = dirOf(up.Old)
117 }
118 if base != cur {
119 return WorkflowTreeChange{base, cur}, true, nil
120 }
121 }
122 return WorkflowTreeChange{}, false, nil
123}