From 2aa0699aecff2e596e280165ea48eac85c5f9a6a Mon Sep 17 00:00:00 2001
From: R <me@hackerc.at>
Date: Fri, 10 Jun 2022 23:16:42 +0200
Subject: [PATCH] refactor: remove github.com/pkg/errors dependency (#1077)

* refactor: split out common/git

* refactor: move git options to separate func

* refactor: remove github.com/pkg/errors dependency

* fix(golangci-lint): forbid github.com/pkg/errors

* style: fix typo

* style: fix typo

Co-authored-by: mergify[bot] <37929162+mergify[bot]@users.noreply.github.com>
---
 .golangci.yml                         |  4 +-
 go.mod                                |  2 +-
 pkg/common/{ => git}/git.go           | 77 +++++++++++++++++----------
 pkg/common/{ => git}/git_test.go      | 20 +++----
 pkg/container/docker_pull.go          |  4 +-
 pkg/container/docker_run.go           | 62 +++++++++++----------
 pkg/container/file_collector.go       |  3 +-
 pkg/model/github_context.go           |  7 +--
 pkg/model/planner.go                  | 11 ++--
 pkg/model/planner_test.go             |  4 +-
 pkg/runner/run_context.go             | 11 ++--
 pkg/runner/step_action_remote.go      | 21 ++++----
 pkg/runner/step_action_remote_test.go |  6 ++-
 13 files changed, 134 insertions(+), 98 deletions(-)
 rename pkg/common/{ => git}/git.go (90%)
 rename pkg/common/{ => git}/git_test.go (96%)

diff --git a/.golangci.yml b/.golangci.yml
index f48e55c..d31aa34 100644
--- a/.golangci.yml
+++ b/.golangci.yml
@@ -1,4 +1,4 @@
-# Minimum golangci-lint version required: v1.42.0
+# Minimum golangci-lint version required: v1.46.0
 run:
   timeout: 3m
 
@@ -22,9 +22,11 @@ linters-settings:
     list-type: blacklist
     include-go-root: true
     packages:
+      - github.com/pkg/errors
       - gotest.tools/v3/assert
       - log
     packages-with-error-message:
+      - github.com/pkg/errors: 'Please use "errors" package from standard library'
       - gotest.tools/v3: 'Please keep tests unified using only github.com/stretchr/testify'
       - log: 'Please keep logging unified using only github.com/sirupsen/logrus'
 
diff --git a/go.mod b/go.mod
index 2457c40..0b0a3bb 100644
--- a/go.mod
+++ b/go.mod
@@ -20,7 +20,6 @@ require (
 	github.com/moby/buildkit v0.10.3
 	github.com/opencontainers/image-spec v1.0.3-0.20211202183452-c5a74bcca799
 	github.com/opencontainers/selinux v1.10.1
-	github.com/pkg/errors v0.9.1
 	github.com/rhysd/actionlint v1.6.13
 	github.com/sabhiram/go-gitignore v0.0.0-20210923224102-525f6e181f06
 	github.com/sirupsen/logrus v1.8.1
@@ -58,6 +57,7 @@ require (
 	github.com/moby/sys/mountinfo v0.6.0 // indirect
 	github.com/opencontainers/go-digest v1.0.0 // indirect
 	github.com/opencontainers/runc v1.1.1 // indirect
+	github.com/pkg/errors v0.9.1 // indirect
 	github.com/pmezard/go-difflib v1.0.0 // indirect
 	github.com/rivo/uniseg v0.2.0 // indirect
 	github.com/robfig/cron v1.2.0 // indirect
diff --git a/pkg/common/git.go b/pkg/common/git/git.go
similarity index 90%
rename from pkg/common/git.go
rename to pkg/common/git/git.go
index 6c1f501..51c8072 100644
--- a/pkg/common/git.go
+++ b/pkg/common/git/git.go
@@ -1,7 +1,8 @@
-package common
+package git
 
 import (
 	"context"
+	"errors"
 	"fmt"
 	"io"
 	"io/ioutil"
@@ -12,13 +13,14 @@ import (
 	"strings"
 	"sync"
 
-	git "github.com/go-git/go-git/v5"
+	"github.com/nektos/act/pkg/common"
+
+	"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/transport/http"
 	"github.com/go-ini/ini"
 	"github.com/mattn/go-isatty"
-	"github.com/pkg/errors"
 	log "github.com/sirupsen/logrus"
 )
 
@@ -29,8 +31,28 @@ var (
 	githubSSHRegex      = regexp.MustCompile(`github.com[:/](.+)/(.+?)(?:.git)?$`)
 
 	cloneLock sync.Mutex
+
+	ErrShortRef = errors.New("short SHA references are not supported")
+	ErrNoRepo   = errors.New("unable to find git repo")
 )
 
+type Error struct {
+	err    error
+	commit string
+}
+
+func (e *Error) Error() string {
+	return e.err.Error()
+}
+
+func (e *Error) Unwrap() error {
+	return e.err
+}
+
+func (e *Error) Commit() string {
+	return e.commit
+}
+
 // FindGitRevision get the current git revision
 func FindGitRevision(file string) (shortSha string, sha string, err error) {
 	gitDir, err := findGitDirectory(file)
@@ -222,7 +244,7 @@ func findGitDirectory(fromFile string) (string, error) {
 	if err == nil && fi.Mode().IsDir() {
 		return gitPath, nil
 	} else if dir == "/" || dir == "C:\\" || dir == "c:\\" {
-		return "", errors.New("unable to find git repo")
+		return "", &Error{err: ErrNoRepo}
 	}
 
 	return findGitDirectory(filepath.Dir(dir))
@@ -277,11 +299,27 @@ func CloneIfRequired(ctx context.Context, refName plumbing.ReferenceName, input
 	return r, nil
 }
 
+func gitOptions(token string) (fetchOptions git.FetchOptions, pullOptions git.PullOptions) {
+	fetchOptions.RefSpecs = []config.RefSpec{"refs/*:refs/*", "HEAD:refs/heads/HEAD"}
+	pullOptions.Force = true
+
+	if token != "" {
+		auth := &http.BasicAuth{
+			Username: "token",
+			Password: token,
+		}
+		fetchOptions.Auth = auth
+		pullOptions.Auth = auth
+	}
+
+	return fetchOptions, pullOptions
+}
+
 // NewGitCloneExecutor creates an executor to clone git repos
 // nolint:gocyclo
-func NewGitCloneExecutor(input NewGitCloneExecutorInput) Executor {
+func NewGitCloneExecutor(input NewGitCloneExecutorInput) common.Executor {
 	return func(ctx context.Context) error {
-		logger := Logger(ctx)
+		logger := common.Logger(ctx)
 		logger.Infof("  \u2601  git clone '%s' # ref=%s", input.URL, input.Ref)
 		logger.Debugf("  cloning %s to %s", input.URL, input.Dir)
 
@@ -295,15 +333,7 @@ func NewGitCloneExecutor(input NewGitCloneExecutorInput) Executor {
 		}
 
 		// fetch latest changes
-		fetchOptions := git.FetchOptions{
-			RefSpecs: []config.RefSpec{"refs/*:refs/*", "HEAD:refs/heads/HEAD"},
-		}
-		if input.Token != "" {
-			fetchOptions.Auth = &http.BasicAuth{
-				Username: "token",
-				Password: input.Token,
-			}
-		}
+		fetchOptions, pullOptions := gitOptions(input.Token)
 
 		err = r.Fetch(&fetchOptions)
 		if err != nil && !errors.Is(err, git.NoErrAlreadyUpToDate) {
@@ -317,7 +347,10 @@ func NewGitCloneExecutor(input NewGitCloneExecutorInput) Executor {
 		}
 
 		if hash.String() != input.Ref && strings.HasPrefix(hash.String(), input.Ref) {
-			return errors.Wrap(errors.New(hash.String()), "short SHA references are not supported")
+			return &Error{
+				err:    ErrShortRef,
+				commit: hash.String(),
+			}
 		}
 
 		// At this point we need to know if it's a tag or a branch
@@ -365,17 +398,7 @@ func NewGitCloneExecutor(input NewGitCloneExecutorInput) Executor {
 			}
 		}
 
-		pullOptions := git.PullOptions{
-			Force: true,
-		}
-		if input.Token != "" {
-			pullOptions.Auth = &http.BasicAuth{
-				Username: "token",
-				Password: input.Token,
-			}
-		}
-
-		if err = w.Pull(&pullOptions); err != nil && err.Error() != "already up-to-date" {
+		if err = w.Pull(&pullOptions); err != nil && err != git.NoErrAlreadyUpToDate {
 			logger.Debugf("Unable to pull %s: %v", refName, err)
 		}
 		logger.Debugf("Cloned %s to %s", input.URL, input.Dir)
diff --git a/pkg/common/git_test.go b/pkg/common/git/git_test.go
similarity index 96%
rename from pkg/common/git_test.go
rename to pkg/common/git/git_test.go
index 48c6455..932eb14 100644
--- a/pkg/common/git_test.go
+++ b/pkg/common/git/git_test.go
@@ -1,4 +1,4 @@
-package common
+package git
 
 import (
 	"context"
@@ -173,25 +173,26 @@ func TestGitFindRef(t *testing.T) {
 
 func TestGitCloneExecutor(t *testing.T) {
 	for name, tt := range map[string]struct {
-		Err, URL, Ref string
+		Err      error
+		URL, Ref string
 	}{
 		"tag": {
-			Err: "",
+			Err: nil,
 			URL: "https://github.com/actions/checkout",
 			Ref: "v2",
 		},
 		"branch": {
-			Err: "",
+			Err: nil,
 			URL: "https://github.com/anchore/scan-action",
 			Ref: "act-fails",
 		},
 		"sha": {
-			Err: "",
+			Err: nil,
 			URL: "https://github.com/actions/checkout",
 			Ref: "5a4ac9002d0be2fb38bd78e4b4dbde5606d7042f", // v2
 		},
 		"short-sha": {
-			Err: "short SHA references are not supported: 5a4ac9002d0be2fb38bd78e4b4dbde5606d7042f",
+			Err: &Error{ErrShortRef, "5a4ac9002d0be2fb38bd78e4b4dbde5606d7042f"},
 			URL: "https://github.com/actions/checkout",
 			Ref: "5a4ac90", // v2
 		},
@@ -204,10 +205,11 @@ func TestGitCloneExecutor(t *testing.T) {
 			})
 
 			err := clone(context.Background())
-			if tt.Err == "" {
-				assert.Empty(t, err)
+			if tt.Err != nil {
+				assert.Error(t, err)
+				assert.Equal(t, tt.Err, err)
 			} else {
-				assert.EqualError(t, err, tt.Err)
+				assert.Empty(t, err)
 			}
 		})
 	}
diff --git a/pkg/container/docker_pull.go b/pkg/container/docker_pull.go
index e20d686..804f768 100644
--- a/pkg/container/docker_pull.go
+++ b/pkg/container/docker_pull.go
@@ -4,10 +4,10 @@ import (
 	"context"
 	"encoding/base64"
 	"encoding/json"
+	"fmt"
 
 	"github.com/docker/distribution/reference"
 	"github.com/docker/docker/api/types"
-	"github.com/pkg/errors"
 	log "github.com/sirupsen/logrus"
 
 	"github.com/nektos/act/pkg/common"
@@ -37,7 +37,7 @@ func NewDockerPullExecutor(input NewDockerPullExecutorInput) common.Executor {
 			imageExists, err := ImageExistsLocally(ctx, input.Image, input.Platform)
 			log.Debugf("Image exists? %v", imageExists)
 			if err != nil {
-				return errors.WithMessagef(err, "unable to determine if image already exists for image %q (%s)", input.Image, input.Platform)
+				return fmt.Errorf("unable to determine if image already exists for image '%s' (%s): %w", input.Image, input.Platform, err)
 			}
 
 			if !imageExists {
diff --git a/pkg/container/docker_run.go b/pkg/container/docker_run.go
index 26761cf..29ac7ff 100644
--- a/pkg/container/docker_run.go
+++ b/pkg/container/docker_run.go
@@ -28,7 +28,6 @@ import (
 	specs "github.com/opencontainers/image-spec/specs-go/v1"
 
 	"github.com/Masterminds/semver"
-	"github.com/pkg/errors"
 	log "github.com/sirupsen/logrus"
 	"golang.org/x/term"
 
@@ -236,11 +235,11 @@ func GetDockerClient(ctx context.Context) (cli client.APIClient, err error) {
 		cli, err = client.NewClientWithOpts(client.FromEnv)
 	}
 	if err != nil {
-		return nil, errors.WithStack(err)
+		return nil, fmt.Errorf("failed to connect to docker daemon: %w", err)
 	}
 	cli.NegotiateAPIVersion(ctx)
 
-	return cli, err
+	return cli, nil
 }
 
 func GetHostInfo(ctx context.Context) (info types.Info, err error) {
@@ -276,8 +275,11 @@ func (cr *containerReference) connect() common.Executor {
 func (cr *containerReference) Close() common.Executor {
 	return func(ctx context.Context) error {
 		if cr.cli != nil {
-			cr.cli.Close()
+			err := cr.cli.Close()
 			cr.cli = nil
+			if err != nil {
+				return fmt.Errorf("failed to close client: %w", err)
+			}
 		}
 		return nil
 	}
@@ -292,7 +294,7 @@ func (cr *containerReference) find() common.Executor {
 			All: true,
 		})
 		if err != nil {
-			return errors.WithStack(err)
+			return fmt.Errorf("failed to list containers: %w", err)
 		}
 
 		for _, c := range containers {
@@ -321,7 +323,7 @@ func (cr *containerReference) remove() common.Executor {
 			Force:         true,
 		})
 		if err != nil {
-			logger.Error(errors.WithStack(err))
+			logger.Error(fmt.Errorf("failed to remove container: %w", err))
 		}
 
 		logger.Debugf("Removed container: %v", cr.id)
@@ -369,7 +371,7 @@ func (cr *containerReference) create(capAdd []string, capDrop []string) common.E
 			desiredPlatform := strings.SplitN(cr.input.Platform, `/`, 2)
 
 			if len(desiredPlatform) != 2 {
-				logger.Panicf("Incorrect container platform option. %s is not a valid platform.", cr.input.Platform)
+				return fmt.Errorf("incorrect container platform option '%s'", cr.input.Platform)
 			}
 
 			platSpecs = &specs.Platform{
@@ -387,7 +389,7 @@ func (cr *containerReference) create(capAdd []string, capDrop []string) common.E
 			UsernsMode:  container.UsernsMode(input.UsernsMode),
 		}, nil, platSpecs, input.Name)
 		if err != nil {
-			return errors.WithStack(err)
+			return fmt.Errorf("failed to create container: %w", err)
 		}
 		logger.Debugf("Created container name=%s id=%v from image %v (platform: %s)", input.Name, resp.ID, input.Image, input.Platform)
 		logger.Debugf("ENV ==> %v", input.Env)
@@ -397,7 +399,7 @@ func (cr *containerReference) create(capAdd []string, capDrop []string) common.E
 	}
 }
 
-var singleLineEnvPattern, mulitiLineEnvPattern *regexp.Regexp
+var singleLineEnvPattern, multiLineEnvPattern *regexp.Regexp
 
 func (cr *containerReference) extractEnv(srcPath string, env *map[string]string) common.Executor {
 	if singleLineEnvPattern == nil {
@@ -405,7 +407,7 @@ func (cr *containerReference) extractEnv(srcPath string, env *map[string]string)
 		// SOME_VAR=data=moredata
 		// SOME_VAR=datamoredata
 		singleLineEnvPattern = regexp.MustCompile(`^([^=]*)\=(.*)$`)
-		mulitiLineEnvPattern = regexp.MustCompile(`^([^<]+)<<(\w+)$`)
+		multiLineEnvPattern = regexp.MustCompile(`^([^<]+)<<(\w+)$`)
 	}
 
 	localEnv := *env
@@ -415,10 +417,11 @@ func (cr *containerReference) extractEnv(srcPath string, env *map[string]string)
 			return nil
 		}
 		defer envTar.Close()
+
 		reader := tar.NewReader(envTar)
 		_, err = reader.Next()
 		if err != nil && err != io.EOF {
-			return errors.WithStack(err)
+			return fmt.Errorf("failed to read tar archive: %w", err)
 		}
 		s := bufio.NewScanner(reader)
 		multiLineEnvKey := ""
@@ -439,9 +442,9 @@ func (cr *containerReference) extractEnv(srcPath string, env *map[string]string)
 				}
 				multiLineEnvContent += line
 			}
-			if mulitiLineEnvStart := mulitiLineEnvPattern.FindStringSubmatch(line); mulitiLineEnvStart != nil {
-				multiLineEnvKey = mulitiLineEnvStart[1]
-				multiLineEnvDelimiter = mulitiLineEnvStart[2]
+			if multiLineEnvStart := multiLineEnvPattern.FindStringSubmatch(line); multiLineEnvStart != nil {
+				multiLineEnvKey = multiLineEnvStart[1]
+				multiLineEnvDelimiter = multiLineEnvStart[2]
 			}
 		}
 		env = &localEnv
@@ -486,14 +489,14 @@ func (cr *containerReference) extractPath(env *map[string]string) common.Executo
 	return func(ctx context.Context) error {
 		pathTar, _, err := cr.cli.CopyFromContainer(ctx, cr.id, localEnv["GITHUB_PATH"])
 		if err != nil {
-			return errors.WithStack(err)
+			return fmt.Errorf("failed to copy from container: %w", err)
 		}
 		defer pathTar.Close()
 
 		reader := tar.NewReader(pathTar)
 		_, err = reader.Next()
 		if err != nil && err != io.EOF {
-			return errors.WithStack(err)
+			return fmt.Errorf("failed to read tar archive: %w", err)
 		}
 		s := bufio.NewScanner(reader)
 		for s.Scan() {
@@ -547,14 +550,14 @@ func (cr *containerReference) exec(cmd []string, env map[string]string, user, wo
 			AttachStdout: true,
 		})
 		if err != nil {
-			return errors.WithStack(err)
+			return fmt.Errorf("failed to create exec: %w", err)
 		}
 
 		resp, err := cr.cli.ContainerExecAttach(ctx, idResp.ID, types.ExecStartCheck{
 			Tty: isTerminal,
 		})
 		if err != nil {
-			return errors.WithStack(err)
+			return fmt.Errorf("failed to attach to exec: %w", err)
 		}
 		defer resp.Close()
 
@@ -565,14 +568,17 @@ func (cr *containerReference) exec(cmd []string, env map[string]string, user, wo
 
 		inspectResp, err := cr.cli.ContainerExecInspect(ctx, idResp.ID)
 		if err != nil {
-			return errors.WithStack(err)
+			return fmt.Errorf("failed to inspect exec: %w", err)
 		}
 
-		if inspectResp.ExitCode == 0 {
+		switch inspectResp.ExitCode {
+		case 0:
 			return nil
+		case 127:
+			return fmt.Errorf("exitcode '%d': command not found, please refer to https://github.com/nektos/act/issues/107 for more information", inspectResp.ExitCode)
+		default:
+			return fmt.Errorf("exitcode '%d': failure", inspectResp.ExitCode)
 		}
-
-		return fmt.Errorf("exit with `FAILURE`: %v", inspectResp.ExitCode)
 	}
 }
 
@@ -679,11 +685,11 @@ func (cr *containerReference) copyDir(dstPath string, srcPath string, useGitIgno
 		logger.Debugf("Extracting content from '%s' to '%s'", tarFile.Name(), dstPath)
 		_, err = tarFile.Seek(0, 0)
 		if err != nil {
-			return errors.WithStack(err)
+			return fmt.Errorf("failed to seek tar archive: %w", err)
 		}
 		err = cr.cli.CopyToContainer(ctx, cr.id, dstPath, tarFile, types.CopyToContainerOptions{})
 		if err != nil {
-			return errors.WithStack(err)
+			return fmt.Errorf("failed to copy content to container: %w", err)
 		}
 		return nil
 	}
@@ -715,7 +721,7 @@ func (cr *containerReference) copyContent(dstPath string, files ...*FileEntry) c
 		logger.Debugf("Extracting content to '%s'", dstPath)
 		err := cr.cli.CopyToContainer(ctx, cr.id, dstPath, &buf, types.CopyToContainerOptions{})
 		if err != nil {
-			return errors.WithStack(err)
+			return fmt.Errorf("failed to copy content to container: %w", err)
 		}
 		return nil
 	}
@@ -729,7 +735,7 @@ func (cr *containerReference) attach() common.Executor {
 			Stderr: true,
 		})
 		if err != nil {
-			return errors.WithStack(err)
+			return fmt.Errorf("failed to attach to container: %w", err)
 		}
 		isTerminal := term.IsTerminal(int(os.Stdout.Fd()))
 
@@ -762,7 +768,7 @@ func (cr *containerReference) start() common.Executor {
 		logger.Debugf("Starting container: %v", cr.id)
 
 		if err := cr.cli.ContainerStart(ctx, cr.id, types.ContainerStartOptions{}); err != nil {
-			return errors.WithStack(err)
+			return fmt.Errorf("failed to start container: %w", err)
 		}
 
 		logger.Debugf("Started container: %v", cr.id)
@@ -778,7 +784,7 @@ func (cr *containerReference) wait() common.Executor {
 		select {
 		case err := <-errCh:
 			if err != nil {
-				return errors.WithStack(err)
+				return fmt.Errorf("failed to wait for container: %w", err)
 			}
 		case status := <-statusCh:
 			statusCode = status.StatusCode
diff --git a/pkg/container/file_collector.go b/pkg/container/file_collector.go
index 1e5a8af..84821aa 100644
--- a/pkg/container/file_collector.go
+++ b/pkg/container/file_collector.go
@@ -15,7 +15,6 @@ import (
 	"github.com/go-git/go-git/v5/plumbing/filemode"
 	"github.com/go-git/go-git/v5/plumbing/format/gitignore"
 	"github.com/go-git/go-git/v5/plumbing/format/index"
-	"github.com/pkg/errors"
 )
 
 type fileCollectorHandler interface {
@@ -151,7 +150,7 @@ func (fc *fileCollector) collectFiles(ctx context.Context, submodulePath []strin
 		if fi.Mode()&os.ModeSymlink == os.ModeSymlink {
 			linkName, err := fc.Fs.Readlink(file)
 			if err != nil {
-				return errors.WithMessagef(err, "unable to readlink %s", file)
+				return fmt.Errorf("unable to readlink '%s': %w", file, err)
 			}
 			return fc.Handler.WriteFile(path, fi, linkName, nil)
 		} else if !fi.Mode().IsRegular() {
diff --git a/pkg/model/github_context.go b/pkg/model/github_context.go
index 26e61ae..de8b0f8 100644
--- a/pkg/model/github_context.go
+++ b/pkg/model/github_context.go
@@ -3,7 +3,8 @@ package model
 import (
 	"fmt"
 
-	"github.com/nektos/act/pkg/common"
+	"github.com/nektos/act/pkg/common/git"
+
 	log "github.com/sirupsen/logrus"
 )
 
@@ -85,8 +86,8 @@ func withDefaultBranch(b string, event map[string]interface{}) map[string]interf
 	return event
 }
 
-var findGitRef = common.FindGitRef
-var findGitRevision = common.FindGitRevision
+var findGitRef = git.FindGitRef
+var findGitRevision = git.FindGitRevision
 
 func (ghc *GithubContext) SetRefAndSha(defaultBranch string, repoPath string) {
 	// https://docs.github.com/en/actions/learn-github-actions/events-that-trigger-workflows
diff --git a/pkg/model/planner.go b/pkg/model/planner.go
index 47c4c24..fafaa74 100644
--- a/pkg/model/planner.go
+++ b/pkg/model/planner.go
@@ -10,7 +10,6 @@ import (
 	"regexp"
 	"sort"
 
-	"github.com/pkg/errors"
 	log "github.com/sirupsen/logrus"
 )
 
@@ -130,16 +129,16 @@ func NewWorkflowPlanner(path string, noWorkflowRecurse bool) (WorkflowPlanner, e
 			log.Debugf("Reading workflow '%s'", f.Name())
 			workflow, err := ReadWorkflow(f)
 			if err != nil {
-				f.Close()
+				_ = f.Close()
 				if err == io.EOF {
-					return nil, errors.WithMessagef(err, "unable to read workflow, %s file is empty", wf.workflowFileInfo.Name())
+					return nil, fmt.Errorf("unable to read workflow '%s': file is empty: %w", wf.workflowFileInfo.Name(), err)
 				}
 				return nil, err
 			}
 			_, err = f.Seek(0, 0)
 			if err != nil {
-				f.Close()
-				return nil, errors.WithMessagef(err, "error occurring when resetting io pointer, %s", wf.workflowFileInfo.Name())
+				_ = f.Close()
+				return nil, fmt.Errorf("error occurring when resetting io pointer in '%s': %w", wf.workflowFileInfo.Name(), err)
 			}
 
 			workflow.File = wf.workflowFileInfo.Name()
@@ -155,7 +154,7 @@ func NewWorkflowPlanner(path string, noWorkflowRecurse bool) (WorkflowPlanner, e
 			}
 
 			wp.workflows = append(wp.workflows, workflow)
-			f.Close()
+			_ = f.Close()
 		}
 	}
 
diff --git a/pkg/model/planner_test.go b/pkg/model/planner_test.go
index 8ba5ca7..551b7b3 100644
--- a/pkg/model/planner_test.go
+++ b/pkg/model/planner_test.go
@@ -22,8 +22,8 @@ func TestPlanner(t *testing.T) {
 		{"invalid-job-name/invalid-2.yml", "workflow is not valid. 'invalid-job-name-2': Job name '1234invalid-JOB-Name-v123-docker_hub' is invalid. Names must start with a letter or '_' and contain only alphanumeric characters, '-', or '_'", false},
 		{"invalid-job-name/valid-1.yml", "", false},
 		{"invalid-job-name/valid-2.yml", "", false},
-		{"empty-workflow", "unable to read workflow, push.yml file is empty: EOF", false},
-		{"nested", "unable to read workflow, fail.yml file is empty: EOF", false},
+		{"empty-workflow", "unable to read workflow 'push.yml': file is empty: EOF", false},
+		{"nested", "unable to read workflow 'fail.yml': file is empty: EOF", false},
 		{"nested", "", true},
 	}
 
diff --git a/pkg/runner/run_context.go b/pkg/runner/run_context.go
index 3e1ee2c..66cfa7b 100644
--- a/pkg/runner/run_context.go
+++ b/pkg/runner/run_context.go
@@ -11,14 +11,13 @@ import (
 	"strings"
 
 	"github.com/kballard/go-shellquote"
+	"github.com/mitchellh/go-homedir"
+	"github.com/opencontainers/selinux/go-selinux"
+	log "github.com/sirupsen/logrus"
 	"github.com/spf13/pflag"
 
-	"github.com/mitchellh/go-homedir"
-	log "github.com/sirupsen/logrus"
-
-	selinux "github.com/opencontainers/selinux/go-selinux"
-
 	"github.com/nektos/act/pkg/common"
+	"github.com/nektos/act/pkg/common/git"
 	"github.com/nektos/act/pkg/container"
 	"github.com/nektos/act/pkg/exprparser"
 	"github.com/nektos/act/pkg/model"
@@ -476,7 +475,7 @@ func (rc *RunContext) getGithubContext() *model.GithubContext {
 	}
 
 	repoPath := rc.Config.Workdir
-	repo, err := common.FindGithubRepo(repoPath, rc.Config.GitHubInstance, rc.Config.RemoteName)
+	repo, err := git.FindGithubRepo(repoPath, rc.Config.GitHubInstance, rc.Config.RemoteName)
 	if err != nil {
 		log.Warningf("unable to get git repo: %v", err)
 	} else {
diff --git a/pkg/runner/step_action_remote.go b/pkg/runner/step_action_remote.go
index 7f55c68..cf1ddea 100644
--- a/pkg/runner/step_action_remote.go
+++ b/pkg/runner/step_action_remote.go
@@ -2,6 +2,7 @@ package runner
 
 import (
 	"context"
+	"errors"
 	"fmt"
 	"io"
 	"io/ioutil"
@@ -12,8 +13,10 @@ import (
 	"strings"
 
 	"github.com/nektos/act/pkg/common"
+	"github.com/nektos/act/pkg/common/git"
 	"github.com/nektos/act/pkg/model"
-	"github.com/pkg/errors"
+
+	gogit "github.com/go-git/go-git/v5"
 )
 
 type stepActionRemote struct {
@@ -29,7 +32,7 @@ type stepActionRemote struct {
 }
 
 var (
-	stepActionRemoteNewCloneExecutor = common.NewGitCloneExecutor
+	stepActionRemoteNewCloneExecutor = git.NewGitCloneExecutor
 )
 
 func (sar *stepActionRemote) prepareActionExecutor() common.Executor {
@@ -54,7 +57,7 @@ func (sar *stepActionRemote) prepareActionExecutor() common.Executor {
 			}
 
 			actionDir := fmt.Sprintf("%s/%s", sar.RunContext.ActionCacheDir(), strings.ReplaceAll(sar.Step.Uses, "/", "-"))
-			gitClone := stepActionRemoteNewCloneExecutor(common.NewGitCloneExecutorInput{
+			gitClone := stepActionRemoteNewCloneExecutor(git.NewGitCloneExecutorInput{
 				URL:   sar.remoteAction.CloneURL(),
 				Ref:   sar.remoteAction.Ref,
 				Dir:   actionDir,
@@ -62,13 +65,13 @@ func (sar *stepActionRemote) prepareActionExecutor() common.Executor {
 			})
 			var ntErr common.Executor
 			if err := gitClone(ctx); err != nil {
-				if err.Error() == "short SHA references are not supported" {
-					err = errors.Cause(err)
-					return fmt.Errorf("Unable to resolve action `%s`, the provided ref `%s` is the shortened version of a commit SHA, which is not supported. Please use the full commit SHA `%s` instead", sar.Step.Uses, sar.remoteAction.Ref, err.Error())
-				} else if err.Error() != "some refs were not updated" {
-					return err
-				} else {
+				if errors.Is(err, git.ErrShortRef) {
+					return fmt.Errorf("Unable to resolve action `%s`, the provided ref `%s` is the shortened version of a commit SHA, which is not supported. Please use the full commit SHA `%s` instead",
+						sar.Step.Uses, sar.remoteAction.Ref, err.(*git.Error).Commit())
+				} else if errors.Is(err, gogit.ErrForceNeeded) { // TODO: figure out if it will be easy to shadow/alias go-git err's
 					ntErr = common.NewInfoExecutor("Non-terminating error while running 'git clone': %v", err)
+				} else {
+					return err
 				}
 			}
 
diff --git a/pkg/runner/step_action_remote_test.go b/pkg/runner/step_action_remote_test.go
index 209bee5..4292801 100644
--- a/pkg/runner/step_action_remote_test.go
+++ b/pkg/runner/step_action_remote_test.go
@@ -7,7 +7,9 @@ import (
 	"testing"
 
 	"github.com/nektos/act/pkg/common"
+	"github.com/nektos/act/pkg/common/git"
 	"github.com/nektos/act/pkg/model"
+
 	"github.com/stretchr/testify/assert"
 	"github.com/stretchr/testify/mock"
 	"gopkg.in/yaml.v3"
@@ -123,7 +125,7 @@ func TestStepActionRemote(t *testing.T) {
 			clonedAction := false
 
 			origStepAtionRemoteNewCloneExecutor := stepActionRemoteNewCloneExecutor
-			stepActionRemoteNewCloneExecutor = func(input common.NewGitCloneExecutorInput) common.Executor {
+			stepActionRemoteNewCloneExecutor = func(input git.NewGitCloneExecutorInput) common.Executor {
 				return func(ctx context.Context) error {
 					clonedAction = true
 					return nil
@@ -208,7 +210,7 @@ func TestStepActionRemotePre(t *testing.T) {
 			sarm := &stepActionRemoteMocks{}
 
 			origStepAtionRemoteNewCloneExecutor := stepActionRemoteNewCloneExecutor
-			stepActionRemoteNewCloneExecutor = func(input common.NewGitCloneExecutorInput) common.Executor {
+			stepActionRemoteNewCloneExecutor = func(input git.NewGitCloneExecutorInput) common.Executor {
 				return func(ctx context.Context) error {
 					clonedAction = true
 					return nil