Repository: nektos/act (language: go)

Issue:
Enhancement: working with git worktree
## Describe feature

For a working act workflow, when you are using `git worktree` ACT does not work. It does not fill repository data, certainly because it does not follow links in `.git` file in a git worktree.

If there is a workaround, i'd take it! :)

Example:
```
WARN[0000] unable to get git repo: unable to find git repo
WARN[0000] unable to get git revision: unable to find git repo
WARN[0000] unable to get git ref: unable to find git repo
WARN[0000] unable to get git repo: unable to find git repo
WARN[0000] unable to get git revision: unable to find git repo
WARN[0000] unable to get git ref: unable to find git repo
```

```
$ cat .git
gitdir: /home/xxx/yyy/zzz.git/worktrees/dev
```


Discussion / hints:
Issue is stale and will be closed in 14 days unless there is new activity
@catthehacker Thanks for creating this tool, I've made a PR for this feature
#829 
Thank @cplee, I'm just trying to maintain it 🐱‍💻
@cplee thx!
Awesome @lvlrt, thanks 🤩 
Just got bit by this one. Is there a timeline for merging the PR?
No, because it doesn't work

Fix patch:
diff --git a/pkg/common/git/git.go b/pkg/common/git/git.go
index d03dada3d87..74df3c0f618 100644
--- a/pkg/common/git/git.go
+++ b/pkg/common/git/git.go
@@ -7,7 +7,6 @@ import (
 	"io"
 	"os"
 	"path"
-	"path/filepath"
 	"regexp"
 	"strings"
 	"sync"
@@ -17,8 +16,8 @@ import (
 	"github.com/go-git/go-git/v5"
 	"github.com/go-git/go-git/v5/config"
 	"github.com/go-git/go-git/v5/plumbing"
+	"github.com/go-git/go-git/v5/plumbing/storer"
 	"github.com/go-git/go-git/v5/plumbing/transport/http"
-	"github.com/go-ini/ini"
 	"github.com/mattn/go-isatty"
 	log "github.com/sirupsen/logrus"
 )
@@ -55,41 +54,40 @@ func (e *Error) Commit() string {
 // FindGitRevision get the current git revision
 func FindGitRevision(ctx context.Context, file string) (shortSha string, sha string, err error) {
 	logger := common.Logger(ctx)
-	gitDir, err := findGitDirectory(file)
+
+	gitDir, err := git.PlainOpenWithOptions(
+		file,
+		&git.PlainOpenOptions{
+			DetectDotGit:          true,
+			EnableDotGitCommonDir: true,
+		},
+	)
+
 	if err != nil {
+		logger.WithError(err).Error("path", file, "not located inside a git repository")
 		return "", "", err
 	}
 
-	bts, err := os.ReadFile(filepath.Join(gitDir, "HEAD"))
+	head, err := gitDir.Reference(plumbing.HEAD, true)
 	if err != nil {
 		return "", "", err
 	}
 
-	var ref = strings.TrimSpace(strings.TrimPrefix(string(bts), "ref:"))
-	var refBuf []byte
-	if strings.HasPrefix(ref, "refs/") {
-		// load commitid ref
-		refBuf, err = os.ReadFile(filepath.Join(gitDir, ref))
-		if err != nil {
-			return "", "", err
-		}
-	} else {
-		refBuf = []byte(ref)
+	if head.Hash().IsZero() {
+		return "", "", fmt.Errorf("HEAD sha1 could not be resolved")
 	}
 
-	logger.Debugf("Found revision: %s", refBuf)
-	return string(refBuf[:7]), strings.TrimSpace(string(refBuf)), nil
+	hash := head.Hash().String()
+
+	logger.Debugf("Found revision: %s", hash)
+	return hash[:7], strings.TrimSpace(hash), nil
 }
 
 // FindGitRef get the current git ref
 func FindGitRef(ctx context.Context, file string) (string, error) {
 	logger := common.Logger(ctx)
-	gitDir, err := findGitDirectory(file)
-	if err != nil {
-		return "", err
-	}
-	logger.Debugf("Loading revision from git directory '%s'", gitDir)
 
+	logger.Debugf("Loading revision from git directory")
 	_, ref, err := FindGitRevision(ctx, file)
 	if err != nil {
 		return "", err
@@ -100,28 +98,58 @@ func FindGitRef(ctx context.Context, file string) (string, error) {
 	// Prefer the git library to iterate over the references and find a matching tag or branch.
 	var refTag = ""
 	var refBranch = ""
-	r, err := git.PlainOpen(filepath.Join(gitDir, ".."))
-	if err == nil {
-		iter, err := r.References()
-		if err == nil {
-			for {
-				r, err := iter.Next()
-				if r == nil || err != nil {
-					break
-				}
-				// logger.Debugf("Reference: name=%s sha=%s", r.Name().String(), r.Hash().String())
-				if r.Hash().String() == ref {
-					if r.Name().IsTag() {
-						refTag = r.Name().String()
-					}
-					if r.Name().IsBranch() {
-						refBranch = r.Name().String()
-					}
-				}
+	repo, err := git.PlainOpenWithOptions(
+		file,
+		&git.PlainOpenOptions{
+			DetectDotGit:          true,
+			EnableDotGitCommonDir: true,
+		},
+	)
+
+	if err != nil {
+		return "", err
+	}
+
+	iter, err := repo.References()
+	if err != nil {
+		return "", err
+	}
+
+	// find the reference that matches the revision's has
+	err = iter.ForEach(func(r *plumbing.Reference) error {
+		/* tags and branches will have the same hash
+		 * when a user checks out a tag, it is not mentioned explicitly
+		 * in the go-git package, we must identify the revision
+		 * then check if any tag matches that revision,
+		 * if so then we checked out a tag
+		 * else we look for branches and if matches,
+		 * it means we checked out a branch
+		 *
+		 * If a branches matches first we must continue and check all tags (all references)
+		 * in case we match with a tag later in the interation
+		 */
+		if r.Hash().String() == ref {
+			if r.Name().IsTag() {
+				refTag = r.Name().String()
+			}
+			if r.Name().IsBranch() {
+				refBranch = r.Name().String()
 			}
-			iter.Close()
 		}
+
+		// we found what we where looking for
+		if refTag != "" && refBranch != "" {
+			return storer.ErrStop
+		}
+
+		return nil
+	})
+
+	if err != nil {
+		return "", err
 	}
+
+	// order matters here see above comment.
 	if refTag != "" {
 		return refTag, nil
 	}
@@ -129,39 +157,7 @@ func FindGitRef(ctx context.Context, file string) (string, error) {
 		return refBranch, nil
 	}
 
-	// If the above doesn't work, fall back to the old way
-
-	// try tags first
-	tag, err := findGitPrettyRef(ctx, ref, gitDir, "refs/tags")
-	if err != nil || tag != "" {
-		return tag, err
-	}
-	// and then branches
-	return findGitPrettyRef(ctx, ref, gitDir, "refs/heads")
-}
-
-func findGitPrettyRef(ctx context.Context, head, root, sub string) (string, error) {
-	var name string
-	var err = filepath.Walk(filepath.Join(root, sub), func(path string, info os.FileInfo, err error) error {
-		if err != nil {
-			return err
-		}
-		if name != "" || info.IsDir() {
-			return nil
-		}
-		var bts []byte
-		if bts, err = os.ReadFile(path); err != nil {
-			return err
-		}
-		var pointsTo = strings.TrimSpace(string(bts))
-		if head == pointsTo {
-			// On Windows paths are separated with backslash character so they should be replaced to provide proper git refs format
-			name = strings.TrimPrefix(strings.ReplaceAll(strings.Replace(path, root, "", 1), `\`, `/`), "/")
-			common.Logger(ctx).Debugf("HEAD matches %s", name)
-		}
-		return nil
-	})
-	return name, err
+	return "", fmt.Errorf("failed to identify reference (tag/branch) for the checked-out revision '%s'", ref)
 }
 
 // FindGithubRepo get the repo
@@ -179,26 +175,27 @@ func FindGithubRepo(ctx context.Context, file, githubInstance, remoteName string
 }
 
 func findGitRemoteURL(ctx context.Context, file, remoteName string) (string, error) {
-	gitDir, err := findGitDirectory(file)
+	repo, err := git.PlainOpenWithOptions(
+		file,
+		&git.PlainOpenOptions{
+			DetectDotGit:          true,
+			EnableDotGitCommonDir: true,
+		},
+	)
 	if err != nil {
 		return "", err
 	}
-	common.Logger(ctx).Debugf("Loading slug from git directory '%s'", gitDir)
 
-	gitconfig, err := ini.InsensitiveLoad(fmt.Sprintf("%s/config", gitDir))
-	if err != nil {
-		return "", err
-	}
-	remote, err := gitconfig.GetSection(fmt.Sprintf(`remote "%s"`, remoteName))
+	remote, err := repo.Remote(remoteName)
 	if err != nil {
 		return "", err
 	}
-	urlKey, err := remote.GetKey("url")
-	if err != nil {
-		return "", err
+
+	if len(remote.Config().URLs) < 1 {
+		return "", fmt.Errorf("remote '%s' exists but has no URL", remoteName)
 	}
-	url := urlKey.String()
-	return url, nil
+
+	return remote.Config().URLs[0], nil
 }
 
 func findGitSlug(url string, githubInstance string) (string, string, error) {
@@ -222,35 +219,6 @@ func findGitSlug(url string, githubInstance string) (string, string, error) {
 	return "", url, nil
 }
 
-func findGitDirectory(fromFile string) (string, error) {
-	absPath, err := filepath.Abs(fromFile)
-	if err != nil {
-		return "", err
-	}
-
-	fi, err := os.Stat(absPath)
-	if err != nil {
-		return "", err
-	}
-
-	var dir string
-	if fi.Mode().IsDir() {
-		dir = absPath
-	} else {
-		dir = filepath.Dir(absPath)
-	}
-
-	gitPath := filepath.Join(dir, ".git")
-	fi, err = os.Stat(gitPath)
-	if err == nil && fi.Mode().IsDir() {
-		return gitPath, nil
-	} else if dir == "/" || dir == "C:\\" || dir == "c:\\" {
-		return "", &Error{err: ErrNoRepo}
-	}
-
-	return findGitDirectory(filepath.Dir(dir))
-}
-
 // NewGitCloneExecutorInput the input for the NewGitCloneExecutor
 type NewGitCloneExecutorInput struct {
 	URL   string


Test patch:
diff --git a/pkg/common/git/git_test.go b/pkg/common/git/git_test.go
index 9798193ed02..5d3e58864ec 100644
--- a/pkg/common/git/git_test.go
+++ b/pkg/common/git/git_test.go
@@ -82,12 +82,19 @@ func TestFindGitRemoteURL(t *testing.T) {
 	assert.NoError(err)
 
 	remoteURL := "https://git-codecommit.us-east-1.amazonaws.com/v1/repos/my-repo-name"
-	err = gitCmd("config", "-f", fmt.Sprintf("%s/.git/config", basedir), "--add", "remote.origin.url", remoteURL)
+	err = gitCmd("-C", basedir, "remote", "add", "origin", remoteURL)
 	assert.NoError(err)
 
 	u, err := findGitRemoteURL(context.Background(), basedir, "origin")
 	assert.NoError(err)
 	assert.Equal(remoteURL, u)
+
+	remoteURL = "git@github.com/AwesomeOwner/MyAwesomeRepo.git"
+	err = gitCmd("-C", basedir, "remote", "add", "upstream", remoteURL)
+	assert.NoError(err)
+	u, err = findGitRemoteURL(context.Background(), basedir, "upstream")
+	assert.NoError(err)
+	assert.Equal(remoteURL, u)
 }
 
 func TestGitFindRef(t *testing.T) {