summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--files_panel.go48
-rw-r--r--gui.go239
-rw-r--r--merge_panel.go136
-rwxr-xr-xtest/generate_basic_repo.sh12
4 files changed, 248 insertions, 187 deletions
diff --git a/files_panel.go b/files_panel.go
index f29cbb75a..dd1f85525 100644
--- a/files_panel.go
+++ b/files_panel.go
@@ -29,12 +29,24 @@ func stagedFiles(files []GitFile) []GitFile {
return result
}
+func stageSelectedFile(g *gocui.Gui) error {
+ file, err := getSelectedFile(g)
+ if err != nil {
+ return err
+ }
+ return stageFile(file.Name)
+}
+
func handleFilePress(g *gocui.Gui, v *gocui.View) error {
file, err := getSelectedFile(g)
if err != nil {
return err
}
+ if file.HasMergeConflicts {
+ return handleSwitchToMerge(g, v)
+ }
+
if file.HasUnstagedChanges {
stageFile(file.Name)
} else {
@@ -64,22 +76,22 @@ func getSelectedFile(g *gocui.Gui) (GitFile, error) {
}
func handleFileRemove(g *gocui.Gui, v *gocui.View) error {
- file, err := getSelectedFile(g)
- if err != nil {
- return err
- }
- var deleteVerb string
- if file.Tracked {
- deleteVerb = "checkout"
- } else {
- deleteVerb = "delete"
- }
- return createConfirmationPanel(g, v, strings.Title(deleteVerb)+" file", "Are you sure you want to "+deleteVerb+" "+file.Name+" (you will lose your changes)?", func(g *gocui.Gui, v *gocui.View) error {
- if err := removeFile(file); err != nil {
- panic(err)
- }
- return refreshFiles(g)
- }, nil)
+ file, err := getSelectedFile(g)
+ if err != nil {
+ return err
+ }
+ var deleteVerb string
+ if file.Tracked {
+ deleteVerb = "checkout"
+ } else {
+ deleteVerb = "delete"
+ }
+ return createConfirmationPanel(g, v, strings.Title(deleteVerb)+" file", "Are you sure you want to "+deleteVerb+" "+file.Name+" (you will lose your changes)?", func(g *gocui.Gui, v *gocui.View) error {
+ if err := removeFile(file); err != nil {
+ panic(err)
+ }
+ return refreshFiles(g)
+ }, nil)
}
func handleIgnoreFile(g *gocui.Gui, v *gocui.View) error {
@@ -176,7 +188,7 @@ func handleVsCodeFileOpen(g *gocui.Gui, v *gocui.View) error {
}
func handleRefreshFiles(g *gocui.Gui, v *gocui.View) error {
- return refreshFiles(g)
+ return refreshFiles(g)
}
func refreshStateGitFiles() {
@@ -291,7 +303,7 @@ func handleSwitchToMerge(g *gocui.Gui, v *gocui.View) error {
return nil
}
if !file.HasMergeConflicts {
- return nil
+ return createErrorPanel(g, "This file has no merge conflicts")
}
switchFocus(g, v, mergeView)
return refreshMergePanel(g)
diff --git a/gui.go b/gui.go
index 90b177107..e01bfa7ef 100644
--- a/gui.go
+++ b/gui.go
@@ -66,124 +66,127 @@ func handleRefresh(g *gocui.Gui, v *gocui.View) error {
}
func keybindings(g *gocui.Gui) error {
- if err := g.SetKeybinding("", gocui.KeyTab, gocui.ModNone, nextView); err != nil {
- return err
- }
- if err := g.SetKeybinding("", 'q', gocui.ModNone, quit); err != nil {
- return err
- }
- if err := g.SetKeybinding("", gocui.KeyCtrlC, gocui.ModNone, quit); err != nil {
- return err
- }
- if err := g.SetKeybinding("", gocui.KeyArrowDown, gocui.ModNone, cursorDown); err != nil {
- return err
- }
- if err := g.SetKeybinding("", gocui.KeyArrowUp, gocui.ModNone, cursorUp); err != nil {
- return err
- }
- if err := g.SetKeybinding("", gocui.KeyPgup, gocui.ModNone, scrollUpMain); err != nil {
- return err
- }
- if err := g.SetKeybinding("", gocui.KeyPgdn, gocui.ModNone, scrollDownMain); err != nil {
- return err
- }
- if err := g.SetKeybinding("", 'P', gocui.ModNone, pushFiles); err != nil {
- return err
- }
- if err := g.SetKeybinding("", 'p', gocui.ModNone, pullFiles); err != nil {
- return err
- }
- if err := g.SetKeybinding("", 'R', gocui.ModNone, handleRefresh); err != nil {
- return err
- }
- if err := g.SetKeybinding("files", 'c', gocui.ModNone, handleCommitPress); err != nil {
- return err
- }
- if err := g.SetKeybinding("files", gocui.KeySpace, gocui.ModNone, handleFilePress); err != nil {
- return err
- }
- if err := g.SetKeybinding("files", 'd', gocui.ModNone, handleFileRemove); err != nil {
- return err
- }
- if err := g.SetKeybinding("files", 'm', gocui.ModNone, handleSwitchToMerge); err != nil {
- return err
- }
- if err := g.SetKeybinding("files", 'o', gocui.ModNone, handleFileOpen); err != nil {
- return err
- }
- if err := g.SetKeybinding("files", 's', gocui.ModNone, handleSublimeFileOpen); err != nil {
- return err
- }
- if err := g.SetKeybinding("files", 'i', gocui.ModNone, handleIgnoreFile); err != nil {
- return err
- }
- if err := g.SetKeybinding("files", 'r', gocui.ModNone, handleRefreshFiles); err != nil {
- return err
- }
- if err := g.SetKeybinding("files", 'S', gocui.ModNone, handleStashSave); err != nil {
- return err
- }
- if err := g.SetKeybinding("files", 'a', gocui.ModNone, handleAbortMerge); err != nil {
- return err
- }
- if err := g.SetKeybinding("main", gocui.KeyArrowUp, gocui.ModNone, handleSelectTop); err != nil {
- return err
- }
- if err := g.SetKeybinding("main", gocui.KeyEsc, gocui.ModNone, handleEscapeMerge); err != nil {
- return err
- }
- if err := g.SetKeybinding("main", gocui.KeyArrowDown, gocui.ModNone, handleSelectBottom); err != nil {
- return err
- }
- if err := g.SetKeybinding("main", gocui.KeySpace, gocui.ModNone, handlePickConflict); err != nil {
- return err
- }
- if err := g.SetKeybinding("main", gocui.KeyArrowLeft, gocui.ModNone, handleSelectPrevConflict); err != nil {
- return err
- }
- if err := g.SetKeybinding("main", gocui.KeyArrowRight, gocui.ModNone, handleSelectNextConflict); err != nil {
- return err
- }
- if err := g.SetKeybinding("main", 'z', gocui.ModNone, handlePopFileSnapshot); err != nil {
- return err
- }
- if err := g.SetKeybinding("branches", gocui.KeySpace, gocui.ModNone, handleBranchPress); err != nil {
- return err
- }
- if err := g.SetKeybinding("branches", 'c', gocui.ModNone, handleCheckoutByName); err != nil {
- return err
- }
- if err := g.SetKeybinding("branches", 'F', gocui.ModNone, handleForceCheckout); err != nil {
- return err
- }
- if err := g.SetKeybinding("branches", 'n', gocui.ModNone, handleNewBranch); err != nil {
- return err
- }
- if err := g.SetKeybinding("branches", 'm', gocui.ModNone, handleMerge); err != nil {
- return err
- }
- if err := g.SetKeybinding("commits", 's', gocui.ModNone, handleCommitSquashDown); err != nil {
- return err
- }
- if err := g.SetKeybinding("commits", 'r', gocui.ModNone, handleRenameCommit); err != nil {
- return err
- }
- if err := g.SetKeybinding("commits", 'g', gocui.ModNone, handleResetToCommit); err != nil {
- return err
- }
- if err := g.SetKeybinding("stash", gocui.KeySpace, gocui.ModNone, handleStashApply); err != nil {
- return err
- }
- // TODO: come up with a better keybinding (p/P used for pushing/pulling which
- // I'd like to be global. Perhaps all global keybindings should use a modifier
- // like command? But then there's gonna be hotkey conflicts with the terminal
- if err := g.SetKeybinding("stash", 'k', gocui.ModNone, handleStashPop); err != nil {
- return err
- }
- if err := g.SetKeybinding("stash", 'd', gocui.ModNone, handleStashDrop); err != nil {
- return err
- }
- return nil
+ if err := g.SetKeybinding("", gocui.KeyTab, gocui.ModNone, nextView); err != nil {
+ return err
+ }
+ if err := g.SetKeybinding("", 'q', gocui.ModNone, quit); err != nil {
+ return err
+ }
+ if err := g.SetKeybinding("", gocui.KeyCtrlC, gocui.ModNone, quit); err != nil {
+ return err
+ }
+ if err := g.SetKeybinding("", gocui.KeyArrowDown, gocui.ModNone, cursorDown); err != nil {
+ return err
+ }
+ if err := g.SetKeybinding("", gocui.KeyArrowUp, gocui.ModNone, cursorUp); err != nil {
+ return err
+ }
+ if err := g.SetKeybinding("", gocui.KeyPgup, gocui.ModNone, scrollUpMain); err != nil {
+ return err
+ }
+ if err := g.SetKeybinding("", gocui.KeyPgdn, gocui.ModNone, scrollDownMain); err != nil {
+ return err
+ }
+ if err := g.SetKeybinding("", 'P', gocui.ModNone, pushFiles); err != nil {
+ return err
+ }
+ if err := g.SetKeybinding("", 'p', gocui.ModNone, pullFiles); err != nil {
+ return err
+ }
+ if err := g.SetKeybinding("", 'R', gocui.ModNone, handleRefresh); err != nil {
+ return err
+ }
+ if err := g.SetKeybinding("files", 'c', gocui.ModNone, handleCommitPress); err != nil {
+ return err
+ }
+ if err := g.SetKeybinding("files", gocui.KeySpace, gocui.ModNone, handleFilePress); err != nil {
+ return err
+ }
+ if err := g.SetKeybinding("files", 'd', gocui.ModNone, handleFileRemove); err != nil {
+ return err
+ }
+ if err := g.SetKeybinding("files", 'm', gocui.ModNone, handleSwitchToMerge); err != nil {
+ return err
+ }
+ if err := g.SetKeybinding("files", 'o', gocui.ModNone, handleFileOpen); err != nil {
+ return err
+ }
+ if err := g.SetKeybinding("files", 's', gocui.ModNone, handleSublimeFileOpen); err != nil {
+ return err
+ }
+ if err := g.SetKeybinding("files", 'i', gocui.ModNone, handleIgnoreFile); err != nil {
+ return err
+ }
+ if err := g.SetKeybinding("files", 'r', gocui.ModNone, handleRefreshFiles); err != nil {
+ return err
+ }
+ if err := g.SetKeybinding("files", 'S', gocui.ModNone, handleStashSave); err != nil {
+ return err
+ }
+ if err := g.SetKeybinding("files", 'a', gocui.ModNone, handleAbortMerge); err != nil {
+ return err
+ }
+ if err := g.SetKeybinding("main", gocui.KeyArrowUp, gocui.ModNone, handleSelectTop); err != nil {
+ return err
+ }
+ if err := g.SetKeybinding("main", gocui.KeyEsc, gocui.ModNone, handleEscapeMerge); err != nil {
+ return err
+ }
+ if err := g.SetKeybinding("main", gocui.KeyArrowDown, gocui.ModNone, handleSelectBottom); err != nil {
+ return err
+ }
+ if err := g.SetKeybinding("main", gocui.KeySpace, gocui.ModNone, handlePickHunk); err != nil {
+ return err
+ }
+ if err := g.SetKeybinding("main", 'b', gocui.ModNone, handlePickBothHunks); err != nil {
+ return err
+ }
+ if err := g.SetKeybinding("main", gocui.KeyArrowLeft, gocui.ModNone, handleSelectPrevConflict); err != nil {
+ return err
+ }
+ if err := g.SetKeybinding("main", gocui.KeyArrowRight, gocui.ModNone, handleSelectNextConflict); err != nil {
+ return err
+ }
+ if err := g.SetKeybinding("main", 'z', gocui.ModNone, handlePopFileSnapshot); err != nil {
+ return err
+ }
+ if err := g.SetKeybinding("branches", gocui.KeySpace, gocui.ModNone, handleBranchPress); err != nil {
+ return err
+ }
+ if err := g.SetKeybinding("branches", 'c', gocui.ModNone, handleCheckoutByName); err != nil {
+ return err
+ }
+ if err := g.SetKeybinding("branches", 'F', gocui.ModNone, handleForceCheckout); err != nil {
+ return err
+ }
+ if err := g.SetKeybinding("branches", 'n', gocui.ModNone, handleNewBranch); err != nil {
+ return err
+ }
+ if err := g.SetKeybinding("branches", 'm', gocui.ModNone, handleMerge); err != nil {
+ return err
+ }
+ if err := g.SetKeybinding("commits", 's', gocui.ModNone, handleCommitSquashDown); err != nil {
+ return err
+ }
+ if err := g.SetKeybinding("commits", 'r', gocui.ModNone, handleRenameCommit); err != nil {
+ return err
+ }
+ if err := g.SetKeybinding("commits", 'g', gocui.ModNone, handleResetToCommit); err != nil {
+ return err
+ }
+ if err := g.SetKeybinding("stash", gocui.KeySpace, gocui.ModNone, handleStashApply); err != nil {
+ return err
+ }
+ // TODO: come up with a better keybinding (p/P used for pushing/pulling which
+ // I'd like to be global. Perhaps all global keybindings should use a modifier
+ // like command? But then there's gonna be hotkey conflicts with the terminal
+ if err := g.SetKeybinding("stash", 'k', gocui.ModNone, handleStashPop); err != nil {
+ return err
+ }
+ if err := g.SetKeybinding("stash", 'd', gocui.ModNone, handleStashDrop); err != nil {
+ return err
+ }
+ return nil
}
func layout(g *gocui.Gui) error {
diff --git a/merge_panel.go b/merge_panel.go
index c5e910a2c..f0043ee94 100644
--- a/merge_panel.go
+++ b/merge_panel.go
@@ -6,6 +6,7 @@ import (
"bufio"
"bytes"
"io/ioutil"
+ "math"
"os"
"strings"
@@ -86,16 +87,21 @@ func handleSelectPrevConflict(g *gocui.Gui, v *gocui.View) error {
return refreshMergePanel(g)
}
-func isIndexToDelete(i int, conflict conflict, top bool) bool {
+func isIndexToDelete(i int, conflict conflict, pick string) bool {
return i == conflict.middle ||
i == conflict.start ||
i == conflict.end ||
- (!top && i > conflict.start && i < conflict.middle) ||
- (top && i > conflict.middle && i < conflict.end)
+ pick != "both" &&
+ (pick == "bottom" && i > conflict.start && i < conflict.middle) ||
+ (pick == "top" && i > conflict.middle && i < conflict.end)
}
-func resolveConflict(filename string, conflict conflict, top bool) error {
- file, err := os.Open(filename)
+func resolveConflict(g *gocui.Gui, conflict conflict, pick string) error {
+ gitFile, err := getSelectedFile(g)
+ if err != nil {
+ return err
+ }
+ file, err := os.Open(gitFile.Name)
if err != nil {
return err
}
@@ -108,16 +114,20 @@ func resolveConflict(filename string, conflict conflict, top bool) error {
if err != nil {
break
}
- if !isIndexToDelete(i, conflict, top) {
+ if !isIndexToDelete(i, conflict, pick) {
output += line
}
}
devLog(output)
- return ioutil.WriteFile(filename, []byte(output), 0644)
+ return ioutil.WriteFile(gitFile.Name, []byte(output), 0644)
}
-func pushFileSnapshot(filename string) error {
- content, err := catFile(filename)
+func pushFileSnapshot(g *gocui.Gui) error {
+ gitFile, err := getSelectedFile(g)
+ if err != nil {
+ return err
+ }
+ content, err := catFile(gitFile.Name)
if err != nil {
return err
}
@@ -139,14 +149,25 @@ func handlePopFileSnapshot(g *gocui.Gui, v *gocui.View) error {
return refreshMergePanel(g)
}
-func handlePickConflict(g *gocui.Gui, v *gocui.View) error {
+func handlePickHunk(g *gocui.Gui, v *gocui.View) error {
conflict := state.Conflicts[state.ConflictIndex]
- gitFile, err := getSelectedFile(g)
+ pushFileSnapshot(g)
+ pick := "bottom"
+ if state.ConflictTop {
+ pick = "top"
+ }
+ err := resolveConflict(g, conflict, pick)
if err != nil {
- return err
+ panic(err)
}
- pushFileSnapshot(gitFile.Name)
- err = resolveConflict(gitFile.Name, conflict, state.ConflictTop)
+ refreshMergePanel(g)
+ return nil
+}
+
+func handlePickBothHunks(g *gocui.Gui, v *gocui.View) error {
+ conflict := state.Conflicts[state.ConflictIndex]
+ pushFileSnapshot(g)
+ err := resolveConflict(g, conflict, "both")
if err != nil {
panic(err)
}
@@ -159,46 +180,48 @@ func currentViewName(g *gocui.Gui) string {
}
func refreshMergePanel(g *gocui.Gui) error {
- cat, err := catSelectedFile(g)
- if err != nil {
- return err
- }
- state.Conflicts, err = findConflicts(cat)
- if err != nil {
- return err
- }
-
- if len(state.Conflicts) == 0 {
- state.ConflictIndex = 0
- } else if state.ConflictIndex > len(state.Conflicts)-1 {
- state.ConflictIndex = len(state.Conflicts) - 1
- }
- hasFocus := currentViewName(g) == "main"
- if hasFocus {
- renderMergeOptions(g)
- }
- content, err := coloredConflictFile(cat, state.Conflicts, state.ConflictIndex, state.ConflictTop, hasFocus)
- if err != nil {
- return err
- }
- if err := scrollToConflict(g); err != nil {
- return err
- }
- return renderString(g, "main", content)
+ cat, err := catSelectedFile(g)
+ if err != nil {
+ return err
+ }
+ state.Conflicts, err = findConflicts(cat)
+ if err != nil {
+ return err
+ }
+
+ if len(state.Conflicts) == 0 {
+ return handleCompleteMerge(g)
+ } else if state.ConflictIndex > len(state.Conflicts)-1 {
+ state.ConflictIndex = len(state.Conflicts) - 1
+ }
+ hasFocus := currentViewName(g) == "main"
+ if hasFocus {
+ renderMergeOptions(g)
+ }
+ content, err := coloredConflictFile(cat, state.Conflicts, state.ConflictIndex, state.ConflictTop, hasFocus)
+ if err != nil {
+ return err
+ }
+ if err := scrollToConflict(g); err != nil {
+ return err
+ }
+ return renderString(g, "main", content)
}
func scrollToConflict(g *gocui.Gui) error {
- mainView, err := g.View("main")
- if err != nil {
- return err
- }
- if len(state.Conflicts) == 0 {
- return nil
- }
- conflict := state.Conflicts[state.ConflictIndex]
- ox, oy := mainView.Origin()
- devLog(oy, conflict.start)
- return mainView.SetOrigin(ox, conflict.start)
+ mainView, err := g.View("main")
+ if err != nil {
+ return err
+ }
+ if len(state.Conflicts) == 0 {
+ return nil
+ }
+ conflict := state.Conflicts[state.ConflictIndex]
+ ox, _ := mainView.Origin()
+ _, height := mainView.Size()
+ conflictMiddle := (conflict.end + conflict.start) / 2
+ newOriginY := int(math.Max(0, float64(conflictMiddle-(height/2))))
+ return mainView.SetOrigin(ox, newOriginY)
}
func switchToMerging(g *gocui.Gui) error {
@@ -216,6 +239,7 @@ func renderMergeOptions(g *gocui.Gui) error {
"up/down": "pick hunk",
"left/right": "previous/next commit",
"space": "pick hunk",
+ "b": "pick both hunks",
"z": "undo",
})
}
@@ -228,3 +252,13 @@ func handleEscapeMerge(g *gocui.Gui, v *gocui.View) error {
refreshFiles(g)
return switchFocus(g, v, filesView)
}
+
+func handleCompleteMerge(g *gocui.Gui) error {
+ filesView, err := g.View("files")
+ if err != nil {
+ return err
+ }
+ stageSelectedFile(g)
+ refreshFiles(g)
+ return switchFocus(g, nil, filesView)
+}
diff --git a/test/generate_basic_repo.sh b/test/generate_basic_repo.sh
index 385ea8520..ef04333c7 100755
--- a/test/generate_basic_repo.sh
+++ b/test/generate_basic_repo.sh
@@ -18,19 +18,31 @@ cd ${reponame}
git init
+function add_spacing {
+ for i in {1..60}
+ do
+ echo "..." >> $1
+ done
+}
+
echo "Here is a story that has been told throuhg the ages" >> file1
+
git add file1
git commit -m "first commit"
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
git commit -m "first commit on develop"
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
git commit -m "first commit on develop"