From 9a4a14e59d394b67d0492ca0e89fbe21dbb4344e Mon Sep 17 00:00:00 2001 From: Mike Landau Date: Mon, 29 May 2023 15:37:47 -0700 Subject: [PATCH 1/4] [pull] Implement pull --- internal/boxcli/global.go | 63 +--------------------- internal/boxcli/pull.go | 102 +++++++++++++++++++++++++++++++++++ internal/devconfig/config.go | 7 ++- internal/impl/global.go | 68 +++-------------------- internal/pullbox/config.go | 50 +++++++++++++++++ internal/pullbox/git/git.go | 82 ++++++++++++++++++++++++++++ internal/pullbox/pullbox.go | 54 +++++++++++-------- internal/pullbox/tar.go | 44 +++++++++++++-- 8 files changed, 317 insertions(+), 153 deletions(-) create mode 100644 internal/boxcli/pull.go create mode 100644 internal/pullbox/config.go create mode 100644 internal/pullbox/git/git.go diff --git a/internal/boxcli/global.go b/internal/boxcli/global.go index db57ab5e516..1ea751ea062 100644 --- a/internal/boxcli/global.go +++ b/internal/boxcli/global.go @@ -5,19 +5,13 @@ package boxcli import ( "fmt" - "io/fs" - "github.com/AlecAivazis/survey/v2" "github.com/pkg/errors" "github.com/spf13/cobra" "go.jetpack.io/devbox" "go.jetpack.io/devbox/internal/ux" ) -type globalPullCmdFlags struct { - force bool -} - func globalCmd() *cobra.Command { globalCmd := &cobra.Command{} @@ -31,6 +25,7 @@ func globalCmd() *cobra.Command { addCommandAndHideConfigFlag(globalCmd, addCmd()) addCommandAndHideConfigFlag(globalCmd, installCmd()) + addCommandAndHideConfigFlag(globalCmd, pullCmd()) addCommandAndHideConfigFlag(globalCmd, removeCmd()) addCommandAndHideConfigFlag(globalCmd, runCmd()) addCommandAndHideConfigFlag(globalCmd, servicesCmd()) @@ -39,7 +34,6 @@ func globalCmd() *cobra.Command { // Create list for non-global? Mike: I want it :) globalCmd.AddCommand(globalListCmd()) - globalCmd.AddCommand(globalPullCmd()) return globalCmd } @@ -59,27 +53,6 @@ func globalListCmd() *cobra.Command { } } -func globalPullCmd() *cobra.Command { - flags := globalPullCmdFlags{} - cmd := &cobra.Command{ - Use: "pull | ", - Short: "Pull a global config from a file or URL", - Long: "Pull a global config from a file or URL. URLs must be prefixed with 'http://' or 'https://'.", - PreRunE: ensureNixInstalled, - RunE: func(cmd *cobra.Command, args []string) error { - return pullGlobalCmdFunc(cmd, args, flags.force) - }, - Args: cobra.ExactArgs(1), - } - - cmd.Flags().BoolVarP( - &flags.force, "force", "f", false, - "Force overwrite of existing global config files", - ) - - return cmd -} - func listGlobalCmdFunc(cmd *cobra.Command, args []string) error { path, err := ensureGlobalConfig(cmd) if err != nil { @@ -93,40 +66,6 @@ func listGlobalCmdFunc(cmd *cobra.Command, args []string) error { return box.PrintGlobalList() } -func pullGlobalCmdFunc( - cmd *cobra.Command, - args []string, - overwrite bool, -) error { - path, err := ensureGlobalConfig(cmd) - if err != nil { - return errors.WithStack(err) - } - - box, err := devbox.Open(path, cmd.ErrOrStderr()) - if err != nil { - return errors.WithStack(err) - } - err = box.PullGlobal(cmd.Context(), overwrite, args[0]) - if errors.Is(err, fs.ErrExist) { - prompt := &survey.Confirm{ - Message: "File(s) already exists. Overwrite?", - } - if err = survey.AskOne(prompt, &overwrite); err != nil { - return errors.WithStack(err) - } - if !overwrite { - return nil - } - err = box.PullGlobal(cmd.Context(), overwrite, args[0]) - } - if err != nil { - return err - } - - return installCmdFunc(cmd, runCmdFlags{config: configFlags{path: path}}) -} - var globalConfigPath string func ensureGlobalConfig(cmd *cobra.Command) (string, error) { diff --git a/internal/boxcli/pull.go b/internal/boxcli/pull.go new file mode 100644 index 00000000000..e90cda6330c --- /dev/null +++ b/internal/boxcli/pull.go @@ -0,0 +1,102 @@ +// Copyright 2023 Jetpack Technologies Inc and contributors. All rights reserved. +// Use of this source code is governed by the license in the LICENSE file. + +package boxcli + +import ( + "io/fs" + "os" + "path/filepath" + + "github.com/AlecAivazis/survey/v2" + "github.com/pkg/errors" + "github.com/spf13/cobra" + "go.jetpack.io/devbox" + "go.jetpack.io/devbox/internal/pullbox/git" +) + +type pullCmdFlags struct { + config configFlags + force bool +} + +func pullCmd() *cobra.Command { + flags := pullCmdFlags{} + cmd := &cobra.Command{ + Use: "pull | ", + Short: "Pull a config from a file or URL", + Long: "Pull a config from a file or URL. URLs must be prefixed with 'http://' or 'https://'.", + PreRunE: ensureNixInstalled, + RunE: func(cmd *cobra.Command, args []string) error { + return pullCmdFunc(cmd, args, flags.force) + }, + Args: cobra.ExactArgs(1), + } + + cmd.Flags().BoolVarP( + &flags.force, "force", "f", false, + "Force overwrite of existing global config files", + ) + + flags.config.register(cmd) + + return cmd +} + +func pullCmdFunc( + cmd *cobra.Command, + args []string, + overwrite bool, +) error { + path, err := ensureGlobalConfig(cmd) + if err != nil { + return errors.WithStack(err) + } + + box, err := devbox.Open(path, cmd.ErrOrStderr()) + if err != nil { + return errors.WithStack(err) + } + + pullPath, err := absolutizeIfLocal(args[0]) + if err != nil { + return errors.WithStack(err) + } + + err = box.PullGlobal(cmd.Context(), overwrite, pullPath) + if prompt := pullErrorPrompt(err); prompt != "" { + prompt := &survey.Confirm{Message: prompt} + if err = survey.AskOne(prompt, &overwrite); err != nil { + return errors.WithStack(err) + } + if !overwrite { + return nil + } + err = box.PullGlobal(cmd.Context(), overwrite, pullPath) + } + if err != nil { + return err + } + + return installCmdFunc(cmd, runCmdFlags{config: configFlags{path: path}}) +} + +func pullErrorPrompt(err error) string { + switch { + case errors.Is(err, fs.ErrExist): + return "File(s) already exists. Overwrite?" + case errors.Is(err, git.ErrExist): + return "Directory is not empty. Overwrite?" + case errors.Is(err, git.ErrUncommittedChanges): + return "Uncommitted changes. Overwrite?" + default: + return "" + } +} + +func absolutizeIfLocal(path string) (string, error) { + if _, err := os.Stat(path); err == nil { + return filepath.Abs(path) + } + return path, nil +} diff --git a/internal/devconfig/config.go b/internal/devconfig/config.go index 9021d06363c..aed3617546b 100644 --- a/internal/devconfig/config.go +++ b/internal/devconfig/config.go @@ -6,7 +6,6 @@ package devconfig import ( "io" "net/http" - "net/url" "path/filepath" "regexp" "strings" @@ -127,8 +126,8 @@ func Load(path string) (*Config, error) { return cfg, validateConfig(cfg) } -func LoadConfigFromURL(url *url.URL) (*Config, error) { - res, err := http.Get(url.String()) +func LoadConfigFromURL(url string) (*Config, error) { + res, err := http.Get(url) if err != nil { return nil, errors.WithStack(err) } @@ -138,7 +137,7 @@ func LoadConfigFromURL(url *url.URL) (*Config, error) { if err != nil { return nil, errors.WithStack(err) } - ext := filepath.Ext(url.Path) + ext := filepath.Ext(url) if !cuecfg.IsSupportedExtension(ext) { ext = ".json" } diff --git a/internal/impl/global.go b/internal/impl/global.go index efc6926ce34..6e55b5ccdb0 100644 --- a/internal/impl/global.go +++ b/internal/impl/global.go @@ -7,15 +7,11 @@ import ( "context" "fmt" "io/fs" - "net/url" "os" "path/filepath" - "strings" "github.com/pkg/errors" - "github.com/samber/lo" - "go.jetpack.io/devbox/internal/devconfig" "go.jetpack.io/devbox/internal/pullbox" "go.jetpack.io/devbox/internal/xdg" ) @@ -28,11 +24,13 @@ func (d *Devbox) PullGlobal( force bool, path string, ) error { - u, err := url.Parse(path) - if err == nil && u.Scheme != "" { - return d.pullGlobalFromURL(ctx, force, u) - } - return d.pullGlobalFromPath(ctx, path) + // u, err := url.Parse(path) + // if (err == nil && u.Scheme != "") || git.IsRepoURL(path) { + // return d.pullGlobalFromURL(ctx, force, path) + // } + // return d.pullGlobalFromPath(ctx, path) + fmt.Fprintf(d.writer, "Pulling global config from %s\n", path) + return pullbox.New(d, path, force).Pull() } func (d *Devbox) PrintGlobalList() error { @@ -42,58 +40,6 @@ func (d *Devbox) PrintGlobalList() error { return nil } -func (d *Devbox) pullGlobalFromURL( - ctx context.Context, - overwrite bool, - configURL *url.URL, -) error { - fmt.Fprintf(d.writer, "Pulling global config from %s\n", configURL) - puller := pullbox.New() - if ok, err := puller.URLIsArchive(configURL.String()); ok { - fmt.Fprintf( - d.writer, - "%s is an archive, extracting to %s\n", - configURL, - d.ProjectDir(), - ) - return puller.DownloadAndExtract( - overwrite, - configURL.String(), - d.projectDir, - ) - } else if err != nil { - return err - } - cfg, err := devconfig.LoadConfigFromURL(configURL) - if err != nil { - return err - } - return d.addFromPull(ctx, cfg) -} - -func (d *Devbox) pullGlobalFromPath(ctx context.Context, path string) error { - fmt.Fprintf(d.writer, "Pulling global config from %s\n", path) - cfg, err := devconfig.Load(path) - if err != nil { - return err - } - return d.addFromPull(ctx, cfg) -} - -func (d *Devbox) addFromPull(ctx context.Context, cfg *devconfig.Config) error { - diff, _ := lo.Difference(cfg.Packages, d.cfg.Packages) - if len(diff) == 0 { - fmt.Fprint(d.writer, "No new packages to install\n") - return nil - } - fmt.Fprintf( - d.writer, - "Installing the following packages: %s\n", - strings.Join(diff, ", "), - ) - return d.Add(ctx, diff...) -} - func GlobalDataPath() (string, error) { path := xdg.DataSubpath(filepath.Join("devbox/global", currentGlobalProfile)) if err := os.MkdirAll(path, 0755); err != nil { diff --git a/internal/pullbox/config.go b/internal/pullbox/config.go new file mode 100644 index 00000000000..7e25e72dcb5 --- /dev/null +++ b/internal/pullbox/config.go @@ -0,0 +1,50 @@ +// Copyright 2023 Jetpack Technologies Inc and contributors. All rights reserved. +// Use of this source code is governed by the license in the LICENSE file. + +package pullbox + +import ( + "net/url" + "os" + "path/filepath" + + "github.com/pkg/errors" + "go.jetpack.io/devbox/internal/cuecfg" + "go.jetpack.io/devbox/internal/devconfig" +) + +func (p *pullbox) IsTextDevboxConfig() bool { + if u, err := url.Parse(p.url); err == nil { + ext := filepath.Ext(u.Path) + return cuecfg.IsSupportedExtension(ext) + } + // For invalid URLS, just look at the extension + ext := filepath.Ext(p.url) + return cuecfg.IsSupportedExtension(ext) +} + +func (p *pullbox) pullTextDevboxConfig() error { + if p.isLocalConfig() { + return p.copy(p.overwrite, p.url, p.ProjectDir()) + } + + cfg, err := devconfig.LoadConfigFromURL(p.url) + if err != nil { + return err + } + + tmpDir, err := os.MkdirTemp("", "devbox") + if err != nil { + return errors.WithStack(err) + } + if err = cfg.SaveTo(tmpDir); err != nil { + return err + } + + return p.copy(p.overwrite, tmpDir, p.ProjectDir()) +} + +func (p *pullbox) isLocalConfig() bool { + _, err := os.Stat(p.url) + return err == nil +} diff --git a/internal/pullbox/git/git.go b/internal/pullbox/git/git.go new file mode 100644 index 00000000000..b15a4a3aa51 --- /dev/null +++ b/internal/pullbox/git/git.go @@ -0,0 +1,82 @@ +// Copyright 2023 Jetpack Technologies Inc and contributors. All rights reserved. +// Use of this source code is governed by the license in the LICENSE file. + +package git + +import ( + "bytes" + "io" + "os" + "os/exec" + "path/filepath" + "strings" + + "github.com/pkg/errors" +) + +const alreadyExists = "already exists and is not an empty directory" +const uncommittedChanges = "Please commit your changes or stash them before you merge" + +var ErrExist = errors.New(alreadyExists) +var ErrUncommittedChanges = errors.New("uncommitted changes") + +func CloneOrPull(repo, dir string, overwrite bool) error { + if isDirRepoRoot(dir) { + return pull(dir, overwrite) + } + return clone(repo, dir, overwrite) +} + +func isDirRepoRoot(dir string) bool { + _, err := os.Stat(filepath.Join(dir, ".git")) + return err == nil +} + +func IsRepoURL(url string) bool { + // For now only support ssh + return strings.HasPrefix(url, "git@") +} + +func clone(repo, dir string, overwrite bool) error { + if overwrite { + _ = os.RemoveAll(dir) + _ = os.MkdirAll(dir, 0755) + } + + cmd := exec.Command("git", "clone", repo, dir) + buf := bytes.NewBuffer(nil) + cmd.Stderr = io.MultiWriter(os.Stderr, buf) + cmd.Stdout = os.Stdout + cmd.Dir = dir + err := cmd.Run() + if strings.Contains(buf.String(), alreadyExists) { + return ErrExist + } + return errors.WithStack(err) +} + +func pull(dir string, overwrite bool) error { + if overwrite { + if err := reset(dir); err != nil { + return err + } + } + cmd := exec.Command("git", "pull") + buf := bytes.NewBuffer(nil) + cmd.Stderr = io.MultiWriter(os.Stderr, buf) + cmd.Stdout = os.Stdout + cmd.Dir = dir + err := cmd.Run() + if strings.Contains(buf.String(), uncommittedChanges) { + return ErrUncommittedChanges + } + return errors.WithStack(err) +} + +func reset(dir string) error { + cmd := exec.Command("git", "reset", "--hard") + cmd.Stderr = os.Stderr + cmd.Stdout = os.Stdout + cmd.Dir = dir + return errors.WithStack(cmd.Run()) +} diff --git a/internal/pullbox/pullbox.go b/internal/pullbox/pullbox.go index 2f5e5231554..4dbdd6830ba 100644 --- a/internal/pullbox/pullbox.go +++ b/internal/pullbox/pullbox.go @@ -4,39 +4,47 @@ package pullbox import ( - "net/http" - "strings" + "go.jetpack.io/devbox/internal/boxcli/usererr" + "go.jetpack.io/devbox/internal/pullbox/git" ) +type devboxProject interface { + ProjectDir() string +} + type pullbox struct { + devboxProject + overwrite bool + url string } -func New() *pullbox { - return &pullbox{} +func New(devbox devboxProject, url string, overwrite bool) *pullbox { + return &pullbox{devbox, overwrite, url} } -func (p *pullbox) DownloadAndExtract(overwrite bool, url, target string) error { - data, err := download(url) - if err != nil { - return err +func (p *pullbox) Pull() error { + if git.IsRepoURL(p.url) { + return git.CloneOrPull(p.url, p.ProjectDir(), p.overwrite) } - tmpDir, err := extract(data) - if err != nil { - return err + + if p.IsTextDevboxConfig() { + return p.pullTextDevboxConfig() } - return p.copy(overwrite, tmpDir, target) -} + if isArchive, err := urlIsArchive(p.url); err != nil { + return err + } else if isArchive { + data, err := download(p.url) + if err != nil { + return err + } + tmpDir, err := extract(data) + if err != nil { + return err + } -// URLIsArchive checks if a file URL points to an archive file -func (p *pullbox) URLIsArchive(url string) (bool, error) { - response, err := http.Head(url) - if err != nil { - return false, err + return p.copy(p.overwrite, tmpDir, p.ProjectDir()) } - defer response.Body.Close() - contentType := response.Header.Get("Content-Type") - return strings.Contains(contentType, "tar") || - strings.Contains(contentType, "zip") || - strings.Contains(contentType, "octet-stream"), nil + + return usererr.New("Could not determine how to pull %s", p.url) } diff --git a/internal/pullbox/tar.go b/internal/pullbox/tar.go index 963aab8c358..c8007372ffe 100644 --- a/internal/pullbox/tar.go +++ b/internal/pullbox/tar.go @@ -6,9 +6,11 @@ package pullbox import ( "fmt" "io/fs" + "net/http" "os" "os/exec" "path/filepath" + "strings" "syscall" "github.com/pkg/errors" @@ -52,11 +54,28 @@ func extract(data []byte) (string, error) { } func (p *pullbox) copy(overwrite bool, src, dst string) error { - srcFiles, err := os.ReadDir(src) + srcFileInfo, err := os.Stat(src) if err != nil { return errors.WithStack(err) } + var srcFiles []fs.FileInfo + if srcFileInfo.IsDir() { + entries, err := os.ReadDir(src) + if err != nil { + return errors.WithStack(err) + } + for _, entry := range entries { + info, err := entry.Info() + if err != nil { + return errors.WithStack(err) + } + srcFiles = append(srcFiles, info) + } + } else { + srcFiles = []fs.FileInfo{srcFileInfo} + } + if !overwrite { for _, srcFile := range srcFiles { dstPath := filepath.Join(dst, srcFile.Name()) @@ -68,8 +87,14 @@ func (p *pullbox) copy(overwrite bool, src, dst string) error { } for _, srcFile := range srcFiles { - srcPath := filepath.Join(src, srcFile.Name()) - if err := exec.Command("cp", "-rf", srcPath, dst).Run(); err != nil { + srcPath := src + if srcFileInfo.IsDir() { + srcPath = filepath.Join(src, srcFile.Name()) + } + cmd := exec.Command("cp", "-rf", srcPath, dst) + cmd.Stderr = os.Stderr + cmd.Stdout = os.Stdout + if err := cmd.Run(); err != nil { return err } } @@ -86,3 +111,16 @@ func isModifiedConfig(path string) bool { } return false } + +// urlIsArchive checks if a file URL points to an archive file +func urlIsArchive(url string) (bool, error) { + response, err := http.Head(url) + if err != nil { + return false, err + } + defer response.Body.Close() + contentType := response.Header.Get("Content-Type") + return strings.Contains(contentType, "tar") || + strings.Contains(contentType, "zip") || + strings.Contains(contentType, "octet-stream"), nil +} From 7c416926c78a97ec307b82924228509491e7b5cb Mon Sep 17 00:00:00 2001 From: Mike Landau Date: Tue, 30 May 2023 10:52:58 -0700 Subject: [PATCH 2/4] Remove commented code --- internal/impl/global.go | 5 ----- 1 file changed, 5 deletions(-) diff --git a/internal/impl/global.go b/internal/impl/global.go index 6e55b5ccdb0..2a5014e0670 100644 --- a/internal/impl/global.go +++ b/internal/impl/global.go @@ -24,11 +24,6 @@ func (d *Devbox) PullGlobal( force bool, path string, ) error { - // u, err := url.Parse(path) - // if (err == nil && u.Scheme != "") || git.IsRepoURL(path) { - // return d.pullGlobalFromURL(ctx, force, path) - // } - // return d.pullGlobalFromPath(ctx, path) fmt.Fprintf(d.writer, "Pulling global config from %s\n", path) return pullbox.New(d, path, force).Pull() } From f9346b9e3c69191336074c4c73fbc6fceea85eb1 Mon Sep 17 00:00:00 2001 From: Mike Landau Date: Wed, 31 May 2023 15:45:42 -0700 Subject: [PATCH 3/4] Don't create local git repo --- devbox.go | 2 +- internal/boxcli/pull.go | 37 +++++++++----------- internal/impl/global.go | 2 +- internal/pullbox/git/git.go | 67 +++++++------------------------------ internal/pullbox/pullbox.go | 6 +++- internal/pullbox/tar.go | 6 ++++ 6 files changed, 41 insertions(+), 79 deletions(-) diff --git a/devbox.go b/devbox.go index 18e7dced796..613a87b498a 100644 --- a/devbox.go +++ b/devbox.go @@ -34,7 +34,7 @@ type Devbox interface { PrintEnv(ctx context.Context, includeHooks bool) (string, error) PrintGlobalList() error PrintEnvrcContent(w io.Writer) error - PullGlobal(ctx context.Context, overwrite bool, path string) error + Pull(ctx context.Context, overwrite bool, path string) error // Remove removes Nix packages from the config so that it no longer exists in // the devbox environment. Remove(ctx context.Context, pkgs ...string) error diff --git a/internal/boxcli/pull.go b/internal/boxcli/pull.go index e90cda6330c..4f5188731df 100644 --- a/internal/boxcli/pull.go +++ b/internal/boxcli/pull.go @@ -12,7 +12,6 @@ import ( "github.com/pkg/errors" "github.com/spf13/cobra" "go.jetpack.io/devbox" - "go.jetpack.io/devbox/internal/pullbox/git" ) type pullCmdFlags struct { @@ -28,14 +27,14 @@ func pullCmd() *cobra.Command { Long: "Pull a config from a file or URL. URLs must be prefixed with 'http://' or 'https://'.", PreRunE: ensureNixInstalled, RunE: func(cmd *cobra.Command, args []string) error { - return pullCmdFunc(cmd, args, flags.force) + return pullCmdFunc(cmd, args[0], &flags) }, Args: cobra.ExactArgs(1), } cmd.Flags().BoolVarP( &flags.force, "force", "f", false, - "Force overwrite of existing global config files", + "Force overwrite of existing [global] config files", ) flags.config.register(cmd) @@ -45,50 +44,44 @@ func pullCmd() *cobra.Command { func pullCmdFunc( cmd *cobra.Command, - args []string, - overwrite bool, + url string, + flags *pullCmdFlags, ) error { - path, err := ensureGlobalConfig(cmd) + box, err := devbox.Open(flags.config.path, cmd.ErrOrStderr()) if err != nil { return errors.WithStack(err) } - box, err := devbox.Open(path, cmd.ErrOrStderr()) + pullPath, err := absolutizeIfLocal(url) if err != nil { return errors.WithStack(err) } - pullPath, err := absolutizeIfLocal(args[0]) - if err != nil { - return errors.WithStack(err) - } - - err = box.PullGlobal(cmd.Context(), overwrite, pullPath) + err = box.Pull(cmd.Context(), flags.force, pullPath) if prompt := pullErrorPrompt(err); prompt != "" { prompt := &survey.Confirm{Message: prompt} - if err = survey.AskOne(prompt, &overwrite); err != nil { + if err = survey.AskOne(prompt, &flags.force); err != nil { return errors.WithStack(err) } - if !overwrite { + if !flags.force { return nil } - err = box.PullGlobal(cmd.Context(), overwrite, pullPath) + err = box.Pull(cmd.Context(), flags.force, pullPath) } if err != nil { return err } - return installCmdFunc(cmd, runCmdFlags{config: configFlags{path: path}}) + return installCmdFunc( + cmd, + runCmdFlags{config: configFlags{path: flags.config.path}}, + ) } func pullErrorPrompt(err error) string { switch { case errors.Is(err, fs.ErrExist): - return "File(s) already exists. Overwrite?" - case errors.Is(err, git.ErrExist): - return "Directory is not empty. Overwrite?" - case errors.Is(err, git.ErrUncommittedChanges): - return "Uncommitted changes. Overwrite?" + return "Global profile already exists. Overwrite?" default: return "" } diff --git a/internal/impl/global.go b/internal/impl/global.go index 2a5014e0670..b5c20188b91 100644 --- a/internal/impl/global.go +++ b/internal/impl/global.go @@ -19,7 +19,7 @@ import ( // In the future we will support multiple global profiles const currentGlobalProfile = "default" -func (d *Devbox) PullGlobal( +func (d *Devbox) Pull( ctx context.Context, force bool, path string, diff --git a/internal/pullbox/git/git.go b/internal/pullbox/git/git.go index b15a4a3aa51..172aa19ff9b 100644 --- a/internal/pullbox/git/git.go +++ b/internal/pullbox/git/git.go @@ -4,8 +4,6 @@ package git import ( - "bytes" - "io" "os" "os/exec" "path/filepath" @@ -14,22 +12,18 @@ import ( "github.com/pkg/errors" ) -const alreadyExists = "already exists and is not an empty directory" -const uncommittedChanges = "Please commit your changes or stash them before you merge" - -var ErrExist = errors.New(alreadyExists) -var ErrUncommittedChanges = errors.New("uncommitted changes") - -func CloneOrPull(repo, dir string, overwrite bool) error { - if isDirRepoRoot(dir) { - return pull(dir, overwrite) +func CloneToTmp(repo string) (string, error) { + tmpDir, err := os.MkdirTemp("", "devbox") + if err != nil { + return "", errors.WithStack(err) } - return clone(repo, dir, overwrite) -} - -func isDirRepoRoot(dir string) bool { - _, err := os.Stat(filepath.Join(dir, ".git")) - return err == nil + if err := clone(repo, tmpDir); err != nil { + return "", errors.WithStack(err) + } + if err := os.RemoveAll(filepath.Join(tmpDir, ".git")); err != nil { + return "", errors.WithStack(err) + } + return tmpDir, nil } func IsRepoURL(url string) bool { @@ -37,46 +31,11 @@ func IsRepoURL(url string) bool { return strings.HasPrefix(url, "git@") } -func clone(repo, dir string, overwrite bool) error { - if overwrite { - _ = os.RemoveAll(dir) - _ = os.MkdirAll(dir, 0755) - } - +func clone(repo, dir string) error { cmd := exec.Command("git", "clone", repo, dir) - buf := bytes.NewBuffer(nil) - cmd.Stderr = io.MultiWriter(os.Stderr, buf) - cmd.Stdout = os.Stdout - cmd.Dir = dir - err := cmd.Run() - if strings.Contains(buf.String(), alreadyExists) { - return ErrExist - } - return errors.WithStack(err) -} - -func pull(dir string, overwrite bool) error { - if overwrite { - if err := reset(dir); err != nil { - return err - } - } - cmd := exec.Command("git", "pull") - buf := bytes.NewBuffer(nil) - cmd.Stderr = io.MultiWriter(os.Stderr, buf) + cmd.Stderr = os.Stderr cmd.Stdout = os.Stdout cmd.Dir = dir err := cmd.Run() - if strings.Contains(buf.String(), uncommittedChanges) { - return ErrUncommittedChanges - } return errors.WithStack(err) } - -func reset(dir string) error { - cmd := exec.Command("git", "reset", "--hard") - cmd.Stderr = os.Stderr - cmd.Stdout = os.Stdout - cmd.Dir = dir - return errors.WithStack(cmd.Run()) -} diff --git a/internal/pullbox/pullbox.go b/internal/pullbox/pullbox.go index 4dbdd6830ba..442767906e0 100644 --- a/internal/pullbox/pullbox.go +++ b/internal/pullbox/pullbox.go @@ -24,7 +24,11 @@ func New(devbox devboxProject, url string, overwrite bool) *pullbox { func (p *pullbox) Pull() error { if git.IsRepoURL(p.url) { - return git.CloneOrPull(p.url, p.ProjectDir(), p.overwrite) + tmpDir, err := git.CloneToTmp(p.url) + if err != nil { + return err + } + return p.copy(p.overwrite, tmpDir, p.ProjectDir()) } if p.IsTextDevboxConfig() { diff --git a/internal/pullbox/tar.go b/internal/pullbox/tar.go index c8007372ffe..177e9a02cfb 100644 --- a/internal/pullbox/tar.go +++ b/internal/pullbox/tar.go @@ -86,6 +86,12 @@ func (p *pullbox) copy(overwrite bool, src, dst string) error { } } + if overwrite { + if err := os.RemoveAll(dst); err != nil { + return errors.WithStack(err) + } + } + for _, srcFile := range srcFiles { srcPath := src if srcFileInfo.IsDir() { From 3d63a1962dc6e72de46254636a41804bb4e87d83 Mon Sep 17 00:00:00 2001 From: Mike Landau Date: Wed, 31 May 2023 16:00:43 -0700 Subject: [PATCH 4/4] Recreate folder if overwrite --- internal/pullbox/tar.go | 3 +++ 1 file changed, 3 insertions(+) diff --git a/internal/pullbox/tar.go b/internal/pullbox/tar.go index 177e9a02cfb..4546d0b70bc 100644 --- a/internal/pullbox/tar.go +++ b/internal/pullbox/tar.go @@ -90,6 +90,9 @@ func (p *pullbox) copy(overwrite bool, src, dst string) error { if err := os.RemoveAll(dst); err != nil { return errors.WithStack(err) } + if err := os.MkdirAll(dst, 0755); err != nil { + return errors.WithStack(err) + } } for _, srcFile := range srcFiles {