summaryrefslogtreecommitdiffstats
path: root/pkg/commands/loaders
diff options
context:
space:
mode:
authorJesse Duffield <jessedduffield@gmail.com>2021-12-30 17:19:01 +1100
committerJesse Duffield <jessedduffield@gmail.com>2022-01-04 09:07:15 +1100
commit9b2b0fc1226ebe0858fcbed59d87bcaae8c8a2e9 (patch)
tree8d7a9e4c321e40a9b0dab0f2260ec4131fb0eefe /pkg/commands/loaders
parent96c2887fd0c1ca95e6b3d55756be8d424f8d905a (diff)
WIP
Diffstat (limited to 'pkg/commands/loaders')
-rw-r--r--pkg/commands/loaders/branches.go26
-rw-r--r--pkg/commands/loaders/commit_files.go57
-rw-r--r--pkg/commands/loaders/commits.go15
-rw-r--r--pkg/commands/loaders/commits_test.go2
-rw-r--r--pkg/commands/loaders/files.go134
-rw-r--r--pkg/commands/loaders/files_test.go203
6 files changed, 420 insertions, 17 deletions
diff --git a/pkg/commands/loaders/branches.go b/pkg/commands/loaders/branches.go
index fc3c15fc9..60e4f7aac 100644
--- a/pkg/commands/loaders/branches.go
+++ b/pkg/commands/loaders/branches.go
@@ -4,7 +4,6 @@ import (
"regexp"
"strings"
- "github.com/jesseduffield/lazygit/pkg/commands"
"github.com/jesseduffield/lazygit/pkg/commands/models"
"github.com/jesseduffield/lazygit/pkg/common"
"github.com/jesseduffield/lazygit/pkg/utils"
@@ -29,9 +28,14 @@ type BranchLoader struct {
reflogCommits []*models.Commit
}
+type BranchLoaderGitCommand interface {
+ GetRawBranches() (string, error)
+ CurrentBranchName() (string, string, error)
+}
+
func NewBranchLoader(
cmn *common.Common,
- gitCommand *commands.GitCommand,
+ gitCommand BranchLoaderGitCommand,
reflogCommits []*models.Commit,
) *BranchLoader {
return &BranchLoader{
@@ -43,10 +47,10 @@ func NewBranchLoader(
}
// Load the list of branches for the current repo
-func (b *BranchLoader) Load() []*models.Branch {
- branches := b.obtainBranches()
+func (self *BranchLoader) Load() []*models.Branch {
+ branches := self.obtainBranches()
- reflogBranches := b.obtainReflogBranches()
+ reflogBranches := self.obtainReflogBranches()
// loop through reflog branches. If there is a match, merge them, then remove it from the branches and keep it in the reflog branches
branchesWithRecency := make([]*models.Branch, 0)
@@ -78,7 +82,7 @@ outer:
}
}
if !foundHead {
- currentBranchName, currentBranchDisplayName, err := b.getCurrentBranchName()
+ currentBranchName, currentBranchDisplayName, err := self.getCurrentBranchName()
if err != nil {
panic(err)
}
@@ -87,8 +91,8 @@ outer:
return branches
}
-func (b *BranchLoader) obtainBranches() []*models.Branch {
- output, err := b.getRawBranches()
+func (self *BranchLoader) obtainBranches() []*models.Branch {
+ output, err := self.getRawBranches()
if err != nil {
panic(err)
}
@@ -150,11 +154,11 @@ func (b *BranchLoader) obtainBranches() []*models.Branch {
// TODO: only look at the new reflog commits, and otherwise store the recencies in
// int form against the branch to recalculate the time ago
-func (b *BranchLoader) obtainReflogBranches() []*models.Branch {
+func (self *BranchLoader) obtainReflogBranches() []*models.Branch {
foundBranchesMap := map[string]bool{}
re := regexp.MustCompile(`checkout: moving from ([\S]+) to ([\S]+)`)
- reflogBranches := make([]*models.Branch, 0, len(b.reflogCommits))
- for _, commit := range b.reflogCommits {
+ reflogBranches := make([]*models.Branch, 0, len(self.reflogCommits))
+ for _, commit := range self.reflogCommits {
if match := re.FindStringSubmatch(commit.Name); len(match) == 3 {
recency := utils.UnixToTimeAgo(commit.UnixTimestamp)
for _, branchName := range match[1:] {
diff --git a/pkg/commands/loaders/commit_files.go b/pkg/commands/loaders/commit_files.go
new file mode 100644
index 000000000..1b65737ec
--- /dev/null
+++ b/pkg/commands/loaders/commit_files.go
@@ -0,0 +1,57 @@
+package loaders
+
+import (
+ "fmt"
+ "strings"
+
+ "github.com/jesseduffield/lazygit/pkg/commands/models"
+ "github.com/jesseduffield/lazygit/pkg/commands/oscommands"
+ "github.com/jesseduffield/lazygit/pkg/common"
+)
+
+type CommitFileLoader struct {
+ *common.Common
+ cmd oscommands.ICmdObjBuilder
+}
+
+func NewCommitFileLoader(common *common.Common, cmd oscommands.ICmdObjBuilder) *CommitFileLoader {
+ return &CommitFileLoader{
+ Common: common,
+ cmd: cmd,
+ }
+}
+
+// GetFilesInDiff get the specified commit files
+func (self *CommitFileLoader) GetFilesInDiff(from string, to string, reverse bool) ([]*models.CommitFile, error) {
+ reverseFlag := ""
+ if reverse {
+ reverseFlag = " -R "
+ }
+
+ filenames, err := self.cmd.New(fmt.Sprintf("git diff --submodule --no-ext-diff --name-status -z --no-renames %s %s %s", reverseFlag, from, to)).RunWithOutput()
+ if err != nil {
+ return nil, err
+ }
+
+ return self.getCommitFilesFromFilenames(filenames), nil
+}
+
+// filenames string is something like "file1\nfile2\nfile3"
+func (self *CommitFileLoader) getCommitFilesFromFilenames(filenames string) []*models.CommitFile {
+ commitFiles := make([]*models.CommitFile, 0)
+
+ lines := strings.Split(strings.TrimRight(filenames, "\x00"), "\x00")
+ n := len(lines)
+ for i := 0; i < n-1; i += 2 {
+ // typical result looks like 'A my_file' meaning my_file was added
+ changeStatus := lines[i]
+ name := lines[i+1]
+
+ commitFiles = append(commitFiles, &models.CommitFile{
+ Name: name,
+ ChangeStatus: changeStatus,
+ })
+ }
+
+ return commitFiles
+}
diff --git a/pkg/commands/loaders/commits.go b/pkg/commands/loaders/commits.go
index bbd520c81..6f2336e3a 100644
--- a/pkg/commands/loaders/commits.go
+++ b/pkg/commands/loaders/commits.go
@@ -9,7 +9,6 @@ import (
"strconv"
"strings"
- "github.com/jesseduffield/lazygit/pkg/commands"
"github.com/jesseduffield/lazygit/pkg/commands/models"
"github.com/jesseduffield/lazygit/pkg/commands/oscommands"
"github.com/jesseduffield/lazygit/pkg/commands/types/enums"
@@ -37,20 +36,26 @@ type CommitLoader struct {
dotGitDir string
}
+type CommitLoaderGitCommand interface {
+ CurrentBranchName() (string, string, error)
+ RebaseMode() (enums.RebaseMode, error)
+ GetCmd() oscommands.ICmdObjBuilder
+ GetDotGitDir() string
+}
+
// making our dependencies explicit for the sake of easier testing
func NewCommitLoader(
cmn *common.Common,
- gitCommand *commands.GitCommand,
- osCommand *oscommands.OSCommand,
+ gitCommand CommitLoaderGitCommand,
) *CommitLoader {
return &CommitLoader{
Common: cmn,
- cmd: gitCommand.Cmd,
+ cmd: gitCommand.GetCmd(),
getCurrentBranchName: gitCommand.CurrentBranchName,
getRebaseMode: gitCommand.RebaseMode,
readFile: ioutil.ReadFile,
walkFiles: filepath.Walk,
- dotGitDir: gitCommand.DotGitDir,
+ dotGitDir: gitCommand.GetDotGitDir(),
}
}
diff --git a/pkg/commands/loaders/commits_test.go b/pkg/commands/loaders/commits_test.go
index f793567f7..5491fb4a5 100644
--- a/pkg/commands/loaders/commits_test.go
+++ b/pkg/commands/loaders/commits_test.go
@@ -189,7 +189,7 @@ func TestGetCommits(t *testing.T) {
t.Run(scenario.testName, func(t *testing.T) {
builder := &CommitLoader{
Common: utils.NewDummyCommon(),
- cmd: oscommands.NewCmdObjBuilderDummy(scenario.runner),
+ cmd: oscommands.NewDummyCmdObjBuilder(scenario.runner),
getCurrentBranchName: func() (string, string, error) {
return scenario.currentBranchName, scenario.currentBranchName, nil
},
diff --git a/pkg/commands/loaders/files.go b/pkg/commands/loaders/files.go
new file mode 100644
index 000000000..a1964a2b9
--- /dev/null
+++ b/pkg/commands/loaders/files.go
@@ -0,0 +1,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
+}
diff --git a/pkg/commands/loaders/files_test.go b/pkg/commands/loaders/files_test.go
new file mode 100644
index 000000000..1613290bb
--- /dev/null
+++ b/pkg/commands/loaders/files_test.go
@@ -0,0 +1,203 @@
+package loaders
+
+import (
+ "testing"
+
+ "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/utils"
+ "github.com/stretchr/testify/assert"
+)
+
+func TestGitCommandGetStatusFiles(t *testing.T) {
+ type scenario struct {
+ testName string
+ runner oscommands.ICmdObjRunner
+ expectedFiles []*models.File
+ }
+
+ scenarios := []scenario{
+ {
+ "No files found",
+ oscommands.NewFakeRunner(t).
+ Expect(`git status --untracked-files=yes --porcelain -z`, "", nil),
+ []*models.File{},
+ },
+ {
+ "Several files found",
+ oscommands.NewFakeRunner(t).
+ Expect(
+ `git status --untracked-files=yes --porcelain -z`,
+ "MM file1.txt\x00A file3.txt\x00AM file2.txt\x00?? file4.txt\x00UU file5.txt",
+ nil,
+ ),
+ []*models.File{
+ {
+ Name: "file1.txt",
+ HasStagedChanges: true,
+ HasUnstagedChanges: true,
+ Tracked: true,
+ Added: false,
+ Deleted: false,
+ HasMergeConflicts: false,
+ HasInlineMergeConflicts: false,
+ DisplayString: "MM file1.txt",
+ Type: "file",
+ ShortStatus: "MM",
+ },
+ {
+ Name: "file3.txt",
+ HasStagedChanges: true,
+ HasUnstagedChanges: false,
+ Tracked: false,
+ Added: true,
+ Deleted: false,
+ HasMergeConflicts: false,
+ HasInlineMergeConflicts: false,
+ DisplayString: "A file3.txt",
+ Type: "file",
+ ShortStatus: "A ",
+ },
+ {
+ Name: "file2.txt",
+ HasStagedChanges: true,
+ HasUnstagedChanges: true,
+ Tracked: false,
+ Added: true,
+ Deleted: false,
+ HasMergeConflicts: false,
+ HasInlineMergeConflicts: false,
+ DisplayString: "AM file2.txt",
+ Type: "file",
+ ShortStatus: "AM",
+ },
+ {
+ Name: "file4.txt",
+ HasStagedChanges: false,
+ HasUnstagedChanges: true,
+ Tracked: false,
+ Added: true,
+ Deleted: false,
+ HasMergeConflicts: false,
+ HasInlineMergeConflicts: false,
+ DisplayString: "?? file4.txt",
+ Type: "file",
+ ShortStatus: "??",
+ },
+ {
+ Name: "file5.txt",
+ HasStagedChanges: false,
+ HasUnstagedChanges: true,
+ Tracked: true,
+ Added: false,
+ Deleted: false,
+ HasMergeConflicts: true,
+ HasInlineMergeConflicts: true,
+ DisplayString: "UU file5.txt",
+ Type: "file",
+ ShortStatus: "UU",
+ },
+ },
+ },
+ {
+ "File with new line char",
+ oscommands.NewFakeRunner(t).
+ Expect(`git status --untracked-files=yes --porcelain -z`, "MM a\nb.txt", nil),
+ []*models.File{
+ {
+ Name: "a\nb.txt",
+ HasStagedChanges: true,
+ HasUnstagedChanges: true,
+ Tracked: true,
+ Added: false,
+ Deleted: false,
+ HasMergeConflicts: false,
+ HasInlineMergeConflicts: false,
+ DisplayString: "MM a\nb.txt",
+ Type: "file",
+ ShortStatus: "MM",
+ },
+ },
+ },
+ {
+ "Renamed files",
+ oscommands.NewFakeRunner(t).
+ Expect(
+ `git status --untracked-files=yes --porcelain -z`,
+ "R after1.txt\x00before1.txt\x00RM after2.txt\x00before2.txt",
+ nil,
+ ),
+ []*models.File{
+ {
+ Name: "after1.txt",
+ PreviousName: "before1.txt",
+ HasStagedChanges: true,
+ HasUnstagedChanges: false,
+ Tracked: true,
+ Added: false,
+ Deleted: false,
+ HasMergeConflicts: false,
+ HasInlineMergeConflicts: false,
+ DisplayString: "R before1.txt -> after1.txt",
+ Type: "file",
+ ShortStatus: "R ",
+ },
+ {
+ Name: "after2.txt",
+ PreviousName: "before2.txt",
+ HasStagedChanges: true,
+ HasUnstagedChanges: true,
+ Tracked: true,
+ Added: false,
+ Deleted: false,
+ HasMergeConflicts: false,
+ HasInlineMergeConflicts: false,
+ DisplayString: "RM before2.txt -> after2.txt",
+ Type: "file",
+ ShortStatus: "RM",
+ },
+ },
+ },
+ {
+ "File with arrow in name",
+ oscommands.NewFakeRunner(t).
+ Expect(
+ `git status --untracked-files=yes --porcelain -z`,
+ `?? a -> b.txt`,
+ nil,
+ ),
+ []*models.File{
+ {
+ Name: "a -> b.txt",
+ HasStagedChanges: false,
+ HasUnstagedChanges: true,
+ Tracked: false,
+ Added: true,
+ Deleted: false,
+ HasMergeConflicts: false,
+ HasInlineMergeConflicts: false,
+ DisplayString: "?? a -> b.txt",
+ Type: "file",
+ ShortStatus: "??",
+ },
+ },
+ },
+ }
+
+ for _, s := range scenarios {
+ t.Run(s.testName, func(t *testing.T) {
+ cmd := oscommands.NewDummyCmdObjBuilder(s.runner)
+ gitConfig := git_config.NewFakeGitConfig(map[string]string{"status.showUntrackedFiles": "yes"})
+
+ loader := &FileLoader{
+ Common: utils.NewDummyCommon(),
+ cmd: cmd,
+ gitConfig: gitConfig,
+ getFileType: func(string) string { return "file" },
+ }
+
+ assert.EqualValues(t, s.expectedFiles, loader.GetStatusFiles(GetStatusFileOptions{}))
+ })
+ }
+}