summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorIbrahim Serdar Acikgoz <serdaracikgoz86@gmail.com>2018-12-05 02:19:41 +0300
committerGitHub <noreply@github.com>2018-12-05 02:19:41 +0300
commit5c481dfe96fc40b8b7ad4fdc63fa0968e6832939 (patch)
tree6442b45aeac0e88e83656e871c0e0872d59e4044
parentDevelop (#20) (diff)
downloadgitbatch-5c481dfe96fc40b8b7ad4fdc63fa0968e6832939.tar.gz
Develop (#23)
* restyle on repository selection * restyle continued on selections * added initial logging to git package * reverse scrolling on commits implemented * queue converted to first in first out principle * added sorting * select all feature added * code reuse improved and added scrolling to controls view * update readme
-rw-r--r--README.md46
-rw-r--r--pkg/git/repository-sort.go59
-rw-r--r--pkg/git/repository.go10
-rw-r--r--pkg/gui/cheatsheet.go3
-rw-r--r--pkg/gui/diffview.go33
-rw-r--r--pkg/gui/gui-util.go58
-rw-r--r--pkg/gui/keybindings.go72
-rw-r--r--pkg/gui/mainview.go92
-rw-r--r--pkg/queue/queue.go4
9 files changed, 278 insertions, 99 deletions
diff --git a/README.md b/README.md
index ba70ce8..4b156ae 100644
--- a/README.md
+++ b/README.md
@@ -1,15 +1,15 @@
[![Build Status](https://travis-ci.com/isacikgoz/gitbatch.svg?branch=master)](https://travis-ci.com/isacikgoz/gitbatch) [![MIT License](https://img.shields.io/badge/license-MIT-brightgreen.svg)](/LICENSE) [![Go Report Card](https://goreportcard.com/badge/github.com/isacikgoz/gitbatch)](https://goreportcard.com/report/github.com/isacikgoz/gitbatch)
## gitbatch
-Aim of this tool to make your local repositories synchronized with remotes easily. Since my daily work is tied to many repositories I often end up walking on many directories and manually pulling updates etc. To make this routine more elegant, I created a simple tool to handle this job. I really enjoy working on this project and I hope it will be a useful tool.
+Aim of this tool to make your local repositories synchronized with remotes easily. I inspired from lazygit and decided to build this project for my needs; Since my daily work is tied to many repositories I often end up walking on many directories and manually pulling updates etc. To make this routine faster, I created a simple tool to handle this job. I really enjoy working on this project and I hope it will be a useful tool.
**Disclaimer**
- Authentication required repositories are **not supported** using ssh is recommended if you need to authenticate to fetch/pull
- [Connecting to GitHub with SSH](https://help.github.com/articles/connecting-to-github-with-ssh/)
- [GitLab and SSH keys](https://docs.gitlab.com/ee/ssh/)
- [BitBucket Set up an SSH key](https://confluence.atlassian.com/bitbucket/set-up-ssh-for-git-728138079.html)
-- Some strange behavior is expected and feedbacks are welcome. For now, known issues are:
- - At very low probability app fails to load repositories, try again it will open next time (multithreading problem)
+- Feedbacks are welcome. For now, known issues are:
+ - At very low probability app fails to load repositories, try again it will load next time (multithreading problem)
- Sometimes when you scroll too fast while pulling/fetching/merging, some multithreading problem occurs and app crashes (will fix soon)
- colors vary to your terminal theme colors, so if the contrast is not enough on some color decisions; discussions are welcome
@@ -36,49 +36,21 @@ run the `gitbatch` command from the parent of your git repositories. For start-u
### Controls
-- **tab**: Switch mode
-- **↑** or **k**: Up
-- **↓** or **j**: Down
-- **b**: Iterate over branches
-- **r**: Iterate over remotes
-- **e**: Iterate over remote branches
-- **s**: Iterate over commits
-- **d**: Show commit diff
-- **c**: Controls or close windows if a pop-up window opened
-- **enter**: Start queue
-- **space**: Add to queue
-- **ctrl + c**: Force application to quit
-- **q**: Quit
+Please see [Controls page](https://github.com/isacikgoz/gitbatch/wiki/Controls)
### Modes
-- **FETCH**: fetches the selected **remote** e.g. *Origin*, *Upstream*, etc.
-- **PULL**: fetches the selected **remote** and merges selected **remote branch** into **active branch** e.g. origin/master → master
-- **MERGE**: merges the selected **remote branch** into **active branch** e.g. origin/master → master
+Please see [Modes page](https://github.com/isacikgoz/gitbatch/wiki/Modes)
### Display
-#### Repository Screen
-↖ 0 ↘ 0 → master ✗ ips.server.slave.native •
-↖ (pushables) ↘ (pullables) → (branch) (✗ if dirty) (repository folder name) (• if queued)
-
-- if pushables or pullables appear to be "**?**", that means no upstream configured for the active branch
- - to resolve this issue;
- 1. change directory the repository and checkout to the branch prints **?** on upstream
- 2. make sure remote is set by running `git remote -v`
- 3. run `git config --local --add branch.<your branch name>.remote=<your remote name>`
- 4. run `git config --local --add branch.<your branch name>.merge=refs/heads/<your branch name>`
-- the queued indicator color represents the operation same as mode color
-
-#### Commit Screen
-- if hash color is cyan it means that commit is a local commit, if yellow it means it is a commit that will merge in to your active branch if you pull or merge
-- you can see the diff by simply pressing **d** on the selected commit
+Please see [Display page](https://github.com/isacikgoz/gitbatch/wiki/Display)
## Further goals
- add testing
-- select all feature
-- arrange repositories to an order e.g. alphabetic, last modified, etc.
-- shift keys, i.e. **s** for iterate **alt + s** for reverse iteration (partially implemented)
+- select all feature ✔
+- arrange repositories to an order e.g. alphabetic, last modified, etc. ✔
+- shift keys, i.e. **s** for iterate **alt + s** for reverse iteration ✔
- recursive repository search from the filesystem
- full src-d/go-git integration (*having some performance issues*)
- implement config file to pre-define repo locations or some settings
diff --git a/pkg/git/repository-sort.go b/pkg/git/repository-sort.go
new file mode 100644
index 0000000..04cd9e7
--- /dev/null
+++ b/pkg/git/repository-sort.go
@@ -0,0 +1,59 @@
+package git
+
+import (
+ "unicode"
+)
+
+// Alphabetical slice is the re-ordered *RepoEntity slice that sorted according
+// to alphabetical order (A-Z)
+type Alphabetical []*RepoEntity
+
+// Len is the interface implementation for Alphabetical sorting function
+func (s Alphabetical) Len() int { return len(s) }
+
+// Swap is the interface implementation for Alphabetical sorting function
+func (s Alphabetical) Swap(i, j int) { s[i], s[j] = s[j], s[i] }
+
+// Less is the interface implementation for Alphabetical sorting function
+func (s Alphabetical) Less(i, j int) bool {
+ iRunes := []rune(s[i].Name)
+ jRunes := []rune(s[j].Name)
+
+ max := len(iRunes)
+ if max > len(jRunes) {
+ max = len(jRunes)
+ }
+
+ for idx := 0; idx < max; idx++ {
+ ir := iRunes[idx]
+ jr := jRunes[idx]
+
+ lir := unicode.ToLower(ir)
+ ljr := unicode.ToLower(jr)
+
+ if lir != ljr {
+ return lir < ljr
+ }
+
+ // the lowercase runes are the same, so compare the original
+ if ir != jr {
+ return ir < jr
+ }
+ }
+ return false
+}
+
+// LastModified slice is the re-ordered *RepoEntity slice that sorted according
+// to last modified date of the repository directory
+type LastModified []*RepoEntity
+
+// Len is the interface implementation for LastModified sorting function
+func (s LastModified) Len() int { return len(s) }
+
+// Swap is the interface implementation for LastModified sorting function
+func (s LastModified) Swap(i, j int) { s[i], s[j] = s[j], s[i] }
+
+// Less is the interface implementation for LastModified sorting function
+func (s LastModified) Less(i, j int) bool {
+ return s[i].ModTime.Unix() > s[j].ModTime.Unix()
+}
diff --git a/pkg/git/repository.go b/pkg/git/repository.go
index 73aebdf..6918371 100644
--- a/pkg/git/repository.go
+++ b/pkg/git/repository.go
@@ -2,6 +2,7 @@ package git
import (
"errors"
+ "time"
"os"
"github.com/isacikgoz/gitbatch/pkg/helpers"
@@ -16,6 +17,7 @@ type RepoEntity struct {
RepoID string
Name string
AbsPath string
+ ModTime time.Time
Repository git.Repository
Branch *Branch
Branches []*Branch
@@ -65,6 +67,7 @@ func InitializeRepository(directory string) (entity *RepoEntity, err error) {
entity = &RepoEntity{RepoID: helpers.RandomString(8),
Name: fileInfo.Name(),
AbsPath: directory,
+ ModTime: fileInfo.ModTime(),
Repository: *r,
State: Available,
}
@@ -156,11 +159,18 @@ func (entity *RepoEntity) Merge() error {
// fetch/pull/merge operations
func (entity *RepoEntity) Refresh() error {
var err error
+ // error can be ignored since the file already exists when app is loading
+ file, _ := os.Open(entity.AbsPath)
+ fileInfo, err := file.Stat()
+ if err != nil {
+ return err
+ }
r, err := git.PlainOpen(entity.AbsPath)
if err != nil {
return err
}
entity.Repository = *r
+ entity.ModTime = fileInfo.ModTime()
if err := entity.loadLocalBranches(); err != nil {
return err
}
diff --git a/pkg/gui/cheatsheet.go b/pkg/gui/cheatsheet.go
index af6d7d9..43020a5 100644
--- a/pkg/gui/cheatsheet.go
+++ b/pkg/gui/cheatsheet.go
@@ -17,10 +17,9 @@ func (gui *Gui) openCheatSheetView(g *gocui.Gui, v *gocui.View) error {
return err
}
v.Title = cheatSheetViewFeature.Title
- fmt.Fprintln(v, " ")
for _, k := range gui.KeyBindings {
if k.View == mainViewFeature.Name || k.View == "" {
- binding := " " + k.Display + ": " + k.Description
+ binding := " " + cyan.Sprint(k.Display) + ": " + k.Description
fmt.Fprintln(v, binding)
}
}
diff --git a/pkg/gui/diffview.go b/pkg/gui/diffview.go
index 06038d8..6f512ce 100644
--- a/pkg/gui/diffview.go
+++ b/pkg/gui/diffview.go
@@ -54,39 +54,6 @@ func (gui *Gui) closeCommitDiffView(g *gocui.Gui, v *gocui.View) error {
return nil
}
-// cursor down acts like half-page down for faster scrolling
-func (gui *Gui) commitCursorDown(g *gocui.Gui, v *gocui.View) error {
- if v != nil {
- ox, oy := v.Origin()
- _, vy := v.Size()
-
- // TODO: do something when it hits bottom
- if err := v.SetOrigin(ox, oy+vy/2); err != nil {
- return err
- }
- }
- return nil
-}
-
-// cursor up acts like half-page up for faster scrolling
-func (gui *Gui) commitCursorUp(g *gocui.Gui, v *gocui.View) error {
- if v != nil {
- ox, oy := v.Origin()
- _, vy := v.Size()
-
- if oy-vy/2 > 0 {
- if err := v.SetOrigin(ox, oy-vy/2); err != nil {
- return err
- }
- } else if oy-vy/2 <= 0 {
- if err := v.SetOrigin(0, 0); err != nil {
- return err
- }
- }
- }
- return nil
-}
-
// colorize the plain diff text collected from system output
// the style is near to original diff command
func colorizeDiff(original string) (colorized []string) {
diff --git a/pkg/gui/gui-util.go b/pkg/gui/gui-util.go
index c09ae6c..2ae8cae 100644
--- a/pkg/gui/gui-util.go
+++ b/pkg/gui/gui-util.go
@@ -1,6 +1,8 @@
package gui
import (
+ "sort"
+
"github.com/isacikgoz/gitbatch/pkg/git"
"github.com/isacikgoz/gitbatch/pkg/helpers"
"github.com/jroimartin/gocui"
@@ -107,3 +109,59 @@ func writeRightHandSide(v *gocui.View, text string, cx, cy int) error {
v.SetCursor(cx, cy)
return nil
}
+
+// sortByName sorts the repositories by A to Z order
+func (gui *Gui) sortByName(g *gocui.Gui, v *gocui.View) error {
+ sort.Sort(git.Alphabetical(gui.State.Repositories))
+ gui.refreshAfterSort(g)
+ return nil
+}
+
+// sortByMod sorts the repositories according to last modifed date
+// the top element will be the last modified
+func (gui *Gui) sortByMod(g *gocui.Gui, v *gocui.View) error {
+ sort.Sort(git.LastModified(gui.State.Repositories))
+ gui.refreshAfterSort(g)
+ return nil
+}
+
+// utility function that refreshes main and side views after that
+func (gui *Gui) refreshAfterSort(g *gocui.Gui) error {
+ gui.refreshMain(g)
+ entity := gui.getSelectedRepository()
+ gui.refreshViews(g, entity)
+ return nil
+}
+
+// cursor down acts like half-page down for faster scrolling
+func (gui *Gui) fastCursorDown(g *gocui.Gui, v *gocui.View) error {
+ if v != nil {
+ ox, oy := v.Origin()
+ _, vy := v.Size()
+
+ // TODO: do something when it hits bottom
+ if err := v.SetOrigin(ox, oy+vy/2); err != nil {
+ return err
+ }
+ }
+ return nil
+}
+
+// cursor up acts like half-page up for faster scrolling
+func (gui *Gui) fastCursorUp(g *gocui.Gui, v *gocui.View) error {
+ if v != nil {
+ ox, oy := v.Origin()
+ _, vy := v.Size()
+
+ if oy-vy/2 > 0 {
+ if err := v.SetOrigin(ox, oy-vy/2); err != nil {
+ return err
+ }
+ } else if oy-vy/2 <= 0 {
+ if err := v.SetOrigin(0, 0); err != nil {
+ return err
+ }
+ }
+ }
+ return nil
+}
diff --git a/pkg/gui/keybindings.go b/pkg/gui/keybindings.go
index f224bc3..a388b51 100644
--- a/pkg/gui/keybindings.go
+++ b/pkg/gui/keybindings.go
@@ -150,6 +150,38 @@ func (gui *Gui) generateKeybindings() error {
Description: "Add to queue",
Vital: true,
}, {
+ View: mainViewFeature.Name,
+ Key: gocui.KeyCtrlSpace,
+ Modifier: gocui.ModNone,
+ Handler: gui.markAllRepositories,
+ Display: "ctrl + space",
+ Description: "Add all to queue",
+ Vital: false,
+ }, {
+ View: mainViewFeature.Name,
+ Key: gocui.KeyBackspace2,
+ Modifier: gocui.ModNone,
+ Handler: gui.unmarkAllRepositories,
+ Display: "backspace",
+ Description: "Remove all from queue",
+ Vital: false,
+ }, {
+ View: mainViewFeature.Name,
+ Key: 'n',
+ Modifier: gocui.ModNone,
+ Handler: gui.sortByName,
+ Display: "n",
+ Description: "Sort repositories by Name",
+ Vital: false,
+ }, {
+ View: mainViewFeature.Name,
+ Key: 'm',
+ Modifier: gocui.ModNone,
+ Handler: gui.sortByMod,
+ Display: "m",
+ Description: "Sort repositories by Modification date",
+ Vital: false,
+ }, {
View: commitDiffViewFeature.Name,
Key: 'c',
Modifier: gocui.ModNone,
@@ -161,7 +193,7 @@ func (gui *Gui) generateKeybindings() error {
View: commitDiffViewFeature.Name,
Key: gocui.KeyArrowUp,
Modifier: gocui.ModNone,
- Handler: gui.commitCursorUp,
+ Handler: gui.fastCursorUp,
Display: "↑",
Description: "Page up",
Vital: true,
@@ -169,7 +201,7 @@ func (gui *Gui) generateKeybindings() error {
View: commitDiffViewFeature.Name,
Key: gocui.KeyArrowDown,
Modifier: gocui.ModNone,
- Handler: gui.commitCursorDown,
+ Handler: gui.fastCursorDown,
Display: "↓",
Description: "Page down",
Vital: true,
@@ -177,7 +209,7 @@ func (gui *Gui) generateKeybindings() error {
View: commitDiffViewFeature.Name,
Key: 'k',
Modifier: gocui.ModNone,
- Handler: gui.commitCursorUp,
+ Handler: gui.fastCursorUp,
Display: "k",
Description: "Page up",
Vital: false,
@@ -185,7 +217,7 @@ func (gui *Gui) generateKeybindings() error {
View: commitDiffViewFeature.Name,
Key: 'j',
Modifier: gocui.ModNone,
- Handler: gui.commitCursorDown,
+ Handler: gui.fastCursorDown,
Display: "j",
Description: "Page down",
Vital: false,
@@ -198,6 +230,38 @@ func (gui *Gui) generateKeybindings() error {
Description: "close/cancel",
Vital: true,
}, {
+ View: cheatSheetViewFeature.Name,
+ Key: gocui.KeyArrowUp,
+ Modifier: gocui.ModNone,
+ Handler: gui.fastCursorUp,
+ Display: "↑",
+ Description: "Up",
+ Vital: true,
+ }, {
+ View: cheatSheetViewFeature.Name,
+ Key: gocui.KeyArrowDown,
+ Modifier: gocui.ModNone,
+ Handler: gui.fastCursorDown,
+ Display: "↓",
+ Description: "Down",
+ Vital: true,
+ }, {
+ View: cheatSheetViewFeature.Name,
+ Key: 'k',
+ Modifier: gocui.ModNone,
+ Handler: gui.fastCursorUp,
+ Display: "k",
+ Description: "Up",
+ Vital: false,
+ }, {
+ View: cheatSheetViewFeature.Name,
+ Key: 'j',
+ Modifier: gocui.ModNone,
+ Handler: gui.fastCursorDown,
+ Display: "j",
+ Description: "Down",
+ Vital: false,
+ }, {
View: errorViewFeature.Name,
Key: 'c',
Modifier: gocui.ModNone,
diff --git a/pkg/gui/mainview.go b/pkg/gui/mainview.go
index e9bd1f3..36f5825 100644
--- a/pkg/gui/mainview.go
+++ b/pkg/gui/mainview.go
@@ -93,36 +93,52 @@ func (gui *Gui) getSelectedRepository() *git.RepoEntity {
return gui.State.Repositories[cy+oy]
}
-// marking repostiry is simply adding the repostirory into the queue. the
+// adds given entity to job queue
+func (gui *Gui) addToQueue(entity *git.RepoEntity) error {
+ var jt queue.JobType
+ switch mode := gui.State.Mode.ModeID; mode {
+ case FetchMode:
+ jt = queue.Fetch
+ case PullMode:
+ jt = queue.Pull
+ case MergeMode:
+ jt = queue.Merge
+ default:
+ return nil
+ }
+ err := gui.State.Queue.AddJob(&queue.Job{
+ JobType: jt,
+ Entity: entity,
+ })
+ if err != nil {
+ return err
+ }
+ entity.State = git.Queued
+ return nil
+}
+
+// removes given entity from job queue
+func (gui *Gui) removeFromQueue(entity *git.RepoEntity) error {
+ err := gui.State.Queue.RemoveFromQueue(entity)
+ if err != nil {
+ return err
+ }
+ entity.State = git.Available
+ return nil
+}
+
+// marking repository is simply adding the repostirory into the queue. the
// function does take its current state into account before adding it
func (gui *Gui) markRepository(g *gocui.Gui, v *gocui.View) error {
r := gui.getSelectedRepository()
if r.State == git.Available || r.State == git.Success {
- var jt queue.JobType
- switch mode := gui.State.Mode.ModeID; mode {
- case FetchMode:
- jt = queue.Fetch
- case PullMode:
- jt = queue.Pull
- case MergeMode:
- jt = queue.Merge
- default:
- return nil
- }
- err := gui.State.Queue.AddJob(&queue.Job{
- JobType: jt,
- Entity: r,
- })
- if err != nil {
+ if err := gui.addToQueue(r); err !=nil {
return err
}
- r.State = git.Queued
} else if r.State == git.Queued {
- err := gui.State.Queue.RemoveFromQueue(r)
- if err != nil {
- return err
+ if err := gui.removeFromQueue(r); err !=nil {
+ return err
}
- r.State = git.Available
} else {
return nil
}
@@ -130,6 +146,38 @@ func (gui *Gui) markRepository(g *gocui.Gui, v *gocui.View) error {
return nil
}
+// add all remaining repositories into the queue. the function does take its
+// current state into account before adding it
+func (gui *Gui) markAllRepositories(g *gocui.Gui, v *gocui.View) error {
+ for _, r := range gui.State.Repositories {
+ if r.State == git.Available || r.State == git.Success {
+ if err := gui.addToQueue(r); err !=nil {
+ return err
+ }
+ } else {
+ continue
+ }
+ }
+ gui.refreshMain(g)
+ return nil
+}
+
+// remove all repositories from the queue. the function does take its
+// current state into account before removing it
+func (gui *Gui) unmarkAllRepositories(g *gocui.Gui, v *gocui.View) error {
+ for _, r := range gui.State.Repositories {
+ if r.State == git.Queued {
+ if err := gui.removeFromQueue(r); err !=nil {
+ return err
+ }
+ } else {
+ continue
+ }
+ }
+ gui.refreshMain(g)
+ return nil
+}
+
// refresh the main view and re-render the repository representations
func (gui *Gui) refreshMain(g *gocui.Gui) error {
mainView, err := g.View(mainViewFeature.Name)
diff --git a/pkg/queue/queue.go b/pkg/queue/queue.go
index b61d43b..871a380 100644
--- a/pkg/queue/queue.go
+++ b/pkg/queue/queue.go
@@ -88,7 +88,9 @@ func (jobQueue *JobQueue) AddJob(j *Job) error {
return errors.New("Same job already is in the queue")
}
}
- jobQueue.series = append(jobQueue.series, j)
+ jobQueue.series = append(jobQueue.series, nil)
+ copy(jobQueue.series[1:], jobQueue.series[0:])
+ jobQueue.series[0] = j
return nil
}