v0.1.0 5228465

tree.go RawHistoryFolder

1// SPDX-License-Identifier: MIT
2
3package git
4
5import (
6	"bytes"
7	"fmt"
8)
9
10// Entry is an entry of a tree.
11type Entry struct {
12	// Name is the file name. It points into the tree buffer and is only
13	// valid during the callback that receives the entry; copy it to keep it.
14	Name []byte
15	Mode Mode
16	ID   ObjectID
17}
18
19// ParseTree calls fn for each entry of a tree object in order, until fn
20// returns false. The entries are not copied, so iterating allocates nothing.
21// It returns ErrCorrupt on a malformed tree.
22func ParseTree(data []byte, fn func(Entry) bool) error {
23	var e Entry
24	for len(data) > 0 {
25		sp := bytes.IndexByte(data, ' ')
26		if sp <= 0 || sp > 7 {
27			return corrupt("tree: bad mode")
28		}
29		var mode uint32
30		for _, c := range data[:sp] {
31			if c < '0' || c > '7' {
32				return corrupt("tree: bad mode")
33			}
34			mode = mode<<3 | uint32(c-'0')
35		}
36		data = data[sp+1:]
37		nul := bytes.IndexByte(data, 0)
38		if nul <= 0 || len(data)-nul-1 < 20 {
39			return corrupt("tree: truncated entry")
40		}
41		e.Name = data[:nul]
42		e.Mode = Mode(mode)
43		copy(e.ID[:], data[nul+1:nul+21])
44		data = data[nul+21:]
45		if !fn(e) {
46			return nil
47		}
48	}
49	return nil
50}
51
52// ReadTree reads the tree id and calls fn for each entry, in tree order,
53// until fn returns false. Entry.Name is only valid during the call. The tree
54// is read into a pooled buffer, so listing allocates nothing per entry.
55func (r *Repo) ReadTree(id ObjectID, fn func(Entry) bool) error {
56	b, err := r.readTyped(id, TypeTree)
57	if err != nil {
58		return err
59	}
60	defer putBuf(b)
61	if err := ParseTree(b.b, fn); err != nil {
62		return wrapObj(id, err)
63	}
64	return nil
65}
66
67// LookupPath finds the entry at path (slash separated, no leading slash)
68// below the tree root. It returns ErrNotFound when a component does not
69// exist or is not a directory. An empty path designates root itself, as a
70// directory.
71func (r *Repo) LookupPath(root ObjectID, path string) (Entry, error) {
72	cur := Entry{Mode: ModeTree, ID: root}
73	for path != "" {
74		var name string
75		name, path, _ = cutByte(path, '/')
76		if name == "" {
77			return Entry{}, notFound("empty path component")
78		}
79		if !cur.Mode.IsTree() {
80			return Entry{}, notFound("%q is not a directory", name)
81		}
82		found := false
83		var next Entry
84		err := r.ReadTree(cur.ID, func(e Entry) bool {
85			if string(e.Name) == name {
86				next = Entry{Mode: e.Mode, ID: e.ID}
87				found = true
88				return false
89			}
90			return true
91		})
92		if err != nil {
93			return Entry{}, err
94		}
95		if !found {
96			return Entry{}, notFound("path component %q", name)
97		}
98		cur = next
99	}
100	return cur, nil
101}
102
103func cutByte(s string, c byte) (before, after string, found bool) {
104	for i := 0; i < len(s); i++ {
105		if s[i] == c {
106			return s[:i], s[i+1:], true
107		}
108	}
109	return s, "", false
110}
111
112func wrongType(id ObjectID, got, want Type) error {
113	return fmt.Errorf("%w: %s is a %s, not a %s", ErrWrongType, id.String(), got, want)
114}
115
116func wrapObj(id ObjectID, err error) error {
117	return fmt.Errorf("object %s: %w", id.String(), err)
118}

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 2

↑↓ moveenter openesc close