Repository: jesseduffield/lazygit
File: pkg/gui/controllers/helpers/refs_helper.go (Go)

Code before:
		Title: self.c.Tr.Actions.CheckoutBranchOrCommit,
		Items: menuItems,
	})
}

func (self *RefsHelper) NewBranch(from string, fromFormattedName string, suggestedBranchName string) error {
	message := utils.ResolvePlaceholderString(
		self.c.Tr.NewBranchNameBranchOff,
		map[string]string{
			"branchName": fromFormattedName,
		},
	)

	if suggestedBranchName == "" {
		var err error

		suggestedBranchName, err = utils.ResolveTemplate(self.c.UserConfig().Git.BranchPrefix, nil, template.FuncMap{
			"runCommand": func(command string) (string, error) {
				output, err := self.c.Git().Custom.RunWithOutput(command)
				if err != nil {
					return "", err
				}

				output = strings.TrimRight(output, "\r\n")

				if strings.ContainsAny(output, "\r\n\t ") {
					return "", fmt.Errorf("command output contains whitespace characters: %s", output)
				}

				return output, nil
			},
		})
		if err != nil {
			return err
		}
	}

	refresh := func() error {
		if self.c.Context().Current() != self.c.Contexts().Branches {
			self.c.Context().Push(self.c.Contexts().Branches)
		}

		self.c.Contexts().LocalCommits.SetSelection(0)
		self.c.Contexts().Branches.SetSelection(0)

		return self.c.Refresh(types.RefreshOptions{Mode: types.BLOCK_UI, KeepBranchSelectionIndex: true})
	}

	self.c.Prompt(types.PromptOpts{
		Title:          message,
		InitialContent: suggestedBranchName,

Review comment (security):
We don't need to forbid spaces, they are fine; you can type them as a user too, they will get replaced by dashes later (in `SanitizedBranchName`). Tabs are not handled though, and maybe we should simply replace them with spaces, for simplicity (after the call to `ResolveTemplate`). In the shared helper function you'd then only check for `\r\n`.

Code after:
		},
	)

	if suggestedBranchName == "" {
		var err error

		suggestedBranchName, err = utils.ResolveTemplate(self.c.UserConfig().Git.BranchPrefix, nil, template.FuncMap{
			"runCommand": self.c.Git().Custom.TemplateFunctionRunCommand,
		})
		if err != nil {
			return err
		}
		suggestedBranchName = strings.ReplaceAll(suggestedBranchName, "\t", " ")
	}

	refresh := func() error {
		if self.c.Context().Current() != self.c.Contexts().Branches {
			self.c.Context().Push(self.c.Contexts().Branches, types.OnFocusOpts{})
		}

		self.c.Contexts().LocalCommits.SetSelection(0)
		self.c.Contexts().Branches.SetSelection(0)

		return self.c.Refresh(types.RefreshOptions{Mode: types.BLOCK_UI, KeepBranchSelectionIndex: true})
	}

	self.c.Prompt(types.PromptOpts{
		Title:          message,
		InitialContent: suggestedBranchName,
		HandleConfirm: func(response string) error {
			self.c.LogAction(self.c.Tr.Actions.CreateBranch)
			newBranchName := SanitizedBranchName(response)
			newBranchFunc := self.c.Git().Branch.New
			if newBranchName != suggestedBranchName {
				newBranchFunc = self.c.Git().Branch.NewWithoutTracking
			}
			if err := newBranchFunc(newBranchName, from); err != nil {
				if IsSwitchBranchUncommittedChangesError(err) {
					// offer to autostash changes
					self.c.Confirm(types.ConfirmOpts{
						Title:  self.c.Tr.AutoStashTitle,
						Prompt: self.c.Tr.AutoStashPrompt,
						HandleConfirm: func() error {
							if err := self.c.Git().Stash.Push(self.c.Tr.StashPrefix + newBranchName); err != nil {
								return err
							}
							if err := newBranchFunc(newBranchName, from); err != nil {
								return err
							}
							popErr := self.c.Git().Stash.Pop(0)
							// Branch switch successful so re-render the UI even if the pop operation failed (e.g. conflict).