v0.5.0 bfab294

git_test.go RawHistoryFolder

1// SPDX-License-Identifier: AGPL-3.0-or-later
2
3package domain_test
4
5import (
6	"strings"
7	"testing"
8
9	"github.com/stretchr/testify/assert"
10	"github.com/stretchr/testify/require"
11
12	"bnema.dev/ermite/internal/domain"
13)
14
15func TestParseObjectID(t *testing.T) {
16	good := strings.Repeat("ab12", 10)
17	id, err := domain.ParseObjectID(good)
18	require.NoError(t, err)
19	assert.Equal(t, good, id.String())
20	assert.False(t, id.IsZero())
21	assert.True(t, domain.ObjectID{}.IsZero())
22
23	for _, bad := range []string{"", "abc", strings.ToUpper(good), good + "0", good[:39], strings.Repeat("g", 40), "main"} {
24		_, err := domain.ParseObjectID(bad)
25		assert.ErrorIs(t, err, domain.ErrInvalidObjectID, bad)
26	}
27}
28
29func TestParseRevision(t *testing.T) {
30	valid := []string{
31		"", "main", "feature/x", "refs/heads/feature/x", "v1.0.0", "HEAD",
32		"abc1234", strings.Repeat("a", 40), "release-2026.10", "café",
33	}
34	for _, s := range valid {
35		r, err := domain.ParseRevision(s)
36		require.NoError(t, err, s)
37		assert.Equal(t, s, r.String())
38	}
39	invalid := []string{
40		"-x", "--upload-pack=x", "/x", "x/", "x.", "a..b", "a@{1}", "@", "a b", "a~1", "a^", "a:b", "a?", "a*", "a[",
41		"a\\b", "a\nb", "a\x00b", "a//b", ".hidden", "a/.b", "x.lock", "a/x.lock/b", "\xff", strings.Repeat("a", 513),
42	}
43	for _, s := range invalid {
44		_, err := domain.ParseRevision(s)
45		assert.ErrorIs(t, err, domain.ErrInvalidRevision, "%q", s)
46	}
47}
48
49func TestRevisionKinds(t *testing.T) {
50	head, _ := domain.ParseRevision("")
51	assert.True(t, head.IsHead())
52	full, _ := domain.ParseRevision(strings.Repeat("a", 40))
53	assert.True(t, full.IsObjectID())
54	assert.False(t, full.IsAbbrevID())
55	short, _ := domain.ParseRevision("abcdef1")
56	assert.True(t, short.IsAbbrevID())
57	tooShort, _ := domain.ParseRevision("abcdef")
58	assert.False(t, tooShort.IsAbbrevID())
59	branch, _ := domain.ParseRevision("deadbeefx")
60	assert.False(t, branch.IsAbbrevID())
61
62	id, _ := domain.ParseObjectID(strings.Repeat("c", 40))
63	assert.True(t, domain.RevisionOf(id).IsObjectID())
64}
65
66func TestParseTreePath(t *testing.T) {
67	root, err := domain.ParseTreePath("")
68	require.NoError(t, err)
69	assert.True(t, root.IsRoot())
70	assert.Equal(t, "x", root.Join("x"))
71
72	p, err := domain.ParseTreePath("a/b/")
73	require.NoError(t, err)
74	assert.Equal(t, "a/b", p.String())
75	assert.Equal(t, "a/b/c", p.Join("c"))
76
77	for _, bad := range []string{"/a", "a//b", "./a", "a/..", "..", ".", "a\x00b", "a\nb", "\xff", strings.Repeat("a", 4097), "//"} {
78		_, err := domain.ParseTreePath(bad)
79		assert.ErrorIs(t, err, domain.ErrInvalidPath, "%q", bad)
80	}
81}
82
83func TestEntryKindOf(t *testing.T) {
84	tests := map[uint32]domain.EntryKind{
85		0o100644: domain.EntryFile,
86		0o100664: domain.EntryFile,
87		0o100755: domain.EntryExecutable,
88		0o040000: domain.EntryDirectory,
89		0o120000: domain.EntrySymlink,
90		0o160000: domain.EntrySubmodule,
91		0o000000: "",
92		0o060000: "",
93	}
94	for mode, want := range tests {
95		assert.Equal(t, want, domain.EntryKindOf(mode), "%o", mode)
96	}
97}
98
99func TestDefaultDiffLimits(t *testing.T) {
100	assert.Equal(t, domain.DiffLimits{MaxFiles: 300, MaxFileLines: 2000, MaxTotalLines: 20000, MaxTotalBytes: 8 << 20}, domain.DefaultDiffLimits())
101}

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