summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorIbrahim Serdar Acikgoz <serdaracikgoz86@gmail.com>2018-12-16 22:16:50 +0300
committerGitHub <noreply@github.com>2018-12-16 22:16:50 +0300
commit9301127f8be4ff3c60ec3378a1d29ee60ba378f6 (patch)
treed1614dce8bd5ef92614932a0a91046e1124d0422
parentMerge pull request #33 from isacikgoz/develop (diff)
parentadd option to set upstream to branch and minor improvements (diff)
downloadgitbatch-9301127f8be4ff3c60ec3378a1d29ee60ba378f6.tar.gz
Merge pull request #34 from isacikgoz/develop
Develop
-rw-r--r--main.go26
-rw-r--r--pkg/app/app.go66
-rw-r--r--pkg/app/config.go39
-rw-r--r--pkg/app/quick.go48
-rw-r--r--pkg/git/cmd-config.go8
-rw-r--r--pkg/git/cmd-fetch.go14
-rw-r--r--pkg/git/cmd-pull.go104
-rw-r--r--pkg/git/repository.go60
-rw-r--r--pkg/git/util-load.go2
-rw-r--r--pkg/gui/branchview.go74
-rw-r--r--pkg/gui/commitsview.go67
-rw-r--r--pkg/gui/controlsview.go (renamed from pkg/gui/cheatsheet.go)0
-rw-r--r--pkg/gui/keybindings.go196
-rw-r--r--pkg/gui/remotebranchview.go78
-rw-r--r--pkg/gui/remotesview.go73
-rw-r--r--pkg/gui/sideviews.go283
-rw-r--r--pkg/gui/util-common.go (renamed from pkg/gui/gui-util.go)0
-rw-r--r--pkg/gui/util-queuehandler.go (renamed from pkg/gui/queuehandler.go)0
-rw-r--r--pkg/gui/util-textstyle.go (renamed from pkg/gui/textstyle.go)0
19 files changed, 633 insertions, 505 deletions
diff --git a/main.go b/main.go
index cb8a9eb..0a8b5b3 100644
--- a/main.go
+++ b/main.go
@@ -1,33 +1,31 @@
package main
import (
- "os"
-
"github.com/isacikgoz/gitbatch/pkg/app"
log "github.com/sirupsen/logrus"
"gopkg.in/alecthomas/kingpin.v2"
)
var (
- // take this as default directory if user does not start app with -d flag
- currentDir, err = os.Getwd()
- 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()
+ dirs = kingpin.Flag("directory", "Directory(s) to roam for git repositories.").Short('d').Strings()
+ mode = kingpin.Flag("mode", "Application start mode, more sensible with quick run.").Short('m').String()
+ recurseDepth = kingpin.Flag("recursive-depth", "Find directories recursively.").Default("0").Short('r').Int()
+ logLevel = kingpin.Flag("log-level", "Logging level; trace,debug,info,warn,error").Default("error").Short('l').String()
+ quick = kingpin.Flag("quick", "runs without gui and fetches/pull remote upstream.").Short('q').Bool()
)
func main() {
- kingpin.Version("gitbatch version 0.1.0 (alpha)")
+ kingpin.Version("gitbatch version 0.1.1 (alpha)")
// parse the command line flag and options
kingpin.Parse()
// set the app
- app, err := app.Setup(app.SetupConfig{
- Directories: *dirs,
- LogLevel: *logLevel,
- IgnoreConfig: *ignoreConfig,
- Depth: *recurseDepth,
+ app, err := app.Setup(&app.SetupConfig{
+ Directories: *dirs,
+ LogLevel: *logLevel,
+ Depth: *recurseDepth,
+ QuickMode: *quick,
+ Mode: *mode,
})
if err != nil {
log.Fatal(err)
diff --git a/pkg/app/app.go b/pkg/app/app.go
index 740e3ff..9c9e9a7 100644
--- a/pkg/app/app.go
+++ b/pkg/app/app.go
@@ -1,6 +1,8 @@
package app
import (
+ "os"
+
"github.com/isacikgoz/gitbatch/pkg/gui"
log "github.com/sirupsen/logrus"
)
@@ -9,40 +11,48 @@ import (
// it has only the gui.Gui pointer for interface entity.
type App struct {
Gui *gui.Gui
- Config *Config
+ Config *SetupConfig
}
// SetupConfig is an assembler data to initiate a setup
type SetupConfig struct {
- Directories []string
- LogLevel string
- IgnoreConfig bool
- Depth int
+ Directories []string
+ LogLevel string
+ Depth int
+ QuickMode bool
+ Mode string
}
// Setup will handle pre-required operations. It is designed to be a wrapper for
// main method right now.
-func Setup(setupConfig SetupConfig) (*App, error) {
+func Setup(setupConfig *SetupConfig) (*App, error) {
// initiate the app and give it initial values
app := &App{}
- setLogLevel(setupConfig.LogLevel)
- var err error
- app.Config, err = LoadConfiguration()
+ if len(setupConfig.Directories) <= 0 {
+ d, _ := os.Getwd()
+ setupConfig.Directories = []string{d}
+ }
+
+ appConfig, err := overrideDefaults(setupConfig)
if err != nil {
- // the error types and handling is not considered yer
- log.Error(err)
- return app, err
+ return nil, err
}
- var directories []string
- if len(app.Config.Directories) <= 0 || setupConfig.IgnoreConfig {
- directories = generateDirectories(setupConfig.Directories, setupConfig.Depth)
- } else {
- directories = generateDirectories(app.Config.Directories, setupConfig.Depth)
+ setLogLevel(appConfig.LogLevel)
+ directories := generateDirectories(appConfig.Directories, appConfig.Depth)
+
+ if appConfig.QuickMode {
+ x := appConfig.Mode == "fetch"
+ y := appConfig.Mode == "pull"
+ if x == y {
+ log.Fatal("Unrecognized quick mode: " + appConfig.Mode)
+ }
+ quick(directories, appConfig.Depth, appConfig.Mode)
+ log.Fatal("Finished")
}
// create a gui.Gui struct and set it as App's gui
- app.Gui, err = gui.NewGui(app.Config.Mode, directories)
+ app.Gui, err = gui.NewGui(appConfig.Mode, directories)
if err != nil {
// the error types and handling is not considered yer
log.Error(err)
@@ -79,3 +89,23 @@ func setLogLevel(logLevel string) {
"level": logLevel,
}).Trace("logging level has been set")
}
+
+func overrideDefaults(setupConfig *SetupConfig) (appConfig *SetupConfig, err error) {
+ appConfig, err = LoadConfiguration()
+ if len(setupConfig.Directories) > 0 {
+ appConfig.Directories = setupConfig.Directories
+ }
+ if len(setupConfig.LogLevel) > 0 {
+ appConfig.LogLevel = setupConfig.LogLevel
+ }
+ if setupConfig.Depth > 0 {
+ appConfig.Depth = setupConfig.Depth
+ }
+ if setupConfig.QuickMode {
+ appConfig.QuickMode = setupConfig.QuickMode
+ }
+ if len(setupConfig.Mode) > 0 {
+ appConfig.Mode = setupConfig.Mode
+ }
+ return appConfig, err
+}
diff --git a/pkg/app/config.go b/pkg/app/config.go
index b613532..57c2e6e 100644
--- a/pkg/app/config.go
+++ b/pkg/app/config.go
@@ -9,12 +9,6 @@ import (
"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"
@@ -28,14 +22,20 @@ var (
// configuration items
var (
- modeKey = "mode"
- modeKeyDefault = "fetch"
- pathsKey = "paths"
- pathsKeyDefault = []string{"."}
+ modeKey = "mode"
+ modeKeyDefault = "fetch"
+ pathsKey = "paths"
+ pathsKeyDefault = []string{"."}
+ logLevelKey = "loglevel"
+ logLevelKeyDefault = "error"
+ qucikKey = "quick"
+ qucikKeyDefault = false
+ recursionKey = "recursion"
+ recursionKeyDefault = 1
)
// LoadConfiguration returns a Config struct is filled
-func LoadConfiguration() (*Config, error) {
+func LoadConfiguration() (*SetupConfig, error) {
if err := initializeConfigurationManager(); err != nil {
return nil, err
}
@@ -45,15 +45,28 @@ func LoadConfiguration() (*Config, error) {
if err := readConfiguration(); err != nil {
return nil, err
}
- config := &Config{
+ var directories []string
+ if len(viper.GetStringSlice(pathsKey)) <= 0 {
+ d, _ := os.Getwd()
+ directories = []string{d}
+ } else {
+ directories = viper.GetStringSlice(pathsKey)
+ }
+ config := &SetupConfig{
+ Directories: directories,
+ LogLevel: viper.GetString(logLevelKey),
+ Depth: viper.GetInt(recursionKey),
+ QuickMode: viper.GetBool(qucikKey),
Mode: viper.GetString(modeKey),
- Directories: viper.GetStringSlice(pathsKey),
}
return config, nil
}
// set default configuration parameters
func setDefaults() error {
+ viper.SetDefault(logLevelKey, logLevelKeyDefault)
+ viper.SetDefault(qucikKey, qucikKeyDefault)
+ viper.SetDefault(recursionKey, recursionKeyDefault)
viper.SetDefault(modeKey, modeKeyDefault)
// viper.SetDefault(pathsKey, pathsKeyDefault)
return nil
diff --git a/pkg/app/quick.go b/pkg/app/quick.go
new file mode 100644
index 0000000..fba5d8f
--- /dev/null
+++ b/pkg/app/quick.go
@@ -0,0 +1,48 @@
+package app
+
+import (
+ "fmt"
+ "sync"
+ "time"
+
+ "github.com/isacikgoz/gitbatch/pkg/git"
+)
+
+func quick(directories []string, depth int, mode string) {
+
+ var wg sync.WaitGroup
+ start := time.Now()
+ for _, dir := range directories {
+ wg.Add(1)
+ go func(d string, mode string) {
+ defer wg.Done()
+ err := operate(d, mode)
+ if err != nil {
+ fmt.Printf("%s: %s\n", d, err.Error())
+ } else {
+ fmt.Printf("%s: successful\n", d)
+ }
+ }(dir, mode)
+ }
+ wg.Wait()
+ elapsed := time.Since(start)
+ fmt.Printf("%d repositories finished in: %s\n", len(directories), elapsed)
+}
+
+func operate(directory, mode string) error {
+ r, err := git.FastInitializeRepo(directory)
+ if err != nil {
+ return err
+ }
+ switch mode {
+ case "fetch":
+ return git.Fetch(r, git.FetchOptions{
+ RemoteName: "origin",
+ })
+ case "pull":
+ return git.Pull(r, git.PullOptions{
+ RemoteName: "origin",
+ })
+ }
+ return nil
+}
diff --git a/pkg/git/cmd-config.go b/pkg/git/cmd-config.go
index 541b0be..dfbf661 100644
--- a/pkg/git/cmd-config.go
+++ b/pkg/git/cmd-config.go
@@ -28,7 +28,7 @@ type ConfigSite string
const (
// ConfigStieLocal
- ConfigStieLocal ConfigSite = "local"
+ ConfigSiteLocal ConfigSite = "local"
// ConfgiSiteGlobal
ConfgiSiteGlobal ConfigSite = "global"
)
@@ -86,10 +86,10 @@ func AddConfig(entity *RepoEntity, options ConfigOptions, value string) (err err
}
-// addConfigWithGit is simply a bare git commit -m <msg> command which is flexible
+// addConfigWithGit is simply a bare git config --add <option> command which is flexible
func addConfigWithGit(entity *RepoEntity, options ConfigOptions, value string) (err error) {
args := make([]string, 0)
- args = append(args, commitCommand)
+ args = append(args, configCommand)
if len(string(options.Site)) > 0 {
args = append(args, "--"+string(options.Site))
}
@@ -99,7 +99,7 @@ func addConfigWithGit(entity *RepoEntity, options ConfigOptions, value string) (
args = append(args, value)
}
if err := GenericGitCommand(entity.AbsPath, args); err != nil {
- log.Warn("Error at git command (commit)")
+ log.Warn("Error at git command (config)")
return err
}
// till this step everything should be ok
diff --git a/pkg/git/cmd-fetch.go b/pkg/git/cmd-fetch.go
index 8a8bf9c..d1d6abb 100644
--- a/pkg/git/cmd-fetch.go
+++ b/pkg/git/cmd-fetch.go
@@ -54,7 +54,13 @@ func Fetch(entity *RepoEntity, options FetchOptions) (err error) {
return err
case fetchCmdModeNative:
// this should be the refspec as default, let's give it a try
- refspec := "+" + "refs/heads/" + entity.Branch.Name + ":" + "/refs/remotes/" + entity.Remote.Branch.Name
+ // TODO: Fix for quick mode, maybe better read config file
+ var refspec string
+ if entity.Branch == nil {
+ refspec = "+refs/heads/*:refs/remotes/origin/*"
+ } else {
+ refspec = "+" + "refs/heads/" + entity.Branch.Name + ":" + "/refs/remotes/" + entity.Remote.Branch.Name
+ }
err = fetchWithGoGit(entity, options, refspec)
return err
}
@@ -85,8 +91,7 @@ func fetchWithGit(entity *RepoEntity, options FetchOptions) (err error) {
return err
}
// till this step everything should be ok
- err = entity.Refresh()
- return err
+ return entity.Refresh()
}
// fetchWithGoGit is the primary fetch method and refspec is the main feature.
@@ -141,6 +146,5 @@ func fetchWithGoGit(entity *RepoEntity, options FetchOptions, refspec string) (e
}
}
// till this step everything should be ok
- err = entity.Refresh()
- return err
+ return entity.Refresh()
}
diff --git a/pkg/git/cmd-pull.go b/pkg/git/cmd-pull.go
new file mode 100644
index 0000000..a493b19
--- /dev/null
+++ b/pkg/git/cmd-pull.go
@@ -0,0 +1,104 @@
+package git
+
+import (
+ log "github.com/sirupsen/logrus"
+ "gopkg.in/src-d/go-git.v4"
+ "gopkg.in/src-d/go-git.v4/plumbing"
+ "gopkg.in/src-d/go-git.v4/plumbing/transport/http"
+)
+
+var (
+ pullCmdMode string
+ pullTryCount int
+
+ pullCommand = "pull"
+ pullCmdModeLegacy = "git"
+ pullCmdModeNative = "go-git"
+ pullMaxTry = 1
+)
+
+// PullOptions defines the rules for pull operation
+type PullOptions struct {
+ // Name of the remote to fetch from. Defaults to origin.
+ RemoteName string
+ // ReferenceName Remote branch to clone. If empty, uses HEAD.
+ ReferenceName string
+ // Fetch only ReferenceName if true.
+ SingleBranch bool
+ // Credentials holds the user and pswd information
+ Credentials Credentials
+ // Force allows the pull to update a local branch even when the remote
+ // branch does not descend from it.
+ Force bool
+}
+
+// Pull ncorporates changes from a remote repository into the current branch.
+func Pull(entity *RepoEntity, options PullOptions) (err error) {
+ // here we configure pull operation
+ // default mode is go-git (this may be configured)
+ pullCmdMode = pullCmdModeNative
+ pullTryCount = 0
+
+ switch pullCmdMode {
+ case pullCmdModeLegacy:
+ err = pullWithGit(entity, options)
+ return err
+ case pullCmdModeNative:
+ err = pullWithGoGit(entity, options)
+ return err
+ }
+ return nil
+}
+
+func pullWithGit(entity *RepoEntity, options PullOptions) (err error) {
+ args := make([]string, 0)
+ args = append(args, pullCommand)
+ // parse options to command line arguments
+ if len(options.RemoteName) > 0 {
+ args = append(args, options.RemoteName)
+ }
+ if options.Force {
+ args = append(args, "-f")
+ }
+ if err := GenericGitCommand(entity.AbsPath, args); err != nil {
+ log.Warn("Error at git command (pull)")
+ return err
+ }
+ return entity.Refresh()
+}
+
+func pullWithGoGit(entity *RepoEntity, options PullOptions) (err error) {
+ opt := &git.PullOptions{
+ RemoteName: options.RemoteName,
+ SingleBranch: options.SingleBranch,
+ Force: options.Force,
+ }
+ if len(options.ReferenceName) > 0 {
+ ref := plumbing.NewRemoteReferenceName(options.RemoteName, options.ReferenceName)
+ opt.ReferenceName = ref
+ }
+ // if any credential is given, let's add it to the git.PullOptions
+ if len(options.Credentials.User) > 0 {
+ protocol, err := entity.authProtocol(entity.Remote)
+ if err != nil {
+ return err
+ }
+ if protocol == authProtocolHttp || protocol == authProtocolHttps {
+ opt.Auth = &http.BasicAuth{
+ Username: options.Credentials.User,
+ Password: options.Credentials.Password,
+ }
+ } else {
+ return ErrInvalidAuthMethod
+ }
+ }
+ w, err := entity.Repository.Worktree()
+ if err != nil {
+ return err
+ }
+ err = w.Pull(opt)
+ if err != nil {
+ return err
+ }
+ return entity.Refresh()
+}
diff --git a/pkg/git/repository.go b/pkg/git/repository.go
index ff67611..f37c813 100644
--- a/pkg/git/repository.go
+++ b/pkg/git/repository.go
@@ -47,33 +47,12 @@ const (
Fail RepoState = 5
)
-// InitializeRepository initializes a RepoEntity struct with its belongings.
-func InitializeRepository(directory string) (entity *RepoEntity, err error) {
- file, err := os.Open(directory)
+// InitializeRepo initializes a RepoEntity struct with its belongings.
+func InitializeRepo(directory string) (entity *RepoEntity, err error) {
+ entity, err = FastInitializeRepo(directory)
if err != nil {
- log.WithFields(log.Fields{
- "directory": directory,
- }).Trace("Cannot open as directory")
- return nil, err
- }
- fileInfo, err := file.Stat()
- if err != nil {
- return nil, err
- }
- r, err := git.PlainOpen(directory)
- if err != nil {
- log.WithFields(log.Fields{
- "directory": directory,
- }).Trace("Cannot open directory as a git repository")
return nil, err
}
- entity = &RepoEntity{RepoID: helpers.RandomString(8),
- Name: fileInfo.Name(),
- AbsPath: directory,
- ModTime: fileInfo.ModTime(),
- Repository: *r,
- State: Available,
- }
// after we intiate the struct we can fill its values
entity.loadLocalBranches()
entity.loadCommits()
@@ -107,11 +86,44 @@ func InitializeRepository(directory string) (entity *RepoEntity, err error) {
return entity, nil
}
+// FastInitializeRepo initializes a RepoEntity struct without its belongings.
+func FastInitializeRepo(directory string) (entity *RepoEntity, err error) {
+ file, err := os.Open(directory)
+ if err != nil {
+ log.WithFields(log.Fields{
+ "directory": directory,
+ }).Trace("Cannot open as directory")
+ return nil, err
+ }
+ fileInfo, err := file.Stat()
+ if err != nil {
+ return nil, err
+ }
+ r, err := git.PlainOpen(directory)
+ if err != nil {
+ log.WithFields(log.Fields{
+ "directory": directory,
+ }).Trace("Cannot open directory as a git repository")
+ return nil, err
+ }
+ entity = &RepoEntity{RepoID: helpers.RandomString(8),
+ Name: fileInfo.Name(),
+ AbsPath: directory,
+ ModTime: fileInfo.ModTime(),
+ Repository: *r,
+ State: Available,
+ }
+ return entity, nil
+}
+
// Refresh the belongings of a repositoriy, this function is called right after
// 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
+ if entity.Branch == nil {
+ return nil
+ }
file, _ := os.Open(entity.AbsPath)
fileInfo, err := file.Stat()
if err != nil {
diff --git a/pkg/git/util-load.go b/pkg/git/util-load.go
index e35e1c1..bf19de8 100644
--- a/pkg/git/util-load.go
+++ b/pkg/git/util-load.go
@@ -24,7 +24,7 @@ func LoadRepositoryEntities(directories []string) (entities []*RepoEntity, err e
// decrement the wait counter by one, we call it in a defer so it's
// called at the end of this goroutine
defer wg.Done()
- entity, err := InitializeRepository(d)
+ entity, err := InitializeRepo(d)
if err != nil {
log.WithFields(log.Fields{
"directory": d,
diff --git a/pkg/gui/branchview.go b/pkg/gui/branchview.go
deleted file mode 100644
index f69a212..0000000
--- a/pkg/gui/branchview.go
+++ /dev/null
@@ -1,74 +0,0 @@
-package gui
-
-import (
- "fmt"
-
- "github.com/isacikgoz/gitbatch/pkg/git"
- "github.com/jroimartin/gocui"
-)
-
-// updates the branchview for given entity
-func (gui *Gui) updateBranch(g *gocui.Gui, entity *git.RepoEntity) error {
- var err error
- out, err := g.View(branchViewFeature.Name)
- if err != nil {
- return err
- }
- out.Clear()
-
- currentindex := 0
- totalbranches := len(entity.Branches)
- for i, b := range entity.Branches {
- if b.Name == entity.Branch.Name {
- currentindex = i
- fmt.Fprintln(out, selectionIndicator+b.Name)
- continue
- }
- fmt.Fprintln(out, tab+b.Name)
- }
- err = gui.smartAnchorRelativeToLine(out, currentindex, totalbranches)
- return err
-}
-
-// iteration handler for the branchview
-func (gui *Gui) nextBranch(g *gocui.Gui, v *gocui.View) error {
- var err error
- entity := gui.getSelectedRepository()
- if err = entity.Checkout(entity.NextBranch()); err != nil {
- err = gui.openErrorView(g, err.Error(),
- "You should manually resolve this issue",
- branchViewFeature.Name)
- return err
- }
- err = gui.checkoutFollowUp(g, entity)
- return err
-}
-
-// iteration handler for the branchview
-func (gui *Gui) previousBranch(g *gocui.Gui, v *gocui.View) error {
- var err error
- entity := gui.getSelectedRepository()
- if err = entity.Checkout(entity.PreviousBranch()); err != nil {
- err = gui.openErrorView(g, err.Error(),
- "You should manually resolve this issue",
- branchViewFeature.Name)
- return err
- }
- err = gui.checkoutFollowUp(g, entity)
- return err
-}
-
-// after checkout a branch some refreshments needed
-func (gui *Gui) checkoutFollowUp(g *gocui.Gui, entity *git.RepoEntity) (err error) {
- if err = gui.updateBranch(g, entity); err != nil {
- return err
- }
- if err = gui.updateCommits(g, entity); err != nil {
- return err
- }
- if err = gui.updateRemoteBranches(g, entity); err != nil {
- return err
- }
- err = gui.refreshMain(g)
- return err
-}
diff --git a/pkg/gui/commitsview.go b/pkg/gui/commitsview.go
deleted file mode 100644
index 6d1325c..0000000
--- a/pkg/gui/commitsview.go
+++ /dev/null
@@ -1,67 +0,0 @@
-package gui
-
-import (
- "fmt"
-
- "github.com/isacikgoz/gitbatch/pkg/git"
- "github.com/jroimartin/gocui"
-)
-
-// updates the commitsview for given entity
-func (gui *Gui) updateCommits(g *gocui.Gui, entity *git.RepoEntity) error {
- var err error
- out, err := g.View(commitViewFeature.Name)
- if err != nil {
- return err
- }
- out.Clear()
-
- currentindex := 0
- totalcommits := len(entity.Commits)
- for i, c := range entity.Commits {
- var body string
- if c.CommitType == git.EvenCommit {
- body = cyan.Sprint(c.Hash[:hashLength]) + " " + c.Message
- } else if c.CommitType == git.LocalCommit {
- body = blue.Sprint(c.Hash[:hashLength]) + " " + c.Message
- } else {
- body = yellow.Sprint(c.Hash[:hashLength]) + " " + c.Message
- }
- if c.Hash == entity.Commit.Hash {
- currentindex = i
- fmt.Fprintln(out, selectionIndicator+body)
- continue
- }
- fmt.Fprintln(out, tab+body)
- }
- if err = gui.smartAnchorRelativeToLine(out, currentindex, totalcommits); err != nil {
- return err
- }
- return err
-}
-
-// iteration handler for the commitsview
-func (gui *Gui) nextCommit(g *gocui.Gui, v *gocui.View) error {
- var err error
- entity := gui.getSelectedRepository()
- if err = entity.NextCommit(); err != nil {
- return err
- }
- if err = gui.updateCommits(g, entity); err != nil {
- return err
- }
- return err
-}
-
-// reverse iteration handler for the commitsview
-func (gui *Gui) prevCommit(g *gocui.Gui, v *gocui.View) error {
- var err error
- entity := gui.getSelectedRepository()
- if err = entity.PreviousCommit(); err != nil {
- return err
- }
- if err = gui.updateCommits(g, entity); err != nil {
- return err
- }
- return err
-}
diff --git a/pkg/gui/cheatsheet.go b/pkg/gui/controlsview.go
index 4b2f97e..4b2f97e 100644
--- a/pkg/gui/cheatsheet.go
+++ b/pkg/gui/controlsview.go
diff --git a/pkg/gui/keybindings.go b/pkg/gui/keybindings.go
index 936c7c6..eff7952 100644
--- a/pkg/gui/keybindings.go
+++ b/pkg/gui/keybindings.go
@@ -67,6 +67,44 @@ func (gui *Gui) generateKeybindings() error {
}
gui.KeyBindings = append(gui.KeyBindings, mainKeybindings...)
}
+ for _, view := range sideViews {
+ sideViewKeybindings := []*KeyBinding{
+ {
+ View: view.Name,
+ Key: gocui.KeyArrowDown,
+ Modifier: gocui.ModNone,
+ Handler: gui.sideViewsNextItem,
+ Display: "↓",
+ Description: "Iterate over branches",
+ Vital: false,
+ }, {
+ View: view.Name,
+ Key: gocui.KeyArrowUp,
+ Modifier: gocui.ModNone,
+ Handler: gui.sideViewsPreviousItem,
+ Display: "↑",
+ Description: "Iterate over branches",
+ Vital: false,
+ }, {
+ View: view.Name,
+ Key: 'j',
+ Modifier: gocui.ModNone,
+ Handler: gui.sideViewsNextItem,
+ Display: "j",
+ Description: "Down",
+ Vital: false,
+ }, {
+ View: view.Name,
+ Key: 'k',
+ Modifier: gocui.ModNone,
+ Handler: gui.sideViewsPreviousItem,
+ Display: "k",
+ Description: "Up",
+ Vital: false,
+ },
+ }
+ gui.KeyBindings = append(gui.KeyBindings, sideViewKeybindings...)
+ }
// Statusviews common keybindings
for _, view := range statusViews {
statusKeybindings := []*KeyBinding{
@@ -374,108 +412,6 @@ func (gui *Gui) generateKeybindings() error {
Display: "ctrl + c",
Description: "Force application to quit",
Vital: false,
- },
- // Branch View Controls
- {
- View: branchViewFeature.Name,
- Key: gocui.KeyArrowDown,
- Modifier: gocui.ModNone,
- Handler: gui.nextBranch,
- Display: "↓",
- Description: "Iterate over branches",
- Vital: false,
- }, {
- View: branchViewFeature.Name,
- Key: gocui.KeyArrowUp,
- Modifier: gocui.ModNone,
- Handler: gui.previousBranch,
- Display: "↑",
- Description: "Iterate over branches",
- Vital: false,
- }, {
- View: branchViewFeature.Name,
- Key: 'j',
- Modifier: gocui.ModNone,
- Handler: gui.nextBranch,
- Display: "j",
- Description: "Down",
- Vital: false,
- }, {
- View: branchViewFeature.Name,
- Key: 'k',
- Modifier: gocui.ModNone,
- Handler: gui.previousBranch,
- Display: "k",
- Description: "Up",
- Vital: false,
- },
- // Remote View Controls
- {
- View: remoteViewFeature.Name,
- Key: gocui.KeyArrowDown,
- Modifier: gocui.ModNone,
- Handler: gui.nextRemote,
- Display: "↓",
- Description: "Iterate over remotes",
- Vital: false,
- }, {
- View: remoteViewFeature.Name,
- Key: gocui.KeyArrowUp,
- Modifier: gocui.ModNone,
- Handler: gui.previousRemote,
- Display: "↑",
- Description: "Iterate over remotes",
- Vital: false,
- }, {
- View: remoteViewFeature.Name,
- Key: 'j',
- Modifier: gocui.ModNone,
- Handler: gui.nextRemote,
- Display: "j",
- Description: "Down",
- Vital: false,
- }, {
- View: remoteViewFeature.Name,
- Key: 'k',
- Modifier: gocui.ModNone,
- Handler: gui.previousRemote,
- Display: "k",
- Description: "Up",
- Vital: false,
- },
- // Remote Branch View Controls
- {
- View: remoteBranchViewFeature.Name,
- Key: gocui.KeyArrowDown,
- Modifier: gocui.ModNone,
- Handler: gui.nextRemoteBranch,
- Display: "↓",
- Description: "Iterate over remote branches",
- Vital: false,
- }, {
- View: remoteBranchViewFeature.Name,
- Key: gocui.KeyArrowUp,
- Modifier: gocui.ModNone,
- Handler: gui.previousRemoteBranch,
- Display: "↑",
- Description: "Iterate over remote branches",
- Vital: false,
- }, {
- View: remoteBranchViewFeature.Name,
- Key: 'j',
- Modifier: gocui.ModNone,
- Handler: gui.nextRemoteBranch,
- Display: "j",
- Description: "Down",
- Vital: false,
- }, {
- View: remoteBranchViewFeature.Name,
- Key: 'k',
- Modifier: gocui.ModNone,
- Handler: gui.previousRemoteBranch,
- Display: "k",
- Description: "Up",
- Vital: false,
}, {
View: remoteBranchViewFeature.Name,
Key: 's',
@@ -484,40 +420,14 @@ func (gui *Gui) generateKeybindings() error {
Display: "s",
Description: "Synch with Remote",
Vital: true,
- },
- // Commit View Controls
- {
- View: commitViewFeature.Name,
- Key: gocui.KeyArrowDown,
- Modifier: gocui.ModNone,
- Handler: gui.nextCommit,
- Display: "↓",
- Description: "Iterate over commits",
- Vital: false,
- }, {
- View: commitViewFeature.Name,
- Key: gocui.KeyArrowUp,
- Modifier: gocui.ModNone,
- Handler: gui.prevCommit,
- Display: "↑",
- Description: "Iterate over commits",
- Vital: false,
}, {
- View: commitViewFeature.Name,
- Key: 'j',
- Modifier: gocui.ModNone,
- Handler: gui.nextCommit,
- Display: "j",
- Description: "Down",
- Vital: false,
- }, {
- View: commitViewFeature.Name,
- Key: 'k',
+ View: branchViewFeature.Name,
+ Key: 'u',
Modifier: gocui.ModNone,
- Handler: gui.prevCommit,
- Display: "k",
- Description: "Up",
- Vital: false,
+ Handler: gui.setUpstreamToBranch,
+ Display: "u",
+ Description: "Set Upstream",
+ Vital: true,
}, {
View: commitViewFeature.Name,
Key: 'd',
@@ -527,6 +437,24 @@ func (gui *Gui) generateKeybindings() error {
Description: "Show commit diff",
Vital: true,
},
+ // upstream confirmation
+ {
+ View: confirmationViewFeature.Name,
+ Key: gocui.KeyEsc,
+ Modifier: gocui.ModNone,
+ Handler: gui.closeConfirmationView,
+ Display: "esc",
+ Description: "close/cancel",
+ Vital: true,
+ }, {
+ View: confirmationViewFeature.Name,
+ Key: gocui.KeyEnter,
+ Modifier: gocui.ModNone,
+ Handler: gui.confirmSetUpstreamToBranch,
+ Display: "enter",
+ Description: "Set Upstream",
+ Vital: true,
+ },
// Diff View Controls
{
View: diffViewFeature.Name,
diff --git a/pkg/gui/remotebranchview.go b/pkg/gui/remotebranchview.go
deleted file mode 100644
index 4974808..0000000
--- a/pkg/gui/remotebranchview.go
+++ /dev/null
@@ -1,78 +0,0 @@
-package gui
-
-import (
- "fmt"
-
- "github.com/isacikgoz/gitbatch/pkg/git"
- "github.com/jroimartin/gocui"
-)
-
-// updates the remotebranchview for given entity
-func (gui *Gui) updateRemoteBranches(g *gocui.Gui, entity *git.RepoEntity) error {
- var err error
- out, err := g.View(remoteBranchViewFeature.Name)
- if err != nil {
- return err
- }
- out.Clear()
- currentindex := 0
- trb := len(entity.Remote.Branches)
- if trb > 0 {
- for i, r := range entity.Remote.Branches {
- rName := r.Name
- if r.Deleted {
- rName = rName + ws + dirty
- }
- if r.Name == entity.Remote.Branch.Name {
- currentindex = i
- fmt.Fprintln(out, selectionIndicator+rName)
- continue
- }
- fmt.Fprintln(out, tab+rName)
- }
- if err = gui.smartAnchorRelativeToLine(out, currentindex, trb); err != nil {
- return err
- }
- }
- return nil
-}
-
-// 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)
- err = gui.updateRemoteBranches(g, entity)
- return err
-}
-
-// iteration handler for the remotebranchview
-func (gui *Gui) nextRemoteBranch(g *gocui.Gui, v *gocui.View) error {
- var err error
- entity := gui.getSelectedRepository()
- if err = entity.Remote.NextRemoteBranch(); err != nil {
- return err
- }
- err = gui.updateRemoteBranches(g, entity)
- return err
-}
-
-// iteration handler for the remotebranchview
-func (gui *Gui) previousRemoteBranch(g *gocui.Gui, v *gocui.View) error {
- var err error
- entity := gui.getSelectedRepository()
- if err = entity.Remote.PreviousRemoteBranch(); err != nil {
- return err
- }
- err = gui.updateRemoteBranches(g, entity)
- return err
-}
diff --git a/pkg/gui/remotesview.go b/pkg/gui/remotesview.go
deleted file mode 100644
index 51323dc..0000000
--- a/pkg/gui/remotesview.go
+++ /dev/null
@@ -1,73 +0,0 @@
-package gui
-
-import (
- "fmt"
-
- "github.com/isacikgoz/gitbatch/pkg/git"
- "github.com/jroimartin/gocui"
-)
-
-// updates the remotesview for given entity
-func (gui *Gui) updateRemotes(g *gocui.Gui, entity *git.RepoEntity) error {
- var err error
- out, err := g.View(remoteViewFeature.Name)
- if err != nil {
- return err
- }
- out.Clear()
-
- currentindex := 0
- totalRemotes := len(entity.Remotes)
- if totalRemotes > 0 {
- for i, r := range entity.Remotes {
- // TODO: maybe the text styling can be moved to textstyle.go file
- _, shortURL := trimRemoteURL(r.URL[0])
- suffix := shortURL
- if r.Name == entity.Remote.Name {
- currentindex = i
- fmt.Fprintln(out, selectionIndicator+r.Name+": "+suffix)
- continue
- }
- fmt.Fprintln(out, tab+r.Name+": "+suffix)
- }
- if err = gui.smartAnchorRelativeToLine(out, currentindex, totalRemotes); err != nil {
- return err
- }
- }
- return nil
-}
-
-// iteration handler for the remotesview
-func (gui *Gui) nextRemote(g *gocui.Gui, v *gocui.View) error {
- var err error
- entity := gui.getSelectedRepository()
- if err = entity.NextRemote(); err != nil {
- return err
- }
- if err = gui.remoteChangeFollowUp(g, entity); err != nil {
- return err
- }
- return err
-}
-
-// iteration handler for the remotesview
-func (gui *Gui) previousRemote(g *gocui.Gui, v *gocui.View) error {
- var err error
- entity := gui.getSelectedRepository()
- if err = entity.PreviousRemote(); err != nil {
- return err
- }
- if err = gui.remoteChangeFollowUp(g, entity); err != nil {
- return err
- }
- return err
-}
-
-// after checkout a remote some refreshments needed
-func (gui *Gui) remoteChangeFollowUp(g *gocui.Gui, entity *git.RepoEntity) (err error) {
- if err = gui.updateRemotes(g, entity); err != nil {
- return err
- }
- err = gui.updateRemoteBranches(g, entity)
- return err
-}
diff --git a/pkg/gui/sideviews.go b/pkg/gui/sideviews.go
new file mode 100644
index 0000000..86aa3cb
--- /dev/null
+++ b/pkg/gui/sideviews.go
@@ -0,0 +1,283 @@
+package gui
+
+import (
+ "fmt"
+
+ "github.com/isacikgoz/gitbatch/pkg/git"
+ "github.com/jroimartin/gocui"
+)
+
+var (
+ confirmationViewFeature = viewFeature{Name: "confirmation", Title: " Confirmation "}
+ sideViews = []viewFeature{remoteViewFeature, remoteBranchViewFeature, branchViewFeature, commitViewFeature}
+)
+
+// updates the remotesview for given entity
+func (gui *Gui) updateRemotes(g *gocui.Gui, entity *git.RepoEntity) error {
+ var err error
+ out, err := g.View(remoteViewFeature.Name)
+ if err != nil {
+ return err
+ }
+ out.Clear()
+
+ currentindex := 0
+ totalRemotes := len(entity.Remotes)
+ if totalRemotes > 0 {
+ for i, r := range entity.Remotes {
+ // TODO: maybe the text styling can be moved to textstyle.go file
+ _, shortURL := trimRemoteURL(r.URL[0])
+ suffix := shortURL
+ if r.Name == entity.Remote.Name {
+ currentindex = i
+ fmt.Fprintln(out, selectionIndicator+r.Name+": "+suffix)
+ continue
+ }
+ fmt.Fprintln(out, tab+r.Name+": "+suffix)
+ }
+ if err = gui.smartAnchorRelativeToLine(out, currentindex, totalRemotes); err != nil {
+ return err
+ }
+ }
+ return nil
+}
+
+// updates the remotebranchview for given entity
+func (gui *Gui) updateRemoteBranches(g *gocui.Gui, entity *git.RepoEntity) error {
+ var err error
+ out, err := g.View(remoteBranchViewFeature.Name)
+ if err != nil {
+ return err
+ }
+ out.Clear()
+ currentindex := 0
+ trb := len(entity.Remote.Branches)
+ if trb > 0 {
+ for i, r := range entity.Remote.Branches {
+ rName := r.Name
+ if r.Deleted {
+ rName = rName + ws + dirty
+ }
+ if r.Name == entity.Remote.Branch.Name {
+ currentindex = i
+ fmt.Fprintln(out, selectionIndicator+rName)
+ continue
+ }
+ fmt.Fprintln(out, tab+rName)
+ }
+ if err = gui.smartAnchorRelativeToLine(out, currentindex, trb); err != nil {
+ return err
+ }
+ }
+ return nil
+}
+
+// updates the branchview for given entity
+func (gui *Gui) updateBranch(g *gocui.Gui, entity *git.RepoEntity) error {
+ var err error
+ out, err := g.View(branchViewFeature.Name)
+ if err != nil {
+ return err
+ }
+ out.Clear()
+
+ currentindex := 0
+ totalbranches := len(entity.Branches)
+ for i, b := range entity.Branches {
+ if b.Name == entity.Branch.Name {
+ currentindex = i
+ fmt.Fprintln(out, selectionIndicator+b.Name)
+ continue
+ }
+ fmt.Fprintln(out, tab+b.Name)
+ }
+ err = gui.smartAnchorRelativeToLine(out, currentindex, totalbranches)
+ return err
+}
+
+// updates the commitsview for given entity
+func (gui *Gui) updateCommits(g *gocui.Gui, entity *git.RepoEntity) error {
+ var err error
+ out, err := g.View(commitViewFeature.Name)
+ if err != nil {
+ return err
+ }
+ out.Clear()
+
+ currentindex := 0
+ totalcommits := len(entity.Commits)
+ for i, c := range entity.Commits {
+ var body string
+ if c.CommitType == git.EvenCommit {
+ body = cyan.Sprint(c.Hash[:hashLength]) + " " + c.Message
+ } else if c.CommitType == git.LocalCommit {
+ body = blue.Sprint(c.Hash[:hashLength]) + " " + c.Message
+ } else {
+ body = yellow.Sprint(c.Hash[:hashLength]) + " " + c.Message
+ }
+ if c.Hash == entity.Commit.Hash {
+ currentindex = i
+ fmt.Fprintln(out, selectionIndicator+body)
+ continue
+ }
+ fmt.Fprintln(out, tab+body)
+ }
+ if err = gui.smartAnchorRelativeToLine(out, currentindex, totalcommits); err != nil {
+ return err
+ }
+ return err
+}
+
+func (gui *Gui) sideViewsNextItem(g *gocui.Gui, v *gocui.View) error {
+ var err error
+ entity := gui.getSelectedRepository()
+ switch viewName := v.Name(); viewName {
+ case remoteBranchViewFeature.Name:
+ if err = entity.Remote.NextRemoteBranch(); err != nil {
+ return err
+ }
+ err = gui.updateRemoteBranches(g, entity)
+ case remoteViewFeature.Name:
+ if err = entity.NextRemote(); err != nil {
+ return err
+ }
+ err = gui.remoteChangeFollowUp(g, entity)
+ case branchViewFeature.Name:
+ if err = entity.Checkout(entity.NextBranch()); err != nil {
+ err = gui.openErrorView(g, err.Error(),
+ "You should manually resolve this issue",
+ branchViewFeature.Name)
+ return err
+ }
+ err = gui.checkoutFollowUp(g, entity)
+ case commitViewFeature.Name:
+ if err = entity.NextCommit(); err != nil {
+ return err
+ }
+ err = gui.updateCommits(g, entity)
+ }
+ return err
+}
+
+func (gui *Gui) sideViewsPreviousItem(g *gocui.Gui, v *gocui.View) error {
+ var err error
+ entity := gui.getSelectedRepository()
+ switch viewName := v.Name(); viewName {
+ case remoteBranchViewFeature.Name:
+ if err = entity.Remote.PreviousRemoteBranch(); err != nil {
+ return err
+ }
+ err = gui.updateRemoteBranches(g, entity)
+ case remoteViewFeature.Name:
+ if err = entity.PreviousRemote(); err != nil {
+ return err
+ }
+ err = gui.remoteChangeFollowUp(g, entity)
+ case branchViewFeature.Name:
+ if err = entity.Checkout(entity.PreviousBranch()); err != nil {
+ err = gui.openErrorView(g, err.Error(),
+ "You should manually resolve this issue",
+ branchViewFeature.Name)
+ return err
+ }
+ err = gui.checkoutFollowUp(g, entity)
+ case commitViewFeature.Name:
+ if err = entity.PreviousCommit(); err != nil {
+ return err
+ }
+ err = gui.updateCommits(g, entity)
+ }
+ return err
+}
+
+// basically does fetch --prune
+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
+ }
+ vr, err := g.View(remoteViewFeature.Name)
+ if err != nil {
+ return err
+ }
+ // have no idea why this works..
+ // some time need to fix, movement aint bad huh?
+ gui.sideViewsNextItem(g, vr)
+ gui.sideViewsPreviousItem(g, vr)
+ err = gui.updateRemoteBranches(g, entity)
+ return err
+}
+
+// basically does fetch --prune
+func (gui *Gui) setUpstreamToBranch(g *gocui.Gui, v *gocui.View) error {
+ maxX, maxY := g.Size()
+
+ entity := gui.getSelectedRepository()
+ v, err := g.SetView(confirmationViewFeature.Name, maxX/2-30, maxY/2-2, maxX/2+30, maxY/2+2)
+ if err != nil {
+ if err != gocui.ErrUnknownView {
+ return err
+ }
+ fmt.Fprintln(v, "branch."+entity.Branch.Name+"."+"remote"+"="+entity.Remote.Name)
+ fmt.Fprintln(v, "branch."+entity.Branch.Name+"."+"merge"+"="+entity.Branch.Reference.Name().String())
+ }
+ return gui.focusToView(confirmationViewFeature.Name)
+}
+
+// basically does fetch --prune
+func (gui *Gui) confirmSetUpstreamToBranch(g *gocui.Gui, v *gocui.View) error {
+ var err error
+ entity := gui.getSelectedRepository()
+ if err = git.AddConfig(entity, git.ConfigOptions{
+ Section: "branch." + entity.Branch.Name,
+ Option: "remote",
+ Site: git.ConfigSiteLocal,
+ }, entity.Remote.Name); err != nil {
+ return err
+ }
+ if err = git.AddConfig(entity, git.ConfigOptions{
+ Section: "branch." + entity.Branch.Name,
+ Option: "merge",
+ Site: git.ConfigSiteLocal,
+ }, entity.Branch.Reference.Name().String()); err != nil {
+ return err
+ }
+ entity.Refresh()
+ gui.refreshMain(g)
+ return gui.closeConfirmationView(g, v)
+}
+
+func (gui *Gui) closeConfirmationView(g *gocui.Gui, v *gocui.View) error {
+ if err := g.DeleteView(v.Name()); err != nil {
+ return err
+ }
+ return gui.closeViewCleanup(branchViewFeature.Name)
+}
+
+// after checkout a remote some refreshments needed
+func (gui *Gui) remoteChangeFollowUp(g *gocui.Gui, entity *git.RepoEntity) (err error) {
+ if err = gui.updateRemotes(g, entity); err != nil {
+ return err
+ }
+ err = gui.updateRemoteBranches(g, entity)
+ return err
+}
+
+// after checkout a branch some refreshments needed
+func (gui *Gui) checkoutFollowUp(g *gocui.Gui, entity *git.RepoEntity) (err error) {
+ if err = gui.updateBranch(g, entity); err != nil {
+ return err
+ }
+ if err = gui.updateCommits(g, entity); err != nil {
+ return err
+ }
+ if err = gui.updateRemoteBranches(g, entity); err != nil {
+ return err
+ }
+ err = gui.refreshMain(g)
+ return err
+}
diff --git a/pkg/gui/gui-util.go b/pkg/gui/util-common.go
index f203bbf..f203bbf 100644
--- a/pkg/gui/gui-util.go
+++ b/pkg/gui/util-common.go
diff --git a/pkg/gui/queuehandler.go b/pkg/gui/util-queuehandler.go
index a814c47..a814c47 100644
--- a/pkg/gui/queuehandler.go
+++ b/pkg/gui/util-queuehandler.go
diff --git a/pkg/gui/textstyle.go b/pkg/gui/util-textstyle.go
index 7c36263..7c36263 100644
--- a/pkg/gui/textstyle.go
+++ b/pkg/gui/util-textstyle.go