github.com/ActiveState/cli@v0.0.0-20240508170324-6801f60cd051/internal/runners/commit/commit.go (about) 1 package commit 2 3 import ( 4 "errors" 5 "time" 6 7 "github.com/ActiveState/cli/internal/analytics" 8 "github.com/ActiveState/cli/internal/config" 9 "github.com/ActiveState/cli/internal/constants" 10 "github.com/ActiveState/cli/internal/errs" 11 "github.com/ActiveState/cli/internal/locale" 12 "github.com/ActiveState/cli/internal/output" 13 "github.com/ActiveState/cli/internal/primer" 14 "github.com/ActiveState/cli/internal/runbits/rationalize" 15 "github.com/ActiveState/cli/pkg/localcommit" 16 bpResp "github.com/ActiveState/cli/pkg/platform/api/buildplanner/response" 17 "github.com/ActiveState/cli/pkg/platform/authentication" 18 "github.com/ActiveState/cli/pkg/platform/model" 19 "github.com/ActiveState/cli/pkg/platform/model/buildplanner" 20 "github.com/ActiveState/cli/pkg/platform/runtime/buildscript" 21 "github.com/ActiveState/cli/pkg/project" 22 ) 23 24 type primeable interface { 25 primer.Outputer 26 primer.Projecter 27 primer.Auther 28 primer.Analyticer 29 primer.SvcModeler 30 primer.Configurer 31 } 32 33 type Commit struct { 34 out output.Outputer 35 proj *project.Project 36 auth *authentication.Auth 37 analytics analytics.Dispatcher 38 svcModel *model.SvcModel 39 cfg *config.Instance 40 } 41 42 func New(p primeable) *Commit { 43 return &Commit{ 44 out: p.Output(), 45 proj: p.Project(), 46 auth: p.Auth(), 47 analytics: p.Analytics(), 48 svcModel: p.SvcModel(), 49 cfg: p.Config(), 50 } 51 } 52 53 var ErrNoChanges = errors.New("buildscript has no changes") 54 55 func rationalizeError(err *error) { 56 var buildPlannerErr *bpResp.BuildPlannerError 57 58 switch { 59 case err == nil: 60 return 61 62 case errors.Is(*err, ErrNoChanges): 63 *err = errs.WrapUserFacing(*err, locale.Tl( 64 "commit_notice_no_change", 65 "No change to the buildscript was found.", 66 ), errs.SetInput()) 67 68 case errs.Matches(*err, buildscript.ErrBuildscriptNotExist): 69 *err = errs.WrapUserFacing(*err, locale.T("err_buildscript_not_exist")) 70 71 // We communicate buildplanner errors verbatim as the intend is that these are curated by the buildplanner 72 case errors.As(*err, &buildPlannerErr): 73 *err = errs.WrapUserFacing(*err, 74 buildPlannerErr.LocalizedError(), 75 errs.SetIf(buildPlannerErr.InputError(), errs.SetInput())) 76 } 77 } 78 79 func (c *Commit) Run() (rerr error) { 80 defer rationalizeError(&rerr) 81 82 if c.proj == nil { 83 return rationalize.ErrNoProject 84 } 85 86 pg := output.StartSpinner(c.out, locale.T("progress_commit"), constants.TerminalAnimationInterval) 87 defer func() { 88 if pg != nil { 89 pg.Stop(locale.T("progress_fail") + "\n") 90 } 91 }() 92 93 // Get buildscript.as representation 94 script, err := buildscript.ScriptFromProject(c.proj) 95 if err != nil { 96 return errs.Wrap(err, "Could not get local build script") 97 } 98 99 // Get equivalent build script for current state of the project 100 localCommitID, err := localcommit.Get(c.proj.Dir()) 101 if err != nil { 102 return errs.Wrap(err, "Unable to get local commit ID") 103 } 104 bp := buildplanner.NewBuildPlannerModel(c.auth) 105 exprProject, atTime, err := bp.GetBuildExpressionAndTime(localCommitID.String()) 106 if err != nil { 107 return errs.Wrap(err, "Could not get remote build expr and time for provided commit") 108 } 109 remoteScript, err := buildscript.NewFromBuildExpression(atTime, exprProject) 110 if err != nil { 111 return errs.Wrap(err, "Could not convert build expression to build script") 112 } 113 114 // Check if there is anything to commit 115 if script.Equals(remoteScript) { 116 return ErrNoChanges 117 } 118 119 var exprAtTime *time.Time 120 if atTime := script.AtTime; atTime != nil { 121 atTimeTime := time.Time(*atTime) 122 exprAtTime = &atTimeTime 123 } 124 125 stagedCommitID, err := bp.StageCommit(buildplanner.StageCommitParams{ 126 Owner: c.proj.Owner(), 127 Project: c.proj.Name(), 128 ParentCommit: localCommitID.String(), 129 Expression: script.Expr, 130 TimeStamp: exprAtTime, 131 }) 132 if err != nil { 133 return errs.Wrap(err, "Could not update project to reflect build script changes.") 134 } 135 136 // Update local commit ID 137 if err := localcommit.Set(c.proj.Dir(), stagedCommitID.String()); err != nil { 138 return errs.Wrap(err, "Could not set local commit ID") 139 } 140 141 // Update our local build expression to match the committed one. This allows our API a way to ensure forward compatibility. 142 newBuildExpr, newAtTime, err := bp.GetBuildExpressionAndTime(stagedCommitID.String()) 143 if err != nil { 144 return errs.Wrap(err, "Unable to get the remote build expression and time") 145 } 146 if err := buildscript.Update(c.proj, newAtTime, newBuildExpr); err != nil { 147 return errs.Wrap(err, "Could not update local build script") 148 } 149 150 pg.Stop(locale.T("progress_success") + "\n") 151 pg = nil 152 153 c.out.Print(output.Prepare( 154 locale.Tl( 155 "commit_success", 156 "", stagedCommitID.String(), c.proj.NamespaceString(), 157 ), 158 &struct { 159 Namespace string `json:"namespace"` 160 Path string `json:"path"` 161 CommitID string `json:"commit_id"` 162 }{ 163 c.proj.NamespaceString(), 164 c.proj.Dir(), 165 stagedCommitID.String(), 166 }, 167 )) 168 169 return nil 170 }