Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 17 additions & 2 deletions cmd/state/internal/cmdtree/pull.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,14 +10,29 @@ import (
func newPullCommand(prime *primer.Values) *captain.Command {
runner := pull.New(prime)

params := &pull.PullParams{}

return captain.NewCommand(
"pull",
locale.Tl("pull_title", "Pulling Remote Project"),
locale.Tl("pull_description", "Pull in the latest version of your project from the ActiveState Platform"),
prime.Output(),
[]*captain.Flag{},
[]*captain.Flag{
{
Name: "force",
Shorthand: "",
Description: locale.Tl("flag_state_pull_force_description", "Force pulling specified project even if it is unrelated to checked out one"),
Value: &params.Force,
},
{
Name: "set-project",
Shorthand: "",
Description: locale.Tl("flag_state_pull_set_project_description", "project even if it is unrelated to checked out one"),
Value: &params.SetProject,
},
},
[]*captain.Argument{},
func(cmd *captain.Command, args []string) error {
return runner.Run()
return runner.Run(params)
}).SetGroup(VCSGroup)
}
81 changes: 73 additions & 8 deletions internal/runners/pull/pull.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,27 +6,38 @@ import (

"github.com/ActiveState/cli/internal/config"
"github.com/ActiveState/cli/internal/constants"
"github.com/ActiveState/cli/internal/errs"
"github.com/ActiveState/cli/internal/hail"
"github.com/ActiveState/cli/internal/locale"
"github.com/ActiveState/cli/internal/logging"
"github.com/ActiveState/cli/internal/output"
"github.com/ActiveState/cli/internal/primer"
"github.com/ActiveState/cli/internal/prompt"
"github.com/ActiveState/cli/pkg/platform/model"
"github.com/ActiveState/cli/pkg/project"
"github.com/go-openapi/strfmt"
)

type Pull struct {
prompt prompt.Prompter
project *project.Project
out output.Outputer
}

type PullParams struct {
Force bool
SetProject string
}

type primeable interface {
primer.Prompter
primer.Projecter
primer.Outputer
}

func New(prime primeable) *Pull {
return &Pull{
prime.Prompt(),
prime.Project(),
prime.Output(),
}
Expand All @@ -48,30 +59,48 @@ func (f *outputFormat) MarshalOutput(format output.Format) interface{} {
return f
}

func (p *Pull) Run() error {
func (p *Pull) Run(params *PullParams) error {
if p.project == nil {
return locale.NewInputError("err_no_project")
}

if p.project.IsHeadless() {
if p.project.IsHeadless() && params.SetProject == "" {
return locale.NewInputError("err_pull_headless", "You must first create a project. Please visit {{.V0}} to create your project.", p.project.URL())
}

// Retrieve latest commit ID on platform
latestID, err := model.LatestCommitID(p.project.Owner(), p.project.Name())
// Determine the project to pull from
target, err := targetProject(p.project, params.SetProject)
if err != nil {
return locale.WrapInputError(err, "err_pull_commit", "Could not retrieve the latest commit for your project.")
return errs.Wrap(err, "Unable to determine target project")
}

if params.SetProject != "" {
related, err := areCommitsRelated(*target.CommitID, p.project.CommitUUID())
if !related && !params.Force {
confirmed, err := p.prompt.Confirm(locale.T("confirm"), locale.Tl("confirm_unrelated_pull_set_project", "If you switch to {{.V0}}, you may lose changes to your project. Are you sure you want to do this?", target.String()), new(bool))
if err != nil {
return locale.WrapError(err, "err_pull_confirm", "Failed to get user confirmation to update project")
}
if !confirmed {
return locale.NewInputError("err_pull_aborted", "Pull aborted by user")
}
}

err = p.project.Source().SetNamespace(target.Owner, target.Project)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This seems to be redundant with line 97

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, thanks for catching. Line 97 should never have been there...

if err != nil {
return locale.WrapError(err, "err_pull_update_namespace", "Cannot update the namespace in your project file.")
}
}

// Update the commit ID in the activestate.yaml
if p.project.CommitID() != latestID.String() {
err := p.project.Source().SetCommit(latestID.String(), false)
if p.project.CommitID() != target.CommitID.String() {
err := p.project.Source().SetCommit(target.CommitID.String(), false)
if err != nil {
return locale.WrapError(err, "err_pull_update", "Cannot update the commit in your project file.")
}

p.out.Print(&outputFormat{
locale.T("pull_updated"),
locale.Tr("pull_updated", target.String(), target.CommitID.String()),
true,
})
} else {
Expand All @@ -96,3 +125,39 @@ func (p *Pull) Run() error {

return nil
}

func targetProject(prj *project.Project, overwrite string) (*project.Namespaced, error) {
ns := prj.Namespace()
if overwrite != "" {
var err error
ns, err = project.ParseNamespace(overwrite)
if err != nil {
return nil, locale.WrapInputError(err, "pull_set_project_parse_err", "Failed to parse namespace {{.V0}}", overwrite)
}
}

// Retrieve commit ID to set the project to (if unset)
if ns.CommitID == nil || *ns.CommitID == "" {
var err error
ns.CommitID, err = model.LatestCommitID(ns.Owner, ns.Project)
if err != nil {
return nil, locale.WrapError(err, "err_pull_commit", "Could not retrieve the latest commit for your project.")
}
}

return ns, nil
}

func areCommitsRelated(targetCommit strfmt.UUID, sourceCommmit strfmt.UUID) (bool, error) {
history, err := model.CommitHistoryFromID(targetCommit)
if err != nil {
return false, locale.WrapError(err, "err_pull_commit_history", "Could not fetch commit history for target project.")
}

for _, c := range history {
if sourceCommmit.String() == c.CommitID.String() {
return true, nil
}
}
return false, nil
}
4 changes: 2 additions & 2 deletions locale/en-us.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -639,7 +639,7 @@ secrets_description_unset:
pull_not_updated:
other: Your activestate.yaml is already up to date!
pull_updated:
other: Your activestate.yaml has been updated to the latest version available.
other: Your project in the activestate.yaml has been updated to {{.V0}}@{{.V1}}.
keypair_cmd_description:
other: Manage Your Keypair
keypair_generate_cmd_description:
Expand Down Expand Up @@ -1630,4 +1630,4 @@ package_ingredient_alternatives_more:
bundle_ingredient_alternatives_more:
other: " - .. (to see more results run `state bundles search {{.V0}}`)"
err_lock_version_invalid:
other: "The locked version '{{.V0}}' could not be verified, are you sure it's valid?"
other: "The locked version '{{.V0}}' could not be verified, are you sure it's valid?"
6 changes: 3 additions & 3 deletions test/integration/activate_int_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -244,7 +244,7 @@ version: %s
e2e.WithArgs("pull"),
e2e.AppendEnv("ACTIVESTATE_CLI_DISABLE_RUNTIME=false"),
)
cp.Expect("Your activestate.yaml has been updated to the latest version available")
cp.Expect("activestate.yaml has been updated to")
cp.ExpectExitCode(0)

c2 := ts.Spawn("activate")
Expand Down Expand Up @@ -363,7 +363,7 @@ version: %s

// Pull to ensure we have an up to date config file
cp := ts.Spawn("pull")
cp.Expect("Your activestate.yaml has been updated to the latest version available")
cp.Expect("activestate.yaml has been updated to")
cp.ExpectExitCode(0)

// Activate in the subdirectory
Expand Down Expand Up @@ -398,7 +398,7 @@ project: "https://platform.activestate.com/ActiveState-CLI/Python3"

// Pull to ensure we have an up to date config file
cp := ts.Spawn("pull")
cp.Expect("Your activestate.yaml has been updated to the latest version available")
cp.Expect("activestate.yaml has been updated to")
cp.ExpectExitCode(0)

// Activate in the subdirectory
Expand Down
31 changes: 31 additions & 0 deletions test/integration/pull_int_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,37 @@ func (suite *PullIntegrationTestSuite) TestPull() {
cp.ExpectExitCode(0)
}

func (suite *PullIntegrationTestSuite) TestPullSetProject() {
suite.OnlyRunForTags(tagsuite.Pull)
ts := e2e.New(suite.T(), false)
defer ts.Close()

ts.PrepareActiveStateYAML(`project: https://platform.activestate.com/ActiveState-CLI/small-python?commitID=9733d11a-dfb3-41de-a37a-843b7c421db4`)

// update to related project
cp := ts.Spawn("pull", "--set-project", "ActiveState-CLI/small-python-fork")
cp.Expect("activestate.yaml has been updated")
cp.ExpectExitCode(0)
}

func (suite *PullIntegrationTestSuite) TestPullSetProjectUnrelated() {
suite.OnlyRunForTags(tagsuite.Pull)
ts := e2e.New(suite.T(), false)
defer ts.Close()

ts.PrepareActiveStateYAML(`project: "https://platform.activestate.com/ActiveState-CLI/small-python?commitID=9733d11a-dfb3-41de-a37a-843b7c421db4"`)

cp := ts.Spawn("pull", "--set-project", "ActiveState-CLI/Python3")
cp.ExpectLongString("you may lose changes to your project")
cp.SendLine("n")
cp.Expect("Pull aborted by user")
cp.ExpectNotExitCode(0)

cp = ts.Spawn("pull", "--force", "--set-project", "ActiveState-CLI/Python3")
cp.Expect("activestate.yaml has been updated")
cp.ExpectExitCode(0)
}

func TestPullIntegrationTestSuite(t *testing.T) {
suite.Run(t, new(PullIntegrationTestSuite))
}