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  }