mirror of
https://github.com/lordmathis/lemma.git
synced 2025-11-05 23:44:22 +00:00
Rework gitutils package to make it testable
This commit is contained in:
176
server/internal/git/client.go
Normal file
176
server/internal/git/client.go
Normal file
@@ -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()
|
||||||
|
}
|
||||||
@@ -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()
|
|
||||||
}
|
|
||||||
@@ -2,7 +2,7 @@ package storage
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"novamd/internal/gitutils"
|
"novamd/internal/git"
|
||||||
)
|
)
|
||||||
|
|
||||||
// RepositoryManager defines the interface for managing Git repositories.
|
// 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 {
|
func (s *Service) SetupGitRepo(userID, workspaceID int, gitURL, gitUser, gitToken string) error {
|
||||||
workspacePath := s.GetWorkspacePath(userID, workspaceID)
|
workspacePath := s.GetWorkspacePath(userID, workspaceID)
|
||||||
if _, ok := s.GitRepos[userID]; !ok {
|
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()
|
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.
|
// 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]
|
userRepos, ok := s.GitRepos[userID]
|
||||||
if !ok {
|
if !ok {
|
||||||
return nil, false
|
return nil, false
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
package storage
|
package storage
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"novamd/internal/gitutils"
|
"novamd/internal/git"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Manager interface combines all storage interfaces.
|
// Manager interface combines all storage interfaces.
|
||||||
@@ -15,7 +15,7 @@ type Manager interface {
|
|||||||
type Service struct {
|
type Service struct {
|
||||||
fs fileSystem
|
fs fileSystem
|
||||||
RootDir string
|
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.
|
// NewService creates a new Storage instance.
|
||||||
@@ -37,6 +37,6 @@ func NewServiceWithFS(rootDir string, fs fileSystem) *Service {
|
|||||||
return &Service{
|
return &Service{
|
||||||
fs: fs,
|
fs: fs,
|
||||||
RootDir: rootDir,
|
RootDir: rootDir,
|
||||||
GitRepos: make(map[int]map[int]*gitutils.GitRepo),
|
GitRepos: make(map[int]map[int]git.Client),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user