summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorIbrahim Serdar Acikgoz <serdaracikgoz86@gmail.com>2018-12-12 02:33:54 +0300
committerIbrahim Serdar Acikgoz <serdaracikgoz86@gmail.com>2018-12-12 02:33:54 +0300
commitd5eefa3d792a37ba104bfba36359ad1d5dea4677 (patch)
tree4aec39644afd62b9379c4d7a682ebeae2c8ed164
parentadded show diff on status view and polish readme (diff)
downloadgitbatch-d5eefa3d792a37ba104bfba36359ad1d5dea4677.tar.gz
migrate fetch operation to go-git impl. for default
-rw-r--r--pkg/git/fetch.go87
-rw-r--r--pkg/git/remote.go11
2 files changed, 89 insertions, 9 deletions
diff --git a/pkg/git/fetch.go b/pkg/git/fetch.go
index 5dad021..d0757bb 100644
--- a/pkg/git/fetch.go
+++ b/pkg/git/fetch.go
@@ -1,10 +1,22 @@
package git
import (
+ "strings"
+
log "github.com/sirupsen/logrus"
+ "gopkg.in/src-d/go-git.v4"
+ "gopkg.in/src-d/go-git.v4/config"
)
-var fetchCommand = "fetch"
+var (
+ fetchCmdMode string
+ fetchTryCount int
+
+ fetchCommand = "fetch"
+ fetchCmdModeLegacy = "git"
+ fetchCmdModeNative = "go-git"
+ fetchMaxTry = 1
+)
// FetchOptions defines the rules for fetch operation
type FetchOptions struct {
@@ -18,13 +30,42 @@ type FetchOptions struct {
// Force allows the fetch to update a local branch even when the remote
// branch does not descend from it.
Force bool
+ // There should be more room for authentication, tags and progress
}
// Fetch branches refs from one or more other repositories, along with the
// objects necessary to complete their histories
-func Fetch(entity *RepoEntity, options FetchOptions) error {
+func Fetch(entity *RepoEntity, options FetchOptions) (err error) {
+ // here we configure fetch operation
+ // default mode is go-git (this may be configured)
+ fetchCmdMode = fetchCmdModeNative
+ fetchTryCount = 0
+ // prune and dry run is not supported from go-git yet, rely on old friend
+ if options.Prune || options.DryRun {
+ fetchCmdMode = fetchCmdModeLegacy
+ }
+ switch fetchCmdMode {
+ case fetchCmdModeLegacy:
+ err = fetchWithGit(entity.AbsPath, options)
+ 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
+ err = fetchWithGoGit(entity, options, refspec)
+ return err
+ }
+ // till this step everything should be ok
+ err = entity.Refresh()
+ return err
+}
+
+// fetchWithGit is simply a bare git fetch <remote> command which is flexible
+// for complex operations, but on the other hand, it ties the app to another
+// tool. To avoid that, using native implementation is preferred.
+func fetchWithGit(abspath string, options FetchOptions) (err error) {
args := make([]string, 0)
args = append(args, fetchCommand)
+ // parse options to command line arguments
if len(options.RemoteName) > 0 {
args = append(args, options.RemoteName)
}
@@ -37,10 +78,44 @@ func Fetch(entity *RepoEntity, options FetchOptions) error {
if options.DryRun {
args = append(args, "--dry-run")
}
- if err := GenericGitCommand(entity.AbsPath, args); err != nil {
- log.Warn("Error while fetching")
- return err
+ if err := GenericGitCommand(abspath, args); err != nil {
+ log.Warn("Error at git command (fetch)")
+ }
+ return err
+}
+
+// fetchWithGoGit is the primary fetch method and refspec is the main feature.
+// RefSpec is a mapping from local branches to remote references The format of
+// the refspec is an optional +, followed by <src>:<dst>, where <src> is the
+// pattern for references on the remote side and <dst> is where those references
+// will be written locally. The + tells Git to update the reference even if it
+// isn’t a fast-forward.
+func fetchWithGoGit(entity *RepoEntity, options FetchOptions, refspec string) (err error) {
+ opt := &git.FetchOptions{
+ RemoteName: options.RemoteName,
+ RefSpecs: []config.RefSpec{config.RefSpec(refspec)},
+ Force: options.Force,
+ }
+ err = entity.Repository.Fetch(opt)
+ if err != nil {
+ if err == git.NoErrAlreadyUpToDate {
+ // Already up-to-date
+ log.Warn(err.Error())
+ // TODO: submit a PR for this kind of error, this type of catch is lame
+ } else if strings.Contains(err.Error(), "couldn't find remote ref") {
+ // we dont have remote ref, so lets pull other things.. maybe it'd be useful
+ rp := entity.Remote.RefSpecs[0]
+ if fetchTryCount < fetchMaxTry {
+ fetchTryCount++
+ fetchWithGoGit(entity, options, rp)
+ } else {
+ return err
+ }
+ // TODO: handle authentication exception
+ } else {
+ log.Warn(err.Error())
+ return err
+ }
}
- entity.Refresh()
return nil
}
diff --git a/pkg/git/remote.go b/pkg/git/remote.go
index 96b8aa7..27f627b 100644
--- a/pkg/git/remote.go
+++ b/pkg/git/remote.go
@@ -10,6 +10,7 @@ import (
type Remote struct {
Name string
URL []string
+ RefSpecs []string
Branch *RemoteBranch
Branches []*RemoteBranch
}
@@ -57,10 +58,14 @@ func (entity *RepoEntity) loadRemotes() error {
remotes, err := r.Remotes()
for _, rm := range remotes {
-
+ rfs := make([]string, 0)
+ for _, rf := range rm.Config().Fetch {
+ rfs = append(rfs, string(rf))
+ }
remote := &Remote{
- Name: rm.Config().Name,
- URL: rm.Config().URLs,
+ Name: rm.Config().Name,
+ URL: rm.Config().URLs,
+ RefSpecs: rfs,
}
remote.loadRemoteBranches(entity)
if len(remote.Branches) > 0 {