mirror of
https://github.com/jesseduffield/lazygit.git
synced 2025-08-10 22:42:00 +02:00
Merge pull request #2645 from jesseduffield/convenient-git-command-building
This commit is contained in:
@@ -117,8 +117,7 @@ func NewGitCommandAux(
|
|||||||
workingTreeCommands := git_commands.NewWorkingTreeCommands(gitCommon, submoduleCommands, fileLoader)
|
workingTreeCommands := git_commands.NewWorkingTreeCommands(gitCommon, submoduleCommands, fileLoader)
|
||||||
rebaseCommands := git_commands.NewRebaseCommands(gitCommon, commitCommands, workingTreeCommands)
|
rebaseCommands := git_commands.NewRebaseCommands(gitCommon, commitCommands, workingTreeCommands)
|
||||||
stashCommands := git_commands.NewStashCommands(gitCommon, fileLoader, workingTreeCommands)
|
stashCommands := git_commands.NewStashCommands(gitCommon, fileLoader, workingTreeCommands)
|
||||||
// TODO: have patch builder take workingTreeCommands in its entirety
|
patchBuilder := patch.NewPatchBuilder(cmn.Log,
|
||||||
patchBuilder := patch.NewPatchBuilder(cmn.Log, workingTreeCommands.ApplyPatch,
|
|
||||||
func(from string, to string, reverse bool, filename string, plain bool) (string, error) {
|
func(from string, to string, reverse bool, filename string, plain bool) (string, error) {
|
||||||
// TODO: make patch builder take Gui.IgnoreWhitespaceInDiffView into
|
// TODO: make patch builder take Gui.IgnoreWhitespaceInDiffView into
|
||||||
// account. For now we just pass false.
|
// account. For now we just pass false.
|
||||||
|
@@ -1,7 +1,6 @@
|
|||||||
package git_commands
|
package git_commands
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"strings"
|
"strings"
|
||||||
@@ -98,13 +97,15 @@ func (self *BisectCommands) GetInfo() *BisectInfo {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (self *BisectCommands) Reset() error {
|
func (self *BisectCommands) Reset() error {
|
||||||
return self.cmd.New("git bisect reset").StreamOutput().Run()
|
cmdStr := NewGitCmd("bisect").Arg("reset").ToString()
|
||||||
|
|
||||||
|
return self.cmd.New(cmdStr).StreamOutput().Run()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (self *BisectCommands) Mark(ref string, term string) error {
|
func (self *BisectCommands) Mark(ref string, term string) error {
|
||||||
return self.cmd.New(
|
cmdStr := NewGitCmd("bisect").Arg(term, ref).ToString()
|
||||||
fmt.Sprintf("git bisect %s %s", term, ref),
|
|
||||||
).
|
return self.cmd.New(cmdStr).
|
||||||
IgnoreEmptyError().
|
IgnoreEmptyError().
|
||||||
StreamOutput().
|
StreamOutput().
|
||||||
Run()
|
Run()
|
||||||
@@ -115,7 +116,9 @@ func (self *BisectCommands) Skip(ref string) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (self *BisectCommands) Start() error {
|
func (self *BisectCommands) Start() error {
|
||||||
return self.cmd.New("git bisect start").StreamOutput().Run()
|
cmdStr := NewGitCmd("bisect").Arg("start").ToString()
|
||||||
|
|
||||||
|
return self.cmd.New(cmdStr).StreamOutput().Run()
|
||||||
}
|
}
|
||||||
|
|
||||||
// tells us whether we've found our problem commit(s). We return a string slice of
|
// tells us whether we've found our problem commit(s). We return a string slice of
|
||||||
@@ -137,7 +140,8 @@ func (self *BisectCommands) IsDone() (bool, []string, error) {
|
|||||||
done := false
|
done := false
|
||||||
candidates := []string{}
|
candidates := []string{}
|
||||||
|
|
||||||
err := self.cmd.New(fmt.Sprintf("git rev-list %s", newSha)).RunAndProcessLines(func(line string) (bool, error) {
|
cmdStr := NewGitCmd("rev-list").Arg(newSha).ToString()
|
||||||
|
err := self.cmd.New(cmdStr).RunAndProcessLines(func(line string) (bool, error) {
|
||||||
sha := strings.TrimSpace(line)
|
sha := strings.TrimSpace(line)
|
||||||
|
|
||||||
if status, ok := info.statusMap[sha]; ok {
|
if status, ok := info.statusMap[sha]; ok {
|
||||||
@@ -167,9 +171,11 @@ func (self *BisectCommands) IsDone() (bool, []string, error) {
|
|||||||
// bisecting is actually a descendant of our current bisect commit. If it's not, we need to
|
// bisecting is actually a descendant of our current bisect commit. If it's not, we need to
|
||||||
// render the commits from the bad commit.
|
// render the commits from the bad commit.
|
||||||
func (self *BisectCommands) ReachableFromStart(bisectInfo *BisectInfo) bool {
|
func (self *BisectCommands) ReachableFromStart(bisectInfo *BisectInfo) bool {
|
||||||
err := self.cmd.New(
|
cmdStr := NewGitCmd("merge-base").
|
||||||
fmt.Sprintf("git merge-base --is-ancestor %s %s", bisectInfo.GetNewSha(), bisectInfo.GetStartSha()),
|
Arg("--is-ancestor", bisectInfo.GetNewSha(), bisectInfo.GetStartSha()).
|
||||||
).DontLog().Run()
|
ToString()
|
||||||
|
|
||||||
|
err := self.cmd.New(cmdStr).DontLog().Run()
|
||||||
|
|
||||||
return err == nil
|
return err == nil
|
||||||
}
|
}
|
||||||
|
@@ -20,12 +20,20 @@ func NewBranchCommands(gitCommon *GitCommon) *BranchCommands {
|
|||||||
|
|
||||||
// New creates a new branch
|
// New creates a new branch
|
||||||
func (self *BranchCommands) New(name string, base string) error {
|
func (self *BranchCommands) New(name string, base string) error {
|
||||||
return self.cmd.New(fmt.Sprintf("git checkout -b %s %s", self.cmd.Quote(name), self.cmd.Quote(base))).Run()
|
cmdStr := NewGitCmd("checkout").
|
||||||
|
Arg("-b", self.cmd.Quote(name), self.cmd.Quote(base)).
|
||||||
|
ToString()
|
||||||
|
|
||||||
|
return self.cmd.New(cmdStr).Run()
|
||||||
}
|
}
|
||||||
|
|
||||||
// CurrentBranchInfo get the current branch information.
|
// CurrentBranchInfo get the current branch information.
|
||||||
func (self *BranchCommands) CurrentBranchInfo() (BranchInfo, error) {
|
func (self *BranchCommands) CurrentBranchInfo() (BranchInfo, error) {
|
||||||
branchName, err := self.cmd.New("git symbolic-ref --short HEAD").DontLog().RunWithOutput()
|
branchName, err := self.cmd.New(
|
||||||
|
NewGitCmd("symbolic-ref").
|
||||||
|
Arg("--short", "HEAD").
|
||||||
|
ToString(),
|
||||||
|
).DontLog().RunWithOutput()
|
||||||
if err == nil && branchName != "HEAD\n" {
|
if err == nil && branchName != "HEAD\n" {
|
||||||
trimmedBranchName := strings.TrimSpace(branchName)
|
trimmedBranchName := strings.TrimSpace(branchName)
|
||||||
return BranchInfo{
|
return BranchInfo{
|
||||||
@@ -34,7 +42,11 @@ func (self *BranchCommands) CurrentBranchInfo() (BranchInfo, error) {
|
|||||||
DetachedHead: false,
|
DetachedHead: false,
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
output, err := self.cmd.New(`git branch --points-at=HEAD --format="%(HEAD)%00%(objectname)%00%(refname)"`).DontLog().RunWithOutput()
|
output, err := self.cmd.New(
|
||||||
|
NewGitCmd("branch").
|
||||||
|
Arg("--points-at=HEAD", "--format=\"%(HEAD)%00%(objectname)%00%(refname)\"").
|
||||||
|
ToString(),
|
||||||
|
).DontLog().RunWithOutput()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return BranchInfo{}, err
|
return BranchInfo{}, err
|
||||||
}
|
}
|
||||||
@@ -59,13 +71,12 @@ func (self *BranchCommands) CurrentBranchInfo() (BranchInfo, error) {
|
|||||||
|
|
||||||
// Delete delete branch
|
// Delete delete branch
|
||||||
func (self *BranchCommands) Delete(branch string, force bool) error {
|
func (self *BranchCommands) Delete(branch string, force bool) error {
|
||||||
command := "git branch -d"
|
cmdStr := NewGitCmd("branch").
|
||||||
|
ArgIfElse(force, "-D", "-d").
|
||||||
|
Arg(self.cmd.Quote(branch)).
|
||||||
|
ToString()
|
||||||
|
|
||||||
if force {
|
return self.cmd.New(cmdStr).Run()
|
||||||
command = "git branch -D"
|
|
||||||
}
|
|
||||||
|
|
||||||
return self.cmd.New(fmt.Sprintf("%s %s", command, self.cmd.Quote(branch))).Run()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Checkout checks out a branch (or commit), with --force if you set the force arg to true
|
// Checkout checks out a branch (or commit), with --force if you set the force arg to true
|
||||||
@@ -75,12 +86,12 @@ type CheckoutOptions struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (self *BranchCommands) Checkout(branch string, options CheckoutOptions) error {
|
func (self *BranchCommands) Checkout(branch string, options CheckoutOptions) error {
|
||||||
forceArg := ""
|
cmdStr := NewGitCmd("checkout").
|
||||||
if options.Force {
|
ArgIf(options.Force, "--force").
|
||||||
forceArg = " --force"
|
Arg(self.cmd.Quote(branch)).
|
||||||
}
|
ToString()
|
||||||
|
|
||||||
return self.cmd.New(fmt.Sprintf("git checkout%s %s", forceArg, self.cmd.Quote(branch))).
|
return self.cmd.New(cmdStr).
|
||||||
// prevents git from prompting us for input which would freeze the program
|
// prevents git from prompting us for input which would freeze the program
|
||||||
// TODO: see if this is actually needed here
|
// TODO: see if this is actually needed here
|
||||||
AddEnvVars("GIT_TERMINAL_PROMPT=0").
|
AddEnvVars("GIT_TERMINAL_PROMPT=0").
|
||||||
@@ -104,15 +115,27 @@ func (self *BranchCommands) GetGraphCmdObj(branchName string) oscommands.ICmdObj
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (self *BranchCommands) SetCurrentBranchUpstream(remoteName string, remoteBranchName string) error {
|
func (self *BranchCommands) SetCurrentBranchUpstream(remoteName string, remoteBranchName string) error {
|
||||||
return self.cmd.New(fmt.Sprintf("git branch --set-upstream-to=%s/%s", self.cmd.Quote(remoteName), self.cmd.Quote(remoteBranchName))).Run()
|
cmdStr := NewGitCmd("branch").
|
||||||
|
Arg(fmt.Sprintf("--set-upstream-to=%s/%s", self.cmd.Quote(remoteName), self.cmd.Quote(remoteBranchName))).
|
||||||
|
ToString()
|
||||||
|
|
||||||
|
return self.cmd.New(cmdStr).Run()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (self *BranchCommands) SetUpstream(remoteName string, remoteBranchName string, branchName string) error {
|
func (self *BranchCommands) SetUpstream(remoteName string, remoteBranchName string, branchName string) error {
|
||||||
return self.cmd.New(fmt.Sprintf("git branch --set-upstream-to=%s/%s %s", self.cmd.Quote(remoteName), self.cmd.Quote(remoteBranchName), self.cmd.Quote(branchName))).Run()
|
cmdStr := NewGitCmd("branch").
|
||||||
|
Arg(fmt.Sprintf("--set-upstream-to=%s/%s", self.cmd.Quote(remoteName), self.cmd.Quote(remoteBranchName))).
|
||||||
|
Arg(self.cmd.Quote(branchName)).
|
||||||
|
ToString()
|
||||||
|
|
||||||
|
return self.cmd.New(cmdStr).Run()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (self *BranchCommands) UnsetUpstream(branchName string) error {
|
func (self *BranchCommands) UnsetUpstream(branchName string) error {
|
||||||
return self.cmd.New(fmt.Sprintf("git branch --unset-upstream %s", self.cmd.Quote(branchName))).Run()
|
cmdStr := NewGitCmd("branch").Arg("--unset-upstream", self.cmd.Quote(branchName)).
|
||||||
|
ToString()
|
||||||
|
|
||||||
|
return self.cmd.New(cmdStr).Run()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (self *BranchCommands) GetCurrentBranchUpstreamDifferenceCount() (string, string) {
|
func (self *BranchCommands) GetCurrentBranchUpstreamDifferenceCount() (string, string) {
|
||||||
@@ -126,29 +149,49 @@ func (self *BranchCommands) GetUpstreamDifferenceCount(branchName string) (strin
|
|||||||
// GetCommitDifferences checks how many pushables/pullables there are for the
|
// GetCommitDifferences checks how many pushables/pullables there are for the
|
||||||
// current branch
|
// current branch
|
||||||
func (self *BranchCommands) GetCommitDifferences(from, to string) (string, string) {
|
func (self *BranchCommands) GetCommitDifferences(from, to string) (string, string) {
|
||||||
command := "git rev-list %s..%s --count"
|
pushableCount, err := self.countDifferences(to, from)
|
||||||
pushableCount, err := self.cmd.New(fmt.Sprintf(command, to, from)).DontLog().RunWithOutput()
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "?", "?"
|
return "?", "?"
|
||||||
}
|
}
|
||||||
pullableCount, err := self.cmd.New(fmt.Sprintf(command, from, to)).DontLog().RunWithOutput()
|
pullableCount, err := self.countDifferences(from, to)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "?", "?"
|
return "?", "?"
|
||||||
}
|
}
|
||||||
return strings.TrimSpace(pushableCount), strings.TrimSpace(pullableCount)
|
return strings.TrimSpace(pushableCount), strings.TrimSpace(pullableCount)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (self *BranchCommands) countDifferences(from, to string) (string, error) {
|
||||||
|
cmdStr := NewGitCmd("rev-list").
|
||||||
|
Arg(fmt.Sprintf("%s..%s", from, to)).
|
||||||
|
Arg("--count").
|
||||||
|
ToString()
|
||||||
|
|
||||||
|
return self.cmd.New(cmdStr).DontLog().RunWithOutput()
|
||||||
|
}
|
||||||
|
|
||||||
func (self *BranchCommands) IsHeadDetached() bool {
|
func (self *BranchCommands) IsHeadDetached() bool {
|
||||||
err := self.cmd.New("git symbolic-ref -q HEAD").DontLog().Run()
|
cmdStr := NewGitCmd("symbolic-ref").Arg("-q", "HEAD").ToString()
|
||||||
|
|
||||||
|
err := self.cmd.New(cmdStr).DontLog().Run()
|
||||||
return err != nil
|
return err != nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (self *BranchCommands) Rename(oldName string, newName string) error {
|
func (self *BranchCommands) Rename(oldName string, newName string) error {
|
||||||
return self.cmd.New(fmt.Sprintf("git branch --move %s %s", self.cmd.Quote(oldName), self.cmd.Quote(newName))).Run()
|
cmdStr := NewGitCmd("branch").
|
||||||
|
Arg("--move", self.cmd.Quote(oldName), self.cmd.Quote(newName)).
|
||||||
|
ToString()
|
||||||
|
|
||||||
|
return self.cmd.New(cmdStr).Run()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (self *BranchCommands) GetRawBranches() (string, error) {
|
func (self *BranchCommands) GetRawBranches() (string, error) {
|
||||||
return self.cmd.New(`git for-each-ref --sort=-committerdate --format="%(HEAD)%00%(refname:short)%00%(upstream:short)%00%(upstream:track)" refs/heads`).DontLog().RunWithOutput()
|
cmdStr := NewGitCmd("for-each-ref").
|
||||||
|
Arg("--sort=-committerdate").
|
||||||
|
Arg(`--format="%(HEAD)%00%(refname:short)%00%(upstream:short)%00%(upstream:track)"`).
|
||||||
|
Arg("refs/heads").
|
||||||
|
ToString()
|
||||||
|
|
||||||
|
return self.cmd.New(cmdStr).DontLog().RunWithOutput()
|
||||||
}
|
}
|
||||||
|
|
||||||
type MergeOpts struct {
|
type MergeOpts struct {
|
||||||
@@ -156,15 +199,12 @@ type MergeOpts struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (self *BranchCommands) Merge(branchName string, opts MergeOpts) error {
|
func (self *BranchCommands) Merge(branchName string, opts MergeOpts) error {
|
||||||
mergeArg := ""
|
command := NewGitCmd("merge").
|
||||||
if self.UserConfig.Git.Merging.Args != "" {
|
Arg("--no-edit").
|
||||||
mergeArg = " " + self.UserConfig.Git.Merging.Args
|
ArgIf(self.UserConfig.Git.Merging.Args != "", self.UserConfig.Git.Merging.Args).
|
||||||
}
|
ArgIf(opts.FastForwardOnly, "--ff-only").
|
||||||
|
Arg(self.cmd.Quote(branchName)).
|
||||||
command := fmt.Sprintf("git merge --no-edit%s %s", mergeArg, self.cmd.Quote(branchName))
|
ToString()
|
||||||
if opts.FastForwardOnly {
|
|
||||||
command = fmt.Sprintf("%s --ff-only", command)
|
|
||||||
}
|
|
||||||
|
|
||||||
return self.cmd.New(command).Run()
|
return self.cmd.New(command).Run()
|
||||||
}
|
}
|
||||||
|
@@ -5,6 +5,7 @@ import (
|
|||||||
|
|
||||||
"github.com/go-errors/errors"
|
"github.com/go-errors/errors"
|
||||||
"github.com/jesseduffield/lazygit/pkg/commands/oscommands"
|
"github.com/jesseduffield/lazygit/pkg/commands/oscommands"
|
||||||
|
"github.com/jesseduffield/lazygit/pkg/config"
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -99,12 +100,53 @@ func TestBranchDeleteBranch(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestBranchMerge(t *testing.T) {
|
func TestBranchMerge(t *testing.T) {
|
||||||
runner := oscommands.NewFakeRunner(t).
|
scenarios := []struct {
|
||||||
Expect(`git merge --no-edit "test"`, "", nil)
|
testName string
|
||||||
instance := buildBranchCommands(commonDeps{runner: runner})
|
userConfig *config.UserConfig
|
||||||
|
opts MergeOpts
|
||||||
|
branchName string
|
||||||
|
expected string
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
testName: "basic",
|
||||||
|
userConfig: &config.UserConfig{},
|
||||||
|
opts: MergeOpts{},
|
||||||
|
branchName: "mybranch",
|
||||||
|
expected: `git merge --no-edit "mybranch"`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
testName: "merging args",
|
||||||
|
userConfig: &config.UserConfig{
|
||||||
|
Git: config.GitConfig{
|
||||||
|
Merging: config.MergingConfig{
|
||||||
|
Args: "--merging-args", // it's up to the user what they put here
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
opts: MergeOpts{},
|
||||||
|
branchName: "mybranch",
|
||||||
|
expected: `git merge --no-edit --merging-args "mybranch"`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
testName: "fast forward only",
|
||||||
|
userConfig: &config.UserConfig{},
|
||||||
|
opts: MergeOpts{FastForwardOnly: true},
|
||||||
|
branchName: "mybranch",
|
||||||
|
expected: `git merge --no-edit --ff-only "mybranch"`,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
assert.NoError(t, instance.Merge("test", MergeOpts{}))
|
for _, s := range scenarios {
|
||||||
|
s := s
|
||||||
|
t.Run(s.testName, func(t *testing.T) {
|
||||||
|
runner := oscommands.NewFakeRunner(t).
|
||||||
|
Expect(s.expected, "", nil)
|
||||||
|
instance := buildBranchCommands(commonDeps{runner: runner, userConfig: s.userConfig})
|
||||||
|
|
||||||
|
assert.NoError(t, instance.Merge(s.branchName, s.opts))
|
||||||
runner.CheckForMissingCalls()
|
runner.CheckForMissingCalls()
|
||||||
|
})
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestBranchCheckout(t *testing.T) {
|
func TestBranchCheckout(t *testing.T) {
|
||||||
|
@@ -22,18 +22,27 @@ func NewCommitCommands(gitCommon *GitCommon) *CommitCommands {
|
|||||||
|
|
||||||
// ResetAuthor resets the author of the topmost commit
|
// ResetAuthor resets the author of the topmost commit
|
||||||
func (self *CommitCommands) ResetAuthor() error {
|
func (self *CommitCommands) ResetAuthor() error {
|
||||||
return self.cmd.New("git commit --allow-empty --only --no-edit --amend --reset-author").Run()
|
cmdStr := NewGitCmd("commit").
|
||||||
|
Arg("--allow-empty", "--only", "--no-edit", "--amend", "--reset-author").
|
||||||
|
ToString()
|
||||||
|
|
||||||
|
return self.cmd.New(cmdStr).Run()
|
||||||
}
|
}
|
||||||
|
|
||||||
// Sets the commit's author to the supplied value. Value is expected to be of the form 'Name <Email>'
|
// Sets the commit's author to the supplied value. Value is expected to be of the form 'Name <Email>'
|
||||||
func (self *CommitCommands) SetAuthor(value string) error {
|
func (self *CommitCommands) SetAuthor(value string) error {
|
||||||
commandStr := fmt.Sprintf("git commit --allow-empty --only --no-edit --amend --author=%s", self.cmd.Quote(value))
|
cmdStr := NewGitCmd("commit").
|
||||||
return self.cmd.New(commandStr).Run()
|
Arg("--allow-empty", "--only", "--no-edit", "--amend", "--author="+self.cmd.Quote(value)).
|
||||||
|
ToString()
|
||||||
|
|
||||||
|
return self.cmd.New(cmdStr).Run()
|
||||||
}
|
}
|
||||||
|
|
||||||
// ResetToCommit reset to commit
|
// ResetToCommit reset to commit
|
||||||
func (self *CommitCommands) ResetToCommit(sha string, strength string, envVars []string) error {
|
func (self *CommitCommands) ResetToCommit(sha string, strength string, envVars []string) error {
|
||||||
return self.cmd.New(fmt.Sprintf("git reset --%s %s", strength, sha)).
|
cmdStr := NewGitCmd("reset").Arg("--"+strength, sha).ToString()
|
||||||
|
|
||||||
|
return self.cmd.New(cmdStr).
|
||||||
// prevents git from prompting us for input which would freeze the program
|
// prevents git from prompting us for input which would freeze the program
|
||||||
// TODO: see if this is actually needed here
|
// TODO: see if this is actually needed here
|
||||||
AddEnvVars("GIT_TERMINAL_PROMPT=0").
|
AddEnvVars("GIT_TERMINAL_PROMPT=0").
|
||||||
@@ -45,38 +54,52 @@ func (self *CommitCommands) CommitCmdObj(message string) oscommands.ICmdObj {
|
|||||||
messageArgs := self.commitMessageArgs(message)
|
messageArgs := self.commitMessageArgs(message)
|
||||||
|
|
||||||
skipHookPrefix := self.UserConfig.Git.SkipHookPrefix
|
skipHookPrefix := self.UserConfig.Git.SkipHookPrefix
|
||||||
noVerifyFlag := ""
|
|
||||||
if skipHookPrefix != "" && strings.HasPrefix(message, skipHookPrefix) {
|
|
||||||
noVerifyFlag = " --no-verify"
|
|
||||||
}
|
|
||||||
|
|
||||||
return self.cmd.New(fmt.Sprintf("git commit%s%s%s", noVerifyFlag, self.signoffFlag(), messageArgs))
|
cmdStr := NewGitCmd("commit").
|
||||||
|
ArgIf(skipHookPrefix != "" && strings.HasPrefix(message, skipHookPrefix), "--no-verify").
|
||||||
|
ArgIf(self.signoffFlag() != "", self.signoffFlag()).
|
||||||
|
Arg(messageArgs...).
|
||||||
|
ToString()
|
||||||
|
|
||||||
|
return self.cmd.New(cmdStr)
|
||||||
}
|
}
|
||||||
|
|
||||||
// RewordLastCommit rewords the topmost commit with the given message
|
// RewordLastCommit rewords the topmost commit with the given message
|
||||||
func (self *CommitCommands) RewordLastCommit(message string) error {
|
func (self *CommitCommands) RewordLastCommit(message string) error {
|
||||||
messageArgs := self.commitMessageArgs(message)
|
messageArgs := self.commitMessageArgs(message)
|
||||||
return self.cmd.New(fmt.Sprintf("git commit --allow-empty --amend --only%s", messageArgs)).Run()
|
|
||||||
|
cmdStr := NewGitCmd("commit").
|
||||||
|
Arg("--allow-empty", "--amend", "--only").
|
||||||
|
Arg(messageArgs...).
|
||||||
|
ToString()
|
||||||
|
|
||||||
|
return self.cmd.New(cmdStr).Run()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (self *CommitCommands) commitMessageArgs(message string) string {
|
func (self *CommitCommands) commitMessageArgs(message string) []string {
|
||||||
msg, description, _ := strings.Cut(message, "\n")
|
msg, description, _ := strings.Cut(message, "\n")
|
||||||
descriptionArgs := ""
|
args := []string{"-m", self.cmd.Quote(msg)}
|
||||||
|
|
||||||
if description != "" {
|
if description != "" {
|
||||||
descriptionArgs = fmt.Sprintf(" -m %s", self.cmd.Quote(description))
|
args = append(args, "-m", self.cmd.Quote(description))
|
||||||
}
|
}
|
||||||
|
|
||||||
return fmt.Sprintf(" -m %s%s", self.cmd.Quote(msg), descriptionArgs)
|
return args
|
||||||
}
|
}
|
||||||
|
|
||||||
// runs git commit without the -m argument meaning it will invoke the user's editor
|
// runs git commit without the -m argument meaning it will invoke the user's editor
|
||||||
func (self *CommitCommands) CommitEditorCmdObj() oscommands.ICmdObj {
|
func (self *CommitCommands) CommitEditorCmdObj() oscommands.ICmdObj {
|
||||||
return self.cmd.New(fmt.Sprintf("git commit%s%s", self.signoffFlag(), self.verboseFlag()))
|
cmdStr := NewGitCmd("commit").
|
||||||
|
ArgIf(self.signoffFlag() != "", self.signoffFlag()).
|
||||||
|
ArgIf(self.verboseFlag() != "", self.verboseFlag()).
|
||||||
|
ToString()
|
||||||
|
|
||||||
|
return self.cmd.New(cmdStr)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (self *CommitCommands) signoffFlag() string {
|
func (self *CommitCommands) signoffFlag() string {
|
||||||
if self.UserConfig.Git.Commit.SignOff {
|
if self.UserConfig.Git.Commit.SignOff {
|
||||||
return " --signoff"
|
return "--signoff"
|
||||||
} else {
|
} else {
|
||||||
return ""
|
return ""
|
||||||
}
|
}
|
||||||
@@ -85,9 +108,9 @@ func (self *CommitCommands) signoffFlag() string {
|
|||||||
func (self *CommitCommands) verboseFlag() string {
|
func (self *CommitCommands) verboseFlag() string {
|
||||||
switch self.config.UserConfig.Git.Commit.Verbose {
|
switch self.config.UserConfig.Git.Commit.Verbose {
|
||||||
case "always":
|
case "always":
|
||||||
return " --verbose"
|
return "--verbose"
|
||||||
case "never":
|
case "never":
|
||||||
return " --no-verbose"
|
return "--no-verbose"
|
||||||
default:
|
default:
|
||||||
return ""
|
return ""
|
||||||
}
|
}
|
||||||
@@ -95,19 +118,25 @@ func (self *CommitCommands) verboseFlag() string {
|
|||||||
|
|
||||||
// Get the subject of the HEAD commit
|
// Get the subject of the HEAD commit
|
||||||
func (self *CommitCommands) GetHeadCommitMessage() (string, error) {
|
func (self *CommitCommands) GetHeadCommitMessage() (string, error) {
|
||||||
message, err := self.cmd.New("git log -1 --pretty=%s").DontLog().RunWithOutput()
|
cmdStr := NewGitCmd("log").Arg("-1", "--pretty=%s").ToString()
|
||||||
|
|
||||||
|
message, err := self.cmd.New(cmdStr).DontLog().RunWithOutput()
|
||||||
return strings.TrimSpace(message), err
|
return strings.TrimSpace(message), err
|
||||||
}
|
}
|
||||||
|
|
||||||
func (self *CommitCommands) GetCommitMessage(commitSha string) (string, error) {
|
func (self *CommitCommands) GetCommitMessage(commitSha string) (string, error) {
|
||||||
cmdStr := "git rev-list --format=%B --max-count=1 " + commitSha
|
cmdStr := NewGitCmd("rev-list").
|
||||||
|
Arg("--format=%B", "--max-count=1", commitSha).
|
||||||
|
ToString()
|
||||||
|
|
||||||
messageWithHeader, err := self.cmd.New(cmdStr).DontLog().RunWithOutput()
|
messageWithHeader, err := self.cmd.New(cmdStr).DontLog().RunWithOutput()
|
||||||
message := strings.Join(strings.SplitAfter(messageWithHeader, "\n")[1:], "")
|
message := strings.Join(strings.SplitAfter(messageWithHeader, "\n")[1:], "")
|
||||||
return strings.TrimSpace(message), err
|
return strings.TrimSpace(message), err
|
||||||
}
|
}
|
||||||
|
|
||||||
func (self *CommitCommands) GetCommitDiff(commitSha string) (string, error) {
|
func (self *CommitCommands) GetCommitDiff(commitSha string) (string, error) {
|
||||||
cmdStr := "git show --no-color " + commitSha
|
cmdStr := NewGitCmd("show").Arg("--no-color", commitSha).ToString()
|
||||||
|
|
||||||
diff, err := self.cmd.New(cmdStr).DontLog().RunWithOutput()
|
diff, err := self.cmd.New(cmdStr).DontLog().RunWithOutput()
|
||||||
return diff, err
|
return diff, err
|
||||||
}
|
}
|
||||||
@@ -118,7 +147,10 @@ type Author struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (self *CommitCommands) GetCommitAuthor(commitSha string) (Author, error) {
|
func (self *CommitCommands) GetCommitAuthor(commitSha string) (Author, error) {
|
||||||
cmdStr := "git show --no-patch --pretty=format:'%an%x00%ae' " + commitSha
|
cmdStr := NewGitCmd("show").
|
||||||
|
Arg("--no-patch", "--pretty=format:'%an%x00%ae'", commitSha).
|
||||||
|
ToString()
|
||||||
|
|
||||||
output, err := self.cmd.New(cmdStr).DontLog().RunWithOutput()
|
output, err := self.cmd.New(cmdStr).DontLog().RunWithOutput()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return Author{}, err
|
return Author{}, err
|
||||||
@@ -138,15 +170,21 @@ func (self *CommitCommands) GetCommitMessageFirstLine(sha string) (string, error
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (self *CommitCommands) GetCommitMessagesFirstLine(shas []string) (string, error) {
|
func (self *CommitCommands) GetCommitMessagesFirstLine(shas []string) (string, error) {
|
||||||
return self.cmd.New(
|
cmdStr := NewGitCmd("show").
|
||||||
fmt.Sprintf("git show --no-patch --pretty=format:%%s %s", strings.Join(shas, " ")),
|
Arg("--no-patch", "--pretty=format:%s").
|
||||||
).DontLog().RunWithOutput()
|
Arg(shas...).
|
||||||
|
ToString()
|
||||||
|
|
||||||
|
return self.cmd.New(cmdStr).DontLog().RunWithOutput()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (self *CommitCommands) GetCommitsOneline(shas []string) (string, error) {
|
func (self *CommitCommands) GetCommitsOneline(shas []string) (string, error) {
|
||||||
return self.cmd.New(
|
cmdStr := NewGitCmd("show").
|
||||||
fmt.Sprintf("git show --no-patch --oneline %s", strings.Join(shas, " ")),
|
Arg("--no-patch", "--oneline").
|
||||||
).DontLog().RunWithOutput()
|
Arg(shas...).
|
||||||
|
ToString()
|
||||||
|
|
||||||
|
return self.cmd.New(cmdStr).DontLog().RunWithOutput()
|
||||||
}
|
}
|
||||||
|
|
||||||
// AmendHead amends HEAD with whatever is staged in your working tree
|
// AmendHead amends HEAD with whatever is staged in your working tree
|
||||||
@@ -155,42 +193,57 @@ func (self *CommitCommands) AmendHead() error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (self *CommitCommands) AmendHeadCmdObj() oscommands.ICmdObj {
|
func (self *CommitCommands) AmendHeadCmdObj() oscommands.ICmdObj {
|
||||||
return self.cmd.New("git commit --amend --no-edit --allow-empty")
|
cmdStr := NewGitCmd("commit").
|
||||||
|
Arg("--amend", "--no-edit", "--allow-empty").
|
||||||
|
ToString()
|
||||||
|
|
||||||
|
return self.cmd.New(cmdStr)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (self *CommitCommands) ShowCmdObj(sha string, filterPath string, ignoreWhitespace bool) oscommands.ICmdObj {
|
func (self *CommitCommands) ShowCmdObj(sha string, filterPath string, ignoreWhitespace bool) oscommands.ICmdObj {
|
||||||
contextSize := self.UserConfig.Git.DiffContextSize
|
contextSize := self.UserConfig.Git.DiffContextSize
|
||||||
filterPathArg := ""
|
|
||||||
if filterPath != "" {
|
|
||||||
filterPathArg = fmt.Sprintf(" -- %s", self.cmd.Quote(filterPath))
|
|
||||||
}
|
|
||||||
ignoreWhitespaceArg := ""
|
|
||||||
if ignoreWhitespace {
|
|
||||||
ignoreWhitespaceArg = " --ignore-all-space"
|
|
||||||
}
|
|
||||||
|
|
||||||
cmdStr := fmt.Sprintf("git show --submodule --color=%s --unified=%d --stat -p %s%s%s",
|
cmdStr := NewGitCmd("show").
|
||||||
self.UserConfig.Git.Paging.ColorArg, contextSize, sha, ignoreWhitespaceArg, filterPathArg)
|
Arg("--submodule").
|
||||||
|
Arg("--color="+self.UserConfig.Git.Paging.ColorArg).
|
||||||
|
Arg(fmt.Sprintf("--unified=%d", contextSize)).
|
||||||
|
Arg("--stat").
|
||||||
|
Arg("-p").
|
||||||
|
Arg(sha).
|
||||||
|
ArgIf(ignoreWhitespace, "--ignore-all-space").
|
||||||
|
ArgIf(filterPath != "", "--", self.cmd.Quote(filterPath)).
|
||||||
|
ToString()
|
||||||
|
|
||||||
return self.cmd.New(cmdStr).DontLog()
|
return self.cmd.New(cmdStr).DontLog()
|
||||||
}
|
}
|
||||||
|
|
||||||
// Revert reverts the selected commit by sha
|
// Revert reverts the selected commit by sha
|
||||||
func (self *CommitCommands) Revert(sha string) error {
|
func (self *CommitCommands) Revert(sha string) error {
|
||||||
return self.cmd.New(fmt.Sprintf("git revert %s", sha)).Run()
|
cmdStr := NewGitCmd("revert").Arg(sha).ToString()
|
||||||
|
|
||||||
|
return self.cmd.New(cmdStr).Run()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (self *CommitCommands) RevertMerge(sha string, parentNumber int) error {
|
func (self *CommitCommands) RevertMerge(sha string, parentNumber int) error {
|
||||||
return self.cmd.New(fmt.Sprintf("git revert %s -m %d", sha, parentNumber)).Run()
|
cmdStr := NewGitCmd("revert").Arg(sha, "-m", fmt.Sprintf("%d", parentNumber)).
|
||||||
|
ToString()
|
||||||
|
|
||||||
|
return self.cmd.New(cmdStr).Run()
|
||||||
}
|
}
|
||||||
|
|
||||||
// CreateFixupCommit creates a commit that fixes up a previous commit
|
// CreateFixupCommit creates a commit that fixes up a previous commit
|
||||||
func (self *CommitCommands) CreateFixupCommit(sha string) error {
|
func (self *CommitCommands) CreateFixupCommit(sha string) error {
|
||||||
return self.cmd.New(fmt.Sprintf("git commit --fixup=%s", sha)).Run()
|
cmdStr := NewGitCmd("commit").Arg("--fixup=" + sha).ToString()
|
||||||
|
|
||||||
|
return self.cmd.New(cmdStr).Run()
|
||||||
}
|
}
|
||||||
|
|
||||||
// a value of 0 means the head commit, 1 is the parent commit, etc
|
// a value of 0 means the head commit, 1 is the parent commit, etc
|
||||||
func (self *CommitCommands) GetCommitMessageFromHistory(value int) (string, error) {
|
func (self *CommitCommands) GetCommitMessageFromHistory(value int) (string, error) {
|
||||||
hash, _ := self.cmd.New(fmt.Sprintf("git log -1 --skip=%d --pretty=%%H", value)).DontLog().RunWithOutput()
|
cmdStr := NewGitCmd("log").Arg("-1", fmt.Sprintf("--skip=%d", value), "--pretty=%H").
|
||||||
|
ToString()
|
||||||
|
|
||||||
|
hash, _ := self.cmd.New(cmdStr).DontLog().RunWithOutput()
|
||||||
formattedHash := strings.TrimSpace(hash)
|
formattedHash := strings.TrimSpace(hash)
|
||||||
if len(formattedHash) == 0 {
|
if len(formattedHash) == 0 {
|
||||||
return "", ErrInvalidCommitIndex
|
return "", ErrInvalidCommitIndex
|
||||||
|
@@ -1,7 +1,6 @@
|
|||||||
package git_commands
|
package git_commands
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/jesseduffield/generics/slices"
|
"github.com/jesseduffield/generics/slices"
|
||||||
@@ -25,12 +24,18 @@ func NewCommitFileLoader(common *common.Common, cmd oscommands.ICmdObjBuilder) *
|
|||||||
|
|
||||||
// GetFilesInDiff get the specified commit files
|
// GetFilesInDiff get the specified commit files
|
||||||
func (self *CommitFileLoader) GetFilesInDiff(from string, to string, reverse bool) ([]*models.CommitFile, error) {
|
func (self *CommitFileLoader) GetFilesInDiff(from string, to string, reverse bool) ([]*models.CommitFile, error) {
|
||||||
reverseFlag := ""
|
cmdStr := NewGitCmd("diff").
|
||||||
if reverse {
|
Arg("--submodule").
|
||||||
reverseFlag = " -R "
|
Arg("--no-ext-diff").
|
||||||
}
|
Arg("--name-status").
|
||||||
|
Arg("-z").
|
||||||
|
Arg("--no-renames").
|
||||||
|
ArgIf(reverse, "-R").
|
||||||
|
Arg(from).
|
||||||
|
Arg(to).
|
||||||
|
ToString()
|
||||||
|
|
||||||
filenames, err := self.cmd.New(fmt.Sprintf("git diff --submodule --no-ext-diff --name-status -z --no-renames %s %s %s", reverseFlag, from, to)).DontLog().RunWithOutput()
|
filenames, err := self.cmd.New(cmdStr).DontLog().RunWithOutput()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
@@ -201,12 +201,11 @@ func (self *CommitLoader) getHydratedRebasingCommits(rebaseMode enums.RebaseMode
|
|||||||
// note that we're not filtering these as we do non-rebasing commits just because
|
// note that we're not filtering these as we do non-rebasing commits just because
|
||||||
// I suspect that will cause some damage
|
// I suspect that will cause some damage
|
||||||
cmdObj := self.cmd.New(
|
cmdObj := self.cmd.New(
|
||||||
fmt.Sprintf(
|
NewGitCmd("show").
|
||||||
"git -c log.showSignature=false show %s --no-patch --oneline %s --abbrev=%d",
|
Config("log.showSignature=false").
|
||||||
strings.Join(commitShas, " "),
|
Arg("--no-patch", "--oneline", "--abbrev=20", prettyFormat).
|
||||||
prettyFormat,
|
Arg(commitShas...).
|
||||||
20,
|
ToString(),
|
||||||
),
|
|
||||||
).DontLog()
|
).DontLog()
|
||||||
|
|
||||||
fullCommits := map[string]*models.Commit{}
|
fullCommits := map[string]*models.Commit{}
|
||||||
@@ -375,8 +374,11 @@ func (self *CommitLoader) getMergeBase(refName string) string {
|
|||||||
|
|
||||||
// We pass all configured main branches to the merge-base call; git will
|
// We pass all configured main branches to the merge-base call; git will
|
||||||
// return the base commit for the closest one.
|
// return the base commit for the closest one.
|
||||||
output, err := self.cmd.New(fmt.Sprintf("git merge-base %s %s",
|
|
||||||
self.cmd.Quote(refName), *self.quotedMainBranches)).DontLog().RunWithOutput()
|
output, err := self.cmd.New(
|
||||||
|
NewGitCmd("merge-base").Arg(self.cmd.Quote(refName), *self.quotedMainBranches).
|
||||||
|
ToString(),
|
||||||
|
).DontLog().RunWithOutput()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
// If there's an error, it must be because one of the main branches that
|
// If there's an error, it must be because one of the main branches that
|
||||||
// used to exist when we called getExistingMainBranches() was deleted
|
// used to exist when we called getExistingMainBranches() was deleted
|
||||||
@@ -391,7 +393,9 @@ func (self *CommitLoader) getExistingMainBranches() string {
|
|||||||
lo.FilterMap(self.UserConfig.Git.MainBranches,
|
lo.FilterMap(self.UserConfig.Git.MainBranches,
|
||||||
func(branchName string, _ int) (string, bool) {
|
func(branchName string, _ int) (string, bool) {
|
||||||
quotedRef := self.cmd.Quote("refs/heads/" + branchName)
|
quotedRef := self.cmd.Quote("refs/heads/" + branchName)
|
||||||
if err := self.cmd.New(fmt.Sprintf("git rev-parse --verify --quiet %s", quotedRef)).DontLog().Run(); err != nil {
|
if err := self.cmd.New(
|
||||||
|
NewGitCmd("rev-parse").Arg("--verify", "--quiet", quotedRef).ToString(),
|
||||||
|
).DontLog().Run(); err != nil {
|
||||||
return "", false
|
return "", false
|
||||||
}
|
}
|
||||||
return quotedRef, true
|
return quotedRef, true
|
||||||
@@ -413,9 +417,10 @@ func ignoringWarnings(commandOutput string) string {
|
|||||||
func (self *CommitLoader) getFirstPushedCommit(refName string) (string, error) {
|
func (self *CommitLoader) getFirstPushedCommit(refName string) (string, error) {
|
||||||
output, err := self.cmd.
|
output, err := self.cmd.
|
||||||
New(
|
New(
|
||||||
fmt.Sprintf("git merge-base %s %s@{u}",
|
NewGitCmd("merge-base").
|
||||||
self.cmd.Quote(refName),
|
Arg(self.cmd.Quote(refName)).
|
||||||
self.cmd.Quote(strings.TrimPrefix(refName, "refs/heads/"))),
|
Arg(self.cmd.Quote(strings.TrimPrefix(refName, "refs/heads/")) + "@{u}").
|
||||||
|
ToString(),
|
||||||
).
|
).
|
||||||
DontLog().
|
DontLog().
|
||||||
RunWithOutput()
|
RunWithOutput()
|
||||||
@@ -428,42 +433,23 @@ func (self *CommitLoader) getFirstPushedCommit(refName string) (string, error) {
|
|||||||
|
|
||||||
// getLog gets the git log.
|
// getLog gets the git log.
|
||||||
func (self *CommitLoader) getLogCmd(opts GetCommitsOptions) oscommands.ICmdObj {
|
func (self *CommitLoader) getLogCmd(opts GetCommitsOptions) oscommands.ICmdObj {
|
||||||
limitFlag := ""
|
|
||||||
if opts.Limit {
|
|
||||||
limitFlag = " -300"
|
|
||||||
}
|
|
||||||
|
|
||||||
followFlag := ""
|
|
||||||
filterFlag := ""
|
|
||||||
if opts.FilterPath != "" {
|
|
||||||
followFlag = " --follow"
|
|
||||||
filterFlag = fmt.Sprintf(" %s", self.cmd.Quote(opts.FilterPath))
|
|
||||||
}
|
|
||||||
|
|
||||||
config := self.UserConfig.Git.Log
|
config := self.UserConfig.Git.Log
|
||||||
|
|
||||||
orderFlag := ""
|
cmdStr := NewGitCmd("log").
|
||||||
if config.Order != "default" {
|
Arg(self.cmd.Quote(opts.RefName)).
|
||||||
orderFlag = " --" + config.Order
|
ArgIf(config.Order != "default", "--"+config.Order).
|
||||||
}
|
ArgIf(opts.All, "--all").
|
||||||
allFlag := ""
|
Arg("--oneline").
|
||||||
if opts.All {
|
Arg(prettyFormat).
|
||||||
allFlag = " --all"
|
Arg("--abbrev=40").
|
||||||
}
|
ArgIf(opts.Limit, "-300").
|
||||||
|
ArgIf(opts.FilterPath != "", "--follow").
|
||||||
|
Arg("--no-show-signature").
|
||||||
|
Arg("--").
|
||||||
|
ArgIf(opts.FilterPath != "", self.cmd.Quote(opts.FilterPath)).
|
||||||
|
ToString()
|
||||||
|
|
||||||
return self.cmd.New(
|
return self.cmd.New(cmdStr).DontLog()
|
||||||
fmt.Sprintf(
|
|
||||||
"git log %s%s%s --oneline %s%s --abbrev=%d%s --no-show-signature --%s",
|
|
||||||
self.cmd.Quote(opts.RefName),
|
|
||||||
orderFlag,
|
|
||||||
allFlag,
|
|
||||||
prettyFormat,
|
|
||||||
limitFlag,
|
|
||||||
40,
|
|
||||||
followFlag,
|
|
||||||
filterFlag,
|
|
||||||
),
|
|
||||||
).DontLog()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const prettyFormat = `--pretty=format:"%H%x00%at%x00%aN%x00%ae%x00%d%x00%p%x00%s"`
|
const prettyFormat = `--pretty=format:"%H%x00%at%x00%aN%x00%ae%x00%d%x00%p%x00%s"`
|
||||||
|
@@ -7,6 +7,7 @@ import (
|
|||||||
gogit "github.com/jesseduffield/go-git/v5"
|
gogit "github.com/jesseduffield/go-git/v5"
|
||||||
"github.com/jesseduffield/lazygit/pkg/commands/git_config"
|
"github.com/jesseduffield/lazygit/pkg/commands/git_config"
|
||||||
"github.com/jesseduffield/lazygit/pkg/commands/oscommands"
|
"github.com/jesseduffield/lazygit/pkg/commands/oscommands"
|
||||||
|
"github.com/jesseduffield/lazygit/pkg/commands/patch"
|
||||||
"github.com/jesseduffield/lazygit/pkg/common"
|
"github.com/jesseduffield/lazygit/pkg/common"
|
||||||
"github.com/jesseduffield/lazygit/pkg/config"
|
"github.com/jesseduffield/lazygit/pkg/config"
|
||||||
"github.com/jesseduffield/lazygit/pkg/utils"
|
"github.com/jesseduffield/lazygit/pkg/utils"
|
||||||
@@ -117,6 +118,26 @@ func buildWorkingTreeCommands(deps commonDeps) *WorkingTreeCommands {
|
|||||||
return NewWorkingTreeCommands(gitCommon, submoduleCommands, fileLoader)
|
return NewWorkingTreeCommands(gitCommon, submoduleCommands, fileLoader)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func buildPatchCommands(deps commonDeps) *PatchCommands {
|
||||||
|
gitCommon := buildGitCommon(deps)
|
||||||
|
rebaseCommands := buildRebaseCommands(deps)
|
||||||
|
commitCommands := buildCommitCommands(deps)
|
||||||
|
statusCommands := buildStatusCommands(deps)
|
||||||
|
stashCommands := buildStashCommands(deps)
|
||||||
|
loadFileFn := func(from string, to string, reverse bool, filename string, plain bool) (string, error) {
|
||||||
|
return "", nil
|
||||||
|
}
|
||||||
|
patchBuilder := patch.NewPatchBuilder(gitCommon.Log, loadFileFn)
|
||||||
|
|
||||||
|
return NewPatchCommands(gitCommon, rebaseCommands, commitCommands, statusCommands, stashCommands, patchBuilder)
|
||||||
|
}
|
||||||
|
|
||||||
|
func buildStatusCommands(deps commonDeps) *StatusCommands {
|
||||||
|
gitCommon := buildGitCommon(deps)
|
||||||
|
|
||||||
|
return NewStatusCommands(gitCommon)
|
||||||
|
}
|
||||||
|
|
||||||
func buildStashCommands(deps commonDeps) *StashCommands {
|
func buildStashCommands(deps commonDeps) *StashCommands {
|
||||||
gitCommon := buildGitCommon(deps)
|
gitCommon := buildGitCommon(deps)
|
||||||
fileLoader := buildFileLoader(gitCommon)
|
fileLoader := buildFileLoader(gitCommon)
|
||||||
@@ -150,3 +171,9 @@ func buildBranchCommands(deps commonDeps) *BranchCommands {
|
|||||||
|
|
||||||
return NewBranchCommands(gitCommon)
|
return NewBranchCommands(gitCommon)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func buildFlowCommands(deps commonDeps) *FlowCommands {
|
||||||
|
gitCommon := buildGitCommon(deps)
|
||||||
|
|
||||||
|
return NewFlowCommands(gitCommon)
|
||||||
|
}
|
||||||
|
@@ -42,7 +42,7 @@ func (self *FileLoader) GetStatusFiles(opts GetStatusFileOptions) []*models.File
|
|||||||
}
|
}
|
||||||
untrackedFilesArg := fmt.Sprintf("--untracked-files=%s", untrackedFilesSetting)
|
untrackedFilesArg := fmt.Sprintf("--untracked-files=%s", untrackedFilesSetting)
|
||||||
|
|
||||||
statuses, err := self.GitStatus(GitStatusOptions{NoRenames: opts.NoRenames, UntrackedFilesArg: untrackedFilesArg})
|
statuses, err := self.gitStatus(GitStatusOptions{NoRenames: opts.NoRenames, UntrackedFilesArg: untrackedFilesArg})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
self.Log.Error(err)
|
self.Log.Error(err)
|
||||||
}
|
}
|
||||||
@@ -81,13 +81,15 @@ type FileStatus struct {
|
|||||||
PreviousName string
|
PreviousName string
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *FileLoader) GitStatus(opts GitStatusOptions) ([]FileStatus, error) {
|
func (c *FileLoader) gitStatus(opts GitStatusOptions) ([]FileStatus, error) {
|
||||||
noRenamesFlag := ""
|
cmdStr := NewGitCmd("status").
|
||||||
if opts.NoRenames {
|
Arg(opts.UntrackedFilesArg).
|
||||||
noRenamesFlag = " --no-renames"
|
Arg("--porcelain").
|
||||||
}
|
Arg("-z").
|
||||||
|
ArgIf(opts.NoRenames, "--no-renames").
|
||||||
|
ToString()
|
||||||
|
|
||||||
statusLines, _, err := c.cmd.New(fmt.Sprintf("git status %s --porcelain -z%s", opts.UntrackedFilesArg, noRenamesFlag)).DontLog().RunWithOutputs()
|
statusLines, _, err := c.cmd.New(cmdStr).DontLog().RunWithOutputs()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return []FileStatus{}, err
|
return []FileStatus{}, err
|
||||||
}
|
}
|
||||||
|
@@ -34,6 +34,7 @@ func (self *FlowCommands) FinishCmdObj(branchName string) (oscommands.ICmdObj, e
|
|||||||
branchType := ""
|
branchType := ""
|
||||||
for _, line := range strings.Split(strings.TrimSpace(prefixes), "\n") {
|
for _, line := range strings.Split(strings.TrimSpace(prefixes), "\n") {
|
||||||
if strings.HasPrefix(line, "gitflow.prefix.") && strings.HasSuffix(line, prefix) {
|
if strings.HasPrefix(line, "gitflow.prefix.") && strings.HasSuffix(line, prefix) {
|
||||||
|
|
||||||
regex := regexp.MustCompile("gitflow.prefix.([^ ]*) .*")
|
regex := regexp.MustCompile("gitflow.prefix.([^ ]*) .*")
|
||||||
matches := regex.FindAllStringSubmatch(line, 1)
|
matches := regex.FindAllStringSubmatch(line, 1)
|
||||||
|
|
||||||
@@ -48,9 +49,13 @@ func (self *FlowCommands) FinishCmdObj(branchName string) (oscommands.ICmdObj, e
|
|||||||
return nil, errors.New(self.Tr.NotAGitFlowBranch)
|
return nil, errors.New(self.Tr.NotAGitFlowBranch)
|
||||||
}
|
}
|
||||||
|
|
||||||
return self.cmd.New("git flow " + branchType + " finish " + suffix), nil
|
cmdStr := NewGitCmd("flow").Arg(branchType, "finish", suffix).ToString()
|
||||||
|
|
||||||
|
return self.cmd.New(cmdStr), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (self *FlowCommands) StartCmdObj(branchType string, name string) oscommands.ICmdObj {
|
func (self *FlowCommands) StartCmdObj(branchType string, name string) oscommands.ICmdObj {
|
||||||
return self.cmd.New("git flow " + branchType + " start " + name)
|
cmdStr := NewGitCmd("flow").Arg(branchType, "start", name).ToString()
|
||||||
|
|
||||||
|
return self.cmd.New(cmdStr)
|
||||||
}
|
}
|
||||||
|
92
pkg/commands/git_commands/flow_test.go
Normal file
92
pkg/commands/git_commands/flow_test.go
Normal file
@@ -0,0 +1,92 @@
|
|||||||
|
package git_commands
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/jesseduffield/lazygit/pkg/commands/git_config"
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestStartCmdObj(t *testing.T) {
|
||||||
|
scenarios := []struct {
|
||||||
|
testName string
|
||||||
|
branchType string
|
||||||
|
name string
|
||||||
|
expected string
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
testName: "basic",
|
||||||
|
branchType: "feature",
|
||||||
|
name: "test",
|
||||||
|
expected: "git flow feature start test",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, s := range scenarios {
|
||||||
|
s := s
|
||||||
|
t.Run(s.testName, func(t *testing.T) {
|
||||||
|
instance := buildFlowCommands(commonDeps{})
|
||||||
|
|
||||||
|
assert.Equal(t,
|
||||||
|
instance.StartCmdObj(s.branchType, s.name).ToString(),
|
||||||
|
s.expected,
|
||||||
|
)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestFinishCmdObj(t *testing.T) {
|
||||||
|
scenarios := []struct {
|
||||||
|
testName string
|
||||||
|
branchName string
|
||||||
|
expected string
|
||||||
|
expectedError string
|
||||||
|
gitConfigMockResponses map[string]string
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
testName: "not a git flow branch",
|
||||||
|
branchName: "mybranch",
|
||||||
|
expected: "",
|
||||||
|
expectedError: "This does not seem to be a git flow branch",
|
||||||
|
gitConfigMockResponses: nil,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
testName: "feature branch without config",
|
||||||
|
branchName: "feature/mybranch",
|
||||||
|
expected: "",
|
||||||
|
expectedError: "This does not seem to be a git flow branch",
|
||||||
|
gitConfigMockResponses: nil,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
testName: "feature branch with config",
|
||||||
|
branchName: "feature/mybranch",
|
||||||
|
expected: "git flow feature finish mybranch",
|
||||||
|
expectedError: "",
|
||||||
|
gitConfigMockResponses: map[string]string{
|
||||||
|
"--local --get-regexp gitflow.prefix": "gitflow.prefix.feature feature/",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, s := range scenarios {
|
||||||
|
s := s
|
||||||
|
t.Run(s.testName, func(t *testing.T) {
|
||||||
|
instance := buildFlowCommands(commonDeps{
|
||||||
|
gitConfig: git_config.NewFakeGitConfig(s.gitConfigMockResponses),
|
||||||
|
})
|
||||||
|
|
||||||
|
cmd, err := instance.FinishCmdObj(s.branchName)
|
||||||
|
|
||||||
|
if s.expectedError != "" {
|
||||||
|
if err == nil {
|
||||||
|
t.Errorf("Expected error, got nil")
|
||||||
|
} else {
|
||||||
|
assert.Equal(t, err.Error(), s.expectedError)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.Equal(t, cmd.ToString(), s.expected)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
54
pkg/commands/git_commands/git_command_builder.go
Normal file
54
pkg/commands/git_commands/git_command_builder.go
Normal file
@@ -0,0 +1,54 @@
|
|||||||
|
package git_commands
|
||||||
|
|
||||||
|
import "strings"
|
||||||
|
|
||||||
|
// convenience struct for building git commands. Especially useful when
|
||||||
|
// including conditional args
|
||||||
|
type GitCommandBuilder struct {
|
||||||
|
// command string
|
||||||
|
args []string
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewGitCmd(command string) *GitCommandBuilder {
|
||||||
|
return &GitCommandBuilder{args: []string{command}}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (self *GitCommandBuilder) Arg(args ...string) *GitCommandBuilder {
|
||||||
|
self.args = append(self.args, args...)
|
||||||
|
|
||||||
|
return self
|
||||||
|
}
|
||||||
|
|
||||||
|
func (self *GitCommandBuilder) ArgIf(condition bool, ifTrue ...string) *GitCommandBuilder {
|
||||||
|
if condition {
|
||||||
|
self.Arg(ifTrue...)
|
||||||
|
}
|
||||||
|
|
||||||
|
return self
|
||||||
|
}
|
||||||
|
|
||||||
|
func (self *GitCommandBuilder) ArgIfElse(condition bool, ifTrue string, ifFalse string) *GitCommandBuilder {
|
||||||
|
if condition {
|
||||||
|
return self.Arg(ifTrue)
|
||||||
|
} else {
|
||||||
|
return self.Arg(ifFalse)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (self *GitCommandBuilder) Config(value string) *GitCommandBuilder {
|
||||||
|
// config settings come before the command
|
||||||
|
self.args = append([]string{"-c", value}, self.args...)
|
||||||
|
|
||||||
|
return self
|
||||||
|
}
|
||||||
|
|
||||||
|
func (self *GitCommandBuilder) RepoPath(value string) *GitCommandBuilder {
|
||||||
|
// repo path comes before the command
|
||||||
|
self.args = append([]string{"-C", value}, self.args...)
|
||||||
|
|
||||||
|
return self
|
||||||
|
}
|
||||||
|
|
||||||
|
func (self *GitCommandBuilder) ToString() string {
|
||||||
|
return "git " + strings.Join(self.args, " ")
|
||||||
|
}
|
56
pkg/commands/git_commands/git_command_builder_test.go
Normal file
56
pkg/commands/git_commands/git_command_builder_test.go
Normal file
@@ -0,0 +1,56 @@
|
|||||||
|
package git_commands
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestGitCommandBuilder(t *testing.T) {
|
||||||
|
scenarios := []struct {
|
||||||
|
input string
|
||||||
|
expected string
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
input: NewGitCmd("push").
|
||||||
|
Arg("--force-with-lease").
|
||||||
|
Arg("--set-upstream").
|
||||||
|
Arg("origin").
|
||||||
|
Arg("master").
|
||||||
|
ToString(),
|
||||||
|
expected: "git push --force-with-lease --set-upstream origin master",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
input: NewGitCmd("push").ArgIf(true, "--test").ToString(),
|
||||||
|
expected: "git push --test",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
input: NewGitCmd("push").ArgIf(false, "--test").ToString(),
|
||||||
|
expected: "git push",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
input: NewGitCmd("push").ArgIfElse(true, "-b", "-a").ToString(),
|
||||||
|
expected: "git push -b",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
input: NewGitCmd("push").ArgIfElse(false, "-a", "-b").ToString(),
|
||||||
|
expected: "git push -b",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
input: NewGitCmd("push").Arg("-a", "-b").ToString(),
|
||||||
|
expected: "git push -a -b",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
input: NewGitCmd("push").Config("user.name=foo").Config("user.email=bar").ToString(),
|
||||||
|
expected: "git -c user.email=bar -c user.name=foo push",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
input: NewGitCmd("push").RepoPath("a/b/c").ToString(),
|
||||||
|
expected: "git -C a/b/c push",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, s := range scenarios {
|
||||||
|
assert.Equal(t, s.input, s.expected)
|
||||||
|
}
|
||||||
|
}
|
@@ -2,6 +2,8 @@ package git_commands
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"path/filepath"
|
||||||
|
"time"
|
||||||
|
|
||||||
"github.com/fsmiamoto/git-todo-parser/todo"
|
"github.com/fsmiamoto/git-todo-parser/todo"
|
||||||
"github.com/go-errors/errors"
|
"github.com/go-errors/errors"
|
||||||
@@ -9,6 +11,7 @@ import (
|
|||||||
"github.com/jesseduffield/lazygit/pkg/commands/models"
|
"github.com/jesseduffield/lazygit/pkg/commands/models"
|
||||||
"github.com/jesseduffield/lazygit/pkg/commands/patch"
|
"github.com/jesseduffield/lazygit/pkg/commands/patch"
|
||||||
"github.com/jesseduffield/lazygit/pkg/commands/types/enums"
|
"github.com/jesseduffield/lazygit/pkg/commands/types/enums"
|
||||||
|
"github.com/jesseduffield/lazygit/pkg/utils"
|
||||||
)
|
)
|
||||||
|
|
||||||
type PatchCommands struct {
|
type PatchCommands struct {
|
||||||
@@ -39,6 +42,53 @@ func NewPatchCommands(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type ApplyPatchOpts struct {
|
||||||
|
ThreeWay bool
|
||||||
|
Cached bool
|
||||||
|
Index bool
|
||||||
|
Reverse bool
|
||||||
|
}
|
||||||
|
|
||||||
|
func (self *PatchCommands) ApplyCustomPatch(reverse bool) error {
|
||||||
|
patch := self.PatchBuilder.PatchToApply(reverse)
|
||||||
|
|
||||||
|
return self.ApplyPatch(patch, ApplyPatchOpts{
|
||||||
|
Index: true,
|
||||||
|
ThreeWay: true,
|
||||||
|
Reverse: reverse,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func (self *PatchCommands) ApplyPatch(patch string, opts ApplyPatchOpts) error {
|
||||||
|
filepath, err := self.SaveTemporaryPatch(patch)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return self.applyPatchFile(filepath, opts)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (self *PatchCommands) applyPatchFile(filepath string, opts ApplyPatchOpts) error {
|
||||||
|
cmdStr := NewGitCmd("apply").
|
||||||
|
ArgIf(opts.ThreeWay, "--3way").
|
||||||
|
ArgIf(opts.Cached, "--cached").
|
||||||
|
ArgIf(opts.Index, "--index").
|
||||||
|
ArgIf(opts.Reverse, "--reverse").
|
||||||
|
Arg(self.cmd.Quote(filepath)).
|
||||||
|
ToString()
|
||||||
|
|
||||||
|
return self.cmd.New(cmdStr).Run()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (self *PatchCommands) SaveTemporaryPatch(patch string) (string, error) {
|
||||||
|
filepath := filepath.Join(self.os.GetTempDir(), utils.GetCurrentRepoName(), time.Now().Format("Jan _2 15.04.05.000000000")+".patch")
|
||||||
|
self.Log.Infof("saving temporary patch to %s", filepath)
|
||||||
|
if err := self.os.CreateFileWithContent(filepath, patch); err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
return filepath, nil
|
||||||
|
}
|
||||||
|
|
||||||
// DeletePatchesFromCommit applies a patch in reverse for a commit
|
// DeletePatchesFromCommit applies a patch in reverse for a commit
|
||||||
func (self *PatchCommands) DeletePatchesFromCommit(commits []*models.Commit, commitIndex int) error {
|
func (self *PatchCommands) DeletePatchesFromCommit(commits []*models.Commit, commitIndex int) error {
|
||||||
if err := self.rebase.BeginInteractiveRebaseForCommit(commits, commitIndex); err != nil {
|
if err := self.rebase.BeginInteractiveRebaseForCommit(commits, commitIndex); err != nil {
|
||||||
@@ -46,7 +96,7 @@ func (self *PatchCommands) DeletePatchesFromCommit(commits []*models.Commit, com
|
|||||||
}
|
}
|
||||||
|
|
||||||
// apply each patch in reverse
|
// apply each patch in reverse
|
||||||
if err := self.PatchBuilder.ApplyPatches(true); err != nil {
|
if err := self.ApplyCustomPatch(true); err != nil {
|
||||||
_ = self.rebase.AbortRebase()
|
_ = self.rebase.AbortRebase()
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@@ -72,7 +122,7 @@ func (self *PatchCommands) MovePatchToSelectedCommit(commits []*models.Commit, s
|
|||||||
}
|
}
|
||||||
|
|
||||||
// apply each patch forward
|
// apply each patch forward
|
||||||
if err := self.PatchBuilder.ApplyPatches(false); err != nil {
|
if err := self.ApplyCustomPatch(false); err != nil {
|
||||||
// Don't abort the rebase here; this might cause conflicts, so give
|
// Don't abort the rebase here; this might cause conflicts, so give
|
||||||
// the user a chance to resolve them
|
// the user a chance to resolve them
|
||||||
return err
|
return err
|
||||||
@@ -121,7 +171,7 @@ func (self *PatchCommands) MovePatchToSelectedCommit(commits []*models.Commit, s
|
|||||||
}
|
}
|
||||||
|
|
||||||
// apply each patch in reverse
|
// apply each patch in reverse
|
||||||
if err := self.PatchBuilder.ApplyPatches(true); err != nil {
|
if err := self.ApplyCustomPatch(true); err != nil {
|
||||||
_ = self.rebase.AbortRebase()
|
_ = self.rebase.AbortRebase()
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@@ -144,7 +194,7 @@ func (self *PatchCommands) MovePatchToSelectedCommit(commits []*models.Commit, s
|
|||||||
self.rebase.onSuccessfulContinue = func() error {
|
self.rebase.onSuccessfulContinue = func() error {
|
||||||
// now we should be up to the destination, so let's apply forward these patches to that.
|
// now we should be up to the destination, so let's apply forward these patches to that.
|
||||||
// ideally we would ensure we're on the right commit but I'm not sure if that check is necessary
|
// ideally we would ensure we're on the right commit but I'm not sure if that check is necessary
|
||||||
if err := self.rebase.workingTree.ApplyPatch(patch, "index", "3way"); err != nil {
|
if err := self.ApplyPatch(patch, ApplyPatchOpts{Index: true, ThreeWay: true}); err != nil {
|
||||||
// Don't abort the rebase here; this might cause conflicts, so give
|
// Don't abort the rebase here; this might cause conflicts, so give
|
||||||
// the user a chance to resolve them
|
// the user a chance to resolve them
|
||||||
return err
|
return err
|
||||||
@@ -177,7 +227,7 @@ func (self *PatchCommands) MovePatchIntoIndex(commits []*models.Commit, commitId
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := self.PatchBuilder.ApplyPatches(true); err != nil {
|
if err := self.ApplyCustomPatch(true); err != nil {
|
||||||
if self.status.WorkingTreeState() == enums.REBASE_MODE_REBASING {
|
if self.status.WorkingTreeState() == enums.REBASE_MODE_REBASING {
|
||||||
_ = self.rebase.AbortRebase()
|
_ = self.rebase.AbortRebase()
|
||||||
}
|
}
|
||||||
@@ -201,7 +251,7 @@ func (self *PatchCommands) MovePatchIntoIndex(commits []*models.Commit, commitId
|
|||||||
|
|
||||||
self.rebase.onSuccessfulContinue = func() error {
|
self.rebase.onSuccessfulContinue = func() error {
|
||||||
// add patches to index
|
// add patches to index
|
||||||
if err := self.rebase.workingTree.ApplyPatch(patch, "index", "3way"); err != nil {
|
if err := self.ApplyPatch(patch, ApplyPatchOpts{Index: true, ThreeWay: true}); err != nil {
|
||||||
if self.status.WorkingTreeState() == enums.REBASE_MODE_REBASING {
|
if self.status.WorkingTreeState() == enums.REBASE_MODE_REBASING {
|
||||||
_ = self.rebase.AbortRebase()
|
_ = self.rebase.AbortRebase()
|
||||||
}
|
}
|
||||||
@@ -226,7 +276,7 @@ func (self *PatchCommands) PullPatchIntoNewCommit(commits []*models.Commit, comm
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := self.PatchBuilder.ApplyPatches(true); err != nil {
|
if err := self.ApplyCustomPatch(true); err != nil {
|
||||||
_ = self.rebase.AbortRebase()
|
_ = self.rebase.AbortRebase()
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@@ -242,7 +292,7 @@ func (self *PatchCommands) PullPatchIntoNewCommit(commits []*models.Commit, comm
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := self.rebase.workingTree.ApplyPatch(patch, "index", "3way"); err != nil {
|
if err := self.ApplyPatch(patch, ApplyPatchOpts{Index: true, ThreeWay: true}); err != nil {
|
||||||
_ = self.rebase.AbortRebase()
|
_ = self.rebase.AbortRebase()
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@@ -267,5 +317,7 @@ func (self *PatchCommands) PullPatchIntoNewCommit(commits []*models.Commit, comm
|
|||||||
// only some lines of a range of adjacent added lines. To solve this, we
|
// only some lines of a range of adjacent added lines. To solve this, we
|
||||||
// get the diff of HEAD and the original commit and then apply that.
|
// get the diff of HEAD and the original commit and then apply that.
|
||||||
func (self *PatchCommands) diffHeadAgainstCommit(commit *models.Commit) (string, error) {
|
func (self *PatchCommands) diffHeadAgainstCommit(commit *models.Commit) (string, error) {
|
||||||
return self.cmd.New(fmt.Sprintf("git diff HEAD..%s", commit.Sha)).RunWithOutput()
|
cmdStr := NewGitCmd("diff").Arg("HEAD.." + commit.Sha).ToString()
|
||||||
|
|
||||||
|
return self.cmd.New(cmdStr).RunWithOutput()
|
||||||
}
|
}
|
||||||
|
66
pkg/commands/git_commands/patch_test.go
Normal file
66
pkg/commands/git_commands/patch_test.go
Normal file
@@ -0,0 +1,66 @@
|
|||||||
|
package git_commands
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
"regexp"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/go-errors/errors"
|
||||||
|
"github.com/jesseduffield/lazygit/pkg/commands/oscommands"
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestWorkingTreeApplyPatch(t *testing.T) {
|
||||||
|
type scenario struct {
|
||||||
|
testName string
|
||||||
|
runner *oscommands.FakeCmdObjRunner
|
||||||
|
test func(error)
|
||||||
|
}
|
||||||
|
|
||||||
|
expectFn := func(regexStr string, errToReturn error) func(cmdObj oscommands.ICmdObj) (string, error) {
|
||||||
|
return func(cmdObj oscommands.ICmdObj) (string, error) {
|
||||||
|
re := regexp.MustCompile(regexStr)
|
||||||
|
cmdStr := cmdObj.ToString()
|
||||||
|
matches := re.FindStringSubmatch(cmdStr)
|
||||||
|
assert.Equal(t, 2, len(matches), fmt.Sprintf("unexpected command: %s", cmdStr))
|
||||||
|
|
||||||
|
filename := matches[1]
|
||||||
|
|
||||||
|
content, err := os.ReadFile(filename)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
|
||||||
|
assert.Equal(t, "test", string(content))
|
||||||
|
|
||||||
|
return "", errToReturn
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
scenarios := []scenario{
|
||||||
|
{
|
||||||
|
testName: "valid case",
|
||||||
|
runner: oscommands.NewFakeRunner(t).
|
||||||
|
ExpectFunc(expectFn(`git apply --cached "(.*)"`, nil)),
|
||||||
|
test: func(err error) {
|
||||||
|
assert.NoError(t, err)
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
testName: "command returns error",
|
||||||
|
runner: oscommands.NewFakeRunner(t).
|
||||||
|
ExpectFunc(expectFn(`git apply --cached "(.*)"`, errors.New("error"))),
|
||||||
|
test: func(err error) {
|
||||||
|
assert.Error(t, err)
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, s := range scenarios {
|
||||||
|
s := s
|
||||||
|
t.Run(s.testName, func(t *testing.T) {
|
||||||
|
instance := buildPatchCommands(commonDeps{runner: s.runner})
|
||||||
|
s.test(instance.ApplyPatch("test", ApplyPatchOpts{Cached: true}))
|
||||||
|
s.runner.CheckForMissingCalls()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
@@ -176,23 +176,21 @@ type PrepareInteractiveRebaseCommandOpts struct {
|
|||||||
func (self *RebaseCommands) PrepareInteractiveRebaseCommand(opts PrepareInteractiveRebaseCommandOpts) oscommands.ICmdObj {
|
func (self *RebaseCommands) PrepareInteractiveRebaseCommand(opts PrepareInteractiveRebaseCommandOpts) oscommands.ICmdObj {
|
||||||
ex := oscommands.GetLazygitPath()
|
ex := oscommands.GetLazygitPath()
|
||||||
|
|
||||||
|
cmdStr := NewGitCmd("rebase").
|
||||||
|
Arg("--interactive").
|
||||||
|
Arg("--autostash").
|
||||||
|
Arg("--keep-empty").
|
||||||
|
ArgIf(!self.version.IsOlderThan(2, 26, 0), "--empty=keep").
|
||||||
|
Arg("--no-autosquash").
|
||||||
|
ArgIf(!self.version.IsOlderThan(2, 22, 0), "--rebase-merges").
|
||||||
|
Arg(opts.baseShaOrRoot).
|
||||||
|
ToString()
|
||||||
|
|
||||||
debug := "FALSE"
|
debug := "FALSE"
|
||||||
if self.Debug {
|
if self.Debug {
|
||||||
debug = "TRUE"
|
debug = "TRUE"
|
||||||
}
|
}
|
||||||
|
|
||||||
emptyArg := " --empty=keep"
|
|
||||||
if self.version.IsOlderThan(2, 26, 0) {
|
|
||||||
emptyArg = ""
|
|
||||||
}
|
|
||||||
|
|
||||||
rebaseMergesArg := " --rebase-merges"
|
|
||||||
if self.version.IsOlderThan(2, 22, 0) {
|
|
||||||
rebaseMergesArg = ""
|
|
||||||
}
|
|
||||||
|
|
||||||
cmdStr := fmt.Sprintf("git rebase --interactive --autostash --keep-empty%s --no-autosquash%s %s",
|
|
||||||
emptyArg, rebaseMergesArg, opts.baseShaOrRoot)
|
|
||||||
self.Log.WithField("command", cmdStr).Debug("RunCommand")
|
self.Log.WithField("command", cmdStr).Debug("RunCommand")
|
||||||
|
|
||||||
cmdObj := self.cmd.New(cmdStr)
|
cmdObj := self.cmd.New(cmdStr)
|
||||||
@@ -228,7 +226,8 @@ func (self *RebaseCommands) AmendTo(commits []*models.Commit, commitIndex int) e
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Get the sha of the commit we just created
|
// Get the sha of the commit we just created
|
||||||
fixupSha, err := self.cmd.New("git rev-parse --verify HEAD").RunWithOutput()
|
cmdStr := NewGitCmd("rev-parse").Arg("--verify", "HEAD").ToString()
|
||||||
|
fixupSha, err := self.cmd.New(cmdStr).RunWithOutput()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@@ -265,14 +264,11 @@ func (self *RebaseCommands) SquashAllAboveFixupCommits(commit *models.Commit) er
|
|||||||
shaOrRoot = "--root"
|
shaOrRoot = "--root"
|
||||||
}
|
}
|
||||||
|
|
||||||
return self.runSkipEditorCommand(
|
cmdStr := NewGitCmd("rebase").
|
||||||
self.cmd.New(
|
Arg("--interactive", "--rebase-merges", "--autostash", "--autosquash", shaOrRoot).
|
||||||
fmt.Sprintf(
|
ToString()
|
||||||
"git rebase --interactive --rebase-merges --autostash --autosquash %s",
|
|
||||||
shaOrRoot,
|
return self.runSkipEditorCommand(self.cmd.New(cmdStr))
|
||||||
),
|
|
||||||
),
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// BeginInteractiveRebaseForCommit starts an interactive rebase to edit the current
|
// BeginInteractiveRebaseForCommit starts an interactive rebase to edit the current
|
||||||
@@ -308,7 +304,9 @@ func (self *RebaseCommands) RebaseBranch(branchName string) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (self *RebaseCommands) GenericMergeOrRebaseActionCmdObj(commandType string, command string) oscommands.ICmdObj {
|
func (self *RebaseCommands) GenericMergeOrRebaseActionCmdObj(commandType string, command string) oscommands.ICmdObj {
|
||||||
return self.cmd.New("git " + commandType + " --" + command)
|
cmdStr := NewGitCmd(commandType).Arg("--" + command).ToString()
|
||||||
|
|
||||||
|
return self.cmd.New(cmdStr)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (self *RebaseCommands) ContinueRebase() error {
|
func (self *RebaseCommands) ContinueRebase() error {
|
||||||
@@ -365,7 +363,9 @@ func (self *RebaseCommands) DiscardOldFileChanges(commits []*models.Commit, comm
|
|||||||
}
|
}
|
||||||
|
|
||||||
// check if file exists in previous commit (this command returns an error if the file doesn't exist)
|
// check if file exists in previous commit (this command returns an error if the file doesn't exist)
|
||||||
if err := self.cmd.New("git cat-file -e HEAD^:" + self.cmd.Quote(fileName)).Run(); err != nil {
|
cmdStr := NewGitCmd("cat-file").Arg("-e", "HEAD^:"+self.cmd.Quote(fileName)).ToString()
|
||||||
|
|
||||||
|
if err := self.cmd.New(cmdStr).Run(); err != nil {
|
||||||
if err := self.os.Remove(fileName); err != nil {
|
if err := self.os.Remove(fileName); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
@@ -15,44 +15,52 @@ func NewRemoteCommands(gitCommon *GitCommon) *RemoteCommands {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (self *RemoteCommands) AddRemote(name string, url string) error {
|
func (self *RemoteCommands) AddRemote(name string, url string) error {
|
||||||
return self.cmd.
|
cmdStr := NewGitCmd("remote").
|
||||||
New(fmt.Sprintf("git remote add %s %s", self.cmd.Quote(name), self.cmd.Quote(url))).
|
Arg("add", self.cmd.Quote(name), self.cmd.Quote(url)).
|
||||||
Run()
|
ToString()
|
||||||
|
|
||||||
|
return self.cmd.New(cmdStr).Run()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (self *RemoteCommands) RemoveRemote(name string) error {
|
func (self *RemoteCommands) RemoveRemote(name string) error {
|
||||||
return self.cmd.
|
cmdStr := NewGitCmd("remote").
|
||||||
New(fmt.Sprintf("git remote remove %s", self.cmd.Quote(name))).
|
Arg("remove", self.cmd.Quote(name)).
|
||||||
Run()
|
ToString()
|
||||||
|
|
||||||
|
return self.cmd.New(cmdStr).Run()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (self *RemoteCommands) RenameRemote(oldRemoteName string, newRemoteName string) error {
|
func (self *RemoteCommands) RenameRemote(oldRemoteName string, newRemoteName string) error {
|
||||||
return self.cmd.
|
cmdStr := NewGitCmd("remote").
|
||||||
New(fmt.Sprintf("git remote rename %s %s", self.cmd.Quote(oldRemoteName), self.cmd.Quote(newRemoteName))).
|
Arg("rename", self.cmd.Quote(oldRemoteName), self.cmd.Quote(newRemoteName)).
|
||||||
Run()
|
ToString()
|
||||||
|
|
||||||
|
return self.cmd.New(cmdStr).Run()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (self *RemoteCommands) UpdateRemoteUrl(remoteName string, updatedUrl string) error {
|
func (self *RemoteCommands) UpdateRemoteUrl(remoteName string, updatedUrl string) error {
|
||||||
return self.cmd.
|
cmdStr := NewGitCmd("remote").
|
||||||
New(fmt.Sprintf("git remote set-url %s %s", self.cmd.Quote(remoteName), self.cmd.Quote(updatedUrl))).
|
Arg("set-url", self.cmd.Quote(remoteName), self.cmd.Quote(updatedUrl)).
|
||||||
Run()
|
ToString()
|
||||||
|
|
||||||
|
return self.cmd.New(cmdStr).Run()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (self *RemoteCommands) DeleteRemoteBranch(remoteName string, branchName string) error {
|
func (self *RemoteCommands) DeleteRemoteBranch(remoteName string, branchName string) error {
|
||||||
command := fmt.Sprintf("git push %s --delete %s", self.cmd.Quote(remoteName), self.cmd.Quote(branchName))
|
cmdStr := NewGitCmd("push").
|
||||||
return self.cmd.New(command).PromptOnCredentialRequest().WithMutex(self.syncMutex).Run()
|
Arg(self.cmd.Quote(remoteName), "--delete", self.cmd.Quote(branchName)).
|
||||||
|
ToString()
|
||||||
|
|
||||||
|
return self.cmd.New(cmdStr).PromptOnCredentialRequest().WithMutex(self.syncMutex).Run()
|
||||||
}
|
}
|
||||||
|
|
||||||
// CheckRemoteBranchExists Returns remote branch
|
// CheckRemoteBranchExists Returns remote branch
|
||||||
func (self *RemoteCommands) CheckRemoteBranchExists(branchName string) bool {
|
func (self *RemoteCommands) CheckRemoteBranchExists(branchName string) bool {
|
||||||
_, err := self.cmd.
|
cmdStr := NewGitCmd("show-ref").
|
||||||
New(
|
Arg("--verify", "--", fmt.Sprintf("refs/remotes/origin/%s", self.cmd.Quote(branchName))).
|
||||||
fmt.Sprintf("git show-ref --verify -- refs/remotes/origin/%s",
|
ToString()
|
||||||
self.cmd.Quote(branchName),
|
|
||||||
),
|
_, err := self.cmd.New(cmdStr).DontLog().RunWithOutput()
|
||||||
).
|
|
||||||
DontLog().
|
|
||||||
RunWithOutput()
|
|
||||||
|
|
||||||
return err == nil
|
return err == nil
|
||||||
}
|
}
|
||||||
|
@@ -31,7 +31,8 @@ func NewRemoteLoader(
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (self *RemoteLoader) GetRemotes() ([]*models.Remote, error) {
|
func (self *RemoteLoader) GetRemotes() ([]*models.Remote, error) {
|
||||||
remoteBranchesStr, err := self.cmd.New("git branch -r").DontLog().RunWithOutput()
|
cmdStr := NewGitCmd("branch").Arg("-r").ToString()
|
||||||
|
remoteBranchesStr, err := self.cmd.New(cmdStr).DontLog().RunWithOutput()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
@@ -26,68 +26,94 @@ func NewStashCommands(
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (self *StashCommands) DropNewest() error {
|
func (self *StashCommands) DropNewest() error {
|
||||||
return self.cmd.New("git stash drop").Run()
|
cmdStr := NewGitCmd("stash").Arg("drop").ToString()
|
||||||
|
|
||||||
|
return self.cmd.New(cmdStr).Run()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (self *StashCommands) Drop(index int) error {
|
func (self *StashCommands) Drop(index int) error {
|
||||||
return self.cmd.New(fmt.Sprintf("git stash drop stash@{%d}", index)).Run()
|
cmdStr := NewGitCmd("stash").Arg("drop", fmt.Sprintf("stash@{%d}", index)).
|
||||||
|
ToString()
|
||||||
|
|
||||||
|
return self.cmd.New(cmdStr).Run()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (self *StashCommands) Pop(index int) error {
|
func (self *StashCommands) Pop(index int) error {
|
||||||
return self.cmd.New(fmt.Sprintf("git stash pop stash@{%d}", index)).Run()
|
cmdStr := NewGitCmd("stash").Arg("pop", fmt.Sprintf("stash@{%d}", index)).
|
||||||
|
ToString()
|
||||||
|
|
||||||
|
return self.cmd.New(cmdStr).Run()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (self *StashCommands) Apply(index int) error {
|
func (self *StashCommands) Apply(index int) error {
|
||||||
return self.cmd.New(fmt.Sprintf("git stash apply stash@{%d}", index)).Run()
|
cmdStr := NewGitCmd("stash").Arg("apply", fmt.Sprintf("stash@{%d}", index)).
|
||||||
|
ToString()
|
||||||
|
|
||||||
|
return self.cmd.New(cmdStr).Run()
|
||||||
}
|
}
|
||||||
|
|
||||||
// Save save stash
|
// Save save stash
|
||||||
func (self *StashCommands) Save(message string) error {
|
func (self *StashCommands) Save(message string) error {
|
||||||
return self.cmd.New("git stash save " + self.cmd.Quote(message)).Run()
|
cmdStr := NewGitCmd("stash").Arg("save", self.cmd.Quote(message)).
|
||||||
|
ToString()
|
||||||
|
|
||||||
|
return self.cmd.New(cmdStr).Run()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (self *StashCommands) Store(sha string, message string) error {
|
func (self *StashCommands) Store(sha string, message string) error {
|
||||||
trimmedMessage := strings.Trim(message, " \t")
|
trimmedMessage := strings.Trim(message, " \t")
|
||||||
if len(trimmedMessage) > 0 {
|
|
||||||
return self.cmd.New(fmt.Sprintf("git stash store %s -m %s", self.cmd.Quote(sha), self.cmd.Quote(trimmedMessage))).Run()
|
cmdStr := NewGitCmd("stash").Arg("store", self.cmd.Quote(sha)).
|
||||||
}
|
ArgIf(trimmedMessage != "", "-m", self.cmd.Quote(trimmedMessage)).
|
||||||
return self.cmd.New(fmt.Sprintf("git stash store %s", self.cmd.Quote(sha))).Run()
|
ToString()
|
||||||
|
|
||||||
|
return self.cmd.New(cmdStr).Run()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (self *StashCommands) Sha(index int) (string, error) {
|
func (self *StashCommands) Sha(index int) (string, error) {
|
||||||
sha, _, err := self.cmd.New(fmt.Sprintf("git rev-parse refs/stash@{%d}", index)).DontLog().RunWithOutputs()
|
cmdStr := NewGitCmd("rev-parse").
|
||||||
|
Arg(fmt.Sprintf("refs/stash@{%d}", index)).
|
||||||
|
ToString()
|
||||||
|
|
||||||
|
sha, _, err := self.cmd.New(cmdStr).DontLog().RunWithOutputs()
|
||||||
return strings.Trim(sha, "\r\n"), err
|
return strings.Trim(sha, "\r\n"), err
|
||||||
}
|
}
|
||||||
|
|
||||||
func (self *StashCommands) ShowStashEntryCmdObj(index int, ignoreWhitespace bool) oscommands.ICmdObj {
|
func (self *StashCommands) ShowStashEntryCmdObj(index int, ignoreWhitespace bool) oscommands.ICmdObj {
|
||||||
ignoreWhitespaceFlag := ""
|
cmdStr := NewGitCmd("stash").Arg("show").
|
||||||
if ignoreWhitespace {
|
Arg("-p").
|
||||||
ignoreWhitespaceFlag = " --ignore-all-space"
|
Arg("--stat").
|
||||||
}
|
Arg(fmt.Sprintf("--color=%s", self.UserConfig.Git.Paging.ColorArg)).
|
||||||
|
Arg(fmt.Sprintf("--unified=%d", self.UserConfig.Git.DiffContextSize)).
|
||||||
cmdStr := fmt.Sprintf(
|
ArgIf(ignoreWhitespace, "--ignore-all-space").
|
||||||
"git stash show -p --stat --color=%s --unified=%d%s stash@{%d}",
|
Arg(fmt.Sprintf("stash@{%d}", index)).
|
||||||
self.UserConfig.Git.Paging.ColorArg,
|
ToString()
|
||||||
self.UserConfig.Git.DiffContextSize,
|
|
||||||
ignoreWhitespaceFlag,
|
|
||||||
index,
|
|
||||||
)
|
|
||||||
|
|
||||||
return self.cmd.New(cmdStr).DontLog()
|
return self.cmd.New(cmdStr).DontLog()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (self *StashCommands) StashAndKeepIndex(message string) error {
|
func (self *StashCommands) StashAndKeepIndex(message string) error {
|
||||||
return self.cmd.New(fmt.Sprintf("git stash save %s --keep-index", self.cmd.Quote(message))).Run()
|
cmdStr := NewGitCmd("stash").Arg("save", self.cmd.Quote(message), "--keep-index").
|
||||||
|
ToString()
|
||||||
|
|
||||||
|
return self.cmd.New(cmdStr).Run()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (self *StashCommands) StashUnstagedChanges(message string) error {
|
func (self *StashCommands) StashUnstagedChanges(message string) error {
|
||||||
if err := self.cmd.New("git commit --no-verify -m \"[lazygit] stashing unstaged changes\"").Run(); err != nil {
|
if err := self.cmd.New(
|
||||||
|
NewGitCmd("commit").
|
||||||
|
Arg("--no-verify", "-m", self.cmd.Quote("[lazygit] stashing unstaged changes")).
|
||||||
|
ToString(),
|
||||||
|
).Run(); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
if err := self.Save(message); err != nil {
|
if err := self.Save(message); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
if err := self.cmd.New("git reset --soft HEAD^").Run(); err != nil {
|
|
||||||
|
if err := self.cmd.New(
|
||||||
|
NewGitCmd("reset").Arg("--soft", "HEAD^").ToString(),
|
||||||
|
).Run(); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
@@ -97,7 +123,9 @@ func (self *StashCommands) StashUnstagedChanges(message string) error {
|
|||||||
// shoutouts to Joe on https://stackoverflow.com/questions/14759748/stashing-only-staged-changes-in-git-is-it-possible
|
// shoutouts to Joe on https://stackoverflow.com/questions/14759748/stashing-only-staged-changes-in-git-is-it-possible
|
||||||
func (self *StashCommands) SaveStagedChanges(message string) error {
|
func (self *StashCommands) SaveStagedChanges(message string) error {
|
||||||
// wrap in 'writing', which uses a mutex
|
// wrap in 'writing', which uses a mutex
|
||||||
if err := self.cmd.New("git stash --keep-index").Run(); err != nil {
|
if err := self.cmd.New(
|
||||||
|
NewGitCmd("stash").Arg("--keep-index").ToString(),
|
||||||
|
).Run(); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -105,15 +133,22 @@ func (self *StashCommands) SaveStagedChanges(message string) error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := self.cmd.New("git stash apply stash@{1}").Run(); err != nil {
|
if err := self.cmd.New(
|
||||||
|
NewGitCmd("stash").Arg("apply", "stash@{1}").ToString(),
|
||||||
|
).Run(); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := self.os.PipeCommands("git stash show -p", "git apply -R"); err != nil {
|
if err := self.os.PipeCommands(
|
||||||
|
NewGitCmd("stash").Arg("show", "-p").ToString(),
|
||||||
|
NewGitCmd("apply").Arg("-R").ToString(),
|
||||||
|
); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := self.cmd.New("git stash drop stash@{1}").Run(); err != nil {
|
if err := self.cmd.New(
|
||||||
|
NewGitCmd("stash").Arg("drop", "stash@{1}").ToString(),
|
||||||
|
).Run(); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -135,7 +170,10 @@ func (self *StashCommands) SaveStagedChanges(message string) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (self *StashCommands) StashIncludeUntrackedChanges(message string) error {
|
func (self *StashCommands) StashIncludeUntrackedChanges(message string) error {
|
||||||
return self.cmd.New(fmt.Sprintf("git stash save %s --include-untracked", self.cmd.Quote(message))).Run()
|
return self.cmd.New(
|
||||||
|
NewGitCmd("stash").Arg("save", self.cmd.Quote(message), "--include-untracked").
|
||||||
|
ToString(),
|
||||||
|
).Run()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (self *StashCommands) Rename(index int, message string) error {
|
func (self *StashCommands) Rename(index int, message string) error {
|
||||||
|
@@ -32,7 +32,8 @@ func (self *StashLoader) GetStashEntries(filterPath string) []*models.StashEntry
|
|||||||
return self.getUnfilteredStashEntries()
|
return self.getUnfilteredStashEntries()
|
||||||
}
|
}
|
||||||
|
|
||||||
rawString, err := self.cmd.New("git stash list --name-only").DontLog().RunWithOutput()
|
cmdStr := NewGitCmd("stash").Arg("list", "--name-only").ToString()
|
||||||
|
rawString, err := self.cmd.New(cmdStr).DontLog().RunWithOutput()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return self.getUnfilteredStashEntries()
|
return self.getUnfilteredStashEntries()
|
||||||
}
|
}
|
||||||
@@ -65,7 +66,9 @@ outer:
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (self *StashLoader) getUnfilteredStashEntries() []*models.StashEntry {
|
func (self *StashLoader) getUnfilteredStashEntries() []*models.StashEntry {
|
||||||
rawString, _ := self.cmd.New("git stash list -z --pretty='%gs'").DontLog().RunWithOutput()
|
cmdStr := NewGitCmd("stash").Arg("list", "-z", "--pretty='%gs'").ToString()
|
||||||
|
|
||||||
|
rawString, _ := self.cmd.New(cmdStr).DontLog().RunWithOutput()
|
||||||
return slices.MapWithIndex(utils.SplitNul(rawString), func(line string, index int) *models.StashEntry {
|
return slices.MapWithIndex(utils.SplitNul(rawString), func(line string, index int) *models.StashEntry {
|
||||||
return self.stashEntryFromLine(line, index)
|
return self.stashEntryFromLine(line, index)
|
||||||
})
|
})
|
||||||
|
@@ -56,7 +56,9 @@ func (self *StatusCommands) IsBareRepo() (bool, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func IsBareRepo(osCommand *oscommands.OSCommand) (bool, error) {
|
func IsBareRepo(osCommand *oscommands.OSCommand) (bool, error) {
|
||||||
res, err := osCommand.Cmd.New("git rev-parse --is-bare-repository").DontLog().RunWithOutput()
|
res, err := osCommand.Cmd.New(
|
||||||
|
NewGitCmd("rev-parse").Arg("--is-bare-repository").ToString(),
|
||||||
|
).DontLog().RunWithOutput()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return false, err
|
return false, err
|
||||||
}
|
}
|
||||||
|
@@ -2,7 +2,6 @@ package git_commands
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"bufio"
|
"bufio"
|
||||||
"fmt"
|
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"regexp"
|
"regexp"
|
||||||
@@ -82,38 +81,60 @@ func (self *SubmoduleCommands) Stash(submodule *models.SubmoduleConfig) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
return self.cmd.New("git -C " + self.cmd.Quote(submodule.Path) + " stash --include-untracked").Run()
|
cmdStr := NewGitCmd("stash").
|
||||||
|
RepoPath(self.cmd.Quote(submodule.Path)).
|
||||||
|
Arg("--include-untracked").
|
||||||
|
ToString()
|
||||||
|
|
||||||
|
return self.cmd.New(cmdStr).Run()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (self *SubmoduleCommands) Reset(submodule *models.SubmoduleConfig) error {
|
func (self *SubmoduleCommands) Reset(submodule *models.SubmoduleConfig) error {
|
||||||
return self.cmd.New("git submodule update --init --force -- " + self.cmd.Quote(submodule.Path)).Run()
|
cmdStr := NewGitCmd("submodule").
|
||||||
|
Arg("update", "--init", "--force", "--", self.cmd.Quote(submodule.Path)).
|
||||||
|
ToString()
|
||||||
|
|
||||||
|
return self.cmd.New(cmdStr).Run()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (self *SubmoduleCommands) UpdateAll() error {
|
func (self *SubmoduleCommands) UpdateAll() error {
|
||||||
// not doing an --init here because the user probably doesn't want that
|
// not doing an --init here because the user probably doesn't want that
|
||||||
return self.cmd.New("git submodule update --force").Run()
|
cmdStr := NewGitCmd("submodule").Arg("update", "--force").ToString()
|
||||||
|
|
||||||
|
return self.cmd.New(cmdStr).Run()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (self *SubmoduleCommands) Delete(submodule *models.SubmoduleConfig) error {
|
func (self *SubmoduleCommands) Delete(submodule *models.SubmoduleConfig) error {
|
||||||
// based on https://gist.github.com/myusuf3/7f645819ded92bda6677
|
// based on https://gist.github.com/myusuf3/7f645819ded92bda6677
|
||||||
|
|
||||||
if err := self.cmd.New("git submodule deinit --force -- " + self.cmd.Quote(submodule.Path)).Run(); err != nil {
|
if err := self.cmd.New(
|
||||||
if strings.Contains(err.Error(), "did not match any file(s) known to git") {
|
NewGitCmd("submodule").
|
||||||
if err := self.cmd.New("git config --file .gitmodules --remove-section submodule." + self.cmd.Quote(submodule.Name)).Run(); err != nil {
|
Arg("deinit", "--force", "--", self.cmd.Quote(submodule.Path)).ToString(),
|
||||||
|
).Run(); err != nil {
|
||||||
|
if !strings.Contains(err.Error(), "did not match any file(s) known to git") {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := self.cmd.New("git config --remove-section submodule." + self.cmd.Quote(submodule.Name)).Run(); err != nil {
|
if err := self.cmd.New(
|
||||||
|
NewGitCmd("config").
|
||||||
|
Arg("--file", ".gitmodules", "--remove-section", "submodule."+self.cmd.Quote(submodule.Path)).
|
||||||
|
ToString(),
|
||||||
|
).Run(); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
// if there's an error here about it not existing then we'll just continue to do `git rm`
|
if err := self.cmd.New(
|
||||||
} else {
|
NewGitCmd("config").
|
||||||
|
Arg("--remove-section", "submodule."+self.cmd.Quote(submodule.Path)).
|
||||||
|
ToString(),
|
||||||
|
).Run(); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := self.cmd.New("git rm --force -r " + submodule.Path).Run(); err != nil {
|
if err := self.cmd.New(
|
||||||
|
NewGitCmd("rm").Arg("--force", "-r", submodule.Path).ToString(),
|
||||||
|
).Run(); err != nil {
|
||||||
// if the directory isn't there then that's fine
|
// if the directory isn't there then that's fine
|
||||||
self.Log.Error(err)
|
self.Log.Error(err)
|
||||||
}
|
}
|
||||||
@@ -122,24 +143,35 @@ func (self *SubmoduleCommands) Delete(submodule *models.SubmoduleConfig) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (self *SubmoduleCommands) Add(name string, path string, url string) error {
|
func (self *SubmoduleCommands) Add(name string, path string, url string) error {
|
||||||
return self.cmd.
|
cmdStr := NewGitCmd("submodule").
|
||||||
New(
|
Arg("add").
|
||||||
fmt.Sprintf(
|
Arg("--force").
|
||||||
"git submodule add --force --name %s -- %s %s ",
|
Arg("--name").
|
||||||
self.cmd.Quote(name),
|
Arg(self.cmd.Quote(name)).
|
||||||
self.cmd.Quote(url),
|
Arg("--").
|
||||||
self.cmd.Quote(path),
|
Arg(self.cmd.Quote(url)).
|
||||||
)).
|
Arg(self.cmd.Quote(path)).
|
||||||
Run()
|
ToString()
|
||||||
|
|
||||||
|
return self.cmd.New(cmdStr).Run()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (self *SubmoduleCommands) UpdateUrl(name string, path string, newUrl string) error {
|
func (self *SubmoduleCommands) UpdateUrl(name string, path string, newUrl string) error {
|
||||||
|
setUrlCmdStr := NewGitCmd("config").
|
||||||
|
Arg(
|
||||||
|
"--file", ".gitmodules", "submodule."+self.cmd.Quote(name)+".url", self.cmd.Quote(newUrl),
|
||||||
|
).
|
||||||
|
ToString()
|
||||||
|
|
||||||
// the set-url command is only for later git versions so we're doing it manually here
|
// the set-url command is only for later git versions so we're doing it manually here
|
||||||
if err := self.cmd.New("git config --file .gitmodules submodule." + self.cmd.Quote(name) + ".url " + self.cmd.Quote(newUrl)).Run(); err != nil {
|
if err := self.cmd.New(setUrlCmdStr).Run(); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := self.cmd.New("git submodule sync -- " + self.cmd.Quote(path)).Run(); err != nil {
|
syncCmdStr := NewGitCmd("submodule").Arg("sync", "--", self.cmd.Quote(path)).
|
||||||
|
ToString()
|
||||||
|
|
||||||
|
if err := self.cmd.New(syncCmdStr).Run(); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -147,27 +179,45 @@ func (self *SubmoduleCommands) UpdateUrl(name string, path string, newUrl string
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (self *SubmoduleCommands) Init(path string) error {
|
func (self *SubmoduleCommands) Init(path string) error {
|
||||||
return self.cmd.New("git submodule init -- " + self.cmd.Quote(path)).Run()
|
cmdStr := NewGitCmd("submodule").Arg("init", "--", self.cmd.Quote(path)).
|
||||||
|
ToString()
|
||||||
|
|
||||||
|
return self.cmd.New(cmdStr).Run()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (self *SubmoduleCommands) Update(path string) error {
|
func (self *SubmoduleCommands) Update(path string) error {
|
||||||
return self.cmd.New("git submodule update --init -- " + self.cmd.Quote(path)).Run()
|
cmdStr := NewGitCmd("submodule").Arg("update", "--init", "--", self.cmd.Quote(path)).
|
||||||
|
ToString()
|
||||||
|
|
||||||
|
return self.cmd.New(cmdStr).Run()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (self *SubmoduleCommands) BulkInitCmdObj() oscommands.ICmdObj {
|
func (self *SubmoduleCommands) BulkInitCmdObj() oscommands.ICmdObj {
|
||||||
return self.cmd.New("git submodule init")
|
cmdStr := NewGitCmd("submodule").Arg("init").
|
||||||
|
ToString()
|
||||||
|
|
||||||
|
return self.cmd.New(cmdStr)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (self *SubmoduleCommands) BulkUpdateCmdObj() oscommands.ICmdObj {
|
func (self *SubmoduleCommands) BulkUpdateCmdObj() oscommands.ICmdObj {
|
||||||
return self.cmd.New("git submodule update")
|
cmdStr := NewGitCmd("submodule").Arg("update").
|
||||||
|
ToString()
|
||||||
|
|
||||||
|
return self.cmd.New(cmdStr)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (self *SubmoduleCommands) ForceBulkUpdateCmdObj() oscommands.ICmdObj {
|
func (self *SubmoduleCommands) ForceBulkUpdateCmdObj() oscommands.ICmdObj {
|
||||||
return self.cmd.New("git submodule update --force")
|
cmdStr := NewGitCmd("submodule").Arg("update", "--force").
|
||||||
|
ToString()
|
||||||
|
|
||||||
|
return self.cmd.New(cmdStr)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (self *SubmoduleCommands) BulkDeinitCmdObj() oscommands.ICmdObj {
|
func (self *SubmoduleCommands) BulkDeinitCmdObj() oscommands.ICmdObj {
|
||||||
return self.cmd.New("git submodule deinit --all --force")
|
cmdStr := NewGitCmd("submodule").Arg("deinit", "--all", "--force").
|
||||||
|
ToString()
|
||||||
|
|
||||||
|
return self.cmd.New(cmdStr)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (self *SubmoduleCommands) ResetSubmodules(submodules []*models.SubmoduleConfig) error {
|
func (self *SubmoduleCommands) ResetSubmodules(submodules []*models.SubmoduleConfig) error {
|
||||||
|
@@ -1,8 +1,6 @@
|
|||||||
package git_commands
|
package git_commands
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
|
||||||
|
|
||||||
"github.com/go-errors/errors"
|
"github.com/go-errors/errors"
|
||||||
"github.com/jesseduffield/lazygit/pkg/commands/oscommands"
|
"github.com/jesseduffield/lazygit/pkg/commands/oscommands"
|
||||||
)
|
)
|
||||||
@@ -26,26 +24,16 @@ type PushOpts struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (self *SyncCommands) PushCmdObj(opts PushOpts) (oscommands.ICmdObj, error) {
|
func (self *SyncCommands) PushCmdObj(opts PushOpts) (oscommands.ICmdObj, error) {
|
||||||
cmdStr := "git push"
|
if opts.UpstreamBranch != "" && opts.UpstreamRemote == "" {
|
||||||
|
|
||||||
if opts.Force {
|
|
||||||
cmdStr += " --force-with-lease"
|
|
||||||
}
|
|
||||||
|
|
||||||
if opts.SetUpstream {
|
|
||||||
cmdStr += " --set-upstream"
|
|
||||||
}
|
|
||||||
|
|
||||||
if opts.UpstreamRemote != "" {
|
|
||||||
cmdStr += " " + self.cmd.Quote(opts.UpstreamRemote)
|
|
||||||
}
|
|
||||||
|
|
||||||
if opts.UpstreamBranch != "" {
|
|
||||||
if opts.UpstreamRemote == "" {
|
|
||||||
return nil, errors.New(self.Tr.MustSpecifyOriginError)
|
return nil, errors.New(self.Tr.MustSpecifyOriginError)
|
||||||
}
|
}
|
||||||
cmdStr += " " + self.cmd.Quote(opts.UpstreamBranch)
|
|
||||||
}
|
cmdStr := NewGitCmd("push").
|
||||||
|
ArgIf(opts.Force, "--force-with-lease").
|
||||||
|
ArgIf(opts.SetUpstream, "--set-upstream").
|
||||||
|
ArgIf(opts.UpstreamRemote != "", self.cmd.Quote(opts.UpstreamRemote)).
|
||||||
|
ArgIf(opts.UpstreamBranch != "", self.cmd.Quote(opts.UpstreamBranch)).
|
||||||
|
ToString()
|
||||||
|
|
||||||
cmdObj := self.cmd.New(cmdStr).PromptOnCredentialRequest().WithMutex(self.syncMutex)
|
cmdObj := self.cmd.New(cmdStr).PromptOnCredentialRequest().WithMutex(self.syncMutex)
|
||||||
return cmdObj, nil
|
return cmdObj, nil
|
||||||
@@ -68,14 +56,10 @@ type FetchOptions struct {
|
|||||||
|
|
||||||
// Fetch fetch git repo
|
// Fetch fetch git repo
|
||||||
func (self *SyncCommands) Fetch(opts FetchOptions) error {
|
func (self *SyncCommands) Fetch(opts FetchOptions) error {
|
||||||
cmdStr := "git fetch"
|
cmdStr := NewGitCmd("fetch").
|
||||||
|
ArgIf(opts.RemoteName != "", self.cmd.Quote(opts.RemoteName)).
|
||||||
if opts.RemoteName != "" {
|
ArgIf(opts.BranchName != "", self.cmd.Quote(opts.BranchName)).
|
||||||
cmdStr = fmt.Sprintf("%s %s", cmdStr, self.cmd.Quote(opts.RemoteName))
|
ToString()
|
||||||
}
|
|
||||||
if opts.BranchName != "" {
|
|
||||||
cmdStr = fmt.Sprintf("%s %s", cmdStr, self.cmd.Quote(opts.BranchName))
|
|
||||||
}
|
|
||||||
|
|
||||||
cmdObj := self.cmd.New(cmdStr)
|
cmdObj := self.cmd.New(cmdStr)
|
||||||
if opts.Background {
|
if opts.Background {
|
||||||
@@ -93,18 +77,12 @@ type PullOptions struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (self *SyncCommands) Pull(opts PullOptions) error {
|
func (self *SyncCommands) Pull(opts PullOptions) error {
|
||||||
cmdStr := "git pull --no-edit"
|
cmdStr := NewGitCmd("pull").
|
||||||
|
Arg("--no-edit").
|
||||||
if opts.FastForwardOnly {
|
ArgIf(opts.FastForwardOnly, "--ff-only").
|
||||||
cmdStr += " --ff-only"
|
ArgIf(opts.RemoteName != "", self.cmd.Quote(opts.RemoteName)).
|
||||||
}
|
ArgIf(opts.BranchName != "", self.cmd.Quote(opts.BranchName)).
|
||||||
|
ToString()
|
||||||
if opts.RemoteName != "" {
|
|
||||||
cmdStr = fmt.Sprintf("%s %s", cmdStr, self.cmd.Quote(opts.RemoteName))
|
|
||||||
}
|
|
||||||
if opts.BranchName != "" {
|
|
||||||
cmdStr = fmt.Sprintf("%s %s", cmdStr, self.cmd.Quote(opts.BranchName))
|
|
||||||
}
|
|
||||||
|
|
||||||
// setting GIT_SEQUENCE_EDITOR to ':' as a way of skipping it, in case the user
|
// setting GIT_SEQUENCE_EDITOR to ':' as a way of skipping it, in case the user
|
||||||
// has 'pull.rebase = interactive' configured.
|
// has 'pull.rebase = interactive' configured.
|
||||||
@@ -112,11 +90,18 @@ func (self *SyncCommands) Pull(opts PullOptions) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (self *SyncCommands) FastForward(branchName string, remoteName string, remoteBranchName string) error {
|
func (self *SyncCommands) FastForward(branchName string, remoteName string, remoteBranchName string) error {
|
||||||
cmdStr := fmt.Sprintf("git fetch %s %s:%s", self.cmd.Quote(remoteName), self.cmd.Quote(remoteBranchName), self.cmd.Quote(branchName))
|
cmdStr := NewGitCmd("fetch").
|
||||||
|
Arg(self.cmd.Quote(remoteName)).
|
||||||
|
Arg(self.cmd.Quote(remoteBranchName) + ":" + self.cmd.Quote(branchName)).
|
||||||
|
ToString()
|
||||||
|
|
||||||
return self.cmd.New(cmdStr).PromptOnCredentialRequest().WithMutex(self.syncMutex).Run()
|
return self.cmd.New(cmdStr).PromptOnCredentialRequest().WithMutex(self.syncMutex).Run()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (self *SyncCommands) FetchRemote(remoteName string) error {
|
func (self *SyncCommands) FetchRemote(remoteName string) error {
|
||||||
cmdStr := fmt.Sprintf("git fetch %s", self.cmd.Quote(remoteName))
|
cmdStr := NewGitCmd("fetch").
|
||||||
|
Arg(self.cmd.Quote(remoteName)).
|
||||||
|
ToString()
|
||||||
|
|
||||||
return self.cmd.New(cmdStr).PromptOnCredentialRequest().WithMutex(self.syncMutex).Run()
|
return self.cmd.New(cmdStr).PromptOnCredentialRequest().WithMutex(self.syncMutex).Run()
|
||||||
}
|
}
|
||||||
|
@@ -1,9 +1,5 @@
|
|||||||
package git_commands
|
package git_commands
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
)
|
|
||||||
|
|
||||||
type TagCommands struct {
|
type TagCommands struct {
|
||||||
*GitCommon
|
*GitCommon
|
||||||
}
|
}
|
||||||
@@ -15,25 +11,32 @@ func NewTagCommands(gitCommon *GitCommon) *TagCommands {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (self *TagCommands) CreateLightweight(tagName string, ref string) error {
|
func (self *TagCommands) CreateLightweight(tagName string, ref string) error {
|
||||||
if len(ref) > 0 {
|
cmdStr := NewGitCmd("tag").Arg("--", self.cmd.Quote(tagName)).
|
||||||
return self.cmd.New(fmt.Sprintf("git tag -- %s %s", self.cmd.Quote(tagName), self.cmd.Quote(ref))).Run()
|
ArgIf(len(ref) > 0, self.cmd.Quote(ref)).
|
||||||
} else {
|
ToString()
|
||||||
return self.cmd.New(fmt.Sprintf("git tag -- %s", self.cmd.Quote(tagName))).Run()
|
|
||||||
}
|
return self.cmd.New(cmdStr).Run()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (self *TagCommands) CreateAnnotated(tagName, ref, msg string) error {
|
func (self *TagCommands) CreateAnnotated(tagName, ref, msg string) error {
|
||||||
if len(ref) > 0 {
|
cmdStr := NewGitCmd("tag").Arg(self.cmd.Quote(tagName)).
|
||||||
return self.cmd.New(fmt.Sprintf("git tag %s %s -m %s", self.cmd.Quote(tagName), self.cmd.Quote(ref), self.cmd.Quote(msg))).Run()
|
ArgIf(len(ref) > 0, self.cmd.Quote(ref)).
|
||||||
} else {
|
Arg("-m", self.cmd.Quote(msg)).
|
||||||
return self.cmd.New(fmt.Sprintf("git tag %s -m %s", self.cmd.Quote(tagName), self.cmd.Quote(msg))).Run()
|
ToString()
|
||||||
}
|
|
||||||
|
return self.cmd.New(cmdStr).Run()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (self *TagCommands) Delete(tagName string) error {
|
func (self *TagCommands) Delete(tagName string) error {
|
||||||
return self.cmd.New(fmt.Sprintf("git tag -d %s", self.cmd.Quote(tagName))).Run()
|
cmdStr := NewGitCmd("tag").Arg("-d", self.cmd.Quote(tagName)).
|
||||||
|
ToString()
|
||||||
|
|
||||||
|
return self.cmd.New(cmdStr).Run()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (self *TagCommands) Push(remoteName string, tagName string) error {
|
func (self *TagCommands) Push(remoteName string, tagName string) error {
|
||||||
return self.cmd.New(fmt.Sprintf("git push %s tag %s", self.cmd.Quote(remoteName), self.cmd.Quote(tagName))).PromptOnCredentialRequest().WithMutex(self.syncMutex).Run()
|
cmdStr := NewGitCmd("push").Arg(self.cmd.Quote(remoteName), "tag", self.cmd.Quote(tagName)).
|
||||||
|
ToString()
|
||||||
|
|
||||||
|
return self.cmd.New(cmdStr).PromptOnCredentialRequest().WithMutex(self.syncMutex).Run()
|
||||||
}
|
}
|
||||||
|
@@ -28,7 +28,8 @@ func NewTagLoader(
|
|||||||
func (self *TagLoader) GetTags() ([]*models.Tag, error) {
|
func (self *TagLoader) GetTags() ([]*models.Tag, error) {
|
||||||
// get remote branches, sorted by creation date (descending)
|
// get remote branches, sorted by creation date (descending)
|
||||||
// see: https://git-scm.com/docs/git-tag#Documentation/git-tag.txt---sortltkeygt
|
// see: https://git-scm.com/docs/git-tag#Documentation/git-tag.txt---sortltkeygt
|
||||||
tagsOutput, err := self.cmd.New(`git tag --list -n --sort=-creatordate`).DontLog().RunWithOutput()
|
cmdStr := NewGitCmd("tag").Arg("--list", "-n", "--sort=-creatordate").ToString()
|
||||||
|
tagsOutput, err := self.cmd.New(cmdStr).DontLog().RunWithOutput()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
@@ -15,7 +15,7 @@ type GitVersion struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func GetGitVersion(osCommand *oscommands.OSCommand) (*GitVersion, error) {
|
func GetGitVersion(osCommand *oscommands.OSCommand) (*GitVersion, error) {
|
||||||
versionStr, _, err := osCommand.Cmd.New("git --version").RunWithOutputs()
|
versionStr, _, err := osCommand.Cmd.New(NewGitCmd("--version").ToString()).RunWithOutputs()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
@@ -3,15 +3,11 @@ package git_commands
|
|||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
|
||||||
"strings"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/go-errors/errors"
|
"github.com/go-errors/errors"
|
||||||
"github.com/jesseduffield/generics/slices"
|
"github.com/jesseduffield/generics/slices"
|
||||||
"github.com/jesseduffield/lazygit/pkg/commands/models"
|
"github.com/jesseduffield/lazygit/pkg/commands/models"
|
||||||
"github.com/jesseduffield/lazygit/pkg/commands/oscommands"
|
"github.com/jesseduffield/lazygit/pkg/commands/oscommands"
|
||||||
"github.com/jesseduffield/lazygit/pkg/utils"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
type WorkingTreeCommands struct {
|
type WorkingTreeCommands struct {
|
||||||
@@ -33,7 +29,7 @@ func NewWorkingTreeCommands(
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (self *WorkingTreeCommands) OpenMergeToolCmdObj() oscommands.ICmdObj {
|
func (self *WorkingTreeCommands) OpenMergeToolCmdObj() oscommands.ICmdObj {
|
||||||
return self.cmd.New("git mergetool")
|
return self.cmd.New(NewGitCmd("mergetool").ToString())
|
||||||
}
|
}
|
||||||
|
|
||||||
func (self *WorkingTreeCommands) OpenMergeTool() error {
|
func (self *WorkingTreeCommands) OpenMergeTool() error {
|
||||||
@@ -49,30 +45,37 @@ func (self *WorkingTreeCommands) StageFiles(paths []string) error {
|
|||||||
quotedPaths := slices.Map(paths, func(path string) string {
|
quotedPaths := slices.Map(paths, func(path string) string {
|
||||||
return self.cmd.Quote(path)
|
return self.cmd.Quote(path)
|
||||||
})
|
})
|
||||||
return self.cmd.New(fmt.Sprintf("git add -- %s", strings.Join(quotedPaths, " "))).Run()
|
|
||||||
|
cmdStr := NewGitCmd("add").Arg("--").Arg(quotedPaths...).ToString()
|
||||||
|
|
||||||
|
return self.cmd.New(cmdStr).Run()
|
||||||
}
|
}
|
||||||
|
|
||||||
// StageAll stages all files
|
// StageAll stages all files
|
||||||
func (self *WorkingTreeCommands) StageAll() error {
|
func (self *WorkingTreeCommands) StageAll() error {
|
||||||
return self.cmd.New("git add -A").Run()
|
cmdStr := NewGitCmd("add").Arg("-A").ToString()
|
||||||
|
|
||||||
|
return self.cmd.New(cmdStr).Run()
|
||||||
}
|
}
|
||||||
|
|
||||||
// UnstageAll unstages all files
|
// UnstageAll unstages all files
|
||||||
func (self *WorkingTreeCommands) UnstageAll() error {
|
func (self *WorkingTreeCommands) UnstageAll() error {
|
||||||
return self.cmd.New("git reset").Run()
|
return self.cmd.New(NewGitCmd("reset").ToString()).Run()
|
||||||
}
|
}
|
||||||
|
|
||||||
// UnStageFile unstages a file
|
// UnStageFile unstages a file
|
||||||
// we accept an array of filenames for the cases where a file has been renamed i.e.
|
// we accept an array of filenames for the cases where a file has been renamed i.e.
|
||||||
// we accept the current name and the previous name
|
// we accept the current name and the previous name
|
||||||
func (self *WorkingTreeCommands) UnStageFile(fileNames []string, reset bool) error {
|
func (self *WorkingTreeCommands) UnStageFile(fileNames []string, reset bool) error {
|
||||||
command := "git rm --cached --force -- %s"
|
for _, name := range fileNames {
|
||||||
|
var cmdStr string
|
||||||
if reset {
|
if reset {
|
||||||
command = "git reset HEAD -- %s"
|
cmdStr = NewGitCmd("reset").Arg("HEAD", "--", self.cmd.Quote(name)).ToString()
|
||||||
|
} else {
|
||||||
|
cmdStr = NewGitCmd("rm").Arg("--cached", "--force", "--", self.cmd.Quote(name)).ToString()
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, name := range fileNames {
|
err := self.cmd.New(cmdStr).Run()
|
||||||
err := self.cmd.New(fmt.Sprintf(command, self.cmd.Quote(name))).Run()
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@@ -137,22 +140,31 @@ func (self *WorkingTreeCommands) DiscardAllFileChanges(file *models.File) error
|
|||||||
quotedFileName := self.cmd.Quote(file.Name)
|
quotedFileName := self.cmd.Quote(file.Name)
|
||||||
|
|
||||||
if file.ShortStatus == "AA" {
|
if file.ShortStatus == "AA" {
|
||||||
if err := self.cmd.New("git checkout --ours -- " + quotedFileName).Run(); err != nil {
|
if err := self.cmd.New(
|
||||||
|
NewGitCmd("checkout").Arg("--ours", "--", quotedFileName).ToString(),
|
||||||
|
).Run(); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
if err := self.cmd.New("git add -- " + quotedFileName).Run(); err != nil {
|
|
||||||
|
if err := self.cmd.New(
|
||||||
|
NewGitCmd("add").Arg("--", quotedFileName).ToString(),
|
||||||
|
).Run(); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
if file.ShortStatus == "DU" {
|
if file.ShortStatus == "DU" {
|
||||||
return self.cmd.New("git rm -- " + quotedFileName).Run()
|
return self.cmd.New(
|
||||||
|
NewGitCmd("rm").Arg("rm", "--", quotedFileName).ToString(),
|
||||||
|
).Run()
|
||||||
}
|
}
|
||||||
|
|
||||||
// if the file isn't tracked, we assume you want to delete it
|
// if the file isn't tracked, we assume you want to delete it
|
||||||
if file.HasStagedChanges || file.HasMergeConflicts {
|
if file.HasStagedChanges || file.HasMergeConflicts {
|
||||||
if err := self.cmd.New("git reset -- " + quotedFileName).Run(); err != nil {
|
if err := self.cmd.New(
|
||||||
|
NewGitCmd("reset").Arg("--", quotedFileName).ToString(),
|
||||||
|
).Run(); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -184,7 +196,8 @@ func (self *WorkingTreeCommands) DiscardUnstagedDirChanges(node IFileNode) error
|
|||||||
}
|
}
|
||||||
|
|
||||||
quotedPath := self.cmd.Quote(node.GetPath())
|
quotedPath := self.cmd.Quote(node.GetPath())
|
||||||
if err := self.cmd.New("git checkout -- " + quotedPath).Run(); err != nil {
|
cmdStr := NewGitCmd("checkout").Arg("--", quotedPath).ToString()
|
||||||
|
if err := self.cmd.New(cmdStr).Run(); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -209,7 +222,8 @@ func (self *WorkingTreeCommands) RemoveUntrackedDirFiles(node IFileNode) error {
|
|||||||
// DiscardUnstagedFileChanges directly
|
// DiscardUnstagedFileChanges directly
|
||||||
func (self *WorkingTreeCommands) DiscardUnstagedFileChanges(file *models.File) error {
|
func (self *WorkingTreeCommands) DiscardUnstagedFileChanges(file *models.File) error {
|
||||||
quotedFileName := self.cmd.Quote(file.Name)
|
quotedFileName := self.cmd.Quote(file.Name)
|
||||||
return self.cmd.New("git checkout -- " + quotedFileName).Run()
|
cmdStr := NewGitCmd("checkout").Arg("--", quotedFileName).ToString()
|
||||||
|
return self.cmd.New(cmdStr).Run()
|
||||||
}
|
}
|
||||||
|
|
||||||
// Ignore adds a file to the gitignore for the repo
|
// Ignore adds a file to the gitignore for the repo
|
||||||
@@ -230,61 +244,32 @@ func (self *WorkingTreeCommands) WorktreeFileDiff(file *models.File, plain bool,
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (self *WorkingTreeCommands) WorktreeFileDiffCmdObj(node models.IFile, plain bool, cached bool, ignoreWhitespace bool) oscommands.ICmdObj {
|
func (self *WorkingTreeCommands) WorktreeFileDiffCmdObj(node models.IFile, plain bool, cached bool, ignoreWhitespace bool) oscommands.ICmdObj {
|
||||||
cachedArg := ""
|
|
||||||
trackedArg := "--"
|
|
||||||
colorArg := self.UserConfig.Git.Paging.ColorArg
|
colorArg := self.UserConfig.Git.Paging.ColorArg
|
||||||
quotedPath := self.cmd.Quote(node.GetPath())
|
|
||||||
quotedPrevPath := ""
|
|
||||||
ignoreWhitespaceArg := ""
|
|
||||||
contextSize := self.UserConfig.Git.DiffContextSize
|
|
||||||
if cached {
|
|
||||||
cachedArg = " --cached"
|
|
||||||
}
|
|
||||||
if !node.GetIsTracked() && !node.GetHasStagedChanges() && !cached && node.GetIsFile() {
|
|
||||||
trackedArg = "--no-index -- /dev/null"
|
|
||||||
}
|
|
||||||
if plain {
|
if plain {
|
||||||
colorArg = "never"
|
colorArg = "never"
|
||||||
}
|
}
|
||||||
if ignoreWhitespace {
|
|
||||||
ignoreWhitespaceArg = " --ignore-all-space"
|
|
||||||
}
|
|
||||||
if prevPath := node.GetPreviousPath(); prevPath != "" {
|
|
||||||
quotedPrevPath = " " + self.cmd.Quote(prevPath)
|
|
||||||
}
|
|
||||||
|
|
||||||
cmdStr := fmt.Sprintf("git diff --submodule --no-ext-diff --unified=%d --color=%s%s%s %s %s%s", contextSize, colorArg, ignoreWhitespaceArg, cachedArg, trackedArg, quotedPath, quotedPrevPath)
|
contextSize := self.UserConfig.Git.DiffContextSize
|
||||||
|
prevPath := node.GetPreviousPath()
|
||||||
|
noIndex := !node.GetIsTracked() && !node.GetHasStagedChanges() && !cached && node.GetIsFile()
|
||||||
|
|
||||||
|
cmdStr := NewGitCmd("diff").
|
||||||
|
Arg("--submodule").
|
||||||
|
Arg("--no-ext-diff").
|
||||||
|
Arg(fmt.Sprintf("--unified=%d", contextSize)).
|
||||||
|
Arg(fmt.Sprintf("--color=%s", colorArg)).
|
||||||
|
ArgIf(ignoreWhitespace, "--ignore-all-space").
|
||||||
|
ArgIf(cached, "--cached").
|
||||||
|
ArgIf(noIndex, "--no-index").
|
||||||
|
Arg("--").
|
||||||
|
ArgIf(noIndex, "/dev/null").
|
||||||
|
Arg(self.cmd.Quote(node.GetPath())).
|
||||||
|
ArgIf(prevPath != "", self.cmd.Quote(prevPath)).
|
||||||
|
ToString()
|
||||||
|
|
||||||
return self.cmd.New(cmdStr).DontLog()
|
return self.cmd.New(cmdStr).DontLog()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (self *WorkingTreeCommands) ApplyPatch(patch string, flags ...string) error {
|
|
||||||
filepath, err := self.SaveTemporaryPatch(patch)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
return self.ApplyPatchFile(filepath, flags...)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (self *WorkingTreeCommands) ApplyPatchFile(filepath string, flags ...string) error {
|
|
||||||
flagStr := ""
|
|
||||||
for _, flag := range flags {
|
|
||||||
flagStr += " --" + flag
|
|
||||||
}
|
|
||||||
|
|
||||||
return self.cmd.New(fmt.Sprintf("git apply%s %s", flagStr, self.cmd.Quote(filepath))).Run()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (self *WorkingTreeCommands) SaveTemporaryPatch(patch string) (string, error) {
|
|
||||||
filepath := filepath.Join(self.os.GetTempDir(), utils.GetCurrentRepoName(), time.Now().Format("Jan _2 15.04.05.000000000")+".patch")
|
|
||||||
self.Log.Infof("saving temporary patch to %s", filepath)
|
|
||||||
if err := self.os.CreateFileWithContent(filepath, patch); err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
return filepath, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// ShowFileDiff get the diff of specified from and to. Typically this will be used for a single commit so it'll be 123abc^..123abc
|
// ShowFileDiff get the diff of specified from and to. Typically this will be used for a single commit so it'll be 123abc^..123abc
|
||||||
// but when we're in diff mode it could be any 'from' to any 'to'. The reverse flag is also here thanks to diff mode.
|
// but when we're in diff mode it could be any 'from' to any 'to'. The reverse flag is also here thanks to diff mode.
|
||||||
func (self *WorkingTreeCommands) ShowFileDiff(from string, to string, reverse bool, fileName string, plain bool,
|
func (self *WorkingTreeCommands) ShowFileDiff(from string, to string, reverse bool, fileName string, plain bool,
|
||||||
@@ -296,49 +281,59 @@ func (self *WorkingTreeCommands) ShowFileDiff(from string, to string, reverse bo
|
|||||||
func (self *WorkingTreeCommands) ShowFileDiffCmdObj(from string, to string, reverse bool, fileName string, plain bool,
|
func (self *WorkingTreeCommands) ShowFileDiffCmdObj(from string, to string, reverse bool, fileName string, plain bool,
|
||||||
ignoreWhitespace bool,
|
ignoreWhitespace bool,
|
||||||
) oscommands.ICmdObj {
|
) oscommands.ICmdObj {
|
||||||
colorArg := self.UserConfig.Git.Paging.ColorArg
|
|
||||||
contextSize := self.UserConfig.Git.DiffContextSize
|
contextSize := self.UserConfig.Git.DiffContextSize
|
||||||
|
|
||||||
|
colorArg := self.UserConfig.Git.Paging.ColorArg
|
||||||
if plain {
|
if plain {
|
||||||
colorArg = "never"
|
colorArg = "never"
|
||||||
}
|
}
|
||||||
|
|
||||||
reverseFlag := ""
|
cmdStr := NewGitCmd("diff").
|
||||||
if reverse {
|
Arg("--submodule").
|
||||||
reverseFlag = " -R"
|
Arg("--no-ext-diff").
|
||||||
}
|
Arg(fmt.Sprintf("--unified=%d", contextSize)).
|
||||||
|
Arg("--no-renames").
|
||||||
|
Arg(fmt.Sprintf("--color=%s", colorArg)).
|
||||||
|
Arg(from).
|
||||||
|
Arg(to).
|
||||||
|
ArgIf(reverse, "-R").
|
||||||
|
ArgIf(ignoreWhitespace, "--ignore-all-space").
|
||||||
|
Arg("--").
|
||||||
|
Arg(self.cmd.Quote(fileName)).
|
||||||
|
ToString()
|
||||||
|
|
||||||
ignoreWhitespaceFlag := ""
|
return self.cmd.New(cmdStr).DontLog()
|
||||||
if ignoreWhitespace {
|
|
||||||
ignoreWhitespaceFlag = " --ignore-all-space"
|
|
||||||
}
|
|
||||||
|
|
||||||
return self.cmd.
|
|
||||||
New(
|
|
||||||
fmt.Sprintf(
|
|
||||||
"git diff --submodule --no-ext-diff --unified=%d --no-renames --color=%s%s%s%s%s -- %s",
|
|
||||||
contextSize, colorArg, pad(from), pad(to), reverseFlag, ignoreWhitespaceFlag, self.cmd.Quote(fileName)),
|
|
||||||
).
|
|
||||||
DontLog()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// CheckoutFile checks out the file for the given commit
|
// CheckoutFile checks out the file for the given commit
|
||||||
func (self *WorkingTreeCommands) CheckoutFile(commitSha, fileName string) error {
|
func (self *WorkingTreeCommands) CheckoutFile(commitSha, fileName string) error {
|
||||||
return self.cmd.New(fmt.Sprintf("git checkout %s -- %s", commitSha, self.cmd.Quote(fileName))).Run()
|
cmdStr := NewGitCmd("checkout").Arg(commitSha, "--", self.cmd.Quote(fileName)).
|
||||||
|
ToString()
|
||||||
|
|
||||||
|
return self.cmd.New(cmdStr).Run()
|
||||||
}
|
}
|
||||||
|
|
||||||
// DiscardAnyUnstagedFileChanges discards any unstages file changes via `git checkout -- .`
|
// DiscardAnyUnstagedFileChanges discards any unstaged file changes via `git checkout -- .`
|
||||||
func (self *WorkingTreeCommands) DiscardAnyUnstagedFileChanges() error {
|
func (self *WorkingTreeCommands) DiscardAnyUnstagedFileChanges() error {
|
||||||
return self.cmd.New("git checkout -- .").Run()
|
cmdStr := NewGitCmd("checkout").Arg("--", ".").
|
||||||
|
ToString()
|
||||||
|
|
||||||
|
return self.cmd.New(cmdStr).Run()
|
||||||
}
|
}
|
||||||
|
|
||||||
// RemoveTrackedFiles will delete the given file(s) even if they are currently tracked
|
// RemoveTrackedFiles will delete the given file(s) even if they are currently tracked
|
||||||
func (self *WorkingTreeCommands) RemoveTrackedFiles(name string) error {
|
func (self *WorkingTreeCommands) RemoveTrackedFiles(name string) error {
|
||||||
return self.cmd.New("git rm -r --cached -- " + self.cmd.Quote(name)).Run()
|
cmdStr := NewGitCmd("rm").Arg("-r", "--cached", "--", self.cmd.Quote(name)).
|
||||||
|
ToString()
|
||||||
|
|
||||||
|
return self.cmd.New(cmdStr).Run()
|
||||||
}
|
}
|
||||||
|
|
||||||
// RemoveUntrackedFiles runs `git clean -fd`
|
// RemoveUntrackedFiles runs `git clean -fd`
|
||||||
func (self *WorkingTreeCommands) RemoveUntrackedFiles() error {
|
func (self *WorkingTreeCommands) RemoveUntrackedFiles() error {
|
||||||
return self.cmd.New("git clean -fd").Run()
|
cmdStr := NewGitCmd("clean").Arg("-fd").ToString()
|
||||||
|
|
||||||
|
return self.cmd.New(cmdStr).Run()
|
||||||
}
|
}
|
||||||
|
|
||||||
// ResetAndClean removes all unstaged changes and removes all untracked files
|
// ResetAndClean removes all unstaged changes and removes all untracked files
|
||||||
@@ -363,23 +358,23 @@ func (self *WorkingTreeCommands) ResetAndClean() error {
|
|||||||
|
|
||||||
// ResetHardHead runs `git reset --hard`
|
// ResetHardHead runs `git reset --hard`
|
||||||
func (self *WorkingTreeCommands) ResetHard(ref string) error {
|
func (self *WorkingTreeCommands) ResetHard(ref string) error {
|
||||||
return self.cmd.New("git reset --hard " + self.cmd.Quote(ref)).Run()
|
cmdStr := NewGitCmd("reset").Arg("--hard", self.cmd.Quote(ref)).
|
||||||
|
ToString()
|
||||||
|
|
||||||
|
return self.cmd.New(cmdStr).Run()
|
||||||
}
|
}
|
||||||
|
|
||||||
// ResetSoft runs `git reset --soft HEAD`
|
// ResetSoft runs `git reset --soft HEAD`
|
||||||
func (self *WorkingTreeCommands) ResetSoft(ref string) error {
|
func (self *WorkingTreeCommands) ResetSoft(ref string) error {
|
||||||
return self.cmd.New("git reset --soft " + self.cmd.Quote(ref)).Run()
|
cmdStr := NewGitCmd("reset").Arg("--soft", self.cmd.Quote(ref)).
|
||||||
|
ToString()
|
||||||
|
|
||||||
|
return self.cmd.New(cmdStr).Run()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (self *WorkingTreeCommands) ResetMixed(ref string) error {
|
func (self *WorkingTreeCommands) ResetMixed(ref string) error {
|
||||||
return self.cmd.New("git reset --mixed " + self.cmd.Quote(ref)).Run()
|
cmdStr := NewGitCmd("reset").Arg("--mixed", self.cmd.Quote(ref)).
|
||||||
}
|
ToString()
|
||||||
|
|
||||||
// so that we don't have unnecessary space in our commands we use this helper function to prepend spaces to args so that in the format string we can go '%s%s%s' and if any args are missing we won't have gaps.
|
return self.cmd.New(cmdStr).Run()
|
||||||
func pad(str string) string {
|
|
||||||
if str == "" {
|
|
||||||
return ""
|
|
||||||
}
|
|
||||||
|
|
||||||
return " " + str
|
|
||||||
}
|
}
|
||||||
|
@@ -2,8 +2,6 @@ package git_commands
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"os"
|
|
||||||
"regexp"
|
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/go-errors/errors"
|
"github.com/go-errors/errors"
|
||||||
@@ -430,60 +428,6 @@ func TestWorkingTreeCheckoutFile(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestWorkingTreeApplyPatch(t *testing.T) {
|
|
||||||
type scenario struct {
|
|
||||||
testName string
|
|
||||||
runner *oscommands.FakeCmdObjRunner
|
|
||||||
test func(error)
|
|
||||||
}
|
|
||||||
|
|
||||||
expectFn := func(regexStr string, errToReturn error) func(cmdObj oscommands.ICmdObj) (string, error) {
|
|
||||||
return func(cmdObj oscommands.ICmdObj) (string, error) {
|
|
||||||
re := regexp.MustCompile(regexStr)
|
|
||||||
cmdStr := cmdObj.ToString()
|
|
||||||
matches := re.FindStringSubmatch(cmdStr)
|
|
||||||
assert.Equal(t, 2, len(matches), fmt.Sprintf("unexpected command: %s", cmdStr))
|
|
||||||
|
|
||||||
filename := matches[1]
|
|
||||||
|
|
||||||
content, err := os.ReadFile(filename)
|
|
||||||
assert.NoError(t, err)
|
|
||||||
|
|
||||||
assert.Equal(t, "test", string(content))
|
|
||||||
|
|
||||||
return "", errToReturn
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
scenarios := []scenario{
|
|
||||||
{
|
|
||||||
testName: "valid case",
|
|
||||||
runner: oscommands.NewFakeRunner(t).
|
|
||||||
ExpectFunc(expectFn(`git apply --cached "(.*)"`, nil)),
|
|
||||||
test: func(err error) {
|
|
||||||
assert.NoError(t, err)
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
testName: "command returns error",
|
|
||||||
runner: oscommands.NewFakeRunner(t).
|
|
||||||
ExpectFunc(expectFn(`git apply --cached "(.*)"`, errors.New("error"))),
|
|
||||||
test: func(err error) {
|
|
||||||
assert.Error(t, err)
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, s := range scenarios {
|
|
||||||
s := s
|
|
||||||
t.Run(s.testName, func(t *testing.T) {
|
|
||||||
instance := buildWorkingTreeCommands(commonDeps{runner: s.runner})
|
|
||||||
s.test(instance.ApplyPatch("test", "cached"))
|
|
||||||
s.runner.CheckForMissingCalls()
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestWorkingTreeDiscardUnstagedFileChanges(t *testing.T) {
|
func TestWorkingTreeDiscardUnstagedFileChanges(t *testing.T) {
|
||||||
type scenario struct {
|
type scenario struct {
|
||||||
testName string
|
testName string
|
||||||
|
@@ -29,7 +29,6 @@ type fileInfo struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type (
|
type (
|
||||||
applyPatchFunc func(patch string, flags ...string) error
|
|
||||||
loadFileDiffFunc func(from string, to string, reverse bool, filename string, plain bool) (string, error)
|
loadFileDiffFunc func(from string, to string, reverse bool, filename string, plain bool) (string, error)
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -47,17 +46,14 @@ type PatchBuilder struct {
|
|||||||
// fileInfoMap starts empty but you add files to it as you go along
|
// fileInfoMap starts empty but you add files to it as you go along
|
||||||
fileInfoMap map[string]*fileInfo
|
fileInfoMap map[string]*fileInfo
|
||||||
Log *logrus.Entry
|
Log *logrus.Entry
|
||||||
applyPatch applyPatchFunc
|
|
||||||
|
|
||||||
// loadFileDiff loads the diff of a file, for a given to (typically a commit SHA)
|
// loadFileDiff loads the diff of a file, for a given to (typically a commit SHA)
|
||||||
loadFileDiff loadFileDiffFunc
|
loadFileDiff loadFileDiffFunc
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewPatchBuilder returns a new PatchBuilder
|
func NewPatchBuilder(log *logrus.Entry, loadFileDiff loadFileDiffFunc) *PatchBuilder {
|
||||||
func NewPatchBuilder(log *logrus.Entry, applyPatch applyPatchFunc, loadFileDiff loadFileDiffFunc) *PatchBuilder {
|
|
||||||
return &PatchBuilder{
|
return &PatchBuilder{
|
||||||
Log: log,
|
Log: log,
|
||||||
applyPatch: applyPatch,
|
|
||||||
loadFileDiff: loadFileDiff,
|
loadFileDiff: loadFileDiff,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -70,6 +66,20 @@ func (p *PatchBuilder) Start(from, to string, reverse bool, canRebase bool) {
|
|||||||
p.fileInfoMap = map[string]*fileInfo{}
|
p.fileInfoMap = map[string]*fileInfo{}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (p *PatchBuilder) PatchToApply(reverse bool) string {
|
||||||
|
patch := ""
|
||||||
|
|
||||||
|
for filename, info := range p.fileInfoMap {
|
||||||
|
if info.mode == UNSELECTED {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
patch += p.RenderPatchForFile(filename, true, reverse)
|
||||||
|
}
|
||||||
|
|
||||||
|
return patch
|
||||||
|
}
|
||||||
|
|
||||||
func (p *PatchBuilder) addFileWhole(info *fileInfo) {
|
func (p *PatchBuilder) addFileWhole(info *fileInfo) {
|
||||||
info.mode = WHOLE
|
info.mode = WHOLE
|
||||||
lineCount := len(strings.Split(info.diff, "\n"))
|
lineCount := len(strings.Split(info.diff, "\n"))
|
||||||
@@ -234,25 +244,6 @@ func (p *PatchBuilder) GetFileIncLineIndices(filename string) ([]int, error) {
|
|||||||
return info.includedLineIndices, nil
|
return info.includedLineIndices, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *PatchBuilder) ApplyPatches(reverse bool) error {
|
|
||||||
patch := ""
|
|
||||||
|
|
||||||
applyFlags := []string{"index", "3way"}
|
|
||||||
if reverse {
|
|
||||||
applyFlags = append(applyFlags, "reverse")
|
|
||||||
}
|
|
||||||
|
|
||||||
for filename, info := range p.fileInfoMap {
|
|
||||||
if info.mode == UNSELECTED {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
patch += p.RenderPatchForFile(filename, true, reverse)
|
|
||||||
}
|
|
||||||
|
|
||||||
return p.applyPatch(patch, applyFlags...)
|
|
||||||
}
|
|
||||||
|
|
||||||
// clears the patch
|
// clears the patch
|
||||||
func (p *PatchBuilder) Reset() {
|
func (p *PatchBuilder) Reset() {
|
||||||
p.To = ""
|
p.To = ""
|
||||||
|
@@ -199,7 +199,7 @@ func (self *CustomPatchOptionsMenuAction) handleApplyPatch(reverse bool) error {
|
|||||||
action = "Apply patch in reverse"
|
action = "Apply patch in reverse"
|
||||||
}
|
}
|
||||||
self.c.LogAction(action)
|
self.c.LogAction(action)
|
||||||
if err := self.c.Git().Patch.PatchBuilder.ApplyPatches(reverse); err != nil {
|
if err := self.c.Git().Patch.ApplyCustomPatch(reverse); err != nil {
|
||||||
return self.c.Error(err)
|
return self.c.Error(err)
|
||||||
}
|
}
|
||||||
return self.c.Refresh(types.RefreshOptions{Mode: types.ASYNC})
|
return self.c.Refresh(types.RefreshOptions{Mode: types.ASYNC})
|
||||||
|
@@ -4,6 +4,7 @@ import (
|
|||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/jesseduffield/gocui"
|
"github.com/jesseduffield/gocui"
|
||||||
|
"github.com/jesseduffield/lazygit/pkg/commands/git_commands"
|
||||||
"github.com/jesseduffield/lazygit/pkg/commands/patch"
|
"github.com/jesseduffield/lazygit/pkg/commands/patch"
|
||||||
"github.com/jesseduffield/lazygit/pkg/gui/types"
|
"github.com/jesseduffield/lazygit/pkg/gui/types"
|
||||||
)
|
)
|
||||||
@@ -213,15 +214,14 @@ func (self *StagingController) applySelection(reverse bool) error {
|
|||||||
|
|
||||||
// apply the patch then refresh this panel
|
// apply the patch then refresh this panel
|
||||||
// create a new temp file with the patch, then call git apply with that patch
|
// create a new temp file with the patch, then call git apply with that patch
|
||||||
applyFlags := []string{}
|
|
||||||
if reverse {
|
|
||||||
applyFlags = append(applyFlags, "reverse")
|
|
||||||
}
|
|
||||||
if !reverse || self.staged {
|
|
||||||
applyFlags = append(applyFlags, "cached")
|
|
||||||
}
|
|
||||||
self.c.LogAction(self.c.Tr.Actions.ApplyPatch)
|
self.c.LogAction(self.c.Tr.Actions.ApplyPatch)
|
||||||
err := self.c.Git().WorkingTree.ApplyPatch(patchToApply, applyFlags...)
|
err := self.c.Git().Patch.ApplyPatch(
|
||||||
|
patchToApply,
|
||||||
|
git_commands.ApplyPatchOpts{
|
||||||
|
Reverse: reverse,
|
||||||
|
Cached: !reverse || self.staged,
|
||||||
|
},
|
||||||
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return self.c.Error(err)
|
return self.c.Error(err)
|
||||||
}
|
}
|
||||||
@@ -262,7 +262,7 @@ func (self *StagingController) editHunk() error {
|
|||||||
}).
|
}).
|
||||||
FormatPlain()
|
FormatPlain()
|
||||||
|
|
||||||
patchFilepath, err := self.c.Git().WorkingTree.SaveTemporaryPatch(patchText)
|
patchFilepath, err := self.c.Git().Patch.SaveTemporaryPatch(patchText)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@@ -289,11 +289,13 @@ func (self *StagingController) editHunk() error {
|
|||||||
}).
|
}).
|
||||||
FormatPlain()
|
FormatPlain()
|
||||||
|
|
||||||
applyFlags := []string{"cached"}
|
if err := self.c.Git().Patch.ApplyPatch(
|
||||||
if self.staged {
|
newPatchText,
|
||||||
applyFlags = append(applyFlags, "reverse")
|
git_commands.ApplyPatchOpts{
|
||||||
}
|
Reverse: self.staged,
|
||||||
if err := self.c.Git().WorkingTree.ApplyPatch(newPatchText, applyFlags...); err != nil {
|
Cached: true,
|
||||||
|
},
|
||||||
|
); err != nil {
|
||||||
return self.c.Error(err)
|
return self.c.Error(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -136,7 +136,6 @@ M file1
|
|||||||
}
|
}
|
||||||
patchBuilder := patch.NewPatchBuilder(
|
patchBuilder := patch.NewPatchBuilder(
|
||||||
utils.NewDummyLog(),
|
utils.NewDummyLog(),
|
||||||
func(patch string, flags ...string) error { return nil },
|
|
||||||
func(from string, to string, reverse bool, filename string, plain bool) (string, error) {
|
func(from string, to string, reverse bool, filename string, plain bool) (string, error) {
|
||||||
return "", nil
|
return "", nil
|
||||||
},
|
},
|
||||||
|
Reference in New Issue
Block a user