diff --git a/internal/runners/push/push.go b/internal/runners/push/push.go index 463f29de57..9d798a0888 100644 --- a/internal/runners/push/push.go +++ b/internal/runners/push/push.go @@ -148,6 +148,10 @@ func (r *Push) Run(params PushParams) (rerr error) { } } + bp := model.NewBuildPlannerModel(r.auth) + var newCommitID strfmt.UUID // the commit to update localcommit with + var branch *mono_models.Branch // the branch to write to as.yaml if it changed + // Create remote project var projectCreated bool if intend&intendCreateProject > 0 || targetPjm == nil { @@ -170,61 +174,93 @@ func (r *Push) Run(params PushParams) (rerr error) { } r.out.Notice(locale.Tl("push_creating_project", "Creating project [NOTICE]{{.V1}}[/RESET] under [NOTICE]{{.V0}}[/RESET] on the ActiveState Platform", targetNamespace.Owner, targetNamespace.Project)) - targetPjm, err = model.CreateEmptyProject(targetNamespace.Owner, targetNamespace.Project, r.project.Private()) + + // Create a new project with the current project's buildexpression. + expr, err := bp.GetBuildExpression(r.project.Owner(), r.project.Name(), commitID.String()) if err != nil { - return errs.Wrap(err, "Failed to create project %s", r.project.Namespace().String()) + return errs.Wrap(err, "Could not get buildexpression") + } + newCommitID, err = bp.CreateProject(&model.CreateProjectParams{ + Owner: targetNamespace.Owner, + Project: targetNamespace.Project, + Private: r.project.Private(), + Description: locale.T("commit_message_add_initial"), + Expr: expr, + }) + if err != nil { + return locale.WrapError(err, "err_push_create_project", "Could not create new project") } - projectCreated = true - } - - // Now we get to the actual push logic - r.out.Notice(locale.Tl("push_to_project", "Pushing to project [NOTICE]{{.V1}}[/RESET] under [NOTICE]{{.V0}}[/RESET].", targetNamespace.Owner, targetNamespace.Project)) - - // Detect the target branch - var branch *mono_models.Branch - if projectCreated || r.project.BranchName() == "" { - // https://www.pivotaltracker.com/story/show/176806415 - // If we have created an empty project the only existing branch will be the default one + // Fetch the newly created project's default branch (for updating activestate.yaml with). + targetPjm, err = model.LegacyFetchProjectByName(targetNamespace.Owner, targetNamespace.Project) + if err != nil { + return errs.Wrap(err, "Failed to fetch newly created project") + } branch, err = model.DefaultBranchForProject(targetPjm) if err != nil { return errs.Wrap(err, "Project has no default branch") } + + projectCreated = true + } else { - branch, err = model.BranchForProjectByName(targetPjm, r.project.BranchName()) - if err != nil { - return errs.Wrap(err, "Could not get branch %s", r.project.BranchName()) - } - } - // Check if branch is already up to date - if branch.CommitID != nil && branch.CommitID.String() == commitID.String() { - return errNoChanges - } + // Now we get to the actual push logic + r.out.Notice(locale.Tl("push_to_project", "Pushing to project [NOTICE]{{.V1}}[/RESET] under [NOTICE]{{.V0}}[/RESET].", targetNamespace.Owner, targetNamespace.Project)) - // Check whether there is a conflict - if branch.CommitID != nil { - mergeStrategy, err := model.MergeCommit(*branch.CommitID, commitID) - if err != nil { - if errors.Is(err, model.ErrMergeCommitInHistory) { - return errNoChanges + // Detect the target branch + if r.project.BranchName() == "" { + branch, err = model.DefaultBranchForProject(targetPjm) + if err != nil { + return errs.Wrap(err, "Project has no default branch") + } + } else { + branch, err = model.BranchForProjectByName(targetPjm, r.project.BranchName()) + if err != nil { + return errs.Wrap(err, "Could not get branch %s", r.project.BranchName()) } - if !errors.Is(err, model.ErrMergeFastForward) { - if params.Namespace.IsValid() { - return errTargetInvalidHistory + } + + // Check if branch is already up to date + if branch.CommitID != nil && branch.CommitID.String() == commitID.String() { + return errNoChanges + } + + // Check whether there is a conflict + if branch.CommitID != nil { + mergeStrategy, err := model.MergeCommit(*branch.CommitID, commitID) + if err != nil { + if errors.Is(err, model.ErrMergeCommitInHistory) { + return errNoChanges } - return errs.Wrap(err, "Could not detect if merge is necessary") + if !errors.Is(err, model.ErrMergeFastForward) { + if params.Namespace.IsValid() { + return errTargetInvalidHistory + } + return errs.Wrap(err, "Could not detect if merge is necessary") + } + } + if mergeStrategy != nil { + return errPullNeeded } } - if mergeStrategy != nil { - return errPullNeeded + + // Perform the push. + newCommitID, err = bp.AttachStagedCommit(&model.AttachStagedCommitParams{ + Owner: targetNamespace.Owner, + Project: targetNamespace.Project, + ParentCommitID: *branch.CommitID, + StagedCommitID: commitID, + Branch: branch.BranchID.String(), + }) + if err != nil { + return errs.Wrap(err, "Could not attach staged commit") } } - // Update the project at the given commit id. - err = model.UpdateProjectBranchCommitWithModel(targetPjm, branch.Label, commitID) - if err != nil { - return errs.Wrap(err, "Failed to update new project %s to current commitID", targetNamespace.String()) + // Update the project's commitID with the create project or push result. + if err := localcommit.Set(r.project.Dir(), newCommitID.String()); err != nil { + return errs.Wrap(err, "Unable to create local commit file") } // Write the project namespace to the as.yaml, if it changed diff --git a/pkg/platform/api/buildplanner/model/buildplan.go b/pkg/platform/api/buildplanner/model/buildplan.go index 303e84298d..3217f41411 100644 --- a/pkg/platform/api/buildplanner/model/buildplan.go +++ b/pkg/platform/api/buildplanner/model/buildplan.go @@ -380,6 +380,12 @@ type CreateProjectResult struct { ProjectCreated *projectCreated `json:"createProject"` } +type AttachStagedCommitResult struct { + Type string `json:"__typename"` + Commit *Commit `json:"attachStagedCommit"` + *Error +} + // Error contains an error message. type Error struct { Message string `json:"message"` diff --git a/pkg/platform/api/buildplanner/request/attachstagedcommit.go b/pkg/platform/api/buildplanner/request/attachstagedcommit.go new file mode 100644 index 0000000000..7ff8204cd3 --- /dev/null +++ b/pkg/platform/api/buildplanner/request/attachstagedcommit.go @@ -0,0 +1,56 @@ +package request + +func AttachStagedCommit(owner, project, parentCommit, commit, branch string) *attachStagedCommit { + return &attachStagedCommit{map[string]interface{}{ + "organization": owner, + "project": project, + "parentCommit": parentCommit, + "commit": commit, + "branch": branch, + }} +} + +type attachStagedCommit struct { + vars map[string]interface{} +} + +func (b *attachStagedCommit) Query() string { + return ` +mutation ($organization: String!, $project: String!, $parentCommit: ID!, $commit: ID!, $branch: String!) { + attachStagedCommit(input:{organization:$organization, project:$project, parentCommitId:$parentCommit, stagedCommitId:$commit, branchRef:$branch}) { + ... on Commit { + __typename + commitId + } + ... on NotFound { + __typename + message + mayNeedAuthentication + } + ... on ParseError { + __typename + message + } + ... on Forbidden { + __typename + message + } + ... on HeadOnBranchMoved { + __typename + message + commitId + branchId + } + ... on NoChangeSinceLastCommit { + __typename + message + commitId + } + } +} +` +} + +func (b *attachStagedCommit) Vars() map[string]interface{} { + return b.vars +} diff --git a/pkg/platform/model/buildplanner.go b/pkg/platform/model/buildplanner.go index 6237d5410a..8507911c77 100644 --- a/pkg/platform/model/buildplanner.go +++ b/pkg/platform/model/buildplanner.go @@ -297,6 +297,38 @@ func (bp *BuildPlanner) StageCommit(params StageCommitParams) (strfmt.UUID, erro return resp.Commit.CommitID, nil } +type AttachStagedCommitParams struct { + Owner string + Project string + ParentCommitID strfmt.UUID + StagedCommitID strfmt.UUID + Branch string +} + +func (bp *BuildPlanner) AttachStagedCommit(params *AttachStagedCommitParams) (strfmt.UUID, error) { + logging.Debug("AttachStagedCommit, owner: %s, project: %s", params.Owner, params.Project) + request := request.AttachStagedCommit(params.Owner, params.Project, params.ParentCommitID.String(), params.StagedCommitID.String(), params.Branch) + resp := &bpModel.AttachStagedCommitResult{} + err := bp.client.Run(request, resp) + if err != nil { + return "", processBuildPlannerError(err, "Failed to attach staged commit") + } + + if resp.Commit == nil { + return "", errs.New("Attached, staged commit is nil") + } + + if bpModel.IsErrorResponse(resp.Commit.Type) { + return "", bpModel.ProcessCommitError(resp.Commit, "Could not process error response from attach stage commit") + } + + if resp.Commit.CommitID == "" { + return "", errs.New("Attach, staged commit does not contain commitID") + } + + return resp.Commit.CommitID, nil +} + func (bp *BuildPlanner) GetBuildExpression(owner, project, commitID string) (*buildexpression.BuildExpression, error) { logging.Debug("GetBuildExpression, owner: %s, project: %s, commitID: %s", owner, project, commitID) resp := &bpModel.BuildExpression{} @@ -325,6 +357,11 @@ func (bp *BuildPlanner) GetBuildExpression(owner, project, commitID string) (*bu return expression, nil } +// CreateProjectParams contains information for the project to create. +// When creating a project from scratch, the PlatformID, Language, Version, and Timestamp fields +// are used to create a buildexpression to use. +// When creating a project based off of another one, the Expr field is used (PlatformID, Language, +// Version, and Timestamp are ignored). type CreateProjectParams struct { Owner string Project string @@ -334,38 +371,43 @@ type CreateProjectParams struct { Private bool Timestamp strfmt.DateTime Description string + Expr *buildexpression.BuildExpression } func (bp *BuildPlanner) CreateProject(params *CreateProjectParams) (strfmt.UUID, error) { logging.Debug("CreateProject, owner: %s, project: %s, language: %s, version: %s", params.Owner, params.Project, params.Language, params.Version) - // Construct an initial buildexpression for the new project. - expr, err := buildexpression.NewEmpty() - if err != nil { - return "", errs.Wrap(err, "Unable to create initial buildexpression") - } + expr := params.Expr + if expr == nil { + // Construct an initial buildexpression for the new project. + var err error + expr, err = buildexpression.NewEmpty() + if err != nil { + return "", errs.Wrap(err, "Unable to create initial buildexpression") + } - // Add the platform. - expr.UpdatePlatform(model.OperationAdded, params.PlatformID) + // Add the platform. + expr.UpdatePlatform(model.OperationAdded, params.PlatformID) - // Create a requirement for the given language and version. - versionRequirements, err := VersionStringToRequirements(params.Version) - if err != nil { - return "", errs.Wrap(err, "Unable to read version") - } - expr.UpdateRequirement(model.OperationAdded, bpModel.Requirement{ - Name: params.Language, - Namespace: "language", // TODO: make this a constant DX-1738 - VersionRequirement: versionRequirements, - }) + // Create a requirement for the given language and version. + versionRequirements, err := VersionStringToRequirements(params.Version) + if err != nil { + return "", errs.Wrap(err, "Unable to read version") + } + expr.UpdateRequirement(model.OperationAdded, bpModel.Requirement{ + Name: params.Language, + Namespace: "language", // TODO: make this a constant DX-1738 + VersionRequirement: versionRequirements, + }) - // Add the timestamp. - expr.UpdateTimestamp(params.Timestamp) + // Add the timestamp. + expr.UpdateTimestamp(params.Timestamp) + } // Create the project. request := request.CreateProject(params.Owner, params.Project, params.Private, expr, params.Description) resp := &bpModel.CreateProjectResult{} - err = bp.client.Run(request, resp) + err := bp.client.Run(request, resp) if err != nil { return "", processBuildPlannerError(err, "Failed to create project") }