detached 98d36bf

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}

Keyboard shortcuts

Single-key shortcuts are on. ctrl k and esc work whatever this says.

ctrl k :
go to a section, branch, tag, file or repository
?
this list
j k
move the row cursor down / up
enter
open the row under the cursor; on Files, a directory opens or closes in place
↑ ↓ ← → home end
in the file tree (focus on a row): previous / next row, close / open a directory or go to its parent / first entry, first / last row. They never open a file
l h
on Files: open a directory or a file / close a directory or go up to its parent, then to the parent page. Elsewhere: into the side panel (on a merge request, its files) / back to the content; there j k move between its controls
g g G
first / last row
/
filter the list; on Files, find a file anywhere in the repository (esc clears, enter opens the best match)
b
switch branch or tag
g s g f g c g b g m g i g r g a
tabs: summary, files, commits, branches & tags, merge requests, issues, releases, actions (when available)
g h
home
w
wide mode: use the whole width of the screen, or not (remembered)
y
copy a permalink pinned to the commit
d u x
diff: split / unified / collapse or expand all
e E
diff: the whole file in view / every file, with the lines the hunks hide
n
merge request: next note in the diff
esc
close a dialog, leave a field

Branches 4

Tags 45

↑↓ moveenter openesc close