summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorIbrahim Serdar Acikgoz <serdaracikgoz86@gmail.com>2018-12-10 03:00:24 +0300
committerGitHub <noreply@github.com>2018-12-10 03:00:24 +0300
commit0f55d3652c215074df03de4390856d4389a1b729 (patch)
tree852ca843d5a67852d69054f816bb8b746df8b49e
parentMerge pull request #28 from isacikgoz/develop (diff)
parentadded recursive option and some minor code formating (diff)
downloadgitbatch-0f55d3652c215074df03de4390856d4389a1b729.tar.gz
Merge pull request #29 from isacikgoz/develop
Develop
-rw-r--r--README.md10
-rw-r--r--main.go14
-rw-r--r--pkg/app/app.go31
-rw-r--r--pkg/app/config.go112
-rw-r--r--pkg/app/files.go78
-rw-r--r--pkg/git/add.go52
-rw-r--r--pkg/git/branch.go21
-rw-r--r--pkg/git/commands.go11
-rw-r--r--pkg/git/fetch.go24
-rw-r--r--pkg/git/merge.go16
-rw-r--r--pkg/git/remote.go17
-rw-r--r--pkg/git/repository-sort.go4
-rw-r--r--pkg/git/repository.go16
-rw-r--r--pkg/git/reset.go55
-rw-r--r--pkg/git/rev-list.go13
-rw-r--r--pkg/git/stash.go95
-rw-r--r--pkg/git/status.go81
-rw-r--r--pkg/gui/branchview.go2
-rw-r--r--pkg/gui/gui-navigate.go54
-rw-r--r--pkg/gui/gui-util.go100
-rw-r--r--pkg/gui/gui.go33
-rw-r--r--pkg/gui/keybindings.go220
-rw-r--r--pkg/gui/mainview.go54
-rw-r--r--pkg/gui/remotebranchview.go20
-rw-r--r--pkg/gui/remotesview.go2
-rw-r--r--pkg/gui/stagedview.go84
-rw-r--r--pkg/gui/stashview.go89
-rw-r--r--pkg/gui/statusview.go174
-rw-r--r--pkg/gui/textstyle.go10
-rw-r--r--pkg/gui/unstagedview.go80
-rw-r--r--pkg/helpers/command.go2
-rw-r--r--pkg/queue/job.go1
32 files changed, 1357 insertions, 218 deletions
diff --git a/README.md b/README.md
index ed3396a..e3bfdd7 100644
--- a/README.md
+++ b/README.md
@@ -10,7 +10,6 @@ Aim of this tool to make your local repositories synchronized with remotes easil
- [BitBucket Set up an SSH key](https://confluence.atlassian.com/bitbucket/set-up-ssh-for-git-728138079.html)
- 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.
Here is the screencast of the app:
@@ -44,15 +43,16 @@ For more information;
- 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
+- 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
+- implement config file to pre-define repo locations or some settings ✔
- resolve authentication issues
## Credits
-- [go-git](https://github.com/src-d/go-git) for git interface
+- [go-git](https://github.com/src-d/go-git) for git interface (partially)
- [gocui](https://github.com/jroimartin/gocui) for user interface
- [logrus](https://github.com/sirupsen/logrus) for logging
-- [lazygit](https://github.com/jesseduffield/lazygit) as app template
+- [viper](https://github.com/spf13/viper) for configuration management
- [color](https://github.com/fatih/color) for colored text
+- [lazygit](https://github.com/jesseduffield/lazygit) as app template and reference
- [kingpin](https://github.com/alecthomas/kingpin) for command-line flag&options
diff --git a/main.go b/main.go
index 011f952..27cdaaf 100644
--- a/main.go
+++ b/main.go
@@ -11,18 +11,24 @@ import (
var (
// take this as default directory if user does not start app with -d flag
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()
+ dirs = kingpin.Flag("directory", "Directory to roam for git repositories").Default(currentDir).Short('d').Strings()
+ ignoreConfig = kingpin.Flag("ignore-config", "Ignore config file").Short('i').Bool()
+ recurseDepth = kingpin.Flag("recursive-depth", "Find directories recursively").Default("1").Short('r').Int()
logLevel = kingpin.Flag("log-level", "Logging level; trace,debug,info,warn,error").Default("error").Short('l').String()
)
func main() {
- kingpin.Version("gitbatch version 0.0.1 (alpha)")
+ kingpin.Version("gitbatch version 0.0.2 (alpha)")
// parse the command line flag and options
kingpin.Parse()
// set the app
- app, err := app.Setup(*dir, *repoPattern, *logLevel)
+ app, err := app.Setup(app.SetupConfig{
+ Directories: *dirs,
+ LogLevel: *logLevel,
+ IgnoreConfig: *ignoreConfig,
+ Depth: *recurseDepth,
+ })
if err != nil {
log.Fatal(err)
}
diff --git a/pkg/app/app.go b/pkg/app/app.go
index d1ab15b..9082386 100644
--- a/pkg/app/app.go
+++ b/pkg/app/app.go
@@ -8,20 +8,41 @@ import (
// The App struct is responsible to hold app-wide related entities. Currently
// it has only the gui.Gui pointer for interface entity.
type App struct {
- Gui *gui.Gui
+ Gui *gui.Gui
+ Config *Config
+}
+
+// SetupConfig is an assembler data to initiate a setup
+type SetupConfig struct {
+ Directories []string
+ LogLevel string
+ IgnoreConfig bool
+ Depth int
}
// Setup will handle pre-required operations. It is designed to be a wrapper for
// main method right now.
-func Setup(directory, repoPattern, logLevel string) (*App, error) {
+func Setup(setupConfig SetupConfig) (*App, error) {
// initiate the app and give it initial values
app := &App{}
- setLogLevel(logLevel)
+ setLogLevel(setupConfig.LogLevel)
var err error
- directories := generateDirectories(directory, repoPattern)
+ app.Config, err = LoadConfiguration()
+ if err != nil {
+ // the error types and handling is not considered yer
+ log.Error(err)
+ return app, err
+ }
+ directories := make([]string, 0)
+
+ if len(app.Config.Directories) <= 0 || setupConfig.IgnoreConfig {
+ directories = generateDirectories(setupConfig.Directories, setupConfig.Depth)
+ } else {
+ directories = generateDirectories(app.Config.Directories, setupConfig.Depth)
+ }
// create a gui.Gui struct and set it as App's gui
- app.Gui, err = gui.NewGui(directories)
+ app.Gui, err = gui.NewGui(app.Config.Mode, directories)
if err != nil {
// the error types and handling is not considered yer
log.Error(err)
diff --git a/pkg/app/config.go b/pkg/app/config.go
new file mode 100644
index 0000000..94052e6
--- /dev/null
+++ b/pkg/app/config.go
@@ -0,0 +1,112 @@
+package app
+
+import (
+ "os"
+ "path/filepath"
+ "runtime"
+
+ log "github.com/sirupsen/logrus"
+ "github.com/spf13/viper"
+)
+
+// Config type is the configuration entity of the application
+type Config struct {
+ Mode string
+ Directories []string
+}
+
+// config file stuff
+var (
+ configFileName = "config"
+ configFileExt = ".yml"
+ configType = "yaml"
+ appName = "gitbatch"
+
+ configurationDirectory = filepath.Join(osConfigDirectory(), appName)
+ configFileAbsPath = filepath.Join(configurationDirectory, configFileName)
+)
+
+// configuration items
+var (
+ modeKey = "mode"
+ modeKeyDefault = "fetch"
+ pathsKey = "paths"
+ pathsKeyDefault = []string{"."}
+)
+
+// LoadConfiguration returns a Config struct is filled
+func LoadConfiguration() (*Config, error) {
+ if err := initializeConfigurationManager(); err != nil {
+ return nil, err
+ }
+ if err := setDefaults(); err != nil {
+ return nil, err
+ }
+ if err := readConfiguration(); err != nil {
+ return nil, err
+ }
+ config := &Config{
+ Mode: viper.GetString(modeKey),
+ Directories: viper.GetStringSlice(pathsKey),
+ }
+ return config, nil
+}
+
+// set default configuration parameters
+func setDefaults() error {
+ viper.SetDefault(modeKey, modeKeyDefault)
+ // viper.SetDefault(pathsKey, pathsKeyDefault)
+ return nil
+}
+
+// read configuration from file
+func readConfiguration() error {
+ err := viper.ReadInConfig() // Find and read the config file
+ if err != nil { // Handle errors reading the config file
+ // if file does not exist, simply create one
+ if _, err := os.Stat(configFileAbsPath + configFileExt); os.IsNotExist(err) {
+ os.MkdirAll(configurationDirectory, 0755)
+ os.Create(configFileAbsPath + configFileExt)
+ } else {
+ return err
+ }
+ // let's write defaults
+ if err := viper.WriteConfig(); err != nil {
+ return err
+ }
+ }
+ return nil
+}
+
+// write configuration to a file
+func writeConfiguration() error {
+ if err := viper.WriteConfig(); err != nil {
+ return err
+ }
+ return nil
+}
+
+// initialize the configuration manager
+func initializeConfigurationManager() error {
+ // config viper
+ viper.AddConfigPath(configurationDirectory)
+ viper.SetConfigName(configFileName)
+ viper.SetConfigType(configType)
+
+ return nil
+}
+
+// returns OS dependent config directory
+func osConfigDirectory() (osConfigDirectory string) {
+ switch osname := runtime.GOOS; osname {
+ case "windows":
+ osConfigDirectory = os.Getenv("APPDATA")
+ case "darwin":
+ osConfigDirectory = os.Getenv("HOME") + "/Library/Application Support"
+ case "linux":
+ osConfigDirectory = os.Getenv("HOME") + "/.config"
+ default:
+ log.Warn("Operating system couldn't be recognized")
+ }
+ return osConfigDirectory
+}
diff --git a/pkg/app/files.go b/pkg/app/files.go
index f6ae947..b6ec662 100644
--- a/pkg/app/files.go
+++ b/pkg/app/files.go
@@ -9,50 +9,88 @@ import (
log "github.com/sirupsen/logrus"
)
-// generateDirectories is to find all the files in given path. This method
+// generateDirectories returns poosible git repositories to pipe into git pkg's
+// load function
+func generateDirectories(directories []string, depth int) (gitDirectories []string) {
+ for i := 0; i <= depth; i++ {
+ nonrepos, repos := walkRecursive(directories, gitDirectories)
+ directories = nonrepos
+ gitDirectories = repos
+ }
+ return gitDirectories
+}
+
+// returns given values, first search directories and second stands for possible
+// git repositories. Call this func from a "for i := 0; i<depth; i++" loop
+func walkRecursive(search, appendant []string) ([]string, []string) {
+ max := len(search)
+ for i := 0; i < max; i++ {
+ if i >= len(search) {
+ continue
+ }
+ // find possible repositories and remaining ones, b slice is possible ones
+ a, b, err := seperateDirectories(search[i])
+ if err != nil {
+ log.WithFields(log.Fields{
+ "directory": search[i],
+ }).Trace("Can't read directory")
+ continue
+ }
+ // since we started to search let's get rid of it and remove from search
+ // array
+ search[i] = search[len(search)-1]
+ search = search[:len(search)-1]
+ // lets append what we have found to continue recursion
+ search = append(search, a...)
+ appendant = append(appendant, b...)
+ }
+ return search, appendant
+}
+
+// seperateDirectories is to find all the files in given path. This method
// does not check if the given file is a valid git repositories
-func generateDirectories(directory string, repoPattern string) (directories []string) {
+func seperateDirectories(directory string) (directories, gitDirectories []string, err error) {
files, err := ioutil.ReadDir(directory)
-
// can we read the directory?
if err != nil {
- log.Fatal(err)
+ log.WithFields(log.Fields{
+ "directory": directory,
+ }).Trace("Can't read directory")
+ return directories, gitDirectories, nil
}
-
- // filter according to a pattern
- filteredFiles := filterDirectories(files, repoPattern)
-
- // now let's iterate over the our desired git directories
- for _, f := range filteredFiles {
+ for _, f := range files {
repo := directory + string(os.PathSeparator) + f.Name()
file, err := os.Open(repo)
-
// if we cannot open it, simply continue to iteration and don't consider
if err != nil {
log.WithFields(log.Fields{
"file": file,
- "directory": directory,
+ "directory": repo,
}).Trace("Failed to open file in the directory")
continue
}
dir, err := filepath.Abs(file.Name())
if err != nil {
- log.Fatal(err)
+ return nil, nil, err
+ }
+ // with this approach, we ignore submodule or sub repositoreis in a git repository
+ _, err = os.Open(dir + string(os.PathSeparator) + ".git")
+ if err != nil {
+ directories = append(directories, dir)
+ } else {
+ gitDirectories = append(gitDirectories, dir)
}
-
- // shaping our directory slice
- directories = append(directories, dir)
}
- return directories
+ return directories, gitDirectories, nil
}
// takes a fileInfo slice and returns it with the ones matches with the
-// repoPattern string
-func filterDirectories(files []os.FileInfo, repoPattern string) []os.FileInfo {
+// pattern string
+func filterDirectories(files []os.FileInfo, pattern string) []os.FileInfo {
var filteredRepos []os.FileInfo
for _, f := range files {
// it is just a simple filter
- if strings.Contains(f.Name(), repoPattern) {
+ if strings.Contains(f.Name(), pattern) && f.Name() != ".git" {
filteredRepos = append(filteredRepos, f)
} else {
continue
diff --git a/pkg/git/add.go b/pkg/git/add.go
new file mode 100644
index 0000000..6abb868
--- /dev/null
+++ b/pkg/git/add.go
@@ -0,0 +1,52 @@
+package git
+
+import (
+ "errors"
+ "strings"
+
+ log "github.com/sirupsen/logrus"
+)
+
+var addCommand = "add"
+
+type AddOptions struct {
+ Update bool
+ Force bool
+ DryRun bool
+}
+
+func (file *File) Add(option AddOptions) error {
+ args := make([]string, 0)
+ args = append(args, addCommand)
+ args = append(args, file.Name)
+ if option.Update {
+ args = append(args, "--update")
+ }
+ if option.Force {
+ args = append(args, "--force")
+ }
+ if option.DryRun {
+ args = append(args, "--dry-run")
+ }
+ out, err := GenericGitCommandWithOutput(strings.TrimSuffix(file.AbsPath, file.Name), args)
+ if err != nil {
+ log.Warn("Error while add command")
+ return errors.New(out + "\n" + err.Error())
+ }
+ return nil
+}
+
+func (entity *RepoEntity) AddAll(option AddOptions) error {
+ args := make([]string, 0)
+ args = append(args, addCommand)
+ if option.DryRun {
+ args = append(args, "--dry-run")
+ }
+ args = append(args, ".")
+ out, err := GenericGitCommandWithOutput(entity.AbsPath, args)
+ if err != nil {
+ log.Warn("Error while add command")
+ return errors.New(out + "\n" + err.Error())
+ }
+ return nil
+}
diff --git a/pkg/git/branch.go b/pkg/git/branch.go
index 4f54ced..0fbfdf0 100644
--- a/pkg/git/branch.go
+++ b/pkg/git/branch.go
@@ -6,8 +6,8 @@ import (
"gopkg.in/src-d/go-git.v4"
"gopkg.in/src-d/go-git.v4/plumbing"
"regexp"
- "strings"
"strconv"
+ "strings"
)
// Branch is the wrapper of go-git's Reference struct. In addition to that, it
@@ -51,7 +51,7 @@ func (entity *RepoEntity) loadLocalBranches() error {
pushables, err := RevList(entity, RevListOptions{
Ref1: "@{u}",
Ref2: "HEAD",
- })
+ })
if err != nil {
push = pushables[0]
} else {
@@ -60,7 +60,7 @@ func (entity *RepoEntity) loadLocalBranches() error {
pullables, err := RevList(entity, RevListOptions{
Ref1: "HEAD",
Ref2: "@{u}",
- })
+ })
if err != nil {
pull = pullables[0]
} else {
@@ -129,12 +129,9 @@ func (entity *RepoEntity) Checkout(branch *Branch) error {
entity.Commit = entity.Commits[0]
entity.Branch = branch
entity.RefreshPushPull()
- // TODO: same code on 3 different occasion, maybe something wrong?
// make this conditional on global scale
- if err = entity.Remote.switchRemoteBranch(entity.Remote.Name + "/" + entity.Branch.Name); err != nil {
- // probably couldn't find, but its ok.
- log.Trace("Cannot find proper remote branch " + err.Error())
- return nil
+ if err := entity.Remote.SyncBranches(branch.Name); err != nil {
+ return err
}
return nil
}
@@ -163,7 +160,7 @@ func (entity *RepoEntity) RefreshPushPull() {
pushables, err := RevList(entity, RevListOptions{
Ref1: "@{u}",
Ref2: "HEAD",
- })
+ })
if err != nil {
entity.Branch.Pushables = pushables[0]
} else {
@@ -172,7 +169,7 @@ func (entity *RepoEntity) RefreshPushPull() {
pullables, err := RevList(entity, RevListOptions{
Ref1: "HEAD",
Ref2: "@{u}",
- })
+ })
if err != nil {
entity.Branch.Pullables = pullables[0]
} else {
@@ -187,7 +184,7 @@ func (entity *RepoEntity) pullDiffsToUpstream() ([]*Commit, error) {
pullables, err := RevList(entity, RevListOptions{
Ref1: "HEAD",
Ref2: "@{u}",
- })
+ })
if err != nil {
// possibly found nothing or no upstream set
} else {
@@ -210,7 +207,7 @@ func (entity *RepoEntity) pushDiffsToUpstream() ([]string, error) {
pushables, err := RevList(entity, RevListOptions{
Ref1: "@{u}",
Ref2: "HEAD",
- })
+ })
if err != nil {
return make([]string, 0), nil
}
diff --git a/pkg/git/commands.go b/pkg/git/commands.go
index 0a1820b..b78b501 100644
--- a/pkg/git/commands.go
+++ b/pkg/git/commands.go
@@ -13,7 +13,7 @@ func GenericGitCommand(repoPath string, args []string) error {
return nil
}
-// GenericGitCommand runs any git command with returning output
+// GenericGitCommandWithOutput runs any git command with returning output
func GenericGitCommandWithOutput(repoPath string, args []string) (string, error) {
out, err := helpers.RunCommandWithOutput(repoPath, "git", args)
if err != nil {
@@ -22,6 +22,15 @@ func GenericGitCommandWithOutput(repoPath string, args []string) (string, error)
return helpers.TrimTrailingNewline(out), nil
}
+// GenericGitCommandWithErrorOutput runs any git command with returning output
+func GenericGitCommandWithErrorOutput(repoPath string, args []string) (string, error) {
+ out, err := helpers.RunCommandWithOutput(repoPath, "git", args)
+ if err != nil {
+ return helpers.TrimTrailingNewline(out), err
+ }
+ return helpers.TrimTrailingNewline(out), nil
+}
+
// GitShow is conventional git show command without any argument
func GitShow(repoPath, hash string) string {
args := []string{"show", hash}
diff --git a/pkg/git/fetch.go b/pkg/git/fetch.go
index 1b3bb69..5dad021 100644
--- a/pkg/git/fetch.go
+++ b/pkg/git/fetch.go
@@ -6,17 +6,18 @@ import (
var fetchCommand = "fetch"
+// FetchOptions defines the rules for fetch operation
type FetchOptions struct {
- // Name of the remote to fetch from. Defaults to origin.
- RemoteName string
- // Before fetching, remove any remote-tracking references that no longer
- // exist on the remote.
- Prune bool
- // Show what would be done, without making any changes.
- DryRun bool
- // Force allows the fetch to update a local branch even when the remote
- // branch does not descend from it.
- Force bool
+ // Name of the remote to fetch from. Defaults to origin.
+ RemoteName string
+ // Before fetching, remove any remote-tracking references that no longer
+ // exist on the remote.
+ Prune bool
+ // Show what would be done, without making any changes.
+ DryRun bool
+ // Force allows the fetch to update a local branch even when the remote
+ // branch does not descend from it.
+ Force bool
}
// Fetch branches refs from one or more other repositories, along with the
@@ -40,5 +41,6 @@ func Fetch(entity *RepoEntity, options FetchOptions) error {
log.Warn("Error while fetching")
return err
}
+ entity.Refresh()
return nil
-} \ No newline at end of file
+}
diff --git a/pkg/git/merge.go b/pkg/git/merge.go
index f2a8fae..984bc4b 100644
--- a/pkg/git/merge.go
+++ b/pkg/git/merge.go
@@ -6,13 +6,14 @@ import (
var mergeCommand = "merge"
+// MergeOptions defines the rules of a merge operation
type MergeOptions struct {
- // Name of the branch to merge with.
- BranchName string
- // Be verbose.
- Verbose bool
- // With true do not show a diffstat at the end of the merge.
- NoStat bool
+ // Name of the branch to merge with.
+ BranchName string
+ // Be verbose.
+ Verbose bool
+ // With true do not show a diffstat at the end of the merge.
+ NoStat bool
}
// Merge incorporates changes from the named commits or branches into the
@@ -33,5 +34,6 @@ func Merge(entity *RepoEntity, options MergeOptions) error {
log.Warn("Error while merging")
return err
}
+ entity.Refresh()
return nil
-} \ No newline at end of file
+}
diff --git a/pkg/git/remote.go b/pkg/git/remote.go
index d1aeeab..62eb0db 100644
--- a/pkg/git/remote.go
+++ b/pkg/git/remote.go
@@ -22,9 +22,8 @@ func (entity *RepoEntity) NextRemote() error {
} else {
entity.Remote = entity.Remotes[currentRemoteIndex+1]
}
- // TODO: same code on 3 different occasion, maybe something wrong?
- if err := entity.Remote.switchRemoteBranch(entity.Remote.Name + "/" + entity.Branch.Name); err != nil {
- // probably couldn't find, but its ok.
+ if err := entity.Remote.SyncBranches(entity.Branch.Name); err != nil {
+ return err
}
return nil
}
@@ -37,9 +36,8 @@ func (entity *RepoEntity) PreviousRemote() error {
} else {
entity.Remote = entity.Remotes[currentRemoteIndex-1]
}
- // TODO: same code on 3 different occasion, maybe something wrong?
- if err := entity.Remote.switchRemoteBranch(entity.Remote.Name + "/" + entity.Branch.Name); err != nil {
- // probably couldn't find, but its ok.
+ if err := entity.Remote.SyncBranches(entity.Branch.Name); err != nil {
+ return err
}
return nil
}
@@ -81,3 +79,10 @@ func (entity *RepoEntity) loadRemotes() error {
}
return err
}
+
+func (remote *Remote) SyncBranches(branchName string) error {
+ if err := remote.switchRemoteBranch(remote.Name + "/" + branchName); err != nil {
+ // probably couldn't find, but its ok.
+ }
+ return nil
+}
diff --git a/pkg/git/repository-sort.go b/pkg/git/repository-sort.go
index 04cd9e7..de5f454 100644
--- a/pkg/git/repository-sort.go
+++ b/pkg/git/repository-sort.go
@@ -9,7 +9,7 @@ import (
type Alphabetical []*RepoEntity
// Len is the interface implementation for Alphabetical sorting function
-func (s Alphabetical) Len() int { return len(s) }
+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] }
@@ -48,7 +48,7 @@ func (s Alphabetical) Less(i, j int) bool {
type LastModified []*RepoEntity
// Len is the interface implementation for LastModified sorting function
-func (s LastModified) Len() int { return len(s) }
+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] }
diff --git a/pkg/git/repository.go b/pkg/git/repository.go
index e75281c..b127305 100644
--- a/pkg/git/repository.go
+++ b/pkg/git/repository.go
@@ -2,8 +2,8 @@ package git
import (
"errors"
- "time"
"os"
+ "time"
"github.com/isacikgoz/gitbatch/pkg/helpers"
log "github.com/sirupsen/logrus"
@@ -25,6 +25,7 @@ type RepoEntity struct {
Remotes []*Remote
Commit *Commit
Commits []*Commit
+ Stasheds []*StashedItem
State RepoState
}
@@ -85,11 +86,16 @@ func InitializeRepository(directory string) (entity *RepoEntity, err error) {
entity.loadRemotes()
// set the active branch to repositories HEAD
entity.Branch = entity.getActiveBranch()
+ if err = entity.loadStashedItems(); err != nil {
+ // TODO: fix here.
+ }
if len(entity.Remotes) > 0 {
// TODO: tend to take origin/master as default
entity.Remote = entity.Remotes[0]
- // TODO: same code on 3 different occasion, maybe something wrong?
- if err = entity.Remote.switchRemoteBranch(entity.Remote.Name + "/" + entity.Branch.Name); err != nil {
+ if entity.Branch == nil {
+ return nil, errors.New("Unable to find a valid branch")
+ }
+ if err = entity.Remote.SyncBranches(entity.Branch.Name); err != nil {
// probably couldn't find, but its ok.
}
} else {
@@ -118,6 +124,7 @@ func (entity *RepoEntity) Refresh() error {
if err := entity.loadLocalBranches(); err != nil {
return err
}
+ entity.Branch.Clean = entity.isClean()
entity.RefreshPushPull()
if err := entity.loadCommits(); err != nil {
return err
@@ -125,5 +132,8 @@ func (entity *RepoEntity) Refresh() error {
if err := entity.loadRemotes(); err != nil {
return err
}
+ if err := entity.loadStashedItems(); err != nil {
+ return err
+ }
return nil
}
diff --git a/pkg/git/reset.go b/pkg/git/reset.go
new file mode 100644
index 0000000..f93225c
--- /dev/null
+++ b/pkg/git/reset.go
@@ -0,0 +1,55 @@
+package git
+
+import (
+ "errors"
+ "strings"
+
+ log "github.com/sirupsen/logrus"
+)
+
+var resetCommand = "reset"
+
+// ResetOptions defines the rules of git reset command
+type ResetOptions struct {
+ Hard bool
+ Merge bool
+ Keep bool
+}
+
+// Reset is the wrapper of "git reset" command
+func (file *File) Reset(option ResetOptions) error {
+ args := make([]string, 0)
+ args = append(args, resetCommand)
+ args = append(args, "--")
+ args = append(args, file.Name)
+ if option.Hard {
+ args = append(args, "--hard")
+ }
+ if option.Merge {
+ args = append(args, "--merge")
+ }
+ if option.Keep {
+ args = append(args, "--keep")
+ }
+ out, err := GenericGitCommandWithOutput(strings.TrimSuffix(file.AbsPath, file.Name), args)
+ if err != nil {
+ log.Warn("Error while add command")
+ return errors.New(out + "\n" + err.Error())
+ }
+ return nil
+}
+
+// ResetAll resets the changes in a repository, should be used wise
+func (entity *RepoEntity) ResetAll(option ResetOptions) error {
+ args := make([]string, 0)
+ args = append(args, resetCommand)
+ if option.Hard {
+ args = append(args, "--hard")
+ }
+ out, err := GenericGitCommandWithOutput(entity.AbsPath, args)
+ if err != nil {
+ log.Warn("Error while add command")
+ return errors.New(out + "\n" + err.Error())
+ }
+ return nil
+}
diff --git a/pkg/git/rev-list.go b/pkg/git/rev-list.go
index f9f45da..5debe47 100644
--- a/pkg/git/rev-list.go
+++ b/pkg/git/rev-list.go
@@ -9,11 +9,12 @@ import (
var revlistCommand = "rev-list"
var hashLength = 40
+// RevListOptions defines the rules of rev-list func
type RevListOptions struct {
- // Ref1 is the first reference hash to link
- Ref1 string
- // Ref2 is the second reference hash to link
- Ref2 string
+ // Ref1 is the first reference hash to link
+ Ref1 string
+ // Ref2 is the second reference hash to link
+ Ref2 string
}
// RevList returns the commit hashes that are links from the given commit(s).
@@ -22,7 +23,7 @@ func RevList(entity *RepoEntity, options RevListOptions) ([]string, error) {
args := make([]string, 0)
args = append(args, revlistCommand)
if len(options.Ref1) > 0 && len(options.Ref2) > 0 {
- arg1 := options.Ref1+".."+options.Ref2
+ arg1 := options.Ref1 + ".." + options.Ref2
args = append(args, arg1)
}
out, err := GenericGitCommandWithOutput(entity.AbsPath, args)
@@ -39,4 +40,4 @@ func RevList(entity *RepoEntity, options RevListOptions) ([]string, error) {
}
}
return hashes, nil
-} \ No newline at end of file
+}
diff --git a/pkg/git/stash.go b/pkg/git/stash.go
new file mode 100644
index 0000000..66254c8
--- /dev/null
+++ b/pkg/git/stash.go
@@ -0,0 +1,95 @@
+package git
+
+import (
+ "regexp"
+ "strconv"
+ "strings"
+
+ log "github.com/sirupsen/logrus"
+)
+
+var stashCommand = "stash"
+
+// StashedItem holds the required fields for a stashed change
+type StashedItem struct {
+ StashID int
+ BranchName string
+ Hash string
+ Description string
+ EntityPath string
+}
+
+func stashGet(entity *RepoEntity, option string) string {
+ args := make([]string, 0)
+ args = append(args, stashCommand)
+ args = append(args, option)
+ out, err := GenericGitCommandWithOutput(entity.AbsPath, args)
+ if err != nil {
+ log.Warn("Error while stash command")
+ return "?"
+ }
+ return out
+}
+
+func (entity *RepoEntity) loadStashedItems() error {
+ entity.Stasheds = make([]*StashedItem, 0)
+ output := stashGet(entity, "list")
+ stashIDRegex := regexp.MustCompile(`stash@{[\d]+}:`)
+ stashIDRegexInt := regexp.MustCompile(`[\d]+`)
+ stashBranchRegex := regexp.MustCompile(`[\w]+: `)
+ stashHashRegex := regexp.MustCompile(`[\w]{7}`)
+
+ stashlist := strings.Split(output, "\n")
+ for _, stashitem := range stashlist {
+ // find id
+ id := stashIDRegexInt.FindString(stashIDRegex.FindString(stashitem))
+ i, err := strconv.Atoi(id)
+ if err != nil {
+ // probably something isn't right let's continue over this iteration
+ log.Trace("cannot initiate stashed item")
+ continue
+ }
+ // trim id section
+ trimmed := stashIDRegex.Split(stashitem, 2)[1]
+
+ // find branch
+ stashBranchRegexMatch := stashBranchRegex.FindString(trimmed)
+ branchName := stashBranchRegexMatch[:len(stashBranchRegexMatch)-2]
+
+ // trim branch section
+ trimmed = stashBranchRegex.Split(trimmed, 2)[1]
+ hash := stashHashRegex.FindString(trimmed)
+
+ // trim hash
+ desc := stashHashRegex.Split(trimmed, 2)[1][1:]
+
+ entity.Stasheds = append(entity.Stasheds, &StashedItem{
+ StashID: i,
+ BranchName: branchName,
+ Hash: hash,
+ Description: desc,
+ EntityPath: entity.AbsPath,
+ })
+ }
+ return nil
+}
+
+// Stash is the wrapper of convetional "git stash" command
+func (entity *RepoEntity) Stash() (output string, err error) {
+ args := make([]string, 0)
+ args = append(args, stashCommand)
+
+ output, err = GenericGitCommandWithErrorOutput(entity.AbsPath, args)
+ entity.Refresh()
+ return output, err
+}
+
+// Pop is the wrapper of "git stash pop" command that used for a file
+func (stashedItem *StashedItem) Pop() (output string, err error) {
+ args := make([]string, 0)
+ args = append(args, stashCommand)
+ args = append(args, "pop")
+ args = append(args, "stash@{"+strconv.Itoa(stashedItem.StashID)+"}")
+ output, err = GenericGitCommandWithErrorOutput(stashedItem.EntityPath, args)
+ return output, err
+}
diff --git a/pkg/git/status.go b/pkg/git/status.go
new file mode 100644
index 0000000..8db2ec7
--- /dev/null
+++ b/pkg/git/status.go
@@ -0,0 +1,81 @@
+package git
+
+import (
+ "os"
+ "regexp"
+ "strings"
+
+ log "github.com/sirupsen/logrus"
+)
+
+var statusCommand = "status"
+
+// File represents the status of a file in an index or work tree
+type File struct {
+ Name string
+ AbsPath string
+ X FileStatus
+ Y FileStatus
+}
+
+// FileStatus is the short representation of state of a file
+type FileStatus rune
+
+var (
+ // StatusNotupdated says file not updated
+ StatusNotupdated FileStatus = ' '
+ // StatusModified says file is modifed
+ StatusModified FileStatus = 'M'
+ // StatusAdded says file is added to index
+ StatusAdded FileStatus = 'A'
+ // StatusDeleted says file is deleted
+ StatusDeleted FileStatus = 'D'
+ // StatusRenamed says file is renamed
+ StatusRenamed FileStatus = 'R'
+ // StatusCopied says file is copied
+ StatusCopied FileStatus = 'C'
+ // StatusUpdated says file is updated
+ StatusUpdated FileStatus = 'U'
+ // StatusUntracked says file is untraced
+ StatusUntracked FileStatus = '?'
+ // StatusIgnored says file is ignored
+ StatusIgnored FileStatus = '!'
+)
+
+func shortStatus(entity *RepoEntity, option string) string {
+ args := make([]string, 0)
+ args = append(args, statusCommand)
+ args = append(args, option)
+ args = append(args, "--short")
+ out, err := GenericGitCommandWithOutput(entity.AbsPath, args)
+ if err != nil {
+ log.Warn("Error while status command")
+ return "?"
+ }
+ return out
+}
+
+// LoadFiles function simply commands a git status and collects output in a
+// structured way
+func (entity *RepoEntity) LoadFiles() ([]*File, error) {
+ files := make([]*File, 0)
+ output := shortStatus(entity, "--untracked-files=all")
+ if len(output) == 0 {
+ return files, nil
+ }
+ fileslist := strings.Split(output, "\n")
+ for _, file := range fileslist {
+ x := rune(file[0])
+ y := rune(file[1])
+ relativePathRegex := regexp.MustCompile(`[(\w|/|.|\-)]+`)
+ path := relativePathRegex.FindString(file[2:])
+
+ files = append(files, &File{
+ Name: path,
+ AbsPath: entity.AbsPath + string(os.PathSeparator) + path,
+ X: FileStatus(x),
+ Y: FileStatus(y),
+ })
+ }
+ return files, nil
+}
diff --git a/pkg/gui/branchview.go b/pkg/gui/branchview.go
index b6a2b43..c955cf0 100644
--- a/pkg/gui/branchview.go
+++ b/pkg/gui/branchview.go
@@ -83,4 +83,4 @@ func (gui *Gui) checkoutFollowUp(g *gocui.Gui, entity *git.RepoEntity) (err erro
return err
}
return nil
-} \ No newline at end of file
+}
diff --git a/pkg/gui/gui-navigate.go b/pkg/gui/gui-navigate.go
deleted file mode 100644
index 56d96de..0000000
--- a/pkg/gui/gui-navigate.go
+++ /dev/null
@@ -1,54 +0,0 @@
-package gui
-
-import (
- log "github.com/sirupsen/logrus"
- "github.com/jroimartin/gocui"
-)
-
-// focus to next view
-func (gui *Gui) nextView(g *gocui.Gui, v *gocui.View) error {
- var focusedViewName string
- if v == nil || v.Name() == mainViews[len(mainViews)-1].Name {
- focusedViewName = mainViews[0].Name
- } else {
- for i := range mainViews {
- if v.Name() == mainViews[i].Name {
- focusedViewName = mainViews[i+1].Name
- break
- }
- if i == len(mainViews)-1 {
- return nil
- }
- }
- }
- if _, err := g.SetCurrentView(focusedViewName); err != nil {
- log.Warn("Loading view cannot be focused.")
- return nil
- }
- gui.updateKeyBindingsView(g, focusedViewName)
- return nil
-}
-
-// focus to previous view
-func (gui *Gui) previousView(g *gocui.Gui, v *gocui.View) error {
- var focusedViewName string
- if v == nil || v.Name() == mainViews[0].Name {
- focusedViewName = mainViews[len(mainViews)-1].Name
- } else {
- for i := range mainViews {
- if v.Name() == mainViews[i].Name {
- focusedViewName = mainViews[i-1].Name
- break
- }
- if i == len(mainViews)-1 {
- return nil
- }
- }
- }
- if _, err := g.SetCurrentView(focusedViewName); err != nil {
- log.Warn("Loading view cannot be focused.")
- return nil
- }
- gui.updateKeyBindingsView(g, focusedViewName)
- return nil
-}
diff --git a/pkg/gui/gui-util.go b/pkg/gui/gui-util.go
index 2ae8cae..2462723 100644
--- a/pkg/gui/gui-util.go
+++ b/pkg/gui/gui-util.go
@@ -1,11 +1,10 @@
package gui
import (
- "sort"
-
"github.com/isacikgoz/gitbatch/pkg/git"
"github.com/isacikgoz/gitbatch/pkg/helpers"
"github.com/jroimartin/gocui"
+ log "github.com/sirupsen/logrus"
)
// refreshes the side views of the application for given git.RepoEntity struct
@@ -26,18 +25,70 @@ func (gui *Gui) refreshViews(g *gocui.Gui, entity *git.RepoEntity) error {
return err
}
+// focus to next view
+func (gui *Gui) nextViewOfGroup(g *gocui.Gui, v *gocui.View, group []viewFeature) error {
+ var focusedViewName string
+ if v == nil || v.Name() == group[len(group)-1].Name {
+ focusedViewName = group[0].Name
+ } else {
+ for i := range group {
+ if v.Name() == group[i].Name {
+ focusedViewName = group[i+1].Name
+ break
+ }
+ if i == len(group)-1 {
+ return nil
+ }
+ }
+ }
+ if _, err := g.SetCurrentView(focusedViewName); err != nil {
+ log.WithFields(log.Fields{
+ "view": focusedViewName,
+ }).Warn("View cannot be focused.")
+ return nil
+ }
+ gui.updateKeyBindingsView(g, focusedViewName)
+ return nil
+}
+
+// focus to previous view
+func (gui *Gui) previousViewOfGroup(g *gocui.Gui, v *gocui.View, group []viewFeature) error {
+ var focusedViewName string
+ if v == nil || v.Name() == group[0].Name {
+ focusedViewName = group[len(group)-1].Name
+ } else {
+ for i := range group {
+ if v.Name() == group[i].Name {
+ focusedViewName = group[i-1].Name
+ break
+ }
+ if i == len(group)-1 {
+ return nil
+ }
+ }
+ }
+ if _, err := g.SetCurrentView(focusedViewName); err != nil {
+ log.WithFields(log.Fields{
+ "view": focusedViewName,
+ }).Warn("View cannot be focused.")
+ return nil
+ }
+ gui.updateKeyBindingsView(g, focusedViewName)
+ return nil
+}
+
// siwtch the app mode
// TODO: switching can be made with conventional iteration
func (gui *Gui) switchMode(g *gocui.Gui, v *gocui.View) error {
- switch mode := gui.State.Mode.ModeID; mode {
- case FetchMode:
- gui.State.Mode = pullMode
- case PullMode:
- gui.State.Mode = mergeMode
- case MergeMode:
- gui.State.Mode = fetchMode
- default:
- gui.State.Mode = fetchMode
+ for i, mode := range modes {
+ if mode == gui.State.Mode {
+ if i == len(modes)-1 {
+ gui.State.Mode = modes[0]
+ break
+ }
+ gui.State.Mode = modes[i+1]
+ break
+ }
}
gui.updateKeyBindingsView(g, mainViewFeature.Name)
return nil
@@ -110,35 +161,14 @@ func writeRightHandSide(v *gocui.View, text string, cx, cy int) error {
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()
-
+ if len(v.BufferLines())+len(v.ViewBufferLines()) <= vy+oy || len(v.ViewBufferLines()) < vy {
+ return nil
+ }
// TODO: do something when it hits bottom
if err := v.SetOrigin(ox, oy+vy/2); err != nil {
return err
diff --git a/pkg/gui/gui.go b/pkg/gui/gui.go
index 0598383..0cf9458 100644
--- a/pkg/gui/gui.go
+++ b/pkg/gui/gui.go
@@ -41,15 +41,15 @@ type mode struct {
}
// ModeID is the mode indicator for the gui
-type ModeID int8
+type ModeID string
const (
// FetchMode puts the gui in fetch state
- FetchMode ModeID = 0
+ FetchMode ModeID = "fetch"
// PullMode puts the gui in pull state
- PullMode ModeID = 1
+ PullMode ModeID = "pull"
// MergeMode puts the gui in merge state
- MergeMode ModeID = 2
+ MergeMode ModeID = "merge"
)
var (
@@ -70,10 +70,11 @@ var (
mergeMode = mode{ModeID: MergeMode, DisplayString: "Merge", CommandString: "merge"}
mainViews = []viewFeature{mainViewFeature, remoteViewFeature, remoteBranchViewFeature, branchViewFeature, commitViewFeature}
+ modes = []mode{fetchMode, pullMode, mergeMode}
)
// NewGui creates a Gui opject and fill it's state related entites
-func NewGui(directoies []string) (*Gui, error) {
+func NewGui(mode string, directoies []string) (*Gui, error) {
initialState := guiState{
Directories: directoies,
Mode: fetchMode,
@@ -82,6 +83,12 @@ func NewGui(directoies []string) (*Gui, error) {
gui := &Gui{
State: initialState,
}
+ for _, m := range modes {
+ if string(m.ModeID) == mode {
+ gui.State.Mode = m
+ break
+ }
+ }
return gui, nil
}
@@ -194,6 +201,22 @@ func (gui *Gui) layout(g *gocui.Gui) error {
return nil
}
+// focus to next view
+func (gui *Gui) nextMainView(g *gocui.Gui, v *gocui.View) error {
+ if err := gui.nextViewOfGroup(g, v, mainViews); err != nil {
+ return err
+ }
+ return nil
+}
+
+// focus to previous view
+func (gui *Gui) previousMainView(g *gocui.Gui, v *gocui.View) error {
+ if err := gui.previousViewOfGroup(g, v, mainViews); err != nil {
+ return err
+ }
+ return nil
+}
+
// quit from the gui and end its loop
func (gui *Gui) quit(g *gocui.Gui, v *gocui.View) error {
return gocui.ErrQuit
diff --git a/pkg/gui/keybindings.go b/pkg/gui/keybindings.go
index b2622bc..1a6d6c9 100644
--- a/pkg/gui/keybindings.go
+++ b/pkg/gui/keybindings.go
@@ -43,7 +43,7 @@ func (gui *Gui) generateKeybindings() error {
View: view.Name,
Key: gocui.KeyArrowLeft,
Modifier: gocui.ModNone,
- Handler: gui.previousView,
+ Handler: gui.previousMainView,
Display: "←",
Description: "Previous Panel",
Vital: false,
@@ -51,7 +51,7 @@ func (gui *Gui) generateKeybindings() error {
View: view.Name,
Key: gocui.KeyArrowRight,
Modifier: gocui.ModNone,
- Handler: gui.nextView,
+ Handler: gui.nextMainView,
Display: "→",
Description: "Next Panel",
Vital: false,
@@ -59,7 +59,7 @@ func (gui *Gui) generateKeybindings() error {
View: view.Name,
Key: 'l',
Modifier: gocui.ModNone,
- Handler: gui.nextView,
+ Handler: gui.nextMainView,
Display: "l",
Description: "Previous Panel",
Vital: false,
@@ -67,7 +67,7 @@ func (gui *Gui) generateKeybindings() error {
View: view.Name,
Key: 'h',
Modifier: gocui.ModNone,
- Handler: gui.previousView,
+ Handler: gui.previousMainView,
Display: "h",
Description: "Next Panel",
Vital: false,
@@ -77,7 +77,143 @@ func (gui *Gui) generateKeybindings() error {
gui.KeyBindings = append(gui.KeyBindings, binding)
}
}
+ // Statusviews common keybindings
+ for _, view := range statusViews {
+ statusKeybindings := []*KeyBinding{
+ {
+ View: view.Name,
+ Key: 'c',
+ Modifier: gocui.ModNone,
+ Handler: gui.closeStatusView,
+ Display: "c",
+ Description: "Close/Cancel",
+ Vital: true,
+ }, {
+ View: view.Name,
+ Key: gocui.KeyArrowLeft,
+ Modifier: gocui.ModNone,
+ Handler: gui.previousStatusView,
+ Display: "←",
+ Description: "Previous Panel",
+ Vital: false,
+ }, {
+ View: view.Name,
+ Key: gocui.KeyArrowRight,
+ Modifier: gocui.ModNone,
+ Handler: gui.nextStatusView,
+ Display: "→",
+ Description: "Next Panel",
+ Vital: false,
+ }, {
+ View: view.Name,
+ Key: 'l',
+ Modifier: gocui.ModNone,
+ Handler: gui.nextStatusView,
+ Display: "l",
+ Description: "Previous Panel",
+ Vital: false,
+ }, {
+ View: view.Name,
+ Key: 'h',
+ Modifier: gocui.ModNone,
+ Handler: gui.previousStatusView,
+ Display: "h",
+ Description: "Next Panel",
+ Vital: false,
+ }, {
+ View: view.Name,
+ Key: gocui.KeyArrowUp,
+ Modifier: gocui.ModNone,
+ Handler: gui.statusCursorUp,
+ Display: "↑",
+ Description: "Up",
+ Vital: false,
+ }, {
+ View: view.Name,
+ Key: gocui.KeyArrowDown,
+ Modifier: gocui.ModNone,
+ Handler: gui.statusCursorDown,
+ Display: "↓",
+ Description: "Down",
+ Vital: false,
+ }, {
+ View: view.Name,
+ Key: 'k',
+ Modifier: gocui.ModNone,
+ Handler: gui.statusCursorUp,
+ Display: "k",
+ Description: "Up",
+ Vital: false,
+ }, {
+ View: view.Name,
+ Key: 'j',
+ Modifier: gocui.ModNone,
+ Handler: gui.statusCursorDown,
+ Display: "j",
+ Description: "Down",
+ Vital: false,
+ }, {
+ View: view.Name,
+ Key: 't',
+ Modifier: gocui.ModNone,
+ Handler: gui.stashChanges,
+ Display: "t",
+ Description: "Save to Stash",
+ Vital: true,
+ },
+ }
+ for _, binding := range statusKeybindings {
+ gui.KeyBindings = append(gui.KeyBindings, binding)
+ }
+ }
individualKeybindings := []*KeyBinding{
+ // stash view
+ {
+ View: stashViewFeature.Name,
+ Key: 'p',
+ Modifier: gocui.ModNone,
+ Handler: gui.popStash,
+ Display: "p",
+ Description: "Pop Item",
+ Vital: true,
+ },
+ // staged view
+ {
+ View: stageViewFeature.Name,
+ Key: 'r',
+ Modifier: gocui.ModNone,
+ Handler: gui.resetChanges,
+ Display: "r",
+ Description: "Reset Item",
+ Vital: true,
+ }, {
+ View: stageViewFeature.Name,
+ Key: gocui.KeyCtrlR,
+ Modifier: gocui.ModNone,
+ Handler: gui.resetAllChanges,
+ Display: "ctrl+r",
+ Description: "Reset All Items",
+ Vital: true,
+ },
+ // unstaged view
+ {
+ View: unstageViewFeature.Name,
+ Key: 'a',
+ Modifier: gocui.ModNone,
+ Handler: gui.addChanges,
+ Display: "a",
+ Description: "Add Item",
+ Vital: true,
+ }, {
+ View: unstageViewFeature.Name,
+ Key: gocui.KeyCtrlA,
+ Modifier: gocui.ModNone,
+ Handler: gui.addAllChanges,
+ Display: "ctrl+a",
+ Description: "Add All Items",
+ Vital: true,
+ },
+ // Main view controls
{
View: mainViewFeature.Name,
Key: gocui.KeyArrowUp,
@@ -167,6 +303,14 @@ func (gui *Gui) generateKeybindings() error {
Description: "Sort repositories by Modification date",
Vital: false,
}, {
+ View: mainViewFeature.Name,
+ Key: 's',
+ Modifier: gocui.ModNone,
+ Handler: gui.openStatusView,
+ Display: "s",
+ Description: "Open Status",
+ Vital: true,
+ }, {
View: "",
Key: gocui.KeyCtrlC,
Modifier: gocui.ModNone,
@@ -174,8 +318,8 @@ func (gui *Gui) generateKeybindings() error {
Display: "ctrl + c",
Description: "Force application to quit",
Vital: false,
- },
- // Branch View Controls
+ },
+ // Branch View Controls
{
View: branchViewFeature.Name,
Key: gocui.KeyArrowDown,
@@ -209,7 +353,7 @@ func (gui *Gui) generateKeybindings() error {
Description: "Up",
Vital: false,
},
- // Remote View Controls
+ // Remote View Controls
{
View: remoteViewFeature.Name,
Key: gocui.KeyArrowDown,
@@ -243,7 +387,7 @@ func (gui *Gui) generateKeybindings() error {
Description: "Up",
Vital: false,
},
- // Remote Branch View Controls
+ // Remote Branch View Controls
{
View: remoteBranchViewFeature.Name,
Key: gocui.KeyArrowDown,
@@ -276,8 +420,16 @@ func (gui *Gui) generateKeybindings() error {
Display: "k",
Description: "Up",
Vital: false,
+ }, {
+ View: remoteBranchViewFeature.Name,
+ Key: 's',
+ Modifier: gocui.ModNone,
+ Handler: gui.syncRemoteBranch,
+ Display: "s",
+ Description: "Synch with Remote",
+ Vital: true,
},
- // Commit View Controls
+ // Commit View Controls
{
View: commitViewFeature.Name,
Key: gocui.KeyArrowDown,
@@ -286,7 +438,7 @@ func (gui *Gui) generateKeybindings() error {
Display: "↓",
Description: "Iterate over commits",
Vital: false,
- },{
+ }, {
View: commitViewFeature.Name,
Key: gocui.KeyArrowUp,
Modifier: gocui.ModNone,
@@ -310,7 +462,7 @@ func (gui *Gui) generateKeybindings() error {
Display: "k",
Description: "Up",
Vital: false,
- },{
+ }, {
View: commitViewFeature.Name,
Key: 'd',
Modifier: gocui.ModNone,
@@ -318,8 +470,8 @@ func (gui *Gui) generateKeybindings() error {
Display: "d",
Description: "Show commit diff",
Vital: true,
- },
- // Diff View Controls
+ },
+ // Diff View Controls
{
View: commitDiffViewFeature.Name,
Key: 'c',
@@ -360,8 +512,8 @@ func (gui *Gui) generateKeybindings() error {
Display: "j",
Description: "Page down",
Vital: false,
- },
- // Application Controls
+ },
+ // Application Controls
{
View: cheatSheetViewFeature.Name,
Key: 'c',
@@ -402,8 +554,8 @@ func (gui *Gui) generateKeybindings() error {
Display: "j",
Description: "Down",
Vital: false,
- },
- // Error View
+ },
+ // Error View
{
View: errorViewFeature.Name,
Key: 'c',
@@ -412,7 +564,39 @@ func (gui *Gui) generateKeybindings() error {
Display: "c",
Description: "close/cancel",
Vital: true,
- },
+ }, {
+ View: errorViewFeature.Name,
+ Key: gocui.KeyArrowUp,
+ Modifier: gocui.ModNone,
+ Handler: gui.fastCursorUp,
+ Display: "↑",
+ Description: "Up",
+ Vital: true,
+ }, {
+ View: errorViewFeature.Name,
+ Key: gocui.KeyArrowDown,
+ Modifier: gocui.ModNone,
+ Handler: gui.fastCursorDown,
+ Display: "↓",
+ Description: "Down",
+ Vital: true,
+ }, {
+ View: errorViewFeature.Name,
+ Key: 'k',
+ Modifier: gocui.ModNone,
+ Handler: gui.fastCursorUp,
+ Display: "k",
+ Description: "Up",
+ Vital: false,
+ }, {
+ View: errorViewFeature.Name,
+ Key: 'j',
+ Modifier: gocui.ModNone,
+ Handler: gui.fastCursorDown,
+ Display: "j",
+ Description: "Down",
+ Vital: false,
+ },
}
for _, binding := range individualKeybindings {
gui.KeyBindings = append(gui.KeyBindings, binding)
diff --git a/pkg/gui/mainview.go b/pkg/gui/mainview.go
index 58f8422..b421775 100644
--- a/pkg/gui/mainview.go
+++ b/pkg/gui/mainview.go
@@ -2,6 +2,7 @@ package gui
import (
"fmt"
+ "sort"
"github.com/isacikgoz/gitbatch/pkg/git"
"github.com/isacikgoz/gitbatch/pkg/queue"
@@ -36,6 +37,19 @@ func (gui *Gui) fillMain(g *gocui.Gui) error {
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)
+ if err != nil {
+ return err
+ }
+ mainView.Clear()
+ for _, r := range gui.State.Repositories {
+ fmt.Fprintln(mainView, gui.displayString(r))
+ }
+ return nil
+}
+
// moves the cursor downwards for the main view and if it goes to bottom it
// prevents from going further
func (gui *Gui) cursorDown(g *gocui.Gui, v *gocui.View) error {
@@ -135,12 +149,12 @@ func (gui *Gui) removeFromQueue(entity *git.RepoEntity) error {
func (gui *Gui) markRepository(g *gocui.Gui, v *gocui.View) error {
r := gui.getSelectedRepository()
if r.State == git.Available || r.State == git.Success {
- if err := gui.addToQueue(r); err !=nil {
+ if err := gui.addToQueue(r); err != nil {
return err
}
} else if r.State == git.Queued {
- if err := gui.removeFromQueue(r); err !=nil {
- return err
+ if err := gui.removeFromQueue(r); err != nil {
+ return err
}
} else {
return nil
@@ -154,7 +168,7 @@ func (gui *Gui) markRepository(g *gocui.Gui, v *gocui.View) error {
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 {
+ if err := gui.addToQueue(r); err != nil {
return err
}
} else {
@@ -170,7 +184,7 @@ func (gui *Gui) markAllRepositories(g *gocui.Gui, v *gocui.View) error {
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 {
+ if err := gui.removeFromQueue(r); err != nil {
return err
}
} else {
@@ -181,15 +195,25 @@ func (gui *Gui) unmarkAllRepositories(g *gocui.Gui, v *gocui.View) error {
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)
- if err != nil {
- return err
- }
- mainView.Clear()
- for _, r := range gui.State.Repositories {
- fmt.Fprintln(mainView, gui.displayString(r))
- }
+// 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
}
diff --git a/pkg/gui/remotebranchview.go b/pkg/gui/remotebranchview.go
index eb65d55..6c58565 100644
--- a/pkg/gui/remotebranchview.go
+++ b/pkg/gui/remotebranchview.go
@@ -38,6 +38,26 @@ func (gui *Gui) updateRemoteBranches(g *gocui.Gui, entity *git.RepoEntity) error
}
// iteration handler for the remotebranchview
+func (gui *Gui) syncRemoteBranch(g *gocui.Gui, v *gocui.View) error {
+ var err error
+ entity := gui.getSelectedRepository()
+ if err = git.Fetch(entity, git.FetchOptions{
+ RemoteName: entity.Remote.Name,
+ Prune: true,
+ }); err != nil {
+ return err
+ }
+ // have no idea why this works..
+ // some time need to fix, movement aint bad huh?
+ gui.nextRemote(g, v)
+ gui.previousRemote(g, v)
+ if err = gui.updateRemoteBranches(g, entity); err != nil {
+ return err
+ }
+ return nil
+}
+
+// iteration handler for the remotebranchview
func (gui *Gui) nextRemoteBranch(g *gocui.Gui, v *gocui.View) error {
var err error
entity := gui.getSelectedRepository()
diff --git a/pkg/gui/remotesview.go b/pkg/gui/remotesview.go
index 6f31265..55366b2 100644
--- a/pkg/gui/remotesview.go
+++ b/pkg/gui/remotesview.go
@@ -72,4 +72,4 @@ func (gui *Gui) remoteChangeFollowUp(g *gocui.Gui, entity *git.RepoEntity) (err
return err
}
return nil
-} \ No newline at end of file
+}
diff --git a/pkg/gui/stagedview.go b/pkg/gui/stagedview.go
new file mode 100644
index 0000000..3e83a96
--- /dev/null
+++ b/pkg/gui/stagedview.go
@@ -0,0 +1,84 @@
+package gui
+
+import (
+ "fmt"
+
+ "github.com/isacikgoz/gitbatch/pkg/git"
+ "github.com/jroimartin/gocui"
+)
+
+// staged view
+func (gui *Gui) openStageView(g *gocui.Gui) error {
+ maxX, maxY := g.Size()
+
+ v, err := g.SetView(stageViewFeature.Name, 6, 5, maxX/2-1, int(0.75*float32(maxY))-1)
+ if err != nil {
+ if err != gocui.ErrUnknownView {
+ return err
+ }
+ v.Title = stageViewFeature.Title
+ }
+ entity := gui.getSelectedRepository()
+ if err := refreshStagedView(g, entity); err != nil {
+ return err
+ }
+ gui.updateKeyBindingsView(g, stageViewFeature.Name)
+ if _, err := g.SetCurrentView(stageViewFeature.Name); err != nil {
+ return err
+ }
+ return nil
+}
+
+func (gui *Gui) resetChanges(g *gocui.Gui, v *gocui.View) error {
+ entity := gui.getSelectedRepository()
+ files, _, err := generateFileLists(entity)
+ if err != nil {
+ return err
+ }
+ if len(files) <= 0 {
+ return nil
+ }
+ _, cy := v.Cursor()
+ _, oy := v.Origin()
+ if err := files[cy+oy].Reset(git.ResetOptions{}); err != nil {
+ return err
+ }
+ if err := refreshAllStatusView(g, entity); err != nil {
+ return err
+ }
+ return nil
+}
+
+func (gui *Gui) resetAllChanges(g *gocui.Gui, v *gocui.View) error {
+ entity := gui.getSelectedRepository()
+ if err := entity.ResetAll(git.ResetOptions{}); err != nil {
+ return err
+ }
+ if err := refreshAllStatusView(g, entity); err != nil {
+ return err
+ }
+ return nil
+}
+
+// refresh the main view and re-render the repository representations
+func refreshStagedView(g *gocui.Gui, entity *git.RepoEntity) error {
+ stageView, err := g.View(stageViewFeature.Name)
+ if err != nil {
+ return err
+ }
+ stageView.Clear()
+ _, cy := stageView.Cursor()
+ _, oy := stageView.Origin()
+ files, _, err := generateFileLists(entity)
+ if err != nil {
+ return err
+ }
+ for i, file := range files {
+ var prefix string
+ if i == cy+oy {
+ prefix = prefix + selectionIndicator
+ }
+ fmt.Fprintf(stageView, "%s%s%s %s\n", prefix, green.Sprint(string(file.X)), red.Sprint(string(file.Y)), file.Name)
+ }
+ return nil
+}
diff --git a/pkg/gui/stashview.go b/pkg/gui/stashview.go
new file mode 100644
index 0000000..cfdc104
--- /dev/null
+++ b/pkg/gui/stashview.go
@@ -0,0 +1,89 @@
+package gui
+
+import (
+ "fmt"
+
+ "github.com/isacikgoz/gitbatch/pkg/git"
+ "github.com/jroimartin/gocui"
+)
+
+// stash view
+func (gui *Gui) openStashView(g *gocui.Gui) error {
+ maxX, maxY := g.Size()
+
+ v, err := g.SetView(stashViewFeature.Name, 6, int(0.75*float32(maxY)), maxX-6, maxY-3)
+ if err != nil {
+ if err != gocui.ErrUnknownView {
+ return err
+ }
+ v.Title = stashViewFeature.Title
+ }
+ entity := gui.getSelectedRepository()
+ if err := refreshStashView(g, entity); err != nil {
+ return err
+ }
+ return nil
+}
+
+//
+func (gui *Gui) stashChanges(g *gocui.Gui, v *gocui.View) error {
+ entity := gui.getSelectedRepository()
+ output, err := entity.Stash()
+ if err != nil {
+ if err = gui.openErrorView(g, output,
+ "You should manually resolve this issue",
+ stashViewFeature.Name); err != nil {
+ return err
+ }
+ }
+ if err := refreshAllStatusView(g, entity); err != nil {
+ return err
+ }
+ return nil
+}
+
+//
+func (gui *Gui) popStash(g *gocui.Gui, v *gocui.View) error {
+ entity := gui.getSelectedRepository()
+ _, oy := v.Origin()
+ _, cy := v.Cursor()
+ if len(entity.Stasheds) <= 0 {
+ return nil
+ }
+ stashedItem := entity.Stasheds[oy+cy]
+ output, err := stashedItem.Pop()
+ if err != nil {
+ if err = gui.openErrorView(g, output,
+ "You should manually resolve this issue",
+ stashViewFeature.Name); err != nil {
+ return err
+ }
+ }
+ if err := entity.Refresh(); err != nil {
+ return err
+ }
+ if err := refreshAllStatusView(g, entity); err != nil {
+ return err
+ }
+ return nil
+}
+
+// refresh the main view and re-render the repository representations
+func refreshStashView(g *gocui.Gui, entity *git.RepoEntity) error {
+ stashView, err := g.View(stashViewFeature.Name)
+ if err != nil {
+ return err
+ }
+ stashView.Clear()
+ _, cy := stashView.Cursor()
+ _, oy := stashView.Origin()
+ stashedItems := entity.Stasheds
+ for i, stashedItem := range stashedItems {
+ var prefix string
+ if i == cy+oy {
+ prefix = prefix + selectionIndicator
+ }
+ fmt.Fprintf(stashView, "%s%d %s: %s (%s)\n", prefix, stashedItem.StashID, cyan.Sprint(stashedItem.BranchName), stashedItem.Description, cyan.Sprint(stashedItem.Hash))
+ }
+ return nil
+}
diff --git a/pkg/gui/statusview.go b/pkg/gui/statusview.go
new file mode 100644
index 0000000..d95a56f
--- /dev/null
+++ b/pkg/gui/statusview.go
@@ -0,0 +1,174 @@
+package gui
+
+import (
+ "fmt"
+
+ "github.com/isacikgoz/gitbatch/pkg/git"
+ "github.com/jroimartin/gocui"
+)
+
+var (
+ statusHeaderViewFeature = viewFeature{Name: "status-header", Title: " Status Header "}
+ // statusViewFeature = viewFeature{Name: "status", Title: " Status "}
+ stageViewFeature = viewFeature{Name: "staged", Title: " Staged "}
+ unstageViewFeature = viewFeature{Name: "unstaged", Title: " Unstaged "}
+ stashViewFeature = viewFeature{Name: "stash", Title: " Stash "}
+
+ statusViews = []viewFeature{stageViewFeature, unstageViewFeature, stashViewFeature}
+)
+
+// open the status layout
+func (gui *Gui) openStatusView(g *gocui.Gui, v *gocui.View) error {
+ gui.openStatusHeaderView(g)
+ gui.openStageView(g)
+ gui.openUnStagedView(g)
+ gui.openStashView(g)
+ return nil
+}
+
+// focus to next view
+func (gui *Gui) nextStatusView(g *gocui.Gui, v *gocui.View) error {
+ if err := gui.nextViewOfGroup(g, v, statusViews); err != nil {
+ return err
+ }
+ return nil
+}
+
+// focus to previous view
+func (gui *Gui) previousStatusView(g *gocui.Gui, v *gocui.View) error {
+ if err := gui.previousViewOfGroup(g, v, statusViews); err != nil {
+ return err
+ }
+ return nil
+}
+
+// moves the cursor downwards for the main view and if it goes to bottom it
+// prevents from going further
+func (gui *Gui) statusCursorDown(g *gocui.Gui, v *gocui.View) error {
+ if v != nil {
+ cx, cy := v.Cursor()
+ ox, oy := v.Origin()
+ ly := len(v.BufferLines()) - 2 // why magic number? have no idea
+
+ // if we are at the end we just return
+ if cy+oy == ly {
+ return nil
+ }
+ if err := v.SetCursor(cx, cy+1); err != nil {
+
+ if err := v.SetOrigin(ox, oy+1); err != nil {
+ return err
+ }
+ }
+ entity := gui.getSelectedRepository()
+ if err := refreshStatusView(v.Name(), g, entity); err != nil {
+ return err
+ }
+ }
+ return nil
+}
+
+// moves the cursor upwards for the main view
+func (gui *Gui) statusCursorUp(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
+ }
+ }
+ entity := gui.getSelectedRepository()
+ if err := refreshStatusView(v.Name(), g, entity); err != nil {
+ return err
+ }
+ }
+ return nil
+}
+
+// header og the status layout
+func (gui *Gui) openStatusHeaderView(g *gocui.Gui) error {
+ maxX, _ := g.Size()
+ entity := gui.getSelectedRepository()
+ v, err := g.SetView(statusHeaderViewFeature.Name, 6, 2, maxX-6, 4)
+ if err != nil {
+ if err != gocui.ErrUnknownView {
+ return err
+ }
+ fmt.Fprintln(v, entity.AbsPath)
+ // v.Frame = false
+ v.Wrap = true
+ }
+ return nil
+}
+
+// close the opened stat views
+func (gui *Gui) closeStatusView(g *gocui.Gui, v *gocui.View) error {
+ if err := g.DeleteView(stashViewFeature.Name); err != nil {
+ return err
+ }
+ if err := g.DeleteView(unstageViewFeature.Name); err != nil {
+ return err
+ }
+ if err := g.DeleteView(stageViewFeature.Name); err != nil {
+ return err
+ }
+ if err := g.DeleteView(statusHeaderViewFeature.Name); err != nil {
+ return err
+ }
+ if _, err := g.SetCurrentView(mainViewFeature.Name); err != nil {
+ return err
+ }
+ entity := gui.getSelectedRepository()
+ if err := gui.refreshMain(g); err != nil {
+ return err
+ }
+ if err := gui.refreshViews(g, entity); err != nil {
+ return err
+ }
+ gui.updateKeyBindingsView(g, mainViewFeature.Name)
+ return nil
+}
+
+func generateFileLists(entity *git.RepoEntity) (staged, unstaged []*git.File, err error) {
+ files, err := entity.LoadFiles()
+ if err != nil {
+ return nil, nil, err
+ }
+ for _, file := range files {
+ if file.X != git.StatusNotupdated && file.X != git.StatusUntracked && file.X != git.StatusIgnored && file.X != git.StatusUpdated {
+ staged = append(staged, file)
+ }
+ if file.Y != git.StatusNotupdated {
+ unstaged = append(unstaged, file)
+ }
+ }
+ return staged, unstaged, err
+}
+
+func refreshStatusView(viewName string, g *gocui.Gui, entity *git.RepoEntity) error {
+ switch viewName {
+ case stageViewFeature.Name:
+ if err := refreshStagedView(g, entity); err != nil {
+ return err
+ }
+ case unstageViewFeature.Name:
+ if err := refreshUnstagedView(g, entity); err != nil {
+ return err
+ }
+ case stashViewFeature.Name:
+ if err := refreshStashView(g, entity); err != nil {
+ return err
+ }
+ }
+ return nil
+}
+
+func refreshAllStatusView(g *gocui.Gui, entity *git.RepoEntity) error {
+ for _, v := range statusViews {
+ if err := refreshStatusView(v.Name, g, entity); err != nil {
+ return err
+ }
+ }
+ return nil
+}
diff --git a/pkg/gui/textstyle.go b/pkg/gui/textstyle.go
index 554a590..50bc945 100644
--- a/pkg/gui/textstyle.go
+++ b/pkg/gui/textstyle.go
@@ -45,7 +45,7 @@ var (
keyBindingSeperator = "░"
selectionIndicator = ws + string(green.Sprint("→")) + ws
- tab = ws
+ tab = ws
)
// this function handles the render and representation of the repository
@@ -56,11 +56,11 @@ func (gui *Gui) displayString(entity *git.RepoEntity) string {
repoName := ""
if entity.Branch.Pushables != "?" {
- prefix = prefix + pushable + ws + entity.Branch.Pushables +
- ws + pullable + ws + entity.Branch.Pullables
+ prefix = prefix + pushable + ws + entity.Branch.Pushables +
+ ws + pullable + ws + entity.Branch.Pullables
} else {
- prefix = prefix + pushable + ws + yellow.Sprint(entity.Branch.Pushables) +
- ws + pullable + ws + yellow.Sprint(entity.Branch.Pullables)
+ prefix = prefix + pushable + ws + yellow.Sprint(entity.Branch.Pushables) +
+ ws + pullable + ws + yellow.Sprint(entity.Branch.Pullables)
}
selectedEntity := gui.getSelectedRepository()
diff --git a/pkg/gui/unstagedview.go b/pkg/gui/unstagedview.go
new file mode 100644
index 0000000..93082cc
--- /dev/null
+++ b/pkg/gui/unstagedview.go
@@ -0,0 +1,80 @@
+package gui
+
+import (
+ "fmt"
+
+ "github.com/isacikgoz/gitbatch/pkg/git"
+ "github.com/jroimartin/gocui"
+)
+
+// not staged view
+func (gui *Gui) openUnStagedView(g *gocui.Gui) error {
+ maxX, maxY := g.Size()
+
+ v, err := g.SetView(unstageViewFeature.Name, maxX/2+1, 5, maxX-6, int(0.75*float32(maxY))-1)
+ if err != nil {
+ if err != gocui.ErrUnknownView {
+ return err
+ }
+ v.Title = unstageViewFeature.Title
+ }
+ entity := gui.getSelectedRepository()
+ if err := refreshUnstagedView(g, entity); err != nil {
+ return err
+ }
+ return nil
+}
+
+func (gui *Gui) addChanges(g *gocui.Gui, v *gocui.View) error {
+ entity := gui.getSelectedRepository()
+ _, files, err := generateFileLists(entity)
+ if err != nil {
+ return err
+ }
+ if len(files) <= 0 {
+ return nil
+ }
+ _, cy := v.Cursor()
+ _, oy := v.Origin()
+ if err := files[cy+oy].Add(git.AddOptions{}); err != nil {
+ return err
+ }
+ if err := refreshAllStatusView(g, entity); err != nil {
+ return err
+ }
+ return nil
+}
+
+func (gui *Gui) addAllChanges(g *gocui.Gui, v *gocui.View) error {
+ entity := gui.getSelectedRepository()
+ if err := entity.AddAll(git.AddOptions{}); err != nil {
+ return err
+ }
+ if err := refreshAllStatusView(g, entity); err != nil {
+ return err
+ }
+ return nil
+}
+
+// refresh the main view and re-render the repository representations
+func refreshUnstagedView(g *gocui.Gui, entity *git.RepoEntity) error {
+ stageView, err := g.View(unstageViewFeature.Name)
+ if err != nil {
+ return err
+ }
+ stageView.Clear()
+ _, cy := stageView.Cursor()
+ _, oy := stageView.Origin()
+ _, files, err := generateFileLists(entity)
+ if err != nil {
+ return err
+ }
+ for i, file := range files {
+ var prefix string
+ if i == cy+oy {
+ prefix = prefix + selectionIndicator
+ }
+ fmt.Fprintf(stageView, "%s%s%s %s\n", prefix, red.Sprint(string(file.X)), red.Sprint(string(file.Y)), file.Name)
+ }
+ return nil
+}
diff --git a/pkg/helpers/command.go b/pkg/helpers/command.go
index 2485b56..b861812 100644
--- a/pkg/helpers/command.go
+++ b/pkg/helpers/command.go
@@ -19,7 +19,7 @@ func RunCommandWithOutput(dir string, command string, args []string) (string, er
}
// GetCommandStatus returns if we supposed to get return value as an int of a command
-// this method can be used. It is practical when you use a command and process a
+// this method can be used. It is practical when you use a command and process a
// failover acoording to a soecific return code
func GetCommandStatus(dir string, command string, args []string) (int, error) {
cmd := exec.Command(command, args...)
diff --git a/pkg/queue/job.go b/pkg/queue/job.go
index e2dd94e..1fe5ba1 100644
--- a/pkg/queue/job.go
+++ b/pkg/queue/job.go
@@ -63,6 +63,5 @@ func (job *Job) start() error {
return nil
}
job.Entity.State = git.Success
- job.Entity.Refresh()
return nil
}