Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: support git worktrees #14

Merged
merged 1 commit into from
Oct 23, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 3 additions & 2 deletions repository/errors.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package repository
import "errors"

var (
ErrNoActiveBranch = errors.New("no active branch")
ErrNoRemoteOrigin = errors.New("no remote named \"origin\" found")
ErrNoActiveBranch = errors.New("no active branch")
ErrNoRemoteOrigin = errors.New("no remote named \"origin\" found")
ErrUnableToReadGitFile = errors.New("unable to read .git file")
)
81 changes: 70 additions & 11 deletions repository/repository.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,25 @@ package repository

import (
"errors"
"os"
"path/filepath"
"strings"

"github.com/go-git/go-billy/v5/helper/chroot"
"github.com/go-git/go-git/v5"
"github.com/go-git/go-git/v5/storage/filesystem"
)

type Repository struct {
goGitRepository *git.Repository
workingDirectory string

// Root git directory, usually workingDirectory/.git
gitDirectory string

// Will be different from gitDirectory if the repository is an external worktree
// e.g. sample-repo/.git/worktrees/sample-repo-external
// See: https://git-scm.com/docs/git-worktree
worktreeGitDirectory string
}

// Return git repository in given directory or parent directories.
Expand All @@ -20,10 +32,10 @@ func FindInParents(path string) (Repository, error) {
return Repository{}, err
}

goGitRepository, err := git.PlainOpen(absolutePath)

_, err = git.PlainOpen(absolutePath)
// Found valid git repository
if err == nil {
return Repository{goGitRepository: goGitRepository}, nil
return makeRepository(absolutePath)
}

if errors.Is(err, git.ErrRepositoryNotExists) {
Expand All @@ -39,24 +51,71 @@ func FindInParents(path string) (Repository, error) {
return Repository{}, err
}

func makeRepository(workingDirectory string) (Repository, error) {
goGitRepository, err := git.PlainOpen(workingDirectory)
if err != nil {
return Repository{}, err
}

// Get git directory
storage, ok := goGitRepository.Storer.(*filesystem.Storage)
if !ok {
return Repository{}, errors.New("storage is not filesystem")
}
filesystem, ok := storage.Filesystem().(*chroot.ChrootHelper)
if !ok {
return Repository{}, errors.New("filesystem is not ChrootHelper")
}
gitDirectory := filesystem.Root()

// check if gitDirectory includes .git/worktrees in path
// if so, we are in an external worktree
if strings.Contains(gitDirectory, ".git/worktrees") {
worktreeGitDirectory := gitDirectory
// go 2 directories up to get the actual git directory
gitDirectory = filepath.Dir(filepath.Dir(gitDirectory))

return Repository{
workingDirectory: workingDirectory,
gitDirectory: gitDirectory,
worktreeGitDirectory: worktreeGitDirectory,
}, nil
} else {
return Repository{
workingDirectory: workingDirectory,
gitDirectory: gitDirectory,
worktreeGitDirectory: gitDirectory,
}, nil
}
}

func (repo *Repository) CurrentBranchName() (string, error) {
// get current head
head, err := repo.goGitRepository.Head()
// Get HEAD file contents from git directory
// We can't use go-git to get the current branch because it doesn't support worktrees
headFile, err := os.ReadFile(filepath.Join(repo.worktreeGitDirectory, "HEAD"))
if err != nil {
return "", err
return "", errors.New("unable to read HEAD")
}

if !head.Name().IsBranch() {
// Parse HEAD file to get branch name
headFileSplit := strings.Split(string(headFile), "ref: refs/heads/")
if len(headFileSplit) != 2 {
// if the HEAD file doesn't include "ref: refs/heads/", we are not on a branch
return "", ErrNoActiveBranch
}
branch := strings.TrimSpace(headFileSplit[1])

// current branch name
return head.Name().Short(), nil
return branch, nil
}

func (repo *Repository) OriginUrl() (string, error) {
goGitRepo, err := git.PlainOpen(repo.gitDirectory)
if err != nil {
return "", err
}

// check if there is a remote named origin
origin, err := repo.goGitRepository.Remote("origin")
origin, err := goGitRepo.Remote("origin")
if err != nil {
return "", ErrNoRemoteOrigin
}
Expand Down