github.com/ActiveState/cli@v0.0.0-20240508170324-6801f60cd051/internal/runbits/git/git.go (about) 1 package git 2 3 import ( 4 "fmt" 5 "os" 6 "path/filepath" 7 "strings" 8 9 "github.com/ActiveState/cli/internal/analytics" 10 anaConsts "github.com/ActiveState/cli/internal/analytics/constants" 11 "github.com/ActiveState/cli/internal/constants" 12 "github.com/ActiveState/cli/internal/errs" 13 "github.com/ActiveState/cli/internal/fileutils" 14 "github.com/ActiveState/cli/internal/locale" 15 "github.com/ActiveState/cli/internal/output" 16 "github.com/ActiveState/cli/pkg/platform/model" 17 "github.com/ActiveState/cli/pkg/project" 18 "github.com/ActiveState/cli/pkg/projectfile" 19 "gopkg.in/src-d/go-git.v4" 20 ) 21 22 // Repository is the interface used to represent a version control system repository 23 type Repository interface { 24 CloneProject(owner, name, path string, out output.Outputer, an analytics.Dispatcher) error 25 } 26 27 // NewRepo returns a new repository 28 func NewRepo() *Repo { 29 return &Repo{} 30 } 31 32 // Repo represents a git repository 33 type Repo struct { 34 } 35 36 // CloneProject will attempt to clone the associalted public git repository 37 // for the project identified by <owner>/<name> to the given directory 38 func (r *Repo) CloneProject(owner, name, path string, out output.Outputer, an analytics.Dispatcher) error { 39 project, err := model.LegacyFetchProjectByName(owner, name) 40 if err != nil { 41 return locale.WrapError(err, "err_git_fetch_project", "Could not fetch project details") 42 } 43 44 tempDir, err := os.MkdirTemp("", fmt.Sprintf("state-activate-repo-%s-%s", owner, name)) 45 if err != nil { 46 return locale.WrapError(err, "err_git_tempdir", "Could not create temporary directory for git clone operation") 47 } 48 defer os.RemoveAll(tempDir) 49 50 if project.RepoURL == nil { 51 return locale.NewError("err_nil_repo_url", "Project returned empty repository URL") 52 } 53 54 out.Notice(output.Title(locale.Tr("git_cloning_project_heading"))) 55 out.Notice(locale.Tr("git_cloning_project", *project.RepoURL)) 56 _, err = git.PlainClone(tempDir, false, &git.CloneOptions{ 57 URL: *project.RepoURL, 58 Progress: os.Stdout, 59 }) 60 if err != nil { 61 err = locale.WrapError(err, "err_clone_repo", "Could not clone repository with URL: {{.V0}}, error received: {{.V1}}.", *project.RepoURL, err.Error()) 62 tipMsg := locale.Tl( 63 "err_tip_git_ssh-add", 64 "If you are using an SSH key please ensure it's configured by running '[ACTIONABLE]ssh-add <path-to-key>[/RESET]'.", 65 ) 66 return errs.AddTips(err, tipMsg) 67 } 68 69 err = EnsureCorrectProject(owner, name, filepath.Join(tempDir, constants.ConfigFileName), *project.RepoURL, out, an) 70 if err != nil { 71 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.") 72 } 73 74 err = MoveFiles(tempDir, path) 75 if err != nil { 76 return locale.WrapError(err, "err_git_move_files", "Could not move cloned files") 77 } 78 79 return nil 80 } 81 82 func EnsureCorrectProject(owner, name, projectFilePath, repoURL string, out output.Outputer, an analytics.Dispatcher) error { 83 if !fileutils.FileExists(projectFilePath) { 84 return nil 85 } 86 87 projectFile, err := projectfile.Parse(projectFilePath) 88 if err != nil { 89 return locale.WrapError(err, "err_git_parse_projectfile", "Could not parse projectfile") 90 } 91 92 proj, err := project.NewLegacy(projectFile) 93 if err != nil { 94 return locale.WrapError(err, "err_git_project", "Could not create new project from project file at: {{.V0}}", projectFile.Path()) 95 } 96 97 if !(strings.EqualFold(proj.Owner(), owner)) || !(strings.EqualFold(proj.Name(), name)) { 98 out.Notice(locale.Tr("warning_git_project_mismatch", repoURL, project.NewNamespace(owner, name, "").String(), constants.DocumentationURLMismatch)) 99 err = proj.Source().SetNamespace(owner, name) 100 if err != nil { 101 return locale.WrapError(err, "err_git_update_mismatch", "Could not update projectfile namespace") 102 } 103 an.Event(anaConsts.CatMisc, "git-project-mismatch") 104 } 105 106 return nil 107 } 108 109 func MoveFiles(src, dest string) error { 110 err := verifyDestinationDirectory(dest) 111 if err != nil { 112 return locale.WrapError(err, "err_git_verify_dir", "Could not verify destination directory") 113 } 114 115 err = fileutils.MoveAllFilesCrossDisk(src, dest) 116 if err != nil { 117 return locale.WrapError(err, "err_git_move_file", "Could not move files from {{.V0}} to {{.V1}}", src, dest) 118 } 119 120 return nil 121 } 122 123 func verifyDestinationDirectory(dest string) error { 124 if !fileutils.DirExists(dest) { 125 return fileutils.Mkdir(dest) 126 } 127 128 empty, err := fileutils.IsEmptyDir(dest) 129 if err != nil { 130 return locale.WrapError(err, "err_git_empty_dir", "Could not verify if destination directory is empty") 131 } 132 if !empty { 133 return locale.NewError("err_git_in_use", "Destination directory is not empty") 134 } 135 136 return nil 137 }