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  }