diff --git a/pkg/filecollector/file_collector.go b/pkg/filecollector/file_collector.go index 8547bb7c5fa..c039926bbe0 100644 --- a/pkg/filecollector/file_collector.go +++ b/pkg/filecollector/file_collector.go @@ -68,7 +68,7 @@ func (cc *CopyCollector) WriteFile(fpath string, fi fs.FileInfo, linkName string if err := os.MkdirAll(filepath.Dir(fdestpath), 0o777); err != nil { return err } - if f == nil { + if linkName != "" { return os.Symlink(linkName, fdestpath) } df, err := os.OpenFile(fdestpath, os.O_CREATE|os.O_WRONLY, fi.Mode()) diff --git a/pkg/runner/action_cache.go b/pkg/runner/action_cache.go index 1099d78f99a..9af06db0335 100644 --- a/pkg/runner/action_cache.go +++ b/pkg/runner/action_cache.go @@ -6,10 +6,12 @@ import ( "crypto/rand" "encoding/hex" "errors" + "fmt" "io" "io/fs" "path" "strings" + "time" git "github.com/go-git/go-git/v5" config "github.com/go-git/go-git/v5/config" @@ -78,6 +80,44 @@ func (c GoGitActionCache) Fetch(ctx context.Context, cacheDir, url, ref, token s return hash.String(), nil } +type GitFileInfo struct { + name string + size int64 + modTime time.Time + isDir bool + mode fs.FileMode +} + +// IsDir implements fs.FileInfo. +func (g *GitFileInfo) IsDir() bool { + return g.isDir +} + +// ModTime implements fs.FileInfo. +func (g *GitFileInfo) ModTime() time.Time { + return g.modTime +} + +// Mode implements fs.FileInfo. +func (g *GitFileInfo) Mode() fs.FileMode { + return g.mode +} + +// Name implements fs.FileInfo. +func (g *GitFileInfo) Name() string { + return g.name +} + +// Size implements fs.FileInfo. +func (g *GitFileInfo) Size() int64 { + return g.size +} + +// Sys implements fs.FileInfo. +func (g *GitFileInfo) Sys() any { + return nil +} + func (c GoGitActionCache) GetTarArchive(ctx context.Context, cacheDir, sha, includePrefix string) (io.ReadCloser, error) { gitPath := path.Join(c.Path, safeFilename(cacheDir)+".git") gogitrepo, err := git.PlainOpen(gitPath) @@ -88,6 +128,10 @@ func (c GoGitActionCache) GetTarArchive(ctx context.Context, cacheDir, sha, incl if err != nil { return nil, err } + t, err := commit.Tree() + if err != nil { + return nil, err + } files, err := commit.Files() if err != nil { return nil, err @@ -108,45 +152,63 @@ func (c GoGitActionCache) GetTarArchive(ctx context.Context, cacheDir, sha, incl tw := tar.NewWriter(wpipe) cleanIncludePrefix := path.Clean(includePrefix) wpipe.CloseWithError(files.ForEach(func(f *object.File) error { - if err := ctx.Err(); err != nil { - return err - } - name := f.Name - if strings.HasPrefix(name, cleanIncludePrefix+"/") { - name = name[len(cleanIncludePrefix)+1:] - } else if cleanIncludePrefix != "." && name != cleanIncludePrefix { - return nil - } - fmode, err := f.Mode.ToOSFileMode() - if err != nil { - return err - } - if fmode&fs.ModeSymlink == fs.ModeSymlink { - content, err := f.Contents() - if err != nil { - return err - } - return tw.WriteHeader(&tar.Header{ - Name: name, - Mode: int64(fmode), - Linkname: content, - }) - } - err = tw.WriteHeader(&tar.Header{ - Name: name, - Mode: int64(fmode), - Size: f.Size, - }) - if err != nil { - return err - } - reader, err := f.Reader() - if err != nil { - return err - } - _, err = io.Copy(tw, reader) - return err + return actionCacheCopyFileOrDir(ctx, cleanIncludePrefix, t, tw, f.Name, f) })) }() return rpipe, err } + +func actionCacheCopyFileOrDir(ctx context.Context, cleanIncludePrefix string, t *object.Tree, tw *tar.Writer, origin string, f *object.File) error { + if err := ctx.Err(); err != nil { + return err + } + name := origin + if strings.HasPrefix(name, cleanIncludePrefix+"/") { + name = name[len(cleanIncludePrefix)+1:] + } else if cleanIncludePrefix != "." && name != cleanIncludePrefix { + return nil + } + fmode, err := f.Mode.ToOSFileMode() + if err != nil { + return err + } + if fmode&fs.ModeSymlink == fs.ModeSymlink { + content, err := f.Contents() + if err != nil { + return err + } + + destPath := path.Join(path.Dir(f.Name), content) + + subtree, err := t.Tree(destPath) + if err == nil { + return subtree.Files().ForEach(func(ft *object.File) error { + return actionCacheCopyFileOrDir(ctx, cleanIncludePrefix, t, tw, origin+strings.TrimPrefix(ft.Name, f.Name), f) + }) + } + + f, err := t.File(destPath) + if err != nil { + return fmt.Errorf("%s (%s): %w", destPath, origin, err) + } + return actionCacheCopyFileOrDir(ctx, cleanIncludePrefix, t, tw, origin, f) + } + header, err := tar.FileInfoHeader(&GitFileInfo{ + name: name, + mode: fmode, + size: f.Size, + }, "") + if err != nil { + return err + } + err = tw.WriteHeader(header) + if err != nil { + return err + } + reader, err := f.Reader() + if err != nil { + return err + } + _, err = io.Copy(tw, reader) + return err +}