summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
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
commita1abe10a125e1b74c38726f02565140b1a440b95 (patch)
tree3e75807e0ab6eada4916f97784627c656ffe187b
parentMerge branch 'develop' of github.com:isacikgoz/gitbatch into develop (diff)
downloadgitbatch-a1abe10a125e1b74c38726f02565140b1a440b95.tar.gz
implementing authentication for fetch operation
-rw-r--r--pkg/git/authentication.go26
-rw-r--r--pkg/git/errors.go24
-rw-r--r--pkg/git/fetch.go23
-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.go165
-rw-r--r--pkg/gui/gui.go5
-rw-r--r--pkg/gui/keybindings.go32
-rw-r--r--pkg/gui/mainview.go11
-rw-r--r--pkg/gui/queuehandler.go9
-rw-r--r--pkg/gui/textstyle.go7
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)