internal/gitaly/storage/fs.go (110 lines of code) (raw):
package storage
import (
"errors"
"fmt"
"io/fs"
"os"
"path/filepath"
"strings"
"gitlab.com/gitlab-org/gitaly/v16/internal/gitaly/storage/mode"
"gitlab.com/gitlab-org/gitaly/v16/internal/structerr"
)
type noopFS struct {
root string
}
// NewNoopFS returns an FS implementation that is not tied to a transaction
// and operates directly on the storage root.
func NewNoopFS(root string) FS {
return noopFS{root: root}
}
func (f noopFS) Root() string { return f.root }
func (f noopFS) RecordRead(path string) error { return nil }
func (f noopFS) RecordFile(path string) error { return nil }
func (f noopFS) RecordLink(sourcePath, destinationPath string) error { return nil }
func (f noopFS) RecordDirectory(path string) error { return nil }
func (f noopFS) RecordRemoval(path string) error { return nil }
func newTargetIsFileError(path string) error {
return structerr.NewFailedPrecondition("target is a file").WithMetadata("path", path)
}
// Link creates a hard link from source to destination and records the operation.
func Link(f FS, source, destination string) error {
if err := os.Link(filepath.Join(f.Root(), source), filepath.Join(f.Root(), destination)); err != nil {
return fmt.Errorf("link: %w", err)
}
if err := f.RecordLink(source, destination); err != nil {
return fmt.Errorf("record link: %w", err)
}
return nil
}
// Mkdir creates a directory at path and records the operation.
func Mkdir(f FS, path string) error {
if err := os.Mkdir(filepath.Join(f.Root(), path), mode.Directory); err != nil {
return fmt.Errorf("mkdir: %w", err)
}
if err := f.RecordDirectory(path); err != nil {
return fmt.Errorf("record directory: %w", err)
}
return nil
}
// MkdirAll creates all missing directories along the path and records each operation.
func MkdirAll(f FS, path string) error {
if info, err := os.Lstat(filepath.Join(f.Root(), path)); err != nil && !errors.Is(err, fs.ErrNotExist) {
return fmt.Errorf("stat: %w", err)
} else if info != nil && !info.IsDir() {
return newTargetIsFileError(path)
}
var (
currentRelativePath string
currentSuffix = path
hasMore = true
)
for hasMore {
var prefix string
prefix, currentSuffix, hasMore = strings.Cut(currentSuffix, "/")
currentRelativePath = filepath.Join(currentRelativePath, prefix)
if err := Mkdir(f, currentRelativePath); err != nil {
if errors.Is(err, fs.ErrExist) {
// The directory already existed. Continue to the child directory.
continue
}
return fmt.Errorf("create parent directory: %w", err)
}
}
return nil
}
// RecordDirectoryCreation records the operations to create a given directory all of its children.
func RecordDirectoryCreation(f FS, relativePath string) error {
if err := walkDirectory(f.Root(), relativePath,
func(relativePath string, dirEntry fs.DirEntry) error {
// Create the directories before descending in them so they exist when
// we try to create the children.
if err := f.RecordDirectory(relativePath); err != nil {
return fmt.Errorf("record directory: %w", err)
}
return nil
},
func(relativePath string, dirEntry fs.DirEntry) error {
// The parent directory has already been created so we can immediately create
// the file.
if err := f.RecordFile(relativePath); err != nil {
return fmt.Errorf("record file: %w", err)
}
return nil
},
func(relativePath string, dirEntry fs.DirEntry) error {
return nil
},
); err != nil {
return fmt.Errorf("walk directory: %w", err)
}
return nil
}
// RecordDirectoryRemoval records a directory to be removed with all of its children.
func RecordDirectoryRemoval(f FS, storageRoot, directoryRelativePath string) error {
if err := walkDirectory(storageRoot, directoryRelativePath,
func(string, fs.DirEntry) error { return nil },
func(relativePath string, dirEntry fs.DirEntry) error {
if err := f.RecordRemoval(relativePath); err != nil {
return fmt.Errorf("record file removal: %w", err)
}
return nil
},
func(relativePath string, dirEntry fs.DirEntry) error {
if err := f.RecordRemoval(relativePath); err != nil {
return fmt.Errorf("record directory removal: %w", err)
}
return nil
},
); err != nil {
return fmt.Errorf("walk directory: %w", err)
}
return nil
}