summaryrefslogtreecommitdiffstats
path: root/pkg/filecollector/file_collector.go
diff options
context:
space:
mode:
Diffstat (limited to 'pkg/filecollector/file_collector.go')
-rw-r--r--pkg/filecollector/file_collector.go210
1 files changed, 210 insertions, 0 deletions
diff --git a/pkg/filecollector/file_collector.go b/pkg/filecollector/file_collector.go
new file mode 100644
index 0000000..8547bb7
--- /dev/null
+++ b/pkg/filecollector/file_collector.go
@@ -0,0 +1,210 @@
+package filecollector
+
+import (
+ "archive/tar"
+ "context"
+ "fmt"
+ "io"
+ "io/fs"
+ "os"
+ "path"
+ "path/filepath"
+ "strings"
+
+ git "github.com/go-git/go-git/v5"
+ "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"
+)
+
+type Handler interface {
+ WriteFile(path string, fi fs.FileInfo, linkName string, f io.Reader) error
+}
+
+type TarCollector struct {
+ TarWriter *tar.Writer
+ UID int
+ GID int
+ DstDir string
+}
+
+func (tc TarCollector) WriteFile(fpath string, fi fs.FileInfo, linkName string, f io.Reader) error {
+ // create a new dir/file header
+ header, err := tar.FileInfoHeader(fi, linkName)
+ if err != nil {
+ return err
+ }
+
+ // update the name to correctly reflect the desired destination when untaring
+ header.Name = path.Join(tc.DstDir, fpath)
+ header.Mode = int64(fi.Mode())
+ header.ModTime = fi.ModTime()
+ header.Uid = tc.UID
+ header.Gid = tc.GID
+
+ // write the header
+ if err := tc.TarWriter.WriteHeader(header); err != nil {
+ return err
+ }
+
+ // this is a symlink no reader provided
+ if f == nil {
+ return nil
+ }
+
+ // copy file data into tar writer
+ if _, err := io.Copy(tc.TarWriter, f); err != nil {
+ return err
+ }
+ return nil
+}
+
+type CopyCollector struct {
+ DstDir string
+}
+
+func (cc *CopyCollector) WriteFile(fpath string, fi fs.FileInfo, linkName string, f io.Reader) error {
+ fdestpath := filepath.Join(cc.DstDir, fpath)
+ if err := os.MkdirAll(filepath.Dir(fdestpath), 0o777); err != nil {
+ return err
+ }
+ if f == nil {
+ return os.Symlink(linkName, fdestpath)
+ }
+ df, err := os.OpenFile(fdestpath, os.O_CREATE|os.O_WRONLY, fi.Mode())
+ if err != nil {
+ return err
+ }
+ defer df.Close()
+ if _, err := io.Copy(df, f); err != nil {
+ return err
+ }
+ return nil
+}
+
+type FileCollector struct {
+ Ignorer gitignore.Matcher
+ SrcPath string
+ SrcPrefix string
+ Fs Fs
+ Handler Handler
+}
+
+type Fs interface {
+ Walk(root string, fn filepath.WalkFunc) error
+ OpenGitIndex(path string) (*index.Index, error)
+ Open(path string) (io.ReadCloser, error)
+ Readlink(path string) (string, error)
+}
+
+type DefaultFs struct {
+}
+
+func (*DefaultFs) Walk(root string, fn filepath.WalkFunc) error {
+ return filepath.Walk(root, fn)
+}
+
+func (*DefaultFs) OpenGitIndex(path string) (*index.Index, error) {
+ r, err := git.PlainOpen(path)
+ if err != nil {
+ return nil, err
+ }
+ i, err := r.Storer.Index()
+ if err != nil {
+ return nil, err
+ }
+ return i, nil
+}
+
+func (*DefaultFs) Open(path string) (io.ReadCloser, error) {
+ return os.Open(path)
+}
+
+func (*DefaultFs) Readlink(path string) (string, error) {
+ return os.Readlink(path)
+}
+
+//nolint:gocyclo
+func (fc *FileCollector) CollectFiles(ctx context.Context, submodulePath []string) filepath.WalkFunc {
+ i, _ := fc.Fs.OpenGitIndex(path.Join(fc.SrcPath, path.Join(submodulePath...)))
+ return func(file string, fi os.FileInfo, err error) error {
+ if err != nil {
+ return err
+ }
+ if ctx != nil {
+ select {
+ case <-ctx.Done():
+ return fmt.Errorf("copy cancelled")
+ default:
+ }
+ }
+
+ sansPrefix := strings.TrimPrefix(file, fc.SrcPrefix)
+ split := strings.Split(sansPrefix, string(filepath.Separator))
+ // The root folders should be skipped, submodules only have the last path component set to "." by filepath.Walk
+ if fi.IsDir() && len(split) > 0 && split[len(split)-1] == "." {
+ return nil
+ }
+ var entry *index.Entry
+ if i != nil {
+ entry, err = i.Entry(strings.Join(split[len(submodulePath):], "/"))
+ } else {
+ err = index.ErrEntryNotFound
+ }
+ if err != nil && fc.Ignorer != nil && fc.Ignorer.Match(split, fi.IsDir()) {
+ if fi.IsDir() {
+ if i != nil {
+ ms, err := i.Glob(strings.Join(append(split[len(submodulePath):], "**"), "/"))
+ if err != nil || len(ms) == 0 {
+ return filepath.SkipDir
+ }
+ } else {
+ return filepath.SkipDir
+ }
+ } else {
+ return nil
+ }
+ }
+ if err == nil && entry.Mode == filemode.Submodule {
+ err = fc.Fs.Walk(file, fc.CollectFiles(ctx, split))
+ if err != nil {
+ return err
+ }
+ return filepath.SkipDir
+ }
+ path := filepath.ToSlash(sansPrefix)
+
+ // return on non-regular files (thanks to [kumo](https://medium.com/@komuw/just-like-you-did-fbdd7df829d3) for this suggested update)
+ if fi.Mode()&os.ModeSymlink == os.ModeSymlink {
+ linkName, err := fc.Fs.Readlink(file)
+ if err != nil {
+ return fmt.Errorf("unable to readlink '%s': %w", file, err)
+ }
+ return fc.Handler.WriteFile(path, fi, linkName, nil)
+ } else if !fi.Mode().IsRegular() {
+ return nil
+ }
+
+ // open file
+ f, err := fc.Fs.Open(file)
+ if err != nil {
+ return err
+ }
+ defer f.Close()
+
+ if ctx != nil {
+ // make io.Copy cancellable by closing the file
+ cpctx, cpfinish := context.WithCancel(ctx)
+ defer cpfinish()
+ go func() {
+ select {
+ case <-cpctx.Done():
+ case <-ctx.Done():
+ f.Close()
+ }
+ }()
+ }
+
+ return fc.Handler.WriteFile(path, fi, "", f)
+ }
+}