mirror of https://github.com/restic/restic.git
archiver: Shortcut for handling empty files
Regular files that are empty according to stat are now not opened for reading their contents. Such files are quite common (in my homedir, at least) and we can save multiple system calls this way. On a network filesystem, that can mean round trips. Also, we can back up empty files that we cannot open for reading. Finally, fixes #4257. Existing tests cover this case. fs.Reader now no longer has a meaningful Size. Nothing depended on that.
This commit is contained in:
parent
f646406822
commit
50d8377e31
|
@ -363,6 +363,26 @@ func (arch *Archiver) Save(ctx context.Context, snPath, target string, previous
|
||||||
|
|
||||||
switch {
|
switch {
|
||||||
case fs.IsRegularFile(fi):
|
case fs.IsRegularFile(fi):
|
||||||
|
if fi.Size() == 0 {
|
||||||
|
// Shortcut for empty files. Git uses lots of these, and
|
||||||
|
// some virtual filesystems (notably juicefs; #4257) present
|
||||||
|
// infinitely-sized special files as empty regular files.
|
||||||
|
// We can also save empty files without being able to open them.
|
||||||
|
debug.Log(" %v empty", target)
|
||||||
|
|
||||||
|
node, err := arch.nodeFromFileInfo(snPath, target, fi)
|
||||||
|
if err != nil {
|
||||||
|
return FutureNode{}, false, err
|
||||||
|
}
|
||||||
|
node.Content = restic.IDs{}
|
||||||
|
fn = newFutureNodeWithResult(futureNodeResult{
|
||||||
|
snPath: snPath,
|
||||||
|
target: target,
|
||||||
|
node: node,
|
||||||
|
})
|
||||||
|
return fn, false, nil
|
||||||
|
}
|
||||||
|
|
||||||
debug.Log(" %v regular file", target)
|
debug.Log(" %v regular file", target)
|
||||||
|
|
||||||
// check if the file has not changed before performing a fopen operation (more expensive, specially
|
// check if the file has not changed before performing a fopen operation (more expensive, specially
|
||||||
|
|
|
@ -23,7 +23,6 @@ type Reader struct {
|
||||||
// for FileInfo
|
// for FileInfo
|
||||||
Mode os.FileMode
|
Mode os.FileMode
|
||||||
ModTime time.Time
|
ModTime time.Time
|
||||||
Size int64
|
|
||||||
|
|
||||||
AllowEmptyFile bool
|
AllowEmptyFile bool
|
||||||
|
|
||||||
|
@ -65,7 +64,6 @@ func (fs *Reader) Open(name string) (f File, err error) {
|
||||||
func (fs *Reader) fi() os.FileInfo {
|
func (fs *Reader) fi() os.FileInfo {
|
||||||
return fakeFileInfo{
|
return fakeFileInfo{
|
||||||
name: fs.Name,
|
name: fs.Name,
|
||||||
size: fs.Size,
|
|
||||||
mode: fs.Mode,
|
mode: fs.Mode,
|
||||||
modtime: fs.ModTime,
|
modtime: fs.ModTime,
|
||||||
}
|
}
|
||||||
|
@ -107,7 +105,6 @@ func (fs *Reader) Lstat(name string) (os.FileInfo, error) {
|
||||||
getDirInfo := func(name string) os.FileInfo {
|
getDirInfo := func(name string) os.FileInfo {
|
||||||
fi := fakeFileInfo{
|
fi := fakeFileInfo{
|
||||||
name: fs.Base(name),
|
name: fs.Base(name),
|
||||||
size: 0,
|
|
||||||
mode: os.ModeDir | 0755,
|
mode: os.ModeDir | 0755,
|
||||||
modtime: time.Now(),
|
modtime: time.Now(),
|
||||||
}
|
}
|
||||||
|
@ -292,7 +289,6 @@ func (d fakeDir) Readdir(n int) ([]os.FileInfo, error) {
|
||||||
// fakeFileInfo implements the bare minimum of os.FileInfo.
|
// fakeFileInfo implements the bare minimum of os.FileInfo.
|
||||||
type fakeFileInfo struct {
|
type fakeFileInfo struct {
|
||||||
name string
|
name string
|
||||||
size int64
|
|
||||||
mode os.FileMode
|
mode os.FileMode
|
||||||
modtime time.Time
|
modtime time.Time
|
||||||
}
|
}
|
||||||
|
@ -302,7 +298,8 @@ func (fi fakeFileInfo) Name() string {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (fi fakeFileInfo) Size() int64 {
|
func (fi fakeFileInfo) Size() int64 {
|
||||||
return fi.size
|
// Fake size to fool the archiver's empty file check.
|
||||||
|
return -1
|
||||||
}
|
}
|
||||||
|
|
||||||
func (fi fakeFileInfo) Mode() os.FileMode {
|
func (fi fakeFileInfo) Mode() os.FileMode {
|
||||||
|
|
|
@ -142,10 +142,6 @@ func verifyDirectoryContentsFI(t testing.TB, fs FS, dir string, want []os.FileIn
|
||||||
t.Errorf("entry %d: wrong value for ModTime: want %v, got %v", i, fi1.ModTime(), fi2.ModTime())
|
t.Errorf("entry %d: wrong value for ModTime: want %v, got %v", i, fi1.ModTime(), fi2.ModTime())
|
||||||
}
|
}
|
||||||
|
|
||||||
if fi1.Size() != fi2.Size() {
|
|
||||||
t.Errorf("entry %d: wrong value for Size: want %v, got %v", i, fi1.Size(), fi2.Size())
|
|
||||||
}
|
|
||||||
|
|
||||||
if fi1.Sys() != fi2.Sys() {
|
if fi1.Sys() != fi2.Sys() {
|
||||||
t.Errorf("entry %d: wrong value for Sys: want %v, got %v", i, fi1.Sys(), fi2.Sys())
|
t.Errorf("entry %d: wrong value for Sys: want %v, got %v", i, fi1.Sys(), fi2.Sys())
|
||||||
}
|
}
|
||||||
|
@ -202,7 +198,6 @@ func TestFSReader(t *testing.T) {
|
||||||
mode: 0644,
|
mode: 0644,
|
||||||
modtime: now,
|
modtime: now,
|
||||||
name: filename,
|
name: filename,
|
||||||
size: int64(len(data)),
|
|
||||||
}
|
}
|
||||||
verifyDirectoryContentsFI(t, fs, "/", []os.FileInfo{fi})
|
verifyDirectoryContentsFI(t, fs, "/", []os.FileInfo{fi})
|
||||||
},
|
},
|
||||||
|
@ -214,7 +209,6 @@ func TestFSReader(t *testing.T) {
|
||||||
mode: 0644,
|
mode: 0644,
|
||||||
modtime: now,
|
modtime: now,
|
||||||
name: filename,
|
name: filename,
|
||||||
size: int64(len(data)),
|
|
||||||
}
|
}
|
||||||
verifyDirectoryContentsFI(t, fs, ".", []os.FileInfo{fi})
|
verifyDirectoryContentsFI(t, fs, ".", []os.FileInfo{fi})
|
||||||
},
|
},
|
||||||
|
@ -324,7 +318,6 @@ func TestFSReader(t *testing.T) {
|
||||||
ReadCloser: io.NopCloser(bytes.NewReader(data)),
|
ReadCloser: io.NopCloser(bytes.NewReader(data)),
|
||||||
|
|
||||||
Mode: 0644,
|
Mode: 0644,
|
||||||
Size: int64(len(data)),
|
|
||||||
ModTime: now,
|
ModTime: now,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -359,7 +352,6 @@ func TestFSReaderDir(t *testing.T) {
|
||||||
ReadCloser: io.NopCloser(bytes.NewReader(data)),
|
ReadCloser: io.NopCloser(bytes.NewReader(data)),
|
||||||
|
|
||||||
Mode: 0644,
|
Mode: 0644,
|
||||||
Size: int64(len(data)),
|
|
||||||
ModTime: now,
|
ModTime: now,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue