github.com/ActiveState/cli@v0.0.0-20240508170324-6801f60cd051/internal/runbits/checkout/checkout.go (about)

     1  package checkout
     2  
     3  import (
     4  	"path/filepath"
     5  
     6  	"github.com/ActiveState/cli/internal/analytics"
     7  	"github.com/ActiveState/cli/internal/config"
     8  	"github.com/ActiveState/cli/internal/locale"
     9  	"github.com/ActiveState/cli/internal/primer"
    10  	"github.com/ActiveState/cli/pkg/platform/runtime/buildscript"
    11  	"github.com/go-openapi/strfmt"
    12  
    13  	"github.com/ActiveState/cli/internal/constants"
    14  	"github.com/ActiveState/cli/internal/errs"
    15  	"github.com/ActiveState/cli/internal/fileutils"
    16  	"github.com/ActiveState/cli/internal/language"
    17  	"github.com/ActiveState/cli/internal/osutils"
    18  	"github.com/ActiveState/cli/internal/output"
    19  	"github.com/ActiveState/cli/internal/runbits/git"
    20  	"github.com/ActiveState/cli/pkg/localcommit"
    21  	"github.com/ActiveState/cli/pkg/platform/api/mono/mono_models"
    22  	"github.com/ActiveState/cli/pkg/platform/authentication"
    23  	"github.com/ActiveState/cli/pkg/platform/model"
    24  	"github.com/ActiveState/cli/pkg/project"
    25  	"github.com/ActiveState/cli/pkg/projectfile"
    26  )
    27  
    28  type primeable interface {
    29  	primer.Outputer
    30  	primer.Analyticer
    31  	primer.Configurer
    32  	primer.Auther
    33  }
    34  
    35  // Checkout will checkout the given platform project at the given path
    36  // This includes cloning an associated repository and creating the activestate.yaml
    37  // It does not activate any environment
    38  type Checkout struct {
    39  	repo git.Repository
    40  	output.Outputer
    41  	config     *config.Instance
    42  	analytics  analytics.Dispatcher
    43  	branchName string
    44  	auth       *authentication.Auth
    45  }
    46  
    47  type errCommitDoesNotBelong struct {
    48  	CommitID strfmt.UUID
    49  }
    50  
    51  func (e errCommitDoesNotBelong) Error() string {
    52  	return "commitID does not belong to the given branch"
    53  }
    54  
    55  func New(repo git.Repository, prime primeable) *Checkout {
    56  	return &Checkout{repo, prime.Output(), prime.Config(), prime.Analytics(), "", prime.Auth()}
    57  }
    58  
    59  func (r *Checkout) Run(ns *project.Namespaced, branchName, cachePath, targetPath string, noClone bool) (_ string, rerr error) {
    60  	defer r.rationalizeError(&rerr)
    61  
    62  	path, err := r.pathToUse(ns, targetPath)
    63  	if err != nil {
    64  		return "", errs.Wrap(err, "Could not get path to use")
    65  	}
    66  
    67  	path, err = filepath.Abs(path)
    68  	if err != nil {
    69  		return "", errs.Wrap(err, "Could not get absolute path")
    70  	}
    71  
    72  	// If project does not exist at path then we must checkout
    73  	// the project and create the project file
    74  	pj, err := model.FetchProjectByName(ns.Owner, ns.Project, r.auth)
    75  	if err != nil {
    76  		return "", locale.WrapError(err, "err_fetch_project", "", ns.String())
    77  	}
    78  
    79  	var branch *mono_models.Branch
    80  	commitID := ns.CommitID
    81  
    82  	switch {
    83  	// Fetch the branch the given commitID is on.
    84  	case commitID != nil:
    85  		for _, b := range pj.Branches {
    86  			if belongs, err := model.CommitBelongsToBranch(ns.Owner, ns.Project, b.Label, *commitID, r.auth); err == nil && belongs {
    87  				branch = b
    88  				break
    89  			} else if err != nil {
    90  				return "", errs.Wrap(err, "Could not determine which branch the given commitID belongs to")
    91  			}
    92  		}
    93  		if branch == nil {
    94  			return "", &errCommitDoesNotBelong{CommitID: *commitID}
    95  		}
    96  
    97  	// Fetch the given project branch.
    98  	case branchName != "":
    99  		branch, err = model.BranchForProjectByName(pj, branchName)
   100  		if err != nil {
   101  			return "", locale.WrapError(err, "err_fetch_branch", "", branchName)
   102  		}
   103  		commitID = branch.CommitID
   104  
   105  	// Fetch the default branch for the given project.
   106  	default:
   107  		branch, err = model.DefaultBranchForProject(pj)
   108  		if err != nil {
   109  			return "", errs.Wrap(err, "Could not grab branch for project")
   110  		}
   111  		commitID = branch.CommitID
   112  	}
   113  
   114  	if commitID == nil {
   115  		return "", errs.New("commitID is nil")
   116  	}
   117  
   118  	// Clone the related repo, if it is defined
   119  	if !noClone && pj.RepoURL != nil && *pj.RepoURL != "" {
   120  		err := r.repo.CloneProject(ns.Owner, ns.Project, path, r.Outputer, r.analytics)
   121  		if err != nil {
   122  			return "", locale.WrapError(err, "err_clone_project", "Could not clone associated git repository")
   123  		}
   124  	}
   125  
   126  	language, err := getLanguage(*commitID, r.auth)
   127  	if err != nil {
   128  		return "", errs.Wrap(err, "Could not get language from commitID")
   129  	}
   130  
   131  	if cachePath != "" && !filepath.IsAbs(cachePath) {
   132  		cachePath, err = filepath.Abs(cachePath)
   133  		if err != nil {
   134  			return "", errs.Wrap(err, "Could not get absolute path for cache")
   135  		}
   136  	}
   137  
   138  	// Match the case of the organization.
   139  	// Otherwise the incorrect case will be written to the project file.
   140  	owners, err := model.FetchOrganizationsByIDs([]strfmt.UUID{pj.OrganizationID}, r.auth)
   141  	if err != nil {
   142  		return "", errs.Wrap(err, "Unable to get the project's org")
   143  	}
   144  	if len(owners) == 0 {
   145  		return "", locale.NewInputError("err_no_org_name", "Your project's organization name could not be found")
   146  	}
   147  	owner := owners[0].URLName
   148  
   149  	// Create the config file, if the repo clone didn't already create it
   150  	configFile := filepath.Join(path, constants.ConfigFileName)
   151  	if !fileutils.FileExists(configFile) {
   152  		_, err = projectfile.Create(&projectfile.CreateParams{
   153  			Owner:      owner,
   154  			Project:    pj.Name, // match case on the Platform
   155  			BranchName: branch.Label,
   156  			Directory:  path,
   157  			Language:   language.String(),
   158  			Cache:      cachePath,
   159  		})
   160  		if err != nil {
   161  			if osutils.IsAccessDeniedError(err) {
   162  				return "", &ErrNoPermission{err, path}
   163  			}
   164  			return "", errs.Wrap(err, "Could not create projectfile")
   165  		}
   166  	}
   167  
   168  	err = localcommit.Set(path, commitID.String())
   169  	if err != nil {
   170  		return "", errs.Wrap(err, "Could not create local commit file")
   171  	}
   172  
   173  	if r.config.GetBool(constants.OptinBuildscriptsConfig) {
   174  		if err := buildscript.Initialize(path, r.auth); err != nil {
   175  			return "", errs.Wrap(err, "Unable to initialize buildscript")
   176  		}
   177  	}
   178  
   179  	return path, nil
   180  }
   181  
   182  func getLanguage(commitID strfmt.UUID, auth *authentication.Auth) (language.Language, error) {
   183  	modelLanguage, err := model.LanguageByCommit(commitID, auth)
   184  	if err != nil {
   185  		return language.Unset, locale.WrapError(err, "err_language_by_commit", "", string(commitID))
   186  	}
   187  
   188  	return language.MakeByNameAndVersion(modelLanguage.Name, modelLanguage.Version), nil
   189  }