diff options
| author | Ibrahim Serdar Acikgoz <serdaracikgoz86@gmail.com> | 2018-12-16 22:16:50 +0300 |
|---|---|---|
| committer | GitHub <noreply@github.com> | 2018-12-16 22:16:50 +0300 |
| commit | 9301127f8be4ff3c60ec3378a1d29ee60ba378f6 (patch) | |
| tree | d1614dce8bd5ef92614932a0a91046e1124d0422 | |
| parent | Merge pull request #33 from isacikgoz/develop (diff) | |
| parent | add option to set upstream to branch and minor improvements (diff) | |
| download | gitbatch-9301127f8be4ff3c60ec3378a1d29ee60ba378f6.tar.gz | |
Merge pull request #34 from isacikgoz/develop
Develop
| -rw-r--r-- | main.go | 26 | ||||
| -rw-r--r-- | pkg/app/app.go | 66 | ||||
| -rw-r--r-- | pkg/app/config.go | 39 | ||||
| -rw-r--r-- | pkg/app/quick.go | 48 | ||||
| -rw-r--r-- | pkg/git/cmd-config.go | 8 | ||||
| -rw-r--r-- | pkg/git/cmd-fetch.go | 14 | ||||
| -rw-r--r-- | pkg/git/cmd-pull.go | 104 | ||||
| -rw-r--r-- | pkg/git/repository.go | 60 | ||||
| -rw-r--r-- | pkg/git/util-load.go | 2 | ||||
| -rw-r--r-- | pkg/gui/branchview.go | 74 | ||||
| -rw-r--r-- | pkg/gui/commitsview.go | 67 | ||||
| -rw-r--r-- | pkg/gui/controlsview.go (renamed from pkg/gui/cheatsheet.go) | 0 | ||||
| -rw-r--r-- | pkg/gui/keybindings.go | 196 | ||||
| -rw-r--r-- | pkg/gui/remotebranchview.go | 78 | ||||
| -rw-r--r-- | pkg/gui/remotesview.go | 73 | ||||
| -rw-r--r-- | pkg/gui/sideviews.go | 283 | ||||
| -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
@@ -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 |
