diff options
| author | İbrahim Serdar Açıkgöz <serdaracikgoz86@gmail.com> | 2018-12-12 18:38:02 +0300 |
|---|---|---|
| committer | İbrahim Serdar Açıkgöz <serdaracikgoz86@gmail.com> | 2018-12-12 18:38:02 +0300 |
| commit | a1abe10a125e1b74c38726f02565140b1a440b95 (patch) | |
| tree | 3e75807e0ab6eada4916f97784627c656ffe187b | |
| parent | Merge branch 'develop' of github.com:isacikgoz/gitbatch into develop (diff) | |
| download | gitbatch-a1abe10a125e1b74c38726f02565140b1a440b95.tar.gz | |
implementing authentication for fetch operation
| -rw-r--r-- | pkg/git/authentication.go | 26 | ||||
| -rw-r--r-- | pkg/git/errors.go | 24 | ||||
| -rw-r--r-- | pkg/git/fetch.go | 23 | ||||
| -rw-r--r-- | pkg/git/job.go (renamed from pkg/queue/job.go) | 53 | ||||
| -rw-r--r-- | pkg/git/queue.go (renamed from pkg/queue/queue.go) | 8 | ||||
| -rw-r--r-- | pkg/gui/authenticationview.go | 165 | ||||
| -rw-r--r-- | pkg/gui/gui.go | 5 | ||||
| -rw-r--r-- | pkg/gui/keybindings.go | 32 | ||||
| -rw-r--r-- | pkg/gui/mainview.go | 11 | ||||
| -rw-r--r-- | pkg/gui/queuehandler.go | 9 | ||||
| -rw-r--r-- | pkg/gui/textstyle.go | 7 |
11 files changed, 320 insertions, 43 deletions
diff --git a/pkg/git/authentication.go b/pkg/git/authentication.go new file mode 100644 index 0000000..def1f99 --- /dev/null +++ b/pkg/git/authentication.go @@ -0,0 +1,26 @@ +package git + +import ( + "net/url" +) + +// Credentials holds user credentials to authenticate and authorize while +// communicating with remote if required +type Credentials struct { + User string + Password string +} + +var ( + authProtocolHttp = "http" + authProtocolHttps = "https" + authProtocolSSH = "ssh" +) + +func (entity *RepoEntity) authProtocol(remote *Remote) (p string, err error) { + u, err := url.Parse(remote.URL[0]) + if err != nil { + return p, err + } + return u.Scheme, err +} diff --git a/pkg/git/errors.go b/pkg/git/errors.go new file mode 100644 index 0000000..8621cbc --- /dev/null +++ b/pkg/git/errors.go @@ -0,0 +1,24 @@ +package git + +import ( + "errors" +) + +var ( + // ErrGitCommand is thrown when git command returned an error code + ErrGitCommand = errors.New("Git command returned error code") + // ErrAuthenticationRequired is thrown when an authentication required on + // a remote operation + ErrAuthenticationRequired = errors.New("Authentication required") + // ErrAuthorizationFailed is thrown when authorization failed while trying + // to authenticate with remote + ErrAuthorizationFailed = errors.New("Authorization failed") + // ErrInvalidAuthMethod is thrown when invalid auth method is invoked + ErrInvalidAuthMethod = errors.New("invalid auth method") + // ErrAlreadyUpToDate is thrown when a repository is already up to date + // with its src on merge/fetch/pull + ErrAlreadyUpToDate = errors.New("Already up to date") + // ErrCouldNotFindRemoteRef is thrown when trying to fetch/pull cannot + // find suitable remote reference + ErrCouldNotFindRemoteRef = errors.New("Could not find remote ref") +) diff --git a/pkg/git/fetch.go b/pkg/git/fetch.go index d0757bb..39a0277 100644 --- a/pkg/git/fetch.go +++ b/pkg/git/fetch.go @@ -6,6 +6,8 @@ import ( log "github.com/sirupsen/logrus" "gopkg.in/src-d/go-git.v4" "gopkg.in/src-d/go-git.v4/config" + "gopkg.in/src-d/go-git.v4/plumbing/transport" + "gopkg.in/src-d/go-git.v4/plumbing/transport/http" ) var ( @@ -22,6 +24,8 @@ var ( type FetchOptions struct { // Name of the remote to fetch from. Defaults to origin. RemoteName string + // Credentials holds the user and pswd information + Credentials Credentials // Before fetching, remove any remote-tracking references that no longer // exist on the remote. Prune bool @@ -96,6 +100,21 @@ func fetchWithGoGit(entity *RepoEntity, options FetchOptions, refspec string) (e RefSpecs: []config.RefSpec{config.RefSpec(refspec)}, Force: options.Force, } + 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 + } + } + err = entity.Repository.Fetch(opt) if err != nil { if err == git.NoErrAlreadyUpToDate { @@ -111,7 +130,9 @@ func fetchWithGoGit(entity *RepoEntity, options FetchOptions, refspec string) (e } else { return err } - // TODO: handle authentication exception + } else if err == transport.ErrAuthenticationRequired { + log.Warn(err.Error()) + return ErrAuthenticationRequired } else { log.Warn(err.Error()) return err diff --git a/pkg/queue/job.go b/pkg/git/job.go index 1fe5ba1..13ef566 100644 --- a/pkg/queue/job.go +++ b/pkg/git/job.go @@ -1,15 +1,14 @@ -package queue +package git import ( "time" - - "github.com/isacikgoz/gitbatch/pkg/git" ) // Job relates the type of the operation and the entity type Job struct { JobType JobType - Entity *git.RepoEntity + Entity *RepoEntity + Options interface{} } // JobType is the a git operation supported @@ -17,51 +16,57 @@ type JobType string const ( // Fetch is wrapper of git fetch command - Fetch JobType = "fetch" + FetchJob JobType = "fetch" // Pull is wrapper of git pull command - Pull JobType = "pull" + PullJob JobType = "pull" // Merge is wrapper of git merge command - Merge JobType = "merge" + MergeJob JobType = "merge" ) // starts the job func (job *Job) start() error { - job.Entity.State = git.Working + job.Entity.State = Working // added for testing, TODO: remove time.Sleep(time.Second) // TODO: Handle errors? switch mode := job.JobType; mode { - case Fetch: - if err := git.Fetch(job.Entity, git.FetchOptions{ - RemoteName: job.Entity.Remote.Name, - }); err != nil { - job.Entity.State = git.Fail - return nil + case FetchJob: + var opts FetchOptions + if job.Options != nil { + opts = job.Options.(FetchOptions) + } else { + opts = FetchOptions{ + RemoteName: job.Entity.Remote.Name, + } + } + if err := Fetch(job.Entity, opts); err != nil { + job.Entity.State = Fail + return err } - case Pull: - if err := git.Fetch(job.Entity, git.FetchOptions{ + case PullJob: + if err := Fetch(job.Entity, FetchOptions{ RemoteName: job.Entity.Remote.Name, }); err != nil { - job.Entity.State = git.Fail + job.Entity.State = Fail return nil } - if err := git.Merge(job.Entity, git.MergeOptions{ + if err := Merge(job.Entity, MergeOptions{ BranchName: job.Entity.Remote.Branch.Name, }); err != nil { - job.Entity.State = git.Fail + job.Entity.State = Fail return nil } - case Merge: - if err := git.Merge(job.Entity, git.MergeOptions{ + case MergeJob: + if err := Merge(job.Entity, MergeOptions{ BranchName: job.Entity.Remote.Branch.Name, }); err != nil { - job.Entity.State = git.Fail + job.Entity.State = Fail return nil } default: - job.Entity.State = git.Available + job.Entity.State = Available return nil } - job.Entity.State = git.Success + job.Entity.State = Success return nil } diff --git a/pkg/queue/queue.go b/pkg/git/queue.go index 93626f0..e95250a 100644 --- a/pkg/queue/queue.go +++ b/pkg/git/queue.go @@ -1,9 +1,7 @@ -package queue +package git import ( "errors" - - "github.com/isacikgoz/gitbatch/pkg/git" ) // JobQueue holds the slice of Jobs @@ -51,7 +49,7 @@ func (jobQueue *JobQueue) StartNext() (j *Job, finished bool, err error) { // RemoveFromQueue deletes the given entity and its job from the queue // TODO: it is not safe if the job has been started -func (jobQueue *JobQueue) RemoveFromQueue(entity *git.RepoEntity) error { +func (jobQueue *JobQueue) RemoveFromQueue(entity *RepoEntity) error { removed := false for i, job := range jobQueue.series { if job.Entity.RepoID == entity.RepoID { @@ -68,7 +66,7 @@ func (jobQueue *JobQueue) RemoveFromQueue(entity *git.RepoEntity) error { // IsInTheQueue function; since the job and entity is not tied with its own // struct, this function returns true if that entity is in the queue along with // the jobs type -func (jobQueue *JobQueue) IsInTheQueue(entity *git.RepoEntity) (inTheQueue bool, jt JobType) { +func (jobQueue *JobQueue) IsInTheQueue(entity *RepoEntity) (inTheQueue bool, jt JobType) { inTheQueue = false for _, job := range jobQueue.series { if job.Entity.RepoID == entity.RepoID { diff --git a/pkg/gui/authenticationview.go b/pkg/gui/authenticationview.go new file mode 100644 index 0000000..240b46c --- /dev/null +++ b/pkg/gui/authenticationview.go @@ -0,0 +1,165 @@ +package gui + +import ( + "fmt" + "github.com/isacikgoz/gitbatch/pkg/git" + "github.com/jroimartin/gocui" + log "github.com/sirupsen/logrus" +) + +var ( + authenticationReturnView string + authenticationViewFeature = viewFeature{Name: "authentication", Title: " Authentication "} + authUserFeature = viewFeature{Name: "authuser", Title: " User "} + authPasswordViewFeature = viewFeature{Name: "authpasswd", Title: " Password "} + authUserLabelFeature = viewFeature{Name: "authuserlabel", Title: " User: "} + authPswdLabelViewFeature = viewFeature{Name: "authpasswdlabel", Title: " Password: "} + + authViews = []viewFeature{authUserFeature, authPasswordViewFeature} + authLabels = []viewFeature{authenticationViewFeature, authUserLabelFeature, authPswdLabelViewFeature} + + jobRequiresAuth *git.Job +) + +// open an error view to inform user with a message and a useful note +func (gui *Gui) openAuthenticationView(g *gocui.Gui, jobQueue *git.JobQueue, job *git.Job, returnViewName string) error { + maxX, maxY := g.Size() + // lets remove this job from the queue so that it won't block anything + // also it is already unsuccessfully finished + jobRequiresAuth = job + if job.Entity.State != git.Fail { + if err := jobQueue.RemoveFromQueue(job.Entity); err != nil { + log.Fatal(err.Error()) + return err + } + } + + authenticationReturnView = returnViewName + v, err := g.SetView(authenticationViewFeature.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, " Enter credentials for: "+red.Sprint(job.Entity.Remote.URL[0])) + } + g.Cursor = true + if err := gui.openUserView(g); err != nil { + return nil + } + if err := gui.openPasswordView(g); err != nil { + return nil + } + return nil +} + +// close the opened error view +func (gui *Gui) closeAuthenticationView(g *gocui.Gui, v *gocui.View) error { + g.Cursor = false + for _, vf := range authLabels { + if err := g.DeleteView(vf.Name); err != nil { + return nil + } + } + for _, vf := range authViews { + if err := g.DeleteView(vf.Name); err != nil { + return nil + } + } + if _, err := g.SetCurrentView(authenticationReturnView); err != nil { + return err + } + gui.updateKeyBindingsView(g, authenticationReturnView) + return nil +} + +// close the opened error view +func (gui *Gui) submitAuthenticationView(g *gocui.Gui, v *gocui.View) error { + g.Cursor = false + v_user, err := g.View(authUserFeature.Name) + v_pswd, err := g.View(authPasswordViewFeature.Name) + creduser := v_user.ViewBuffer() + credpswd := v_pswd.ViewBuffer() + // Maybe pause implementation can be added + switch mode := jobRequiresAuth.JobType; mode { + case git.FetchJob: + jobRequiresAuth.Options = git.FetchOptions{ + RemoteName: jobRequiresAuth.Entity.Remote.Name, + Credentials: git.Credentials{ + User: creduser, + Password: credpswd, + }, + } + } + err = gui.State.Queue.AddJob(jobRequiresAuth) + if err != nil { + return err + } + jobRequiresAuth.Entity.State = git.Queued + gui.refreshMain(g) + gui.refreshViews(g, jobRequiresAuth.Entity) + gui.closeAuthenticationView(g, v) + return nil +} + +// open an error view to inform user with a message and a useful note +func (gui *Gui) openUserView(g *gocui.Gui) error { + maxX, maxY := g.Size() + vlabel, err := g.SetView(authUserLabelFeature.Name, maxX/2-30, maxY/2-1, maxX/2-19, maxY/2+1) + if err != nil { + if err != gocui.ErrUnknownView { + return err + } + fmt.Fprintln(vlabel, authUserLabelFeature.Title) + vlabel.Frame = false + } + v, err := g.SetView(authUserFeature.Name, maxX/2-18, maxY/2-1, maxX/2+29, maxY/2+1) + if err != nil { + if err != gocui.ErrUnknownView { + return err + } + v.Title = authUserFeature.Title + v.Editable = true + v.Frame = false + } + gui.updateKeyBindingsView(g, authUserFeature.Name) + if _, err := g.SetCurrentView(authUserFeature.Name); err != nil { + return err + } + return nil +} + +// open an error view to inform user with a message and a useful note +func (gui *Gui) openPasswordView(g *gocui.Gui) error { + maxX, maxY := g.Size() + vlabel, err := g.SetView(authPswdLabelViewFeature.Name, maxX/2-30, maxY/2, maxX/2-19, maxY/2+2) + if err != nil { + if err != gocui.ErrUnknownView { + return err + } + fmt.Fprintln(vlabel, authPswdLabelViewFeature.Title) + vlabel.Frame = false + } + v, err := g.SetView(authPasswordViewFeature.Name, maxX/2-18, maxY/2, maxX/2+29, maxY/2+2) + if err != nil { + if err != gocui.ErrUnknownView { + return err + } + v.Title = authPasswordViewFeature.Title + v.Editable = true + v.Mask ^= '*' + v.Frame = false + } + return nil +} + +// focus to next view +func (gui *Gui) nextAuthView(g *gocui.Gui, v *gocui.View) error { + err := gui.nextViewOfGroup(g, v, authViews) + return err +} + +// focus to previous view +func (gui *Gui) previousAuthView(g *gocui.Gui, v *gocui.View) error { + err := gui.previousViewOfGroup(g, v, authViews) + return err +} diff --git a/pkg/gui/gui.go b/pkg/gui/gui.go index 1eb7969..1fb94ad 100644 --- a/pkg/gui/gui.go +++ b/pkg/gui/gui.go @@ -4,7 +4,6 @@ import ( "fmt" "github.com/isacikgoz/gitbatch/pkg/git" - "github.com/isacikgoz/gitbatch/pkg/queue" "github.com/jroimartin/gocui" log "github.com/sirupsen/logrus" ) @@ -23,7 +22,7 @@ type guiState struct { Repositories []*git.RepoEntity Directories []string Mode mode - Queue *queue.JobQueue + Queue *git.JobQueue } // this struct encapsulates the name and title of a view. the name of a view is @@ -78,7 +77,7 @@ func NewGui(mode string, directoies []string) (*Gui, error) { initialState := guiState{ Directories: directoies, Mode: fetchMode, - Queue: queue.CreateJobQueue(), + Queue: git.CreateJobQueue(), } gui := &Gui{ State: initialState, diff --git a/pkg/gui/keybindings.go b/pkg/gui/keybindings.go index 77bd0a2..9b23fa2 100644 --- a/pkg/gui/keybindings.go +++ b/pkg/gui/keybindings.go @@ -174,6 +174,38 @@ func (gui *Gui) generateKeybindings() error { gui.KeyBindings = append(gui.KeyBindings, binding) } } + for _, view := range authViews { + authKeybindings := []*KeyBinding{ + { + View: view.Name, + Key: gocui.KeyEsc, + Modifier: gocui.ModNone, + Handler: gui.closeAuthenticationView, + Display: "esc", + Description: "close/cancel", + Vital: true, + }, { + View: view.Name, + Key: gocui.KeyTab, + Modifier: gocui.ModNone, + Handler: gui.nextAuthView, + Display: "tab", + Description: "Next Panel", + Vital: false, + }, { + View: view.Name, + Key: gocui.KeyEnter, + Modifier: gocui.ModNone, + Handler: gui.submitAuthenticationView, + Display: "enter", + Description: "Submit", + Vital: false, + }, + } + for _, binding := range authKeybindings { + gui.KeyBindings = append(gui.KeyBindings, binding) + } + } individualKeybindings := []*KeyBinding{ // stash view { diff --git a/pkg/gui/mainview.go b/pkg/gui/mainview.go index b421775..fe8931b 100644 --- a/pkg/gui/mainview.go +++ b/pkg/gui/mainview.go @@ -5,7 +5,6 @@ import ( "sort" "github.com/isacikgoz/gitbatch/pkg/git" - "github.com/isacikgoz/gitbatch/pkg/queue" "github.com/jroimartin/gocui" ) @@ -112,18 +111,18 @@ func (gui *Gui) getSelectedRepository() *git.RepoEntity { // adds given entity to job queue func (gui *Gui) addToQueue(entity *git.RepoEntity) error { - var jt queue.JobType + var jt git.JobType switch mode := gui.State.Mode.ModeID; mode { case FetchMode: - jt = queue.Fetch + jt = git.FetchJob case PullMode: - jt = queue.Pull + jt = git.PullJob case MergeMode: - jt = queue.Merge + jt = git.MergeJob default: return nil } - err := gui.State.Queue.AddJob(&queue.Job{ + err := gui.State.Queue.AddJob(&git.Job{ JobType: jt, Entity: entity, }) diff --git a/pkg/gui/queuehandler.go b/pkg/gui/queuehandler.go index 3be1472..cec6f8b 100644 --- a/pkg/gui/queuehandler.go +++ b/pkg/gui/queuehandler.go @@ -1,7 +1,9 @@ package gui import ( + "github.com/isacikgoz/gitbatch/pkg/git" "github.com/jroimartin/gocui" + log "github.com/sirupsen/logrus" ) // this function starts the queue and updates the gui with the result of an @@ -16,6 +18,13 @@ func (gui *Gui) startQueue(g *gocui.Gui, v *gocui.View) error { }) defer gui.updateKeyBindingsView(g, mainViewFeature.Name) if err != nil { + if err == git.ErrAuthenticationRequired { + err := gui_go.openAuthenticationView(g, gui_go.State.Queue, job, v.Name()) + if err != nil { + log.Warn(err.Error()) + return + } + } return } if finished { diff --git a/pkg/gui/textstyle.go b/pkg/gui/textstyle.go index 33f172d..efbbceb 100644 --- a/pkg/gui/textstyle.go +++ b/pkg/gui/textstyle.go @@ -6,7 +6,6 @@ import ( "github.com/fatih/color" "github.com/isacikgoz/gitbatch/pkg/git" - "github.com/isacikgoz/gitbatch/pkg/queue" ) var ( @@ -87,11 +86,11 @@ func (gui *Gui) displayString(entity *git.RepoEntity) string { if entity.State == git.Queued { if inQueue, ty := gui.State.Queue.IsInTheQueue(entity); inQueue { switch mode := ty; mode { - case queue.Fetch: + case git.FetchJob: suffix = blue.Sprint(queuedSymbol) - case queue.Pull: + case git.PullJob: suffix = magenta.Sprint(queuedSymbol) - case queue.Merge: + case git.MergeJob: suffix = cyan.Sprint(queuedSymbol) default: suffix = green.Sprint(queuedSymbol) |
