diff options
| author | Ibrahim Serdar Acikgoz <serdaracikgoz86@gmail.com> | 2018-11-18 16:31:42 +0300 |
|---|---|---|
| committer | Ibrahim Serdar Acikgoz <serdaracikgoz86@gmail.com> | 2018-11-18 16:31:42 +0300 |
| commit | fc396fa87810a31c032f13431eb1725b551c637d (patch) | |
| tree | 4b2f2055d1578f54e7eb634f09ea2fe6a37f8b16 | |
| parent | initialized cli interface (diff) | |
| download | gitbatch-fc396fa87810a31c032f13431eb1725b551c637d.tar.gz | |
added user interface
| -rw-r--r-- | main.go | 193 | ||||
| -rw-r--r-- | pkg/app/app.go | 38 | ||||
| -rw-r--r-- | pkg/command/command.go | 44 | ||||
| -rw-r--r-- | pkg/git/git-commands.go | 38 | ||||
| -rw-r--r-- | pkg/git/git.go | 41 | ||||
| -rw-r--r-- | pkg/gui/gui.go | 196 |
6 files changed, 366 insertions, 184 deletions
@@ -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 +} + |
