v0.34.0 7c1886f

ci_workflow_guard_test.go RawHistoryFolder

1// SPDX-License-Identifier: AGPL-3.0-or-later
2
3package domain_test
4
5import (
6	"fmt"
7	"testing"
8
9	"bnema.dev/ermite/internal/domain"
10	"github.com/stretchr/testify/assert"
11	"github.com/stretchr/testify/require"
12)
13
14func TestWorkflowChangeOf(t *testing.T) {
15	id := func(n int) domain.ObjectID {
16		v, err := domain.ParseObjectID(fmt.Sprintf("%040x", n))
17		require.NoError(t, err)
18		return v
19	}
20	a, b, c, d := id(1), id(2), id(3), id(4)
21	x, y, z := id(11), id(12), id(13)
22	dir := func(v domain.ObjectID) domain.Subtree { return domain.Subtree{Kind: domain.EntryDirectory, ID: v} }
23	up := func(old, new domain.ObjectID) domain.RefUpdate {
24		o, n := old.String(), new.String()
25		if old.IsZero() {
26			o = domain.ZeroOID
27		}
28		if new.IsZero() {
29			n = domain.ZeroOID
30		}
31		return domain.RefUpdate{Old: o, New: n}
32	}
33	zero := domain.ObjectID{}
34	commit := func(v domain.ObjectID, parents ...domain.ObjectID) domain.PushedCommit {
35		return domain.PushedCommit{ID: v, Parents: parents}
36	}
37	type row struct {
38		name     string
39		ups      []domain.RefUpdate
40		commits  []domain.PushedCommit
41		trees    map[string]domain.Subtree
42		treeEnds []domain.ObjectID
43		blobs    []domain.ObjectID
44		want     domain.WorkflowTreeChange
45		found    bool
46		missing  bool
47	}
48	change := func(old, new domain.ObjectID) domain.WorkflowTreeChange {
49		return domain.WorkflowTreeChange{Old: old, New: new}
50	}
51	rows := []row{
52		{name: "no updates"},
53		{name: "delete", ups: []domain.RefUpdate{up(a, zero)}},
54		{name: "new blob ignores even a misleading subtree", ups: []domain.RefUpdate{up(a, b)}, trees: map[string]domain.Subtree{a.String(): dir(x), b.String(): dir(y)}, blobs: []domain.ObjectID{b}},
55		{name: "old blob is not a directory", ups: []domain.RefUpdate{up(a, b)}, trees: map[string]domain.Subtree{a.String(): dir(x), b.String(): dir(y)}, blobs: []domain.ObjectID{a}, want: change(zero, y), found: true},
56		{name: "new ref at HEAD", ups: []domain.RefUpdate{up(zero, a)}, trees: map[string]domain.Subtree{domain.HeadKey: dir(x), a.String(): dir(x)}},
57		{name: "new ref same directory as HEAD", ups: []domain.RefUpdate{up(zero, b)}, trees: map[string]domain.Subtree{domain.HeadKey: dir(x), b.String(): dir(x)}},
58		{name: "new ref old workflows", ups: []domain.RefUpdate{up(zero, b)}, trees: map[string]domain.Subtree{domain.HeadKey: dir(x), b.String(): dir(y)}, want: change(x, y), found: true},
59		{name: "unborn HEAD", ups: []domain.RefUpdate{up(zero, a)}, trees: map[string]domain.Subtree{a.String(): dir(x)}, want: change(zero, x), found: true},
60		{name: "HEAD has no workflows", ups: []domain.RefUpdate{up(zero, a)}},
61		{name: "same workflow directory", ups: []domain.RefUpdate{up(a, b)}, trees: map[string]domain.Subtree{a.String(): dir(x), b.String(): dir(x)}},
62		{name: "move back or remove one file from surviving directory", ups: []domain.RefUpdate{up(a, b)}, trees: map[string]domain.Subtree{a.String(): dir(x), b.String(): dir(y)}, want: change(x, y), found: true},
63		{name: "directory disappears", ups: []domain.RefUpdate{up(a, b)}, trees: map[string]domain.Subtree{a.String(): dir(x)}},
64		{name: "lookalike path absent", ups: []domain.RefUpdate{up(a, b)}},
65		{name: "root with workflows", commits: []domain.PushedCommit{commit(a)}, trees: map[string]domain.Subtree{a.String(): dir(x)}, want: change(zero, x), found: true},
66		{name: "root without workflows", commits: []domain.PushedCommit{commit(a)}},
67		{name: "plain pushed commit", commits: []domain.PushedCommit{commit(b, a)}, trees: map[string]domain.Subtree{a.String(): dir(x), b.String(): dir(x)}},
68		{name: "pushed directory disappears", commits: []domain.PushedCommit{commit(b, a)}, trees: map[string]domain.Subtree{a.String(): dir(x)}},
69		{name: "merge keeps second parent", commits: []domain.PushedCommit{commit(c, a, b)}, trees: map[string]domain.Subtree{a.String(): dir(x), b.String(): dir(y), c.String(): dir(y)}},
70		{name: "merge differs from all parents", commits: []domain.PushedCommit{commit(c, a, b)}, trees: map[string]domain.Subtree{a.String(): dir(x), b.String(): dir(y), c.String(): dir(z)}, want: change(x, z), found: true},
71		{name: "merge new side changes workflows", commits: []domain.PushedCommit{commit(d, a, c), commit(c, b)}, trees: map[string]domain.Subtree{a.String(): dir(x), b.String(): dir(y), c.String(): dir(z), d.String(): dir(x)}, want: change(y, z), found: true},
72		{name: "merge adopts other parent refused by ref rule", ups: []domain.RefUpdate{up(a, c)}, commits: []domain.PushedCommit{commit(c, a, b)}, trees: map[string]domain.Subtree{a.String(): dir(x), b.String(): dir(y), c.String(): dir(y)}, want: change(x, y), found: true},
73		{name: "change then revert", ups: []domain.RefUpdate{up(a, c)}, commits: []domain.PushedCommit{commit(c, b), commit(b, a)}, trees: map[string]domain.Subtree{a.String(): dir(x), b.String(): dir(y), c.String(): dir(x)}, want: change(y, x), found: true},
74		{name: "change below plain tip", commits: []domain.PushedCommit{commit(d, c), commit(c, b)}, trees: map[string]domain.Subtree{b.String(): dir(x), c.String(): dir(y), d.String(): dir(y)}, want: change(x, y), found: true},
75		{name: "all new commits before any ref", ups: []domain.RefUpdate{up(a, b)}, commits: []domain.PushedCommit{commit(d, c)}, trees: map[string]domain.Subtree{a.String(): dir(x), b.String(): dir(y), c.String(): dir(y), d.String(): dir(z)}, want: change(y, z), found: true},
76		{name: "first ref wins", ups: []domain.RefUpdate{up(a, b), up(c, d)}, trees: map[string]domain.Subtree{a.String(): dir(x), b.String(): dir(y), c.String(): dir(y), d.String(): dir(z)}, want: change(x, y), found: true},
77		{name: "plain ref then changed ref", ups: []domain.RefUpdate{up(a, b), up(c, d)}, trees: map[string]domain.Subtree{a.String(): dir(x), b.String(): dir(x), c.String(): dir(y), d.String(): dir(z)}, want: change(y, z), found: true},
78		{name: "missing ends fail closed", ups: []domain.RefUpdate{up(a, b)}, missing: true},
79	}
80	rows = append(rows,
81		row{name: "new tree end with workflows", treeEnds: []domain.ObjectID{a}, ups: []domain.RefUpdate{up(zero, a)}, trees: map[string]domain.Subtree{a.String(): dir(x)}, want: change(zero, x), found: true},
82		row{name: "new tree end without workflows", treeEnds: []domain.ObjectID{a}, ups: []domain.RefUpdate{up(zero, a)}},
83		row{name: "new blob ref", ups: []domain.RefUpdate{up(zero, a)}, blobs: []domain.ObjectID{a}},
84	)
85
86	for _, kind := range []domain.EntryKind{domain.EntrySubmodule, domain.EntrySymlink, domain.EntryFile, domain.EntryExecutable} {
87		rows = append(rows,
88			row{name: string(kind) + " to directory same id", ups: []domain.RefUpdate{up(a, b)}, trees: map[string]domain.Subtree{a.String(): {Kind: kind, ID: x}, b.String(): dir(x)}, want: change(zero, x), found: true},
89			row{name: "directory to " + string(kind), ups: []domain.RefUpdate{up(a, b)}, trees: map[string]domain.Subtree{a.String(): dir(x), b.String(): {Kind: kind, ID: x}}},
90			row{name: "same " + string(kind), ups: []domain.RefUpdate{up(a, b)}, trees: map[string]domain.Subtree{a.String(): {Kind: kind, ID: x}, b.String(): {Kind: kind, ID: x}}},
91		)
92	}
93	rows = append(rows, row{name: "gitlink to symlink", ups: []domain.RefUpdate{up(a, b)}, trees: map[string]domain.Subtree{a.String(): {Kind: domain.EntrySubmodule, ID: x}, b.String(): {Kind: domain.EntrySymlink, ID: y}}})
94	for _, r := range rows {
95		t.Run(r.name, func(t *testing.T) {
96			data := domain.PushSubtrees{Ends: map[string]domain.PushObjectType{}, Commits: r.commits, Subtrees: r.trees}
97			if !r.missing {
98				for _, u := range r.ups {
99					for _, oid := range []string{u.Old, u.New} {
100						data.Ends[oid] = domain.PushObjectCommit
101					}
102				}
103			}
104			for _, tree := range r.treeEnds {
105				data.Ends[tree.String()] = domain.PushObjectTree
106			}
107			for _, b := range r.blobs {
108				data.Ends[b.String()] = domain.PushObjectBlob
109			}
110			got, found, err := domain.WorkflowChangeOf(r.ups, data)
111			if r.missing {
112				require.ErrorIs(t, err, domain.ErrInvalid)
113				return
114			}
115			require.NoError(t, err)
116			assert.Equal(t, r.found, found)
117			assert.Equal(t, r.want, got)
118		})
119	}
120}

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 3

Tags 43

↑↓ moveenter openesc close