1
0
mirror of https://github.com/jesseduffield/lazygit.git synced 2025-02-03 13:21:56 +02:00
Jesse Duffield 63dc07fded Construct arg vector manually rather than parse string
By constructing an arg vector manually, we no longer need to quote arguments

Mandate that args must be passed when building a command

Now you need to provide an args array when building a command.
There are a handful of places where we need to deal with a string,
such as with user-defined custom commands, and for those we now require
that at the callsite they use str.ToArgv to do that. I don't want
to provide a method out of the box for it because I want to discourage its
use.

For some reason we were invoking a command through a shell when amending a
commit, and I don't believe we needed to do that as there was nothing user-
supplied about the command. So I've switched to using a regular command out-
side the shell there
2023-05-23 19:49:19 +10:00

219 lines
6.0 KiB
Go

package git_commands
import (
"fmt"
"strings"
"github.com/jesseduffield/lazygit/pkg/commands/oscommands"
"github.com/jesseduffield/lazygit/pkg/utils"
"github.com/mgutz/str"
)
type BranchCommands struct {
*GitCommon
}
func NewBranchCommands(gitCommon *GitCommon) *BranchCommands {
return &BranchCommands{
GitCommon: gitCommon,
}
}
// New creates a new branch
func (self *BranchCommands) New(name string, base string) error {
cmdArgs := NewGitCmd("checkout").
Arg("-b", name, base).
ToArgv()
return self.cmd.New(cmdArgs).Run()
}
// CurrentBranchInfo get the current branch information.
func (self *BranchCommands) CurrentBranchInfo() (BranchInfo, error) {
branchName, err := self.cmd.New(
NewGitCmd("symbolic-ref").
Arg("--short", "HEAD").
ToArgv(),
).DontLog().RunWithOutput()
if err == nil && branchName != "HEAD\n" {
trimmedBranchName := strings.TrimSpace(branchName)
return BranchInfo{
RefName: trimmedBranchName,
DisplayName: trimmedBranchName,
DetachedHead: false,
}, nil
}
output, err := self.cmd.New(
NewGitCmd("branch").
Arg("--points-at=HEAD", "--format=%(HEAD)%00%(objectname)%00%(refname)").
ToArgv(),
).DontLog().RunWithOutput()
if err != nil {
return BranchInfo{}, err
}
for _, line := range utils.SplitLines(output) {
split := strings.Split(strings.TrimRight(line, "\r\n"), "\x00")
if len(split) == 3 && split[0] == "*" {
sha := split[1]
displayName := split[2]
return BranchInfo{
RefName: sha,
DisplayName: displayName,
DetachedHead: true,
}, nil
}
}
return BranchInfo{
RefName: "HEAD",
DisplayName: "HEAD",
DetachedHead: true,
}, nil
}
// Delete delete branch
func (self *BranchCommands) Delete(branch string, force bool) error {
cmdArgs := NewGitCmd("branch").
ArgIfElse(force, "-D", "-d").
Arg(branch).
ToArgv()
return self.cmd.New(cmdArgs).Run()
}
// Checkout checks out a branch (or commit), with --force if you set the force arg to true
type CheckoutOptions struct {
Force bool
EnvVars []string
}
func (self *BranchCommands) Checkout(branch string, options CheckoutOptions) error {
cmdArgs := NewGitCmd("checkout").
ArgIf(options.Force, "--force").
Arg(branch).
ToArgv()
return self.cmd.New(cmdArgs).
// prevents git from prompting us for input which would freeze the program
// TODO: see if this is actually needed here
AddEnvVars("GIT_TERMINAL_PROMPT=0").
AddEnvVars(options.EnvVars...).
Run()
}
// GetGraph gets the color-formatted graph of the log for the given branch
// Currently it limits the result to 100 commits, but when we get async stuff
// working we can do lazy loading
func (self *BranchCommands) GetGraph(branchName string) (string, error) {
return self.GetGraphCmdObj(branchName).DontLog().RunWithOutput()
}
func (self *BranchCommands) GetGraphCmdObj(branchName string) oscommands.ICmdObj {
branchLogCmdTemplate := self.UserConfig.Git.BranchLogCmd
templateValues := map[string]string{
"branchName": self.cmd.Quote(branchName),
}
resolvedTemplate := utils.ResolvePlaceholderString(branchLogCmdTemplate, templateValues)
return self.cmd.New(str.ToArgv(resolvedTemplate)).DontLog()
}
func (self *BranchCommands) SetCurrentBranchUpstream(remoteName string, remoteBranchName string) error {
cmdArgs := NewGitCmd("branch").
Arg(fmt.Sprintf("--set-upstream-to=%s/%s", remoteName, remoteBranchName)).
ToArgv()
return self.cmd.New(cmdArgs).Run()
}
func (self *BranchCommands) SetUpstream(remoteName string, remoteBranchName string, branchName string) error {
cmdArgs := NewGitCmd("branch").
Arg(fmt.Sprintf("--set-upstream-to=%s/%s", remoteName, remoteBranchName)).
Arg(branchName).
ToArgv()
return self.cmd.New(cmdArgs).Run()
}
func (self *BranchCommands) UnsetUpstream(branchName string) error {
cmdArgs := NewGitCmd("branch").Arg("--unset-upstream", branchName).
ToArgv()
return self.cmd.New(cmdArgs).Run()
}
func (self *BranchCommands) GetCurrentBranchUpstreamDifferenceCount() (string, string) {
return self.GetCommitDifferences("HEAD", "HEAD@{u}")
}
func (self *BranchCommands) GetUpstreamDifferenceCount(branchName string) (string, string) {
return self.GetCommitDifferences(branchName, branchName+"@{u}")
}
// GetCommitDifferences checks how many pushables/pullables there are for the
// current branch
func (self *BranchCommands) GetCommitDifferences(from, to string) (string, string) {
pushableCount, err := self.countDifferences(to, from)
if err != nil {
return "?", "?"
}
pullableCount, err := self.countDifferences(from, to)
if err != nil {
return "?", "?"
}
return strings.TrimSpace(pushableCount), strings.TrimSpace(pullableCount)
}
func (self *BranchCommands) countDifferences(from, to string) (string, error) {
cmdArgs := NewGitCmd("rev-list").
Arg(fmt.Sprintf("%s..%s", from, to)).
Arg("--count").
ToArgv()
return self.cmd.New(cmdArgs).DontLog().RunWithOutput()
}
func (self *BranchCommands) IsHeadDetached() bool {
cmdArgs := NewGitCmd("symbolic-ref").Arg("-q", "HEAD").ToArgv()
err := self.cmd.New(cmdArgs).DontLog().Run()
return err != nil
}
func (self *BranchCommands) Rename(oldName string, newName string) error {
cmdArgs := NewGitCmd("branch").
Arg("--move", oldName, newName).
ToArgv()
return self.cmd.New(cmdArgs).Run()
}
func (self *BranchCommands) GetRawBranches() (string, error) {
cmdArgs := NewGitCmd("for-each-ref").
Arg("--sort=-committerdate").
Arg(`--format=%(HEAD)%00%(refname:short)%00%(upstream:short)%00%(upstream:track)`).
Arg("refs/heads").
ToArgv()
return self.cmd.New(cmdArgs).DontLog().RunWithOutput()
}
type MergeOpts struct {
FastForwardOnly bool
}
func (self *BranchCommands) Merge(branchName string, opts MergeOpts) error {
cmdArgs := NewGitCmd("merge").
Arg("--no-edit").
ArgIf(self.UserConfig.Git.Merging.Args != "", self.UserConfig.Git.Merging.Args).
ArgIf(opts.FastForwardOnly, "--ff-only").
Arg(branchName).
ToArgv()
return self.cmd.New(cmdArgs).Run()
}
func (self *BranchCommands) AllBranchesLogCmdObj() oscommands.ICmdObj {
return self.cmd.New(str.ToArgv(self.UserConfig.Git.AllBranchesLogCmd)).DontLog()
}