Skip to content

Commit

Permalink
more work on rebasing including visual indicators
Browse files Browse the repository at this point in the history
  • Loading branch information
jesseduffield committed Dec 10, 2018
1 parent cce6f40 commit e0ff46f
Show file tree
Hide file tree
Showing 10 changed files with 221 additions and 51 deletions.
21 changes: 14 additions & 7 deletions pkg/commands/commit.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)}
Expand Down
122 changes: 108 additions & 14 deletions pkg/commands/git.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -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 {
Expand Down Expand Up @@ -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")
}
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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 <[email protected]>
// 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)
}
Expand All @@ -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
}
Expand Down
11 changes: 11 additions & 0 deletions pkg/commands/os.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
9 changes: 6 additions & 3 deletions pkg/gui/branches_panel.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand All @@ -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)
}
Expand Down
37 changes: 26 additions & 11 deletions pkg/gui/files_panel.go
Original file line number Diff line number Diff line change
Expand Up @@ -118,22 +118,25 @@ 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 {
return err
}
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
}
Expand Down Expand Up @@ -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)
Expand All @@ -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"))
Expand All @@ -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")
Expand Down Expand Up @@ -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
}

Expand Down
1 change: 1 addition & 0 deletions pkg/gui/gui.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
2 changes: 1 addition & 1 deletion pkg/gui/keybindings.go
Original file line number Diff line number Diff line change
Expand Up @@ -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",
}, {
Expand Down
Loading

0 comments on commit e0ff46f

Please sign in to comment.