Avoid redundant calls to filepath.Clean (#2652)
filepath.Clean shows up in profiles as a hot spot, and there seem to be
many redundant calls, particularly in ignorelist handling. We can avoid
these redundant calls by pre-cleaning entries in the ignore list, and
providing fast paths when we know we're already dealing with a cleaned
candidate path.
Before:
580ms 3.03% 72.35% 590ms 3.08% path/filepath.(*lazybuf).append (inline)
390ms 2.03% 74.39% 990ms 5.16% path/filepath.Clean
After:
0.13s 0.69% 84.01% 0.17s 0.91% path/filepath.(*lazybuf).append (inline)
0.13s 0.69% 84.70% 0.31s 1.65% path/filepath.Clean
This commit is contained in:
parent
81a7294032
commit
32ce1bf67e
|
|
@ -153,7 +153,7 @@ func Test_CachingCopyCommand_ExecuteCommand(t *testing.T) {
|
||||||
extractedFiles: []string{"/foo.txt"},
|
extractedFiles: []string{"/foo.txt"},
|
||||||
contextFiles: []string{"foo.txt"},
|
contextFiles: []string{"foo.txt"},
|
||||||
}
|
}
|
||||||
c.extractFn = func(_ string, _ *tar.Header, _ io.Reader) error {
|
c.extractFn = func(_ string, _ *tar.Header, _ string, _ io.Reader) error {
|
||||||
*tc.count++
|
*tc.count++
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
@ -166,7 +166,7 @@ func Test_CachingCopyCommand_ExecuteCommand(t *testing.T) {
|
||||||
description: "with no image",
|
description: "with no image",
|
||||||
expectErr: true,
|
expectErr: true,
|
||||||
}
|
}
|
||||||
c.extractFn = func(_ string, _ *tar.Header, _ io.Reader) error {
|
c.extractFn = func(_ string, _ *tar.Header, _ string, _ io.Reader) error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
tc.command = c
|
tc.command = c
|
||||||
|
|
@ -176,7 +176,7 @@ func Test_CachingCopyCommand_ExecuteCommand(t *testing.T) {
|
||||||
c := &CachingCopyCommand{
|
c := &CachingCopyCommand{
|
||||||
img: fakeImage{},
|
img: fakeImage{},
|
||||||
}
|
}
|
||||||
c.extractFn = func(_ string, _ *tar.Header, _ io.Reader) error {
|
c.extractFn = func(_ string, _ *tar.Header, _ string, _ io.Reader) error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
return testCase{
|
return testCase{
|
||||||
|
|
@ -193,7 +193,7 @@ func Test_CachingCopyCommand_ExecuteCommand(t *testing.T) {
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
c.extractFn = func(_ string, _ *tar.Header, _ io.Reader) error {
|
c.extractFn = func(_ string, _ *tar.Header, _ string, _ io.Reader) error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
tc := testCase{
|
tc := testCase{
|
||||||
|
|
|
||||||
|
|
@ -204,7 +204,7 @@ func Test_CachingRunCommand_ExecuteCommand(t *testing.T) {
|
||||||
extractedFiles: []string{"/foo.txt"},
|
extractedFiles: []string{"/foo.txt"},
|
||||||
contextFiles: []string{"foo.txt"},
|
contextFiles: []string{"foo.txt"},
|
||||||
}
|
}
|
||||||
c.extractFn = func(_ string, _ *tar.Header, _ io.Reader) error {
|
c.extractFn = func(_ string, _ *tar.Header, _ string, _ io.Reader) error {
|
||||||
*tc.count++
|
*tc.count++
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
@ -217,7 +217,7 @@ func Test_CachingRunCommand_ExecuteCommand(t *testing.T) {
|
||||||
desctiption: "with no image",
|
desctiption: "with no image",
|
||||||
expectErr: true,
|
expectErr: true,
|
||||||
}
|
}
|
||||||
c.extractFn = func(_ string, _ *tar.Header, _ io.Reader) error {
|
c.extractFn = func(_ string, _ *tar.Header, _ string, _ io.Reader) error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
tc.command = c
|
tc.command = c
|
||||||
|
|
@ -228,7 +228,7 @@ func Test_CachingRunCommand_ExecuteCommand(t *testing.T) {
|
||||||
img: fakeImage{},
|
img: fakeImage{},
|
||||||
}
|
}
|
||||||
|
|
||||||
c.extractFn = func(_ string, _ *tar.Header, _ io.Reader) error {
|
c.extractFn = func(_ string, _ *tar.Header, _ string, _ io.Reader) error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -246,7 +246,7 @@ func Test_CachingRunCommand_ExecuteCommand(t *testing.T) {
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
c.extractFn = func(_ string, _ *tar.Header, _ io.Reader) error {
|
c.extractFn = func(_ string, _ *tar.Header, _ string, _ io.Reader) error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
tc := testCase{
|
tc := testCase{
|
||||||
|
|
|
||||||
|
|
@ -80,7 +80,7 @@ func ResolvePaths(paths []string, wl []util.IgnoreListEntry) (pathsToAdd []strin
|
||||||
|
|
||||||
// If the given path is a symlink and the target is part of the ignorelist
|
// If the given path is a symlink and the target is part of the ignorelist
|
||||||
// ignore the target
|
// ignore the target
|
||||||
if util.CheckProvidedIgnoreList(evaled, wl) {
|
if util.CheckCleanedPathAgainstProvidedIgnoreList(evaled, wl) {
|
||||||
logrus.Debugf("Path %s is ignored, ignoring it", evaled)
|
logrus.Debugf("Path %s is ignored, ignoring it", evaled)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -60,7 +60,7 @@ type IgnoreListEntry struct {
|
||||||
|
|
||||||
var defaultIgnoreList = []IgnoreListEntry{
|
var defaultIgnoreList = []IgnoreListEntry{
|
||||||
{
|
{
|
||||||
Path: config.KanikoDir,
|
Path: filepath.Clean(config.KanikoDir),
|
||||||
PrefixMatchOnly: false,
|
PrefixMatchOnly: false,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|
@ -86,7 +86,7 @@ type FileContext struct {
|
||||||
ExcludedFiles []string
|
ExcludedFiles []string
|
||||||
}
|
}
|
||||||
|
|
||||||
type ExtractFunction func(string, *tar.Header, io.Reader) error
|
type ExtractFunction func(string, *tar.Header, string, io.Reader) error
|
||||||
|
|
||||||
type FSConfig struct {
|
type FSConfig struct {
|
||||||
includeWhiteout bool
|
includeWhiteout bool
|
||||||
|
|
@ -100,11 +100,17 @@ func IgnoreList() []IgnoreListEntry {
|
||||||
}
|
}
|
||||||
|
|
||||||
func AddToIgnoreList(entry IgnoreListEntry) {
|
func AddToIgnoreList(entry IgnoreListEntry) {
|
||||||
ignorelist = append(ignorelist, entry)
|
ignorelist = append(ignorelist, IgnoreListEntry{
|
||||||
|
Path: filepath.Clean(entry.Path),
|
||||||
|
PrefixMatchOnly: entry.PrefixMatchOnly,
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
func AddToDefaultIgnoreList(entry IgnoreListEntry) {
|
func AddToDefaultIgnoreList(entry IgnoreListEntry) {
|
||||||
defaultIgnoreList = append(defaultIgnoreList, entry)
|
defaultIgnoreList = append(defaultIgnoreList, IgnoreListEntry{
|
||||||
|
Path: filepath.Clean(entry.Path),
|
||||||
|
PrefixMatchOnly: entry.PrefixMatchOnly,
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
func IncludeWhiteout() FSOpt {
|
func IncludeWhiteout() FSOpt {
|
||||||
|
|
@ -175,7 +181,8 @@ func GetFSFromLayers(root string, layers []v1.Layer, opts ...FSOpt) ([]string, e
|
||||||
return nil, errors.Wrap(err, fmt.Sprintf("error reading tar %d", i))
|
return nil, errors.Wrap(err, fmt.Sprintf("error reading tar %d", i))
|
||||||
}
|
}
|
||||||
|
|
||||||
path := filepath.Join(root, filepath.Clean(hdr.Name))
|
cleanedName := filepath.Clean(hdr.Name)
|
||||||
|
path := filepath.Join(root, cleanedName)
|
||||||
base := filepath.Base(path)
|
base := filepath.Base(path)
|
||||||
dir := filepath.Dir(path)
|
dir := filepath.Dir(path)
|
||||||
|
|
||||||
|
|
@ -185,7 +192,7 @@ func GetFSFromLayers(root string, layers []v1.Layer, opts ...FSOpt) ([]string, e
|
||||||
name := strings.TrimPrefix(base, archive.WhiteoutPrefix)
|
name := strings.TrimPrefix(base, archive.WhiteoutPrefix)
|
||||||
path := filepath.Join(dir, name)
|
path := filepath.Join(dir, name)
|
||||||
|
|
||||||
if CheckIgnoreList(path) {
|
if CheckCleanedPathAgainstIgnoreList(path) {
|
||||||
logrus.Tracef("Not deleting %s, as it's ignored", path)
|
logrus.Tracef("Not deleting %s, as it's ignored", path)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
@ -205,11 +212,11 @@ func GetFSFromLayers(root string, layers []v1.Layer, opts ...FSOpt) ([]string, e
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := cfg.extractFunc(root, hdr, tr); err != nil {
|
if err := cfg.extractFunc(root, hdr, cleanedName, tr); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
extractedFiles = append(extractedFiles, filepath.Join(root, filepath.Clean(hdr.Name)))
|
extractedFiles = append(extractedFiles, filepath.Join(root, cleanedName))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return extractedFiles, nil
|
return extractedFiles, nil
|
||||||
|
|
@ -224,7 +231,7 @@ func DeleteFilesystem() error {
|
||||||
return nil //nolint:nilerr
|
return nil //nolint:nilerr
|
||||||
}
|
}
|
||||||
|
|
||||||
if CheckIgnoreList(path) {
|
if CheckCleanedPathAgainstIgnoreList(path) {
|
||||||
if !isExist(path) {
|
if !isExist(path) {
|
||||||
logrus.Debugf("Path %s ignored, but not exists", path)
|
logrus.Debugf("Path %s ignored, but not exists", path)
|
||||||
return nil
|
return nil
|
||||||
|
|
@ -276,16 +283,17 @@ func UnTar(r io.Reader, dest string) ([]string, error) {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
if err := ExtractFile(dest, hdr, tr); err != nil {
|
cleanedName := filepath.Clean(hdr.Name)
|
||||||
|
if err := ExtractFile(dest, hdr, cleanedName, tr); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
extractedFiles = append(extractedFiles, filepath.Join(dest, filepath.Clean(hdr.Name)))
|
extractedFiles = append(extractedFiles, filepath.Join(dest, cleanedName))
|
||||||
}
|
}
|
||||||
return extractedFiles, nil
|
return extractedFiles, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func ExtractFile(dest string, hdr *tar.Header, tr io.Reader) error {
|
func ExtractFile(dest string, hdr *tar.Header, cleanedName string, tr io.Reader) error {
|
||||||
path := filepath.Join(dest, filepath.Clean(hdr.Name))
|
path := filepath.Join(dest, cleanedName)
|
||||||
base := filepath.Base(path)
|
base := filepath.Base(path)
|
||||||
dir := filepath.Dir(path)
|
dir := filepath.Dir(path)
|
||||||
mode := hdr.FileInfo().Mode()
|
mode := hdr.FileInfo().Mode()
|
||||||
|
|
@ -297,7 +305,7 @@ func ExtractFile(dest string, hdr *tar.Header, tr io.Reader) error {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
if CheckIgnoreList(abs) && !checkIgnoreListRoot(dest) {
|
if CheckCleanedPathAgainstIgnoreList(abs) && !checkIgnoreListRoot(dest) {
|
||||||
logrus.Debugf("Not adding %s because it is ignored", path)
|
logrus.Debugf("Not adding %s because it is ignored", path)
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
@ -358,7 +366,7 @@ func ExtractFile(dest string, hdr *tar.Header, tr io.Reader) error {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
if CheckIgnoreList(abs) {
|
if CheckCleanedPathAgainstIgnoreList(abs) {
|
||||||
logrus.Tracef("Skipping link from %s to %s because %s is ignored", hdr.Linkname, path, hdr.Linkname)
|
logrus.Tracef("Skipping link from %s to %s because %s is ignored", hdr.Linkname, path, hdr.Linkname)
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
@ -399,6 +407,7 @@ func ExtractFile(dest string, hdr *tar.Header, tr io.Reader) error {
|
||||||
}
|
}
|
||||||
|
|
||||||
func IsInProvidedIgnoreList(path string, wl []IgnoreListEntry) bool {
|
func IsInProvidedIgnoreList(path string, wl []IgnoreListEntry) bool {
|
||||||
|
path = filepath.Clean(path)
|
||||||
for _, entry := range wl {
|
for _, entry := range wl {
|
||||||
if !entry.PrefixMatchOnly && path == entry.Path {
|
if !entry.PrefixMatchOnly && path == entry.Path {
|
||||||
return true
|
return true
|
||||||
|
|
@ -412,9 +421,9 @@ func IsInIgnoreList(path string) bool {
|
||||||
return IsInProvidedIgnoreList(path, ignorelist)
|
return IsInProvidedIgnoreList(path, ignorelist)
|
||||||
}
|
}
|
||||||
|
|
||||||
func CheckProvidedIgnoreList(path string, wl []IgnoreListEntry) bool {
|
func CheckCleanedPathAgainstProvidedIgnoreList(path string, wl []IgnoreListEntry) bool {
|
||||||
for _, wl := range ignorelist {
|
for _, wl := range ignorelist {
|
||||||
if HasFilepathPrefix(path, wl.Path, wl.PrefixMatchOnly) {
|
if hasCleanedFilepathPrefix(path, wl.Path, wl.PrefixMatchOnly) {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -423,7 +432,11 @@ func CheckProvidedIgnoreList(path string, wl []IgnoreListEntry) bool {
|
||||||
}
|
}
|
||||||
|
|
||||||
func CheckIgnoreList(path string) bool {
|
func CheckIgnoreList(path string) bool {
|
||||||
return CheckProvidedIgnoreList(path, ignorelist)
|
return CheckCleanedPathAgainstIgnoreList(filepath.Clean(path))
|
||||||
|
}
|
||||||
|
|
||||||
|
func CheckCleanedPathAgainstIgnoreList(path string) bool {
|
||||||
|
return CheckCleanedPathAgainstProvidedIgnoreList(path, ignorelist)
|
||||||
}
|
}
|
||||||
|
|
||||||
func checkIgnoreListRoot(root string) bool {
|
func checkIgnoreListRoot(root string) bool {
|
||||||
|
|
@ -463,7 +476,7 @@ func DetectFilesystemIgnoreList(path string) error {
|
||||||
}
|
}
|
||||||
if lineArr[4] != config.RootDir {
|
if lineArr[4] != config.RootDir {
|
||||||
logrus.Tracef("Adding ignore list entry %s from line: %s", lineArr[4], line)
|
logrus.Tracef("Adding ignore list entry %s from line: %s", lineArr[4], line)
|
||||||
ignorelist = append(ignorelist, IgnoreListEntry{
|
AddToIgnoreList(IgnoreListEntry{
|
||||||
Path: lineArr[4],
|
Path: lineArr[4],
|
||||||
PrefixMatchOnly: false,
|
PrefixMatchOnly: false,
|
||||||
})
|
})
|
||||||
|
|
@ -480,12 +493,13 @@ func DetectFilesystemIgnoreList(path string) error {
|
||||||
func RelativeFiles(fp string, root string) ([]string, error) {
|
func RelativeFiles(fp string, root string) ([]string, error) {
|
||||||
var files []string
|
var files []string
|
||||||
fullPath := filepath.Join(root, fp)
|
fullPath := filepath.Join(root, fp)
|
||||||
|
cleanedRoot := filepath.Clean(root)
|
||||||
logrus.Debugf("Getting files and contents at root %s for %s", root, fullPath)
|
logrus.Debugf("Getting files and contents at root %s for %s", root, fullPath)
|
||||||
err := filepath.Walk(fullPath, func(path string, info os.FileInfo, err error) error {
|
err := filepath.Walk(fullPath, func(path string, info os.FileInfo, err error) error {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
if CheckIgnoreList(path) && !HasFilepathPrefix(path, root, false) {
|
if CheckCleanedPathAgainstIgnoreList(path) && !hasCleanedFilepathPrefix(filepath.Clean(path), cleanedRoot, false) {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
relPath, err := filepath.Rel(root, path)
|
relPath, err := filepath.Rel(root, path)
|
||||||
|
|
@ -591,7 +605,7 @@ func CreateFile(path string, reader io.Reader, perm os.FileMode, uid uint32, gid
|
||||||
// AddVolumePath adds the given path to the volume ignorelist.
|
// AddVolumePath adds the given path to the volume ignorelist.
|
||||||
func AddVolumePathToIgnoreList(path string) {
|
func AddVolumePathToIgnoreList(path string) {
|
||||||
logrus.Infof("Adding volume %s to ignorelist", path)
|
logrus.Infof("Adding volume %s to ignorelist", path)
|
||||||
ignorelist = append(ignorelist, IgnoreListEntry{
|
AddToIgnoreList(IgnoreListEntry{
|
||||||
Path: path,
|
Path: path,
|
||||||
PrefixMatchOnly: true,
|
PrefixMatchOnly: true,
|
||||||
})
|
})
|
||||||
|
|
@ -778,11 +792,12 @@ func (c FileContext) ExcludesFile(path string) bool {
|
||||||
|
|
||||||
// HasFilepathPrefix checks if the given file path begins with prefix
|
// HasFilepathPrefix checks if the given file path begins with prefix
|
||||||
func HasFilepathPrefix(path, prefix string, prefixMatchOnly bool) bool {
|
func HasFilepathPrefix(path, prefix string, prefixMatchOnly bool) bool {
|
||||||
prefix = filepath.Clean(prefix)
|
return hasCleanedFilepathPrefix(filepath.Clean(path), filepath.Clean(prefix), prefixMatchOnly)
|
||||||
prefixArray := strings.Split(prefix, "/")
|
}
|
||||||
path = filepath.Clean(path)
|
|
||||||
pathArray := strings.SplitN(path, "/", len(prefixArray)+1)
|
|
||||||
|
|
||||||
|
func hasCleanedFilepathPrefix(path, prefix string, prefixMatchOnly bool) bool {
|
||||||
|
prefixArray := strings.Split(prefix, "/")
|
||||||
|
pathArray := strings.SplitN(path, "/", len(prefixArray)+1)
|
||||||
if len(pathArray) < len(prefixArray) {
|
if len(pathArray) < len(prefixArray) {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
@ -968,7 +983,7 @@ func CopyOwnership(src string, destDir string, root string) error {
|
||||||
}
|
}
|
||||||
destPath := filepath.Join(destDir, relPath)
|
destPath := filepath.Join(destDir, relPath)
|
||||||
|
|
||||||
if CheckIgnoreList(src) && CheckIgnoreList(destPath) {
|
if CheckCleanedPathAgainstIgnoreList(src) && CheckCleanedPathAgainstIgnoreList(destPath) {
|
||||||
if !isExist(destPath) {
|
if !isExist(destPath) {
|
||||||
logrus.Debugf("Path %s ignored, but not exists", destPath)
|
logrus.Debugf("Path %s ignored, but not exists", destPath)
|
||||||
return nil
|
return nil
|
||||||
|
|
@ -979,7 +994,7 @@ func CopyOwnership(src string, destDir string, root string) error {
|
||||||
logrus.Debugf("Not copying ownership for %s, as it's ignored", destPath)
|
logrus.Debugf("Not copying ownership for %s, as it's ignored", destPath)
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
if CheckIgnoreList(destDir) && CheckIgnoreList(path) {
|
if CheckIgnoreList(destDir) && CheckCleanedPathAgainstIgnoreList(path) {
|
||||||
if !isExist(path) {
|
if !isExist(path) {
|
||||||
logrus.Debugf("Path %s ignored, but not exists", path)
|
logrus.Debugf("Path %s ignored, but not exists", path)
|
||||||
return nil
|
return nil
|
||||||
|
|
@ -1118,7 +1133,7 @@ func GetFSInfoMap(dir string, existing map[string]os.FileInfo) (map[string]os.Fi
|
||||||
timer := timing.Start("Walking filesystem with Stat")
|
timer := timing.Start("Walking filesystem with Stat")
|
||||||
godirwalk.Walk(dir, &godirwalk.Options{
|
godirwalk.Walk(dir, &godirwalk.Options{
|
||||||
Callback: func(path string, ent *godirwalk.Dirent) error {
|
Callback: func(path string, ent *godirwalk.Dirent) error {
|
||||||
if CheckIgnoreList(path) {
|
if CheckCleanedPathAgainstIgnoreList(path) {
|
||||||
if IsDestDir(path) {
|
if IsDestDir(path) {
|
||||||
logrus.Tracef("Skipping paths under %s, as it is a ignored directory", path)
|
logrus.Tracef("Skipping paths under %s, as it is a ignored directory", path)
|
||||||
return filepath.SkipDir
|
return filepath.SkipDir
|
||||||
|
|
|
||||||
|
|
@ -810,7 +810,7 @@ func TestExtractFile(t *testing.T) {
|
||||||
defer os.RemoveAll(r)
|
defer os.RemoveAll(r)
|
||||||
|
|
||||||
for _, hdr := range tc.hdrs {
|
for _, hdr := range tc.hdrs {
|
||||||
if err := ExtractFile(r, hdr, bytes.NewReader(tc.contents)); err != nil {
|
if err := ExtractFile(r, hdr, filepath.Clean(hdr.Name), bytes.NewReader(tc.contents)); err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -1008,7 +1008,7 @@ func Test_CopyFile_skips_self(t *testing.T) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func fakeExtract(dest string, hdr *tar.Header, tr io.Reader) error {
|
func fakeExtract(_ string, _ *tar.Header, _ string, _ io.Reader) error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue