summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorIbrahim Serdar Acikgoz <serdaracikgoz86@gmail.com>2018-11-18 16:44:24 +0300
committerGitHub <noreply@github.com>2018-11-18 16:44:24 +0300
commit4e2ddf090fbd067cd7cc565d6bceba5d0d069f05 (patch)
tree4145a3d75a7768dc7efb210a29610656d8754f9a
parentUpdate README.md (diff)
parentupdate readme. (diff)
downloadgitbatch-4e2ddf090fbd067cd7cc565d6bceba5d0d069f05.tar.gz
Merge pull request #2 from isacikgoz/develop
Develop
-rw-r--r--README.md6
-rw-r--r--main.go57
-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
7 files changed, 386 insertions, 34 deletions
diff --git a/README.md b/README.md
index ece4e44..0578837 100644
--- a/README.md
+++ b/README.md
@@ -3,9 +3,13 @@
## gitbatch
Aim of this simple application to make your local repositories syncrhonized with remotes easily. This is still a work in progress application.
+clone the repository:
+```bash
+git clone https://github.com/isacikgoz/gitbatch
+```
use it like:
```bash
-go run main.go -d '/Users/ibrahim/git' -p gitbatch -r origin -b master
+go run main.go -d '/Users/ibrahim/git' -p gitbatch
```
to get help:
```bash
diff --git a/main.go b/main.go
index 509f9ab..b2d2b2d 100644
--- a/main.go
+++ b/main.go
@@ -1,53 +1,51 @@
package main
import (
+ "github.com/isacikgoz/gitbatch/pkg/app"
+ "github.com/isacikgoz/gitbatch/pkg/git"
+ "gopkg.in/alecthomas/kingpin.v2"
"io/ioutil"
"log"
- "strings"
"os"
- "gopkg.in/alecthomas/kingpin.v2"
- "gopkg.in/src-d/go-git.v4"
- "gopkg.in/src-d/go-git.v4/plumbing"
+ "strings"
)
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()
- branch = kingpin.Flag("branch", "branch to be pulled").Default("master").Short('b').String()
- remote = kingpin.Flag("remote", "remote name te be pulled").Default("origin").Short('r').String()
+ 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 []git.RepoEntity
)
func main() {
kingpin.Parse()
- FindRepos(*dir)
+ repositories = FindRepos(*dir)
+
+ app, err := app.Setup(repositories)
+ if err != nil {
+ log.Fatal(err)
+ }
+
+ defer app.Close()
}
-func FindRepos(directory string) []string {
- var gitRepositories []string
+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)
- if err !=nil {
+
+ entity, err := git.InitializeRepository(repo)
+ if err != nil {
continue
}
- // Get the working directory for the repository
- w, err := r.Worktree()
- CheckIfError(err)
-
- ref := plumbing.ReferenceName("refs/heads/" + *branch)
- err = w.Pull(&git.PullOptions{
- RemoteName: *remote,
- Progress: os.Stdout,
- ReferenceName: ref,
- })
- CheckIfError(err)
+ gitRepositories = append(gitRepositories, entity)
}
return gitRepositories
}
@@ -64,11 +62,4 @@ func FilterRepos(files []os.FileInfo) []os.FileInfo {
return filteredRepos
}
-// CheckIfError should be used to naively panics if an error is not nil.
-func CheckIfError(err error) {
- if err == nil {
- return
- }
- log.Fatal(err)
- os.Exit(1)
-}
+
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
+}
+