@@ -62,7 +62,7 @@ func (c Command) Run(ctx context.Context) error {
62
62
Path : g .Directory ,
63
63
Ref : g .Ref ,
64
64
}
65
- err = cloneAndCopy (ctx , repoSpec , c .Pkg .UniquePath .String ())
65
+ err = NewCloner ( repoSpec ). cloneAndCopy (ctx , c .Pkg .UniquePath .String ())
66
66
if err != nil {
67
67
return errors .E (op , c .Pkg .UniquePath , err )
68
68
}
@@ -94,21 +94,55 @@ func (c Command) validate(kf *kptfilev1.KptFile) error {
94
94
return nil
95
95
}
96
96
97
+ // Cloner clones an upstream repo defined by a repoSpec.
98
+ // Optionally, previously cloned repos can be cached
99
+ // rather than recloning them each time.
100
+ type Cloner struct {
101
+ // repoSpec spec to clone
102
+ repoSpec * git.RepoSpec
103
+
104
+ // cachedRepos
105
+ cachedRepo map [string ]* gitutil.GitUpstreamRepo
106
+ }
107
+
108
+ type NewClonerOption func (* Cloner )
109
+
110
+ func WithCachedRepo (r map [string ]* gitutil.GitUpstreamRepo ) NewClonerOption {
111
+ return func (c * Cloner ) {
112
+ c .cachedRepo = r
113
+ }
114
+ }
115
+
116
+ func NewCloner (r * git.RepoSpec , opts ... NewClonerOption ) * Cloner {
117
+ c := & Cloner {
118
+ repoSpec : r ,
119
+ }
120
+ for _ , opt := range opts {
121
+ opt (c )
122
+ }
123
+ if c .cachedRepo == nil {
124
+ c .cachedRepo = make (map [string ]* gitutil.GitUpstreamRepo )
125
+ }
126
+ return c
127
+ }
128
+
97
129
// cloneAndCopy fetches the provided repo and copies the content into the
98
130
// directory specified by dest. The provided name is set as `metadata.name`
99
131
// of the Kptfile of the package.
100
- func cloneAndCopy (ctx context.Context , r * git. RepoSpec , dest string ) error {
132
+ func ( c * Cloner ) cloneAndCopy (ctx context.Context , dest string ) error {
101
133
const op errors.Op = "fetch.cloneAndCopy"
102
134
pr := printer .FromContextOrDie (ctx )
103
135
104
- err := ClonerUsingGitExec (ctx , r )
136
+ err := c . ClonerUsingGitExec (ctx )
105
137
if err != nil {
106
138
return errors .E (op , errors .Git , types .UniquePath (dest ), err )
107
139
}
108
- defer os .RemoveAll (r .Dir )
140
+ defer os .RemoveAll (c .repoSpec .Dir )
141
+ // update cache before removing clone dir
142
+ defer delete (c .cachedRepo , c .repoSpec .CloneSpec ())
109
143
110
- sourcePath := filepath .Join (r . Dir , r .Path )
111
- pr .Printf ("Adding package %q.\n " , strings .TrimPrefix (r .Path , "/" ))
144
+ sourcePath := filepath .Join (c . repoSpec . Dir , c . repoSpec .Path )
145
+ pr .Printf ("Adding package %q.\n " , strings .TrimPrefix (c . repoSpec .Path , "/" ))
112
146
if err := pkgutil .CopyPackage (sourcePath , dest , true , pkg .All ); err != nil {
113
147
return errors .E (op , types .UniquePath (dest ), err )
114
148
}
@@ -117,7 +151,7 @@ func cloneAndCopy(ctx context.Context, r *git.RepoSpec, dest string) error {
117
151
return errors .E (op , types .UniquePath (dest ), err )
118
152
}
119
153
120
- if err := kptfileutil .UpdateUpstreamLockFromGit (dest , r ); err != nil {
154
+ if err := kptfileutil .UpdateUpstreamLockFromGit (dest , c . repoSpec ); err != nil {
121
155
return errors .E (op , errors .Git , types .UniquePath (dest ), err )
122
156
}
123
157
return nil
@@ -129,46 +163,51 @@ func cloneAndCopy(ctx context.Context, r *git.RepoSpec, dest string) error {
129
163
// for versioning multiple kpt packages in a single repo independently. It
130
164
// relies on the private clonerUsingGitExec function to try fetching different
131
165
// refs.
132
- func ClonerUsingGitExec (ctx context.Context , repoSpec * git. RepoSpec ) error {
166
+ func ( c * Cloner ) ClonerUsingGitExec (ctx context.Context ) error {
133
167
const op errors.Op = "fetch.ClonerUsingGitExec"
134
168
135
169
// Create a local representation of the upstream repo. This will initialize
136
170
// the cache for the specified repo uri if it isn't already there. It also
137
171
// fetches and caches all tag and branch refs from the upstream repo.
138
- upstreamRepo , err := gitutil .NewGitUpstreamRepo (ctx , repoSpec .CloneSpec ())
139
- if err != nil {
140
- return errors .E (op , errors .Git , errors .Repo (repoSpec .CloneSpec ()), err )
172
+ upstreamRepo , exists := c .cachedRepo [c .repoSpec .CloneSpec ()]
173
+ if ! exists {
174
+ newUpstreamRemp , err := gitutil .NewGitUpstreamRepo (ctx , c .repoSpec .CloneSpec ())
175
+ if err != nil {
176
+ return errors .E (op , errors .Git , errors .Repo (c .repoSpec .CloneSpec ()), err )
177
+ }
178
+ upstreamRepo = newUpstreamRemp
179
+ c .cachedRepo [c .repoSpec .CloneSpec ()] = upstreamRepo
141
180
}
142
181
143
182
// Check if we have a ref in the upstream that matches the package-specific
144
183
// reference. If we do, we use that reference.
145
- ps := strings .Split (repoSpec .Path , "/" )
184
+ ps := strings .Split (c . repoSpec .Path , "/" )
146
185
for len (ps ) != 0 {
147
186
p := path .Join (ps ... )
148
- packageRef := path .Join (strings .TrimLeft (p , "/" ), repoSpec .Ref )
187
+ packageRef := path .Join (strings .TrimLeft (p , "/" ), c . repoSpec .Ref )
149
188
if _ , found := upstreamRepo .ResolveTag (packageRef ); found {
150
- repoSpec .Ref = packageRef
189
+ c . repoSpec .Ref = packageRef
151
190
break
152
191
}
153
192
ps = ps [:len (ps )- 1 ]
154
193
}
155
194
156
195
// Pull the required ref into the repo git cache.
157
- dir , err := upstreamRepo .GetRepo (ctx , []string {repoSpec .Ref })
196
+ dir , err := upstreamRepo .GetRepo (ctx , []string {c . repoSpec .Ref })
158
197
if err != nil {
159
- return errors .E (op , errors .Git , errors .Repo (repoSpec .CloneSpec ()), err )
198
+ return errors .E (op , errors .Git , errors .Repo (c . repoSpec .CloneSpec ()), err )
160
199
}
161
200
162
201
gitRunner , err := gitutil .NewLocalGitRunner (dir )
163
202
if err != nil {
164
- return errors .E (op , errors .Git , errors .Repo (repoSpec .CloneSpec ()), err )
203
+ return errors .E (op , errors .Git , errors .Repo (c . repoSpec .CloneSpec ()), err )
165
204
}
166
205
167
206
// Find the commit SHA for the ref that was just fetched. We need the SHA
168
207
// rather than the ref to be able to do a hard reset of the cache repo.
169
- commit , found := upstreamRepo .ResolveRef (repoSpec .Ref )
208
+ commit , found := upstreamRepo .ResolveRef (c . repoSpec .Ref )
170
209
if ! found {
171
- commit = repoSpec .Ref
210
+ commit = c . repoSpec .Ref
172
211
}
173
212
174
213
// Reset the local repo to the commit we need. Doing a hard reset instead of
@@ -178,34 +217,34 @@ func ClonerUsingGitExec(ctx context.Context, repoSpec *git.RepoSpec) error {
178
217
_ , err = gitRunner .Run (ctx , "reset" , "--hard" , commit )
179
218
if err != nil {
180
219
gitutil .AmendGitExecError (err , func (e * gitutil.GitExecError ) {
181
- e .Repo = repoSpec .CloneSpec ()
220
+ e .Repo = c . repoSpec .CloneSpec ()
182
221
e .Ref = commit
183
222
})
184
- return errors .E (op , errors .Git , errors .Repo (repoSpec .CloneSpec ()), err )
223
+ return errors .E (op , errors .Git , errors .Repo (c . repoSpec .CloneSpec ()), err )
185
224
}
186
225
187
226
// We need to create a temp directory where we can copy the content of the repo.
188
227
// During update, we need to checkout multiple versions of the same repo, so
189
228
// we can't do merges directly from the cache.
190
- repoSpec .Dir , err = ioutil .TempDir ("" , "kpt-get-" )
229
+ c . repoSpec .Dir , err = ioutil .TempDir ("" , "kpt-get-" )
191
230
if err != nil {
192
231
return errors .E (op , errors .Internal , fmt .Errorf ("error creating temp directory: %w" , err ))
193
232
}
194
- repoSpec .Commit = commit
233
+ c . repoSpec .Commit = commit
195
234
196
- pkgPath := filepath .Join (dir , repoSpec .Path )
235
+ pkgPath := filepath .Join (dir , c . repoSpec .Path )
197
236
// Verify that the requested path exists in the repo.
198
237
_ , err = os .Stat (pkgPath )
199
238
if os .IsNotExist (err ) {
200
239
return errors .E (op ,
201
240
errors .Internal ,
202
241
err ,
203
- fmt .Errorf ("path %q does not exist in repo %q" , repoSpec .Path , repoSpec .OrgRepo ))
242
+ fmt .Errorf ("path %q does not exist in repo %q" , c . repoSpec .Path , c . repoSpec .OrgRepo ))
204
243
}
205
244
206
245
// Copy the content of the pkg into the temp directory.
207
246
// Note that we skip the content outside the package directory.
208
- err = copyDir (ctx , pkgPath , repoSpec .AbsPath ())
247
+ err = copyDir (ctx , pkgPath , c . repoSpec .AbsPath ())
209
248
if err != nil {
210
249
return errors .E (op , errors .Internal , fmt .Errorf ("error copying package: %w" , err ))
211
250
}
@@ -226,7 +265,7 @@ func ClonerUsingGitExec(ctx context.Context, repoSpec *git.RepoSpec) error {
226
265
var kfError * pkg.KptfileError
227
266
if errors .As (err , & kfError ) {
228
267
return & pkg.RemoteKptfileError {
229
- RepoSpec : repoSpec ,
268
+ RepoSpec : c . repoSpec ,
230
269
Err : kfError .Err ,
231
270
}
232
271
}
0 commit comments