github.com/ActiveState/cli@v0.0.0-20240508170324-6801f60cd051/internal/runners/revert/revert.go (about) 1 package revert 2 3 import ( 4 "strings" 5 6 "github.com/ActiveState/cli/internal/analytics" 7 "github.com/ActiveState/cli/internal/config" 8 "github.com/ActiveState/cli/internal/errs" 9 "github.com/ActiveState/cli/internal/locale" 10 "github.com/ActiveState/cli/internal/output" 11 "github.com/ActiveState/cli/internal/primer" 12 "github.com/ActiveState/cli/internal/prompt" 13 "github.com/ActiveState/cli/internal/runbits/commit" 14 "github.com/ActiveState/cli/internal/runbits/rationalize" 15 "github.com/ActiveState/cli/internal/runbits/runtime" 16 "github.com/ActiveState/cli/pkg/localcommit" 17 gqlmodel "github.com/ActiveState/cli/pkg/platform/api/graphql/model" 18 "github.com/ActiveState/cli/pkg/platform/authentication" 19 "github.com/ActiveState/cli/pkg/platform/model" 20 "github.com/ActiveState/cli/pkg/platform/model/buildplanner" 21 "github.com/ActiveState/cli/pkg/platform/runtime/target" 22 "github.com/ActiveState/cli/pkg/project" 23 "github.com/go-openapi/strfmt" 24 ) 25 26 type Revert struct { 27 out output.Outputer 28 prompt prompt.Prompter 29 project *project.Project 30 auth *authentication.Auth 31 analytics analytics.Dispatcher 32 svcModel *model.SvcModel 33 cfg *config.Instance 34 } 35 36 type Params struct { 37 CommitID string 38 To bool 39 Force bool 40 } 41 42 type primeable interface { 43 primer.Outputer 44 primer.Prompter 45 primer.Projecter 46 primer.Auther 47 primer.Analyticer 48 primer.SvcModeler 49 primer.Configurer 50 } 51 52 func New(prime primeable) *Revert { 53 return &Revert{ 54 prime.Output(), 55 prime.Prompt(), 56 prime.Project(), 57 prime.Auth(), 58 prime.Analytics(), 59 prime.SvcModel(), 60 prime.Config(), 61 } 62 } 63 64 const remoteCommitID = "REMOTE" 65 const headCommitID = "HEAD" 66 67 func (r *Revert) Run(params *Params) (rerr error) { 68 defer rationalizeError(&rerr) 69 70 if r.project == nil { 71 return rationalize.ErrNoProject 72 } 73 74 commitID := params.CommitID 75 if strings.EqualFold(commitID, headCommitID) { 76 r.out.Notice(locale.T("warn_revert_head")) 77 commitID = remoteCommitID 78 } 79 if !strfmt.IsUUID(commitID) && !strings.EqualFold(commitID, remoteCommitID) { 80 return locale.NewInputError("err_revert_invalid_commit_id", "Invalid commit ID") 81 } 82 latestCommit, err := localcommit.Get(r.project.Dir()) 83 if err != nil { 84 return errs.Wrap(err, "Unable to get local commit") 85 } 86 if strings.EqualFold(commitID, remoteCommitID) { 87 commitID = latestCommit.String() 88 } 89 90 if commitID == latestCommit.String() && params.To { 91 return locale.NewInputError("err_revert_to_current_commit", "The commit to revert to cannot be the latest commit") 92 } 93 r.out.Notice(locale.Tr("operating_message", r.project.NamespaceString(), r.project.Dir())) 94 95 bp := buildplanner.NewBuildPlannerModel(r.auth) 96 targetCommitID := commitID // the commit to revert the contents of, or the commit to revert to 97 revertParams := revertParams{ 98 organization: r.project.Owner(), 99 project: r.project.Name(), 100 parentCommitID: latestCommit.String(), 101 revertCommitID: commitID, 102 } 103 revertFunc := r.revertCommit 104 preposition := "" 105 if params.To { 106 revertFunc = r.revertToCommit 107 preposition = " to" // need leading whitespace 108 } 109 110 targetCommit, err := model.GetCommitWithinCommitHistory(latestCommit, strfmt.UUID(targetCommitID), r.auth) 111 if err != nil { 112 if err == model.ErrCommitNotInHistory { 113 return locale.WrapInputError(err, "err_revert_commit_not_found", "The commit [NOTICE]{{.V0}}[/RESET] was not found in the project's commit history.", commitID) 114 } 115 return errs.AddTips( 116 locale.WrapError(err, "err_revert_get_commit", "", commitID), 117 locale.T("tip_private_project_auth"), 118 ) 119 } 120 121 var orgs []gqlmodel.Organization 122 if targetCommit.Author != nil { 123 var err error 124 orgs, err = model.FetchOrganizationsByIDs([]strfmt.UUID{*targetCommit.Author}, r.auth) 125 if err != nil { 126 return locale.WrapError(err, "err_revert_get_organizations", "Could not get organizations for current user") 127 } 128 } 129 130 if !r.out.Type().IsStructured() { 131 r.out.Print(locale.Tl("revert_info", "You are about to revert{{.V0}} the following commit:", preposition)) 132 if err := commit.PrintCommit(r.out, targetCommit, orgs); err != nil { 133 return locale.WrapError(err, "err_revert_print_commit", "Could not print commit") 134 } 135 } 136 137 defaultChoice := params.Force || !r.out.Config().Interactive 138 revert, err := r.prompt.Confirm("", locale.Tl("revert_confirm", "Continue?"), &defaultChoice) 139 if err != nil { 140 return locale.WrapError(err, "err_revert_confirm", "Could not confirm revert choice") 141 } 142 if !revert { 143 return locale.NewInputError("err_revert_aborted", "Revert aborted by user") 144 } 145 146 revertCommit, err := revertFunc(revertParams, bp) 147 if err != nil { 148 return errs.AddTips( 149 locale.WrapError(err, "err_revert_commit", "", preposition, commitID), 150 locale.Tl("tip_revert_sync", "Please ensure that the local project is synchronized with the platform and that the given commit ID belongs to the current project"), 151 locale.T("tip_private_project_auth")) 152 } 153 154 err = localcommit.Set(r.project.Dir(), revertCommit.String()) 155 if err != nil { 156 return errs.Wrap(err, "Unable to set local commit") 157 } 158 159 _, err = runtime.SolveAndUpdate(r.auth, r.out, r.analytics, r.project, &revertCommit, target.TriggerRevert, r.svcModel, r.cfg, runtime.OptOrderChanged) 160 if err != nil { 161 return locale.WrapError(err, "err_refresh_runtime") 162 } 163 164 r.out.Print(output.Prepare( 165 locale.Tl("revert_success", "Successfully reverted{{.V0}} commit: {{.V1}}", preposition, commitID), 166 &struct { 167 CurrentCommitID string `json:"current_commit_id"` 168 }{ 169 revertCommit.String(), 170 }, 171 )) 172 r.out.Notice(locale.T("operation_success_local")) 173 return nil 174 } 175 176 type revertParams struct { 177 organization string 178 project string 179 parentCommitID string 180 revertCommitID string 181 } 182 183 func (r *Revert) revertCommit(params revertParams, bp *buildplanner.BuildPlanner) (strfmt.UUID, error) { 184 newCommitID, err := bp.RevertCommit(params.organization, params.project, params.parentCommitID, params.revertCommitID) 185 if err != nil { 186 return "", errs.Wrap(err, "Could not revert commit") 187 } 188 189 return newCommitID, nil 190 } 191 192 func (r *Revert) revertToCommit(params revertParams, bp *buildplanner.BuildPlanner) (strfmt.UUID, error) { 193 buildExpression, err := bp.GetBuildExpression(params.revertCommitID) 194 if err != nil { 195 return "", errs.Wrap(err, "Could not get build expression") 196 } 197 198 stageCommitParams := buildplanner.StageCommitParams{ 199 Owner: params.organization, 200 Project: params.project, 201 ParentCommit: params.parentCommitID, 202 Description: locale.Tl("revert_commit_description", "Revert to commit {{.V0}}", params.revertCommitID), 203 Expression: buildExpression, 204 } 205 206 newCommitID, err := bp.StageCommit(stageCommitParams) 207 if err != nil { 208 return "", errs.Wrap(err, "Could not stage commit") 209 } 210 211 return newCommitID, nil 212 }