From c158ac54720fcd500cd5413cdefb69695d5a2e5f Mon Sep 17 00:00:00 2001 From: silverwind Date: Sat, 5 Sep 2026 08:52:10 +0000 Subject: [PATCH] perf: speed up action downloads (#1209) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit An action pinned to a full commit hash cloned the whole repository, and a cached one hit the network on every run. Now only the pinned commit is fetched at depth 1, a cached commit is reused with no network at all, and the action's `.git` directory no longer ships into the job container, matching GitHub. ``` repo cold ms warm ms cache KiB actions/checkout 5807 → 787 1064 → 12 11492 → 2349 actions/setup-node 15257 → 1013 1365 → 21 64303 → 9924 actions/cache 18782 → 760 1589 → 16 60890 → 12507 actions/setup-go 5513 → 764 589 → 20 16600 → 9104 docker/login-action 25809 → 1225 1288 → 10 81709 → 12503 ``` Reviewed-on: https://gitea.com/gitea/runner/pulls/1209 Reviewed-by: bircni Co-authored-by: silverwind --- act/common/git/git.go | 127 ++++++++++++++++++------- act/common/git/git_test.go | 116 ++++++++++++++++++++-- act/container/container_types.go | 2 +- act/container/docker_run.go | 13 +-- act/container/docker_run_test.go | 2 +- act/container/host_environment.go | 9 +- act/container/host_environment_test.go | 24 ++++- act/filecollector/file_collector.go | 15 ++- act/runner/action.go | 2 +- act/runner/action_test.go | 4 +- act/runner/container_mock_test.go | 4 +- act/runner/patch_actions_test.go | 2 +- act/runner/step_action_remote.go | 2 +- 13 files changed, 255 insertions(+), 67 deletions(-) diff --git a/act/common/git/git.go b/act/common/git/git.go index 69769f9a..febb5106 100644 --- a/act/common/git/git.go +++ b/act/common/git/git.go @@ -391,7 +391,11 @@ func NewGitCloneExecutor(input NewGitCloneExecutorInput) common.Executor { } } - if !isOfflineMode { + // A just-cloned ref is as current as a fetch would make it, and a commit hash never moves. + _, _, present := refRevision(r, input.Ref) + refresh := !isOfflineMode && (!present || (reused && !plumbing.IsHash(input.Ref))) + + if refresh { err = r.FetchContext(ctx, &fetchOptions) if err != nil && !errors.Is(err, git.NoErrAlreadyUpToDate) { return err @@ -411,23 +415,7 @@ func NewGitCloneExecutor(input NewGitCloneExecutorInput) common.Executor { } } - // At this point we need to know if it's a tag or a branch - // And the easiest way to do it is duck typing - // - // If err is nil, it's a tag so let's proceed with that hash like we would if - // it was a sha - refType := "tag" - rev = plumbing.Revision(path.Join("refs", "tags", input.Ref)) - if _, err := r.Tag(input.Ref); errors.Is(err, git.ErrTagNotFound) { - rName := plumbing.ReferenceName(path.Join("refs", "remotes", "origin", input.Ref)) - if _, err := r.Reference(rName, false); errors.Is(err, plumbing.ErrReferenceNotFound) { - refType = "sha" - rev = plumbing.Revision(input.Ref) - } else { - refType = "branch" - rev = plumbing.Revision(rName) - } - } + rev, refType, _ := refRevision(r, input.Ref) if hash, err = r.ResolveRevision(rev); err != nil { logger.Errorf("Unable to resolve %s: %v", input.Ref, err) @@ -459,7 +447,7 @@ func NewGitCloneExecutor(input NewGitCloneExecutorInput) common.Executor { reusedMsg := "" switch { - case !isOfflineMode && !shallow: + case refresh && !shallow: // In shallow mode the depth-limited fetch above already advanced the ref. if err = w.PullContext(ctx, &pullOptions); err != nil && !errors.Is(err, git.NoErrAlreadyUpToDate) { logger.Debugf("Unable to pull %s: %v", refName, err) @@ -501,27 +489,37 @@ func NewGitCloneExecutor(input NewGitCloneExecutorInput) common.Executor { } } -// cloneAtDepth clones input.URL into input.Dir using opts. -// With input.Depth > 0 it first tries a shallow, single-branch clone of input.Ref, falling back when error. +// cloneAtDepth falls back to a full clone when the shallow attempt fails. func cloneAtDepth(ctx context.Context, input NewGitCloneExecutorInput, opts git.CloneOptions, logger log.FieldLogger) (*git.Repository, error) { if input.Depth > 0 { - for _, refName := range []plumbing.ReferenceName{ - plumbing.NewBranchReferenceName(input.Ref), - plumbing.NewTagReferenceName(input.Ref), - } { - shallowOpts := opts - shallowOpts.Depth = input.Depth - shallowOpts.SingleBranch = true - shallowOpts.ReferenceName = refName - shallowOpts.Tags = git.NoTags - - r, err := git.PlainCloneContext(ctx, input.Dir, false, &shallowOpts) + if plumbing.IsHash(input.Ref) { + r, err := fetchPinnedSHA(ctx, input, opts) if err == nil { return r, nil } - logger.Debugf("Shallow clone of %s as %s failed: %v", input.URL, refName, err) - if rmErr := os.RemoveAll(input.Dir); rmErr != nil { - return nil, fmt.Errorf("remove partial clone %s: %w", input.Dir, rmErr) + logger.Debugf("Shallow fetch of %s at %s failed: %v", input.URL, input.Ref, err) + if err := removePartialClone(input.Dir); err != nil { + return nil, err + } + } else { + for _, refName := range []plumbing.ReferenceName{ + plumbing.NewBranchReferenceName(input.Ref), + plumbing.NewTagReferenceName(input.Ref), + } { + shallowOpts := opts + shallowOpts.Depth = input.Depth + shallowOpts.SingleBranch = true + shallowOpts.ReferenceName = refName + shallowOpts.Tags = git.NoTags + + r, err := git.PlainCloneContext(ctx, input.Dir, false, &shallowOpts) + if err == nil { + return r, nil + } + logger.Debugf("Shallow clone of %s as %s failed: %v", input.URL, refName, err) + if err := removePartialClone(input.Dir); err != nil { + return nil, err + } } } logger.Debugf("Falling back to a full clone of %s for ref %q", input.URL, input.Ref) @@ -530,14 +528,65 @@ func cloneAtDepth(ctx context.Context, input NewGitCloneExecutorInput, opts git. return git.PlainCloneContext(ctx, input.Dir, false, &opts) } +func removePartialClone(dir string) error { + if err := os.RemoveAll(dir); err != nil { + return fmt.Errorf("remove partial clone %s: %w", dir, err) + } + return nil +} + +func fetchPinnedSHA(ctx context.Context, input NewGitCloneExecutorInput, opts git.CloneOptions) (*git.Repository, error) { + r, err := git.PlainInit(input.Dir, false) + if err != nil { + return nil, err + } + if _, err := r.CreateRemote(&config.RemoteConfig{Name: "origin", URLs: []string{input.URL}}); err != nil { + return nil, err + } + if err := r.FetchContext(ctx, &git.FetchOptions{ + RefSpecs: []config.RefSpec{pinnedRefSpec(input.Ref)}, + Depth: input.Depth, + Tags: git.NoTags, + Auth: opts.Auth, + Progress: opts.Progress, + InsecureSkipTLS: opts.InsecureSkipTLS, + }); err != nil { + return nil, err + } + return r, nil +} + +// pinnedRefSpec keeps a hash-shaped name out of refs/heads and refs/tags. +func pinnedRefSpec(sha string) config.RefSpec { + return config.RefSpec(fmt.Sprintf("+%s:refs/pinned/%s", sha, sha)) +} + +// refRevision picks the revision to check out and reports whether it resolves locally. +func refRevision(r *git.Repository, ref string) (plumbing.Revision, string, bool) { + if plumbing.IsHash(ref) { + // git ignores a ref named as 40 hex digits, so a full hash always denotes the commit itself. + _, err := r.CommitObject(plumbing.NewHash(ref)) + return plumbing.Revision(ref), "sha", err == nil + } + if _, err := r.Tag(ref); err == nil { + return plumbing.Revision(path.Join("refs", "tags", ref)), "tag", true + } + remoteRef := plumbing.ReferenceName(path.Join("refs", "remotes", "origin", ref)) + if _, err := r.Reference(remoteRef, false); err == nil { + return plumbing.Revision(remoteRef), "branch", true + } + rev := plumbing.Revision(ref) + _, err := r.ResolveRevision(rev) + return rev, "sha", err == nil +} + // isShallow reports whether the local repository was cloned with a limited depth. func isShallow(r *git.Repository) bool { shallows, err := r.Storer.Shallow() return err == nil && len(shallows) > 0 } -// shallowFetchRefSpec returns the single refspec that updates only input.Ref, keeping a shallow clone from re-downloading every branch's history. -// ok is false when the ref is not present locally as a tag or remote-tracking branch, in which case the broad default refspec is used. +// shallowFetchRefSpec limits a shallow update to the requested ref, falling back to the broad refspec when it is not present locally. func shallowFetchRefSpec(r *git.Repository, ref string) (config.RefSpec, bool) { tagRef := plumbing.NewTagReferenceName(ref) if _, err := r.Reference(tagRef, false); err == nil { @@ -548,5 +597,9 @@ func shallowFetchRefSpec(r *git.Repository, ref string) (config.RefSpec, bool) { branchRef := plumbing.NewBranchReferenceName(ref) return config.RefSpec(fmt.Sprintf("+%s:%s", branchRef, remoteRef)), true } + if plumbing.IsHash(ref) { + // The broad refspec carries only advertised tips, never a pinned commit. + return pinnedRefSpec(ref), true + } return "", false } diff --git a/act/common/git/git_test.go b/act/common/git/git_test.go index 04f6481a..8cfbee5e 100644 --- a/act/common/git/git_test.go +++ b/act/common/git/git_test.go @@ -16,6 +16,7 @@ import ( "strconv" "strings" "sync" + "sync/atomic" "syscall" "testing" "time" @@ -24,6 +25,9 @@ import ( gogit "github.com/go-git/go-git/v5" gogitconfig "github.com/go-git/go-git/v5/config" + "github.com/go-git/go-git/v5/plumbing/transport" + gogitclient "github.com/go-git/go-git/v5/plumbing/transport/client" + gogitfile "github.com/go-git/go-git/v5/plumbing/transport/file" log "github.com/sirupsen/logrus" logrustest "github.com/sirupsen/logrus/hooks/test" "github.com/stretchr/testify/assert" @@ -435,7 +439,7 @@ func TestGitCloneExecutorShallow(t *testing.T) { require.NoError(t, gitCmd("-C", workDir, "commit", "--allow-empty", "-m", m)) } require.NoError(t, gitCmd("-C", workDir, "tag", "v1")) - sha := gitRevParse(t, workDir, "HEAD~1") // c2, a SHA that go-git cannot shallow-clone + unadvertisedSHA := gitRevParse(t, workDir, "HEAD~1") require.NoError(t, gitCmd("-C", workDir, "push", "-u", "origin", "main")) require.NoError(t, gitCmd("-C", workDir, "push", "origin", "v1")) @@ -461,14 +465,43 @@ func TestGitCloneExecutorShallow(t *testing.T) { assert.Equal(t, "c3", gitHeadSubject(t, dir)) }) - t.Run("SHA falls back to a full clone", func(t *testing.T) { + unadvertisedRemote := func(t *testing.T, allowUnadvertised bool) string { + remote := filepath.Join(t.TempDir(), "remote.git") + require.NoError(t, gitCmd("clone", "--bare", remoteDir, remote)) + require.NoError(t, gitCmd("-C", remote, "config", "uploadpack.allowAnySHA1InWant", strconv.FormatBool(allowUnadvertised))) + return remote + } + + t.Run("commit hash falls back to a full clone when the remote refuses unadvertised objects", func(t *testing.T) { dir := t.TempDir() require.NoError(t, NewGitCloneExecutor(NewGitCloneExecutorInput{ - URL: remoteDir, Ref: sha, Dir: dir, Depth: 1, + URL: unadvertisedRemote(t, false), Ref: unadvertisedSHA, Dir: dir, Depth: 1, })(t.Context())) - // go-git cannot shallow-clone a raw SHA, so it falls back to a full clone; the absence of a shallow marker proves the fallback happened. - assert.NoFileExists(t, shallowMarker(dir), "a SHA ref must not produce a shallow clone") - assert.Equal(t, sha, gitRevParse(t, dir, "HEAD")) + assert.NoFileExists(t, shallowMarker(dir)) + assert.Equal(t, unadvertisedSHA, gitRevParse(t, dir, "HEAD")) + }) + + t.Run("commit hash is fetched shallowly, moved to another hash, then reused without reaching the remote", func(t *testing.T) { + remote := unadvertisedRemote(t, true) + dir := t.TempDir() + cloneAt := func(ref string) error { + return NewGitCloneExecutor(NewGitCloneExecutorInput{ + URL: remote, Ref: ref, Dir: dir, Depth: 1, + })(t.Context()) + } + + require.NoError(t, cloneAt(unadvertisedSHA)) + assert.FileExists(t, shallowMarker(dir)) + assert.Equal(t, 1, gitRevCount(t, dir)) + assert.Equal(t, unadvertisedSHA, gitRevParse(t, dir, "HEAD")) + + olderSHA := gitRevParse(t, workDir, "HEAD~2") + require.NoError(t, cloneAt(olderSHA)) + assert.Equal(t, olderSHA, gitRevParse(t, dir, "HEAD")) + + require.NoError(t, os.RemoveAll(remote)) + require.NoError(t, cloneAt(olderSHA)) + assert.Equal(t, olderSHA, gitRevParse(t, dir, "HEAD")) }) t.Run("moving branch updates while staying shallow", func(t *testing.T) { @@ -491,6 +524,77 @@ func TestGitCloneExecutorShallow(t *testing.T) { }) } +func TestGitCloneExecutorColdCloneSkipsRefresh(t *testing.T) { + remoteDir := t.TempDir() + require.NoError(t, gitCmd("init", "--bare", "--initial-branch=main", remoteDir)) + workDir := t.TempDir() + require.NoError(t, gitCmd("clone", remoteDir, workDir)) + require.NoError(t, gitCmd("-C", workDir, "checkout", "-b", "main")) + require.NoError(t, gitCmd("-C", workDir, "commit", "--allow-empty", "-m", "c1")) + require.NoError(t, gitCmd("-C", workDir, "tag", "v1")) + require.NoError(t, gitCmd("-C", workDir, "push", "-u", "origin", "main")) + require.NoError(t, gitCmd("-C", workDir, "push", "origin", "v1")) + + for name, tt := range map[string]struct { + Ref string + Depth int + }{ + "shallow branch": {"main", 1}, + "full clone tag": {"v1", 0}, + } { + t.Run(name, func(t *testing.T) { + counter := installCountingTransport(t) + require.NoError(t, NewGitCloneExecutor(NewGitCloneExecutorInput{ + URL: remoteDir, Ref: tt.Ref, Dir: t.TempDir(), Depth: tt.Depth, + })(t.Context())) + assert.Equal(t, int64(1), counter.sessions.Load()) + }) + } +} + +func TestGitCloneExecutorPinnedHashIgnoresShadowingRef(t *testing.T) { + remoteDir := t.TempDir() + require.NoError(t, gitCmd("init", "--bare", "--initial-branch=main", remoteDir)) + workDir := t.TempDir() + require.NoError(t, gitCmd("clone", remoteDir, workDir)) + require.NoError(t, gitCmd("-C", workDir, "checkout", "-b", "main")) + require.NoError(t, gitCmd("-C", workDir, "commit", "--allow-empty", "-m", "pinned")) + pinned := gitRevParse(t, workDir, "HEAD") + require.NoError(t, gitCmd("-C", workDir, "commit", "--allow-empty", "-m", "decoy")) + require.NoError(t, gitCmd("-C", workDir, "tag", pinned)) + require.NoError(t, gitCmd("-C", workDir, "push", "-u", "origin", "main")) + require.NoError(t, gitCmd("-C", workDir, "push", "origin", pinned)) + + for name, depth := range map[string]int{"shallow": 1, "full clone": 0} { + t.Run(name, func(t *testing.T) { + dir := t.TempDir() + require.NoError(t, NewGitCloneExecutor(NewGitCloneExecutorInput{ + URL: remoteDir, Ref: pinned, Dir: dir, Depth: depth, + })(t.Context())) + assert.Equal(t, pinned, gitRevParse(t, dir, "HEAD")) + }) + } +} + +type countingTransport struct { + transport.Transport + sessions atomic.Int64 +} + +func (c *countingTransport) NewUploadPackSession(ep *transport.Endpoint, auth transport.AuthMethod) (transport.UploadPackSession, error) { + c.sessions.Add(1) + return c.Transport.NewUploadPackSession(ep, auth) +} + +func installCountingTransport(t *testing.T) *countingTransport { + t.Helper() + counter := &countingTransport{Transport: gogitfile.DefaultClient} + gogitclient.InstallProtocol("file", counter) + t.Cleanup(func() { gogitclient.InstallProtocol("file", gogitfile.DefaultClient) }) + + return counter +} + func gitRevParse(t *testing.T, dir, rev string) string { t.Helper() out, err := exec.Command("git", "-C", dir, "rev-parse", rev).Output() diff --git a/act/container/container_types.go b/act/container/container_types.go index 6fa53a68..c46b2fe0 100644 --- a/act/container/container_types.go +++ b/act/container/container_types.go @@ -90,7 +90,7 @@ type Info struct { type Container interface { Create(capAdd, capDrop []string) common.Executor Copy(destPath string, files ...*FileEntry) common.Executor - CopyDir(destPath, srcPath string, useGitIgnore bool) common.Executor + CopyDir(destPath, srcPath string, useGitIgnore, skipGitDir bool) common.Executor GetContainerArchive(ctx context.Context, srcPath string) (io.ReadCloser, error) Inspect(ctx context.Context) (*Info, error) DumpLogs(ctx context.Context) error diff --git a/act/container/docker_run.go b/act/container/docker_run.go index 60aea8e8..da98658b 100644 --- a/act/container/docker_run.go +++ b/act/container/docker_run.go @@ -138,12 +138,12 @@ func (cr *containerReference) Copy(destPath string, files ...*FileEntry) common. ).IfNot(common.Dryrun) } -func (cr *containerReference) CopyDir(destPath, srcPath string, useGitIgnore bool) common.Executor { +func (cr *containerReference) CopyDir(destPath, srcPath string, useGitIgnore, skipGitDir bool) common.Executor { return common.NewPipelineExecutor( common.NewInfoExecutor("docker cp src=%s dst=%s", srcPath, destPath), cr.connect(), cr.find(), - cr.copyDir(destPath, srcPath, useGitIgnore), + cr.copyDir(destPath, srcPath, useGitIgnore, skipGitDir), func(ctx context.Context) error { // If this fails, then folders have wrong permissions on non root container if cr.UID != 0 || cr.GID != 0 { @@ -940,7 +940,7 @@ func (cr *containerReference) waitForCommand(ctx context.Context, resp client.Hi } } -func (cr *containerReference) copyDir(dstPath, srcPath string, useGitIgnore bool) common.Executor { +func (cr *containerReference) copyDir(dstPath, srcPath string, useGitIgnore, skipGitDir bool) common.Executor { return func(ctx context.Context) error { if cr.id == "" { return cr.missingContainerError("copy directory to %s", dstPath) @@ -981,9 +981,10 @@ func (cr *containerReference) copyDir(dstPath, srcPath string, useGitIgnore bool } fc := &filecollector.FileCollector{ - Ignorer: ignorer, - SrcPath: srcPath, - SrcPrefix: srcPrefix, + Ignorer: ignorer, + SrcPath: srcPath, + SrcPrefix: srcPrefix, + SkipGitDir: skipGitDir, Handler: &filecollector.TarCollector{ TarWriter: tw, UID: cr.UID, diff --git a/act/container/docker_run_test.go b/act/container/docker_run_test.go index 728f186b..9623c6b3 100644 --- a/act/container/docker_run_test.go +++ b/act/container/docker_run_test.go @@ -471,7 +471,7 @@ func TestRejectsMissingContainer(t *testing.T) { assert.Contains(t, err.Error(), `container "job-1" does not exist`, op) } check("copyContent", cr.copyContent("/var/run/act", &FileEntry{Name: "x", Mode: 0o644})(ctx)) - check("copyDir", cr.copyDir("/var/run/act", "/src", false)(ctx)) + check("copyDir", cr.copyDir("/var/run/act", "/src", false, false)(ctx)) check("exec", cr.exec([]string{"echo"}, nil, "", "")(ctx)) _, err := cr.GetContainerArchive(ctx, "/var/run/act/x") check("GetContainerArchive", err) diff --git a/act/container/host_environment.go b/act/container/host_environment.go index 796ef1eb..47a9d414 100644 --- a/act/container/host_environment.go +++ b/act/container/host_environment.go @@ -92,7 +92,7 @@ func (e *HostEnvironment) Copy(destPath string, files ...*FileEntry) common.Exec } } -func (e *HostEnvironment) CopyDir(destPath, srcPath string, useGitIgnore bool) common.Executor { +func (e *HostEnvironment) CopyDir(destPath, srcPath string, useGitIgnore, skipGitDir bool) common.Executor { return func(ctx context.Context) error { logger := common.Logger(ctx) srcPrefix := filepath.Dir(srcPath) @@ -110,9 +110,10 @@ func (e *HostEnvironment) CopyDir(destPath, srcPath string, useGitIgnore bool) c ignorer = gitignore.NewMatcher(ps) } fc := &filecollector.FileCollector{ - Ignorer: ignorer, - SrcPath: srcPath, - SrcPrefix: srcPrefix, + Ignorer: ignorer, + SrcPath: srcPath, + SrcPrefix: srcPrefix, + SkipGitDir: skipGitDir, Handler: &filecollector.CopyCollector{ DstDir: destPath, }, diff --git a/act/container/host_environment_test.go b/act/container/host_environment_test.go index bb2598f7..790e9947 100644 --- a/act/container/host_environment_test.go +++ b/act/container/host_environment_test.go @@ -47,10 +47,32 @@ func TestCopyDir(t *testing.T) { _ = os.MkdirAll(e.TmpDir, 0o700) _ = os.MkdirAll(e.ToolCache, 0o700) _ = os.MkdirAll(e.ActPath, 0o700) - err := e.CopyDir(e.Workdir, e.Path, true)(ctx) + err := e.CopyDir(e.Workdir, e.Path, true, false)(ctx) assert.NoError(t, err) } +func TestCopyDirSkipGitDir(t *testing.T) { + for name, skipGitDir := range map[string]bool{"kept for a workspace copy": false, "skipped for an action copy": true} { + t.Run(name, func(t *testing.T) { + src := filepath.Join(t.TempDir(), "action") + require.NoError(t, os.MkdirAll(filepath.Join(src, ".git", "objects"), 0o700)) + require.NoError(t, os.WriteFile(filepath.Join(src, ".git", "objects", "pack"), []byte("x"), 0o600)) + require.NoError(t, os.WriteFile(filepath.Join(src, "action.yml"), []byte("name: x\n"), 0o600)) + + dest := filepath.Join(t.TempDir(), "dest") + e := &HostEnvironment{StdOut: os.Stdout} + require.NoError(t, e.CopyDir(dest, src+string(filepath.Separator), false, skipGitDir)(context.Background())) + + assert.FileExists(t, filepath.Join(dest, "action.yml")) + if skipGitDir { + assert.NoDirExists(t, filepath.Join(dest, ".git")) + } else { + assert.FileExists(t, filepath.Join(dest, ".git", "objects", "pack")) + } + }) + } +} + func TestGetContainerArchive(t *testing.T) { dir := t.TempDir() ctx := context.Background() diff --git a/act/filecollector/file_collector.go b/act/filecollector/file_collector.go index c2e9e457..eece72ab 100644 --- a/act/filecollector/file_collector.go +++ b/act/filecollector/file_collector.go @@ -94,10 +94,11 @@ func (cc *CopyCollector) WriteFile(fpath string, fi fs.FileInfo, linkName string } type FileCollector struct { - Ignorer gitignore.Matcher - SrcPath string - SrcPrefix string - Handler Handler + Ignorer gitignore.Matcher + SrcPath string + SrcPrefix string + SkipGitDir bool + Handler Handler } func openGitIndex(path string) (*index.Index, error) { @@ -124,6 +125,12 @@ func (fc *FileCollector) CollectFiles(ctx context.Context, submodulePath []strin if fi.IsDir() && len(split) > 0 && split[len(split)-1] == "." { return nil } + if fc.SkipGitDir && split[len(split)-1] == ".git" { + if fi.IsDir() { + return filepath.SkipDir + } + return nil + } var entry *index.Entry if i != nil { entry, err = i.Entry(strings.Join(split[len(submodulePath):], "/")) diff --git a/act/runner/action.go b/act/runner/action.go index 2e332f42..b25163e7 100644 --- a/act/runner/action.go +++ b/act/runner/action.go @@ -154,7 +154,7 @@ func maybeCopyToActionDir(ctx context.Context, step actionStep, actionDir, actio return err } - return rc.JobContainer.CopyDir(containerActionDirCopy, actionDir+"/", rc.Config.UseGitIgnore)(ctx) + return rc.JobContainer.CopyDir(containerActionDirCopy, actionDir+"/", rc.Config.UseGitIgnore, true)(ctx) } func runActionImpl(step actionStep, actionDir string, remoteAction *remoteAction) common.Executor { diff --git a/act/runner/action_test.go b/act/runner/action_test.go index 7a0e756f..31cf3d17 100644 --- a/act/runner/action_test.go +++ b/act/runner/action_test.go @@ -227,7 +227,7 @@ func TestActionRunner(t *testing.T) { ctx := context.Background() cm := &containerMock{} - cm.On("CopyDir", "/var/run/act/actions/dir/", "dir/", false).Return(func(ctx context.Context) error { return nil }) + cm.On("CopyDir", "/var/run/act/actions/dir/", "dir/", false, true).Return(func(ctx context.Context) error { return nil }) envMatcher := mock.MatchedBy(func(env map[string]string) bool { for k, v := range tt.expectedEnv { @@ -337,7 +337,7 @@ func TestMaybeCopyToActionDirHoldsCloneLock(t *testing.T) { copyEntered := make(chan struct{}) cm := &containerMock{} - cm.On("CopyDir", "/var/run/act/actions/", actionDir+"/", false).Return(func(ctx context.Context) error { + cm.On("CopyDir", "/var/run/act/actions/", actionDir+"/", false, true).Return(func(ctx context.Context) error { close(copyEntered) <-releaseCopy return nil diff --git a/act/runner/container_mock_test.go b/act/runner/container_mock_test.go index 890a526d..2e9041a1 100644 --- a/act/runner/container_mock_test.go +++ b/act/runner/container_mock_test.go @@ -57,8 +57,8 @@ func (cm *containerMock) Copy(destPath string, files ...*container.FileEntry) co return args.Get(0).(func(context.Context) error) } -func (cm *containerMock) CopyDir(destPath, srcPath string, useGitIgnore bool) common.Executor { - args := cm.Called(destPath, srcPath, useGitIgnore) +func (cm *containerMock) CopyDir(destPath, srcPath string, useGitIgnore, skipGitDir bool) common.Executor { + args := cm.Called(destPath, srcPath, useGitIgnore, skipGitDir) return args.Get(0).(func(context.Context) error) } diff --git a/act/runner/patch_actions_test.go b/act/runner/patch_actions_test.go index ffb27c88..6a6d651f 100644 --- a/act/runner/patch_actions_test.go +++ b/act/runner/patch_actions_test.go @@ -244,7 +244,7 @@ func TestPatchActionsAtTheContainerCopy(t *testing.T) { require.NoError(t, os.WriteFile(script, []byte(gateTSC), 0o600)) var copied string - cm.On("CopyDir", mock.Anything, mock.Anything, mock.Anything).Return(func(context.Context) error { + cm.On("CopyDir", mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(func(context.Context) error { body, err := os.ReadFile(script) require.NoError(t, err) copied = string(body) diff --git a/act/runner/step_action_remote.go b/act/runner/step_action_remote.go index 11037e97..8429fb6c 100644 --- a/act/runner/step_action_remote.go +++ b/act/runner/step_action_remote.go @@ -169,7 +169,7 @@ func (sar *stepActionRemote) main() common.Executor { return fmt.Errorf("unable to interpolate with.path: %w", err) } copyToPath := path.Join(sar.RunContext.JobContainer.ToContainerPath(sar.RunContext.Config.Workdir), checkoutPath) - return sar.RunContext.JobContainer.CopyDir(copyToPath, sar.RunContext.Config.Workdir+string(filepath.Separator)+".", sar.RunContext.Config.UseGitIgnore)(ctx) + return sar.RunContext.JobContainer.CopyDir(copyToPath, sar.RunContext.Config.Workdir+string(filepath.Separator)+".", sar.RunContext.Config.UseGitIgnore, false)(ctx) } return sar.runAction(sar, sar.actionDir(), sar.remoteAction)(ctx)