mirror of
				https://github.com/Theodor-Springmann-Stiftung/kgpz_web.git
				synced 2025-10-30 17:45:30 +00:00 
			
		
		
		
	
		
			
				
	
	
		
			251 lines
		
	
	
		
			5.1 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
			
		
		
	
	
			251 lines
		
	
	
		
			5.1 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
| package providers
 | |
| 
 | |
| import (
 | |
| 	"errors"
 | |
| 	"fmt"
 | |
| 	"os"
 | |
| 	"sync"
 | |
| 	"time"
 | |
| 
 | |
| 	"github.com/Theodor-Springmann-Stiftung/kgpz_web/helpers/logging"
 | |
| 	"github.com/go-git/go-git/v5"
 | |
| 	"github.com/go-git/go-git/v5/plumbing"
 | |
| )
 | |
| 
 | |
| var InvalidBranchError = errors.New("The currently checked out branch does not match the requested branch. Please checkout the correct branch first.")
 | |
| var InvalidStateError = errors.New("The GitProvider is not in a valid state. Fix the issues or continue without Git data.")
 | |
| var NoURLProvidedError = errors.New("No URL provided for GitProvider.")
 | |
| var NoPathProvidedError = errors.New("No path or branch provided for GitProvider.")
 | |
| 
 | |
| // NOTE: GitProvider does not open any worktree files, it only
 | |
| // - reads in information from the repo, given a path
 | |
| // - clones a repo, given an URL & a path
 | |
| // - pulls a repo, given a path
 | |
| // In case of success it updates it's state: the commit hash and date. Then it closes the repo.
 | |
| type GitProvider struct {
 | |
| 	mu sync.Mutex
 | |
| 
 | |
| 	URL    string
 | |
| 	Path   string
 | |
| 	Branch string
 | |
| 	Commit string
 | |
| 	Date   time.Time
 | |
| }
 | |
| 
 | |
| func NewGitProvider(url string, path string, branch string) (*GitProvider, error) {
 | |
| 	// TODO: check if directory is empty
 | |
| 	// TODO: force clone
 | |
| 	if _, err := os.Stat(path); err == nil {
 | |
| 		return GitProviderFromPath(path, branch)
 | |
| 	}
 | |
| 
 | |
| 	return GitProviderFromURL(url, path, branch)
 | |
| }
 | |
| 
 | |
| func GitProviderFromPath(path string, branch string) (*GitProvider, error) {
 | |
| 	if branch == "" || path == "" {
 | |
| 		return nil, NoPathProvidedError
 | |
| 	}
 | |
| 
 | |
| 	gp := GitProvider{Path: path, Branch: branch}
 | |
| 	if err := gp.Read(); err != nil {
 | |
| 		return nil, err
 | |
| 	}
 | |
| 
 | |
| 	return &gp, nil
 | |
| }
 | |
| 
 | |
| func GitProviderFromURL(url string, path string, branch string) (*GitProvider, error) {
 | |
| 	if url == "" {
 | |
| 		return nil, NoURLProvidedError
 | |
| 	}
 | |
| 
 | |
| 	if branch == "" || path == "" {
 | |
| 		return nil, NoPathProvidedError
 | |
| 	}
 | |
| 
 | |
| 	gp := GitProvider{URL: url, Path: path, Branch: branch}
 | |
| 	if err := gp.Clone(); err != nil {
 | |
| 		return nil, err
 | |
| 	}
 | |
| 
 | |
| 	return &gp, nil
 | |
| }
 | |
| 
 | |
| // Returs true if the repo was updated remotely, false otherwise
 | |
| func (g *GitProvider) Pull() (error, bool) {
 | |
| 	g.mu.Lock()
 | |
| 	defer g.mu.Unlock()
 | |
| 
 | |
| 	branch := plumbing.NewBranchReferenceName(g.Branch)
 | |
| 	repo, err := git.PlainOpen(g.Path)
 | |
| 	if err != nil {
 | |
| 		logging.Error(err, "Error opening repository")
 | |
| 		return err, false
 | |
| 	}
 | |
| 
 | |
| 	wt, err := repo.Worktree()
 | |
| 	if err != nil {
 | |
| 		logging.Error(err, "Error getting worktree")
 | |
| 		return err, false
 | |
| 	}
 | |
| 
 | |
| 	if err := wt.Pull(&git.PullOptions{
 | |
| 		RemoteName:    "origin",
 | |
| 		ReferenceName: branch,
 | |
| 		Progress:      os.Stdout,
 | |
| 	}); err != nil {
 | |
| 		if err == git.NoErrAlreadyUpToDate {
 | |
| 			return nil, false
 | |
| 		}
 | |
| 		logging.Error(err, "Error pulling repository")
 | |
| 		return err, false
 | |
| 	}
 | |
| 	defer wt.Clean(&git.CleanOptions{Dir: true})
 | |
| 
 | |
| 	oldCommit := g.Commit
 | |
| 	if err := g.setValues(repo); err != nil {
 | |
| 		logging.Error(err, "Error setting values for new commit")
 | |
| 		return err, false
 | |
| 	}
 | |
| 
 | |
| 	if oldCommit == g.Commit {
 | |
| 		return nil, false
 | |
| 	}
 | |
| 
 | |
| 	return nil, true
 | |
| }
 | |
| 
 | |
| func (g *GitProvider) Clone() error {
 | |
| 	if g.URL == "" {
 | |
| 		return NoURLProvidedError
 | |
| 	}
 | |
| 
 | |
| 	g.mu.Lock()
 | |
| 	defer g.mu.Unlock()
 | |
| 
 | |
| 	branch := plumbing.NewBranchReferenceName(g.Branch)
 | |
| 
 | |
| 	repo, err := git.PlainClone(g.Path, false, &git.CloneOptions{
 | |
| 		URL:      g.URL,
 | |
| 		Progress: os.Stdout,
 | |
| 	})
 | |
| 
 | |
| 	if err != nil {
 | |
| 		return err
 | |
| 	}
 | |
| 
 | |
| 	wt, err := repo.Worktree()
 | |
| 	if err != nil {
 | |
| 		return err
 | |
| 	}
 | |
| 	defer wt.Clean(&git.CleanOptions{Dir: true})
 | |
| 
 | |
| 	if err := wt.Checkout(&git.CheckoutOptions{
 | |
| 		Branch: branch,
 | |
| 		Force:  true,
 | |
| 	}); err != nil {
 | |
| 		return err
 | |
| 	}
 | |
| 
 | |
| 	return g.setValues(repo)
 | |
| }
 | |
| 
 | |
| // Implement String Interface
 | |
| func (g *GitProvider) String() string {
 | |
| 	return fmt.Sprintf("GitProvider\nURL: %s\nPath: %s\nBranch: %s\nCommit: %s\nDate: %s\n", g.URL, g.Path, g.Branch, g.Commit, g.Date)
 | |
| }
 | |
| 
 | |
| func (g *GitProvider) setValues(repo *git.Repository) error {
 | |
| 	log, err := repo.Log(&git.LogOptions{})
 | |
| 	if err != nil {
 | |
| 		return err
 | |
| 	}
 | |
| 	defer log.Close()
 | |
| 
 | |
| 	commit, err := log.Next()
 | |
| 	if err != nil {
 | |
| 		return err
 | |
| 	}
 | |
| 
 | |
| 	g.Commit = commit.Hash.String()
 | |
| 	g.Date = commit.Author.When
 | |
| 
 | |
| 	return nil
 | |
| }
 | |
| 
 | |
| func (g *GitProvider) Read() error {
 | |
| 	g.mu.Lock()
 | |
| 	defer g.mu.Unlock()
 | |
| 
 | |
| 	repo, err := git.PlainOpen(g.Path)
 | |
| 	if err != nil {
 | |
| 		return err
 | |
| 	}
 | |
| 
 | |
| 	if err := g.ValidateBranch(repo); err != nil {
 | |
| 		branch := plumbing.NewBranchReferenceName(g.Branch)
 | |
| 		wt, err := repo.Worktree()
 | |
| 		if err != nil {
 | |
| 			return err
 | |
| 		}
 | |
| 		defer wt.Clean(&git.CleanOptions{Dir: true})
 | |
| 
 | |
| 		if err := wt.Checkout(&git.CheckoutOptions{
 | |
| 			Branch: branch,
 | |
| 			Force:  true,
 | |
| 		}); err != nil {
 | |
| 			return err
 | |
| 		}
 | |
| 
 | |
| 		if err := g.ValidateBranch(repo); err != nil {
 | |
| 			return err
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	return g.setValues(repo)
 | |
| }
 | |
| 
 | |
| func (g *GitProvider) Validate() error {
 | |
| 	repo, err := git.PlainOpen(g.Path)
 | |
| 	if err != nil {
 | |
| 		return err
 | |
| 	}
 | |
| 
 | |
| 	if err := g.ValidateBranch(repo); err != nil {
 | |
| 		return err
 | |
| 	}
 | |
| 
 | |
| 	if err := g.ValidateCommit(); err != nil {
 | |
| 		return err
 | |
| 	}
 | |
| 
 | |
| 	return nil
 | |
| }
 | |
| 
 | |
| func (g *GitProvider) ValidateBranch(repo *git.Repository) error {
 | |
| 	head, err := repo.Head()
 | |
| 	if err != nil {
 | |
| 		return err
 | |
| 	}
 | |
| 
 | |
| 	cbranch := head.Name().Short()
 | |
| 	if cbranch != g.Branch {
 | |
| 		return InvalidBranchError
 | |
| 	}
 | |
| 
 | |
| 	return nil
 | |
| }
 | |
| 
 | |
| func (g *GitProvider) Wait() {
 | |
| 	g.mu.Lock()
 | |
| 	defer g.mu.Unlock()
 | |
| }
 | |
| 
 | |
| func (g *GitProvider) ValidateCommit() error {
 | |
| 	if g.Commit == "" || g.Date.IsZero() {
 | |
| 		return InvalidStateError
 | |
| 	}
 | |
| 	return nil
 | |
| }
 | 
