package git import ( "errors" "farga/lib" "fmt" "os" "path/filepath" "strings" git "github.com/go-git/go-git/v6" "github.com/go-git/go-git/v6/plumbing" "github.com/go-git/go-git/v6/plumbing/object" "github.com/google/uuid" ) var ErrEmptyRepo = errors.New("git: repository has no branches. (blank repo)") var ErrRepoDoesNotExist = errors.New("git: no such repository exists.") var ErrRepoHasNoBranches = errors.New("git: repository has no branches.") var ErrRepoHasNoHead = errors.New("git: repository has no HEAD.") var ErrInvalidBranch = errors.New("git: no such branch exists.") func RepoPath(user uuid.UUID, repo uuid.UUID) string { base, err := filepath.Abs(lib.GitReposDir) if err != nil { return "" } return strings.TrimSuffix(filepath.Join( base, user.String(), repo.String(), )+"/", ".git") } // creates a git repo func InitRepo(userID uuid.UUID, repoID uuid.UUID) (*git.Repository, error) { repoPath := RepoPath(userID, repoID) // if you want to auto-setup a branch, run: // plumbedBranch := plumbing.NewBranchReferenceName(branch) // repo, err := git.PlainInit(repoPath, true, git.WithDefaultBranch(plumbedBranch)) // is a bare repo: https://tms-outsource.com/blog/posts/what-is-a-bare-repository/ repo, err := git.PlainInit(repoPath, true) // is a bare repo: https://tms-outsource.com/blog/posts/what-is-a-bare-repository/ return repo, err } type GitRepo struct { Git *git.Repository Head *plumbing.Reference ShortHash string LatestCommit *object.Commit Readme ReadmeType Files []File } // Opens a git repo by the defined branch (branch can be nil to be autoselected as HEAD) func Repo(userID uuid.UUID, repoID uuid.UUID, branch string) (*GitRepo, error) { path := RepoPath(userID, repoID) repo, err := git.PlainOpen(path) if err != nil { switch err { case git.ErrRepositoryNotExists: return nil, ErrRepoDoesNotExist default: return nil, err } } head, err := ResolveHead(repo, branch) // potentially nil. if err != nil && err != ErrRepoHasNoHead { return nil, err } var latestCommit *object.Commit var shortHash string var readme ReadmeType // extra info if head != nil { latestCommit, err = object.GetCommit(repo.Storer, head.Hash()) if err != nil { fmt.Println(err) } else { if tree, err := latestCommit.Tree(); err == nil { readme = Readme(tree) } shortHash, err = ShortenHash(head.Hash().String()) } } return &GitRepo{ Git: repo, ShortHash: shortHash, Head: head, LatestCommit: latestCommit, Readme: readme, }, nil } // resolves the head of a git repo func ResolveHead(repo *git.Repository, branchName string) (ref *plumbing.Reference, err error) { // if a branch was provided if branchName != "" { ref, err = repo.Reference(plumbing.NewBranchReferenceName(branchName), false) if errors.Is(err, plumbing.ErrReferenceNotFound) { return nil, ErrInvalidBranch // doesn't exist. } return ref, nil } ref, err = repo.Head() // get HEAD as default if err != nil { // no HEAD? fine. I'll do it myself branches, branchErr := repo.Branches() if branchErr != nil { return nil, branchErr // idk man. } firstBranch, nextErr := branches.Next() branches.Close() if nextErr != nil { // holy shit there are no branches return nil, ErrRepoHasNoHead } // thank god there's a branch return firstBranch, nil } // just return HEAD return ref, nil } // gets the tree from a commit. func BaseTree(repo *git.Repository, head *plumbing.Reference) (*object.Tree, error) { commit, err := object.GetCommit(repo.Storer, head.Hash()) if err != nil { return nil, err } return commit.Tree() } // deletes a repo on-disk. func DeleteRepo(userID uuid.UUID, repoID uuid.UUID) { os.RemoveAll(RepoPath(userID, repoID)) }