diff options
| author | İbrahim Serdar Açıkgöz <serdaracikgoz86@gmail.com> | 2019-01-04 10:47:22 +0300 |
|---|---|---|
| committer | İbrahim Serdar Açıkgöz <serdaracikgoz86@gmail.com> | 2019-01-04 10:47:22 +0300 |
| commit | ba246669df4f6c80fcba7445ae7e56fbc2d92541 (patch) | |
| tree | dcdbcaa1c86ac3bd580331774dcb721feab3b75d /core/command/fetch.go | |
| parent | refactor on var name (diff) | |
| download | gitbatch-ba246669df4f6c80fcba7445ae7e56fbc2d92541.tar.gz | |
add state to repository and some renaming
Diffstat (limited to 'core/command/fetch.go')
| -rw-r--r-- | core/command/fetch.go | 163 |
1 files changed, 163 insertions, 0 deletions
diff --git a/core/command/fetch.go b/core/command/fetch.go new file mode 100644 index 0000000..cc827bf --- /dev/null +++ b/core/command/fetch.go @@ -0,0 +1,163 @@ +package command + +import ( + "os" + "strings" + + gerr "github.com/isacikgoz/gitbatch/core/errors" + "github.com/isacikgoz/gitbatch/core/git" + log "github.com/sirupsen/logrus" + gogit "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 ( + fetchCmdMode string + fetchTryCount int + + fetchCommand = "fetch" + fetchCmdModeLegacy = "git" + fetchCmdModeNative = "go-git" + fetchMaxTry = 1 +) + +// FetchOptions defines the rules for fetch operation +type FetchOptions struct { + // Name of the remote to fetch from. Defaults to origin. + RemoteName string + // Credentials holds the user and pswd information + Credentials git.Credentials + // Before fetching, remove any remote-tracking references that no longer + // exist on the remote. + Prune bool + // Show what would be done, without making any changes. + DryRun bool + // Process logs the output to stdout + Progress bool + // 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(r *git.Repository, 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(r, options) + return err + case fetchCmdModeNative: + // this should be the refspec as default, let's give it a try + // TODO: Fix for quick mode, maybe better read config file + var refspec string + if r.State.Branch == nil { + refspec = "+refs/heads/*:refs/remotes/origin/*" + } else { + refspec = "+" + "refs/heads/" + r.State.Branch.Name + ":" + "/refs/remotes/" + r.State.Remote.Branch.Name + } + err = fetchWithGoGit(r, options, refspec) + return err + } + return nil +} + +// 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(r *git.Repository, 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) + } + if options.Prune { + args = append(args, "-p") + } + if options.Force { + args = append(args, "-f") + } + if options.DryRun { + args = append(args, "--dry-run") + } + if out, err := GenericGitCommandWithOutput(r.AbsPath, args); err != nil { + return gerr.ParseGitError(out, err) + } + r.SetWorkStatus(git.Success) + // till this step everything should be ok + return r.Refresh() +} + +// 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(r *git.Repository, options FetchOptions, refspec string) (err error) { + opt := &gogit.FetchOptions{ + RemoteName: options.RemoteName, + RefSpecs: []config.RefSpec{config.RefSpec(refspec)}, + Force: options.Force, + } + // if any credential is given, let's add it to the git.FetchOptions + if len(options.Credentials.User) > 0 { + protocol, err := git.AuthProtocol(r.State.Remote) + if err != nil { + return err + } + if protocol == git.AuthProtocolHTTP || protocol == git.AuthProtocolHTTPS { + opt.Auth = &http.BasicAuth{ + Username: options.Credentials.User, + Password: options.Credentials.Password, + } + } else { + return gerr.ErrInvalidAuthMethod + } + } + if options.Progress { + opt.Progress = os.Stdout + } + + if err := r.Repo.Fetch(opt); err != nil { + if err == gogit.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 := r.State.Remote.RefSpecs[0] + if fetchTryCount < fetchMaxTry { + fetchTryCount++ + fetchWithGoGit(r, options, rp) + } else { + return err + } + // TODO: submit a PR for this kind of error, this type of catch is lame + } else if strings.Contains(err.Error(), "SSH_AUTH_SOCK") { + // The env variable SSH_AUTH_SOCK is not defined, maybe git can handle this + return fetchWithGit(r, options) + } else if err == transport.ErrAuthenticationRequired { + log.Warn(err.Error()) + return gerr.ErrAuthenticationRequired + } else { + log.Warn(err.Error()) + return fetchWithGit(r, options) + } + } + + r.SetWorkStatus(git.Success) + // till this step everything should be ok + return r.Refresh() +} |
