summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorIbrahim Serdar Acikgoz <serdaracikgoz86@gmail.com>2018-11-18 16:31:42 +0300
committerIbrahim Serdar Acikgoz <serdaracikgoz86@gmail.com>2018-11-18 16:31:42 +0300
commitfc396fa87810a31c032f13431eb1725b551c637d (patch)
tree4b2f2055d1578f54e7eb634f09ea2fe6a37f8b16
parentinitialized cli interface (diff)
downloadgitbatch-fc396fa87810a31c032f13431eb1725b551c637d.tar.gz
added user interface
-rw-r--r--main.go193
-rw-r--r--pkg/app/app.go38
-rw-r--r--pkg/command/command.go44
-rw-r--r--pkg/git/git-commands.go38
-rw-r--r--pkg/git/git.go41
-rw-r--r--pkg/gui/gui.go196
6 files changed, 366 insertions, 184 deletions
diff --git a/main.go b/main.go
index fa06408..b2d2b2d 100644
--- a/main.go
+++ b/main.go
@@ -1,66 +1,50 @@
package main
import (
- "fmt"
- "github.com/jroimartin/gocui"
+ "github.com/isacikgoz/gitbatch/pkg/app"
+ "github.com/isacikgoz/gitbatch/pkg/git"
"gopkg.in/alecthomas/kingpin.v2"
- "gopkg.in/src-d/go-git.v4"
"io/ioutil"
"log"
"os"
"strings"
)
-type RepoEntity struct {
- Name string
- Repository git.Repository
-}
-
var (
currentDir, err = os.Getwd()
dir = kingpin.Flag("directory", "Directory to roam for git repositories.").Default(currentDir).Short('d').String()
repoPattern = kingpin.Flag("pattern", "Pattern to filter repositories").Short('p').String()
- repositories []RepoEntity
- selectedRepoEntity RepoEntity
+ repositories []git.RepoEntity
)
func main() {
kingpin.Parse()
repositories = FindRepos(*dir)
- g, err := gocui.NewGui(gocui.OutputNormal)
+ app, err := app.Setup(repositories)
if err != nil {
log.Fatal(err)
}
- defer g.Close()
-
- g.SetManagerFunc(layout)
- if err := keybindings(g); err != nil {
- log.Fatal(err)
- }
-
- if err := g.MainLoop(); err != nil && err != gocui.ErrQuit {
- log.Fatal(err)
- }
+ defer app.Close()
}
-func FindRepos(directory string) []RepoEntity {
- var gitRepositories []RepoEntity
+func FindRepos(directory string) []git.RepoEntity {
+ var gitRepositories []git.RepoEntity
files, err := ioutil.ReadDir(directory)
if err != nil {
log.Fatal(err)
}
+
filteredFiles := FilterRepos(files)
for _, f := range filteredFiles {
repo := directory + "/" + f.Name()
- r, err := git.PlainOpen(repo)
+ entity, err := git.InitializeRepository(repo)
if err != nil {
continue
}
- entity := RepoEntity{f.Name(), *r}
gitRepositories = append(gitRepositories, entity)
}
return gitRepositories
@@ -78,163 +62,4 @@ func FilterRepos(files []os.FileInfo) []os.FileInfo {
return filteredRepos
}
-func layout(g *gocui.Gui) error {
- maxX, maxY := g.Size()
-
- if v, err := g.SetView("main", 0, 0, maxX-1, int(0.65*float32(maxY))-3); err != nil {
- if err != gocui.ErrUnknownView {
- return err
- }
- v.Title = " Matched Repositories "
- v.Highlight = true
- v.SelBgColor = gocui.ColorGreen
- v.SelFgColor = gocui.ColorBlack
-
- for _, r := range repositories {
- fmt.Fprintln(v, r.Name)
- }
-
- if _, err = setCurrentViewOnTop(g, "main"); err != nil {
- return err
- }
- }
-
- if v, err := g.SetView("detail", 0, int(0.65*float32(maxY))-2, maxX-1, maxY-2); err != nil {
- if err != gocui.ErrUnknownView {
- return err
- }
- v.Title = " Repository Detail "
- v.Wrap = true
- v.Autoscroll = true
- }
-
- if v, err := g.SetView("keybindings", -1, maxY-2, maxX, maxY); err != nil {
- if err != gocui.ErrUnknownView {
- return err
- }
- v.BgColor = gocui.ColorBlue
- v.FgColor = gocui.ColorYellow
- v.Frame = false
- fmt.Fprintln(v, "q: quit ↑ ↓: navigate space: select")
- }
- return nil
-}
-
-func keybindings(g *gocui.Gui) error {
- if err := g.SetKeybinding("", gocui.KeyCtrlC, gocui.ModNone, quit); err != nil {
- return err
- }
- if err := g.SetKeybinding("", 'q', gocui.ModNone, quit); err != nil {
- return err
- }
- if err := g.SetKeybinding("main", gocui.KeyArrowDown, gocui.ModNone, cursorDown); err != nil {
- return err
- }
- if err := g.SetKeybinding("main", gocui.KeyArrowUp, gocui.ModNone, cursorUp); err != nil {
- return err
- }
- if err := g.SetKeybinding("main", gocui.KeySpace, gocui.ModNone, markRepository); err != nil {
- return err
- }
- return nil
-}
-
-func cursorDown(g *gocui.Gui, v *gocui.View) error {
- if v != nil {
- cx, cy := v.Cursor()
- if err := v.SetCursor(cx, cy+1); err != nil {
- ox, oy := v.Origin()
- if err := v.SetOrigin(ox, oy+1); err != nil {
- return err
- }
- }
- updateDetail(g, v)
- }
- return nil
-}
-
-func cursorUp(g *gocui.Gui, v *gocui.View) error {
- if v != nil {
- ox, oy := v.Origin()
- cx, cy := v.Cursor()
- if err := v.SetCursor(cx, cy-1); err != nil && oy > 0 {
- if err := v.SetOrigin(ox, oy-1); err != nil {
- return err
- }
- }
- updateDetail(g, v)
- }
- return nil
-}
-
-func quit(g *gocui.Gui, v *gocui.View) error {
- return gocui.ErrQuit
-}
-
-func setCurrentViewOnTop(g *gocui.Gui, name string) (*gocui.View, error) {
- if _, err := g.SetCurrentView(name); err != nil {
- return nil, err
- }
- return g.SetViewOnTop(name)
-}
-
-func getSelectedRepository(g *gocui.Gui, v *gocui.View) (RepoEntity, error) {
- var l string
- var err error
- var r RepoEntity
-
- _, cy := v.Cursor()
- if l, err = v.Line(cy); err != nil {
- return r, err
- }
-
- for _, sr := range repositories {
- if l == sr.Name {
- return sr, nil
- }
- }
- return r, err
-}
-func updateDetail(g *gocui.Gui, v *gocui.View) error {
- var err error
-
- _, cy := v.Cursor()
- if _, err = v.Line(cy); err != nil {
- return err
- }
-
- out, err := g.View("detail")
- if err != nil {
- return err
- }
-
- out.Clear()
-
- if repo, err := getSelectedRepository(g, v); err != nil {
- return err
- } else {
- if list, err := repo.Repository.Remotes(); err != nil {
- return err
- } else {
- for _, r := range list {
- fmt.Fprintln(out, r)
- }
- }
- }
- return nil
-}
-
-func markRepository(g *gocui.Gui, v *gocui.View) error {
- var l string
- var err error
-
- _, cy := v.Cursor()
- if l, err = v.Line(cy); err != nil {
- return err
- } else {
- l = l + " X"
- }
-
- return nil
-}
diff --git a/pkg/app/app.go b/pkg/app/app.go
new file mode 100644
index 0000000..9d6a0e6
--- /dev/null
+++ b/pkg/app/app.go
@@ -0,0 +1,38 @@
+package app
+
+import (
+ "github.com/isacikgoz/gitbatch/pkg/gui"
+ "github.com/isacikgoz/gitbatch/pkg/git"
+ "io"
+)
+
+// App struct
+type App struct {
+ closers []io.Closer
+}
+
+// Setup bootstrap a new application
+func Setup(repositories []git.RepoEntity) (*App, error) {
+ app := &App{
+ closers: []io.Closer{},
+ }
+
+ var err error
+
+ err = gui.Run(repositories)
+ if err != nil {
+ return app, err
+ }
+ return app, nil
+}
+
+// Close closes any resources
+func (app *App) Close() error {
+ for _, closer := range app.closers {
+ err := closer.Close()
+ if err != nil {
+ return err
+ }
+ }
+ return nil
+}
diff --git a/pkg/command/command.go b/pkg/command/command.go
new file mode 100644
index 0000000..2c5a0c3
--- /dev/null
+++ b/pkg/command/command.go
@@ -0,0 +1,44 @@
+package command
+
+import (
+ "os/exec"
+ "syscall"
+ "log"
+)
+
+func RunCommandWithOutput(dir string, command string, args []string) (string, error) {
+ cmd := exec.Command(command, args...)
+ if dir != "" {
+ cmd.Dir = dir
+ }
+ output, err := cmd.Output()
+ return string(output), err
+}
+
+func GetCommandStatus(dir string, command string, args []string) (int, error) {
+ cmd := exec.Command(command, args...)
+ if dir != "" {
+ cmd.Dir = dir
+ }
+ var err error
+ if err := cmd.Start(); err != nil {
+ return -1, err
+ }
+ if err := cmd.Wait(); err != nil {
+ if exiterr, ok := err.(*exec.ExitError); ok {
+ // The program has exited with an exit code != 0
+
+ // This works on both Unix and Windows. Although package
+ // syscall is generally platform dependent, WaitStatus is
+ // defined for both Unix and Windows and in both cases has
+ // an ExitStatus() method with the same signature.
+ if status, ok := exiterr.Sys().(syscall.WaitStatus); ok {
+ statusCode := status.ExitStatus()
+ return statusCode, err
+ }
+ } else {
+ log.Fatalf("cmd.Wait: %v", err)
+ }
+ }
+ return -1, err
+}
diff --git a/pkg/git/git-commands.go b/pkg/git/git-commands.go
new file mode 100644
index 0000000..beb6936
--- /dev/null
+++ b/pkg/git/git-commands.go
@@ -0,0 +1,38 @@
+package git
+
+import (
+ "strings"
+ "github.com/isacikgoz/gitbatch/pkg/command"
+)
+
+// UpstreamDifferenceCount checks how many pushables/pullables there are for the
+// current branch
+func UpstreamDifferenceCount(repoPath string) (string, string) {
+ args := []string{"rev-list", "@{u}..HEAD", "--count"}
+ pushableCount, err := command.RunCommandWithOutput(repoPath, "git", args)
+ if err != nil {
+ return "?", "?"
+ }
+ args = []string{"rev-list", "HEAD..@{u}", "--count"}
+ pullableCount, err := command.RunCommandWithOutput(repoPath, "git", args)
+ if err != nil {
+ return "?", "?"
+ }
+ return strings.TrimSpace(pushableCount), strings.TrimSpace(pullableCount)
+}
+
+func CurrentBranchName(repoPath string) (string, error) {
+ args := []string{"symbolic-ref", "--short", "HEAD"}
+ branchName, err := command.RunCommandWithOutput(repoPath, "git", args)
+ if err != nil {
+ args = []string{"rev-parse", "--short", "HEAD"}
+ branchName, err = command.RunCommandWithOutput(repoPath, "git", args)
+ if err != nil {
+ return "", err
+ }
+ }
+ return branchName, nil
+}
+
+
+
diff --git a/pkg/git/git.go b/pkg/git/git.go
new file mode 100644
index 0000000..c96fc96
--- /dev/null
+++ b/pkg/git/git.go
@@ -0,0 +1,41 @@
+package git
+
+import (
+ "gopkg.in/src-d/go-git.v4"
+)
+
+type RepoEntity struct {
+ Name string
+ Repository git.Repository
+ Pushables string
+ Pullables string
+ Branch string
+}
+
+func InitializeRepository(directory string) (RepoEntity, error) {
+ var entity RepoEntity
+
+ r, err := git.PlainOpen(directory)
+ if err != nil {
+ return entity, err
+ }
+ entity = RepoEntity{directory, *r, "", "", ""}
+
+ return entity, nil
+}
+
+func InitializeRepositories(directories []string) []RepoEntity {
+ var gitRepositories []RepoEntity
+ for _, f := range directories {
+ r, err := git.PlainOpen(f)
+ if err != nil {
+ continue
+ }
+ entity := RepoEntity{f, *r, "", "", ""}
+ gitRepositories = append(gitRepositories, entity)
+ }
+ return gitRepositories
+}
+
+
+
diff --git a/pkg/gui/gui.go b/pkg/gui/gui.go
new file mode 100644
index 0000000..1d32012
--- /dev/null
+++ b/pkg/gui/gui.go
@@ -0,0 +1,196 @@
+package gui
+
+import (
+ "github.com/isacikgoz/gitbatch/pkg/git"
+ "github.com/jroimartin/gocui"
+ "fmt"
+)
+
+// Gui wraps the gocui Gui object which handles rendering and events
+var (
+ repositories []git.RepoEntity
+)
+
+func layout(g *gocui.Gui) error {
+ maxX, maxY := g.Size()
+
+ if v, err := g.SetView("main", 0, 0, maxX-1, int(0.65*float32(maxY))-3); err != nil {
+ if err != gocui.ErrUnknownView {
+ return err
+ }
+ v.Title = " Matched Repositories "
+ v.Highlight = true
+ v.SelBgColor = gocui.ColorGreen
+ v.SelFgColor = gocui.ColorBlack
+
+ for _, r := range repositories {
+ fmt.Fprintln(v, r.Name)
+ }
+
+ if _, err = setCurrentViewOnTop(g, "main"); err != nil {
+ return err
+ }
+ }
+
+ if v, err := g.SetView("detail", 0, int(0.65*float32(maxY))-2, maxX-1, maxY-2); err != nil {
+ if err != gocui.ErrUnknownView {
+ return err
+ }
+ v.Title = " Repository Detail "
+ v.Wrap = true
+ v.Autoscroll = true
+ }
+
+ if v, err := g.SetView("keybindings", -1, maxY-2, maxX, maxY); err != nil {
+ if err != gocui.ErrUnknownView {
+ return err
+ }
+ v.BgColor = gocui.ColorBlue
+ v.FgColor = gocui.ColorYellow
+ v.Frame = false
+ fmt.Fprintln(v, "q: quit ↑ ↓: navigate space: select")
+ }
+ return nil
+}
+
+func keybindings(g *gocui.Gui) error {
+ if err := g.SetKeybinding("", gocui.KeyCtrlC, gocui.ModNone, quit); err != nil {
+ return err
+ }
+ if err := g.SetKeybinding("", 'q', gocui.ModNone, quit); err != nil {
+ return err
+ }
+ if err := g.SetKeybinding("main", gocui.KeyArrowDown, gocui.ModNone, cursorDown); err != nil {
+ return err
+ }
+ if err := g.SetKeybinding("main", gocui.KeyArrowUp, gocui.ModNone, cursorUp); err != nil {
+ return err
+ }
+ if err := g.SetKeybinding("main", gocui.KeySpace, gocui.ModNone, markRepository); err != nil {
+ return err
+ }
+ return nil
+}
+
+func cursorDown(g *gocui.Gui, v *gocui.View) error {
+ if v != nil {
+ cx, cy := v.Cursor()
+ if err := v.SetCursor(cx, cy+1); err != nil {
+ ox, oy := v.Origin()
+ if err := v.SetOrigin(ox, oy+1); err != nil {
+ return err
+ }
+ }
+ updateDetail(g, v)
+ }
+ return nil
+}
+
+func cursorUp(g *gocui.Gui, v *gocui.View) error {
+ if v != nil {
+ ox, oy := v.Origin()
+ cx, cy := v.Cursor()
+ if err := v.SetCursor(cx, cy-1); err != nil && oy > 0 {
+ if err := v.SetOrigin(ox, oy-1); err != nil {
+ return err
+ }
+ }
+ updateDetail(g, v)
+ }
+ return nil
+}
+
+func quit(g *gocui.Gui, v *gocui.View) error {
+ return gocui.ErrQuit
+}
+
+func setCurrentViewOnTop(g *gocui.Gui, name string) (*gocui.View, error) {
+ if _, err := g.SetCurrentView(name); err != nil {
+ return nil, err
+ }
+ return g.SetViewOnTop(name)
+}
+
+func getSelectedRepository(g *gocui.Gui, v *gocui.View) (git.RepoEntity, error) {
+ var l string
+ var err error
+ var r git.RepoEntity
+
+ _, cy := v.Cursor()
+ if l, err = v.Line(cy); err != nil {
+ return r, err
+ }
+
+ for _, sr := range repositories {
+ if l == sr.Name {
+ return sr, nil
+ }
+ }
+ return r, err
+}
+
+func updateDetail(g *gocui.Gui, v *gocui.View) error {
+ var err error
+
+ _, cy := v.Cursor()
+ if _, err = v.Line(cy); err != nil {
+ return err
+ }
+
+ out, err := g.View("detail")
+ if err != nil {
+ return err
+ }
+
+ out.Clear()
+
+ if repo, err := getSelectedRepository(g, v); err != nil {
+ return err
+ } else {
+ if list, err := repo.Repository.Remotes(); err != nil {
+ return err
+ } else {
+ fmt.Fprintln(out, repo.Name)
+ for _, r := range list {
+ fmt.Fprintln(out, r)
+ }
+ }
+ }
+ return nil
+}
+
+func markRepository(g *gocui.Gui, v *gocui.View) error {
+ var l string
+ var err error
+
+ _, cy := v.Cursor()
+ if l, err = v.Line(cy); err != nil {
+ return err
+ } else {
+ l = l + " X"
+ }
+
+ return nil
+}
+
+// Run setup the gui with keybindings and start the mainloop
+func Run(repos []git.RepoEntity) error {
+ repositories = repos
+ g, err := gocui.NewGui(gocui.OutputNormal)
+ if err != nil {
+ return err
+ }
+ defer g.Close()
+
+ g.SetManagerFunc(layout)
+
+ if err := keybindings(g); err != nil {
+ return err
+ }
+
+ if err := g.MainLoop(); err != nil && err != gocui.ErrQuit {
+ return err
+ }
+ return nil
+}
+