func createRun()

in commands/mr/create/mr_create.go [207:644]


func createRun(opts *CreateOpts) error {
	out := opts.IO.StdOut
	c := opts.IO.Color()
	mrCreateOpts := &gitlab.CreateMergeRequestOptions{}
	glRepo, err := opts.BaseRepo()
	if err != nil {
		return err
	}

	if opts.Recover {
		if err := recovery.FromFile(glRepo.FullName(), "mr.json", opts); err != nil {
			// if the file to recover doesn't exist, we can just ignore the error and move on
			if !errors.Is(err, os.ErrNotExist) {
				fmt.Fprintf(opts.IO.StdErr, "Failed to recover from file: %v", err)
			}
		} else {
			fmt.Fprintln(opts.IO.StdOut, "Recovered create options from file")
		}
	}

	labClient, err := opts.Lab()
	if err != nil {
		return err
	}

	baseRepo, err := opts.BaseRepo()
	if err != nil {
		return err
	}

	headRepo, err := opts.HeadRepo()
	if err != nil {
		return err
	}

	// only fetch source project if it wasn't saved for recovery
	if opts.SourceProject == nil {
		opts.SourceProject, err = api.GetProject(labClient, headRepo.FullName())
		if err != nil {
			return err
		}
	}

	// only fetch target project if it wasn't saved for recovery
	if opts.TargetProject == nil {
		// if the user set the target_project, get details of the target
		if opts.MRCreateTargetProject != "" {
			opts.TargetProject, err = api.GetProject(labClient, opts.MRCreateTargetProject)
			if err != nil {
				return err
			}
		} else {
			// If both the baseRepo and headRepo are the same then re-use the SourceProject
			if baseRepo.FullName() == headRepo.FullName() {
				opts.TargetProject = opts.SourceProject
			} else {
				// Otherwise assume the user wants to create the merge request against the
				// baseRepo
				opts.TargetProject, err = api.GetProject(labClient, baseRepo.FullName())
				if err != nil {
					return err
				}
			}
		}
	}

	if !opts.TargetProject.MergeRequestsEnabled { //nolint:staticcheck
		fmt.Fprintf(opts.IO.StdErr, "Failed to create a merge request for project %q. Please ensure:\n", opts.TargetProject.PathWithNamespace)
		fmt.Fprintf(opts.IO.StdErr, " - You are authenticated with the GitLab CLI.\n")
		fmt.Fprintf(opts.IO.StdErr, " - Merge requests are enabled for this project.\n")
		fmt.Fprintf(opts.IO.StdErr, " - Your role in this project allows you to create merge requests.\n")
		return cmdutils.SilentError
	}

	headRepoRemote, err := repoRemote(opts, headRepo, opts.SourceProject, "glab-head")
	if err != nil {
		return nil
	}

	var baseRepoRemote *glrepo.Remote

	// check if baseRepo is the same as the headRepo and set the remote
	if glrepo.IsSame(baseRepo, headRepo) {
		baseRepoRemote = headRepoRemote
	} else {
		baseRepoRemote, err = repoRemote(opts, baseRepo, opts.TargetProject, "glab-base")
		if err != nil {
			return nil
		}
	}

	if opts.MilestoneFlag != "" {
		opts.Milestone, err = cmdutils.ParseMilestone(labClient, baseRepo, opts.MilestoneFlag)
		if err != nil {
			return err
		}
	}

	if opts.CreateSourceBranch && opts.SourceBranch == "" {
		opts.SourceBranch = utils.ReplaceNonAlphaNumericChars(opts.Title, "-")
	} else if opts.SourceBranch == "" && opts.RelatedIssue == "" {
		opts.SourceBranch, err = opts.Branch()
		if err != nil {
			return err
		}
	}

	if opts.TargetBranch == "" {
		opts.TargetBranch = getTargetBranch(baseRepoRemote)
	}

	if opts.RelatedIssue != "" {
		issue, err := parseIssue(labClient, opts)
		if err != nil {
			return err
		}

		if opts.CopyIssueLabels {
			mrCreateOpts.Labels = (*gitlab.LabelOptions)(&issue.Labels)
		}

		opts.Description += fmt.Sprintf("\n\nCloses #%d", issue.IID)

		if opts.Title == "" {
			opts.Title = fmt.Sprintf("Resolve \"%s\"", issue.Title)
		}

		// MRs created with a related issue will always be created as a draft, same as the UI
		if !opts.IsDraft && !opts.IsWIP {
			opts.IsDraft = true
		}

		if opts.SourceBranch == "" {
			sourceBranch := fmt.Sprintf("%d-%s", issue.IID, utils.ReplaceNonAlphaNumericChars(strings.ToLower(issue.Title), "-"))
			branchOpts := &gitlab.CreateBranchOptions{
				Branch: &sourceBranch,
				Ref:    &opts.TargetBranch,
			}

			_, err = api.CreateBranch(labClient, baseRepo.FullName(), branchOpts)
			if err != nil {
				for branchErr, branchCount := err, 1; branchErr != nil; branchCount++ {
					sourceBranch = fmt.Sprintf("%d-%s-%d", issue.IID, strings.ReplaceAll(strings.ToLower(issue.Title), " ", "-"), branchCount)
					_, branchErr = api.CreateBranch(labClient, baseRepo.FullName(), branchOpts)
				}
			}
			opts.SourceBranch = sourceBranch
		}
	} else {
		opts.TargetTrackingBranch = fmt.Sprintf("%s/%s", baseRepoRemote.Name, opts.TargetBranch)
		if opts.SourceBranch == opts.TargetBranch && glrepo.IsSame(baseRepo, headRepo) {
			fmt.Fprintf(opts.IO.StdErr, "You must be on a different branch other than %q\n", opts.TargetBranch)
			return cmdutils.SilentError
		}

		if opts.Autofill {
			if err = mrBodyAndTitle(opts); err != nil {
				return err
			}
			_, err = api.GetCommit(labClient, baseRepo.FullName(), opts.TargetBranch)
			if err != nil {
				return fmt.Errorf("target branch %s does not exist on remote. Specify target branch with the --target-branch flag",
					opts.TargetBranch)
			}

			opts.ShouldPush = true
		} else if opts.IsInteractive {
			var templateName string
			var templateContents string
			if opts.Description == "" {
				if opts.NoEditor {
					err = prompt.AskMultiline(&opts.Description, "description", "Description:", "")
					if err != nil {
						return err
					}
				} else {
					templateResponse := struct {
						Index int
					}{}
					templateNames, err := cmdutils.ListGitLabTemplates(cmdutils.MergeRequestTemplate)
					if err != nil {
						return fmt.Errorf("error getting templates: %w", err)
					}

					const mrWithCommitsTemplate = "Open a merge request with commit messages."
					const mrEmptyTemplate = "Open a blank merge request."

					templateNames = append(templateNames, mrWithCommitsTemplate)
					templateNames = append(templateNames, mrEmptyTemplate)

					selectQs := []*survey.Question{
						{
							Name: "index",
							Prompt: &survey.Select{
								Message: "Choose a template:",
								Options: templateNames,
							},
						},
					}

					if err := prompt.Ask(selectQs, &templateResponse); err != nil {
						return fmt.Errorf("could not prompt: %w", err)
					}

					templateName = templateNames[templateResponse.Index]
					if templateName == mrWithCommitsTemplate {
						// templateContents should be filled from commit messages
						commits, err := git.Commits(opts.TargetTrackingBranch, opts.SourceBranch)
						if err != nil {
							return fmt.Errorf("failed to get commits: %w", err)
						}
						templateContents, err = mrBody(commits, true)
						if err != nil {
							return err
						}
						if opts.Signoff {
							u, _ := api.CurrentUser(labClient)
							templateContents += "Signed-off-by: " + u.Name + "<" + u.Email + ">"
						}
					} else if templateName == mrEmptyTemplate {
						// blank merge request was choosen, leave templateContents empty
						if opts.Signoff {
							u, _ := api.CurrentUser(labClient)
							templateContents += "Signed-off-by: " + u.Name + "<" + u.Email + ">"
						}
					} else {
						templateContents, err = cmdutils.LoadGitLabTemplate(cmdutils.MergeRequestTemplate, templateName)
						if err != nil {
							return fmt.Errorf("failed to get template contents: %w", err)
						}
					}
				}
			}

			if opts.Title == "" {
				err = prompt.AskQuestionWithInput(&opts.Title, "title", "Title:", "", true)
				if err != nil {
					return err
				}
			}
			if opts.Description == "" {
				if opts.NoEditor {
					err = prompt.AskMultiline(&opts.Description, "description", "Description:", "")
					if err != nil {
						return err
					}
				} else {
					editor, err := cmdutils.GetEditor(opts.Config)
					if err != nil {
						return err
					}
					err = cmdutils.EditorPrompt(&opts.Description, "Description", templateContents, editor)
					if err != nil {
						return err
					}
				}
			}
		}
	}

	if opts.Title == "" {
		return fmt.Errorf("title can't be blank.")
	}

	if opts.IsDraft || opts.IsWIP {
		if opts.IsDraft {
			opts.Title = "Draft: " + opts.Title
		} else {
			opts.Title = "WIP: " + opts.Title
		}
	}
	mrCreateOpts.Title = &opts.Title
	mrCreateOpts.Description = &opts.Description
	mrCreateOpts.SourceBranch = &opts.SourceBranch
	mrCreateOpts.TargetBranch = &opts.TargetBranch

	if opts.AllowCollaboration {
		mrCreateOpts.AllowCollaboration = gitlab.Ptr(true)
	}

	if opts.RemoveSourceBranch {
		mrCreateOpts.RemoveSourceBranch = gitlab.Ptr(true)
	}

	if opts.SquashBeforeMerge {
		mrCreateOpts.Squash = gitlab.Ptr(true)
	}

	if opts.TargetProject != nil {
		mrCreateOpts.TargetProjectID = &opts.TargetProject.ID
	}

	if opts.CreateSourceBranch {
		lb := &gitlab.CreateBranchOptions{
			Branch: &opts.SourceBranch,
			Ref:    &opts.TargetBranch,
		}
		fmt.Fprintln(opts.IO.StdErr, "\nCreating related branch...")
		branch, err := api.CreateBranch(labClient, headRepo.FullName(), lb)
		if err == nil {
			fmt.Fprintln(opts.IO.StdErr, "Branch created: ", branch.WebURL)
		} else {
			fmt.Fprintln(opts.IO.StdErr, "Error creating branch: ", err.Error())
		}
	}

	var action cmdutils.Action

	// submit without prompting for non interactive mode
	if !opts.IsInteractive || opts.Yes {
		action = cmdutils.SubmitAction
	}

	if opts.Web {
		action = cmdutils.PreviewAction
	}

	if action == cmdutils.NoAction {
		action, err = cmdutils.ConfirmSubmission(true, true)
		if err != nil {
			return fmt.Errorf("unable to confirm: %w", err)
		}
	}

	if action == cmdutils.AddMetadataAction {
		metadataOptions := []string{
			"labels",
			"assignees",
			"milestones",
			"reviewers",
		}
		var metadataActions []string

		err := prompt.MultiSelect(&metadataActions, "metadata", "Which metadata types to add?", metadataOptions)
		if err != nil {
			return fmt.Errorf("failed to pick the metadata to add: %w", err)
		}

		for _, x := range metadataActions {
			if x == "labels" {
				err = cmdutils.LabelsPrompt(&opts.Labels, labClient, baseRepoRemote)
				if err != nil {
					return err
				}
			}
			if x == "assignees" {
				// Use minimum permission level 30 (Maintainer) as it is the minimum level
				// to accept a merge request
				err = cmdutils.UsersPrompt(&opts.Assignees, labClient, baseRepoRemote, opts.IO, 30, x)
				if err != nil {
					return err
				}
			}
			if x == "milestones" {
				err = cmdutils.MilestonesPrompt(&opts.Milestone, labClient, baseRepoRemote, opts.IO)
				if err != nil {
					return err
				}
			}
			if x == "reviewers" {
				// Use minimum permission level 30 (Maintainer) as it is the minimum level
				// to accept a merge request
				err = cmdutils.UsersPrompt(&opts.Reviewers, labClient, baseRepoRemote, opts.IO, 30, x)
				if err != nil {
					return err
				}
			}
		}

		// Ask the user again but don't permit AddMetadata a second time
		action, err = cmdutils.ConfirmSubmission(true, false)
		if err != nil {
			return err
		}
	}

	// This check protects against possibly dereferencing a nil pointer.
	if mrCreateOpts.Labels == nil {
		mrCreateOpts.Labels = &gitlab.LabelOptions{}
	}
	// These actions need to be done here, after the `Add metadata` prompt because
	// they are metadata that can be modified by the prompt
	*mrCreateOpts.Labels = append(*mrCreateOpts.Labels, opts.Labels...)

	if len(opts.Assignees) > 0 {
		users, err := api.UsersByNames(labClient, opts.Assignees)
		if err != nil {
			return err
		}
		mrCreateOpts.AssigneeIDs = cmdutils.IDsFromUsers(users)
	}

	if len(opts.Reviewers) > 0 {
		users, err := api.UsersByNames(labClient, opts.Reviewers)
		if err != nil {
			return err
		}
		mrCreateOpts.ReviewerIDs = cmdutils.IDsFromUsers(users)
	}

	if opts.Milestone != 0 {
		mrCreateOpts.MilestoneID = gitlab.Ptr(opts.Milestone)
	}

	if action == cmdutils.CancelAction {
		fmt.Fprintln(opts.IO.StdErr, "Discarded.")
		return nil
	}

	if err := handlePush(opts, headRepoRemote); err != nil {
		return err
	}

	if action == cmdutils.PreviewAction {
		return previewMR(opts)
	}

	if action == cmdutils.SubmitAction {
		message := "\nCreating merge request for %s into %s in %s\n\n"
		if opts.IsDraft || opts.IsWIP {
			message = "\nCreating draft merge request for %s into %s in %s\n\n"
		}

		fmt.Fprintf(opts.IO.StdErr, message, c.Cyan(opts.SourceBranch), c.Cyan(opts.TargetBranch), baseRepo.FullName())

		// It is intentional that we create against the head repo, it is necessary
		// for cross-repository merge requests
		mr, err := api.CreateMR(labClient, headRepo.FullName(), mrCreateOpts)
		if err != nil {
			return err
		}

		fmt.Fprintln(out, mrutils.DisplayMR(c, &mr.BasicMergeRequest, opts.IO.IsaTTY))
		return nil
	}

	return errors.New("expected to cancel, preview in browser, or submit.")
}