Skip to content

Commit 248b96d

Browse files
authored
Use git attributes to determine generated and vendored status for language stats and diffs (#16773)
Replaces #16262 Replaces #16250 Replaces #14833 This PR first implements a `git check-attr` pipe reader - using `git check-attr --stdin -z --cached` - taking account of the change in the output format in git 1.8.5 and creates a helper function to read a tree into a temporary index file for that pipe reader. It then wires this in to the language stats helper and into the git diff generation. Files which are marked generated will be folded by default. Fixes #14786 Fixes #12653
1 parent b83b4fb commit 248b96d

File tree

10 files changed

+736
-17
lines changed

10 files changed

+736
-17
lines changed

modules/analyze/generated.go

+28
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
// Copyright 2021 The Gitea Authors. All rights reserved.
2+
// Use of this source code is governed by a MIT-style
3+
// license that can be found in the LICENSE file.
4+
5+
package analyze
6+
7+
import (
8+
"path/filepath"
9+
"strings"
10+
11+
"github.com/go-enry/go-enry/v2/data"
12+
)
13+
14+
// IsGenerated returns whether or not path is a generated path.
15+
func IsGenerated(path string) bool {
16+
ext := strings.ToLower(filepath.Ext(path))
17+
if _, ok := data.GeneratedCodeExtensions[ext]; ok {
18+
return true
19+
}
20+
21+
for _, m := range data.GeneratedCodeNameMatchers {
22+
if m(path) {
23+
return true
24+
}
25+
}
26+
27+
return false
28+
}

modules/git/repo_attribute.go

+282-3
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,12 @@ package git
66

77
import (
88
"bytes"
9+
"context"
910
"fmt"
11+
"io"
12+
"os"
13+
"strconv"
14+
"strings"
1015
)
1116

1217
// CheckAttributeOpts represents the possible options to CheckAttribute
@@ -21,7 +26,7 @@ type CheckAttributeOpts struct {
2126
func (repo *Repository) CheckAttribute(opts CheckAttributeOpts) (map[string]map[string]string, error) {
2227
err := LoadGitVersion()
2328
if err != nil {
24-
return nil, fmt.Errorf("Git version missing: %v", err)
29+
return nil, fmt.Errorf("git version missing: %v", err)
2530
}
2631

2732
stdOut := new(bytes.Buffer)
@@ -55,13 +60,14 @@ func (repo *Repository) CheckAttribute(opts CheckAttributeOpts) (map[string]map[
5560
cmd := NewCommand(cmdArgs...)
5661

5762
if err := cmd.RunInDirPipeline(repo.Path, stdOut, stdErr); err != nil {
58-
return nil, fmt.Errorf("Failed to run check-attr: %v\n%s\n%s", err, stdOut.String(), stdErr.String())
63+
return nil, fmt.Errorf("failed to run check-attr: %v\n%s\n%s", err, stdOut.String(), stdErr.String())
5964
}
6065

66+
// FIXME: This is incorrect on versions < 1.8.5
6167
fields := bytes.Split(stdOut.Bytes(), []byte{'\000'})
6268

6369
if len(fields)%3 != 1 {
64-
return nil, fmt.Errorf("Wrong number of fields in return from check-attr")
70+
return nil, fmt.Errorf("wrong number of fields in return from check-attr")
6571
}
6672

6773
var name2attribute2info = make(map[string]map[string]string)
@@ -80,3 +86,276 @@ func (repo *Repository) CheckAttribute(opts CheckAttributeOpts) (map[string]map[
8086

8187
return name2attribute2info, nil
8288
}
89+
90+
// CheckAttributeReader provides a reader for check-attribute content that can be long running
91+
type CheckAttributeReader struct {
92+
// params
93+
Attributes []string
94+
Repo *Repository
95+
IndexFile string
96+
WorkTree string
97+
98+
stdinReader io.ReadCloser
99+
stdinWriter *os.File
100+
stdOut attributeWriter
101+
cmd *Command
102+
env []string
103+
ctx context.Context
104+
cancel context.CancelFunc
105+
running chan struct{}
106+
}
107+
108+
// Init initializes the cmd
109+
func (c *CheckAttributeReader) Init(ctx context.Context) error {
110+
c.running = make(chan struct{})
111+
cmdArgs := []string{"check-attr", "--stdin", "-z"}
112+
113+
if len(c.IndexFile) > 0 && CheckGitVersionAtLeast("1.7.8") == nil {
114+
cmdArgs = append(cmdArgs, "--cached")
115+
c.env = []string{"GIT_INDEX_FILE=" + c.IndexFile}
116+
}
117+
118+
if len(c.WorkTree) > 0 && CheckGitVersionAtLeast("1.7.8") == nil {
119+
c.env = []string{"GIT_WORK_TREE=" + c.WorkTree}
120+
}
121+
122+
if len(c.Attributes) > 0 {
123+
cmdArgs = append(cmdArgs, c.Attributes...)
124+
cmdArgs = append(cmdArgs, "--")
125+
} else {
126+
lw := new(nulSeparatedAttributeWriter)
127+
lw.attributes = make(chan attributeTriple)
128+
129+
c.stdOut = lw
130+
c.stdOut.Close()
131+
return fmt.Errorf("no provided Attributes to check")
132+
}
133+
134+
c.ctx, c.cancel = context.WithCancel(ctx)
135+
c.cmd = NewCommandContext(c.ctx, cmdArgs...)
136+
var err error
137+
c.stdinReader, c.stdinWriter, err = os.Pipe()
138+
if err != nil {
139+
return err
140+
}
141+
142+
if CheckGitVersionAtLeast("1.8.5") == nil {
143+
lw := new(nulSeparatedAttributeWriter)
144+
lw.attributes = make(chan attributeTriple, 5)
145+
146+
c.stdOut = lw
147+
} else {
148+
lw := new(lineSeparatedAttributeWriter)
149+
lw.attributes = make(chan attributeTriple, 5)
150+
151+
c.stdOut = lw
152+
}
153+
return nil
154+
}
155+
156+
// Run run cmd
157+
func (c *CheckAttributeReader) Run() error {
158+
stdErr := new(bytes.Buffer)
159+
err := c.cmd.RunInDirTimeoutEnvFullPipelineFunc(c.env, -1, c.Repo.Path, c.stdOut, stdErr, c.stdinReader, func(_ context.Context, _ context.CancelFunc) error {
160+
close(c.running)
161+
return nil
162+
})
163+
defer c.cancel()
164+
_ = c.stdOut.Close()
165+
if err != nil && c.ctx.Err() != nil && err.Error() != "signal: killed" {
166+
return fmt.Errorf("failed to run attr-check. Error: %w\nStderr: %s", err, stdErr.String())
167+
}
168+
169+
return nil
170+
}
171+
172+
// CheckPath check attr for given path
173+
func (c *CheckAttributeReader) CheckPath(path string) (map[string]string, error) {
174+
select {
175+
case <-c.ctx.Done():
176+
return nil, c.ctx.Err()
177+
case <-c.running:
178+
}
179+
180+
if _, err := c.stdinWriter.Write([]byte(path + "\x00")); err != nil {
181+
defer c.cancel()
182+
return nil, err
183+
}
184+
185+
if err := c.stdinWriter.Sync(); err != nil {
186+
defer c.cancel()
187+
return nil, err
188+
}
189+
190+
rs := make(map[string]string)
191+
for range c.Attributes {
192+
select {
193+
case attr := <-c.stdOut.ReadAttribute():
194+
rs[attr.Attribute] = attr.Value
195+
case <-c.ctx.Done():
196+
return nil, c.ctx.Err()
197+
}
198+
}
199+
return rs, nil
200+
}
201+
202+
// Close close pip after use
203+
func (c *CheckAttributeReader) Close() error {
204+
select {
205+
case <-c.running:
206+
default:
207+
close(c.running)
208+
}
209+
defer c.cancel()
210+
return c.stdinWriter.Close()
211+
}
212+
213+
type attributeWriter interface {
214+
io.WriteCloser
215+
ReadAttribute() <-chan attributeTriple
216+
}
217+
218+
type attributeTriple struct {
219+
Filename string
220+
Attribute string
221+
Value string
222+
}
223+
224+
type nulSeparatedAttributeWriter struct {
225+
tmp []byte
226+
attributes chan attributeTriple
227+
working attributeTriple
228+
pos int
229+
}
230+
231+
func (wr *nulSeparatedAttributeWriter) Write(p []byte) (n int, err error) {
232+
l, read := len(p), 0
233+
234+
nulIdx := bytes.IndexByte(p, '\x00')
235+
for nulIdx >= 0 {
236+
wr.tmp = append(wr.tmp, p[:nulIdx]...)
237+
switch wr.pos {
238+
case 0:
239+
wr.working = attributeTriple{
240+
Filename: string(wr.tmp),
241+
}
242+
case 1:
243+
wr.working.Attribute = string(wr.tmp)
244+
case 2:
245+
wr.working.Value = string(wr.tmp)
246+
}
247+
wr.tmp = wr.tmp[:0]
248+
wr.pos++
249+
if wr.pos > 2 {
250+
wr.attributes <- wr.working
251+
wr.pos = 0
252+
}
253+
read += nulIdx + 1
254+
if l > read {
255+
p = p[nulIdx+1:]
256+
nulIdx = bytes.IndexByte(p, '\x00')
257+
} else {
258+
return l, nil
259+
}
260+
}
261+
wr.tmp = append(wr.tmp, p...)
262+
return len(p), nil
263+
}
264+
265+
func (wr *nulSeparatedAttributeWriter) ReadAttribute() <-chan attributeTriple {
266+
return wr.attributes
267+
}
268+
269+
func (wr *nulSeparatedAttributeWriter) Close() error {
270+
close(wr.attributes)
271+
return nil
272+
}
273+
274+
type lineSeparatedAttributeWriter struct {
275+
tmp []byte
276+
attributes chan attributeTriple
277+
}
278+
279+
func (wr *lineSeparatedAttributeWriter) Write(p []byte) (n int, err error) {
280+
l := len(p)
281+
282+
nlIdx := bytes.IndexByte(p, '\n')
283+
for nlIdx >= 0 {
284+
wr.tmp = append(wr.tmp, p[:nlIdx]...)
285+
286+
if len(wr.tmp) == 0 {
287+
// This should not happen
288+
if len(p) > nlIdx+1 {
289+
wr.tmp = wr.tmp[:0]
290+
p = p[nlIdx+1:]
291+
nlIdx = bytes.IndexByte(p, '\n')
292+
continue
293+
} else {
294+
return l, nil
295+
}
296+
}
297+
298+
working := attributeTriple{}
299+
if wr.tmp[0] == '"' {
300+
sb := new(strings.Builder)
301+
remaining := string(wr.tmp[1:])
302+
for len(remaining) > 0 {
303+
rn, _, tail, err := strconv.UnquoteChar(remaining, '"')
304+
if err != nil {
305+
if len(remaining) > 2 && remaining[0] == '"' && remaining[1] == ':' && remaining[2] == ' ' {
306+
working.Filename = sb.String()
307+
wr.tmp = []byte(remaining[3:])
308+
break
309+
}
310+
return l, fmt.Errorf("unexpected tail %s", string(remaining))
311+
}
312+
_, _ = sb.WriteRune(rn)
313+
remaining = tail
314+
}
315+
} else {
316+
idx := bytes.IndexByte(wr.tmp, ':')
317+
if idx < 0 {
318+
return l, fmt.Errorf("unexpected input %s", string(wr.tmp))
319+
}
320+
working.Filename = string(wr.tmp[:idx])
321+
if len(wr.tmp) < idx+2 {
322+
return l, fmt.Errorf("unexpected input %s", string(wr.tmp))
323+
}
324+
wr.tmp = wr.tmp[idx+2:]
325+
}
326+
327+
idx := bytes.IndexByte(wr.tmp, ':')
328+
if idx < 0 {
329+
return l, fmt.Errorf("unexpected input %s", string(wr.tmp))
330+
}
331+
332+
working.Attribute = string(wr.tmp[:idx])
333+
if len(wr.tmp) < idx+2 {
334+
return l, fmt.Errorf("unexpected input %s", string(wr.tmp))
335+
}
336+
337+
working.Value = string(wr.tmp[idx+2:])
338+
339+
wr.attributes <- working
340+
wr.tmp = wr.tmp[:0]
341+
if len(p) > nlIdx+1 {
342+
p = p[nlIdx+1:]
343+
nlIdx = bytes.IndexByte(p, '\n')
344+
continue
345+
} else {
346+
return l, nil
347+
}
348+
}
349+
350+
wr.tmp = append(wr.tmp, p...)
351+
return l, nil
352+
}
353+
354+
func (wr *lineSeparatedAttributeWriter) ReadAttribute() <-chan attributeTriple {
355+
return wr.attributes
356+
}
357+
358+
func (wr *lineSeparatedAttributeWriter) Close() error {
359+
close(wr.attributes)
360+
return nil
361+
}

0 commit comments

Comments
 (0)