summaryrefslogtreecommitdiffstats
path: root/pkg/commands/loaders/files.go
blob: a1964a2b9b5c66f7036df8ce529e4dac7859bb04 (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
package loaders

import (
	"fmt"
	"strings"

	"github.com/jesseduffield/lazygit/pkg/commands/git_config"
	"github.com/jesseduffield/lazygit/pkg/commands/models"
	"github.com/jesseduffield/lazygit/pkg/commands/oscommands"
	"github.com/jesseduffield/lazygit/pkg/common"
	"github.com/jesseduffield/lazygit/pkg/utils"
)

type FileLoader struct {
	*common.Common
	cmd         oscommands.ICmdObjBuilder
	gitConfig   git_config.IGitConfig
	getFileType func(string) string
}

func NewFileLoader(cmn *common.Common, cmd oscommands.ICmdObjBuilder, gitConfig git_config.IGitConfig) *FileLoader {
	return &FileLoader{
		Common:      cmn,
		cmd:         cmd,
		gitConfig:   gitConfig,
		getFileType: oscommands.FileType,
	}
}

type GetStatusFileOptions struct {
	NoRenames bool
}

func (self *FileLoader) GetStatusFiles(opts GetStatusFileOptions) []*models.File {
	// check if config wants us ignoring untracked files
	untrackedFilesSetting := self.gitConfig.Get("status.showUntrackedFiles")

	if untrackedFilesSetting == "" {
		untrackedFilesSetting = "all"
	}
	untrackedFilesArg := fmt.Sprintf("--untracked-files=%s", untrackedFilesSetting)

	statuses, err := self.GitStatus(GitStatusOptions{NoRenames: opts.NoRenames, UntrackedFilesArg: untrackedFilesArg})
	if err != nil {
		self.Log.Error(err)
	}
	files := []*models.File{}

	for _, status := range statuses {
		if strings.HasPrefix(status.StatusString, "warning") {
			self.Log.Warningf("warning when calling git status: %s", status.StatusString)
			continue
		}
		change := status.Change
		stagedChange := change[0:1]
		unstagedChange := change[1:2]
		untracked := utils.IncludesString([]string{"??", "A ", "AM"}, change)
		hasNoStagedChanges := utils.IncludesString([]string{" ", "U", "?"}, stagedChange)
		hasMergeConflicts := utils.IncludesString([]string{"DD", "AA", "UU", "AU", "UA", "UD", "DU"}, change)
		hasInlineMergeConflicts := utils.IncludesString([]string{"UU", "AA"}, change)

		file := &models.File{
			Name:                    status.Name,
			PreviousName:            status.PreviousName,
			DisplayString:           status.StatusString,
			HasStagedChanges:        !hasNoStagedChanges,
			HasUnstagedChanges:      unstagedChange != " ",
			Tracked:                 !untracked,
			Deleted:                 unstagedChange == "D" || stagedChange == "D",
			Added:                   unstagedChange == "A" || untracked,
			HasMergeConflicts:       hasMergeConflicts,
			HasInlineMergeConflicts: hasInlineMergeConflicts,
			Type:                    self.getFileType(status.Name),
			ShortStatus:             change,
		}
		files = append(files, file)
	}

	return files
}

// GitStatus returns the file status of the repo
type GitStatusOptions struct {
	NoRenames         bool
	UntrackedFilesArg string
}

type FileStatus struct {
	StatusString string
	Change       string // ??, MM, AM, ...
	Name         string
	PreviousName string
}

func (c *FileLoader) GitStatus(opts GitStatusOptions) ([]FileStatus, error) {
	noRenamesFlag := ""
	if opts.NoRenames {
		noRenamesFlag = " --no-renames"
	}

	statusLines, err := c.cmd.New(fmt.Sprintf("git status %s --porcelain -z%s", opts.UntrackedFilesArg, noRenamesFlag)).RunWithOutput()
	if err != nil {
		return []FileStatus{}, err
	}

	splitLines := strings.Split(statusLines, "\x00")
	response := []FileStatus{}

	for i := 0; i < len(splitLines); i++ {
		original := splitLines[i]

		if len(original) < 3 {
			continue
		}

		status := FileStatus{
			StatusString: original,
			Change:       original[:2],
			Name:         original[3:],
			PreviousName: "",
		}

		if strings.HasPrefix(status.Change, "R") {
			// if a line starts with 'R' then the next line is the original file.
			status.PreviousName = strings.TrimSpace(splitLines[i+1])
			status.StatusString = fmt.Sprintf("%s %s -> %s", status.Change, status.PreviousName, status.Name)
			i++
		}

		response = append(response, status)
	}

	return response, nil
}