loading up the forgejo repo on tangled to test page performance
1// Copyright 2015 The Gogs Authors. All rights reserved.
2// Copyright 2019 The Gitea Authors. All rights reserved.
3// SPDX-License-Identifier: MIT
4
5package git
6
7import (
8 "bytes"
9 "io"
10 "strings"
11)
12
13// Tree represents a flat directory listing.
14type Tree struct {
15 ID ObjectID
16 ResolvedID ObjectID
17 repo *Repository
18
19 // parent tree
20 ptree *Tree
21
22 entries Entries
23 entriesParsed bool
24
25 entriesRecursive Entries
26 entriesRecursiveParsed bool
27}
28
29// NewTree create a new tree according the repository and tree id
30func NewTree(repo *Repository, id ObjectID) *Tree {
31 return &Tree{
32 ID: id,
33 repo: repo,
34 }
35}
36
37// ListEntries returns all entries of current tree.
38func (t *Tree) ListEntries() (Entries, error) {
39 if t.entriesParsed {
40 return t.entries, nil
41 }
42
43 if t.repo != nil {
44 wr, rd, cancel, err := t.repo.CatFileBatch(t.repo.Ctx)
45 if err != nil {
46 return nil, err
47 }
48 defer cancel()
49
50 _, _ = wr.Write([]byte(t.ID.String() + "\n"))
51 _, typ, sz, err := ReadBatchLine(rd)
52 if err != nil {
53 return nil, err
54 }
55 if typ == "commit" {
56 treeID, err := ReadTreeID(rd, sz)
57 if err != nil && err != io.EOF {
58 return nil, err
59 }
60 _, _ = wr.Write([]byte(treeID + "\n"))
61 _, typ, sz, err = ReadBatchLine(rd)
62 if err != nil {
63 return nil, err
64 }
65 }
66 if typ == "tree" {
67 t.entries, err = catBatchParseTreeEntries(t.ID.Type(), t, rd, sz)
68 if err != nil {
69 return nil, err
70 }
71 t.entriesParsed = true
72 return t.entries, nil
73 }
74
75 // Not a tree just use ls-tree instead
76 if err := DiscardFull(rd, sz+1); err != nil {
77 return nil, err
78 }
79 }
80
81 stdout, _, runErr := NewCommand(t.repo.Ctx, "ls-tree", "-l").AddDynamicArguments(t.ID.String()).RunStdBytes(&RunOpts{Dir: t.repo.Path})
82 if runErr != nil {
83 if strings.Contains(runErr.Error(), "fatal: Not a valid object name") || strings.Contains(runErr.Error(), "fatal: not a tree object") {
84 return nil, ErrNotExist{
85 ID: t.ID.String(),
86 }
87 }
88 return nil, runErr
89 }
90
91 var err error
92 t.entries, err = parseTreeEntries(stdout, t)
93 if err == nil {
94 t.entriesParsed = true
95 }
96
97 return t.entries, err
98}
99
100// listEntriesRecursive returns all entries of current tree recursively including all subtrees
101// extraArgs could be "-l" to get the size, which is slower
102func (t *Tree) listEntriesRecursive(extraArgs TrustedCmdArgs) (Entries, error) {
103 if t.entriesRecursiveParsed {
104 return t.entriesRecursive, nil
105 }
106
107 stdout, _, runErr := NewCommand(t.repo.Ctx, "ls-tree", "-t", "-r").
108 AddArguments(extraArgs...).
109 AddDynamicArguments(t.ID.String()).
110 RunStdBytes(&RunOpts{Dir: t.repo.Path})
111 if runErr != nil {
112 return nil, runErr
113 }
114
115 var err error
116 t.entriesRecursive, err = parseTreeEntries(stdout, t)
117 if err == nil {
118 t.entriesRecursiveParsed = true
119 }
120
121 return t.entriesRecursive, err
122}
123
124// ListEntriesRecursiveFast returns all entries of current tree recursively including all subtrees, no size
125func (t *Tree) ListEntriesRecursiveFast() (Entries, error) {
126 return t.listEntriesRecursive(nil)
127}
128
129// ListEntriesRecursiveWithSize returns all entries of current tree recursively including all subtrees, with size
130func (t *Tree) ListEntriesRecursiveWithSize() (Entries, error) {
131 return t.listEntriesRecursive(TrustedCmdArgs{"--long"})
132}
133
134// SubTree get a sub tree by the sub dir path
135func (t *Tree) SubTree(rpath string) (*Tree, error) {
136 if len(rpath) == 0 {
137 return t, nil
138 }
139
140 paths := strings.Split(rpath, "/")
141 var (
142 err error
143 g = t
144 p = t
145 te *TreeEntry
146 )
147 for _, name := range paths {
148 te, err = p.GetTreeEntryByPath(name)
149 if err != nil {
150 return nil, err
151 }
152
153 g, err = t.repo.getTree(te.ID)
154 if err != nil {
155 return nil, err
156 }
157 g.ptree = p
158 p = g
159 }
160 return g, nil
161}
162
163// LsTree checks if the given filenames are in the tree
164func (repo *Repository) LsTree(ref string, filenames ...string) ([]string, error) {
165 cmd := NewCommand(repo.Ctx, "ls-tree", "-z", "--name-only").
166 AddDashesAndList(append([]string{ref}, filenames...)...)
167
168 res, _, err := cmd.RunStdBytes(&RunOpts{Dir: repo.Path})
169 if err != nil {
170 return nil, err
171 }
172 filelist := make([]string, 0, len(filenames))
173 for _, line := range bytes.Split(res, []byte{'\000'}) {
174 filelist = append(filelist, string(line))
175 }
176
177 return filelist, err
178}
179
180// GetTreePathLatestCommitID returns the latest commit of a tree path
181func (repo *Repository) GetTreePathLatestCommit(refName, treePath string) (*Commit, error) {
182 stdout, _, err := NewCommand(repo.Ctx, "rev-list", "-1").
183 AddDynamicArguments(refName).AddDashesAndList(treePath).
184 RunStdString(&RunOpts{Dir: repo.Path})
185 if err != nil {
186 return nil, err
187 }
188 return repo.GetCommit(strings.TrimSpace(stdout))
189}