summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorIbrahim Serdar Acikgoz <serdaracikgoz86@gmail.com>2018-11-19 01:00:04 +0300
committerGitHub <noreply@github.com>2018-11-19 01:00:04 +0300
commit6c2d8551edc7bf79eb3902dcb876efb2c165dcc8 (patch)
treeefde6f6087638c7ea54f236257344bf28319124e
parentMerge pull request #2 from isacikgoz/develop (diff)
parentadded more information on repositories (diff)
downloadgitbatch-6c2d8551edc7bf79eb3902dcb876efb2c165dcc8.tar.gz
Merge pull request #3 from isacikgoz/develop
added more information on repositories
-rw-r--r--main.go21
-rw-r--r--pkg/app/app.go20
-rw-r--r--pkg/git/git-commands.go3
-rw-r--r--pkg/git/git.go28
-rw-r--r--pkg/gui/gui.go22
-rw-r--r--pkg/utils/utils.go216
6 files changed, 276 insertions, 34 deletions
diff --git a/main.go b/main.go
index b2d2b2d..41da7c7 100644
--- a/main.go
+++ b/main.go
@@ -2,11 +2,11 @@ package main
import (
"github.com/isacikgoz/gitbatch/pkg/app"
- "github.com/isacikgoz/gitbatch/pkg/git"
"gopkg.in/alecthomas/kingpin.v2"
"io/ioutil"
"log"
"os"
+ "path/filepath"
"strings"
)
@@ -14,12 +14,11 @@ 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 []git.RepoEntity
)
func main() {
kingpin.Parse()
- repositories = FindRepos(*dir)
+ repositories := FindRepos(*dir)
app, err := app.Setup(repositories)
if err != nil {
@@ -29,8 +28,7 @@ func main() {
defer app.Close()
}
-func FindRepos(directory string) []git.RepoEntity {
- var gitRepositories []git.RepoEntity
+func FindRepos(directory string) (directories []string) {
files, err := ioutil.ReadDir(directory)
if err != nil {
@@ -39,15 +37,18 @@ func FindRepos(directory string) []git.RepoEntity {
filteredFiles := FilterRepos(files)
for _, f := range filteredFiles {
- repo := directory + "/" + f.Name()
-
- entity, err := git.InitializeRepository(repo)
+ repo := directory + string(os.PathSeparator) + f.Name()
+ file, err := os.Open(repo)
if err != nil {
continue
}
- gitRepositories = append(gitRepositories, entity)
+ dir, err := filepath.Abs(file.Name())
+ if err != nil {
+ log.Fatal(err)
+ }
+ directories = append(directories, dir)
}
- return gitRepositories
+ return directories
}
func FilterRepos(files []os.FileInfo) []os.FileInfo {
diff --git a/pkg/app/app.go b/pkg/app/app.go
index 9d6a0e6..3185c75 100644
--- a/pkg/app/app.go
+++ b/pkg/app/app.go
@@ -12,14 +12,17 @@ type App struct {
}
// Setup bootstrap a new application
-func Setup(repositories []git.RepoEntity) (*App, error) {
+func Setup(directories []string) (*App, error) {
app := &App{
closers: []io.Closer{},
}
- var err error
+ entities, err := createRepositoryEntities(directories)
+ if err != nil {
+ return app, err
+ }
- err = gui.Run(repositories)
+ err = gui.Run(entities)
if err != nil {
return app, err
}
@@ -36,3 +39,14 @@ func (app *App) Close() error {
}
return nil
}
+
+func createRepositoryEntities(directories []string) (entities []git.RepoEntity, err error) {
+ for _, dir := range directories {
+ entity, err := git.InitializeRepository(dir)
+ if err != nil {
+ continue
+ }
+ entities = append(entities, entity)
+ }
+ return entities, nil
+}
diff --git a/pkg/git/git-commands.go b/pkg/git/git-commands.go
index beb6936..1cf6fb7 100644
--- a/pkg/git/git-commands.go
+++ b/pkg/git/git-commands.go
@@ -3,6 +3,7 @@ package git
import (
"strings"
"github.com/isacikgoz/gitbatch/pkg/command"
+ "github.com/isacikgoz/gitbatch/pkg/utils"
)
// UpstreamDifferenceCount checks how many pushables/pullables there are for the
@@ -31,7 +32,7 @@ func CurrentBranchName(repoPath string) (string, error) {
return "", err
}
}
- return branchName, nil
+ return utils.TrimTrailingNewline(branchName), nil
}
diff --git a/pkg/git/git.go b/pkg/git/git.go
index c96fc96..86cc02d 100644
--- a/pkg/git/git.go
+++ b/pkg/git/git.go
@@ -2,10 +2,12 @@ package git
import (
"gopkg.in/src-d/go-git.v4"
+ "os"
)
type RepoEntity struct {
Name string
+ AbsPath string
Repository git.Repository
Pushables string
Pullables string
@@ -14,28 +16,24 @@ type RepoEntity struct {
func InitializeRepository(directory string) (RepoEntity, error) {
var entity RepoEntity
-
+ file, err := os.Open(directory)
+ if err != nil {
+ return entity, err
+ }
+ fileInfo, err := file.Stat()
+ if err != nil {
+ return entity, err
+ }
r, err := git.PlainOpen(directory)
if err != nil {
return entity, err
}
- entity = RepoEntity{directory, *r, "", "", ""}
+ pushable, pullable := UpstreamDifferenceCount(directory)
+ branch, err := CurrentBranchName(directory)
+ entity = RepoEntity{fileInfo.Name(), directory, *r, pushable, pullable, branch}
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
index 1d32012..fcbe9b6 100644
--- a/pkg/gui/gui.go
+++ b/pkg/gui/gui.go
@@ -75,13 +75,23 @@ func keybindings(g *gocui.Gui) error {
func cursorDown(g *gocui.Gui, v *gocui.View) error {
if v != nil {
cx, cy := v.Cursor()
+ ox, oy := v.Origin()
+
+ ly := len(repositories) -1
+
+ // if we are at the end we just return
+ if cy+oy == ly {
+ return nil
+ }
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)
+ if err := updateDetail(g, v); err != nil {
+ return err
+ }
}
return nil
}
@@ -95,7 +105,9 @@ func cursorUp(g *gocui.Gui, v *gocui.View) error {
return err
}
}
- updateDetail(g, v)
+ if err := updateDetail(g, v); err != nil {
+ return err
+ }
}
return nil
}
@@ -145,12 +157,12 @@ func updateDetail(g *gocui.Gui, v *gocui.View) error {
out.Clear()
if repo, err := getSelectedRepository(g, v); err != nil {
- return err
+ out.Clear()
} else {
if list, err := repo.Repository.Remotes(); err != nil {
return err
} else {
- fmt.Fprintln(out, repo.Name)
+ fmt.Fprintln(out, "↑" + repo.Pushables + " ↓" + repo.Pullables + " → " + repo.Branch)
for _, r := range list {
fmt.Fprintln(out, r)
}
diff --git a/pkg/utils/utils.go b/pkg/utils/utils.go
new file mode 100644
index 0000000..8e481b3
--- /dev/null
+++ b/pkg/utils/utils.go
@@ -0,0 +1,216 @@
+package utils
+
+import (
+ "errors"
+ "fmt"
+ "log"
+ "os"
+ "path/filepath"
+ "reflect"
+ "strings"
+ "time"
+
+ "github.com/fatih/color"
+)
+
+// SplitLines takes a multiline string and splits it on newlines
+// currently we are also stripping \r's which may have adverse effects for
+// windows users (but no issues have been raised yet)
+func SplitLines(multilineString string) []string {
+ multilineString = strings.Replace(multilineString, "\r", "", -1)
+ if multilineString == "" || multilineString == "\n" {
+ return make([]string, 0)
+ }
+ lines := strings.Split(multilineString, "\n")
+ if lines[len(lines)-1] == "" {
+ return lines[:len(lines)-1]
+ }
+ return lines
+}
+
+// WithPadding pads a string as much as you want
+func WithPadding(str string, padding int) string {
+ if padding-len(str) < 0 {
+ return str
+ }
+ return str + strings.Repeat(" ", padding-len(str))
+}
+
+// ColoredString takes a string and a colour attribute and returns a colored
+// string with that attribute
+func ColoredString(str string, colorAttribute color.Attribute) string {
+ colour := color.New(colorAttribute)
+ return ColoredStringDirect(str, colour)
+}
+
+// ColoredStringDirect used for aggregating a few color attributes rather than
+// just sending a single one
+func ColoredStringDirect(str string, colour *color.Color) string {
+ return colour.SprintFunc()(fmt.Sprint(str))
+}
+
+// GetCurrentRepoName gets the repo's base name
+func GetCurrentRepoName() string {
+ pwd, err := os.Getwd()
+ if err != nil {
+ log.Fatalln(err.Error())
+ }
+ return filepath.Base(pwd)
+}
+
+// TrimTrailingNewline - Trims the trailing newline
+// TODO: replace with `chomp` after refactor
+func TrimTrailingNewline(str string) string {
+ if strings.HasSuffix(str, "\n") {
+ return str[:len(str)-1]
+ }
+ return str
+}
+
+// NormalizeLinefeeds - Removes all Windows and Mac style line feeds
+func NormalizeLinefeeds(str string) string {
+ str = strings.Replace(str, "\r\n", "\n", -1)
+ str = strings.Replace(str, "\r", "", -1)
+ return str
+}
+
+// GetProjectRoot returns the path to the root of the project. Only to be used
+// in testing contexts, as with binaries it's unlikely this path will exist on
+// the machine
+func GetProjectRoot() string {
+ dir, err := os.Getwd()
+ if err != nil {
+ panic(err)
+ }
+ return strings.Split(dir, "lazygit")[0] + "lazygit"
+}
+
+// Loader dumps a string to be displayed as a loader
+func Loader() string {
+ characters := "|/-\\"
+ now := time.Now()
+ nanos := now.UnixNano()
+ index := nanos / 50000000 % int64(len(characters))
+ return characters[index : index+1]
+}
+
+// ResolvePlaceholderString populates a template with values
+func ResolvePlaceholderString(str string, arguments map[string]string) string {
+ for key, value := range arguments {
+ str = strings.Replace(str, "{{"+key+"}}", value, -1)
+ }
+ return str
+}
+
+// Min returns the minimum of two integers
+func Min(x, y int) int {
+ if x < y {
+ return x
+ }
+ return y
+}
+
+type Displayable interface {
+ GetDisplayStrings() []string
+}
+
+// RenderList takes a slice of items, confirms they implement the Displayable
+// interface, then generates a list of their displaystrings to write to a panel's
+// buffer
+func RenderList(slice interface{}) (string, error) {
+ s := reflect.ValueOf(slice)
+ if s.Kind() != reflect.Slice {
+ return "", errors.New("RenderList given a non-slice type")
+ }
+
+ displayables := make([]Displayable, s.Len())
+
+ for i := 0; i < s.Len(); i++ {
+ value, ok := s.Index(i).Interface().(Displayable)
+ if !ok {
+ return "", errors.New("item does not implement the Displayable interface")
+ }
+ displayables[i] = value
+ }
+
+ return renderDisplayableList(displayables)
+}
+
+// renderDisplayableList takes a list of displayable items, obtains their display
+// strings via GetDisplayStrings() and then returns a single string containing
+// each item's string representation on its own line, with appropriate horizontal
+// padding between the item's own strings
+func renderDisplayableList(items []Displayable) (string, error) {
+ if len(items) == 0 {
+ return "", nil
+ }
+
+ stringArrays := getDisplayStringArrays(items)
+
+ if !displayArraysAligned(stringArrays) {
+ return "", errors.New("Each item must return the same number of strings to display")
+ }
+
+ padWidths := getPadWidths(stringArrays)
+ paddedDisplayStrings := getPaddedDisplayStrings(stringArrays, padWidths)
+
+ return strings.Join(paddedDisplayStrings, "\n"), nil
+}
+
+func getPadWidths(stringArrays [][]string) []int {
+ if len(stringArrays[0]) <= 1 {
+ return []int{}
+ }
+ padWidths := make([]int, len(stringArrays[0])-1)
+ for i := range padWidths {
+ for _, strings := range stringArrays {
+ if len(strings[i]) > padWidths[i] {
+ padWidths[i] = len(strings[i])
+ }
+ }
+ }
+ return padWidths
+}
+
+func getPaddedDisplayStrings(stringArrays [][]string, padWidths []int) []string {
+ paddedDisplayStrings := make([]string, len(stringArrays))
+ for i, stringArray := range stringArrays {
+ if len(stringArray) == 0 {
+ continue
+ }
+ for j, padWidth := range padWidths {
+ paddedDisplayStrings[i] += WithPadding(stringArray[j], padWidth) + " "
+ }
+ paddedDisplayStrings[i] += stringArray[len(padWidths)]
+ }
+ return paddedDisplayStrings
+}
+
+// displayArraysAligned returns true if every string array returned from our
+// list of displayables has the same length
+func displayArraysAligned(stringArrays [][]string) bool {
+ for _, strings := range stringArrays {
+ if len(strings) != len(stringArrays[0]) {
+ return false
+ }
+ }
+ return true
+}
+
+func getDisplayStringArrays(displayables []Displayable) [][]string {
+ stringArrays := make([][]string, len(displayables))
+ for i, item := range displayables {
+ stringArrays[i] = item.GetDisplayStrings()
+ }
+ return stringArrays
+}
+
+// IncludesString if the list contains the string
+func IncludesString(list []string, a string) bool {
+ for _, b := range list {
+ if b == a {
+ return true
+ }
+ }
+ return false
+}