From 208a6d1752d24e10c1ff672142f6fc88b2e1a376 Mon Sep 17 00:00:00 2001 From: Will Roden Date: Mon, 7 Aug 2023 14:11:38 -0500 Subject: [PATCH] update default cache dir --- go.mod | 1 + go.sum | 2 ++ internal/bindown/config.go | 36 +++++++++++++++++++++- internal/bindown/util.go | 63 ++++++++++++++++++++++++++++++++++++++ 4 files changed, 101 insertions(+), 1 deletion(-) diff --git a/go.mod b/go.mod index 4cdabc4..eeef80c 100644 --- a/go.mod +++ b/go.mod @@ -16,6 +16,7 @@ require ( github.com/mholt/archiver/v4 v4.0.0-alpha.8 github.com/posener/complete v1.2.3 github.com/rogpeppe/go-internal v1.10.0 + github.com/sabhiram/go-gitignore v0.0.0-20210923224102-525f6e181f06 github.com/santhosh-tekuri/jsonschema/v5 v5.3.0 github.com/stretchr/testify v1.7.1 github.com/willabides/kongplete v0.3.0 diff --git a/go.sum b/go.sum index 30d635a..24a6cb9 100644 --- a/go.sum +++ b/go.sum @@ -167,6 +167,8 @@ github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFR github.com/rogpeppe/go-internal v1.10.0 h1:TMyTOH3F/DB16zRVcYyreMH6GnZZrwQVAoYjRBZyWFQ= github.com/rogpeppe/go-internal v1.10.0/go.mod h1:UQnix2H7Ngw/k4C5ijL5+65zddjncjaFoBhdsK/akog= github.com/rwcarlsen/goexif v0.0.0-20190401172101-9e8deecbddbd/go.mod h1:hPqNNc0+uJM6H+SuU8sEs5K5IQeKccPqeSjfgcKGgPk= +github.com/sabhiram/go-gitignore v0.0.0-20210923224102-525f6e181f06 h1:OkMGxebDjyw0ULyrTYWeN0UNCCkmCWfjPnIA2W6oviI= +github.com/sabhiram/go-gitignore v0.0.0-20210923224102-525f6e181f06/go.mod h1:+ePHsJ1keEjQtpvf9HHw0f4ZeJ0TLRsxhunSI2hYJSs= github.com/santhosh-tekuri/jsonschema/v5 v5.3.0 h1:uIkTLo0AGRc8l7h5l9r+GcYi9qfVPt6lD4/bhmzfiKo= github.com/santhosh-tekuri/jsonschema/v5 v5.3.0/go.mod h1:FKdcjfQW6rpZSnxxUvEA5H/cDPdvJ/SZJQLWWXWGrZ0= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= diff --git a/internal/bindown/config.go b/internal/bindown/config.go index baaee9b..97d4a57 100644 --- a/internal/bindown/config.go +++ b/internal/bindown/config.go @@ -656,7 +656,10 @@ func NewConfig(ctx context.Context, cfgSrc string, noDefaultDirs bool) (*Config, return cfg, nil } if cfg.Cache == "" { - cfg.Cache = filepath.Join(filepath.Dir(cfgSrc), ".cache") + cfg.Cache, err = findCacheDir(filepath.Dir(cfgSrc)) + if err != nil { + return nil, err + } } if cfg.InstallDir == "" { cfg.InstallDir = filepath.Join(filepath.Dir(cfgSrc), "bin") @@ -664,6 +667,37 @@ func NewConfig(ctx context.Context, cfgSrc string, noDefaultDirs bool) (*Config, return cfg, nil } +// findCacheDir decides between .bindown and .cache for the cache directory to use when +// none is specified. This is necessary because v4 mistakenly made .cache the default. +// We want to use .bindown, but will revert to .cache if it is in .gitignore and .bindown +// does not exist. +func findCacheDir(cfgDir string) (string, error) { + // if .bindown exists, use it + bindownDir := filepath.Join(cfgDir, ".bindown") + info, err := os.Stat(bindownDir) + if err != nil && !os.IsNotExist(err) { + return "", err + } + if err == nil && info.IsDir() { + return bindownDir, nil + } + // if .cache is ignored by .gitignore, use it + cacheDir := filepath.Join(cfgDir, ".cache") + // When a dir is ignored with a trailing slash, we need to check if a file in that dir is ignored. + for _, dir := range []string{cacheDir, filepath.Join(cacheDir, "downloads")} { + var ignored bool + ignored, err = fileIsGitignored(dir) + if err != nil { + return "", err + } + if ignored { + return cacheDir, nil + } + } + // default to .bindown + return bindownDir, nil +} + func configFromHTTP(ctx context.Context, src string) (*Config, error) { req, err := http.NewRequestWithContext(ctx, "GET", src, http.NoBody) if err != nil { diff --git a/internal/bindown/util.go b/internal/bindown/util.go index d8f15ca..8e399e8 100644 --- a/internal/bindown/util.go +++ b/internal/bindown/util.go @@ -15,6 +15,7 @@ import ( "text/template" "github.com/Masterminds/semver/v3" + ignore "github.com/sabhiram/go-gitignore" "golang.org/x/exp/maps" "golang.org/x/exp/slices" "gopkg.in/yaml.v3" @@ -228,3 +229,65 @@ func Unique[V comparable](vals, buf []V) []V { } return buf } + +// fileIsGitignored returns true if the file is ignored by a .gitignore file in the same directory or any parent +// directory from the same git repo. +func fileIsGitignored(filename string) (bool, error) { + dir := filepath.Dir(filename) + repoBase, err := gitRepo(dir) + if err != nil { + return false, err + } + if repoBase == "" { + return false, nil + } + for { + ignoreFile := filepath.Join(dir, ".gitignore") + var info os.FileInfo + info, err = os.Stat(ignoreFile) + if err != nil { + if !os.IsNotExist(err) { + return false, err + } + } else if info.Mode().Type().IsRegular() { + var ig *ignore.GitIgnore + ig, err = ignore.CompileIgnoreFile(ignoreFile) + if err != nil { + return false, err + } + var relFile string + relFile, err = filepath.Rel(dir, filename) + if err != nil { + return false, err + } + if ig.MatchesPath(relFile) { + return true, nil + } + } + if dir == repoBase { + break + } + dir = filepath.Dir(dir) + } + return false, nil +} + +// gitRepo returns the path of the base git repo for a dir. Returns "" +// if dir is not in a git repo. +// Does not use git commands. Just checks for .git directory. +func gitRepo(dir string) (string, error) { + dir = filepath.Clean(dir) + info, err := os.Stat(filepath.Join(dir, ".git")) + if err != nil { + if !os.IsNotExist(err) { + return "", err + } + } else if info.IsDir() { + return dir, nil + } + parent := filepath.Dir(dir) + if parent == dir { + return "", nil + } + return gitRepo(parent) +}