ci_pullattempts.go RawHistoryFolder
1// SPDX-License-Identifier: AGPL-3.0-or-later
2
3package usecase
4
5import (
6 "sync"
7
8 "bnema.dev/ermite/internal/domain"
9)
10
11// DefaultCIPullAttempts bounds the merge requests whose last conflicting merge
12// is remembered.
13const DefaultCIPullAttempts = 4096
14
15// pullAttempt is the pair of tips a merge was tried on.
16type pullAttempt struct{ base, head domain.ObjectID }
17
18// pullAttempts remembers, per merge request, the tips of the last test merge
19// that conflicted. A merge request whose head conflicts has no merge ref, so it
20// never looks up to date to a rescan: without this memory every rescan would try
21// the same merge again, for ever. A rescan skips a merge request while its tips
22// are the remembered ones, and tries it again as soon as one moves. Only
23// conflicts are remembered: a merge that went through is judged by its refs.
24//
25// A least recently used cache bounded by max: a conflict that was forgotten is
26// tried once more by the next rescan, never more often than that.
27type pullAttempts struct {
28 mu sync.Mutex
29 max int
30 items map[pullKey]pullAttempt
31 order []pullKey // oldest use first
32}
33
34func newPullAttempts(max int) *pullAttempts {
35 if max <= 0 {
36 max = DefaultCIPullAttempts
37 }
38 return &pullAttempts{max: max, items: map[pullKey]pullAttempt{}}
39}
40
41// tried reports whether the last conflict of k was on these tips.
42func (a *pullAttempts) tried(k pullKey, base, head domain.ObjectID) bool {
43 a.mu.Lock()
44 defer a.mu.Unlock()
45 at, ok := a.items[k]
46 if ok {
47 a.touch(k)
48 }
49 return ok && at.base == base && at.head == head
50}
51
52// conflicted remembers that the merge of head into base conflicted.
53func (a *pullAttempts) conflicted(k pullKey, base, head domain.ObjectID) {
54 a.mu.Lock()
55 defer a.mu.Unlock()
56 if _, ok := a.items[k]; !ok && len(a.items) >= a.max {
57 delete(a.items, a.order[0])
58 a.order = a.order[1:]
59 }
60 a.items[k] = pullAttempt{base: base, head: head}
61 a.touch(k)
62}
63
64// merged forgets k: its last merge went through.
65func (a *pullAttempts) merged(k pullKey) {
66 a.mu.Lock()
67 defer a.mu.Unlock()
68 if _, ok := a.items[k]; !ok {
69 return
70 }
71 delete(a.items, k)
72 for i, o := range a.order {
73 if o == k {
74 a.order = append(a.order[:i], a.order[i+1:]...)
75 break
76 }
77 }
78}
79
80// touch makes k the most recently used. The caller holds mu.
81func (a *pullAttempts) touch(k pullKey) {
82 for i, o := range a.order {
83 if o == k {
84 a.order = append(a.order[:i], a.order[i+1:]...)
85 break
86 }
87 }
88 a.order = append(a.order, k)
89}