mirror of
https://github.com/jesseduffield/lazygit.git
synced 2025-04-25 12:24:47 +02:00
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
244 lines
7.0 KiB
Go
244 lines
7.0 KiB
Go
// Copyright ©2015 The Go Authors
|
|
// Copyright ©2015 Steve Francia <spf@spf13.com>
|
|
//
|
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
|
// you may not use this file except in compliance with the License.
|
|
// You may obtain a copy of the License at
|
|
//
|
|
// http://www.apache.org/licenses/LICENSE-2.0
|
|
//
|
|
// Unless required by applicable law or agreed to in writing, software
|
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
// See the License for the specific language governing permissions and
|
|
// limitations under the License.
|
|
|
|
package afero
|
|
|
|
import (
|
|
"bytes"
|
|
"io"
|
|
"os"
|
|
"path/filepath"
|
|
"sort"
|
|
"strconv"
|
|
"strings"
|
|
"sync"
|
|
"time"
|
|
)
|
|
|
|
// byName implements sort.Interface.
|
|
type byName []os.FileInfo
|
|
|
|
func (f byName) Len() int { return len(f) }
|
|
func (f byName) Less(i, j int) bool { return f[i].Name() < f[j].Name() }
|
|
func (f byName) Swap(i, j int) { f[i], f[j] = f[j], f[i] }
|
|
|
|
// ReadDir reads the directory named by dirname and returns
|
|
// a list of sorted directory entries.
|
|
func (a Afero) ReadDir(dirname string) ([]os.FileInfo, error) {
|
|
return ReadDir(a.Fs, dirname)
|
|
}
|
|
|
|
func ReadDir(fs Fs, dirname string) ([]os.FileInfo, error) {
|
|
f, err := fs.Open(dirname)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
list, err := f.Readdir(-1)
|
|
f.Close()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
sort.Sort(byName(list))
|
|
return list, nil
|
|
}
|
|
|
|
// ReadFile reads the file named by filename and returns the contents.
|
|
// A successful call returns err == nil, not err == EOF. Because ReadFile
|
|
// reads the whole file, it does not treat an EOF from Read as an error
|
|
// to be reported.
|
|
func (a Afero) ReadFile(filename string) ([]byte, error) {
|
|
return ReadFile(a.Fs, filename)
|
|
}
|
|
|
|
func ReadFile(fs Fs, filename string) ([]byte, error) {
|
|
f, err := fs.Open(filename)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
defer f.Close()
|
|
// It's a good but not certain bet that FileInfo will tell us exactly how much to
|
|
// read, so let's try it but be prepared for the answer to be wrong.
|
|
var n int64
|
|
|
|
if fi, err := f.Stat(); err == nil {
|
|
// Don't preallocate a huge buffer, just in case.
|
|
if size := fi.Size(); size < 1e9 {
|
|
n = size
|
|
}
|
|
}
|
|
// As initial capacity for readAll, use n + a little extra in case Size is zero,
|
|
// and to avoid another allocation after Read has filled the buffer. The readAll
|
|
// call will read into its allocated internal buffer cheaply. If the size was
|
|
// wrong, we'll either waste some space off the end or reallocate as needed, but
|
|
// in the overwhelmingly common case we'll get it just right.
|
|
return readAll(f, n+bytes.MinRead)
|
|
}
|
|
|
|
// readAll reads from r until an error or EOF and returns the data it read
|
|
// from the internal buffer allocated with a specified capacity.
|
|
func readAll(r io.Reader, capacity int64) (b []byte, err error) {
|
|
buf := bytes.NewBuffer(make([]byte, 0, capacity))
|
|
// If the buffer overflows, we will get bytes.ErrTooLarge.
|
|
// Return that as an error. Any other panic remains.
|
|
defer func() {
|
|
e := recover()
|
|
if e == nil {
|
|
return
|
|
}
|
|
if panicErr, ok := e.(error); ok && panicErr == bytes.ErrTooLarge {
|
|
err = panicErr
|
|
} else {
|
|
panic(e)
|
|
}
|
|
}()
|
|
_, err = buf.ReadFrom(r)
|
|
return buf.Bytes(), err
|
|
}
|
|
|
|
// ReadAll reads from r until an error or EOF and returns the data it read.
|
|
// A successful call returns err == nil, not err == EOF. Because ReadAll is
|
|
// defined to read from src until EOF, it does not treat an EOF from Read
|
|
// as an error to be reported.
|
|
func ReadAll(r io.Reader) ([]byte, error) {
|
|
return readAll(r, bytes.MinRead)
|
|
}
|
|
|
|
// WriteFile writes data to a file named by filename.
|
|
// If the file does not exist, WriteFile creates it with permissions perm;
|
|
// otherwise WriteFile truncates it before writing.
|
|
func (a Afero) WriteFile(filename string, data []byte, perm os.FileMode) error {
|
|
return WriteFile(a.Fs, filename, data, perm)
|
|
}
|
|
|
|
func WriteFile(fs Fs, filename string, data []byte, perm os.FileMode) error {
|
|
f, err := fs.OpenFile(filename, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, perm)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
n, err := f.Write(data)
|
|
if err == nil && n < len(data) {
|
|
err = io.ErrShortWrite
|
|
}
|
|
if err1 := f.Close(); err == nil {
|
|
err = err1
|
|
}
|
|
return err
|
|
}
|
|
|
|
// Random number state.
|
|
// We generate random temporary file names so that there's a good
|
|
// chance the file doesn't exist yet - keeps the number of tries in
|
|
// TempFile to a minimum.
|
|
var (
|
|
randNum uint32
|
|
randmu sync.Mutex
|
|
)
|
|
|
|
func reseed() uint32 {
|
|
return uint32(time.Now().UnixNano() + int64(os.Getpid()))
|
|
}
|
|
|
|
func nextRandom() string {
|
|
randmu.Lock()
|
|
r := randNum
|
|
if r == 0 {
|
|
r = reseed()
|
|
}
|
|
r = r*1664525 + 1013904223 // constants from Numerical Recipes
|
|
randNum = r
|
|
randmu.Unlock()
|
|
return strconv.Itoa(int(1e9 + r%1e9))[1:]
|
|
}
|
|
|
|
// TempFile creates a new temporary file in the directory dir,
|
|
// opens the file for reading and writing, and returns the resulting *os.File.
|
|
// The filename is generated by taking pattern and adding a random
|
|
// string to the end. If pattern includes a "*", the random string
|
|
// replaces the last "*".
|
|
// If dir is the empty string, TempFile uses the default directory
|
|
// for temporary files (see os.TempDir).
|
|
// Multiple programs calling TempFile simultaneously
|
|
// will not choose the same file. The caller can use f.Name()
|
|
// to find the pathname of the file. It is the caller's responsibility
|
|
// to remove the file when no longer needed.
|
|
func (a Afero) TempFile(dir, pattern string) (f File, err error) {
|
|
return TempFile(a.Fs, dir, pattern)
|
|
}
|
|
|
|
func TempFile(fs Fs, dir, pattern string) (f File, err error) {
|
|
if dir == "" {
|
|
dir = os.TempDir()
|
|
}
|
|
|
|
var prefix, suffix string
|
|
if pos := strings.LastIndex(pattern, "*"); pos != -1 {
|
|
prefix, suffix = pattern[:pos], pattern[pos+1:]
|
|
} else {
|
|
prefix = pattern
|
|
}
|
|
|
|
nconflict := 0
|
|
for i := 0; i < 10000; i++ {
|
|
name := filepath.Join(dir, prefix+nextRandom()+suffix)
|
|
f, err = fs.OpenFile(name, os.O_RDWR|os.O_CREATE|os.O_EXCL, 0o600)
|
|
if os.IsExist(err) {
|
|
if nconflict++; nconflict > 10 {
|
|
randmu.Lock()
|
|
randNum = reseed()
|
|
randmu.Unlock()
|
|
}
|
|
continue
|
|
}
|
|
break
|
|
}
|
|
return
|
|
}
|
|
|
|
// TempDir creates a new temporary directory in the directory dir
|
|
// with a name beginning with prefix and returns the path of the
|
|
// new directory. If dir is the empty string, TempDir uses the
|
|
// default directory for temporary files (see os.TempDir).
|
|
// Multiple programs calling TempDir simultaneously
|
|
// will not choose the same directory. It is the caller's responsibility
|
|
// to remove the directory when no longer needed.
|
|
func (a Afero) TempDir(dir, prefix string) (name string, err error) {
|
|
return TempDir(a.Fs, dir, prefix)
|
|
}
|
|
|
|
func TempDir(fs Fs, dir, prefix string) (name string, err error) {
|
|
if dir == "" {
|
|
dir = os.TempDir()
|
|
}
|
|
|
|
nconflict := 0
|
|
for i := 0; i < 10000; i++ {
|
|
try := filepath.Join(dir, prefix+nextRandom())
|
|
err = fs.Mkdir(try, 0o700)
|
|
if os.IsExist(err) {
|
|
if nconflict++; nconflict > 10 {
|
|
randmu.Lock()
|
|
randNum = reseed()
|
|
randmu.Unlock()
|
|
}
|
|
continue
|
|
}
|
|
if err == nil {
|
|
name = try
|
|
}
|
|
break
|
|
}
|
|
return
|
|
}
|