github.com/abdfnx/gh-api@v0.0.0-20210414084727-f5432eec23b8/pkg/cmd/repo/clone/clone.go (about) 1 package clone 2 3 import ( 4 "fmt" 5 "net/http" 6 "strings" 7 8 "github.com/MakeNowJust/heredoc" 9 "github.com/abdfnx/gh-api/api" 10 "github.com/abdfnx/gh-api/git" 11 "github.com/abdfnx/gh-api/internal/config" 12 "github.com/abdfnx/gh-api/internal/ghrepo" 13 "github.com/abdfnx/gh-api/pkg/cmdutil" 14 "github.com/abdfnx/gh-api/pkg/iostreams" 15 "github.com/spf13/cobra" 16 "github.com/spf13/pflag" 17 ) 18 19 type CloneOptions struct { 20 HttpClient func() (*http.Client, error) 21 Config func() (config.Config, error) 22 IO *iostreams.IOStreams 23 24 GitArgs []string 25 Repository string 26 } 27 28 func NewCmdClone(f *cmdutil.Factory, runF func(*CloneOptions) error) *cobra.Command { 29 opts := &CloneOptions{ 30 IO: f.IOStreams, 31 HttpClient: f.HttpClient, 32 Config: f.Config, 33 } 34 35 cmd := &cobra.Command{ 36 DisableFlagsInUseLine: true, 37 38 Use: "clone <repository> [<directory>] [-- <gitflags>...]", 39 Args: cmdutil.MinimumArgs(1, "cannot clone: repository argument required"), 40 Short: "Clone a repository locally", 41 Long: heredoc.Doc(` 42 Clone a GitHub repository locally. 43 44 If the "OWNER/" portion of the "OWNER/REPO" repository argument is omitted, it 45 defaults to the name of the authenticating user. 46 47 Pass additional 'git clone' flags by listing them after '--'. 48 `), 49 RunE: func(cmd *cobra.Command, args []string) error { 50 opts.Repository = args[0] 51 opts.GitArgs = args[1:] 52 53 if runF != nil { 54 return runF(opts) 55 } 56 57 return cloneRun(opts) 58 }, 59 } 60 61 cmd.SetFlagErrorFunc(func(cmd *cobra.Command, err error) error { 62 if err == pflag.ErrHelp { 63 return err 64 } 65 return &cmdutil.FlagError{Err: fmt.Errorf("%w\nSeparate git clone flags with '--'.", err)} 66 }) 67 68 return cmd 69 } 70 71 func cloneRun(opts *CloneOptions) error { 72 httpClient, err := opts.HttpClient() 73 if err != nil { 74 return err 75 } 76 77 cfg, err := opts.Config() 78 if err != nil { 79 return err 80 } 81 82 apiClient := api.NewClientFromHTTP(httpClient) 83 84 repositoryIsURL := strings.Contains(opts.Repository, ":") 85 repositoryIsFullName := !repositoryIsURL && strings.Contains(opts.Repository, "/") 86 87 var repo ghrepo.Interface 88 var protocol string 89 90 if repositoryIsURL { 91 repoURL, err := git.ParseURL(opts.Repository) 92 if err != nil { 93 return err 94 } 95 repo, err = ghrepo.FromURL(repoURL) 96 if err != nil { 97 return err 98 } 99 if repoURL.Scheme == "git+ssh" { 100 repoURL.Scheme = "ssh" 101 } 102 protocol = repoURL.Scheme 103 } else { 104 var fullName string 105 if repositoryIsFullName { 106 fullName = opts.Repository 107 } else { 108 host, err := cfg.DefaultHost() 109 if err != nil { 110 return err 111 } 112 currentUser, err := api.CurrentLoginName(apiClient, host) 113 if err != nil { 114 return err 115 } 116 fullName = currentUser + "/" + opts.Repository 117 } 118 119 repo, err = ghrepo.FromFullName(fullName) 120 if err != nil { 121 return err 122 } 123 124 protocol, err = cfg.Get(repo.RepoHost(), "git_protocol") 125 if err != nil { 126 return err 127 } 128 } 129 130 wantsWiki := strings.HasSuffix(repo.RepoName(), ".wiki") 131 if wantsWiki { 132 repoName := strings.TrimSuffix(repo.RepoName(), ".wiki") 133 repo = ghrepo.NewWithHost(repo.RepoOwner(), repoName, repo.RepoHost()) 134 } 135 136 // Load the repo from the API to get the username/repo name in its 137 // canonical capitalization 138 canonicalRepo, err := api.GitHubRepo(apiClient, repo) 139 if err != nil { 140 return err 141 } 142 canonicalCloneURL := ghrepo.FormatRemoteURL(canonicalRepo, protocol) 143 144 // If repo HasWikiEnabled and wantsWiki is true then create a new clone URL 145 if wantsWiki { 146 if !canonicalRepo.HasWikiEnabled { 147 return fmt.Errorf("The '%s' repository does not have a wiki", ghrepo.FullName(canonicalRepo)) 148 } 149 canonicalCloneURL = strings.TrimSuffix(canonicalCloneURL, ".git") + ".wiki.git" 150 } 151 152 cloneDir, err := git.RunClone(canonicalCloneURL, opts.GitArgs) 153 if err != nil { 154 return err 155 } 156 157 // If the repo is a fork, add the parent as an upstream 158 if canonicalRepo.Parent != nil { 159 protocol, err := cfg.Get(canonicalRepo.Parent.RepoHost(), "git_protocol") 160 if err != nil { 161 return err 162 } 163 upstreamURL := ghrepo.FormatRemoteURL(canonicalRepo.Parent, protocol) 164 165 err = git.AddUpstreamRemote(upstreamURL, cloneDir, []string{canonicalRepo.Parent.DefaultBranchRef.Name}) 166 if err != nil { 167 return err 168 } 169 } 170 171 return nil 172 }