diff options
| author | Ibrahim Serdar Acikgoz <serdaracikgoz86@gmail.com> | 2018-11-19 01:00:04 +0300 |
|---|---|---|
| committer | GitHub <noreply@github.com> | 2018-11-19 01:00:04 +0300 |
| commit | 6c2d8551edc7bf79eb3902dcb876efb2c165dcc8 (patch) | |
| tree | efde6f6087638c7ea54f236257344bf28319124e | |
| parent | Merge pull request #2 from isacikgoz/develop (diff) | |
| parent | added more information on repositories (diff) | |
| download | gitbatch-6c2d8551edc7bf79eb3902dcb876efb2c165dcc8.tar.gz | |
Merge pull request #3 from isacikgoz/develop
added more information on repositories
| -rw-r--r-- | main.go | 21 | ||||
| -rw-r--r-- | pkg/app/app.go | 20 | ||||
| -rw-r--r-- | pkg/git/git-commands.go | 3 | ||||
| -rw-r--r-- | pkg/git/git.go | 28 | ||||
| -rw-r--r-- | pkg/gui/gui.go | 22 | ||||
| -rw-r--r-- | pkg/utils/utils.go | 216 |
6 files changed, 276 insertions, 34 deletions
@@ -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 +} |
