diff options
author | Jesse Duffield <jessedduffield@gmail.com> | 2021-12-30 17:19:01 +1100 |
---|---|---|
committer | Jesse Duffield <jessedduffield@gmail.com> | 2022-01-04 09:07:15 +1100 |
commit | 9b2b0fc1226ebe0858fcbed59d87bcaae8c8a2e9 (patch) | |
tree | 8d7a9e4c321e40a9b0dab0f2260ec4131fb0eefe /pkg/commands/loaders | |
parent | 96c2887fd0c1ca95e6b3d55756be8d424f8d905a (diff) |
WIP
Diffstat (limited to 'pkg/commands/loaders')
-rw-r--r-- | pkg/commands/loaders/branches.go | 26 | ||||
-rw-r--r-- | pkg/commands/loaders/commit_files.go | 57 | ||||
-rw-r--r-- | pkg/commands/loaders/commits.go | 15 | ||||
-rw-r--r-- | pkg/commands/loaders/commits_test.go | 2 | ||||
-rw-r--r-- | pkg/commands/loaders/files.go | 134 | ||||
-rw-r--r-- | pkg/commands/loaders/files_test.go | 203 |
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{})) + }) + } +} |