summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--pkg/git/add.go52
-rw-r--r--pkg/git/commands.go9
-rw-r--r--pkg/git/repository.go4
-rw-r--r--pkg/git/reset.go52
-rw-r--r--pkg/git/stash.go44
-rw-r--r--pkg/git/status.go1
-rw-r--r--pkg/gui/gui-util.go4
-rw-r--r--pkg/gui/keybindings.go88
-rw-r--r--pkg/gui/stagedview.go35
-rw-r--r--pkg/gui/stashview.go45
-rw-r--r--pkg/gui/statusview.go18
-rw-r--r--pkg/gui/unstagedview.go37
12 files changed, 356 insertions, 33 deletions
diff --git a/pkg/git/add.go b/pkg/git/add.go
new file mode 100644
index 0000000..73c7d9e
--- /dev/null
+++ b/pkg/git/add.go
@@ -0,0 +1,52 @@
+package git
+
+import (
+ "errors"
+ "strings"
+
+ log "github.com/sirupsen/logrus"
+)
+
+var addCommand = "add"
+
+type AddOptions struct {
+ Update bool
+ Force bool
+ DryRun bool
+}
+
+func (file *File) Add(option AddOptions) error {
+ args := make([]string, 0)
+ args = append(args, addCommand)
+ args = append(args, file.Name)
+ if option.Update {
+ args = append(args, "--update")
+ }
+ if option.Force {
+ args = append(args, "--force")
+ }
+ if option.DryRun {
+ args = append(args, "--dry-run")
+ }
+ out, err := GenericGitCommandWithOutput(strings.TrimSuffix(file.AbsPath, file.Name), args)
+ if err != nil {
+ log.Warn("Error while add command")
+ return errors.New(out + "\n" + err.Error())
+ }
+ return nil
+}
+
+func (entity *RepoEntity) AddAll(option AddOptions) error {
+ args := make([]string, 0)
+ args = append(args, addCommand)
+ if option.DryRun {
+ args = append(args, "--dry-run")
+ }
+ args = append(args, ".")
+ out, err := GenericGitCommandWithOutput(entity.AbsPath, args)
+ if err != nil {
+ log.Warn("Error while add command")
+ return errors.New(out + "\n" + err.Error())
+ }
+ return nil
+} \ No newline at end of file
diff --git a/pkg/git/commands.go b/pkg/git/commands.go
index 0a1820b..825c712 100644
--- a/pkg/git/commands.go
+++ b/pkg/git/commands.go
@@ -22,6 +22,15 @@ func GenericGitCommandWithOutput(repoPath string, args []string) (string, error)
return helpers.TrimTrailingNewline(out), nil
}
+// GenericGitCommand runs any git command with returning output
+func GenericGitCommandWithErrorOutput(repoPath string, args []string) (string, error) {
+ out, err := helpers.RunCommandWithOutput(repoPath, "git", args)
+ if err != nil {
+ return helpers.TrimTrailingNewline(out), err
+ }
+ return helpers.TrimTrailingNewline(out), nil
+}
+
// GitShow is conventional git show command without any argument
func GitShow(repoPath, hash string) string {
args := []string{"show", hash}
diff --git a/pkg/git/repository.go b/pkg/git/repository.go
index 7d1fd73..0ec6eaf 100644
--- a/pkg/git/repository.go
+++ b/pkg/git/repository.go
@@ -122,6 +122,7 @@ func (entity *RepoEntity) Refresh() error {
if err := entity.loadLocalBranches(); err != nil {
return err
}
+ entity.Branch.Clean = entity.isClean()
entity.RefreshPushPull()
if err := entity.loadCommits(); err != nil {
return err
@@ -129,5 +130,8 @@ func (entity *RepoEntity) Refresh() error {
if err := entity.loadRemotes(); err != nil {
return err
}
+ if err := entity.loadStashedItems(); err != nil {
+ return err
+ }
return nil
}
diff --git a/pkg/git/reset.go b/pkg/git/reset.go
new file mode 100644
index 0000000..1a4c71d
--- /dev/null
+++ b/pkg/git/reset.go
@@ -0,0 +1,52 @@
+package git
+
+import (
+ "errors"
+ "strings"
+
+ log "github.com/sirupsen/logrus"
+)
+
+var resetCommand = "reset"
+
+type ResetOptions struct {
+ Hard bool
+ Merge bool
+ Keep bool
+}
+
+func (file *File) Reset(option ResetOptions) error {
+ args := make([]string, 0)
+ args = append(args, resetCommand)
+ args = append(args, "--")
+ args = append(args, file.Name)
+ if option.Hard {
+ args = append(args, "--hard")
+ }
+ if option.Merge {
+ args = append(args, "--merge")
+ }
+ if option.Keep {
+ args = append(args, "--keep")
+ }
+ out, err := GenericGitCommandWithOutput(strings.TrimSuffix(file.AbsPath, file.Name), args)
+ if err != nil {
+ log.Warn("Error while add command")
+ return errors.New(out + "\n" + err.Error())
+ }
+ return nil
+}
+
+func (entity *RepoEntity) ResetAll(option ResetOptions) error {
+ args := make([]string, 0)
+ args = append(args, resetCommand)
+ if option.Hard {
+ args = append(args, "--hard")
+ }
+ out, err := GenericGitCommandWithOutput(entity.AbsPath, args)
+ if err != nil {
+ log.Warn("Error while add command")
+ return errors.New(out + "\n" + err.Error())
+ }
+ return nil
+} \ No newline at end of file
diff --git a/pkg/git/stash.go b/pkg/git/stash.go
index caaaf90..6c647ae 100644
--- a/pkg/git/stash.go
+++ b/pkg/git/stash.go
@@ -15,30 +15,9 @@ type StashedItem struct {
BranchName string
Hash string
Description string
+ EntityPath string
}
-// // StashOption is the option argument for git stash command
-// type StashOption string
-
-// var (
-// StashPop StashOption = "pop"
-// StashPush StashOption = "push"
-// StashDrop StashOption = "drop"
-// )
-
-// // Stash used when you want to record the current state of the working
-// // directory and the index, but want to go back to a clean working directory.
-// func Stash(entity *RepoEntity, option StashOption) error {
-// args := make([]string, 0)
-// args = append(args, stashCommand)
-
-// if err := GenericGitCommand(entity.AbsPath, args); err != nil {
-// log.Warn("Error while stashing")
-// return err
-// }
-// return nil
-// }
-
func stashGet(entity *RepoEntity, option string) string {
args := make([]string, 0)
args = append(args, stashCommand)
@@ -88,17 +67,26 @@ func (entity *RepoEntity) loadStashedItems() error {
BranchName: branchName,
Hash: hash,
Description: desc,
+ EntityPath: entity.AbsPath,
})
}
return nil
}
-func (entity *RepoEntity) Stash() (error) {
+func (entity *RepoEntity) Stash() (output string, err error) {
args := make([]string, 0)
args = append(args, stashCommand)
- if err := GenericGitCommand(entity.AbsPath, args); err != nil {
- log.Warn("Error while stashing")
- return err
- }
- return nil
+
+ output, err = GenericGitCommandWithErrorOutput(entity.AbsPath, args)
+ entity.Refresh()
+ return output, err
+}
+
+func (stashedItem *StashedItem) Pop() (output string, err error) {
+ args := make([]string, 0)
+ args = append(args, stashCommand)
+ args = append(args, "pop")
+ args = append(args, "stash@{"+strconv.Itoa(stashedItem.StashID)+"}")
+ output, err = GenericGitCommandWithErrorOutput(stashedItem.EntityPath, args)
+ return output, err
} \ No newline at end of file
diff --git a/pkg/git/status.go b/pkg/git/status.go
index e1207ee..51a622b 100644
--- a/pkg/git/status.go
+++ b/pkg/git/status.go
@@ -26,6 +26,7 @@ var (
StatusDeleted FileStatus = 'D'
StatusRenamed FileStatus = 'R'
StatusCopied FileStatus = 'C'
+ StatusUpdated FileStatus = 'U'
StatusUntracked FileStatus = '?'
StatusIgnored FileStatus = '!'
)
diff --git a/pkg/gui/gui-util.go b/pkg/gui/gui-util.go
index 2338bac..a0780bd 100644
--- a/pkg/gui/gui-util.go
+++ b/pkg/gui/gui-util.go
@@ -166,7 +166,9 @@ func (gui *Gui) fastCursorDown(g *gocui.Gui, v *gocui.View) error {
if v != nil {
ox, oy := v.Origin()
_, vy := v.Size()
-
+ if len(v.BufferLines())+len(v.ViewBufferLines()) <= vy+oy || len(v.ViewBufferLines()) < vy {
+ return nil
+ }
// TODO: do something when it hits bottom
if err := v.SetOrigin(ox, oy+vy/2); err != nil {
return err
diff --git a/pkg/gui/keybindings.go b/pkg/gui/keybindings.go
index 55c86bd..e808959 100644
--- a/pkg/gui/keybindings.go
+++ b/pkg/gui/keybindings.go
@@ -152,6 +152,14 @@ func (gui *Gui) generateKeybindings() error {
Display: "j",
Description: "Down",
Vital: false,
+ }, {
+ View: view.Name,
+ Key: 't',
+ Modifier: gocui.ModNone,
+ Handler: gui.stashChanges,
+ Display: "t",
+ Description: "Save to Stash",
+ Vital: true,
},
}
for _, binding := range statusKeybindings {
@@ -159,6 +167,52 @@ func (gui *Gui) generateKeybindings() error {
}
}
individualKeybindings := []*KeyBinding{
+ // stash view
+ {
+ View: stashViewFeature.Name,
+ Key: 'p',
+ Modifier: gocui.ModNone,
+ Handler: gui.popStash,
+ Display: "p",
+ Description: "Pop Item",
+ Vital: true,
+ },
+ // staged view
+ {
+ View: stageViewFeature.Name,
+ Key: 'r',
+ Modifier: gocui.ModNone,
+ Handler: gui.resetChanges,
+ Display: "r",
+ Description: "Reset Item",
+ Vital: true,
+ }, {
+ View: stageViewFeature.Name,
+ Key: gocui.KeyCtrlR,
+ Modifier: gocui.ModNone,
+ Handler: gui.resetAllChanges,
+ Display: "ctrl+r",
+ Description: "Reset All Items",
+ Vital: true,
+ },
+ // unstaged view
+ {
+ View: unstageViewFeature.Name,
+ Key: 'a',
+ Modifier: gocui.ModNone,
+ Handler: gui.addChanges,
+ Display: "a",
+ Description: "Add Item",
+ Vital: true,
+ }, {
+ View: unstageViewFeature.Name,
+ Key: gocui.KeyCtrlA,
+ Modifier: gocui.ModNone,
+ Handler: gui.addAllChanges,
+ Display: "ctrl+a",
+ Description: "Add All Items",
+ Vital: true,
+ },
// Main view controls
{
View: mainViewFeature.Name,
@@ -502,7 +556,39 @@ func (gui *Gui) generateKeybindings() error {
Display: "c",
Description: "close/cancel",
Vital: true,
- },
+ }, {
+ View: errorViewFeature.Name,
+ Key: gocui.KeyArrowUp,
+ Modifier: gocui.ModNone,
+ Handler: gui.fastCursorUp,
+ Display: "↑",
+ Description: "Up",
+ Vital: true,
+ }, {
+ View: errorViewFeature.Name,
+ Key: gocui.KeyArrowDown,
+ Modifier: gocui.ModNone,
+ Handler: gui.fastCursorDown,
+ Display: "↓",
+ Description: "Down",
+ Vital: true,
+ }, {
+ View: errorViewFeature.Name,
+ Key: 'k',
+ Modifier: gocui.ModNone,
+ Handler: gui.fastCursorUp,
+ Display: "k",
+ Description: "Up",
+ Vital: false,
+ }, {
+ View: errorViewFeature.Name,
+ Key: 'j',
+ Modifier: gocui.ModNone,
+ Handler: gui.fastCursorDown,
+ Display: "j",
+ Description: "Down",
+ Vital: false,
+ },
}
for _, binding := range individualKeybindings {
gui.KeyBindings = append(gui.KeyBindings, binding)
diff --git a/pkg/gui/stagedview.go b/pkg/gui/stagedview.go
index e3684f0..bae80bd 100644
--- a/pkg/gui/stagedview.go
+++ b/pkg/gui/stagedview.go
@@ -30,6 +30,41 @@ func (gui *Gui) openStageView(g *gocui.Gui) error {
return nil
}
+func (gui *Gui) resetChanges(g *gocui.Gui, v *gocui.View) error {
+ entity := gui.getSelectedRepository()
+ files, _, err := generateFileLists(entity)
+ if err != nil {
+ return err
+ }
+ if len(files) <= 0 {
+ return nil
+ }
+ _, cy := v.Cursor()
+ _, oy := v.Origin()
+ if err := files[cy+oy].Reset(git.ResetOptions{
+
+ }); err != nil {
+ return err
+ }
+ if err := refreshAllStatusView(g, entity); err != nil {
+ return err
+ }
+ return nil
+}
+
+func (gui *Gui) resetAllChanges(g *gocui.Gui, v *gocui.View) error {
+ entity := gui.getSelectedRepository()
+ if err := entity.ResetAll(git.ResetOptions{
+
+ }); err != nil {
+ return err
+ }
+ if err := refreshAllStatusView(g, entity); err != nil {
+ return err
+ }
+ return nil
+}
+
// refresh the main view and re-render the repository representations
func refreshStagedView(g *gocui.Gui, entity *git.RepoEntity) error {
stageView, err := g.View(stageViewFeature.Name)
diff --git a/pkg/gui/stashview.go b/pkg/gui/stashview.go
index 8cdc46c..7322328 100644
--- a/pkg/gui/stashview.go
+++ b/pkg/gui/stashview.go
@@ -26,6 +26,49 @@ func (gui *Gui) openStashView(g *gocui.Gui) error {
return nil
}
+//
+func (gui *Gui) stashChanges(g *gocui.Gui, v *gocui.View) error {
+ entity := gui.getSelectedRepository()
+ output, err := entity.Stash()
+ if err != nil {
+ if err = gui.openErrorView(g, output,
+ "You should manually resolve this issue",
+ stashViewFeature.Name); err != nil {
+ return err
+ }
+ }
+ if err := refreshAllStatusView(g, entity); err != nil {
+ return err
+ }
+ return nil
+}
+
+//
+func (gui *Gui) popStash(g *gocui.Gui, v *gocui.View) error {
+ entity := gui.getSelectedRepository()
+ _, oy := v.Origin()
+ _, cy := v.Cursor()
+ if len(entity.Stasheds) <= 0 {
+ return nil
+ }
+ stashedItem := entity.Stasheds[oy+cy]
+ output, err := stashedItem.Pop()
+ if err != nil {
+ if err = gui.openErrorView(g, output,
+ "You should manually resolve this issue",
+ stashViewFeature.Name); err != nil {
+ return err
+ }
+ }
+ if err := entity.Refresh(); err != nil {
+ return err
+ }
+ if err := refreshAllStatusView(g, entity); err != nil {
+ return err
+ }
+ return nil
+}
+
// refresh the main view and re-render the repository representations
func refreshStashView(g *gocui.Gui, entity *git.RepoEntity) error {
stashView, err := g.View(stashViewFeature.Name)
@@ -41,7 +84,7 @@ func refreshStashView(g *gocui.Gui, entity *git.RepoEntity) error {
if i == cy+oy {
prefix = prefix + selectionIndicator
}
- fmt.Fprintf(stashView, "%s%d %s: %s (%s)\n", prefix, stashedItem.StashID, cyan.Sprint(stashedItem.BranchName), stashedItem.Description, stashedItem.Hash)
+ fmt.Fprintf(stashView, "%s%d %s: %s (%s)\n", prefix, stashedItem.StashID, cyan.Sprint(stashedItem.BranchName), stashedItem.Description, cyan.Sprint(stashedItem.Hash))
}
return nil
} \ No newline at end of file
diff --git a/pkg/gui/statusview.go b/pkg/gui/statusview.go
index b1873e8..fbc90ee 100644
--- a/pkg/gui/statusview.go
+++ b/pkg/gui/statusview.go
@@ -119,6 +119,13 @@ func (gui *Gui) closeStatusView(g *gocui.Gui, v *gocui.View) error {
if _, err := g.SetCurrentView(mainViewFeature.Name); err != nil {
return err
}
+ entity := gui.getSelectedRepository()
+ if err := gui.refreshMain(g); err != nil {
+ return err
+ }
+ if err := gui.refreshViews(g, entity); err != nil {
+ return err
+ }
gui.updateKeyBindingsView(g, mainViewFeature.Name)
return nil
}
@@ -129,7 +136,7 @@ func generateFileLists(entity *git.RepoEntity) (staged, unstaged []*git.File, er
return nil, nil, err
}
for _, file := range files {
- if file.X != git.StatusNotupdated && file.X != git.StatusUntracked && file.X != git.StatusIgnored {
+ if file.X != git.StatusNotupdated && file.X != git.StatusUntracked && file.X != git.StatusIgnored && file.X != git.StatusUpdated {
staged = append(staged, file)
}
if file.Y != git.StatusNotupdated {
@@ -156,3 +163,12 @@ func refreshStatusView(viewName string, g *gocui.Gui, entity *git.RepoEntity) er
}
return nil
}
+
+func refreshAllStatusView(g *gocui.Gui, entity *git.RepoEntity) error {
+ for _, v := range statusViews {
+ if err := refreshStatusView(v.Name, g, entity); err != nil {
+ return err
+ }
+ }
+ return nil
+}
diff --git a/pkg/gui/unstagedview.go b/pkg/gui/unstagedview.go
index 5ee25a7..ede4844 100644
--- a/pkg/gui/unstagedview.go
+++ b/pkg/gui/unstagedview.go
@@ -26,6 +26,41 @@ func (gui *Gui) openUnStagedView(g *gocui.Gui) error {
return nil
}
+func (gui *Gui) addChanges(g *gocui.Gui, v *gocui.View) error {
+ entity := gui.getSelectedRepository()
+ _, files, err := generateFileLists(entity)
+ if err != nil {
+ return err
+ }
+ if len(files) <= 0 {
+ return nil
+ }
+ _, cy := v.Cursor()
+ _, oy := v.Origin()
+ if err := files[cy+oy].Add(git.AddOptions{
+
+ }); err != nil {
+ return err
+ }
+ if err := refreshAllStatusView(g, entity); err != nil {
+ return err
+ }
+ return nil
+}
+
+func (gui *Gui) addAllChanges(g *gocui.Gui, v *gocui.View) error {
+ entity := gui.getSelectedRepository()
+ if err := entity.AddAll(git.AddOptions{
+
+ }); err != nil {
+ return err
+ }
+ if err := refreshAllStatusView(g, entity); err != nil {
+ return err
+ }
+ return nil
+}
+
// refresh the main view and re-render the repository representations
func refreshUnstagedView(g *gocui.Gui, entity *git.RepoEntity) error {
stageView, err := g.View(unstageViewFeature.Name)
@@ -44,7 +79,7 @@ func refreshUnstagedView(g *gocui.Gui, entity *git.RepoEntity) error {
if i == cy+oy {
prefix = prefix + selectionIndicator
}
- fmt.Fprintf(stageView, "%s%s%s %s\n", prefix, green.Sprint(string(file.X)), red.Sprint(string(file.Y)), file.Name)
+ fmt.Fprintf(stageView, "%s%s%s %s\n", prefix, red.Sprint(string(file.X)), red.Sprint(string(file.Y)), file.Name)
}
return nil
} \ No newline at end of file