detached 616a902

feat(domain): classify generated and vendored files by path

commit
616a9025d9ce4bdcf5ca14ce4df456bb4d4aaa05
author
brice · · 2026-10-20 05:40 UTC
parent
cc7ba64
signature
GPG VerifiedCD88F9E564E0102646FB6077A1A07C7C7C3E349D · arch-gpg Verified for the committer email
2 files · +97 -0
A internal/domain/generated.go +46 -0
@@ -0,0 +1,46 @@
1+// SPDX-License-Identifier: AGPL-3.0-or-later
2+
3+package domain
4+
5+import (
6+ "path"
7+ "strings"
8+)
9+
10+var generatedSuffixes = []string{
11+ "_templ.go", ".pb.go", "_gen.go", ".gen.go",
12+ ".min.js", ".min.css", ".js.map", ".css.map",
13+}
14+
15+var generatedNames = map[string]struct{}{
16+ "go.sum": {}, "package-lock.json": {}, "pnpm-lock.yaml": {}, "yarn.lock": {},
17+ "Cargo.lock": {}, "bun.lockb": {}, "bun.lock": {}, "composer.lock": {},
18+ "Gemfile.lock": {}, "poetry.lock": {}, "uv.lock": {}, "flake.lock": {},
19+}
20+
21+// IsGeneratedPath reports whether a changed file is generated or vendored by
22+// its path alone: its diff is collapsed by default in the web view.
23+func IsGeneratedPath(p string) bool {
24+ if p == "" {
25+ return false
26+ }
27+ base := path.Base(p)
28+ if _, ok := generatedNames[base]; ok {
29+ return true
30+ }
31+ for _, s := range generatedSuffixes {
32+ if strings.HasSuffix(base, s) {
33+ return true
34+ }
35+ }
36+ // Directory segments only: the last segment is the file name.
37+ dir := p[:len(p)-len(base)]
38+ for dir != "" {
39+ seg, rest, _ := strings.Cut(dir, "/")
40+ if seg == "vendor" || seg == "node_modules" {
41+ return true
42+ }
43+ dir = rest
44+ }
45+ return false
46+}
@@ -0,0 +1,46 @@
1+// SPDX-License-Identifier: AGPL-3.0-or-later
2+
3+package domain
4+
5+import (
6+ "path"
7+ "strings"
8+)
9+
10+var generatedSuffixes = []string{
11+ "_templ.go", ".pb.go", "_gen.go", ".gen.go",
12+ ".min.js", ".min.css", ".js.map", ".css.map",
13+}
14+
15+var generatedNames = map[string]struct{}{
16+ "go.sum": {}, "package-lock.json": {}, "pnpm-lock.yaml": {}, "yarn.lock": {},
17+ "Cargo.lock": {}, "bun.lockb": {}, "bun.lock": {}, "composer.lock": {},
18+ "Gemfile.lock": {}, "poetry.lock": {}, "uv.lock": {}, "flake.lock": {},
19+}
20+
21+// IsGeneratedPath reports whether a changed file is generated or vendored by
22+// its path alone: its diff is collapsed by default in the web view.
23+func IsGeneratedPath(p string) bool {
24+ if p == "" {
25+ return false
26+ }
27+ base := path.Base(p)
28+ if _, ok := generatedNames[base]; ok {
29+ return true
30+ }
31+ for _, s := range generatedSuffixes {
32+ if strings.HasSuffix(base, s) {
33+ return true
34+ }
35+ }
36+ // Directory segments only: the last segment is the file name.
37+ dir := p[:len(p)-len(base)]
38+ for dir != "" {
39+ seg, rest, _ := strings.Cut(dir, "/")
40+ if seg == "vendor" || seg == "node_modules" {
41+ return true
42+ }
43+ dir = rest
44+ }
45+ return false
46+}
A internal/domain/generated_test.go +51 -0
@@ -0,0 +1,51 @@
1+// SPDX-License-Identifier: AGPL-3.0-or-later
2+
3+package domain
4+
5+import "testing"
6+
7+func TestIsGeneratedPath(t *testing.T) {
8+ t.Parallel()
9+ tests := []struct {
10+ path string
11+ want bool
12+ }{
13+ {"views/a_templ.go", true},
14+ {"api/x.pb.go", true},
15+ {"x_gen.go", true},
16+ {"x.gen.go", true},
17+ {"static/app.min.js", true},
18+ {"static/app.min.css", true},
19+ {"static/app.js.map", true},
20+ {"static/app.css.map", true},
21+ {"go.sum", true},
22+ {"web/package-lock.json", true},
23+ {"pnpm-lock.yaml", true},
24+ {"yarn.lock", true},
25+ {"Cargo.lock", true},
26+ {"bun.lockb", true},
27+ {"bun.lock", true},
28+ {"composer.lock", true},
29+ {"Gemfile.lock", true},
30+ {"poetry.lock", true},
31+ {"uv.lock", true},
32+ {"flake.lock", true},
33+ {"vendor/x.go", true},
34+ {"a/vendor/b/x.go", true},
35+ {"node_modules/a.js", true},
36+ {"web/node_modules/a.js", true},
37+ {"vendor.go", false},
38+ {"templ.go", false},
39+ {"main.go", false},
40+ {"go.mod", false},
41+ {"minify.js", false},
42+ {"myvendor/x.go", false},
43+ {"x.min.jsx", false},
44+ {"", false},
45+ }
46+ for _, tt := range tests {
47+ if got := IsGeneratedPath(tt.path); got != tt.want {
48+ t.Errorf("IsGeneratedPath(%q) = %v, want %v", tt.path, got, tt.want)
49+ }
50+ }
51+}
@@ -0,0 +1,51 @@
1+// SPDX-License-Identifier: AGPL-3.0-or-later
2+
3+package domain
4+
5+import "testing"
6+
7+func TestIsGeneratedPath(t *testing.T) {
8+ t.Parallel()
9+ tests := []struct {
10+ path string
11+ want bool
12+ }{
13+ {"views/a_templ.go", true},
14+ {"api/x.pb.go", true},
15+ {"x_gen.go", true},
16+ {"x.gen.go", true},
17+ {"static/app.min.js", true},
18+ {"static/app.min.css", true},
19+ {"static/app.js.map", true},
20+ {"static/app.css.map", true},
21+ {"go.sum", true},
22+ {"web/package-lock.json", true},
23+ {"pnpm-lock.yaml", true},
24+ {"yarn.lock", true},
25+ {"Cargo.lock", true},
26+ {"bun.lockb", true},
27+ {"bun.lock", true},
28+ {"composer.lock", true},
29+ {"Gemfile.lock", true},
30+ {"poetry.lock", true},
31+ {"uv.lock", true},
32+ {"flake.lock", true},
33+ {"vendor/x.go", true},
34+ {"a/vendor/b/x.go", true},
35+ {"node_modules/a.js", true},
36+ {"web/node_modules/a.js", true},
37+ {"vendor.go", false},
38+ {"templ.go", false},
39+ {"main.go", false},
40+ {"go.mod", false},
41+ {"minify.js", false},
42+ {"myvendor/x.go", false},
43+ {"x.min.jsx", false},
44+ {"", false},
45+ }
46+ for _, tt := range tests {
47+ if got := IsGeneratedPath(tt.path); got != tt.want {
48+ t.Errorf("IsGeneratedPath(%q) = %v, want %v", tt.path, got, tt.want)
49+ }
50+ }
51+}

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