From 7396b57a5df06f6982673e99dda11d5046398a71 Mon Sep 17 00:00:00 2001 From: LordMathis Date: Tue, 19 Nov 2024 22:43:24 +0100 Subject: [PATCH] Rework gitutils package to make it testable --- server/internal/git/client.go | 176 +++++++++++++++++++++++++++++ server/internal/gitutils/git.go | 133 ---------------------- server/internal/storage/git.go | 8 +- server/internal/storage/service.go | 6 +- 4 files changed, 183 insertions(+), 140 deletions(-) create mode 100644 server/internal/git/client.go delete mode 100644 server/internal/gitutils/git.go diff --git a/server/internal/git/client.go b/server/internal/git/client.go new file mode 100644 index 0000000..82790d5 --- /dev/null +++ b/server/internal/git/client.go @@ -0,0 +1,176 @@ +// Package git provides functionalities to interact with Git repositories, including cloning, pulling, committing, and pushing changes. +package git + +import ( + "fmt" + "os" + "path/filepath" + + "github.com/go-git/go-git/v5" + "github.com/go-git/go-git/v5/plumbing/transport/http" +) + +// Config holds the configuration for a Git client +type Config struct { + URL string + Username string + Token string + WorkDir string +} + +// Client defines the interface for Git operations +type Client interface { + Clone() error + Pull() error + Commit(message string) error + Push() error + EnsureRepo() error +} + +// client implements the Client interface +type client struct { + Config + repo *git.Repository +} + +// New creates a new Client instance +// Parameters: +// - url: the URL of the Git repository +// - username: the username for the Git repository +// - token: the access token for the Git repository +// - workDir: the local directory to clone the repository to +// Returns: +// - Client: the Git client +func New(url, username, token, workDir string) Client { + return &client{ + Config: Config{ + URL: url, + Username: username, + Token: token, + WorkDir: workDir, + }, + } +} + +// Clone clones the Git repository to the local directory +// Returns: +// - error: any error that occurred during cloning +func (c *client) Clone() error { + auth := &http.BasicAuth{ + Username: c.Username, + Password: c.Token, + } + + var err error + c.repo, err = git.PlainClone(c.WorkDir, false, &git.CloneOptions{ + URL: c.URL, + Auth: auth, + Progress: os.Stdout, + }) + + if err != nil { + return fmt.Errorf("failed to clone repository: %w", err) + } + + return nil +} + +// Pull pulls the latest changes from the remote repository +// Returns: +// - error: any error that occurred during pulling +func (c *client) Pull() error { + if c.repo == nil { + return fmt.Errorf("repository not initialized") + } + + w, err := c.repo.Worktree() + if err != nil { + return fmt.Errorf("failed to get worktree: %w", err) + } + + auth := &http.BasicAuth{ + Username: c.Username, + Password: c.Token, + } + + err = w.Pull(&git.PullOptions{ + Auth: auth, + Progress: os.Stdout, + }) + + if err != nil && err != git.NoErrAlreadyUpToDate { + return fmt.Errorf("failed to pull changes: %w", err) + } + + return nil +} + +// Commit commits the changes in the repository +// Parameters: +// - message: the commit message +// Returns: +// - error: any error that occurred during committing +func (c *client) Commit(message string) error { + if c.repo == nil { + return fmt.Errorf("repository not initialized") + } + + w, err := c.repo.Worktree() + if err != nil { + return fmt.Errorf("failed to get worktree: %w", err) + } + + _, err = w.Add(".") + if err != nil { + return fmt.Errorf("failed to add changes: %w", err) + } + + _, err = w.Commit(message, &git.CommitOptions{}) + if err != nil { + return fmt.Errorf("failed to commit changes: %w", err) + } + + return nil +} + +// Push pushes the changes to the remote repository +// Returns: +// - error: any error that occurred during pushing +func (c *client) Push() error { + if c.repo == nil { + return fmt.Errorf("repository not initialized") + } + + auth := &http.BasicAuth{ + Username: c.Username, + Password: c.Token, + } + + err := c.repo.Push(&git.PushOptions{ + Auth: auth, + Progress: os.Stdout, + }) + + if err != nil && err != git.NoErrAlreadyUpToDate { + return fmt.Errorf("failed to push changes: %w", err) + } + + return nil +} + +// EnsureRepo ensures the local repository is up-to-date +// Returns: +// - error: any error that occurred during the operation +func (c *client) EnsureRepo() error { + if _, err := os.Stat(filepath.Join(c.WorkDir, ".git")); os.IsNotExist(err) { + return c.Clone() + } + + var err error + c.repo, err = git.PlainOpen(c.WorkDir) + if err != nil { + return fmt.Errorf("failed to open existing repository: %w", err) + } + + return c.Pull() +} diff --git a/server/internal/gitutils/git.go b/server/internal/gitutils/git.go deleted file mode 100644 index 1ee3468..0000000 --- a/server/internal/gitutils/git.go +++ /dev/null @@ -1,133 +0,0 @@ -package gitutils - -import ( - "fmt" - "os" - "path/filepath" - - "github.com/go-git/go-git/v5" - "github.com/go-git/go-git/v5/plumbing/transport/http" -) - -type GitRepo struct { - URL string - Username string - Token string - WorkDir string - repo *git.Repository -} - -func New(url, username, token, workDir string) *GitRepo { - return &GitRepo{ - URL: url, - Username: username, - Token: token, - WorkDir: workDir, - } -} - -func (g *GitRepo) Clone() error { - auth := &http.BasicAuth{ - Username: g.Username, - Password: g.Token, - } - - var err error - g.repo, err = git.PlainClone(g.WorkDir, false, &git.CloneOptions{ - URL: g.URL, - Auth: auth, - Progress: os.Stdout, - }) - - if err != nil { - return fmt.Errorf("failed to clone repository: %w", err) - } - - return nil -} - -func (g *GitRepo) Pull() error { - if g.repo == nil { - return fmt.Errorf("repository not initialized") - } - - w, err := g.repo.Worktree() - if err != nil { - return fmt.Errorf("failed to get worktree: %w", err) - } - - auth := &http.BasicAuth{ - Username: g.Username, - Password: g.Token, - } - - err = w.Pull(&git.PullOptions{ - Auth: auth, - Progress: os.Stdout, - }) - - if err != nil && err != git.NoErrAlreadyUpToDate { - return fmt.Errorf("failed to pull changes: %w", err) - } - - return nil -} - -func (g *GitRepo) Commit(message string) error { - if g.repo == nil { - return fmt.Errorf("repository not initialized") - } - - w, err := g.repo.Worktree() - if err != nil { - return fmt.Errorf("failed to get worktree: %w", err) - } - - _, err = w.Add(".") - if err != nil { - return fmt.Errorf("failed to add changes: %w", err) - } - - _, err = w.Commit(message, &git.CommitOptions{}) - if err != nil { - return fmt.Errorf("failed to commit changes: %w", err) - } - - return nil -} - -func (g *GitRepo) Push() error { - if g.repo == nil { - return fmt.Errorf("repository not initialized") - } - - auth := &http.BasicAuth{ - Username: g.Username, - Password: g.Token, - } - - err := g.repo.Push(&git.PushOptions{ - Auth: auth, - Progress: os.Stdout, - }) - - if err != nil && err != git.NoErrAlreadyUpToDate { - return fmt.Errorf("failed to push changes: %w", err) - } - - return nil -} - -func (g *GitRepo) EnsureRepo() error { - if _, err := os.Stat(filepath.Join(g.WorkDir, ".git")); os.IsNotExist(err) { - return g.Clone() - } - - var err error - g.repo, err = git.PlainOpen(g.WorkDir) - if err != nil { - return fmt.Errorf("failed to open existing repository: %w", err) - } - - return g.Pull() -} diff --git a/server/internal/storage/git.go b/server/internal/storage/git.go index cebd1b0..a626c92 100644 --- a/server/internal/storage/git.go +++ b/server/internal/storage/git.go @@ -2,7 +2,7 @@ package storage import ( "fmt" - "novamd/internal/gitutils" + "novamd/internal/git" ) // RepositoryManager defines the interface for managing Git repositories. @@ -25,9 +25,9 @@ type RepositoryManager interface { func (s *Service) SetupGitRepo(userID, workspaceID int, gitURL, gitUser, gitToken string) error { workspacePath := s.GetWorkspacePath(userID, workspaceID) if _, ok := s.GitRepos[userID]; !ok { - s.GitRepos[userID] = make(map[int]*gitutils.GitRepo) + s.GitRepos[userID] = make(map[int]git.Client) } - s.GitRepos[userID][workspaceID] = gitutils.New(gitURL, gitUser, gitToken, workspacePath) + s.GitRepos[userID][workspaceID] = git.New(gitURL, gitUser, gitToken, workspacePath) return s.GitRepos[userID][workspaceID].EnsureRepo() } @@ -80,7 +80,7 @@ func (s *Service) Pull(userID, workspaceID int) error { } // getGitRepo returns the Git repository for the given user and workspace IDs. -func (s *Service) getGitRepo(userID, workspaceID int) (*gitutils.GitRepo, bool) { +func (s *Service) getGitRepo(userID, workspaceID int) (git.Client, bool) { userRepos, ok := s.GitRepos[userID] if !ok { return nil, false diff --git a/server/internal/storage/service.go b/server/internal/storage/service.go index 5b57b4b..985b66f 100644 --- a/server/internal/storage/service.go +++ b/server/internal/storage/service.go @@ -1,7 +1,7 @@ package storage import ( - "novamd/internal/gitutils" + "novamd/internal/git" ) // Manager interface combines all storage interfaces. @@ -15,7 +15,7 @@ type Manager interface { type Service struct { fs fileSystem RootDir string - GitRepos map[int]map[int]*gitutils.GitRepo // map[userID]map[workspaceID]*gitutils.GitRepo + GitRepos map[int]map[int]git.Client // map[userID]map[workspaceID]*git.Client } // NewService creates a new Storage instance. @@ -37,6 +37,6 @@ func NewServiceWithFS(rootDir string, fs fileSystem) *Service { return &Service{ fs: fs, RootDir: rootDir, - GitRepos: make(map[int]map[int]*gitutils.GitRepo), + GitRepos: make(map[int]map[int]git.Client), } }