1// SPDX-License-Identifier: MIT
2
3package git
4
5import (
6 "errors"
7 "io"
8 "syscall"
9 "time"
10)
11
12// fileStat is the part of a stat result that change detection uses.
13type fileStat struct {
14 exists bool
15 isDir bool
16 size int64
17 mtime int64 // unix nanoseconds
18 ino uint64
19}
20
21// statPath is a path prepared once, so that stat-ing it again and again (what
22// Refresh does) does not build a string, nor the NUL-terminated copy of it
23// that the system call takes, at each call.
24type statPath struct {
25 nul []byte // the path and a NUL byte
26}
27
28func newStatPath(path string) statPath {
29 return statPath{nul: append([]byte(path), 0)}
30}
31
32func (p *statPath) path() string { return string(p.nul[:len(p.nul)-1]) }
33
34// stat returns the stat data of the path, following symbolic links unless
35// lstat. A path that is absent, or has a file in place of a directory on the
36// way, is not an error: exists is false.
37func (p *statPath) stat(lstat bool) (fileStat, error) {
38 var st fileStat
39 err := statNul(p.nul, lstat, &st)
40 return st, err
41}
42
43// sig returns the signature of the file. The time of the read is taken
44// before the stat, see sigOf.
45func (p *statPath) sig() (fileSig, error) {
46 read := time.Now().UnixNano()
47 st, err := p.stat(false)
48 if err != nil {
49 return fileSig{}, err
50 }
51 if !st.exists {
52 return fileSig{read: read}, nil
53 }
54 return fileSig{exists: true, size: st.size, mtime: st.mtime, ino: st.ino, read: read}, nil
55}
56
57// readFull reads the file into buf until buf is full or the file ends, and
58// returns the number of bytes. A directory reads as an empty, missing file:
59// dir is true.
60func (f rawFile) readFull(buf []byte) (n int, dir bool, err error) {
61 for n < len(buf) {
62 m, err := f.read(buf[n:])
63 n += m
64 if err != nil {
65 if errors.Is(err, io.EOF) {
66 break
67 }
68 if errors.Is(err, syscall.EISDIR) {
69 return 0, true, nil
70 }
71 return n, false, err
72 }
73 }
74 return n, false, nil
75}