Skip to content

Commit

Permalink
state push uses buildplanner's attachStagedCommit mutation.
Browse files Browse the repository at this point in the history
  • Loading branch information
mitchell-as committed Oct 25, 2023
1 parent 92da15e commit 0688b1c
Show file tree
Hide file tree
Showing 4 changed files with 198 additions and 58 deletions.
112 changes: 74 additions & 38 deletions internal/runners/push/push.go
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand All @@ -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
Expand Down
6 changes: 6 additions & 0 deletions pkg/platform/api/buildplanner/model/buildplan.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"`
Expand Down
56 changes: 56 additions & 0 deletions pkg/platform/api/buildplanner/request/attachstagedcommit.go
Original file line number Diff line number Diff line change
@@ -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
}
82 changes: 62 additions & 20 deletions pkg/platform/model/buildplanner.go
Original file line number Diff line number Diff line change
Expand Up @@ -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{}
Expand Down Expand Up @@ -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
Expand All @@ -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")
}
Expand Down

0 comments on commit 0688b1c

Please sign in to comment.