func (r *Repo) fetch(ctx context.Context) error {
	if err := fetch(ctx, r.dir, "origin"); err != nil {
		return err
	}
	return nil
}
fetch gets updated refs, and associated objects, from the upstream.
package git
import (
	"errors"
	"fmt"
	"io/ioutil"
	"os"
	"strings"
	"sync"
	"context"
	"time"
)
const (
	defaultInterval = 5 * time.Minute
	defaultTimeout  = 20 * time.Second
	CheckPushTagPrefix = "flux-write-check"
)
var (
	ErrNoChanges  = errors.New("no changes made in repo")
	ErrNoConfig   = errors.New("git repo does not have valid config")
	ErrNotCloned  = errors.New("git repo has not been cloned yet")
	ErrClonedOnly = errors.New("git repo has been cloned but not yet checked for write access")
)
type NotReadyError struct {
	underlying error
}
func (err NotReadyError) Unwrap() error { return err.underlying }
func (err NotReadyError) Error() string {
	return "git repo not ready: " + err.underlying.Error()
}
type GitRepoStatus string
const (
	RepoNoConfig    GitRepoStatus = "unconfigured"  
	RepoNew         GitRepoStatus = "new"           
	RepoCloned      GitRepoStatus = "cloned"        
	RepoReady       GitRepoStatus = "ready"         
	RepoUnreachable GitRepoStatus = "unreachable"   
)
type Repo struct {
	origin   Remote
	branch   string
	interval time.Duration
	timeout  time.Duration
	readonly bool
	mu     sync.RWMutex
	status GitRepoStatus
	err    error
	dir    string
	notify chan struct{}
	C      chan struct{}
}
type Option interface {
	apply(*Repo)
}
type optionFunc func(*Repo)
func (f optionFunc) apply(r *Repo) {
	f(r)
}
type PollInterval time.Duration
func (p PollInterval) apply(r *Repo) {
	r.interval = time.Duration(p)
}
type Timeout time.Duration
func (t Timeout) apply(r *Repo) {
	r.timeout = time.Duration(t)
}
type Branch string
func (b Branch) apply(r *Repo) {
	r.branch = string(b)
}
type IsReadOnly bool
func (ro IsReadOnly) apply(r *Repo) {
	r.readonly = bool(ro)
}
var ReadOnly IsReadOnly = true
func NewRepo(origin Remote, opts ...Option) *Repo {
	status := RepoNew
	if origin.URL == "" {
		status = RepoNoConfig
	}
	r := &Repo{
		origin:   origin,
		status:   status,
		interval: defaultInterval,
		timeout:  defaultTimeout,
		err:      ErrNotCloned,
		notify:   make(chan struct{}, 1),  
		C:        make(chan struct{}, 1),  
	}
	for _, opt := range opts {
		opt.apply(r)
	}
	return r
}
func (r *Repo) Origin() Remote {
	r.mu.RLock()
	defer r.mu.RUnlock()
	return r.origin
}
func (r *Repo) Readonly() bool {
	return r.readonly
}
func (r *Repo) Dir() string {
	r.mu.RLock()
	defer r.mu.RUnlock()
	return r.dir
}
func (r *Repo) Clean() {
	r.mu.Lock()
	if r.dir != "" {
		os.RemoveAll(r.dir)
	}
	r.dir = ""
	r.status = RepoNew
	r.mu.Unlock()
}
func (r *Repo) Status() (GitRepoStatus, error) {
	r.mu.RLock()
	defer r.mu.RUnlock()
	return r.status, r.err
}
func (r *Repo) setUnready(s GitRepoStatus, err error) {
	metricGitReady.Set(MetricRepoUnready)
	r.mu.Lock()
	r.status = s
	r.err = err
	r.mu.Unlock()
}
func (r *Repo) setReady() {
	metricGitReady.Set(MetricRepoReady)
	r.mu.Lock()
	r.status = RepoReady
	r.err = nil
	r.mu.Unlock()
}
func (r *Repo) Notify() {
	select {
	case r.notify <- struct{}{}:
	default:
	}
}
func (r *Repo) refreshed() {
	select {
	case r.C <- struct{}{}:
	default:
	}
}
func (r *Repo) errorIfNotReady() error {
	switch r.status {
	case RepoReady:
		return nil
	case RepoNoConfig:
		return ErrNoConfig
	default:
		return NotReadyError{r.err}
	}
}
func (r *Repo) Revision(ctx context.Context, ref string) (string, error) {
	r.mu.RLock()
	defer r.mu.RUnlock()
	if err := r.errorIfNotReady(); err != nil {
		return "", err
	}
	return refRevision(ctx, r.dir, ref)
}
func (r *Repo) BranchHead(ctx context.Context) (string, error) {
	r.mu.RLock()
	defer r.mu.RUnlock()
	if err := r.errorIfNotReady(); err != nil {
		return "", err
	}
	return refRevision(ctx, r.dir, "heads/"+r.branch)
}
func (r *Repo) CommitsBefore(ctx context.Context, ref string, firstParent bool, paths ...string) ([]Commit, error) {
	r.mu.RLock()
	defer r.mu.RUnlock()
	if err := r.errorIfNotReady(); err != nil {
		return nil, err
	}
	return onelinelog(ctx, r.dir, ref, paths, firstParent)
}
func (r *Repo) CommitsBetween(ctx context.Context, ref1, ref2 string, firstParent bool, paths ...string) ([]Commit, error) {
	r.mu.RLock()
	defer r.mu.RUnlock()
	if err := r.errorIfNotReady(); err != nil {
		return nil, err
	}
	return onelinelog(ctx, r.dir, ref1+".."+ref2, paths, firstParent)
}
func (r *Repo) VerifyTag(ctx context.Context, tag string) (string, error) {
	r.mu.RLock()
	defer r.mu.RUnlock()
	if err := r.errorIfNotReady(); err != nil {
		return "", err
	}
	return verifyTag(ctx, r.dir, tag)
}
func (r *Repo) VerifyCommit(ctx context.Context, commit string) error {
	r.mu.RLock()
	defer r.mu.RUnlock()
	if err := r.errorIfNotReady(); err != nil {
		return err
	}
	return verifyCommit(ctx, r.dir, commit)
}
func (r *Repo) DeleteTag(ctx context.Context, tag string) error {
	r.mu.RLock()
	defer r.mu.RUnlock()
	if err := r.errorIfNotReady(); err != nil {
		return err
	}
	return deleteTag(ctx, r.dir, tag, r.origin.URL)
}
func (r *Repo) NoteRevList(ctx context.Context, notesRef string) (map[string]struct{}, error) {
	return noteRevList(ctx, r.Dir(), notesRef)
}
func (r *Repo) GetNote(ctx context.Context, rev, notesRef string, note interface{}) (bool, error) {
	return getNote(ctx, r.Dir(), notesRef, rev, note)
}
func (r *Repo) step(bg context.Context) bool {
	r.mu.RLock()
	url := r.origin.URL
	dir := r.dir
	status := r.status
	r.mu.RUnlock()
	switch status {
	case RepoNoConfig:
		return false
	case RepoNew, RepoUnreachable:
		rootdir, err := ioutil.TempDir(os.TempDir(), "flux-gitclone")
		if err != nil {
			panic(err)
		}
		ctx, cancel := context.WithTimeout(bg, r.timeout)
		dir, err = mirror(ctx, rootdir, url)
		cancel()
		if err == nil {
			r.mu.Lock()
			r.dir = dir
			ctx, cancel := context.WithTimeout(bg, r.timeout)
			err = r.fetch(ctx)
			cancel()
			r.mu.Unlock()
		}
		if err == nil {
			r.setUnready(RepoCloned, ErrClonedOnly)
			return true
		}
		dir = ""
		os.RemoveAll(rootdir)
		if strings.Contains(strings.ToLower(err.Error()), "could not resolve hostname") {
			r.setUnready(RepoUnreachable, err)
			return false
		}
		r.setUnready(RepoNew, err)
		return false
	case RepoCloned:
		ctx, cancel := context.WithTimeout(bg, r.timeout)
		defer cancel()
		if r.branch != "" {
			r.mu.Lock()
			err := r.fetch(ctx)
			r.mu.Unlock()
			if err != nil {
				r.setUnready(RepoCloned, err)
				return false
			}
			ok, err := refExists(ctx, dir, "refs/heads/"+r.branch)
			if err != nil {
				r.setUnready(RepoCloned, err)
				return false
			}
			if !ok {
				r.setUnready(RepoCloned, fmt.Errorf("configured branch '%s' does not exist", r.branch))
				return false
			}
		}
		if !r.readonly {
			err := checkPush(ctx, dir, url, r.branch)
			if err != nil {
				r.setUnready(RepoCloned, err)
				return false
			}
		}
		r.setReady()
		r.refreshed()
		return true
	case RepoReady:
		return false
	}
	return false
}
func (r *Repo) Ready(ctx context.Context) error {
	for r.step(ctx) {
	}
	_, err := r.Status()
	return err
}
func (r *Repo) Start(shutdown <-chan struct{}, done *sync.WaitGroup) error {
	defer done.Done()
	for {
		ctx, cancel := context.WithTimeout(context.Background(), r.timeout)
		advanced := r.step(ctx)
		cancel()
		if advanced {
			continue
		}
		status, _ := r.Status()
		if status == RepoReady {
			if err := r.refreshLoop(shutdown); err != nil {
				r.setUnready(RepoNew, err)
				continue  
			}
		} else if status == RepoNoConfig {
			return nil
		}
		tryAgain := time.NewTimer(10 * time.Second)
		select {
		case <-shutdown:
			if !tryAgain.Stop() {
				<-tryAgain.C
			}
			return nil
		case <-tryAgain.C:
			continue
		}
	}
}
func (r *Repo) Refresh(ctx context.Context) error {
	r.mu.Lock()
	defer r.mu.Unlock()
	if err := r.errorIfNotReady(); err != nil {
		return err
	}
	if err := r.fetch(ctx); err != nil {
		return err
	}
	r.refreshed()
	return nil
}
func (r *Repo) refreshLoop(shutdown <-chan struct{}) error {
	gitPoll := time.NewTimer(r.interval)
	for {
		select {
		case <-shutdown:
			if !gitPoll.Stop() {
				<-gitPoll.C
			}
			return nil
		case <-gitPoll.C:
			r.Notify()
		case <-r.notify:
			if !gitPoll.Stop() {
				select {
				case <-gitPoll.C:
				default:
				}
			}
			ctx, cancel := context.WithTimeout(context.Background(), r.timeout)
			err := r.Refresh(ctx)
			cancel()
			if err != nil {
				return err
			}
			gitPoll.Reset(r.interval)
		}
	}
}