github.com/zaquestion/lab@v0.25.1/cmd/mr_checkout.go (about) 1 package cmd 2 3 import ( 4 "fmt" 5 6 "github.com/MakeNowJust/heredoc/v2" 7 "github.com/rsteube/carapace" 8 "github.com/spf13/cobra" 9 "github.com/zaquestion/lab/internal/action" 10 "github.com/zaquestion/lab/internal/git" 11 lab "github.com/zaquestion/lab/internal/gitlab" 12 ) 13 14 // mrCheckoutConfig holds configuration values for calls to lab mr checkout 15 type mrCheckoutConfig struct { 16 branch string 17 force bool 18 track bool 19 } 20 21 var ( 22 mrCheckoutCfg mrCheckoutConfig 23 ) 24 25 // listCmd represents the list command 26 var checkoutCmd = &cobra.Command{ 27 Use: "checkout [remote] [<MR id or branch>]", 28 Aliases: []string{"co"}, 29 Short: "Checkout an open merge request", 30 Long: heredoc.Doc(` 31 Checkout an open merge request using the MR's source branch name as 32 local branch name; this behavior can be changed using --branch 33 option.`), 34 Args: cobra.RangeArgs(1, 2), 35 Example: heredoc.Doc(` 36 lab mr checkout origin 10 37 lab mr checkout upstream -b a_branch_name 38 lab mr checkout a_remote -f 39 lab mr checkout upstream --https 40 lab mr checkout upstream -t`), 41 PersistentPreRun: labPersistentPreRun, 42 Run: func(cmd *cobra.Command, args []string) { 43 rn, mrID, err := parseArgsRemoteAndID(args) 44 if err != nil { 45 log.Fatal(err) 46 } 47 48 targetRemote := defaultRemote 49 if len(args) == 2 { 50 // parseArgs above already validated this is a remote 51 targetRemote = args[0] 52 } 53 54 mr, err := lab.MRGet(rn, int(mrID)) 55 if err != nil { 56 log.Fatal(err) 57 } 58 59 // If the config does not specify a branch, use the mr source branch name 60 if mrCheckoutCfg.branch == "" { 61 mrCheckoutCfg.branch = mr.SourceBranch 62 } 63 64 err = git.New("show-ref", "--verify", "--quiet", "refs/heads/"+mrCheckoutCfg.branch).Run() 65 if err == nil { 66 if mrCheckoutCfg.force { 67 err = git.New("branch", "-D", mrCheckoutCfg.branch).Run() 68 if err != nil { 69 log.Fatal(err) 70 } 71 } else { 72 log.Fatalf("branch %s already exists", mrCheckoutCfg.branch) 73 } 74 } 75 76 // By default, fetch to configured branch 77 fetchToRef := mrCheckoutCfg.branch 78 79 // If track, make sure we have a remote for the mr author and then set 80 // the fetchToRef to the mr author/sourceBranch 81 if mrCheckoutCfg.track { 82 // Check if remote already exists 83 project, err := lab.GetProject(mr.SourceProjectID) 84 if err != nil { 85 log.Fatal(err) 86 } 87 88 remotes, err := git.Remotes() 89 if err != nil { 90 log.Fatal(err) 91 } 92 93 remoteName := "" 94 for _, remote := range remotes { 95 path, err := git.PathWithNamespace(remote) 96 if err != nil { 97 continue 98 } 99 if path == project.PathWithNamespace { 100 remoteName = remote 101 break 102 } 103 } 104 105 if remoteName == "" { 106 remoteName = mr.Author.Username 107 urlToRepo := labURLToRepo(project) 108 err := git.RemoteAdd(remoteName, urlToRepo, ".") 109 if err != nil { 110 log.Fatal(err) 111 } 112 } 113 114 trackRef := fmt.Sprintf("refs/remotes/%s/%s", remoteName, mr.SourceBranch) 115 err = git.New("show-ref", "--verify", "--quiet", trackRef).Run() 116 if err == nil { 117 if mrCheckoutCfg.force { 118 err = git.New("update-ref", "-d", trackRef).Run() 119 if err != nil { 120 log.Fatal(err) 121 } 122 } else { 123 log.Fatalf("remote reference %s already exists", trackRef) 124 } 125 } 126 127 fetchToRef = trackRef 128 } 129 130 // https://docs.gitlab.com/ee/user/project/merge_requests/reviews/#checkout-merge-requests-locally-through-the-head-ref 131 mrRef := fmt.Sprintf("refs/merge-requests/%d/head", mrID) 132 fetchRefSpec := fmt.Sprintf("%s:%s", mrRef, fetchToRef) 133 err = git.New("fetch", targetRemote, fetchRefSpec).Run() 134 if err != nil { 135 log.Fatal(err) 136 } 137 138 if mrCheckoutCfg.track { 139 // Create configured branch with tracking from fetchToRef 140 // git branch --flags <branchname> [<start-point>] 141 err = git.New("branch", "--track", mrCheckoutCfg.branch, fetchToRef).Run() 142 if err != nil { 143 log.Fatal(err) 144 } 145 } 146 147 err = git.New("checkout", mrCheckoutCfg.branch).Run() 148 if err != nil { 149 log.Fatal(err) 150 } 151 }, 152 } 153 154 func init() { 155 checkoutCmd.Flags().StringVarP(&mrCheckoutCfg.branch, "branch", "b", "", "checkout merge request with <branch> name") 156 checkoutCmd.Flags().BoolVarP(&mrCheckoutCfg.track, "track", "t", false, "set branch to track remote branch, adds remote if needed") 157 // useHTTP is defined in "project_create.go" 158 checkoutCmd.Flags().BoolVar(&useHTTP, "http", false, "checkout using HTTP protocol instead of SSH") 159 checkoutCmd.Flags().BoolVarP(&mrCheckoutCfg.force, "force", "f", false, "force branch and remote reference override") 160 mrCmd.AddCommand(checkoutCmd) 161 carapace.Gen(checkoutCmd).PositionalCompletion( 162 carapace.ActionCallback(func(c carapace.Context) carapace.Action { 163 c.Args = []string{"origin"} 164 return action.MergeRequests(mrList).Invoke(c).ToA() 165 }), 166 ) 167 }