summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--pkg/commands/commit.go21
-rw-r--r--pkg/commands/git.go122
-rw-r--r--pkg/commands/os.go11
-rw-r--r--pkg/gui/branches_panel.go9
-rw-r--r--pkg/gui/files_panel.go37
-rw-r--r--pkg/gui/gui.go1
-rw-r--r--pkg/gui/keybindings.go2
-rw-r--r--pkg/gui/merge_panel.go17
-rw-r--r--pkg/gui/status_panel.go6
-rwxr-xr-xtest/repos/merge_conflict.sh46
10 files changed, 221 insertions, 51 deletions
diff --git a/pkg/commands/commit.go b/pkg/commands/commit.go
index 4c304271d..6ee972f9f 100644
--- a/pkg/commands/commit.go
+++ b/pkg/commands/commit.go
@@ -8,23 +8,30 @@ import (
type Commit struct {
Sha string
Name string
- Pushed bool
- Merged bool
+ Status string // one of "unpushed", "pushed", "merged", or "rebasing"
DisplayString string
}
// GetDisplayStrings is a function.
func (c *Commit) GetDisplayStrings() []string {
red := color.New(color.FgRed)
- yellow := color.New(color.FgGreen)
- green := color.New(color.FgYellow)
+ yellow := color.New(color.FgYellow)
+ green := color.New(color.FgGreen)
white := color.New(color.FgWhite)
+ blue := color.New(color.FgBlue)
- shaColor := yellow
- if c.Pushed {
+ var shaColor *color.Color
+ switch c.Status {
+ case "unpushed":
shaColor = red
- } else if !c.Merged {
+ case "pushed":
+ shaColor = yellow
+ case "merged":
shaColor = green
+ case "rebasing":
+ shaColor = blue
+ default:
+ shaColor = white
}
return []string{shaColor.Sprint(c.Sha), white.Sprint(c.Name)}
diff --git a/pkg/commands/git.go b/pkg/commands/git.go
index bbd388303..4fed44d2a 100644
--- a/pkg/commands/git.go
+++ b/pkg/commands/git.go
@@ -3,10 +3,14 @@ package commands
import (
"errors"
"fmt"
+ "io/ioutil"
"os"
"os/exec"
+ "path/filepath"
+ "regexp"
"strings"
+ "github.com/fatih/color"
"github.com/jesseduffield/lazygit/pkg/i18n"
"github.com/jesseduffield/lazygit/pkg/utils"
"github.com/sirupsen/logrus"
@@ -149,7 +153,7 @@ func (c *GitCommand) GetStatusFiles() []*File {
HasUnstagedChanges: unstagedChange != " ",
Tracked: !untracked,
Deleted: unstagedChange == "D" || stagedChange == "D",
- HasMergeConflicts: change == "UU",
+ HasMergeConflicts: change == "UU" || change == "AA",
Type: c.OSCommand.FileType(filename),
}
files = append(files, file)
@@ -240,9 +244,9 @@ func (c *GitCommand) GetCommitDifferences(from, to string) (string, string) {
return strings.TrimSpace(pushableCount), strings.TrimSpace(pullableCount)
}
-// GetCommitsToPush Returns the sha's of the commits that have not yet been pushed
+// GetUnpushedCommits Returns the sha's of the commits that have not yet been pushed
// to the remote branch of the current branch, a map is returned to ease look up
-func (c *GitCommand) GetCommitsToPush() map[string]bool {
+func (c *GitCommand) GetUnpushedCommits() map[string]bool {
pushables := map[string]bool{}
o, err := c.OSCommand.RunCommandWithOutput("git rev-list @{u}..HEAD --abbrev-commit")
if err != nil {
@@ -273,6 +277,10 @@ func (c *GitCommand) ContinueRebaseBranch() error {
return c.OSCommand.RunCommand("git rebase --continue")
}
+func (c *GitCommand) SkipRebaseBranch() error {
+ return c.OSCommand.RunCommand("git rebase --skip")
+}
+
func (c *GitCommand) AbortRebaseBranch() error {
return c.OSCommand.RunCommand("git rebase --abort")
}
@@ -455,11 +463,11 @@ func (c *GitCommand) IsInMergeState() (bool, error) {
}
func (c *GitCommand) IsInRebaseState() (bool, error) {
- output, err := c.OSCommand.RunCommandWithOutput("git status --untracked-files=all")
+ exists, err := c.OSCommand.FileExists(".git/rebase-apply")
if err != nil {
return false, err
}
- return strings.Contains(output, "rebase in progress"), nil
+ return exists, nil
}
// RemoveFile directly
@@ -527,24 +535,105 @@ func (c *GitCommand) getMergeBase() (string, error) {
return output, nil
}
+// GetRebasingCommits obtains the commits that we're in the process of rebasing
+func (c *GitCommand) GetRebasingCommits() ([]*Commit, error) {
+ rebasing, err := c.IsInRebaseState()
+ if err != nil {
+ return nil, err
+ }
+ if !rebasing {
+ return nil, nil
+ }
+
+ rewrittenCount := 0
+ bytesContent, err := ioutil.ReadFile(".git/rebase-apply/rewritten")
+ if err == nil {
+ content := string(bytesContent)
+ rewrittenCount = len(strings.Split(content, "\n"))
+ }
+
+ // we know we're rebasing, so lets get all the files whose names have numbers
+ commits := []*Commit{}
+ err = filepath.Walk(".git/rebase-apply", func(path string, f os.FileInfo, err error) error {
+ if rewrittenCount > 0 {
+ rewrittenCount -= 1
+ return nil
+ }
+ if err != nil {
+ return err
+ }
+ re := regexp.MustCompile(`^\d+$`)
+ if !re.MatchString(f.Name()) {
+ return nil
+ }
+ bytesContent, err := ioutil.ReadFile(path)
+ if err != nil {
+ return err
+ }
+ content := string(bytesContent)
+ commit, err := c.CommitFromPatch(content)
+ if err != nil {
+ return err
+ }
+ commits = append([]*Commit{commit}, commits...)
+ return nil
+ })
+ if err != nil {
+ return nil, err
+ }
+
+ return commits, nil
+}
+
+// assuming the file starts like this:
+// From e93d4193e6dd45ca9cf3a5a273d7ba6cd8b8fb20 Mon Sep 17 00:00:00 2001
+// From: Lazygit Tester <test@example.com>
+// Date: Wed, 5 Dec 2018 21:03:23 +1100
+// Subject: second commit on master
+func (c *GitCommand) 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 &Commit{
+ Sha: sha,
+ Name: name,
+ Status: "rebasing",
+ }, nil
+}
+
// GetCommits obtains the commits of the current branch
func (c *GitCommand) GetCommits() ([]*Commit, error) {
- pushables := c.GetCommitsToPush()
+ commits := []*Commit{}
+ // here we want to also prepend the commits that we're in the process of rebasing
+ rebasingCommits, err := c.GetRebasingCommits()
+ if err != nil {
+ return nil, err
+ }
+ if len(rebasingCommits) > 0 {
+ commits = append(commits, rebasingCommits...)
+ }
+
+ unpushedCommits := c.GetUnpushedCommits()
log := c.GetLog()
- lines := utils.SplitLines(log)
- commits := make([]*Commit, len(lines))
// now we can split it up and turn it into commits
- for i, line := range lines {
+ for _, line := range utils.SplitLines(log) {
splitLine := strings.Split(line, " ")
sha := splitLine[0]
- _, pushed := pushables[sha]
- commits[i] = &Commit{
+ _, unpushed := unpushedCommits[sha]
+ status := map[bool]string{true: "unpushed", false: "pushed"}[unpushed]
+ commits = append(commits, &Commit{
Sha: sha,
Name: strings.Join(splitLine[1:], " "),
- Pushed: pushed,
+ Status: status,
DisplayString: strings.Join(splitLine, " "),
- }
+ })
+ }
+ if len(rebasingCommits) > 0 {
+ currentCommit := commits[len(rebasingCommits)]
+ blue := color.New(color.FgYellow)
+ youAreHere := blue.Sprint("<-- YOU ARE HERE ---")
+ currentCommit.Name = fmt.Sprintf("%s %s", youAreHere, currentCommit.Name)
}
return c.setCommitMergedStatuses(commits)
}
@@ -562,7 +651,12 @@ func (c *GitCommand) setCommitMergedStatuses(commits []*Commit) ([]*Commit, erro
if strings.HasPrefix(ancestor, commit.Sha) {
passedAncestor = true
}
- commits[i].Merged = passedAncestor
+ if commit.Status != "pushed" {
+ continue
+ }
+ if passedAncestor {
+ commits[i].Status = "merged"
+ }
}
return commits, nil
}
diff --git a/pkg/commands/os.go b/pkg/commands/os.go
index 6b28a69bb..829034eb8 100644
--- a/pkg/commands/os.go
+++ b/pkg/commands/os.go
@@ -202,3 +202,14 @@ func (c *OSCommand) CreateTempFile(filename, content string) (string, error) {
func (c *OSCommand) RemoveFile(filename string) error {
return os.Remove(filename)
}
+
+// FileExists checks whether a file exists at the specified path
+func (c *OSCommand) FileExists(path string) (bool, error) {
+ if _, err := os.Stat(path); err != nil {
+ if os.IsNotExist(err) {
+ return false, nil
+ }
+ return false, err
+ }
+ return true, nil
+}
diff --git a/pkg/gui/branches_panel.go b/pkg/gui/branches_panel.go
index f4a5e28f5..165a75a4a 100644
--- a/pkg/gui/branches_panel.go
+++ b/pkg/gui/branches_panel.go
@@ -96,7 +96,7 @@ func (gui *Gui) handleBranchesPrevLine(g *gocui.Gui, v *gocui.View) error {
func (gui *Gui) handleRebase(g *gocui.Gui, v *gocui.View) error {
- selectedBranch := gui.getSelectedBranch(v).Name
+ selectedBranch := gui.getSelectedBranch().Name
checkedOutBranch := gui.State.Branches[0].Name
title := "Rebasing"
prompt := fmt.Sprintf("Are you sure you want to rebase %s onto %s?", checkedOutBranch, selectedBranch)
@@ -109,11 +109,14 @@ func (gui *Gui) handleRebase(g *gocui.Gui, v *gocui.View) error {
if err := gui.GitCommand.RebaseBranch(selectedBranch); err != nil {
gui.Log.Errorln(err)
- if err := gui.createConfirmationPanel(g, v, "Rebase failed", "Rebasing failed, would you like to resolve it?",
+ if err := gui.createConfirmationPanel(g, v, "Rebase failed", "Damn, conflicts! To abort press 'esc', otherwise press 'enter'",
func(g *gocui.Gui, v *gocui.View) error {
return nil
}, func(g *gocui.Gui, v *gocui.View) error {
- return gui.GitCommand.AbortRebaseBranch()
+ if err := gui.GitCommand.AbortRebaseBranch(); err != nil {
+ return err
+ }
+ return gui.refreshSidePanels(g)
}); err != nil {
gui.Log.Errorln(err)
}
diff --git a/pkg/gui/files_panel.go b/pkg/gui/files_panel.go
index 95b4f80a4..2059ac97f 100644
--- a/pkg/gui/files_panel.go
+++ b/pkg/gui/files_panel.go
@@ -118,11 +118,7 @@ func (gui *Gui) stageSelectedFile(g *gocui.Gui) error {
return gui.GitCommand.StageFile(file.Name)
}
-func (gui *Gui) handleSwitchToStagingPanel(g *gocui.Gui, v *gocui.View) error {
- stagingView, err := g.View("staging")
- if err != nil {
- return err
- }
+func (gui *Gui) handleEnterFile(g *gocui.Gui, v *gocui.View) error {
file, err := gui.getSelectedFile(g)
if err != nil {
if err != gui.Errors.ErrNoFiles {
@@ -130,10 +126,17 @@ func (gui *Gui) handleSwitchToStagingPanel(g *gocui.Gui, v *gocui.View) error {
}
return nil
}
+ if file.HasMergeConflicts {
+ return gui.handleSwitchToMerge(g, v)
+ }
if !file.HasUnstagedChanges {
gui.Log.WithField("staging", "staging").Info("making error panel")
return gui.createErrorPanel(g, gui.Tr.SLocalize("FileStagingRequirements"))
}
+ stagingView, err := g.View("staging")
+ if err != nil {
+ return err
+ }
if err := gui.switchFocus(g, v, stagingView); err != nil {
return err
}
@@ -256,7 +259,7 @@ func (gui *Gui) handleIgnoreFile(g *gocui.Gui, v *gocui.View) error {
}
func (gui *Gui) handleCommitPress(g *gocui.Gui, filesView *gocui.View) error {
- if len(gui.stagedFiles()) == 0 && !gui.State.HasMergeConflicts {
+ if len(gui.stagedFiles()) == 0 && gui.State.WorkingTreeState == "normal" {
return gui.createErrorPanel(g, gui.Tr.SLocalize("NoStagedFilesToCommit"))
}
commitMessageView := gui.getCommitMessageView(g)
@@ -270,7 +273,7 @@ func (gui *Gui) handleCommitPress(g *gocui.Gui, filesView *gocui.View) error {
}
func (gui *Gui) handleAmendCommitPress(g *gocui.Gui, filesView *gocui.View) error {
- if len(gui.stagedFiles()) == 0 && !gui.State.HasMergeConflicts {
+ if len(gui.stagedFiles()) == 0 && gui.State.WorkingTreeState == "normal" {
return gui.createErrorPanel(g, gui.Tr.SLocalize("NoStagedFilesToCommit"))
}
title := strings.Title(gui.Tr.SLocalize("AmendLastCommit"))
@@ -294,7 +297,7 @@ func (gui *Gui) handleAmendCommitPress(g *gocui.Gui, filesView *gocui.View) erro
// handleCommitEditorPress - handle when the user wants to commit changes via
// their editor rather than via the popup panel
func (gui *Gui) handleCommitEditorPress(g *gocui.Gui, filesView *gocui.View) error {
- if len(gui.stagedFiles()) == 0 && !gui.State.HasMergeConflicts {
+ if len(gui.stagedFiles()) == 0 && gui.State.WorkingTreeState == "normal" {
return gui.createErrorPanel(g, gui.Tr.SLocalize("NoStagedFilesToCommit"))
}
gui.PrepareSubProcess(g, "git", "commit")
@@ -347,15 +350,27 @@ func (gui *Gui) refreshStateFiles() {
files := gui.GitCommand.GetStatusFiles()
gui.State.Files = gui.GitCommand.MergeStatusFiles(gui.State.Files, files)
gui.refreshSelectedLine(&gui.State.Panels.Files.SelectedLine, len(gui.State.Files))
- gui.updateHasMergeConflictStatus()
+ gui.updateWorkTreeState()
}
-func (gui *Gui) updateHasMergeConflictStatus() error {
+func (gui *Gui) updateWorkTreeState() error {
merging, err := gui.GitCommand.IsInMergeState()
if err != nil {
return err
}
- gui.State.HasMergeConflicts = merging
+ if merging {
+ gui.State.WorkingTreeState = "merging"
+ return nil
+ }
+ rebasing, err := gui.GitCommand.IsInRebaseState()
+ if err != nil {
+ return err
+ }
+ if rebasing {
+ gui.State.WorkingTreeState = "rebasing"
+ return nil
+ }
+ gui.State.WorkingTreeState = "normal"
return nil
}
diff --git a/pkg/gui/gui.go b/pkg/gui/gui.go
index 26ebd5f2b..ef41482c9 100644
--- a/pkg/gui/gui.go
+++ b/pkg/gui/gui.go
@@ -125,6 +125,7 @@ type guiState struct {
Platform commands.Platform
Updating bool
Panels *panelStates
+ WorkingTreeState string // one of "merging", "rebasing", "normal"
}
// NewGui builds a new gui handler
diff --git a/pkg/gui/keybindings.go b/pkg/gui/keybindings.go
index 5bc08db59..745e2fa7f 100644
--- a/pkg/gui/keybindings.go
+++ b/pkg/gui/keybindings.go
@@ -216,7 +216,7 @@ func (gui *Gui) GetKeybindings() []*Binding {
ViewName: "files",
Key: gocui.KeyEnter,
Modifier: gocui.ModNone,
- Handler: gui.handleSwitchToStagingPanel,
+ Handler: gui.handleEnterFile,
Description: gui.Tr.SLocalize("StageLines"),
KeyReadable: "enter",
}, {
diff --git a/pkg/gui/merge_panel.go b/pkg/gui/merge_panel.go
index 7c2922c9b..404266d29 100644
--- a/pkg/gui/merge_panel.go
+++ b/pkg/gui/merge_panel.go
@@ -20,11 +20,12 @@ func (gui *Gui) findConflicts(content string) ([]commands.Conflict, error) {
conflicts := make([]commands.Conflict, 0)
var newConflict commands.Conflict
for i, line := range utils.SplitLines(content) {
- if line == "<<<<<<< HEAD" || line == "<<<<<<< MERGE_HEAD" || line == "<<<<<<< Updated upstream" {
+ trimmedLine := strings.TrimPrefix(line, "++")
+ if trimmedLine == "<<<<<<< HEAD" || trimmedLine == "<<<<<<< MERGE_HEAD" || trimmedLine == "<<<<<<< Updated upstream" {
newConflict = commands.Conflict{Start: i}
- } else if line == "=======" {
+ } else if trimmedLine == "=======" {
newConflict.Middle = i
- } else if strings.HasPrefix(line, ">>>>>>> ") {
+ } else if strings.HasPrefix(trimmedLine, ">>>>>>> ") {
newConflict.End = i
conflicts = append(conflicts, newConflict)
}
@@ -258,10 +259,16 @@ func (gui *Gui) handleCompleteMerge(g *gocui.Gui) error {
gui.refreshFiles(g)
if rebase, err := gui.GitCommand.IsInRebaseState(); rebase && err == nil {
if err := gui.GitCommand.ContinueRebaseBranch(); err != nil {
- gui.Log.Errorln(err)
+ if strings.Contains(err.Error(), "No changes - did you forget to use") {
+ if err := gui.GitCommand.SkipRebaseBranch(); err != nil {
+ gui.Log.Errorln(err)
+ }
+ } else {
+ gui.Log.Errorln(err)
+ }
}
if err := gui.refreshSidePanels(g); err != nil {
- gui.Log.Errorln(err)
+ return err
}
}
return gui.switchFocus(g, nil, filesView)
diff --git a/pkg/gui/status_panel.go b/pkg/gui/status_panel.go
index 8d3b34dfe..16017abba 100644
--- a/pkg/gui/status_panel.go
+++ b/pkg/gui/status_panel.go
@@ -22,11 +22,11 @@ func (gui *Gui) refreshStatus(g *gocui.Gui) error {
pushables, pullables := gui.GitCommand.GetCurrentBranchUpstreamDifferenceCount()
fmt.Fprint(v, "↑"+pushables+"↓"+pullables)
branches := gui.State.Branches
- if err := gui.updateHasMergeConflictStatus(); err != nil {
+ if err := gui.updateWorkTreeState(); err != nil {
return err
}
- if gui.State.HasMergeConflicts {
- fmt.Fprint(v, utils.ColoredString(" (merging)", color.FgYellow))
+ if gui.State.WorkingTreeState != "normal" {
+ fmt.Fprint(v, utils.ColoredString(fmt.Sprintf(" (%s)", gui.State.WorkingTreeState), color.FgYellow))
}
if len(branches) == 0 {
diff --git a/test/repos/merge_conflict.sh b/test/repos/merge_conflict.sh
index 357597719..9a8e9f442 100755
--- a/test/repos/merge_conflict.sh
+++ b/test/repos/merge_conflict.sh
@@ -24,30 +24,62 @@ git add file1
git add directory
git commit -m "first commit"
-git checkout -b develop
+git checkout -b develop
echo "once upon a time there was a dog" >> file1
add_spacing file1
echo "once upon a time there was another dog" >> file1
git add file1
-
echo "test2" > directory/file
echo "test2" > directory/file2
git add directory
-
git commit -m "first commit on develop"
-git checkout master
+git checkout master
echo "once upon a time there was a cat" >> file1
add_spacing file1
echo "once upon a time there was another cat" >> file1
git add file1
-
echo "test3" > directory/file
echo "test3" > directory/file2
git add directory
+git commit -m "first commit on master"
-git commit -m "first commit on develop"
-git merge develop # should have a merge conflict here
+git checkout develop
+echo "once upon a time there was a mouse" >> file3
+git add file3
+git commit -m "second commit on develop"
+
+
+git checkout master
+echo "once upon a time there was a horse" >> file3
+git add file3
+git commit -m "second commit on master"
+
+
+git checkout develop
+echo "once upon a time there was a mouse" >> file4
+git add file4
+git commit -m "third commit on develop"
+
+
+git checkout master
+echo "once upon a time there was a horse" >> file4
+git add file4
+git commit -m "third commit on master"
+
+
+git checkout develop
+echo "once upon a time there was a mouse" >> file5
+git add file5
+git commit -m "fourth commit on develop"
+
+
+git checkout master
+echo "once upon a time there was a horse" >> file5
+git add file5
+git commit -m "fourth commit on master"
+
+# git merge develop # should have a merge conflict here