diff --git a/internal/migrator/migrator.go b/internal/migrator/migrator.go index 5dd5be424f..c335512726 100644 --- a/internal/migrator/migrator.go +++ b/internal/migrator/migrator.go @@ -30,7 +30,7 @@ func NewMigrator(auth *authentication.Auth, cfg *config.Instance, svcm *model.Sv case 0: if cfg.GetBool(constants.OptinBuildscriptsConfig) { logging.Debug("Creating buildscript") - info := checkoutinfo.New(project) + info := checkoutinfo.New(project, cfg) if err := buildscript_runbit.Initialize(filepath.Dir(project.Path()), auth, svcm, info); err != nil { return v, errs.Wrap(err, "Failed to initialize buildscript") } diff --git a/internal/primer/primer.go b/internal/primer/primer.go index af9bfa6c07..461a45f6fe 100644 --- a/internal/primer/primer.go +++ b/internal/primer/primer.go @@ -67,14 +67,14 @@ func New(values ...any) *Values { } } } - result.checkoutinfo = checkoutinfo.New(result.projectfile) + result.checkoutinfo = checkoutinfo.New(result.projectfile, result.config) return result } func (v *Values) SetProject(p *project.Project) { v.project = p v.projectfile = p.Source() - v.checkoutinfo = checkoutinfo.New(v.projectfile) + v.checkoutinfo = checkoutinfo.New(v.projectfile, v.config) } type Projecter interface { diff --git a/internal/runbits/buildscript/file.go b/internal/runbits/buildscript/file.go index f7759124e4..fdf2d88822 100644 --- a/internal/runbits/buildscript/file.go +++ b/internal/runbits/buildscript/file.go @@ -23,7 +23,7 @@ type projecter interface { Name() string } -var ErrBuildscriptNotExist = errors.New("Build script does not exist") +var ErrBuildscriptNotExist = checkoutinfo.ErrBuildscriptNotExist func ScriptFromProject(proj projecter) (*buildscript.BuildScript, error) { path := filepath.Join(proj.ProjectDir(), constants.BuildScriptFileName) @@ -47,11 +47,11 @@ func Initialize(path string, auth *authentication.Auth, svcm *model.SvcModel, in if err == nil { return nil // nothing to do, buildscript already exists } - if !errors.Is(err, os.ErrNotExist) { + if !errors.Is(err, os.ErrNotExist) && !errors.Is(err, buildscript.ErrOutdatedAtTime) { return errs.Wrap(err, "Could not read build script from file") } - logging.Debug("Build script does not exist. Creating one.") + logging.Debug("Build script does not exist or is outdated. Creating one.") commitId, err := info.CommitID() if err != nil { return errs.Wrap(err, "Unable to get the local commit ID") diff --git a/internal/runbits/checkout/checkout.go b/internal/runbits/checkout/checkout.go index 314a6e5de9..9c989e4b7d 100644 --- a/internal/runbits/checkout/checkout.go +++ b/internal/runbits/checkout/checkout.go @@ -86,7 +86,7 @@ func (r *Checkout) Run(ns *project.Namespaced, branchName, cachePath, targetPath // Clone the related repo, if it is defined if !noClone && repoURL != nil && *repoURL != "" { - err := r.repo.CloneProject(ns.Owner, ns.Project, path, r.prime.Output(), r.prime.Analytics()) + err := r.repo.CloneProject(ns.Owner, ns.Project, path, r.prime.Output(), r.prime.Analytics(), r.prime.Config()) if err != nil { return "", locale.WrapError(err, "err_clone_project", "Could not clone associated git repository") } diff --git a/internal/runbits/git/git.go b/internal/runbits/git/git.go index 6b49dc9e85..f77a1b7cf3 100644 --- a/internal/runbits/git/git.go +++ b/internal/runbits/git/git.go @@ -8,6 +8,7 @@ import ( "github.com/ActiveState/cli/internal/analytics" anaConsts "github.com/ActiveState/cli/internal/analytics/constants" + "github.com/ActiveState/cli/internal/config" "github.com/ActiveState/cli/internal/constants" "github.com/ActiveState/cli/internal/errs" "github.com/ActiveState/cli/internal/fileutils" @@ -22,7 +23,7 @@ import ( // Repository is the interface used to represent a version control system repository type Repository interface { - CloneProject(owner, name, path string, out output.Outputer, an analytics.Dispatcher) error + CloneProject(owner, name, path string, out output.Outputer, an analytics.Dispatcher, cfg *config.Instance) error } // NewRepo returns a new repository @@ -36,7 +37,7 @@ type Repo struct { // CloneProject will attempt to clone the associalted public git repository // for the project identified by / to the given directory -func (r *Repo) CloneProject(owner, name, path string, out output.Outputer, an analytics.Dispatcher) error { +func (r *Repo) CloneProject(owner, name, path string, out output.Outputer, an analytics.Dispatcher, cfg *config.Instance) error { project, err := model.LegacyFetchProjectByName(owner, name) if err != nil { return locale.WrapError(err, "err_git_fetch_project", "Could not fetch project details") @@ -67,7 +68,7 @@ func (r *Repo) CloneProject(owner, name, path string, out output.Outputer, an an return errs.AddTips(err, tipMsg) } - err = EnsureCorrectProject(owner, name, filepath.Join(tempDir, constants.ConfigFileName), *project.RepoURL, out, an) + err = EnsureCorrectProject(owner, name, filepath.Join(tempDir, constants.ConfigFileName), *project.RepoURL, out, an, cfg) if err != nil { return locale.WrapError(err, "err_git_ensure_project", "Could not ensure that the activestate.yaml in the cloned repository matches the project you are activating.") } @@ -80,7 +81,7 @@ func (r *Repo) CloneProject(owner, name, path string, out output.Outputer, an an return nil } -func EnsureCorrectProject(owner, name, projectFilePath, repoURL string, out output.Outputer, an analytics.Dispatcher) error { +func EnsureCorrectProject(owner, name, projectFilePath, repoURL string, out output.Outputer, an analytics.Dispatcher, cfg *config.Instance) error { if !fileutils.FileExists(projectFilePath) { return nil } @@ -97,7 +98,7 @@ func EnsureCorrectProject(owner, name, projectFilePath, repoURL string, out outp if !(strings.EqualFold(proj.Owner(), owner)) || !(strings.EqualFold(proj.Name(), name)) { out.Notice(locale.Tr("warning_git_project_mismatch", repoURL, project.NewNamespace(owner, name, "").String(), constants.DocumentationURLMismatch)) - info := checkoutinfo.New(projectFile) + info := checkoutinfo.New(projectFile, cfg) err = info.SetNamespace(owner, name) if err != nil { return locale.WrapError(err, "err_git_update_mismatch", "Could not update projectfile namespace") diff --git a/internal/runners/deploy/uninstall/uninstall.go b/internal/runners/deploy/uninstall/uninstall.go index 93993cdc34..b3e8cac129 100644 --- a/internal/runners/deploy/uninstall/uninstall.go +++ b/internal/runners/deploy/uninstall/uninstall.go @@ -85,7 +85,7 @@ func (u *Uninstall) Run(params *Params) error { return locale.WrapError(err, "err_deploy_uninstall_cannot_read_project", "Cannot read project at '{{.V0}}'", path) } - commitID, err := checkoutinfo.New(proj.Source()).CommitID() + commitID, err := checkoutinfo.New(proj.Source(), u.cfg).CommitID() if err != nil { return locale.WrapError(err, "err_deploy_uninstall_cannot_read_commit", "Cannot read commit ID from project at '{{.V0}}'", path) } diff --git a/internal/runners/initialize/init.go b/internal/runners/initialize/init.go index 10988b9b6f..96f503a24c 100644 --- a/internal/runners/initialize/init.go +++ b/internal/runners/initialize/init.go @@ -109,7 +109,7 @@ func inferLanguage(config projectfile.ConfigGetter, auth *authentication.Auth) ( if err != nil { return "", "", false } - commitID, err := checkoutinfo.New(defaultProj.Source()).CommitID() + commitID, err := checkoutinfo.New(defaultProj.Source(), config).CommitID() if err != nil { multilog.Error("Unable to get commit ID: %v", errs.JoinMessage(err)) return "", "", false diff --git a/internal/runners/projects/edit.go b/internal/runners/projects/edit.go index 71db9c1d8c..50923e57d2 100644 --- a/internal/runners/projects/edit.go +++ b/internal/runners/projects/edit.go @@ -149,7 +149,7 @@ func (e *Edit) editLocalCheckout(owner, checkout string, params *EditParams) err return errs.Wrap(err, "Could not get projectfile at %s", checkout) } - info := checkoutinfo.New(pjFile) + info := checkoutinfo.New(pjFile, e.config) err = info.SetNamespace(owner, params.ProjectName) if err != nil { return errs.Wrap(err, "Could not set project namespace at %s", checkout) diff --git a/internal/runners/projects/move.go b/internal/runners/projects/move.go index edf8b294b3..1cc727742c 100644 --- a/internal/runners/projects/move.go +++ b/internal/runners/projects/move.go @@ -98,7 +98,7 @@ func (m *Move) updateLocalCheckout(checkout string, params *MoveParams) error { return errs.Wrap(err, "Could not get projectfile at %s", checkout) } - info := checkoutinfo.New(pjFile) + info := checkoutinfo.New(pjFile, m.config) err = info.SetNamespace(params.NewOwner, params.Namespace.Project) if err != nil { return errs.Wrap(err, "Could not set project namespace at %s", checkout) diff --git a/internal/runners/reset/reset.go b/internal/runners/reset/reset.go index c68dc6ecfc..337a163d18 100644 --- a/internal/runners/reset/reset.go +++ b/internal/runners/reset/reset.go @@ -127,11 +127,6 @@ func (r *Reset) Run(params *Params) error { } } - err = r.prime.CheckoutInfo().SetCommitID(commitID) - if err != nil { - return errs.Wrap(err, "Unable to set local commit") - } - // Ensure the buildscript exists. Normally we should never do this, but reset is used for resetting from a corrupted // state, so it is appropriate. if r.cfg.GetBool(constants.OptinBuildscriptsConfig) { @@ -140,6 +135,11 @@ func (r *Reset) Run(params *Params) error { } } + err = r.prime.CheckoutInfo().SetCommitID(commitID) + if err != nil { + return errs.Wrap(err, "Unable to set local commit") + } + _, err = runtime_runbit.Update(r.prime, trigger.TriggerReset, runtime_runbit.WithoutBuildscriptValidation()) if err != nil { return locale.WrapError(err, "err_refresh_runtime") diff --git a/pkg/buildscript/buildscript.go b/pkg/buildscript/buildscript.go index a315522c02..c2e52ac97d 100644 --- a/pkg/buildscript/buildscript.go +++ b/pkg/buildscript/buildscript.go @@ -19,6 +19,14 @@ func New() (*BuildScript, error) { return UnmarshalBuildExpression([]byte(emptyBuildExpression), nil) } +func (b *BuildScript) Project() string { + return b.raw.CheckoutInfo.Project +} + +func (b *BuildScript) SetProject(url string) { + b.raw.CheckoutInfo.Project = url +} + func (b *BuildScript) AtTime() time.Time { return b.raw.CheckoutInfo.AtTime } diff --git a/pkg/checkoutinfo/checkoutinfo.go b/pkg/checkoutinfo/checkoutinfo.go index 7c4abfe7d6..03d9732444 100644 --- a/pkg/checkoutinfo/checkoutinfo.go +++ b/pkg/checkoutinfo/checkoutinfo.go @@ -1,7 +1,16 @@ package checkoutinfo import ( + "errors" + "os" + "path/filepath" + "github.com/go-openapi/strfmt" + + "github.com/ActiveState/cli/internal/constants" + "github.com/ActiveState/cli/internal/errs" + "github.com/ActiveState/cli/internal/fileutils" + "github.com/ActiveState/cli/pkg/buildscript" ) type ErrInvalidCommitID struct { @@ -20,14 +29,23 @@ type projectfiler interface { SetNamespace(string, string) error SetBranch(string) error SetLegacyCommit(string) error + Dir() string + URL() string +} + +type configurer interface { + GetBool(string) bool } type CheckoutInfo struct { project projectfiler + config configurer } -func New(project projectfiler) *CheckoutInfo { - return &CheckoutInfo{project} +var ErrBuildscriptNotExist = errors.New("Build script does not exist") + +func New(project projectfiler, config configurer) *CheckoutInfo { + return &CheckoutInfo{project, config} } // Owner returns the project owner from activestate.yaml. @@ -57,13 +75,62 @@ func (c *CheckoutInfo) CommitID() (strfmt.UUID, error) { } func (c *CheckoutInfo) SetNamespace(owner, project string) error { - return c.project.SetNamespace(owner, project) + err := c.project.SetNamespace(owner, project) + if err != nil { + return errs.Wrap(err, "Unable to update project") + } + return c.updateBuildScriptProject() } func (c *CheckoutInfo) SetBranch(branch string) error { - return c.project.SetBranch(branch) + err := c.project.SetBranch(branch) + if err != nil { + return errs.Wrap(err, "Unable to update project") + } + return c.updateBuildScriptProject() } func (c *CheckoutInfo) SetCommitID(commitID strfmt.UUID) error { - return c.project.SetLegacyCommit(commitID.String()) + err := c.project.SetLegacyCommit(commitID.String()) + if err != nil { + return errs.Wrap(err, "Unable to update project") + } + return c.updateBuildScriptProject() +} + +func (c *CheckoutInfo) updateBuildScriptProject() error { + if !c.config.GetBool(constants.OptinBuildscriptsConfig) { + return nil + } + + // Note: cannot use functions from buildscript_runbit due to import cycle. + scriptPath := filepath.Join(c.project.Dir(), constants.BuildScriptFileName) + data, err := fileutils.ReadFile(scriptPath) + if err != nil { + if errors.Is(err, os.ErrNotExist) { + return errs.Pack(err, ErrBuildscriptNotExist) + } + return errs.Wrap(err, "Could not read build script from file") + } + + script, err := buildscript.Unmarshal(data) + if err != nil { + return errs.Wrap(err, "Could not unmarshal build script") + } + + if c.project.URL() == script.Project() { + return nil //nothing to update + } + script.SetProject(c.project.URL()) + + data, err = script.Marshal() + if err != nil { + return errs.Wrap(err, "Could not marshal build script") + } + + if err := fileutils.WriteFile(scriptPath, data); err != nil { + return errs.Wrap(err, "Could not write build script to file") + } + + return nil } diff --git a/pkg/projectfile/projectfile.go b/pkg/projectfile/projectfile.go index 110ae6b912..a87c92b204 100644 --- a/pkg/projectfile/projectfile.go +++ b/pkg/projectfile/projectfile.go @@ -1185,6 +1185,7 @@ type ConfigGetter interface { AllKeys() []string GetStringSlice(string) []string GetString(string) string + GetBool(string) bool Set(string, interface{}) error GetThenSet(string, func(interface{}) (interface{}, error)) error Close() error