feat(backend): repo defaultBranch setting & collabs path

Ayman Bagabas created

Change summary

server/backend/file/file.go |  72 ++++++++++---------
server/backend/noop/noop.go | 141 ---------------------------------------
server/backend/noop/repo.go |  32 --------
server/backend/server.go    |   4 +
4 files changed, 41 insertions(+), 208 deletions(-)

Detailed changes

server/backend/file/file.go 🔗

@@ -26,6 +26,7 @@ import (
 	"io/fs"
 	"os"
 	"path/filepath"
+	"strconv"
 	"strings"
 
 	"github.com/charmbracelet/log"
@@ -38,15 +39,16 @@ import (
 
 // sub file and directory names.
 const (
-	anonAccess   = "anon-access"
-	allowKeyless = "allow-keyless"
-	admins       = "admins"
-	repos        = "repos"
-	collabs      = "collaborators"
-	description  = "description"
-	exportOk     = "git-daemon-export-ok"
-	private      = "private"
-	settings     = "settings"
+	anonAccess    = "anon-access"
+	defaultBranch = "default-branch"
+	allowKeyless  = "allow-keyless"
+	admins        = "admins"
+	repos         = "repos"
+	collabs       = "collaborators"
+	description   = "description"
+	exportOk      = "git-daemon-export-ok"
+	private       = "private"
+	settings      = "settings"
 )
 
 var (
@@ -86,7 +88,8 @@ func (fb *FileBackend) adminsPath() string {
 }
 
 func (fb *FileBackend) collabsPath(repo string) string {
-	return filepath.Join(fb.path, collabs, repo, collabs)
+	repo = utils.SanitizeRepo(repo) + ".git"
+	return filepath.Join(fb.reposPath(), repo, collabs)
 }
 
 func readOneLine(path string) (string, error) {
@@ -125,7 +128,7 @@ func NewFileBackend(path string) (*FileBackend, error) {
 		}
 	}
 
-	for _, file := range []string{admins, anonAccess, allowKeyless} {
+	for _, file := range []string{admins, anonAccess, allowKeyless, defaultBranch} {
 		fp := filepath.Join(fb.settingsPath(), file)
 		_, err := os.Stat(fp)
 		if errors.Is(err, fs.ErrNotExist) {
@@ -444,6 +447,26 @@ func (fb *FileBackend) AnonAccess() backend.AccessLevel {
 	}
 }
 
+// DefaultBranch returns the default branch for new repositories.
+//
+// It implements backend.Backend.
+func (fb *FileBackend) DefaultBranch() string {
+	line, err := readOneLine(filepath.Join(fb.settingsPath(), defaultBranch))
+	if err != nil {
+		logger.Debug("failed to read default-branch file", "err", err)
+		return defaults[defaultBranch]
+	}
+
+	return line
+}
+
+// SetDefaultBranch sets the default branch for new repositories.
+//
+// It implements backend.Backend.
+func (fb *FileBackend) SetDefaultBranch(branch string) error {
+	return os.WriteFile(filepath.Join(fb.settingsPath(), defaultBranch), []byte(branch), 0600)
+}
+
 // Description returns the description of the given repo.
 //
 // It implements backend.Backend.
@@ -531,28 +554,14 @@ func (fb *FileBackend) IsPrivate(repo string) bool {
 //
 // It implements backend.Backend.
 func (fb *FileBackend) SetAllowKeyless(allow bool) error {
-	f, err := os.OpenFile(filepath.Join(fb.settingsPath(), allowKeyless), os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0600)
-	if err != nil {
-		return fmt.Errorf("failed to open allow-keyless file: %w", err)
-	}
-
-	defer f.Close() //nolint:errcheck
-	_, err = fmt.Fprintln(f, allow)
-	return err
+	return os.WriteFile(filepath.Join(fb.settingsPath(), allowKeyless), []byte(strconv.FormatBool(allow)), 0600)
 }
 
 // SetAnonAccess sets the anonymous access level.
 //
 // It implements backend.Backend.
 func (fb *FileBackend) SetAnonAccess(level backend.AccessLevel) error {
-	f, err := os.OpenFile(filepath.Join(fb.settingsPath(), anonAccess), os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0600)
-	if err != nil {
-		return fmt.Errorf("failed to open anon-access file: %w", err)
-	}
-
-	defer f.Close() //nolint:errcheck
-	_, err = fmt.Fprintln(f, level.String())
-	return err
+	return os.WriteFile(filepath.Join(fb.settingsPath(), anonAccess), []byte(level.String()), 0600)
 }
 
 // SetDescription sets the description of the given repo.
@@ -560,14 +569,7 @@ func (fb *FileBackend) SetAnonAccess(level backend.AccessLevel) error {
 // It implements backend.Backend.
 func (fb *FileBackend) SetDescription(repo string, desc string) error {
 	repo = utils.SanitizeRepo(repo) + ".git"
-	f, err := os.OpenFile(filepath.Join(fb.reposPath(), repo, description), os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0600)
-	if err != nil {
-		return fmt.Errorf("failed to open description file: %w", err)
-	}
-
-	defer f.Close() //nolint:errcheck
-	_, err = fmt.Fprintln(f, desc)
-	return err
+	return os.WriteFile(filepath.Join(fb.reposPath(), repo, description), []byte(desc), 0600)
 }
 
 // SetPrivate sets the private status of the given repo.

server/backend/noop/noop.go 🔗

@@ -1,141 +0,0 @@
-package noop
-
-import (
-	"fmt"
-	"os"
-	"path/filepath"
-
-	"github.com/charmbracelet/soft-serve/git"
-	"github.com/charmbracelet/soft-serve/server/backend"
-	"golang.org/x/crypto/ssh"
-)
-
-var ErrNotImpl = fmt.Errorf("not implemented")
-
-var _ backend.Backend = (*Noop)(nil)
-
-// Noop is a backend that does nothing. It's used for testing.
-type Noop struct {
-	Port string
-}
-
-// Admins implements backend.Backend
-func (*Noop) Admins() ([]string, error) {
-	return nil, nil
-}
-
-// Collaborators implements backend.Backend
-func (*Noop) Collaborators(repo string) ([]string, error) {
-	return nil, nil
-}
-
-// RemoveAdmin implements backend.Backend
-func (*Noop) RemoveAdmin(pk ssh.PublicKey) error {
-	return nil
-}
-
-// RemoveCollaborator implements backend.Backend
-func (*Noop) RemoveCollaborator(pk ssh.PublicKey, repo string) error {
-	return nil
-}
-
-// AccessLevel implements backend.AccessMethod
-func (*Noop) AccessLevel(repo string, pk ssh.PublicKey) backend.AccessLevel {
-	return backend.AdminAccess
-}
-
-// AddAdmin implements backend.Backend
-func (*Noop) AddAdmin(pk ssh.PublicKey, memo string) error {
-	return ErrNotImpl
-}
-
-// AddCollaborator implements backend.Backend
-func (*Noop) AddCollaborator(pk ssh.PublicKey, memo string, repo string) error {
-	return ErrNotImpl
-}
-
-// AllowKeyless implements backend.Backend
-func (*Noop) AllowKeyless() bool {
-	return true
-}
-
-// AnonAccess implements backend.Backend
-func (*Noop) AnonAccess() backend.AccessLevel {
-	return backend.AdminAccess
-}
-
-// CreateRepository implements backend.Backend
-func (*Noop) CreateRepository(name string, private bool) (backend.Repository, error) {
-	temp, err := os.MkdirTemp("", "soft-serve")
-	if err != nil {
-		return nil, err
-	}
-
-	rp := filepath.Join(temp, name)
-	_, err = git.Init(rp, private)
-	if err != nil {
-		return nil, err
-	}
-
-	return &repo{path: rp}, nil
-}
-
-// DeleteRepository implements backend.Backend
-func (*Noop) DeleteRepository(name string) error {
-	return ErrNotImpl
-}
-
-// Description implements backend.Backend
-func (*Noop) Description(repo string) string {
-	return ""
-}
-
-// IsAdmin implements backend.Backend
-func (*Noop) IsAdmin(pk ssh.PublicKey) bool {
-	return true
-}
-
-// IsCollaborator implements backend.Backend
-func (*Noop) IsCollaborator(pk ssh.PublicKey, repo string) bool {
-	return true
-}
-
-// IsPrivate implements backend.Backend
-func (*Noop) IsPrivate(repo string) bool {
-	return false
-}
-
-// RenameRepository implements backend.Backend
-func (*Noop) RenameRepository(oldName string, newName string) error {
-	return ErrNotImpl
-}
-
-// Repositories implements backend.Backend
-func (*Noop) Repositories() ([]backend.Repository, error) {
-	return nil, ErrNotImpl
-}
-
-// Repository implements backend.Backend
-func (*Noop) Repository(repo string) (backend.Repository, error) {
-	return nil, ErrNotImpl
-}
-
-// SetAllowKeyless implements backend.Backend
-func (*Noop) SetAllowKeyless(allow bool) error {
-	return ErrNotImpl
-}
-
-// SetAnonAccess implements backend.Backend
-func (*Noop) SetAnonAccess(level backend.AccessLevel) error {
-	return ErrNotImpl
-}
-
-// SetDescription implements backend.Backend
-func (*Noop) SetDescription(repo string, desc string) error {
-	return ErrNotImpl
-}
-
-// SetPrivate implements backend.Backend
-func (*Noop) SetPrivate(repo string, priv bool) error {
-	return ErrNotImpl
-}

server/backend/noop/repo.go 🔗

@@ -1,32 +0,0 @@
-package noop
-
-import (
-	"github.com/charmbracelet/soft-serve/git"
-	"github.com/charmbracelet/soft-serve/server/backend"
-)
-
-var _ backend.Repository = (*repo)(nil)
-
-type repo struct {
-	path string
-}
-
-// Description implements backend.Repository
-func (*repo) Description() string {
-	return ""
-}
-
-// IsPrivate implements backend.Repository
-func (*repo) IsPrivate() bool {
-	return false
-}
-
-// Name implements backend.Repository
-func (*repo) Name() string {
-	return ""
-}
-
-// Repository implements backend.Repository
-func (r *repo) Open() (*git.Repository, error) {
-	return git.Open(r.path)
-}

server/backend/server.go 🔗

@@ -10,4 +10,8 @@ type ServerBackend interface {
 	AllowKeyless() bool
 	// SetAllowKeyless sets whether or not keyless access is allowed.
 	SetAllowKeyless(allow bool) error
+	// DefaultBranch returns the default branch for new repositories.
+	DefaultBranch() string
+	// SetDefaultBranch sets the default branch for new repositories.
+	SetDefaultBranch(branch string) error
 }