summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorJesse Duffield <jessedduffield@gmail.com>2019-11-04 19:47:25 +1100
committerJesse Duffield <jessedduffield@gmail.com>2019-11-05 19:22:01 +1100
commitd5e443e8e3609fe38586aed942a3dae3343dbe47 (patch)
tree6d7465b9abd8df3ae903e6d95898054ac3a6d8b4
parenta3c84296bf2fbc8b132d5b2285eedba09813fbee (diff)
Support building and moving patches
WIP
-rw-r--r--pkg/commands/branch_list_builder.go (renamed from pkg/git/branch_list_builder.go)39
-rw-r--r--pkg/commands/commit_file.go31
-rw-r--r--pkg/commands/commit_list_builder.go (renamed from pkg/git/commit_list_builder.go)43
-rw-r--r--pkg/commands/commit_list_builder_test.go (renamed from pkg/git/commit_list_builder_test.go)23
-rw-r--r--pkg/commands/git.go123
-rw-r--r--pkg/commands/git_test.go13
-rw-r--r--pkg/commands/patch_manager.go194
-rw-r--r--pkg/commands/patch_modifier.go (renamed from pkg/git/patch_modifier.go)50
-rw-r--r--pkg/commands/patch_modifier_test.go (renamed from pkg/git/patch_modifier_test.go)4
-rw-r--r--pkg/commands/patch_parser.go (renamed from pkg/git/patch_parser.go)55
-rw-r--r--pkg/commands/patch_rebases.go153
-rw-r--r--pkg/git/testdata/addedFile.diff7
-rw-r--r--pkg/git/testdata/testPatchAfter1.diff13
-rw-r--r--pkg/git/testdata/testPatchAfter2.diff14
-rw-r--r--pkg/git/testdata/testPatchAfter3.diff25
-rw-r--r--pkg/git/testdata/testPatchAfter4.diff19
-rw-r--r--pkg/git/testdata/testPatchBefore.diff15
-rw-r--r--pkg/git/testdata/testPatchBefore2.diff57
-rw-r--r--pkg/gui/branches_panel.go3
-rw-r--r--pkg/gui/commit_files_panel.go89
-rw-r--r--pkg/gui/commits_panel.go24
-rw-r--r--pkg/gui/gui.go20
-rw-r--r--pkg/gui/keybindings.go19
-rw-r--r--pkg/gui/patch_options_panel.go89
-rw-r--r--pkg/gui/staging_panel.go155
-rw-r--r--pkg/gui/view_helpers.go4
-rw-r--r--pkg/i18n/english.go18
-rw-r--r--pkg/utils/utils.go38
28 files changed, 990 insertions, 347 deletions
diff --git a/pkg/git/branch_list_builder.go b/pkg/commands/branch_list_builder.go
index 09c95ba97..d7a232055 100644
--- a/pkg/git/branch_list_builder.go
+++ b/pkg/commands/branch_list_builder.go
@@ -1,10 +1,9 @@
-package git
+package commands
import (
"regexp"
"strings"
- "github.com/jesseduffield/lazygit/pkg/commands"
"github.com/jesseduffield/lazygit/pkg/utils"
"github.com/sirupsen/logrus"
@@ -26,28 +25,28 @@ import (
// BranchListBuilder returns a list of Branch objects for the current repo
type BranchListBuilder struct {
Log *logrus.Entry
- GitCommand *commands.GitCommand
+ GitCommand *GitCommand
}
// NewBranchListBuilder builds a new branch list builder
-func NewBranchListBuilder(log *logrus.Entry, gitCommand *commands.GitCommand) (*BranchListBuilder, error) {
+func NewBranchListBuilder(log *logrus.Entry, gitCommand *GitCommand) (*BranchListBuilder, error) {
return &BranchListBuilder{
Log: log,
GitCommand: gitCommand,
}, nil
}
-func (b *BranchListBuilder) obtainCurrentBranch() *commands.Branch {
+func (b *BranchListBuilder) obtainCurrentBranch() *Branch {
branchName, err := b.GitCommand.CurrentBranchName()
if err != nil {
panic(err.Error())
}
- return &commands.Branch{Name: strings.TrimSpace(branchName)}
+ return &Branch{Name: strings.TrimSpace(branchName)}
}
-func (b *BranchListBuilder) obtainReflogBranches() []*commands.Branch {
- branches := make([]*commands.Branch, 0)
+func (b *BranchListBuilder) obtainReflogBranches() []*Branch {
+ branches := make([]*Branch, 0)
rawString, err := b.GitCommand.OSCommand.RunCommandWithOutput("git reflog -n100 --pretty='%cr|%gs' --grep-reflog='checkout: moving' HEAD")
if err != nil {
return branches
@@ -57,14 +56,14 @@ func (b *BranchListBuilder) obtainReflogBranches() []*commands.Branch {
for _, line := range branchLines {
timeNumber, timeUnit, branchName := branchInfoFromLine(line)
timeUnit = abbreviatedTimeUnit(timeUnit)
- branch := &commands.Branch{Name: branchName, Recency: timeNumber + timeUnit}
+ branch := &Branch{Name: branchName, Recency: timeNumber + timeUnit}
branches = append(branches, branch)
}
return uniqueByName(branches)
}
-func (b *BranchListBuilder) obtainSafeBranches() []*commands.Branch {
- branches := make([]*commands.Branch, 0)
+func (b *BranchListBuilder) obtainSafeBranches() []*Branch {
+ branches := make([]*Branch, 0)
bIter, err := b.GitCommand.Repo.Branches()
if err != nil {
@@ -72,14 +71,14 @@ func (b *BranchListBuilder) obtainSafeBranches() []*commands.Branch {
}
bIter.ForEach(func(b *plumbing.Reference) error {
name := b.Name().Short()
- branches = append(branches, &commands.Branch{Name: name})
+ branches = append(branches, &Branch{Name: name})
return nil
})
return branches
}
-func (b *BranchListBuilder) appendNewBranches(finalBranches, newBranches, existingBranches []*commands.Branch, included bool) []*commands.Branch {
+func (b *BranchListBuilder) appendNewBranches(finalBranches, newBranches, existingBranches []*Branch, included bool) []*Branch {
for _, newBranch := range newBranches {
if included == branchIncluded(newBranch.Name, existingBranches) {
finalBranches = append(finalBranches, newBranch)
@@ -88,7 +87,7 @@ func (b *BranchListBuilder) appendNewBranches(finalBranches, newBranches, existi
return finalBranches
}
-func sanitisedReflogName(reflogBranch *commands.Branch, safeBranches []*commands.Branch) string {
+func sanitisedReflogName(reflogBranch *Branch, safeBranches []*Branch) string {
for _, safeBranch := range safeBranches {
if strings.ToLower(safeBranch.Name) == strings.ToLower(reflogBranch.Name) {
return safeBranch.Name
@@ -98,8 +97,8 @@ func sanitisedReflogName(reflogBranch *commands.Branch, safeBranches []*commands
}
// Build the list of branches for the current repo
-func (b *BranchListBuilder) Build() []*commands.Branch {
- branches := make([]*commands.Branch, 0)
+func (b *BranchListBuilder) Build() []*Branch {
+ branches := make([]*Branch, 0)
head := b.obtainCurrentBranch()
safeBranches := b.obtainSafeBranches()
@@ -112,7 +111,7 @@ func (b *BranchListBuilder) Build() []*commands.Branch {
branches = b.appendNewBranches(branches, safeBranches, branches, false)
if len(branches) == 0 || branches[0].Name != head.Name {
- branches = append([]*commands.Branch{head}, branches...)
+ branches = append([]*Branch{head}, branches...)
}
branches[0].Recency = " *"
@@ -120,7 +119,7 @@ func (b *BranchListBuilder) Build() []*commands.Branch {
return branches
}
-func branchIncluded(branchName string, branches []*commands.Branch) bool {
+func branchIncluded(branchName string, branches []*Branch) bool {
for _, existingBranch := range branches {
if strings.ToLower(existingBranch.Name) == strings.ToLower(branchName) {
return true
@@ -129,8 +128,8 @@ func branchIncluded(branchName string, branches []*commands.Branch) bool {
return false
}
-func uniqueByName(branches []*commands.Branch) []*commands.Branch {
- finalBranches := make([]*commands.Branch, 0)
+func uniqueByName(branches []*Branch) []*Branch {
+ finalBranches := make([]*Branch, 0)
for _, branch := range branches {
if branchIncluded(branch.Name, finalBranches) {
continue
diff --git a/pkg/commands/commit_file.go b/pkg/commands/commit_file.go
index 8bc6a11c2..ddd09b23b 100644
--- a/pkg/commands/commit_file.go
+++ b/pkg/commands/commit_file.go
@@ -1,13 +1,42 @@
package commands
+import (
+ "github.com/fatih/color"
+ "github.com/jesseduffield/lazygit/pkg/theme"
+)
+
// CommitFile : A git commit file
type CommitFile struct {
Sha string
Name string
DisplayString string
+ Status int // one of 'WHOLE' 'PART' 'NONE'
}
+const (
+ // UNSELECTED is for when the commit file has not been added to the patch in any way
+ UNSELECTED = iota
+ // WHOLE is for when you want to add the whole diff of a file to the patch,
+ // including e.g. if it was deleted
+ WHOLE = iota
+ // PART is for when you're only talking about specific lines that have been modified
+ PART
+)
+
// GetDisplayStrings is a function.
func (f *CommitFile) GetDisplayStrings(isFocused bool) []string {
- return []string{f.DisplayString}
+ yellow := color.New(color.FgYellow)
+ green := color.New(color.FgGreen)
+ defaultColor := color.New(theme.DefaultTextColor)
+
+ var colour *color.Color
+ switch f.Status {
+ case UNSELECTED:
+ colour = defaultColor
+ case WHOLE:
+ colour = green
+ case PART:
+ colour = yellow
+ }
+ return []string{colour.Sprint(f.DisplayString)}
}
diff --git a/pkg/git/commit_list_builder.go b/pkg/commands/commit_list_builder.go
index a47fcbfe2..aab6de6a3 100644
--- a/pkg/git/commit_list_builder.go
+++ b/pkg/commands/commit_list_builder.go
@@ -1,4 +1,4 @@
-package git
+package commands
import (
"fmt"
@@ -9,7 +9,6 @@ import (
"strings"
"github.com/fatih/color"
- "github.com/jesseduffield/lazygit/pkg/commands"
"github.com/jesseduffield/lazygit/pkg/i18n"
"github.com/jesseduffield/lazygit/pkg/utils"
"github.com/sirupsen/logrus"
@@ -27,15 +26,15 @@ import (
// CommitListBuilder returns a list of Branch objects for the current repo
type CommitListBuilder struct {
Log *logrus.Entry
- GitCommand *commands.GitCommand
- OSCommand *commands.OSCommand
+ GitCommand *GitCommand
+ OSCommand *OSCommand
Tr *i18n.Localizer
- CherryPickedCommits []*commands.Commit
- DiffEntries []*commands.Commit
+ CherryPickedCommits []*Commit
+ DiffEntries []*Commit
}
// NewCommitListBuilder builds a new commit list builder
-func NewCommitListBuilder(log *logrus.Entry, gitCommand *commands.GitCommand, osCommand *commands.OSCommand, tr *i18n.Localizer, cherryPickedCommits []*commands.Commit, diffEntries []*commands.Commit) (*CommitListBuilder, error) {
+func NewCommitListBuilder(log *logrus.Entry, gitCommand *GitCommand, osCommand *OSCommand, tr *i18n.Localizer, cherryPickedCommits []*Commit, diffEntries []*Commit) (*CommitListBuilder, error) {
return &CommitListBuilder{
Log: log,
GitCommand: gitCommand,
@@ -47,9 +46,9 @@ func NewCommitListBuilder(log *logrus.Entry, gitCommand *commands.GitCommand, os
}
// GetCommits obtains the commits of the current branch
-func (c *CommitListBuilder) GetCommits() ([]*commands.Commit, error) {
- commits := []*commands.Commit{}
- var rebasingCommits []*commands.Commit
+func (c *CommitListBuilder) GetCommits() ([]*Commit, error) {
+ commits := []*Commit{}
+ var rebasingCommits []*Commit
rebaseMode, err := c.GitCommand.RebaseMode()
if err != nil {
return nil, err
@@ -74,7 +73,7 @@ func (c *CommitListBuilder) GetCommits() ([]*commands.Commit, error) {
sha := splitLine[0]
_, unpushed := unpushedCommits[sha]
status := map[bool]string{true: "unpushed", false: "pushed"}[unpushed]
- commits = append(commits, &commands.Commit{
+ commits = append(commits, &Commit{
Sha: sha,
Name: strings.Join(splitLine[1:], " "),
Status: status,
@@ -110,7 +109,7 @@ func (c *CommitListBuilder) GetCommits() ([]*commands.Commit, error) {
}
// getRebasingCommits obtains the commits that we're in the process of rebasing
-func (c *CommitListBuilder) getRebasingCommits(rebaseMode string) ([]*commands.Commit, error) {
+func (c *CommitListBuilder) getRebasingCommits(rebaseMode string) ([]*Commit, error) {
switch rebaseMode {
case "normal":
return c.getNormalRebasingCommits()
@@ -121,7 +120,7 @@ func (c *CommitListBuilder) getRebasingCommits(rebaseMode string) ([]*commands.C
}
}
-func (c *CommitListBuilder) getNormalRebasingCommits() ([]*commands.Commit, error) {
+func (c *CommitListBuilder) getNormalRebasingCommits() ([]*Commit, error) {
rewrittenCount := 0
bytesContent, err := ioutil.ReadFile(fmt.Sprintf("%s/rebase-apply/rewritten", c.GitCommand.DotGitDir))
if err == nil {
@@ -130,7 +129,7 @@ func (c *CommitListBuilder) getNormalRebasingCommits() ([]*commands.Commit, erro
}
// we know we're rebasing, so lets get all the files whose names have numbers
- commits := []*commands.Commit{}
+ commits := []*Commit{}
err = filepath.Walk(fmt.Sprintf("%s/rebase-apply", c.GitCommand.DotGitDir), func(path string, f os.FileInfo, err error) error {
if rewrittenCount > 0 {
rewrittenCount--
@@ -152,7 +151,7 @@ func (c *CommitListBuilder) getNormalRebasingCommits() ([]*commands.Commit, erro
if err != nil {
return err
}
- commits = append([]*commands.Commit{commit}, commits...)
+ commits = append([]*Commit{commit}, commits...)
return nil
})
if err != nil {
@@ -174,7 +173,7 @@ func (c *CommitListBuilder) getNormalRebasingCommits() ([]*commands.Commit, erro
// getInteractiveRebasingCommits takes our git-rebase-todo and our git-rebase-todo.backup files
// and extracts out the sha and names of commits that we still have to go
// in the rebase:
-func (c *CommitListBuilder) getInteractiveRebasingCommits() ([]*commands.Commit, error) {
+func (c *CommitListBuilder) getInteractiveRebasingCommits() ([]*Commit, error) {
bytesContent, err := ioutil.ReadFile(fmt.Sprintf("%s/rebase-merge/git-rebase-todo", c.GitCommand.DotGitDir))
if err != nil {
c.Log.Info(fmt.Sprintf("error occurred reading git-rebase-todo: %s", err.Error()))
@@ -182,14 +181,14 @@ func (c *CommitListBuilder) getInteractiveRebasingCommits() ([]*commands.Commit,
return nil, nil
}
- commits := []*commands.Commit{}
+ commits := []*Commit{}
lines := strings.Split(string(bytesContent), "\n")
for _, line := range lines {
if line == "" || line == "noop" {
return commits, nil
}
splitLine := strings.Split(line, " ")
- commits = append([]*commands.Commit{{
+ commits = append([]*Commit{{
Sha: splitLine[1][0:7],
Name: strings.Join(splitLine[2:], " "),
Status: "rebasing",
@@ -205,18 +204,18 @@ func (c *CommitListBuilder) getInteractiveRebasingCommits() ([]*commands.Commit,
// From: Lazygit Tester <test@example.com>
// Date: Wed, 5 Dec 2018 21:03:23 +1100
// Subject: second commit on master
-func (c *CommitListBuilder) commitFromPatch(content string) (*commands.Commit, error) {
+func (c *CommitListBuilder) commitFromPatch(content string) (*Commit, error) {
lines := strings.Split(content, "\n")
sha := strings.Split(lines[0], " ")[1][0:7]
name := strings.TrimPrefix(lines[3], "Subject: ")
- return &commands.Commit{
+ return &Commit{
Sha: sha,
Name: name,
Status: "rebasing",
}, nil
}
-func (c *CommitListBuilder) setCommitMergedStatuses(commits []*commands.Commit) ([]*commands.Commit, error) {
+func (c *CommitListBuilder) setCommitMergedStatuses(commits []*Commit) ([]*Commit, error) {
ancestor, err := c.getMergeBase()
if err != nil {
return nil, err
@@ -239,7 +238,7 @@ func (c *CommitListBuilder) setCommitMergedStatuses(commits []*commands.Commit)
return commits, nil
}
-func (c *CommitListBuilder) setCommitCherryPickStatuses(commits []*commands.Commit) ([]*commands.Commit, error) {
+func (c *CommitListBuilder) setCommitCherryPickStatuses(commits []*Commit) ([]*Commit, error) {
for _, commit := range commits {
for _, cherryPickedCommit := range c.CherryPickedCommits {
if commit.Sha == cherryPickedCommit.Sha {
diff --git a/pkg/git/commit_list_builder_test.go b/pkg/commands/commit_list_builder_test.go
index 81bb7b3cb..cdd360ce8 100644
--- a/pkg/git/commit_list_builder_test.go
+++ b/pkg/commands/commit_list_builder_test.go
@@ -1,24 +1,23 @@
-package git
+package commands
import (
"os/exec"
"testing"
- "github.com/jesseduffield/lazygit/pkg/commands"
"github.com/jesseduffield/lazygit/pkg/i18n"
"github.com/stretchr/testify/assert"
)
// NewDummyCommitListBuilder creates a new dummy CommitListBuilder for testing
func NewDummyCommitListBuilder() *CommitListBuilder {
- osCommand := commands.NewDummyOSCommand()
+ osCommand := NewDummyOSCommand()
return &CommitListBuilder{
- Log: commands.NewDummyLog(),
- GitCommand: commands.NewDummyGitCommandWithOSCommand(osCommand),
+ Log: NewDummyLog(),
+ GitCommand: NewDummyGitCommandWithOSCommand(osCommand),
OSCommand: osCommand,
- Tr: i18n.NewLocalizer(commands.NewDummyLog()),
- CherryPickedCommits: []*commands.Commit{},
+ Tr: i18n.NewLocalizer(NewDummyLog()),
+ CherryPickedCommits: []*Commit{},
}
}
@@ -199,7 +198,7 @@ func TestCommitListBuilderGetCommits(t *testing.T) {
type scenario struct {
testName string
command func(string, ...string) *exec.Cmd
- test func([]*commands.Commit, error)
+ test func([]*Commit, error)
}
scenarios := []scenario{
@@ -225,7 +224,7 @@ func TestCommitListBuilderGetCommits(t *testing.T) {
return nil
},
- func(commits []*commands.Commit, err error) {
+ func(commits []*Commit, err error) {
assert.NoError(t, err)
assert.Len(t, commits, 0)
},
@@ -252,10 +251,10 @@ func TestCommitListBuilderGetCommits(t *testing.T) {
return nil
},
- func(commits []*commands.Commit, err error) {
+ func(commits []*Commit, err error) {
assert.NoError(t, err)
assert.Len(t, commits, 2)
- assert.EqualValues(t, []*commands.Commit{
+ assert.EqualValues(t, []*Commit{
{
Sha: "8a2bb0e",
Name: "commit 1",
@@ -298,7 +297,7 @@ func TestCommitListBuilderGetCommits(t *testing.T) {
return nil
},
- func(commits []*commands.Commit, err error) {
+ func(commits []*Commit, err error) {
assert.Error(t, err)
assert.Len(t, commits, 0)
},
diff --git a/pkg/commands/git.go b/pkg/commands/git.go
index 6e86fe0b5..815c84423 100644
--- a/pkg/commands/git.go
+++ b/pkg/commands/git.go
@@ -63,16 +63,17 @@ func setupRepositoryAndWorktree(openGitRepository func(string) (*gogit.Repositor
// GitCommand is our main git interface
type GitCommand struct {
- Log *logrus.Entry
- OSCommand *OSCommand
- Worktree *gogit.Worktree
- Repo *gogit.Repository
- Tr *i18n.Localizer
- Config config.AppConfigurer
- getGlobalGitConfig func(string) (string, error)
- getLocalGitConfig func(string) (string, error)
- removeFile func(string) error
- DotGitDir string
+ Log *logrus.Entry
+ OSCommand *OSCommand
+ Worktree *gogit.Worktree
+ Repo *gogit.Repository
+ Tr *i18n.Localizer
+ Config config.AppConfigurer
+ getGlobalGitConfig func(string) (string, error)
+ getLocalGitConfig func(string) (string, error)
+ removeFile func(string) error
+ DotGitDir string
+ onSuccessfulContinue func() error
}
// NewGitCommand it runs git commands
@@ -376,7 +377,7 @@ func (c *GitCommand) Commit(message string, flags string) (*exec.Cmd, error) {
// AmendHead amends HEAD with whatever is staged in your working tree
func (c *GitCommand) AmendHead() (*exec.Cmd, error) {
- command := "git commit --amend --no-edit"
+ command := "git commit --amend --no-edit --allow-empty"
if c.usingGpg() {
return c.OSCommand.PrepareSubProcess(c.OSCommand.Platform.shell, c.OSCommand.Platform.shellArg, command), nil
}
@@ -530,7 +531,7 @@ func (c *GitCommand) Ignore(filename string) error {
// Show shows the diff of a commit
func (c *GitCommand) Show(sha string) (string, error) {
- show, err := c.OSCommand.RunCommandWithOutput(fmt.Sprintf("git show --color %s", sha))
+ show, err := c.OSCommand.RunCommandWithOutput(fmt.Sprintf("git show --color --no-renames %s", sha))
if err != nil {
return "", err
}
@@ -605,11 +606,11 @@ func (c *GitCommand) Diff(file *File, plain bool, cached bool) string {
return s
}
-func (c *GitCommand) ApplyPatch(patch string, reverse bool, cached bool) (string, error) {
+func (c *GitCommand) ApplyPatch(patch string, reverse bool, cached bool, extraFlags string) error {
filename, err := c.OSCommand.CreateTempFile("patch", patch)
if err != nil {
c.Log.Error(err)
- return "", err
+ return err
}
defer func() { _ = c.OSCommand.Remove(filename) }()
@@ -624,7 +625,7 @@ func (c *GitCommand) ApplyPatch(patch string, reverse bool, cached bool) (string
cachedFlag = "--cached"
}
- return c.OSCommand.RunCommandWithOutput(fmt.Sprintf("git apply %s %s %s", cachedFlag, reverseFlag, c.OSCommand.Quote(filename)))
+ return c.OSCommand.RunCommand(fmt.Sprintf("git apply %s %s %s %s", cachedFlag, reverseFlag, extraFlags, c.OSCommand.Quote(filename)))
}
func (c *GitCommand) FastForward(branchName string) error {
@@ -645,13 +646,29 @@ func (c *GitCommand) RunSkipEditorCommand(command string) error {
// GenericMerge takes a commandType of "merge" or "rebase" and a command of "abort", "skip" or "continue"
// By default we skip the editor in the case where a commit will be made
func (c *GitCommand) GenericMerge(commandType string, command string) error {
- return c.RunSkipEditorCommand(
+ err := c.RunSkipEditorCommand(
fmt.Sprintf(
"git %s --%s",
commandType,
command,
),
)
+ if err != nil {
+ return err
+ }
+
+ // sometimes we need to do a sequence of things in a rebase but the user needs to
+ // fix merge conflicts along the way. When this happens we queue up the next step
+ // so that after the next successful rebase continue we can continue from where we left off
+ if commandType == "rebase" && command == "continue" && c.onSuccessfulContinue != nil {
+ f := c.onSuccessfulContinue
+ c.onSuccessfulContinue = nil
+ return f()
+ }
+ if command == "abort" {
+ c.onSuccessfulContinue = nil
+ }
+ return nil
}
func (c *GitCommand) RewordCommit(commits []*Commit, index int) (*exec.Cmd, error) {
@@ -852,8 +869,8 @@ func (c *GitCommand) CherryPickCommits(commits []*Commit) error {
}
// GetCommitFiles get the specified commit files
-func (c *GitCommand) GetCommitFiles(commitSha string) ([]*CommitFile, error) {
- cmd := fmt.Sprintf("git show --pretty= --name-only %s", commitSha)
+func (c *GitCommand) GetCommitFiles(commitSha string, patchManager *PatchManager) ([]*CommitFile, error) {
+ cmd := fmt.Sprintf("git show --pretty= --name-only --no-renames %s", commitSha)
files, err := c.OSCommand.RunCommandWithOutput(cmd)
if err != nil {
return nil, err
@@ -862,10 +879,16 @@ func (c *GitCommand) GetCommitFiles(commitSha string) ([]*CommitFile, error) {
commitFiles := make([]*CommitFile, 0)
for _, file := range strings.Split(strings.TrimRight(files, "\n"), "\n") {
+ status := UNSELECTED
+ if patchManager != nil && patchManager.CommitSha == commitSha {
+ status = patchManager.GetFileStatus(file)
+ }
+
commitFiles = append(commitFiles, &CommitFile{
Sha: commitSha,
Name: file,
DisplayString: file,
+ Status: status,
})
}
@@ -873,8 +896,12 @@ func (c *GitCommand) GetCommitFiles(commitSha string) ([]*CommitFile, error) {
}
// ShowCommitFile get the diff of specified commit file
-func (c *GitCommand) ShowCommitFile(commitSha, fileName string) (string, error) {
- cmd := fmt.Sprintf("git show --color %s -- %s", commitSha, fileName)
+func (c *GitCommand) ShowCommitFile(commitSha, fileName string, plain bool) (string, error) {
+ colorArg := "--color"
+ if plain {
+ colorArg = ""
+ }
+ cmd := fmt.Sprintf("git show --no-renames %s %s -- %s", colorArg, commitSha, fileName)
return c.OSCommand.RunCommandWithOutput(cmd)
}
@@ -886,28 +913,7 @@ func (c *GitCommand) CheckoutFile(commitSha, fileName string) error {
// DiscardOldFileChanges discards changes to a file from an old commit
func (c *GitCommand) DiscardOldFileChanges(commits []*Commit, commitIndex int, fileName string) error {
- if len(commits)-1 < commitIndex {
- return errors.New("index outside of range of commits")
- }
-
- // we can make this GPG thing possible it just means we need to do this in two parts:
- // one where we handle the possibility of a credential request, and the other
- // where we continue the rebase
- if c.usingGpg() {
- return errors.New(c.Tr.SLocalize("DisabledForGPG"))
- }
-
- todo, sha, err := c.GenerateGenericRebaseTodo(commits, commitIndex, "edit")
- if err != nil {
- return err
- }
-
- cmd, err := c.PrepareInteractiveRebaseCommand(sha, todo, true)
- if err != nil {
- return err
- }
-
- if err := c.OSCommand.RunPreparedCommand(cmd); err != nil {
+ if err := c.BeginInteractiveRebaseForCommit(commits, commitIndex); err != nil {
return err
}
@@ -924,7 +930,7 @@ func (c *GitCommand) DiscardOldFileChanges(commits []*Commit, commitIndex int, f
}
// amend the commit
- cmd, err = c.AmendHead()
+ cmd, err := c.AmendHead()
if cmd != nil {
return errors.New("received unexpected pointer to cmd")
}
@@ -1016,3 +1022,34 @@ func (c *GitCommand) StashSaveStagedChanges(message string) error {
return nil
}
+
+// BeginInteractiveRebaseForCommit starts an interactive rebase to edit the current
+// commit and pick all others. After this you'll want to call `c.GenericMerge("rebase", "continue")`
+func (c *GitCommand) BeginInteractiveRebaseForCommit(commits []*Commit, commitIndex int) error {
+ if len(commits)-1 < commitIndex {
+ return errors.New("index outside of range of commits")
+ }
+
+ // we can make this GPG thing possible it just means we need to do this in two parts:
+ // one where we handle the possibility of a credential request, and the other
+ // where we continue the rebase
+ if c.usingGpg() {
+ return errors.New(c.Tr.SLocalize("DisabledForGPG"))
+ }
+
+ todo, sha, err := c.GenerateGenericRebaseTodo(commits, commitIndex, "edit")
+ if err != nil {
+ return err
+ }
+
+ cmd, err := c.PrepareInteractiveRebaseCommand(sha, todo, true)
+ if err != nil {
+ return err
+ }
+
+ if err := c.OSCommand.RunPreparedCommand(cmd); err != nil {
+ return err
+ }
+
+ return nil
+}
diff --git a/pkg/commands/git_test.go b/pkg/commands/git_test.go
index c132de98a..52db798ed 100644
--- a/pkg/commands/git_test.go
+++ b/pkg/commands/git_test.go
@@ -1685,7 +1685,7 @@ func TestGitCommandApplyPatch(t *testing.T) {
type scenario struct {
testName string
command func(string, ...string) *exec.Cmd
- test func(string, error)
+ test func(error)
}
scenarios := []scenario{
@@ -1702,9 +1702,8 @@ func TestGitCommandApplyPatch(t *testing.T) {
return exec.Command("echo", "done")
},
- func(output string, err error) {
+ func(err error) {
assert.NoError(t, err)
- assert.EqualValues(t, "done\n", output)
},
},
{
@@ -1724,7 +1723,7 @@ func TestGitCommandApplyPatch(t *testing.T) {
return exec.Command("test")
},
- func(output string, err error) {
+ func(err error) {
assert.Error(t, err)
},
},
@@ -1734,7 +1733,7 @@ func TestGitCommandApplyPatch(t *testing.T) {
t.Run(s.testName, func(t *testing.T) {
gitCmd := NewDummyGitCommand()
gitCmd.OSCommand.command = s.command
- s.test(gitCmd.ApplyPatch("test", false, true))
+ s.test(gitCmd.ApplyPatch("test", false, true, ""))
})
}
}
@@ -1962,7 +1961,7 @@ func TestGitCommandShowCommitFile(t *testing.T) {
for _, s := range scenarios {
t.Run(s.testName, func(t *testing.T) {
gitCmd.OSCommand.command = s.command
- s.test(gitCmd.ShowCommitFile(s.commitSha, s.fileName))
+ s.test(gitCmd.ShowCommitFile(s.commitSha, s.fileName, true))
})
}
}
@@ -2001,7 +2000,7 @@ func TestGitCommandGetCommitFiles(t *testing.T) {
for _, s := range scenarios {
t.Run(s.testName, func(t *testing.T) {
gitCmd.OSCommand.command = s.command
- s.test(gitCmd.GetCommitFiles(s.commitSha))
+ s.test(gitCmd.GetCommitFiles(s.commitSha, nil))
})
}
}
diff --git a/pkg/commands/patch_manager.go b/pkg/commands/patch_manager.go
new file mode 100644
index 000000000..7c0da245d
--- /dev/null
+++ b/pkg/commands/patch_manager.go
@@ -0,0 +1,194 @@
+package commands
+
+import (
+ "sort"
+ "strings"
+
+ "github.com/jesseduffield/lazygit/pkg/utils"
+ "github.com/sirupsen/logrus"
+)
+
+type fileInfo struct {
+ mode int // one of WHOLE/PART