summaryrefslogtreecommitdiffstats
path: root/pkg/commands/git_commands/stash.go
blob: 572a87a73d7e4ad921874aae9d955bcb7bf7b57b (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
package git_commands

import (
	"fmt"
	"strings"

	"github.com/jesseduffield/lazygit/pkg/commands/oscommands"
)

type StashCommands struct {
	*GitCommon
	fileLoader  *FileLoader
	workingTree *WorkingTreeCommands
}

func NewStashCommands(
	gitCommon *GitCommon,
	fileLoader *FileLoader,
	workingTree *WorkingTreeCommands,
) *StashCommands {
	return &StashCommands{
		GitCommon:   gitCommon,
		fileLoader:  fileLoader,
		workingTree: workingTree,
	}
}

func (self *StashCommands) DropNewest() error {
	return self.cmd.New("git stash drop").Run()
}

func (self *StashCommands) Drop(index int) error {
	return self.cmd.New(fmt.Sprintf("git stash drop stash@{%d}", index)).Run()
}

func (self *StashCommands) Pop(index int) error {
	return self.cmd.New(fmt.Sprintf("git stash pop stash@{%d}", index)).Run()
}

func (self *StashCommands) Apply(index int) error {
	return self.cmd.New(fmt.Sprintf("git stash apply stash@{%d}", index)).Run()
}

// Save save stash
func (self *StashCommands) Save(message string) error {
	return self.cmd.New("git stash save " + self.cmd.Quote(message)).Run()
}

func (self *StashCommands) Store(sha string, message string) error {
	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()
	}
	return self.cmd.New(fmt.Sprintf("git stash store %s", self.cmd.Quote(sha))).Run()
}

func (self *StashCommands) Sha(index int) (string, error) {
	sha, _, err := self.cmd.New(fmt.Sprintf("git rev-parse refs/stash@{%d}", index)).DontLog().RunWithOutputs()
	return strings.Trim(sha, "\r\n"), err
}

func (self *StashCommands) ShowStashEntryCmdObj(index int) oscommands.ICmdObj {
	cmdStr := fmt.Sprintf("git stash show -p --stat --color=%s --unified=%d stash@{%d}", self.UserConfig.Git.Paging.ColorArg, self.UserConfig.Git.DiffContextSize, index)

	return self.cmd.New(cmdStr).DontLog()
}

func (self *StashCommands) StashAndKeepIndex(message string) error {
	return self.cmd.New(fmt.Sprintf("git stash save %s --keep-index", self.cmd.Quote(message))).Run()
}

func (self *StashCommands) StashUnstagedChanges(message string) error {
	if err := self.cmd.New("git commit --no-verify -m \"[lazygit] stashing unstaged changes\"").Run(); err != nil {
		return err
	}
	if err := self.Save(message); err != nil {
		return err
	}
	if err := self.cmd.New("git reset --soft HEAD^").Run(); err != nil {
		return err
	}
	return nil
}

// SaveStagedChanges stashes only the currently staged changes. This takes a few steps
// 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 {
	// wrap in 'writing', which uses a mutex
	if err := self.cmd.New("git stash --keep-index").Run(); err != nil {
		return err
	}

	if err := self.Save(message); err != nil {
		return err
	}

	if err := self.cmd.New("git stash apply stash@{1}").Run(); err != nil {
		return err
	}

	if err := self.os.PipeCommands("git stash show -p", "git apply -R"); err != nil {
		return err
	}

	if err := self.cmd.New("git stash drop stash@{1}").Run(); err != nil {
		return err
	}

	// if you had staged an untracked file, that will now appear as 'AD' in git status
	// meaning it's deleted in your working tree but added in your index. Given that it's
	// now safely stashed, we need to remove it.
	files := self.fileLoader.
		GetStatusFiles(GetStatusFileOptions{})

	for _, file := range files {
		if file.ShortStatus == "AD" {
			if err := self.workingTree.UnStageFile(file.Names(), false); err != nil {
				return err
			}
		}
	}

	return nil
}

func (self *StashCommands) StashIncludeUntrackedChanges(message string) error {
	return self.cmd.New(fmt.Sprintf("git stash save %s --include-untracked", self.cmd.Quote(message))).Run()
}

func (self *StashCommands) Rename(index int, message string) error {
	sha, err := self.Sha(index)
	if err != nil {
		return err
	}

	if err := self.Drop(index); err != nil {
		return err
	}

	err = self.Store(sha, message)
	if err != nil {
		return err
	}

	return nil
}