v0.2.27 9b298e6

gitignore.go RawHistoryFolder

1// Package ignore provides a minimal .gitignore matcher shared across
2// zot. It is intentionally small: enough to drop obvious non-source
3// directories (build outputs, dependency and tool caches) from
4// recursive walks, not a faithful git reimplementation.
5package ignore
6
7import (
8	"os"
9	"path/filepath"
10	"strings"
11)
12
13// Gitignore is a minimal .gitignore matcher. It supports the common
14// patterns used in real repos: blank lines, comments (#), negation (!),
15// directory-only patterns (trailing /), anchored patterns (leading /),
16// and the * / ? / [..] wildcards via filepath.Match. It intentionally
17// does not implement ** globstar or nested per-directory .gitignore
18// files.
19type Gitignore struct {
20	rules []rule
21}
22
23type rule struct {
24	pattern  string
25	negate   bool
26	dirOnly  bool
27	anchored bool
28}
29
30// Load reads the .gitignore at the root directory. A missing or
31// unreadable file yields an empty matcher that ignores nothing.
32func Load(root string) *Gitignore {
33	data, err := os.ReadFile(filepath.Join(root, ".gitignore"))
34	if err != nil {
35		return &Gitignore{}
36	}
37	return Parse(string(data))
38}
39
40// Parse builds a matcher from raw .gitignore file contents.
41func Parse(data string) *Gitignore {
42	g := &Gitignore{}
43	for _, line := range strings.Split(data, "\n") {
44		line = strings.TrimRight(line, "\r")
45		trimmed := strings.TrimSpace(line)
46		if trimmed == "" || strings.HasPrefix(trimmed, "#") {
47			continue
48		}
49		r := rule{pattern: trimmed}
50		if strings.HasPrefix(r.pattern, "!") {
51			r.negate = true
52			r.pattern = r.pattern[1:]
53		}
54		if strings.HasSuffix(r.pattern, "/") {
55			r.dirOnly = true
56			r.pattern = strings.TrimSuffix(r.pattern, "/")
57		}
58		if strings.HasPrefix(r.pattern, "/") {
59			r.anchored = true
60			r.pattern = strings.TrimPrefix(r.pattern, "/")
61		}
62		if r.pattern == "" {
63			continue
64		}
65		g.rules = append(g.rules, r)
66	}
67	return g
68}
69
70// Match reports whether the slash-separated relative path should be
71// ignored. Later rules win, so a trailing negation can re-include a
72// previously ignored path.
73func (g *Gitignore) Match(rel string, isDir bool) bool {
74	ignored := false
75	for _, r := range g.rules {
76		if r.dirOnly && !isDir {
77			continue
78		}
79		if r.matchPath(rel) {
80			ignored = !r.negate
81		}
82	}
83	return ignored
84}
85
86func (r rule) matchPath(rel string) bool {
87	if r.anchored || strings.Contains(r.pattern, "/") {
88		if ok, _ := filepath.Match(r.pattern, rel); ok {
89			return true
90		}
91		// Anchored directory pattern also matches everything beneath it.
92		return strings.HasPrefix(rel, r.pattern+"/")
93	}
94	// Unanchored: match the basename of any path component.
95	base := rel
96	if i := strings.LastIndex(rel, "/"); i >= 0 {
97		base = rel[i+1:]
98	}
99	if ok, _ := filepath.Match(r.pattern, base); ok {
100		return true
101	}
102	// Match a directory component anywhere in the path.
103	for _, part := range strings.Split(rel, "/") {
104		if ok, _ := filepath.Match(r.pattern, part); ok {
105			return true
106		}
107	}
108	return false
109}

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 1

Tags 476

↑↓ moveenter openesc close