Repository: nektos/act (language: go)

Issue:
Allow GITHUB_REPOSITORY to be overridden
### Act version

0.2.33

### Feature description

When developing a github action, it would be cool to be able to write end to end tests with act. We would need the ability to override GITHUB_REPOSITORY (and SHA) to emulate a user using our action in their repo.

Currently GITHUB_REPOSITORY is automatically set to the action we are developing the action in.


Discussion / hints:
You can do with an event json file
I'm having a hard time finding any simple examples of this, do you know of any?
The payloads (link on each event) does contain examples per event:
https://docs.github.com/en/actions/using-workflows/events-that-trigger-workflows

After some more thoughts I'm not sure if all props could be set by the event file.
I'll reopen the request. Maybe someone is willing to implement it
Okay, I tried a few things with the events and inputs and was unable to achieve setting the GITHUB_REPOSITORY
You can create an empty git repository with a different origin, everything else needs code changes of act.
```
git init
git remote add origin https://github.com/owner/repo
```

_I added in a similar tool like this one the following cli flags `--repository`, `--sha`, `--ref` to override the guessed values_

To set a random github.sha, just commit a file.
What tool did you add @ChristopherHX ?
<details><summary>As I have mentioned you can change GITHUB_REPOSITORY via a workaround in act.</summary>

> What tool did you add @ChristopherHX ?

You find a comment about a tool based on actions/runner in https://github.com/cli/cli/issues/3585
</details>
Is it possible to have an option to override any of the `GITHUB_` env variables? We create local repositories to run the e2e tests for our custom actions. So, it would be helpful to control those variables in that environment. 
@KnisterPeter @ChristopherHX are y'all open to adding this feature? I am willing to create a PR for it
Yes, from my point of view it's fine.
Yes, such a feature would get my approval.

Fix patch:
diff --git a/pkg/model/github_context.go b/pkg/model/github_context.go
index 86172dfa05f..9ce8d08a490 100644
--- a/pkg/model/github_context.go
+++ b/pkg/model/github_context.go
@@ -3,6 +3,7 @@ package model
 import (
 	"context"
 	"fmt"
+	"strings"
 
 	"github.com/nektos/act/pkg/common"
 	"github.com/nektos/act/pkg/common/git"
@@ -89,26 +90,22 @@ func withDefaultBranch(ctx context.Context, b string, event map[string]interface
 var findGitRef = git.FindGitRef
 var findGitRevision = git.FindGitRevision
 
-func (ghc *GithubContext) SetRefAndSha(ctx context.Context, defaultBranch string, repoPath string) {
+func (ghc *GithubContext) SetRef(ctx context.Context, defaultBranch string, repoPath string) {
 	logger := common.Logger(ctx)
+
 	// https://docs.github.com/en/actions/learn-github-actions/events-that-trigger-workflows
 	// https://docs.github.com/en/developers/webhooks-and-events/webhooks/webhook-events-and-payloads
 	switch ghc.EventName {
 	case "pull_request_target":
 		ghc.Ref = fmt.Sprintf("refs/heads/%s", ghc.BaseRef)
-		ghc.Sha = asString(nestedMapLookup(ghc.Event, "pull_request", "base", "sha"))
 	case "pull_request", "pull_request_review", "pull_request_review_comment":
 		ghc.Ref = fmt.Sprintf("refs/pull/%.0f/merge", ghc.Event["number"])
 	case "deployment", "deployment_status":
 		ghc.Ref = asString(nestedMapLookup(ghc.Event, "deployment", "ref"))
-		ghc.Sha = asString(nestedMapLookup(ghc.Event, "deployment", "sha"))
 	case "release":
 		ghc.Ref = asString(nestedMapLookup(ghc.Event, "release", "tag_name"))
 	case "push", "create", "workflow_dispatch":
 		ghc.Ref = asString(ghc.Event["ref"])
-		if deleted, ok := ghc.Event["deleted"].(bool); ok && !deleted {
-			ghc.Sha = asString(ghc.Event["after"])
-		}
 	default:
 		defaultBranch := asString(nestedMapLookup(ghc.Event, "repository", "default_branch"))
 		if defaultBranch != "" {
@@ -136,6 +133,23 @@ func (ghc *GithubContext) SetRefAndSha(ctx context.Context, defaultBranch string
 			ghc.Ref = fmt.Sprintf("refs/heads/%s", asString(nestedMapLookup(ghc.Event, "repository", "default_branch")))
 		}
 	}
+}
+
+func (ghc *GithubContext) SetSha(ctx context.Context, repoPath string) {
+	logger := common.Logger(ctx)
+
+	// https://docs.github.com/en/actions/learn-github-actions/events-that-trigger-workflows
+	// https://docs.github.com/en/developers/webhooks-and-events/webhooks/webhook-events-and-payloads
+	switch ghc.EventName {
+	case "pull_request_target":
+		ghc.Sha = asString(nestedMapLookup(ghc.Event, "pull_request", "base", "sha"))
+	case "deployment", "deployment_status":
+		ghc.Sha = asString(nestedMapLookup(ghc.Event, "deployment", "sha"))
+	case "push", "create", "workflow_dispatch":
+		if deleted, ok := ghc.Event["deleted"].(bool); ok && !deleted {
+			ghc.Sha = asString(ghc.Event["after"])
+		}
+	}
 
 	if ghc.Sha == "" {
 		_, sha, err := findGitRevision(ctx, repoPath)
@@ -146,3 +160,48 @@ func (ghc *GithubContext) SetRefAndSha(ctx context.Context, defaultBranch string
 		}
 	}
 }
+
+func (ghc *GithubContext) SetRepositoryAndOwner(ctx context.Context, githubInstance string, remoteName string, repoPath string) {
+	if ghc.Repository == "" {
+		repo, err := git.FindGithubRepo(ctx, repoPath, githubInstance, remoteName)
+		if err != nil {
+			common.Logger(ctx).Warningf("unable to get git repo: %v", err)
+			return
+		}
+		ghc.Repository = repo
+	}
+	ghc.RepositoryOwner = strings.Split(ghc.Repository, "/")[0]
+}
+
+func (ghc *GithubContext) SetRefTypeAndName() {
+	var refType, refName string
+
+	// https://docs.github.com/en/actions/learn-github-actions/environment-variables
+	if strings.HasPrefix(ghc.Ref, "refs/tags/") {
+		refType = "tag"
+		refName = ghc.Ref[len("refs/tags/"):]
+	} else if strings.HasPrefix(ghc.Ref, "refs/heads/") {
+		refType = "branch"
+		refName = ghc.Ref[len("refs/heads/"):]
+	}
+
+	if ghc.RefType == "" {
+		ghc.RefType = refType
+	}
+
+	if ghc.RefName == "" {
+		ghc.RefName = refName
+	}
+}
+
+func (ghc *GithubContext) SetBaseAndHeadRef() {
+	if ghc.EventName == "pull_request" || ghc.EventName == "pull_request_target" {
+		if ghc.BaseRef == "" {
+			ghc.BaseRef = asString(nestedMapLookup(ghc.Event, "pull_request", "base", "ref"))
+		}
+
+		if ghc.HeadRef == "" {
+			ghc.HeadRef = asString(nestedMapLookup(ghc.Event, "pull_request", "head", "ref"))
+		}
+	}
+}
diff --git a/pkg/runner/run_context.go b/pkg/runner/run_context.go
index fa2da05d1ce..db283287f61 100644
--- a/pkg/runner/run_context.go
+++ b/pkg/runner/run_context.go
@@ -21,7 +21,6 @@ import (
 	log "github.com/sirupsen/logrus"
 
 	"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"
@@ -571,6 +570,14 @@ func (rc *RunContext) getGithubContext(ctx context.Context) *model.GithubContext
 		RetentionDays:    rc.Config.Env["GITHUB_RETENTION_DAYS"],
 		RunnerPerflog:    rc.Config.Env["RUNNER_PERFLOG"],
 		RunnerTrackingID: rc.Config.Env["RUNNER_TRACKING_ID"],
+		Repository:       rc.Config.Env["GITHUB_REPOSITORY"],
+		Ref:              rc.Config.Env["GITHUB_REF"],
+		Sha:              rc.Config.Env["SHA_REF"],
+		RefName:          rc.Config.Env["GITHUB_REF_NAME"],
+		RefType:          rc.Config.Env["GITHUB_REF_TYPE"],
+		BaseRef:          rc.Config.Env["GITHUB_BASE_REF"],
+		HeadRef:          rc.Config.Env["GITHUB_HEAD_REF"],
+		Workspace:        rc.Config.Env["GITHUB_WORKSPACE"],
 	}
 	if rc.JobContainer != nil {
 		ghc.EventPath = rc.JobContainer.GetActPath() + "/workflow/event.json"
@@ -599,40 +606,25 @@ func (rc *RunContext) getGithubContext(ctx context.Context) *model.GithubContext
 		ghc.Actor = "nektos/act"
 	}
 
-	repoPath := rc.Config.Workdir
-	repo, err := git.FindGithubRepo(ctx, repoPath, rc.Config.GitHubInstance, rc.Config.RemoteName)
-	if err != nil {
-		logger.Warningf("unable to get git repo: %v", err)
-	} else {
-		ghc.Repository = repo
-		if ghc.RepositoryOwner == "" {
-			ghc.RepositoryOwner = strings.Split(repo, "/")[0]
-		}
-	}
-
 	if rc.EventJSON != "" {
-		err = json.Unmarshal([]byte(rc.EventJSON), &ghc.Event)
+		err := json.Unmarshal([]byte(rc.EventJSON), &ghc.Event)
 		if err != nil {
 			logger.Errorf("Unable to Unmarshal event '%s': %v", rc.EventJSON, err)
 		}
 	}
 
-	if ghc.EventName == "pull_request" || ghc.EventName == "pull_request_target" {
-		ghc.BaseRef = asString(nestedMapLookup(ghc.Event, "pull_request", "base", "ref"))
-		ghc.HeadRef = asString(nestedMapLookup(ghc.Event, "pull_request", "head", "ref"))
+	ghc.SetBaseAndHeadRef()
+	repoPath := rc.Config.Workdir
+	ghc.SetRepositoryAndOwner(ctx, rc.Config.GitHubInstance, rc.Config.RemoteName, repoPath)
+	if ghc.Ref == "" {
+		ghc.SetRef(ctx, rc.Config.DefaultBranch, repoPath)
 	}
-
-	ghc.SetRefAndSha(ctx, rc.Config.DefaultBranch, repoPath)
-
-	// https://docs.github.com/en/actions/learn-github-actions/environment-variables
-	if strings.HasPrefix(ghc.Ref, "refs/tags/") {
-		ghc.RefType = "tag"
-		ghc.RefName = ghc.Ref[len("refs/tags/"):]
-	} else if strings.HasPrefix(ghc.Ref, "refs/heads/") {
-		ghc.RefType = "branch"
-		ghc.RefName = ghc.Ref[len("refs/heads/"):]
+	if ghc.Sha == "" {
+		ghc.SetSha(ctx, repoPath)
 	}
 
+	ghc.SetRefTypeAndName()
+
 	return ghc
 }
 
@@ -662,15 +654,6 @@ func isLocalCheckout(ghc *model.GithubContext, step *model.Step) bool {
 	return true
 }
 
-func asString(v interface{}) string {
-	if v == nil {
-		return ""
-	} else if s, ok := v.(string); ok {
-		return s
-	}
-	return ""
-}
-
 func nestedMapLookup(m map[string]interface{}, ks ...string) (rval interface{}) {
 	var ok bool
 
@@ -709,20 +692,34 @@ func (rc *RunContext) withGithubEnv(ctx context.Context, github *model.GithubCon
 	env["GITHUB_REF_NAME"] = github.RefName
 	env["GITHUB_REF_TYPE"] = github.RefType
 	env["GITHUB_TOKEN"] = github.Token
-	env["GITHUB_SERVER_URL"] = "https://github.com"
-	env["GITHUB_API_URL"] = "https://api.github.com"
-	env["GITHUB_GRAPHQL_URL"] = "https://api.github.com/graphql"
-	env["GITHUB_BASE_REF"] = github.BaseRef
-	env["GITHUB_HEAD_REF"] = github.HeadRef
 	env["GITHUB_JOB"] = rc.JobName
 	env["GITHUB_REPOSITORY_OWNER"] = github.RepositoryOwner
 	env["GITHUB_RETENTION_DAYS"] = github.RetentionDays
 	env["RUNNER_PERFLOG"] = github.RunnerPerflog
 	env["RUNNER_TRACKING_ID"] = github.RunnerTrackingID
+	env["GITHUB_BASE_REF"] = github.BaseRef
+	env["GITHUB_HEAD_REF"] = github.HeadRef
+
+	defaultServerURL := "https://github.com"
+	defaultAPIURL := "https://api.github.com"
+	defaultGraphqlURL := "https://api.github.com/graphql"
+
 	if rc.Config.GitHubInstance != "github.com" {
-		env["GITHUB_SERVER_URL"] = fmt.Sprintf("https://%s", rc.Config.GitHubInstance)
-		env["GITHUB_API_URL"] = fmt.Sprintf("https://%s/api/v3", rc.Config.GitHubInstance)
-		env["GITHUB_GRAPHQL_URL"] = fmt.Sprintf("https://%s/api/graphql", rc.Config.GitHubInstance)
+		defaultServerURL = fmt.Sprintf("https://%s", rc.Config.GitHubInstance)
+		defaultAPIURL = fmt.Sprintf("https://%s/api/v3", rc.Config.GitHubInstance)
+		defaultGraphqlURL = fmt.Sprintf("https://%s/api/graphql", rc.Config.GitHubInstance)
+	}
+
+	if env["GITHUB_SERVER_URL"] == "" {
+		env["GITHUB_SERVER_URL"] = defaultServerURL
+	}
+
+	if env["GITHUB_API_URL"] == "" {
+		env["GITHUB_API_URL"] = defaultAPIURL
+	}
+
+	if env["GITHUB_GRAPHQL_URL"] == "" {
+		env["GITHUB_GRAPHQL_URL"] = defaultGraphqlURL
 	}
 
 	if rc.Config.ArtifactServerPath != "" {


Test patch:
diff --git a/pkg/model/github_context_test.go b/pkg/model/github_context_test.go
index a29009445dd..29bb546a98f 100644
--- a/pkg/model/github_context_test.go
+++ b/pkg/model/github_context_test.go
@@ -9,7 +9,7 @@ import (
 	"github.com/stretchr/testify/assert"
 )
 
-func TestSetRefAndSha(t *testing.T) {
+func TestSetRef(t *testing.T) {
 	log.SetLevel(log.DebugLevel)
 
 	oldFindGitRef := findGitRef
@@ -29,19 +29,11 @@ func TestSetRefAndSha(t *testing.T) {
 		eventName string
 		event     map[string]interface{}
 		ref       string
-		sha       string
 	}{
 		{
 			eventName: "pull_request_target",
-			event: map[string]interface{}{
-				"pull_request": map[string]interface{}{
-					"base": map[string]interface{}{
-						"sha": "pr-base-sha",
-					},
-				},
-			},
-			ref: "refs/heads/master",
-			sha: "pr-base-sha",
+			event:     map[string]interface{}{},
+			ref:       "refs/heads/master",
 		},
 		{
 			eventName: "pull_request",
@@ -49,18 +41,15 @@ func TestSetRefAndSha(t *testing.T) {
 				"number": 1234.,
 			},
 			ref: "refs/pull/1234/merge",
-			sha: "1234fakesha",
 		},
 		{
 			eventName: "deployment",
 			event: map[string]interface{}{
 				"deployment": map[string]interface{}{
 					"ref": "refs/heads/somebranch",
-					"sha": "deployment-sha",
 				},
 			},
 			ref: "refs/heads/somebranch",
-			sha: "deployment-sha",
 		},
 		{
 			eventName: "release",
@@ -70,17 +59,13 @@ func TestSetRefAndSha(t *testing.T) {
 				},
 			},
 			ref: "v1.0.0",
-			sha: "1234fakesha",
 		},
 		{
 			eventName: "push",
 			event: map[string]interface{}{
-				"ref":     "refs/heads/somebranch",
-				"after":   "push-sha",
-				"deleted": false,
+				"ref": "refs/heads/somebranch",
 			},
 			ref: "refs/heads/somebranch",
-			sha: "push-sha",
 		},
 		{
 			eventName: "unknown",
@@ -90,13 +75,11 @@ func TestSetRefAndSha(t *testing.T) {
 				},
 			},
 			ref: "refs/heads/main",
-			sha: "1234fakesha",
 		},
 		{
 			eventName: "no-event",
 			event:     map[string]interface{}{},
 			ref:       "refs/heads/master",
-			sha:       "1234fakesha",
 		},
 	}
 
@@ -108,10 +91,9 @@ func TestSetRefAndSha(t *testing.T) {
 				Event:     table.event,
 			}
 
-			ghc.SetRefAndSha(context.Background(), "main", "/some/dir")
+			ghc.SetRef(context.Background(), "main", "/some/dir")
 
 			assert.Equal(t, table.ref, ghc.Ref)
-			assert.Equal(t, table.sha, ghc.Sha)
 		})
 	}
 
@@ -125,9 +107,96 @@ func TestSetRefAndSha(t *testing.T) {
 			Event:     map[string]interface{}{},
 		}
 
-		ghc.SetRefAndSha(context.Background(), "", "/some/dir")
+		ghc.SetRef(context.Background(), "", "/some/dir")
 
 		assert.Equal(t, "refs/heads/master", ghc.Ref)
-		assert.Equal(t, "1234fakesha", ghc.Sha)
 	})
 }
+
+func TestSetSha(t *testing.T) {
+	log.SetLevel(log.DebugLevel)
+
+	oldFindGitRef := findGitRef
+	oldFindGitRevision := findGitRevision
+	defer func() { findGitRef = oldFindGitRef }()
+	defer func() { findGitRevision = oldFindGitRevision }()
+
+	findGitRef = func(ctx context.Context, file string) (string, error) {
+		return "refs/heads/master", nil
+	}
+
+	findGitRevision = func(ctx context.Context, file string) (string, string, error) {
+		return "", "1234fakesha", nil
+	}
+
+	tables := []struct {
+		eventName string
+		event     map[string]interface{}
+		sha       string
+	}{
+		{
+			eventName: "pull_request_target",
+			event: map[string]interface{}{
+				"pull_request": map[string]interface{}{
+					"base": map[string]interface{}{
+						"sha": "pr-base-sha",
+					},
+				},
+			},
+			sha: "pr-base-sha",
+		},
+		{
+			eventName: "pull_request",
+			event: map[string]interface{}{
+				"number": 1234.,
+			},
+			sha: "1234fakesha",
+		},
+		{
+			eventName: "deployment",
+			event: map[string]interface{}{
+				"deployment": map[string]interface{}{
+					"sha": "deployment-sha",
+				},
+			},
+			sha: "deployment-sha",
+		},
+		{
+			eventName: "release",
+			event:     map[string]interface{}{},
+			sha:       "1234fakesha",
+		},
+		{
+			eventName: "push",
+			event: map[string]interface{}{
+				"after":   "push-sha",
+				"deleted": false,
+			},
+			sha: "push-sha",
+		},
+		{
+			eventName: "unknown",
+			event:     map[string]interface{}{},
+			sha:       "1234fakesha",
+		},
+		{
+			eventName: "no-event",
+			event:     map[string]interface{}{},
+			sha:       "1234fakesha",
+		},
+	}
+
+	for _, table := range tables {
+		t.Run(table.eventName, func(t *testing.T) {
+			ghc := &GithubContext{
+				EventName: table.eventName,
+				BaseRef:   "master",
+				Event:     table.event,
+			}
+
+			ghc.SetSha(context.Background(), "/some/dir")
+
+			assert.Equal(t, table.sha, ghc.Sha)
+		})
+	}
+}