diff --git a/cmd/state/internal/cmdtree/checkout.go b/cmd/state/internal/cmdtree/checkout.go index bf7e7eb25b..39c863cc6d 100644 --- a/cmd/state/internal/cmdtree/checkout.go +++ b/cmd/state/internal/cmdtree/checkout.go @@ -34,6 +34,12 @@ func newCheckoutCommand(prime *primer.Values) *captain.Command { Description: locale.Tl("flag_state_checkout_no_clone_description", "Do not clone the github repository associated with this project (if any)"), Value: ¶ms.NoClone, }, + { + Name: "force", + Shorthand: "f", + Description: locale.Tl("flag_state_checkout_force", "Leave a failed project checkout on disk; do not delete it"), + Value: ¶ms.Force, + }, }, []*captain.Argument{ { diff --git a/internal/runners/checkout/checkout.go b/internal/runners/checkout/checkout.go index 379f288b3a..721343e914 100644 --- a/internal/runners/checkout/checkout.go +++ b/internal/runners/checkout/checkout.go @@ -1,11 +1,16 @@ package checkout import ( + "os" + "path/filepath" + "github.com/ActiveState/cli/internal/analytics" "github.com/ActiveState/cli/internal/config" "github.com/ActiveState/cli/internal/errs" "github.com/ActiveState/cli/internal/locale" "github.com/ActiveState/cli/internal/logging" + "github.com/ActiveState/cli/internal/multilog" + "github.com/ActiveState/cli/internal/osutils" "github.com/ActiveState/cli/internal/output" "github.com/ActiveState/cli/internal/primer" "github.com/ActiveState/cli/internal/runbits/checker" @@ -26,6 +31,7 @@ type Params struct { Branch string RuntimePath string NoClone bool + Force bool } type primeable interface { @@ -77,6 +83,28 @@ func (u *Checkout) Run(params *Params) (rerr error) { return locale.WrapError(err, "err_project_frompath") } + // If an error occurs, remove the created activestate.yaml file and/or directory. + if !params.Force { + defer func() { + if rerr == nil { + return + } + err := os.Remove(proj.Path()) + if err != nil { + multilog.Error("Failed to remove activestate.yaml after `state checkout` error: %v", err) + return + } + if cwd, err := osutils.Getwd(); err == nil { + if createdDir := filepath.Dir(proj.Path()); createdDir != cwd { + err2 := os.RemoveAll(createdDir) + if err2 != nil { + multilog.Error("Failed to remove created directory after `state checkout` error: %v", err2) + } + } + } + }() + } + rti, err := runtime.NewFromProject(proj, target.TriggerCheckout, u.analytics, u.svcModel, u.out, u.auth) if err != nil { return locale.WrapError(err, "err_checkout_runtime_new", "Could not checkout this project.") diff --git a/test/integration/checkout_int_test.go b/test/integration/checkout_int_test.go index f156810d32..f39eb0161c 100644 --- a/test/integration/checkout_int_test.go +++ b/test/integration/checkout_int_test.go @@ -297,6 +297,36 @@ func (suite *CheckoutIntegrationTestSuite) TestCheckoutBuildtimeClosure() { cp.ExpectExitCode(0) } +func (suite *CheckoutIntegrationTestSuite) TestFail() { + suite.OnlyRunForTags(tagsuite.Checkout) + ts := e2e.New(suite.T(), false) + defer ts.Close() + + cp := ts.SpawnWithOpts( + e2e.OptArgs("checkout", "ActiveState-CLI/fail"), + e2e.OptAppendEnv(constants.DisableRuntime+"=false"), + ) + cp.Expect("Something went wrong") + cp.ExpectNotExitCode(0) + suite.Assert().NoDirExists(filepath.Join(ts.Dirs.Work, "fail"), "state checkout fail did not remove created directory") + + cp = ts.SpawnWithOpts( + e2e.OptArgs("checkout", "ActiveState-CLI/fail", "."), + e2e.OptAppendEnv(constants.DisableRuntime+"=false"), + ) + cp.Expect("Something went wrong") + cp.ExpectNotExitCode(0) + suite.Assert().NoFileExists(filepath.Join(ts.Dirs.Work, constants.ConfigFileName), "state checkout fail did not remove created activestate.yaml") + + cp = ts.SpawnWithOpts( + e2e.OptArgs("checkout", "ActiveState-CLI/fail", "--force"), + e2e.OptAppendEnv(constants.DisableRuntime+"=false"), + ) + cp.Expect("Something went wrong") + cp.ExpectNotExitCode(0) + suite.Assert().DirExists(filepath.Join(ts.Dirs.Work, "fail"), "state checkout fail did not leave created directory there despite --force flag override") +} + func TestCheckoutIntegrationTestSuite(t *testing.T) { suite.Run(t, new(CheckoutIntegrationTestSuite)) }