diff --git a/cmd/clone.go b/cmd/clone.go index 69455b1e..14d30bc3 100644 --- a/cmd/clone.go +++ b/cmd/clone.go @@ -12,6 +12,10 @@ import ( "github.com/gabrie30/ghorg/colorlog" "github.com/gabrie30/ghorg/configs" + "github.com/gabrie30/ghorg/internal/bitbucket" + "github.com/gabrie30/ghorg/internal/github" + "github.com/gabrie30/ghorg/internal/gitlab" + "github.com/gabrie30/ghorg/internal/repo" "github.com/korovkin/limiter" "github.com/spf13/cobra" ) @@ -34,6 +38,7 @@ var ( args []string cloneErrors []string cloneInfos []string + targetCloneSource string ) func init() { @@ -56,133 +61,128 @@ func init() { } -// Repo represents an SCM repo -type Repo struct { - Name string - Path string - URL string - CloneURL string -} - var cloneCmd = &cobra.Command{ Use: "clone", Short: "Clone user or org repos from GitHub, GitLab, or Bitbucket", - Long: `Clone user or org repos from GitHub, GitLab, or Bitbucket. See $HOME/.config/ghorg/conf.yaml for defaults, its likely you will need to update some of these values of use the flags to overwrite them. Values are set first by a default value, then based off what is set in $HOME/.config/ghorg/conf.yaml, finally the cli flags, which have the highest level of precedence.`, - Run: func(cmd *cobra.Command, argz []string) { + Long: `Clone user or org repos from GitHub, GitLab, or Bitbucket. See $HOME/ghorg/conf.yaml for defaults, its likely you will need to update some of these values of use the flags to overwrite them. Values are set first by a default value, then based off what is set in $HOME/ghorg/conf.yaml, finally the cli flags, which have the highest level of precedence.`, + Run: cloneFunc, +} - if cmd.Flags().Changed("color") { - colorToggle := cmd.Flag("color").Value.String() - if colorToggle == "on" { - os.Setenv("GHORG_COLOR", colorToggle) - } else { - os.Setenv("GHORG_COLOR", "off") - } +func cloneFunc(cmd *cobra.Command, argz []string) { + if cmd.Flags().Changed("color") { + colorToggle := cmd.Flag("color").Value.String() + if colorToggle == "on" { + os.Setenv("GHORG_COLOR", colorToggle) + } else { + os.Setenv("GHORG_COLOR", "off") } - if len(argz) < 1 { - colorlog.PrintError("You must provide an org or user to clone") - os.Exit(1) + } + + if len(argz) < 1 { + colorlog.PrintError("You must provide an org or user to clone") + os.Exit(1) + } + + if cmd.Flags().Changed("path") { + absolutePath := ensureTrailingSlash(cmd.Flag("path").Value.String()) + os.Setenv("GHORG_ABSOLUTE_PATH_TO_CLONE_TO", absolutePath) + } + + if cmd.Flags().Changed("protocol") { + protocol := cmd.Flag("protocol").Value.String() + os.Setenv("GHORG_CLONE_PROTOCOL", protocol) + } + + if cmd.Flags().Changed("branch") { + os.Setenv("GHORG_BRANCH", cmd.Flag("branch").Value.String()) + } + + if cmd.Flags().Changed("bitbucket-username") { + os.Setenv("GHORG_BITBUCKET_USERNAME", cmd.Flag("bitbucket-username").Value.String()) + } + + if cmd.Flags().Changed("namespace") { + os.Setenv("GHORG_GITLAB_DEFAULT_NAMESPACE", cmd.Flag("namespace").Value.String()) + } + + if cmd.Flags().Changed("clone-type") { + cloneType := strings.ToLower(cmd.Flag("clone-type").Value.String()) + os.Setenv("GHORG_CLONE_TYPE", cloneType) + } + + if cmd.Flags().Changed("scm") { + scmType := strings.ToLower(cmd.Flag("scm").Value.String()) + os.Setenv("GHORG_SCM_TYPE", scmType) + } + + if cmd.Flags().Changed("base-url") { + url := cmd.Flag("base-url").Value.String() + os.Setenv("GHORG_SCM_BASE_URL", url) + } + + if cmd.Flags().Changed("concurrency") { + g := cmd.Flag("concurrency").Value.String() + os.Setenv("GHORG_CONCURRENCY", g) + } + + if cmd.Flags().Changed("skip-archived") { + os.Setenv("GHORG_SKIP_ARCHIVED", "true") + } + + if cmd.Flags().Changed("preserve-dir") { + os.Setenv("GHORG_PRESERVE_DIRECTORY_STRUCTURE", "true") + } + + if cmd.Flags().Changed("backup") { + os.Setenv("GHORG_BACKUP", "true") + } + + configs.GetOrSetToken() + + if cmd.Flags().Changed("token") { + if os.Getenv("GHORG_SCM_TYPE") == "github" { + os.Setenv("GHORG_GITHUB_TOKEN", cmd.Flag("token").Value.String()) + } else if os.Getenv("GHORG_SCM_TYPE") == "gitlab" { + os.Setenv("GHORG_GITLAB_TOKEN", cmd.Flag("token").Value.String()) + } else if os.Getenv("GHORG_SCM_TYPE") == "bitbucket" { + os.Setenv("GHORG_BITBUCKET_APP_PASSWORD", cmd.Flag("token").Value.String()) } + } - if cmd.Flags().Changed("path") { - absolutePath := ensureTrailingSlash(cmd.Flag("path").Value.String()) - os.Setenv("GHORG_ABSOLUTE_PATH_TO_CLONE_TO", absolutePath) - } + err := configs.VerifyTokenSet() + if err != nil { + colorlog.PrintError(err) + os.Exit(1) + } - if cmd.Flags().Changed("protocol") { - protocol := cmd.Flag("protocol").Value.String() - os.Setenv("GHORG_CLONE_PROTOCOL", protocol) - } + err = configs.VerifyConfigsSetCorrectly() + if err != nil { + colorlog.PrintError(err) + os.Exit(1) + } - if cmd.Flags().Changed("branch") { - os.Setenv("GHORG_BRANCH", cmd.Flag("branch").Value.String()) - } + parseParentFolder(argz) + args = argz + targetCloneSource = argz[0] - if cmd.Flags().Changed("bitbucket-username") { - os.Setenv("GHORG_BITBUCKET_USERNAME", cmd.Flag("bitbucket-username").Value.String()) - } - - if cmd.Flags().Changed("namespace") { - os.Setenv("GHORG_GITLAB_DEFAULT_NAMESPACE", cmd.Flag("namespace").Value.String()) - } - - if cmd.Flags().Changed("clone-type") { - cloneType := strings.ToLower(cmd.Flag("clone-type").Value.String()) - os.Setenv("GHORG_CLONE_TYPE", cloneType) - } - - if cmd.Flags().Changed("scm") { - scmType := strings.ToLower(cmd.Flag("scm").Value.String()) - os.Setenv("GHORG_SCM_TYPE", scmType) - } - - if cmd.Flags().Changed("base-url") { - url := cmd.Flag("base-url").Value.String() - os.Setenv("GHORG_SCM_BASE_URL", url) - } - - if cmd.Flags().Changed("concurrency") { - g := cmd.Flag("concurrency").Value.String() - os.Setenv("GHORG_CONCURRENCY", g) - } - - if cmd.Flags().Changed("skip-archived") { - os.Setenv("GHORG_SKIP_ARCHIVED", "true") - } - - if cmd.Flags().Changed("preserve-dir") { - os.Setenv("GHORG_PRESERVE_DIRECTORY_STRUCTURE", "true") - } - - if cmd.Flags().Changed("backup") { - os.Setenv("GHORG_BACKUP", "true") - } - - configs.GetOrSetToken() - - if cmd.Flags().Changed("token") { - if os.Getenv("GHORG_SCM_TYPE") == "github" { - os.Setenv("GHORG_GITHUB_TOKEN", cmd.Flag("token").Value.String()) - } else if os.Getenv("GHORG_SCM_TYPE") == "gitlab" { - os.Setenv("GHORG_GITLAB_TOKEN", cmd.Flag("token").Value.String()) - } else if os.Getenv("GHORG_SCM_TYPE") == "bitbucket" { - os.Setenv("GHORG_BITBUCKET_APP_PASSWORD", cmd.Flag("token").Value.String()) - } - } - - err := configs.VerifyTokenSet() - if err != nil { - colorlog.PrintError(err) - os.Exit(1) - } - - err = configs.VerifyConfigsSetCorrectly() - if err != nil { - colorlog.PrintError(err) - os.Exit(1) - } - - parseParentFolder(argz) - args = argz - - CloneAllRepos() - }, + CloneAllRepos() } // TODO: Figure out how to use go channels for this -func getAllOrgCloneUrls() ([]Repo, error) { +func getAllOrgCloneUrls() ([]repo.Data, error) { asciiTime() PrintConfigs() - var repos []Repo + var repos []repo.Data var err error switch os.Getenv("GHORG_SCM_TYPE") { case "github": - repos, err = getGitHubOrgCloneUrls() + repos, err = github.GetOrgRepos(targetCloneSource) case "gitlab": - repos, err = getGitLabOrgCloneUrls() + repos, err = gitlab.GetOrgRepos(targetCloneSource) case "bitbucket": - repos, err = getBitBucketOrgCloneUrls() + repos, err = bitbucket.GetOrgRepos(targetCloneSource) default: colorlog.PrintError("GHORG_SCM_TYPE not set or unsupported, also make sure its all lowercase") os.Exit(1) @@ -192,18 +192,18 @@ func getAllOrgCloneUrls() ([]Repo, error) { } // TODO: Figure out how to use go channels for this -func getAllUserCloneUrls() ([]Repo, error) { +func getAllUserCloneUrls() ([]repo.Data, error) { asciiTime() PrintConfigs() - var repos []Repo + var repos []repo.Data var err error switch os.Getenv("GHORG_SCM_TYPE") { case "github": - repos, err = getGitHubUserCloneUrls() + repos, err = github.GetUserRepos(targetCloneSource) case "gitlab": - repos, err = getGitLabUserCloneUrls() + repos, err = gitlab.GetUserRepos(targetCloneSource) case "bitbucket": - repos, err = getBitBucketUserCloneUrls() + repos, err = bitbucket.GetUserRepos(targetCloneSource) default: colorlog.PrintError("GHORG_SCM_TYPE not set or unsupported, also make sure its all lowercase") os.Exit(1) @@ -213,7 +213,7 @@ func getAllUserCloneUrls() ([]Repo, error) { } func createDirIfNotExist() { - if _, err := os.Stat(os.Getenv("GHORG_ABSOLUTE_PATH_TO_CLONE_TO") + parentFolder + "_ghorg"); os.IsNotExist(err) { + if _, err := os.Stat(os.Getenv("GHORG_ABSOLUTE_PATH_TO_CLONE_TO") + parentFolder + "_ghorg"); os.IsNotExist(err) { err = os.MkdirAll(os.Getenv("GHORG_ABSOLUTE_PATH_TO_CLONE_TO"), 0700) if err != nil { panic(err) @@ -277,7 +277,7 @@ func readGhorgIgnore() ([]string, error) { func CloneAllRepos() { // resc, errc, infoc := make(chan string), make(chan error), make(chan error) - var cloneTargets []Repo + var cloneTargets []repo.Data var err error if os.Getenv("GHORG_CLONE_TYPE") == "org" { @@ -296,7 +296,7 @@ func CloneAllRepos() { } if len(cloneTargets) == 0 { - colorlog.PrintInfo("No repos found for " + os.Getenv("GHORG_SCM_TYPE") + " " + os.Getenv("GHORG_CLONE_TYPE") + ": " + args[0] + ", check spelling and verify clone-type (user/org) is set correctly e.g. -c=user") + colorlog.PrintInfo("No repos found for " + os.Getenv("GHORG_SCM_TYPE") + " " + os.Getenv("GHORG_CLONE_TYPE") + ": " + targetCloneSource + ", check spelling and verify clone-type (user/org) is set correctly e.g. -c=user") os.Exit(0) } @@ -313,7 +313,7 @@ func CloneAllRepos() { colorlog.PrintInfo("Using ghorgignore, filtering repos down...") - filteredCloneTargets := []Repo{} + filteredCloneTargets := []repo.Data{} var flag bool for _, cloned := range cloneTargets { flag = false @@ -332,7 +332,7 @@ func CloneAllRepos() { } - colorlog.PrintInfo(strconv.Itoa(len(cloneTargets)) + " repos found in " + args[0]) + colorlog.PrintInfo(strconv.Itoa(len(cloneTargets)) + " repos found in " + targetCloneSource) fmt.Println() createDirIfNotExist() diff --git a/cmd/clone_bitbucket.go b/internal/bitbucket/bitbucket.go similarity index 78% rename from cmd/clone_bitbucket.go rename to internal/bitbucket/bitbucket.go index 41094de6..c1c89476 100644 --- a/cmd/clone_bitbucket.go +++ b/internal/bitbucket/bitbucket.go @@ -1,19 +1,21 @@ -package cmd +package bitbucket import ( + "github.com/gabrie30/ghorg/internal/repo" bitbucket "github.com/ktrysmt/go-bitbucket" "os" ) -func getBitBucketOrgCloneUrls() ([]Repo, error) { +// GetOrgRepos gets org repos +func GetOrgRepos(targetOrg string) ([]repo.Data, error) { client := bitbucket.NewBasicAuth(os.Getenv("GHORG_BITBUCKET_USERNAME"), os.Getenv("GHORG_BITBUCKET_APP_PASSWORD")) - cloneData := []Repo{} + cloneData := []repo.Data{} - resp, err := client.Teams.Repositories(args[0]) + resp, err := client.Teams.Repositories(targetOrg) if err != nil { - return []Repo{}, err + return []repo.Data{}, err } values := resp.(map[string]interface{})["values"].([]interface{}) if err != nil { @@ -25,7 +27,7 @@ func getBitBucketOrgCloneUrls() ([]Repo, error) { for _, l := range links { link := l.(map[string]interface{})["href"] linkType := l.(map[string]interface{})["name"] - r := Repo{} + r := repo.Data{} if os.Getenv("GHORG_CLONE_PROTOCOL") == "ssh" && linkType == "ssh" { r.URL = link.(string) r.CloneURL = link.(string) @@ -41,14 +43,15 @@ func getBitBucketOrgCloneUrls() ([]Repo, error) { return cloneData, nil } -func getBitBucketUserCloneUrls() ([]Repo, error) { +// GetUserRepos gets user repos from bitbucket +func GetUserRepos(targetUser string) ([]repo.Data, error) { client := bitbucket.NewBasicAuth(os.Getenv("GHORG_BITBUCKET_USERNAME"), os.Getenv("GHORG_BITBUCKET_APP_PASSWORD")) - cloneData := []Repo{} + cloneData := []repo.Data{} - resp, err := client.Users.Repositories(args[0]) + resp, err := client.Users.Repositories(targetUser) if err != nil { - return []Repo{}, err + return []repo.Data{}, err } values := resp.(map[string]interface{})["values"].([]interface{}) if err != nil { @@ -61,7 +64,7 @@ func getBitBucketUserCloneUrls() ([]Repo, error) { link := l.(map[string]interface{})["href"] linkType := l.(map[string]interface{})["name"] - r := Repo{} + r := repo.Data{} if os.Getenv("GHORG_CLONE_PROTOCOL") == "ssh" && linkType == "ssh" { r.URL = link.(string) r.CloneURL = link.(string) diff --git a/cmd/clone_github.go b/internal/github/github.go similarity index 63% rename from cmd/clone_github.go rename to internal/github/github.go index f6c71971..0a9c39dd 100644 --- a/cmd/clone_github.go +++ b/internal/github/github.go @@ -1,14 +1,17 @@ -package cmd +package github import ( "context" "os" + "strings" + "github.com/gabrie30/ghorg/internal/repo" "github.com/google/go-github/github" "golang.org/x/oauth2" ) -func getGitHubOrgCloneUrls() ([]Repo, error) { +// GetOrgRepos gets org repos +func GetOrgRepos(targetOrg string) ([]repo.Data, error) { ctx := context.Background() ts := oauth2.StaticTokenSource( @@ -25,7 +28,7 @@ func getGitHubOrgCloneUrls() ([]Repo, error) { // get all pages of results var allRepos []*github.Repository for { - repos, resp, err := client.Repositories.ListByOrg(context.Background(), args[0], opt) + repos, resp, err := client.Repositories.ListByOrg(context.Background(), targetOrg, opt) if err != nil { return nil, err @@ -36,23 +39,23 @@ func getGitHubOrgCloneUrls() ([]Repo, error) { } opt.Page = resp.NextPage } - cloneData := []Repo{} + cloneData := []repo.Data{} - for _, repo := range allRepos { - r := Repo{} + for _, ghRepo := range allRepos { + r := repo.Data{} if os.Getenv("GHORG_SKIP_ARCHIVED") == "true" { - if *repo.Archived == true { + if *ghRepo.Archived == true { continue } } if os.Getenv("GHORG_CLONE_PROTOCOL") == "https" { - r.CloneURL = addTokenToHTTPSCloneURL(*repo.CloneURL, os.Getenv("GHORG_GITHUB_TOKEN")) - r.URL = *repo.CloneURL + r.CloneURL = addTokenToHTTPSCloneURL(*ghRepo.CloneURL, os.Getenv("GHORG_GITHUB_TOKEN")) + r.URL = *ghRepo.CloneURL cloneData = append(cloneData, r) } else { - r.CloneURL = *repo.SSHURL - r.URL = *repo.SSHURL + r.CloneURL = *ghRepo.SSHURL + r.URL = *ghRepo.SSHURL cloneData = append(cloneData, r) } } @@ -60,8 +63,8 @@ func getGitHubOrgCloneUrls() ([]Repo, error) { return cloneData, nil } -// TODO: refactor with getAllOrgCloneUrls -func getGitHubUserCloneUrls() ([]Repo, error) { +// GetUserRepos gets user repos +func GetUserRepos(targetUser string) ([]repo.Data, error) { ctx := context.Background() ts := oauth2.StaticTokenSource( @@ -78,7 +81,7 @@ func getGitHubUserCloneUrls() ([]Repo, error) { // get all pages of results var allRepos []*github.Repository for { - repos, resp, err := client.Repositories.List(context.Background(), args[0], opt) + repos, resp, err := client.Repositories.List(context.Background(), targetUser, opt) if err != nil { return nil, err @@ -89,26 +92,31 @@ func getGitHubUserCloneUrls() ([]Repo, error) { } opt.Page = resp.NextPage } - repoData := []Repo{} + repoData := []repo.Data{} - for _, repo := range allRepos { + for _, ghRepo := range allRepos { if os.Getenv("GHORG_SKIP_ARCHIVED") == "true" { - if *repo.Archived == true { + if *ghRepo.Archived == true { continue } } - r := Repo{} + r := repo.Data{} if os.Getenv("GHORG_CLONE_PROTOCOL") == "https" { - r.CloneURL = addTokenToHTTPSCloneURL(*repo.CloneURL, os.Getenv("GHORG_GITHUB_TOKEN")) - r.URL = *repo.CloneURL + r.CloneURL = addTokenToHTTPSCloneURL(*ghRepo.CloneURL, os.Getenv("GHORG_GITHUB_TOKEN")) + r.URL = *ghRepo.CloneURL repoData = append(repoData, r) } else { - r.CloneURL = *repo.SSHURL - r.URL = *repo.SSHURL + r.CloneURL = *ghRepo.SSHURL + r.URL = *ghRepo.SSHURL repoData = append(repoData, r) } } return repoData, nil } + +func addTokenToHTTPSCloneURL(url string, token string) string { + splitURL := strings.Split(url, "https://") + return "https://" + token + "@" + splitURL[1] +} diff --git a/cmd/clone_gitlab.go b/internal/gitlab/gitlab.go similarity index 82% rename from cmd/clone_gitlab.go rename to internal/gitlab/gitlab.go index 7be62492..d5758724 100644 --- a/cmd/clone_gitlab.go +++ b/internal/gitlab/gitlab.go @@ -1,4 +1,4 @@ -package cmd +package gitlab import ( "fmt" @@ -6,11 +6,14 @@ import ( "strings" "github.com/gabrie30/ghorg/colorlog" + "github.com/gabrie30/ghorg/internal/repo" + gitlab "github.com/xanzy/go-gitlab" ) -func getGitLabOrgCloneUrls() ([]Repo, error) { - repoData := []Repo{} +// GetOrgRepos fetches repo data +func GetOrgRepos(targetOrg string) ([]repo.Data, error) { + repoData := []repo.Data{} client, err := determineClient() if err != nil { @@ -34,11 +37,11 @@ func getGitLabOrgCloneUrls() ([]Repo, error) { for { // Get the first page with projects. - ps, resp, err := client.Groups.ListGroupProjects(args[0], opt) + ps, resp, err := client.Groups.ListGroupProjects(targetOrg, opt) if err != nil { // TODO: check if 404, then we know group does not exist - return []Repo{}, err + return []repo.Data{}, err } // List all the projects we've found so far. @@ -58,7 +61,7 @@ func getGitLabOrgCloneUrls() ([]Repo, error) { continue } } - r := Repo{} + r := repo.Data{} r.Path = p.PathWithNamespace if os.Getenv("GHORG_CLONE_PROTOCOL") == "https" { @@ -96,8 +99,8 @@ func determineClient() (*gitlab.Client, error) { return gitlab.NewClient(token) } -func getGitLabUserCloneUrls() ([]Repo, error) { - cloneData := []Repo{} +func GetUserRepos(targetUsername string) ([]repo.Data, error) { + cloneData := []repo.Data{} client, err := determineClient() @@ -114,10 +117,10 @@ func getGitLabUserCloneUrls() ([]Repo, error) { for { // Get the first page with projects. - ps, resp, err := client.Projects.ListUserProjects(args[0], opt) + ps, resp, err := client.Projects.ListUserProjects(targetUsername, opt) if err != nil { // TODO: check if 404, then we know user does not exist - return []Repo{}, err + return []repo.Data{}, err } // List all the projects we've found so far. @@ -128,7 +131,7 @@ func getGitLabUserCloneUrls() ([]Repo, error) { continue } } - r := Repo{} + r := repo.Data{} r.Path = p.PathWithNamespace if os.Getenv("GHORG_CLONE_PROTOCOL") == "https" { r.CloneURL = addTokenToHTTPSCloneURL(p.HTTPURLToRepo, os.Getenv("GHORG_GITLAB_TOKEN")) @@ -152,3 +155,8 @@ func getGitLabUserCloneUrls() ([]Repo, error) { return cloneData, nil } + +func addTokenToHTTPSCloneURL(url string, token string) string { + splitURL := strings.Split(url, "https://") + return "https://oauth2:" + token + "@" + splitURL[1] +} diff --git a/internal/repo/repo.go b/internal/repo/repo.go new file mode 100644 index 00000000..06cbb9a1 --- /dev/null +++ b/internal/repo/repo.go @@ -0,0 +1,10 @@ +// Package repo holds data for a repo +package repo + +// Data represents an SCM repo +type Data struct { + Name string + Path string + URL string + CloneURL string +}