github.com/zaquestion/lab@v0.25.1/cmd/fork.go (about) 1 package cmd 2 3 import ( 4 "fmt" 5 "strings" 6 7 "github.com/MakeNowJust/heredoc/v2" 8 "github.com/spf13/cobra" 9 "github.com/tcnksm/go-gitconfig" 10 "github.com/xanzy/go-gitlab" 11 "github.com/zaquestion/lab/internal/git" 12 lab "github.com/zaquestion/lab/internal/gitlab" 13 ) 14 15 var ( 16 skipClone = false 17 waitFork = true 18 remoteName string 19 targetData struct { 20 project string 21 group string 22 path string 23 } 24 forkOpts *gitlab.ForkProjectOptions 25 ) 26 27 // forkCmd represents the fork command 28 var forkCmd = &cobra.Command{ 29 Use: "fork [remote],[project name or ID]", 30 Short: "Fork a remote repository on GitLab and add as remote", 31 Long: heredoc.Doc(` 32 Fork a remote repository on user's location of choice. 33 Both an already existent remote or a repository path can be specified.`), 34 Example: heredoc.Doc(` 35 lab fork origin 36 lab fork coolGroup/coolProject 37 lab fork 1234567 38 lab fork upstream --remote-name origin 39 lab fork origin --name new-awesome-project 40 lab fork origin -g TheCoolestGroup -n InitialProject 41 lab fork origin -p 'the_dot_git_path' 42 lab fork origin -n 'new_fork' -r 'new_fork_remote' 43 lab fork origin -s`), 44 Args: cobra.MaximumNArgs(1), 45 PersistentPreRun: labPersistentPreRun, 46 Run: func(cmd *cobra.Command, args []string) { 47 skipClone, _ = cmd.Flags().GetBool("skip-clone") 48 noWaitFork, _ := cmd.Flags().GetBool("no-wait") 49 waitFork = !noWaitFork 50 remoteName, _ = cmd.Flags().GetString("remote-name") 51 targetData.project, _ = cmd.Flags().GetString("name") 52 targetData.group, _ = cmd.Flags().GetString("group") 53 targetData.path, _ = cmd.Flags().GetString("path") 54 55 if targetData.project != "" || targetData.group != "" || 56 targetData.path != "" { 57 forkOpts = &gitlab.ForkProjectOptions{ 58 Name: gitlab.String(targetData.project), 59 Namespace: gitlab.String(targetData.group), 60 Path: gitlab.String(targetData.path), 61 } 62 } 63 64 remote, project := "", "" 65 if len(args) == 1 { 66 if ok, _ := git.IsRemote(args[0]); ok { 67 remote = args[0] 68 } else { 69 project = args[0] 70 } 71 } 72 73 if project != "" { 74 forkCleanProject(project) 75 return 76 } 77 78 if remote == "" { 79 remote = "origin" 80 } 81 82 project, err := git.PathWithNamespace(remote) 83 if err != nil { 84 log.Fatal(err) 85 } 86 forkRemoteProject(project) 87 }, 88 } 89 90 // forkRemoteProject handle forks from within an already existent local 91 // repository (working directory), using git-remote information passed (or 92 // not) by the user. Since the directory already exists, only a new remote 93 // is added. 94 func forkRemoteProject(project string) { 95 // Check for custom target namespace 96 remote := determineForkRemote(project) 97 if _, err := gitconfig.Local("remote." + remote + ".url"); err == nil { 98 log.Fatalf("remote: %s already exists", remote) 99 } 100 101 forkRemoteURL, err := lab.Fork(project, forkOpts, useHTTP, waitFork) 102 if err != nil { 103 if err.Error() == "not finished" { 104 fmt.Println("This fork is not ready yet and might take some minutes.") 105 } else { 106 log.Fatal(err) 107 } 108 } 109 110 err = git.RemoteAdd(remote, forkRemoteURL, ".") 111 if err != nil { 112 log.Fatal(err) 113 } 114 } 115 116 // forkCleanProject handle forks when the user passes a project name instead 117 // of a remote name directly. Usually it happens when the user is outside an 118 // existent local repository (working directory). Also, a clone step is 119 // performed when not explicitly skipped by the user with --skip-clone. 120 func forkCleanProject(project string) { 121 // lab.Fork doesn't have access to the useHTTP var, so we need to pass 122 // this info to that, so the process works correctly. 123 _, err := lab.Fork(project, forkOpts, useHTTP, waitFork) 124 if err != nil { 125 if err.Error() == "not finished" && !skipClone { 126 fmt.Println("This fork is not ready yet and might take some minutes.") 127 skipClone = true 128 } else { 129 log.Fatal(err) 130 } 131 } 132 133 if !skipClone { 134 // the clone may happen in a different name/path when compared to 135 // the original source project 136 namespace := "" 137 if targetData.group != "" { 138 namespace = targetData.group + "/" 139 } 140 141 name := project 142 if targetData.path != "" { 143 name = targetData.path 144 } else if targetData.project != "" { 145 name = targetData.project 146 } else { 147 nameParts := strings.Split(name, "/") 148 name = nameParts[len(nameParts)-1] 149 } 150 cloneCmd.Run(nil, []string{namespace + name}) 151 } 152 } 153 154 func determineForkRemote(project string) string { 155 if remoteName != "" { 156 return remoteName 157 } 158 159 name := lab.User() 160 if targetData.group != "" { 161 name = targetData.group 162 } 163 if strings.Split(project, "/")[0] == name { 164 // #78 allow upstream remote to be added when "origin" is 165 // referring to the user fork (and the fork already exists) 166 name = "upstream" 167 } 168 return name 169 } 170 171 func init() { 172 forkCmd.Flags().BoolP("skip-clone", "s", false, "skip clone after remote fork") 173 forkCmd.Flags().Bool("no-wait", false, "don't wait for forking operation to finish") 174 forkCmd.Flags().StringP("name", "n", "", "fork project with a different name") 175 forkCmd.Flags().StringP("group", "g", "", "fork project in a different group (namespace)") 176 forkCmd.Flags().StringP("path", "p", "", "fork project with a different path") 177 forkCmd.Flags().StringP("remote-name", "r", "", "use a custom remote name for the fork") 178 // useHTTP is defined in "util.go" 179 forkCmd.Flags().BoolVar(&useHTTP, "http", false, "fork using HTTP protocol instead of SSH") 180 RootCmd.AddCommand(forkCmd) 181 }