diff options
| author | Ibrahim Serdar Acikgoz <serdaracikgoz86@gmail.com> | 2018-12-05 02:19:41 +0300 |
|---|---|---|
| committer | GitHub <noreply@github.com> | 2018-12-05 02:19:41 +0300 |
| commit | 5c481dfe96fc40b8b7ad4fdc63fa0968e6832939 (patch) | |
| tree | 6442b45aeac0e88e83656e871c0e0872d59e4044 | |
| parent | Develop (#20) (diff) | |
| download | gitbatch-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.md | 46 | ||||
| -rw-r--r-- | pkg/git/repository-sort.go | 59 | ||||
| -rw-r--r-- | pkg/git/repository.go | 10 | ||||
| -rw-r--r-- | pkg/gui/cheatsheet.go | 3 | ||||
| -rw-r--r-- | pkg/gui/diffview.go | 33 | ||||
| -rw-r--r-- | pkg/gui/gui-util.go | 58 | ||||
| -rw-r--r-- | pkg/gui/keybindings.go | 72 | ||||
| -rw-r--r-- | pkg/gui/mainview.go | 92 | ||||
| -rw-r--r-- | pkg/queue/queue.go | 4 |
9 files changed, 278 insertions, 99 deletions
@@ -1,15 +1,15 @@ [](https://travis-ci.com/isacikgoz/gitbatch) [](/LICENSE) [](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 } |
