aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorMichael Muré <batolettre@gmail.com>2018-08-12 21:09:30 +0200
committerMichael Muré <batolettre@gmail.com>2018-08-12 21:09:30 +0200
commite2f4b027c946831c3f4f119d87a80513c7cf8fdc (patch)
treeb2ec57c89c49062ab2e8adeacb2646d2f152db80
parent721ed3248e8bf167a89df14d9fc2bf5b0fe45753 (diff)
downloadgit-bug-e2f4b027c946831c3f4f119d87a80513c7cf8fdc.tar.gz
termui: implement push/pull
-rw-r--r--bug/bug_actions.go12
-rw-r--r--cache/cache.go21
-rw-r--r--commands/push.go7
-rw-r--r--repository/git.go16
-rw-r--r--repository/mock_repo.go8
-rw-r--r--repository/repo.go4
-rw-r--r--termui/bug_table.go100
-rw-r--r--termui/error_popup.go72
-rw-r--r--termui/msg_popup.go89
-rw-r--r--termui/show_bug.go6
-rw-r--r--termui/termui.go14
11 files changed, 247 insertions, 102 deletions
diff --git a/bug/bug_actions.go b/bug/bug_actions.go
index b2bc8b7d..30fc9876 100644
--- a/bug/bug_actions.go
+++ b/bug/bug_actions.go
@@ -13,25 +13,28 @@ const MsgMergeInvalid = "invalid data"
const MsgMergeUpdated = "updated"
const MsgMergeNothing = "nothing to do"
-func Fetch(repo repository.Repo, remote string) error {
+func Fetch(repo repository.Repo, remote string) (string, error) {
remoteRefSpec := fmt.Sprintf(bugsRemoteRefPattern, remote)
fetchRefSpec := fmt.Sprintf("%s*:%s*", bugsRefPattern, remoteRefSpec)
return repo.FetchRefs(remote, fetchRefSpec)
}
-func Push(repo repository.Repo, remote string) error {
+func Push(repo repository.Repo, remote string) (string, error) {
return repo.PushRefs(remote, bugsRefPattern+"*")
}
func Pull(repo repository.Repo, out io.Writer, remote string) error {
fmt.Fprintf(out, "Fetching remote ...\n")
- if err := Fetch(repo, remote); err != nil {
+ stdout, err := Fetch(repo, remote)
+ if err != nil {
return err
}
- fmt.Fprintf(out, "\nMerging data ...\n")
+ out.Write([]byte(stdout))
+
+ fmt.Fprintf(out, "Merging data ...\n")
for merge := range MergeAll(repo, remote) {
if merge.Err != nil {
@@ -42,6 +45,7 @@ func Pull(repo repository.Repo, out io.Writer, remote string) error {
fmt.Fprintf(out, "%s: %s\n", merge.HumanId, merge.Status)
}
}
+
return nil
}
diff --git a/cache/cache.go b/cache/cache.go
index da0a2681..c4177f75 100644
--- a/cache/cache.go
+++ b/cache/cache.go
@@ -2,6 +2,7 @@ package cache
import (
"fmt"
+ "io"
"strings"
"github.com/MichaelMure/git-bug/bug"
@@ -28,6 +29,10 @@ type RepoCacher interface {
// Mutations
NewBug(title string, message string) (BugCacher, error)
NewBugWithFiles(title string, message string, files []util.Hash) (BugCacher, error)
+ Fetch(remote string) (string, error)
+ MergeAll(remote string) <-chan bug.MergeResult
+ Pull(remote string, out io.Writer) error
+ Push(remote string) (string, error)
}
type BugCacher interface {
@@ -188,6 +193,22 @@ func (c *RepoCache) NewBugWithFiles(title string, message string, files []util.H
return cached, nil
}
+func (c *RepoCache) Fetch(remote string) (string, error) {
+ return bug.Fetch(c.repo, remote)
+}
+
+func (c *RepoCache) MergeAll(remote string) <-chan bug.MergeResult {
+ return bug.MergeAll(c.repo, remote)
+}
+
+func (c *RepoCache) Pull(remote string, out io.Writer) error {
+ return bug.Pull(c.repo, out, remote)
+}
+
+func (c *RepoCache) Push(remote string) (string, error) {
+ return bug.Push(c.repo, remote)
+}
+
// Bug ------------------------
type BugCache struct {
diff --git a/commands/push.go b/commands/push.go
index e2c29c68..86c37b46 100644
--- a/commands/push.go
+++ b/commands/push.go
@@ -2,6 +2,8 @@ package commands
import (
"errors"
+ "fmt"
+
"github.com/MichaelMure/git-bug/bug"
"github.com/spf13/cobra"
)
@@ -16,10 +18,13 @@ func runPush(cmd *cobra.Command, args []string) error {
remote = args[0]
}
- if err := bug.Push(repo, remote); err != nil {
+ stdout, err := bug.Push(repo, remote)
+ if err != nil {
return err
}
+ fmt.Println(stdout)
+
return nil
}
diff --git a/repository/git.go b/repository/git.go
index 5748d52a..dc071fdc 100644
--- a/repository/git.go
+++ b/repository/git.go
@@ -151,24 +151,24 @@ func (repo *GitRepo) GetCoreEditor() (string, error) {
}
// FetchRefs fetch git refs from a remote
-func (repo *GitRepo) FetchRefs(remote, refSpec string) error {
- err := repo.runGitCommandInline("fetch", remote, refSpec)
+func (repo *GitRepo) FetchRefs(remote, refSpec string) (string, error) {
+ stdout, err := repo.runGitCommand("fetch", remote, refSpec)
if err != nil {
- return fmt.Errorf("failed to fetch from the remote '%s': %v", remote, err)
+ return stdout, fmt.Errorf("failed to fetch from the remote '%s': %v", remote, err)
}
- return err
+ return stdout, err
}
// PushRefs push git refs to a remote
-func (repo *GitRepo) PushRefs(remote string, refSpec string) error {
- err := repo.runGitCommandInline("push", remote, refSpec)
+func (repo *GitRepo) PushRefs(remote string, refSpec string) (string, error) {
+ stdout, stderr, err := repo.runGitCommandRaw(nil, "push", remote, refSpec)
if err != nil {
- return fmt.Errorf("failed to push to the remote '%s': %v", remote, err)
+ return stdout + stderr, fmt.Errorf("failed to push to the remote '%s': %v", remote, err)
}
- return nil
+ return stdout + stderr, nil
}
// StoreData will store arbitrary data and return the corresponding hash
diff --git a/repository/mock_repo.go b/repository/mock_repo.go
index eb48f85f..20fb3d87 100644
--- a/repository/mock_repo.go
+++ b/repository/mock_repo.go
@@ -54,12 +54,12 @@ func (r *mockRepoForTest) GetCoreEditor() (string, error) {
}
// PushRefs push git refs to a remote
-func (r *mockRepoForTest) PushRefs(remote string, refSpec string) error {
- return nil
+func (r *mockRepoForTest) PushRefs(remote string, refSpec string) (string, error) {
+ return "", nil
}
-func (r *mockRepoForTest) FetchRefs(remote string, refSpec string) error {
- return nil
+func (r *mockRepoForTest) FetchRefs(remote string, refSpec string) (string, error) {
+ return "", nil
}
func (r *mockRepoForTest) StoreData(data []byte) (util.Hash, error) {
diff --git a/repository/repo.go b/repository/repo.go
index 057223ad..372a8066 100644
--- a/repository/repo.go
+++ b/repository/repo.go
@@ -22,10 +22,10 @@ type Repo interface {
GetCoreEditor() (string, error)
// FetchRefs fetch git refs from a remote
- FetchRefs(remote string, refSpec string) error
+ FetchRefs(remote string, refSpec string) (string, error)
// PushRefs push git refs to a remote
- PushRefs(remote string, refSpec string) error
+ PushRefs(remote string, refSpec string) (string, error)
// StoreData will store arbitrary data and return the corresponding hash
StoreData(data []byte) (util.Hash, error)
diff --git a/termui/bug_table.go b/termui/bug_table.go
index 1432d943..cd5ee1f6 100644
--- a/termui/bug_table.go
+++ b/termui/bug_table.go
@@ -1,7 +1,9 @@
package termui
import (
+ "bytes"
"fmt"
+
"github.com/MichaelMure/git-bug/bug"
"github.com/MichaelMure/git-bug/cache"
"github.com/MichaelMure/git-bug/util"
@@ -14,6 +16,8 @@ const bugTableHeaderView = "bugTableHeaderView"
const bugTableFooterView = "bugTableFooterView"
const bugTableInstructionView = "bugTableInstructionView"
+const remote = "origin"
+
type bugTable struct {
repo cache.RepoCacher
allIds []string
@@ -105,7 +109,7 @@ func (bt *bugTable) layout(g *gocui.Gui) error {
v.Frame = false
v.BgColor = gocui.ColorBlue
- fmt.Fprintf(v, "[q] Quit [←,h] Previous page [↓,j] Down [↑,k] Up [→,l] Next page [enter] Open bug [n] New bug")
+ fmt.Fprintf(v, "[q] Quit [←↓↑→,hjkl] Navigation [enter] Open bug [n] New bug [i] Pull [o] Push")
}
_, err = g.SetCurrentView(bugTableView)
@@ -176,6 +180,18 @@ func (bt *bugTable) keybindings(g *gocui.Gui) error {
return err
}
+ // Pull
+ if err := g.SetKeybinding(bugTableView, 'i', gocui.ModNone,
+ bt.pull); err != nil {
+ return err
+ }
+
+ // Push
+ if err := g.SetKeybinding(bugTableView, 'o', gocui.ModNone,
+ bt.push); err != nil {
+ return err
+ }
+
return nil
}
@@ -383,3 +399,85 @@ func (bt *bugTable) openBug(g *gocui.Gui, v *gocui.View) error {
ui.showBug.SetBug(bt.bugs[bt.pageCursor+y])
return ui.activateWindow(ui.showBug)
}
+
+func (bt *bugTable) pull(g *gocui.Gui, v *gocui.View) error {
+ // Note: this is very hacky
+
+ ui.msgPopup.Activate("Pull from remote "+remote, "...")
+
+ go func() {
+ stdout, err := bt.repo.Fetch(remote)
+
+ if err != nil {
+ g.Update(func(gui *gocui.Gui) error {
+ ui.msgPopup.Activate(msgPopupErrorTitle, err.Error())
+ return nil
+ })
+ } else {
+ g.Update(func(gui *gocui.Gui) error {
+ ui.msgPopup.UpdateMessage(stdout)
+ return nil
+ })
+ }
+
+ var buffer bytes.Buffer
+ beginLine := ""
+
+ for merge := range bt.repo.MergeAll(remote) {
+ if merge.Status == bug.MsgMergeNothing {
+ continue
+ }
+
+ if merge.Err != nil {
+ g.Update(func(gui *gocui.Gui) error {
+ ui.msgPopup.Activate(msgPopupErrorTitle, err.Error())
+ return nil
+ })
+ } else {
+ fmt.Fprintf(&buffer, "%s%s: %s",
+ beginLine, util.Cyan(merge.HumanId), merge.Status,
+ )
+
+ beginLine = "\n"
+
+ g.Update(func(gui *gocui.Gui) error {
+ ui.msgPopup.UpdateMessage(buffer.String())
+ return nil
+ })
+ }
+ }
+
+ fmt.Fprintf(&buffer, "%sdone", beginLine)
+
+ g.Update(func(gui *gocui.Gui) error {
+ ui.msgPopup.UpdateMessage(buffer.String())
+ return nil
+ })
+
+ }()
+
+ return nil
+}
+
+func (bt *bugTable) push(g *gocui.Gui, v *gocui.View) error {
+ ui.msgPopup.Activate("Push to remote "+remote, "...")
+
+ go func() {
+ // TODO: make the remote configurable
+ stdout, err := bt.repo.Push(remote)
+
+ if err != nil {
+ g.Update(func(gui *gocui.Gui) error {
+ ui.msgPopup.Activate(msgPopupErrorTitle, err.Error())
+ return nil
+ })
+ } else {
+ g.Update(func(gui *gocui.Gui) error {
+ ui.msgPopup.UpdateMessage(stdout)
+ return nil
+ })
+ }
+ }()
+
+ return nil
+}
diff --git a/termui/error_popup.go b/termui/error_popup.go
deleted file mode 100644
index f8f53feb..00000000
--- a/termui/error_popup.go
+++ /dev/null
@@ -1,72 +0,0 @@
-package termui
-
-import (
- "fmt"
-
- "github.com/MichaelMure/git-bug/util"
- "github.com/jroimartin/gocui"
-)
-
-const errorPopupView = "errorPopupView"
-
-type errorPopup struct {
- message string
-}
-
-func newErrorPopup() *errorPopup {
- return &errorPopup{
- message: "",
- }
-}
-
-func (ep *errorPopup) keybindings(g *gocui.Gui) error {
- if err := g.SetKeybinding(errorPopupView, gocui.KeySpace, gocui.ModNone, ep.close); err != nil {
- return err
- }
- if err := g.SetKeybinding(errorPopupView, gocui.KeyEnter, gocui.ModNone, ep.close); err != nil {
- return err
- }
-
- return nil
-}
-
-func (ep *errorPopup) layout(g *gocui.Gui) error {
- if ep.message == "" {
- return nil
- }
-
- maxX, maxY := g.Size()
-
- width := minInt(30, maxX)
- wrapped, nblines := util.WordWrap(ep.message, width-2)
- height := minInt(nblines+1, maxY)
- x0 := (maxX - width) / 2
- y0 := (maxY - height) / 2
-
- v, err := g.SetView(errorPopupView, x0, y0, x0+width, y0+height)
- if err != nil {
- if err != gocui.ErrUnknownView {
- return err
- }
-
- v.Frame = true
- v.Title = "Error"
-
- fmt.Fprintf(v, wrapped)
- }
-
- if _, err := g.SetCurrentView(errorPopupView); err != nil {
- return err
- }
-
- return nil
-}
-
-func (ep *errorPopup) close(g *gocui.Gui, v *gocui.View) error {
- ep.message = ""
- return g.DeleteView(errorPopupView)
-}
-
-func (ep *errorPopup) Activate(message string) {
- ep.message = message
-}
diff --git a/termui/msg_popup.go b/termui/msg_popup.go
new file mode 100644
index 00000000..dea24fb4
--- /dev/null
+++ b/termui/msg_popup.go
@@ -0,0 +1,89 @@
+package termui
+
+import (
+ "fmt"
+
+ "github.com/MichaelMure/git-bug/util"
+ "github.com/jroimartin/gocui"
+)
+
+const msgPopupView = "msgPopupView"
+
+const msgPopupErrorTitle = "Error"
+
+type msgPopup struct {
+ active bool
+ title string
+ message string
+}
+
+func newMsgPopup() *msgPopup {
+ return &msgPopup{
+ message: "",
+ }
+}
+
+func (ep *msgPopup) keybindings(g *gocui.Gui) error {
+ if err := g.SetKeybinding(msgPopupView, gocui.KeySpace, gocui.ModNone, ep.close); err != nil {
+ return err
+ }
+ if err := g.SetKeybinding(msgPopupView, gocui.KeyEnter, gocui.ModNone, ep.close); err != nil {
+ return err
+ }
+ if err := g.SetKeybinding(msgPopupView, 'q', gocui.ModNone, ep.close); err != nil {
+ return err
+ }
+
+ return nil
+}
+
+func (ep *msgPopup) layout(g *gocui.Gui) error {
+ if !ep.active {
+ return nil
+ }
+
+ maxX, maxY := g.Size()
+
+ width := minInt(60, maxX)
+ wrapped, lines := util.TextWrap(ep.message, width-2)
+ height := minInt(lines+1, maxY-3)
+ x0 := (maxX - width) / 2
+ y0 := (maxY - height) / 2
+
+ v, err := g.SetView(msgPopupView, x0, y0, x0+width, y0+height)
+ if err != nil {
+ if err != gocui.ErrUnknownView {
+ return err
+ }
+
+ v.Frame = true
+ v.Autoscroll = true
+ }
+
+ v.Title = ep.title
+
+ v.Clear()
+ fmt.Fprintf(v, wrapped)
+
+ if _, err := g.SetCurrentView(msgPopupView); err != nil {
+ return err
+ }
+
+ return nil
+}
+
+func (ep *msgPopup) close(g *gocui.Gui, v *gocui.View) error {
+ ep.active = false
+ ep.message = ""
+ return g.DeleteView(msgPopupView)
+}
+
+func (ep *msgPopup) Activate(title string, message string) {
+ ep.active = true
+ ep.title = title
+ ep.message = message
+}
+
+func (ep *msgPopup) UpdateMessage(message string) {
+ ep.message = message
+}
diff --git a/termui/show_bug.go b/termui/show_bug.go
index c0896e08..799af9c7 100644
--- a/termui/show_bug.go
+++ b/termui/show_bug.go
@@ -93,7 +93,7 @@ func (sb *showBug) layout(g *gocui.Gui) error {
}
v.Clear()
- fmt.Fprintf(v, "[q] Save and return [←,h] Left [↓,j] Down [↑,k] Up [→,l] Right ")
+ fmt.Fprintf(v, "[q] Save and return [←↓↑→,hjkl] Navigation ")
if sb.isOnSide {
fmt.Fprint(v, "[a] Add label [r] Remove label")
@@ -591,7 +591,7 @@ func (sb *showBug) addLabel(g *gocui.Gui, v *gocui.View) error {
err := sb.bug.ChangeLabels(trimLabels(labels), nil)
if err != nil {
- ui.errorPopup.Activate(err.Error())
+ ui.msgPopup.Activate(msgPopupErrorTitle, err.Error())
}
g.Update(func(gui *gocui.Gui) error {
@@ -614,7 +614,7 @@ func (sb *showBug) removeLabel(g *gocui.Gui, v *gocui.View) error {
err := sb.bug.ChangeLabels(nil, trimLabels(labels))
if err != nil {
- ui.errorPopup.Activate(err.Error())
+ ui.msgPopup.Activate(msgPopupErrorTitle, err.Error())
}
g.Update(func(gui *gocui.Gui) error {
diff --git a/termui/termui.go b/termui/termui.go
index 535842e4..b45bbc2e 100644
--- a/termui/termui.go
+++ b/termui/termui.go
@@ -19,7 +19,7 @@ type termUI struct {
bugTable *bugTable
showBug *showBug
- errorPopup *errorPopup
+ msgPopup *msgPopup
inputPopup *inputPopup
}
@@ -49,7 +49,7 @@ func Run(repo repository.Repo) error {
cache: c,
bugTable: newBugTable(c),
showBug: newShowBug(c),
- errorPopup: newErrorPopup(),
+ msgPopup: newMsgPopup(),
inputPopup: newInputPopup(),
}
@@ -106,7 +106,7 @@ func layout(g *gocui.Gui) error {
return err
}
- if err := ui.errorPopup.layout(g); err != nil {
+ if err := ui.msgPopup.layout(g); err != nil {
return err
}
@@ -131,7 +131,7 @@ func keybindings(g *gocui.Gui) error {
return err
}
- if err := ui.errorPopup.keybindings(g); err != nil {
+ if err := ui.msgPopup.keybindings(g); err != nil {
return err
}
@@ -166,7 +166,7 @@ func newBugWithEditor(repo cache.RepoCacher) error {
}
if err == input.ErrEmptyTitle {
- ui.errorPopup.Activate("Empty title, aborting.")
+ ui.msgPopup.Activate(msgPopupErrorTitle, "Empty title, aborting.")
} else {
_, err := repo.NewBug(title, message)
if err != nil {
@@ -199,7 +199,7 @@ func addCommentWithEditor(bug cache.BugCacher) error {
}
if err == input.ErrEmptyMessage {
- ui.errorPopup.Activate("Empty message, aborting.")
+ ui.msgPopup.Activate(msgPopupErrorTitle, "Empty message, aborting.")
} else {
err := bug.AddComment(message)
if err != nil {
@@ -232,7 +232,7 @@ func setTitleWithEditor(bug cache.BugCacher) error {
}
if err == input.ErrEmptyTitle {
- ui.errorPopup.Activate("Empty title, aborting.")
+ ui.msgPopup.Activate(msgPopupErrorTitle, "Empty title, aborting.")
} else {
err := bug.SetTitle(title)
if err != nil {