mirror of
https://github.com/jesseduffield/lazygit.git
synced 2025-01-10 04:07:18 +02:00
7b302d8c29
Afero is a package that lets you mock out a filesystem with an in-memory filesystem. It allows us to easily create the files required for a given test without worrying about a cleanup step or different tests tripping on eachother when run in parallel. Later on I'll standardise on using afero over the vanilla os package
331 lines
6.9 KiB
Go
331 lines
6.9 KiB
Go
package afero
|
|
|
|
import (
|
|
"io"
|
|
"os"
|
|
"path/filepath"
|
|
"syscall"
|
|
)
|
|
|
|
// The UnionFile implements the afero.File interface and will be returned
|
|
// when reading a directory present at least in the overlay or opening a file
|
|
// for writing.
|
|
//
|
|
// The calls to
|
|
// Readdir() and Readdirnames() merge the file os.FileInfo / names from the
|
|
// base and the overlay - for files present in both layers, only those
|
|
// from the overlay will be used.
|
|
//
|
|
// When opening files for writing (Create() / OpenFile() with the right flags)
|
|
// the operations will be done in both layers, starting with the overlay. A
|
|
// successful read in the overlay will move the cursor position in the base layer
|
|
// by the number of bytes read.
|
|
type UnionFile struct {
|
|
Base File
|
|
Layer File
|
|
Merger DirsMerger
|
|
off int
|
|
files []os.FileInfo
|
|
}
|
|
|
|
func (f *UnionFile) Close() error {
|
|
// first close base, so we have a newer timestamp in the overlay. If we'd close
|
|
// the overlay first, we'd get a cacheStale the next time we access this file
|
|
// -> cache would be useless ;-)
|
|
if f.Base != nil {
|
|
f.Base.Close()
|
|
}
|
|
if f.Layer != nil {
|
|
return f.Layer.Close()
|
|
}
|
|
return BADFD
|
|
}
|
|
|
|
func (f *UnionFile) Read(s []byte) (int, error) {
|
|
if f.Layer != nil {
|
|
n, err := f.Layer.Read(s)
|
|
if (err == nil || err == io.EOF) && f.Base != nil {
|
|
// advance the file position also in the base file, the next
|
|
// call may be a write at this position (or a seek with SEEK_CUR)
|
|
if _, seekErr := f.Base.Seek(int64(n), io.SeekCurrent); seekErr != nil {
|
|
// only overwrite err in case the seek fails: we need to
|
|
// report an eventual io.EOF to the caller
|
|
err = seekErr
|
|
}
|
|
}
|
|
return n, err
|
|
}
|
|
if f.Base != nil {
|
|
return f.Base.Read(s)
|
|
}
|
|
return 0, BADFD
|
|
}
|
|
|
|
func (f *UnionFile) ReadAt(s []byte, o int64) (int, error) {
|
|
if f.Layer != nil {
|
|
n, err := f.Layer.ReadAt(s, o)
|
|
if (err == nil || err == io.EOF) && f.Base != nil {
|
|
_, err = f.Base.Seek(o+int64(n), io.SeekStart)
|
|
}
|
|
return n, err
|
|
}
|
|
if f.Base != nil {
|
|
return f.Base.ReadAt(s, o)
|
|
}
|
|
return 0, BADFD
|
|
}
|
|
|
|
func (f *UnionFile) Seek(o int64, w int) (pos int64, err error) {
|
|
if f.Layer != nil {
|
|
pos, err = f.Layer.Seek(o, w)
|
|
if (err == nil || err == io.EOF) && f.Base != nil {
|
|
_, err = f.Base.Seek(o, w)
|
|
}
|
|
return pos, err
|
|
}
|
|
if f.Base != nil {
|
|
return f.Base.Seek(o, w)
|
|
}
|
|
return 0, BADFD
|
|
}
|
|
|
|
func (f *UnionFile) Write(s []byte) (n int, err error) {
|
|
if f.Layer != nil {
|
|
n, err = f.Layer.Write(s)
|
|
if err == nil && f.Base != nil { // hmm, do we have fixed size files where a write may hit the EOF mark?
|
|
_, err = f.Base.Write(s)
|
|
}
|
|
return n, err
|
|
}
|
|
if f.Base != nil {
|
|
return f.Base.Write(s)
|
|
}
|
|
return 0, BADFD
|
|
}
|
|
|
|
func (f *UnionFile) WriteAt(s []byte, o int64) (n int, err error) {
|
|
if f.Layer != nil {
|
|
n, err = f.Layer.WriteAt(s, o)
|
|
if err == nil && f.Base != nil {
|
|
_, err = f.Base.WriteAt(s, o)
|
|
}
|
|
return n, err
|
|
}
|
|
if f.Base != nil {
|
|
return f.Base.WriteAt(s, o)
|
|
}
|
|
return 0, BADFD
|
|
}
|
|
|
|
func (f *UnionFile) Name() string {
|
|
if f.Layer != nil {
|
|
return f.Layer.Name()
|
|
}
|
|
return f.Base.Name()
|
|
}
|
|
|
|
// DirsMerger is how UnionFile weaves two directories together.
|
|
// It takes the FileInfo slices from the layer and the base and returns a
|
|
// single view.
|
|
type DirsMerger func(lofi, bofi []os.FileInfo) ([]os.FileInfo, error)
|
|
|
|
var defaultUnionMergeDirsFn = func(lofi, bofi []os.FileInfo) ([]os.FileInfo, error) {
|
|
files := make(map[string]os.FileInfo)
|
|
|
|
for _, fi := range lofi {
|
|
files[fi.Name()] = fi
|
|
}
|
|
|
|
for _, fi := range bofi {
|
|
if _, exists := files[fi.Name()]; !exists {
|
|
files[fi.Name()] = fi
|
|
}
|
|
}
|
|
|
|
rfi := make([]os.FileInfo, len(files))
|
|
|
|
i := 0
|
|
for _, fi := range files {
|
|
rfi[i] = fi
|
|
i++
|
|
}
|
|
|
|
return rfi, nil
|
|
}
|
|
|
|
// Readdir will weave the two directories together and
|
|
// return a single view of the overlayed directories.
|
|
// At the end of the directory view, the error is io.EOF if c > 0.
|
|
func (f *UnionFile) Readdir(c int) (ofi []os.FileInfo, err error) {
|
|
var merge DirsMerger = f.Merger
|
|
if merge == nil {
|
|
merge = defaultUnionMergeDirsFn
|
|
}
|
|
|
|
if f.off == 0 {
|
|
var lfi []os.FileInfo
|
|
if f.Layer != nil {
|
|
lfi, err = f.Layer.Readdir(-1)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
}
|
|
|
|
var bfi []os.FileInfo
|
|
if f.Base != nil {
|
|
bfi, err = f.Base.Readdir(-1)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
}
|
|
merged, err := merge(lfi, bfi)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
f.files = append(f.files, merged...)
|
|
}
|
|
files := f.files[f.off:]
|
|
|
|
if c <= 0 {
|
|
return files, nil
|
|
}
|
|
|
|
if len(files) == 0 {
|
|
return nil, io.EOF
|
|
}
|
|
|
|
if c > len(files) {
|
|
c = len(files)
|
|
}
|
|
|
|
defer func() { f.off += c }()
|
|
return files[:c], nil
|
|
}
|
|
|
|
func (f *UnionFile) Readdirnames(c int) ([]string, error) {
|
|
rfi, err := f.Readdir(c)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
var names []string
|
|
for _, fi := range rfi {
|
|
names = append(names, fi.Name())
|
|
}
|
|
return names, nil
|
|
}
|
|
|
|
func (f *UnionFile) Stat() (os.FileInfo, error) {
|
|
if f.Layer != nil {
|
|
return f.Layer.Stat()
|
|
}
|
|
if f.Base != nil {
|
|
return f.Base.Stat()
|
|
}
|
|
return nil, BADFD
|
|
}
|
|
|
|
func (f *UnionFile) Sync() (err error) {
|
|
if f.Layer != nil {
|
|
err = f.Layer.Sync()
|
|
if err == nil && f.Base != nil {
|
|
err = f.Base.Sync()
|
|
}
|
|
return err
|
|
}
|
|
if f.Base != nil {
|
|
return f.Base.Sync()
|
|
}
|
|
return BADFD
|
|
}
|
|
|
|
func (f *UnionFile) Truncate(s int64) (err error) {
|
|
if f.Layer != nil {
|
|
err = f.Layer.Truncate(s)
|
|
if err == nil && f.Base != nil {
|
|
err = f.Base.Truncate(s)
|
|
}
|
|
return err
|
|
}
|
|
if f.Base != nil {
|
|
return f.Base.Truncate(s)
|
|
}
|
|
return BADFD
|
|
}
|
|
|
|
func (f *UnionFile) WriteString(s string) (n int, err error) {
|
|
if f.Layer != nil {
|
|
n, err = f.Layer.WriteString(s)
|
|
if err == nil && f.Base != nil {
|
|
_, err = f.Base.WriteString(s)
|
|
}
|
|
return n, err
|
|
}
|
|
if f.Base != nil {
|
|
return f.Base.WriteString(s)
|
|
}
|
|
return 0, BADFD
|
|
}
|
|
|
|
func copyFile(base Fs, layer Fs, name string, bfh File) error {
|
|
// First make sure the directory exists
|
|
exists, err := Exists(layer, filepath.Dir(name))
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if !exists {
|
|
err = layer.MkdirAll(filepath.Dir(name), 0o777) // FIXME?
|
|
if err != nil {
|
|
return err
|
|
}
|
|
}
|
|
|
|
// Create the file on the overlay
|
|
lfh, err := layer.Create(name)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
n, err := io.Copy(lfh, bfh)
|
|
if err != nil {
|
|
// If anything fails, clean up the file
|
|
layer.Remove(name)
|
|
lfh.Close()
|
|
return err
|
|
}
|
|
|
|
bfi, err := bfh.Stat()
|
|
if err != nil || bfi.Size() != n {
|
|
layer.Remove(name)
|
|
lfh.Close()
|
|
return syscall.EIO
|
|
}
|
|
|
|
err = lfh.Close()
|
|
if err != nil {
|
|
layer.Remove(name)
|
|
lfh.Close()
|
|
return err
|
|
}
|
|
return layer.Chtimes(name, bfi.ModTime(), bfi.ModTime())
|
|
}
|
|
|
|
func copyToLayer(base Fs, layer Fs, name string) error {
|
|
bfh, err := base.Open(name)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
defer bfh.Close()
|
|
|
|
return copyFile(base, layer, name, bfh)
|
|
}
|
|
|
|
func copyFileToLayer(base Fs, layer Fs, name string, flag int, perm os.FileMode) error {
|
|
bfh, err := base.OpenFile(name, flag, perm)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
defer bfh.Close()
|
|
|
|
return copyFile(base, layer, name, bfh)
|
|
}
|