diff --git a/server/backend/file/file.go b/server/backend/file/file.go index 8ec6523a2f037deb0b9eb17d062724930c2eab38..66d19ac4fb08559eaeb7cd0095c6dd55b1db9f01 100644 --- a/server/backend/file/file.go +++ b/server/backend/file/file.go @@ -66,6 +66,10 @@ type FileBackend struct { // nolint:revive // path is the path to the directory containing the repositories and config // files. path string + + // repos is a map of repositories. + repos map[string]*Repo + // AdditionalAdmins additional admins to the server. AdditionalAdmins []string } @@ -145,6 +149,11 @@ func NewFileBackend(path string) (*FileBackend, error) { _ = f.Close() } } + + if err := fb.initRepos(); err != nil { + return nil, err + } + return fb, nil } @@ -602,9 +611,10 @@ func (fb *FileBackend) SetPrivate(repo string, priv bool) error { // Created repositories are always bare. // // It implements backend.Backend. -func (fb *FileBackend) CreateRepository(name string, private bool) (backend.Repository, error) { - name = sanatizeRepo(name) + ".git" - rp := filepath.Join(fb.reposPath(), name) +func (fb *FileBackend) CreateRepository(repo string, private bool) (backend.Repository, error) { + name := sanatizeRepo(repo) + repo = name + ".git" + rp := filepath.Join(fb.reposPath(), repo) if _, err := os.Stat(rp); err == nil { return nil, os.ErrExist } @@ -614,18 +624,23 @@ func (fb *FileBackend) CreateRepository(name string, private bool) (backend.Repo return nil, err } - fb.SetPrivate(name, private) - fb.SetDescription(name, "") + fb.SetPrivate(repo, private) + fb.SetDescription(repo, "") - return &Repo{path: rp, root: fb.reposPath()}, nil + r := &Repo{path: rp, root: fb.reposPath()} + // Add to cache. + fb.repos[name] = r + return r, nil } // DeleteRepository deletes the given repository. // // It implements backend.Backend. -func (fb *FileBackend) DeleteRepository(name string) error { - name = sanatizeRepo(name) + ".git" - return os.RemoveAll(filepath.Join(fb.reposPath(), name)) +func (fb *FileBackend) DeleteRepository(repo string) error { + name := sanatizeRepo(repo) + delete(fb.repos, name) + repo = name + ".git" + return os.RemoveAll(filepath.Join(fb.reposPath(), repo)) } // RenameRepository renames the given repository. @@ -648,7 +663,12 @@ func (fb *FileBackend) RenameRepository(oldName string, newName string) error { // // It implements backend.Backend. func (fb *FileBackend) Repository(repo string) (backend.Repository, error) { - repo = sanatizeRepo(repo) + ".git" + name := sanatizeRepo(repo) + if r, ok := fb.repos[name]; ok { + return r, nil + } + + repo = name + ".git" rp := filepath.Join(fb.reposPath(), repo) _, err := os.Stat(rp) if err != nil { @@ -661,10 +681,31 @@ func (fb *FileBackend) Repository(repo string) (backend.Repository, error) { return &Repo{path: rp, root: fb.reposPath()}, nil } -// Repositories returns a list of all repositories. -// -// It implements backend.Backend. -func (fb *FileBackend) Repositories() ([]backend.Repository, error) { +// Returns true if path is a directory containing an `objects` directory and a +// `HEAD` file. +func isGitDir(path string) bool { + stat, err := os.Stat(filepath.Join(path, "objects")) + if err != nil { + return false + } + if !stat.IsDir() { + return false + } + + stat, err = os.Stat(filepath.Join(path, "HEAD")) + if err != nil { + return false + } + if stat.IsDir() { + return false + } + + return true +} + +// initRepos initializes the repository cache. +func (fb *FileBackend) initRepos() error { + fb.repos = make(map[string]*Repo) repos := make([]backend.Repository, 0) err := filepath.WalkDir(fb.reposPath(), func(path string, d fs.DirEntry, _ error) error { // Skip non-directories. @@ -677,12 +718,28 @@ func (fb *FileBackend) Repositories() ([]backend.Repository, error) { return nil } - repos = append(repos, &Repo{path: path, root: fb.reposPath()}) + if isGitDir(path) { + r := &Repo{path: path, root: fb.reposPath()} + fb.repos[r.Name()] = r + repos = append(repos, r) + } return nil }) if err != nil { - return nil, err + return err + } + + return nil +} + +// Repositories returns a list of all repositories. +// +// It implements backend.Backend. +func (fb *FileBackend) Repositories() ([]backend.Repository, error) { + repos := make([]backend.Repository, 0) + for _, r := range fb.repos { + repos = append(repos, r) } return repos, nil