-
Notifications
You must be signed in to change notification settings - Fork 36
/
syncer.go
129 lines (106 loc) · 3.39 KB
/
syncer.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
package git
import (
"context"
"fmt"
"sync"
"time"
"github.com/src-d/lookout"
"github.com/src-d/lookout/util/ctxlog"
git "gopkg.in/src-d/go-git.v4"
"gopkg.in/src-d/go-git.v4/config"
"gopkg.in/src-d/go-git.v4/plumbing/transport"
log "gopkg.in/src-d/go-log.v1"
)
const defaultRemoteName = "origin"
type Syncer interface {
Sync(context.Context, ...lookout.ReferencePointer) error
}
// LibrarySyncer syncs the local copy of git repository for a given CommitRevision.
type LibrarySyncer struct {
m sync.Map // holds mutexes per repository
l *Library
authProvider AuthProvider
// fetchTimeout of zero means no timeout.
fetchTimeout time.Duration
}
// AuthProvider is an object that provides go-git auth methods
type AuthProvider interface {
// GitAuth returns a go-git auth method for a repo
GitAuth(ctx context.Context, repoInfo *lookout.RepositoryInfo) transport.AuthMethod
}
// NewSyncer returns a Syncer for the given Library. authProvider can be nil.
// A fetchTimeout of zero means no timeout.
func NewSyncer(l *Library, authProvider AuthProvider, fetchTimeout time.Duration) Syncer {
return &LibrarySyncer{l: l, authProvider: authProvider, fetchTimeout: fetchTimeout}
}
// Sync syncs the local git repository to the given reference pointers.
func (s *LibrarySyncer) Sync(ctx context.Context,
rps ...lookout.ReferencePointer) error {
if len(rps) == 0 {
return fmt.Errorf("at least one reference pointer is required")
}
frp := rps[0]
for _, orp := range rps[1:] {
if orp.InternalRepositoryURL != frp.InternalRepositoryURL {
return fmt.Errorf(
"sync from multiple repositories is not supported")
}
}
repoInfo := frp.Repository()
gitRepo, err := s.l.GetOrInit(ctx, frp.Repository())
if err != nil {
return err
}
var refspecs []config.RefSpec
for _, rp := range rps {
var rs config.RefSpec
if "" == rp.ReferenceName {
rs = config.RefSpec(fmt.Sprintf(config.DefaultFetchRefSpec, defaultRemoteName))
ctxlog.Get(ctx).Warningf("empty ReferenceName given in %v, using default '%s' instead", rp, rs)
} else {
rs = config.RefSpec(fmt.Sprintf("%s:%[1]s", rp.ReferenceName))
}
refspecs = append(refspecs, rs)
}
return s.fetch(ctx, repoInfo, gitRepo, refspecs)
}
func (s *LibrarySyncer) fetch(ctx context.Context, repoInfo *lookout.RepositoryInfo,
r *git.Repository, refspecs []config.RefSpec) (err error) {
ctxlog.Get(ctx).Infof("fetching references for repository %s: %v", repoInfo.CloneURL, refspecs)
start := time.Now()
defer func() {
if err == nil {
ctxlog.Get(ctx).
With(log.Fields{"duration": time.Now().Sub(start)}).
Debugf("references %v fetched for repository %s", refspecs, repoInfo.CloneURL)
}
// in case of error it will be logged on upper level
}()
var auth transport.AuthMethod
if s.authProvider != nil {
auth = s.authProvider.GitAuth(ctx, repoInfo)
}
opts := &git.FetchOptions{
RemoteName: defaultRemoteName,
RefSpecs: refspecs,
Force: true,
Auth: auth,
}
mi, _ := s.m.LoadOrStore(repoInfo.CloneURL, &sync.Mutex{})
mutex := mi.(*sync.Mutex)
mutex.Lock()
defer mutex.Unlock()
if s.fetchTimeout > 0 {
var cancel context.CancelFunc
ctx, cancel = context.WithTimeout(ctx, s.fetchTimeout)
defer cancel()
}
err = r.FetchContext(ctx, opts)
switch err {
case git.NoErrAlreadyUpToDate:
err = nil
case transport.ErrInvalidAuthMethod:
err = fmt.Errorf("wrong go-git authentication method: %s", err.Error())
}
return err
}