fork of go-git with some jj specific features
1package git
2
3import (
4 "bytes"
5 "context"
6 "errors"
7 "fmt"
8 "io"
9 "os"
10 "path/filepath"
11 "runtime"
12 "strings"
13 "testing"
14 "time"
15
16 "github.com/go-git/go-billy/v5/memfs"
17 "github.com/go-git/go-billy/v5/osfs"
18 "github.com/go-git/go-billy/v5/util"
19
20 "github.com/go-git/go-git/v5/config"
21 "github.com/go-git/go-git/v5/plumbing"
22 "github.com/go-git/go-git/v5/plumbing/cache"
23 "github.com/go-git/go-git/v5/plumbing/object"
24 "github.com/go-git/go-git/v5/plumbing/protocol/packp"
25 "github.com/go-git/go-git/v5/plumbing/protocol/packp/capability"
26 "github.com/go-git/go-git/v5/plumbing/storer"
27 "github.com/go-git/go-git/v5/storage"
28 "github.com/go-git/go-git/v5/storage/filesystem"
29 "github.com/go-git/go-git/v5/storage/memory"
30
31 fixtures "github.com/go-git/go-git-fixtures/v4"
32 . "gopkg.in/check.v1"
33)
34
35type RemoteSuite struct {
36 BaseSuite
37}
38
39var _ = Suite(&RemoteSuite{})
40
41func (s *RemoteSuite) TestFetchInvalidEndpoint(c *C) {
42 r := NewRemote(nil, &config.RemoteConfig{Name: "foo", URLs: []string{"http://\\"}})
43 err := r.Fetch(&FetchOptions{RemoteName: "foo"})
44 c.Assert(err, ErrorMatches, ".*invalid character.*")
45}
46
47func (s *RemoteSuite) TestFetchNonExistentEndpoint(c *C) {
48 r := NewRemote(nil, &config.RemoteConfig{Name: "foo", URLs: []string{"ssh://non-existent/foo.git"}})
49 err := r.Fetch(&FetchOptions{})
50 c.Assert(err, NotNil)
51}
52
53func (s *RemoteSuite) TestFetchInvalidSchemaEndpoint(c *C) {
54 r := NewRemote(nil, &config.RemoteConfig{Name: "foo", URLs: []string{"qux://foo"}})
55 err := r.Fetch(&FetchOptions{})
56 c.Assert(err, ErrorMatches, ".*unsupported scheme.*")
57}
58
59func (s *RemoteSuite) TestFetchOverriddenEndpoint(c *C) {
60 r := NewRemote(nil, &config.RemoteConfig{Name: "foo", URLs: []string{"http://perfectly-valid-url.example.com"}})
61 err := r.Fetch(&FetchOptions{RemoteURL: "http://\\"})
62 c.Assert(err, ErrorMatches, ".*invalid character.*")
63}
64
65func (s *RemoteSuite) TestFetchInvalidFetchOptions(c *C) {
66 r := NewRemote(nil, &config.RemoteConfig{Name: "foo", URLs: []string{"qux://foo"}})
67 invalid := config.RefSpec("^*$ñ")
68 err := r.Fetch(&FetchOptions{RefSpecs: []config.RefSpec{invalid}})
69 c.Assert(err, Equals, config.ErrRefSpecMalformedSeparator)
70}
71
72func (s *RemoteSuite) TestFetchWildcard(c *C) {
73 r := NewRemote(memory.NewStorage(), &config.RemoteConfig{
74 URLs: []string{s.GetBasicLocalRepositoryURL()},
75 })
76
77 s.testFetch(c, r, &FetchOptions{
78 RefSpecs: []config.RefSpec{
79 config.RefSpec("+refs/heads/*:refs/remotes/origin/*"),
80 },
81 }, []*plumbing.Reference{
82 plumbing.NewReferenceFromStrings("refs/remotes/origin/master", "6ecf0ef2c2dffb796033e5a02219af86ec6584e5"),
83 plumbing.NewReferenceFromStrings("refs/remotes/origin/branch", "e8d3ffab552895c19b9fcf7aa264d277cde33881"),
84 plumbing.NewReferenceFromStrings("refs/tags/v1.0.0", "6ecf0ef2c2dffb796033e5a02219af86ec6584e5"),
85 })
86}
87
88func (s *RemoteSuite) TestFetchExactSHA1(c *C) {
89 r := NewRemote(memory.NewStorage(), &config.RemoteConfig{
90 URLs: []string{"https://github.com/git-fixtures/basic.git"},
91 })
92
93 s.testFetch(c, r, &FetchOptions{
94 RefSpecs: []config.RefSpec{
95 config.RefSpec("35e85108805c84807bc66a02d91535e1e24b38b9:refs/heads/foo"),
96 },
97 }, []*plumbing.Reference{
98 plumbing.NewReferenceFromStrings("refs/heads/foo", "35e85108805c84807bc66a02d91535e1e24b38b9"),
99 })
100}
101
102func (s *RemoteSuite) TestFetchExactSHA1_NotSoported(c *C) {
103 r := NewRemote(memory.NewStorage(), &config.RemoteConfig{
104 URLs: []string{s.GetBasicLocalRepositoryURL()},
105 })
106
107 err := r.Fetch(&FetchOptions{
108 RefSpecs: []config.RefSpec{
109 config.RefSpec("35e85108805c84807bc66a02d91535e1e24b38b9:refs/heads/foo"),
110 },
111 })
112
113 c.Assert(err, Equals, ErrExactSHA1NotSupported)
114
115}
116
117func (s *RemoteSuite) TestFetchWildcardTags(c *C) {
118 r := NewRemote(memory.NewStorage(), &config.RemoteConfig{
119 URLs: []string{s.GetLocalRepositoryURL(fixtures.ByTag("tags").One())},
120 })
121
122 s.testFetch(c, r, &FetchOptions{
123 RefSpecs: []config.RefSpec{
124 config.RefSpec("+refs/heads/*:refs/remotes/origin/*"),
125 },
126 }, []*plumbing.Reference{
127 plumbing.NewReferenceFromStrings("refs/remotes/origin/master", "f7b877701fbf855b44c0a9e86f3fdce2c298b07f"),
128 plumbing.NewReferenceFromStrings("refs/tags/annotated-tag", "b742a2a9fa0afcfa9a6fad080980fbc26b007c69"),
129 plumbing.NewReferenceFromStrings("refs/tags/tree-tag", "152175bf7e5580299fa1f0ba41ef6474cc043b70"),
130 plumbing.NewReferenceFromStrings("refs/tags/commit-tag", "ad7897c0fb8e7d9a9ba41fa66072cf06095a6cfc"),
131 plumbing.NewReferenceFromStrings("refs/tags/blob-tag", "fe6cb94756faa81e5ed9240f9191b833db5f40ae"),
132 plumbing.NewReferenceFromStrings("refs/tags/lightweight-tag", "f7b877701fbf855b44c0a9e86f3fdce2c298b07f"),
133 })
134}
135
136func (s *RemoteSuite) TestFetch(c *C) {
137 r := NewRemote(memory.NewStorage(), &config.RemoteConfig{
138 URLs: []string{s.GetLocalRepositoryURL(fixtures.ByTag("tags").One())},
139 })
140
141 s.testFetch(c, r, &FetchOptions{
142 RefSpecs: []config.RefSpec{
143 config.RefSpec("+refs/heads/master:refs/remotes/origin/master"),
144 },
145 }, []*plumbing.Reference{
146 plumbing.NewReferenceFromStrings("refs/remotes/origin/master", "f7b877701fbf855b44c0a9e86f3fdce2c298b07f"),
147 })
148}
149
150func (s *RemoteSuite) TestFetchToNewBranch(c *C) {
151 r := NewRemote(memory.NewStorage(), &config.RemoteConfig{
152 URLs: []string{s.GetLocalRepositoryURL(fixtures.ByTag("tags").One())},
153 })
154
155 s.testFetch(c, r, &FetchOptions{
156 RefSpecs: []config.RefSpec{
157 // qualified branch to unqualified branch
158 "refs/heads/master:foo",
159 // unqualified branch to unqualified branch
160 "+master:bar",
161 // unqualified tag to unqualified branch
162 config.RefSpec("tree-tag:tree-tag"),
163 // unqualified tag to qualified tag
164 config.RefSpec("+commit-tag:refs/tags/renamed-tag"),
165 },
166 }, []*plumbing.Reference{
167 plumbing.NewReferenceFromStrings("refs/heads/foo", "f7b877701fbf855b44c0a9e86f3fdce2c298b07f"),
168 plumbing.NewReferenceFromStrings("refs/heads/bar", "f7b877701fbf855b44c0a9e86f3fdce2c298b07f"),
169 plumbing.NewReferenceFromStrings("refs/heads/tree-tag", "152175bf7e5580299fa1f0ba41ef6474cc043b70"),
170 plumbing.NewReferenceFromStrings("refs/tags/tree-tag", "152175bf7e5580299fa1f0ba41ef6474cc043b70"),
171 plumbing.NewReferenceFromStrings("refs/tags/renamed-tag", "ad7897c0fb8e7d9a9ba41fa66072cf06095a6cfc"),
172 plumbing.NewReferenceFromStrings("refs/tags/commit-tag", "ad7897c0fb8e7d9a9ba41fa66072cf06095a6cfc"),
173 })
174}
175
176func (s *RemoteSuite) TestFetchToNewBranchWithAllTags(c *C) {
177 r := NewRemote(memory.NewStorage(), &config.RemoteConfig{
178 URLs: []string{s.GetLocalRepositoryURL(fixtures.ByTag("tags").One())},
179 })
180
181 s.testFetch(c, r, &FetchOptions{
182 Tags: AllTags,
183 RefSpecs: []config.RefSpec{
184 // qualified branch to unqualified branch
185 "+refs/heads/master:foo",
186 // unqualified branch to unqualified branch
187 "master:bar",
188 // unqualified tag to unqualified branch
189 config.RefSpec("+tree-tag:tree-tag"),
190 // unqualified tag to qualified tag
191 config.RefSpec("commit-tag:refs/tags/renamed-tag"),
192 },
193 }, []*plumbing.Reference{
194 plumbing.NewReferenceFromStrings("refs/heads/foo", "f7b877701fbf855b44c0a9e86f3fdce2c298b07f"),
195 plumbing.NewReferenceFromStrings("refs/heads/bar", "f7b877701fbf855b44c0a9e86f3fdce2c298b07f"),
196 plumbing.NewReferenceFromStrings("refs/heads/tree-tag", "152175bf7e5580299fa1f0ba41ef6474cc043b70"),
197 plumbing.NewReferenceFromStrings("refs/tags/tree-tag", "152175bf7e5580299fa1f0ba41ef6474cc043b70"),
198 plumbing.NewReferenceFromStrings("refs/tags/renamed-tag", "ad7897c0fb8e7d9a9ba41fa66072cf06095a6cfc"),
199 plumbing.NewReferenceFromStrings("refs/tags/commit-tag", "ad7897c0fb8e7d9a9ba41fa66072cf06095a6cfc"),
200 plumbing.NewReferenceFromStrings("refs/tags/annotated-tag", "b742a2a9fa0afcfa9a6fad080980fbc26b007c69"),
201 plumbing.NewReferenceFromStrings("refs/tags/blob-tag", "fe6cb94756faa81e5ed9240f9191b833db5f40ae"),
202 plumbing.NewReferenceFromStrings("refs/tags/lightweight-tag", "f7b877701fbf855b44c0a9e86f3fdce2c298b07f"),
203 })
204}
205
206func (s *RemoteSuite) TestFetchNonExistentReference(c *C) {
207 r := NewRemote(memory.NewStorage(), &config.RemoteConfig{
208 URLs: []string{s.GetLocalRepositoryURL(fixtures.ByTag("tags").One())},
209 })
210
211 err := r.Fetch(&FetchOptions{
212 RefSpecs: []config.RefSpec{
213 config.RefSpec("+refs/heads/foo:refs/remotes/origin/foo"),
214 },
215 })
216
217 c.Assert(err, ErrorMatches, "couldn't find remote ref.*")
218 c.Assert(errors.Is(err, NoMatchingRefSpecError{}), Equals, true)
219}
220
221func (s *RemoteSuite) TestFetchContext(c *C) {
222 r := NewRemote(memory.NewStorage(), &config.RemoteConfig{
223 URLs: []string{s.GetLocalRepositoryURL(fixtures.ByTag("tags").One())},
224 })
225
226 ctx, cancel := context.WithCancel(context.Background())
227 defer cancel()
228
229 err := r.FetchContext(ctx, &FetchOptions{
230 RefSpecs: []config.RefSpec{
231 config.RefSpec("+refs/heads/master:refs/remotes/origin/master"),
232 },
233 })
234 c.Assert(err, IsNil)
235}
236
237func (s *RemoteSuite) TestFetchContextCanceled(c *C) {
238 r := NewRemote(memory.NewStorage(), &config.RemoteConfig{
239 URLs: []string{s.GetLocalRepositoryURL(fixtures.ByTag("tags").One())},
240 })
241
242 ctx, cancel := context.WithCancel(context.Background())
243 cancel()
244
245 err := r.FetchContext(ctx, &FetchOptions{
246 RefSpecs: []config.RefSpec{
247 config.RefSpec("+refs/heads/master:refs/remotes/origin/master"),
248 },
249 })
250 c.Assert(err, Equals, context.Canceled)
251}
252
253func (s *RemoteSuite) TestFetchWithAllTags(c *C) {
254 r := NewRemote(memory.NewStorage(), &config.RemoteConfig{
255 URLs: []string{s.GetLocalRepositoryURL(fixtures.ByTag("tags").One())},
256 })
257
258 s.testFetch(c, r, &FetchOptions{
259 Tags: AllTags,
260 RefSpecs: []config.RefSpec{
261 config.RefSpec("+refs/heads/master:refs/remotes/origin/master"),
262 },
263 }, []*plumbing.Reference{
264 plumbing.NewReferenceFromStrings("refs/remotes/origin/master", "f7b877701fbf855b44c0a9e86f3fdce2c298b07f"),
265 plumbing.NewReferenceFromStrings("refs/tags/annotated-tag", "b742a2a9fa0afcfa9a6fad080980fbc26b007c69"),
266 plumbing.NewReferenceFromStrings("refs/tags/tree-tag", "152175bf7e5580299fa1f0ba41ef6474cc043b70"),
267 plumbing.NewReferenceFromStrings("refs/tags/commit-tag", "ad7897c0fb8e7d9a9ba41fa66072cf06095a6cfc"),
268 plumbing.NewReferenceFromStrings("refs/tags/blob-tag", "fe6cb94756faa81e5ed9240f9191b833db5f40ae"),
269 plumbing.NewReferenceFromStrings("refs/tags/lightweight-tag", "f7b877701fbf855b44c0a9e86f3fdce2c298b07f"),
270 })
271}
272
273func (s *RemoteSuite) TestFetchWithNoTags(c *C) {
274 r := NewRemote(memory.NewStorage(), &config.RemoteConfig{
275 URLs: []string{s.GetLocalRepositoryURL(fixtures.ByTag("tags").One())},
276 })
277
278 s.testFetch(c, r, &FetchOptions{
279 Tags: NoTags,
280 RefSpecs: []config.RefSpec{
281 config.RefSpec("+refs/heads/*:refs/remotes/origin/*"),
282 },
283 }, []*plumbing.Reference{
284 plumbing.NewReferenceFromStrings("refs/remotes/origin/master", "f7b877701fbf855b44c0a9e86f3fdce2c298b07f"),
285 })
286
287}
288
289func (s *RemoteSuite) TestFetchWithDepth(c *C) {
290 r := NewRemote(memory.NewStorage(), &config.RemoteConfig{
291 URLs: []string{s.GetBasicLocalRepositoryURL()},
292 })
293
294 s.testFetch(c, r, &FetchOptions{
295 Depth: 1,
296 RefSpecs: []config.RefSpec{
297 config.RefSpec("+refs/heads/*:refs/remotes/origin/*"),
298 },
299 }, []*plumbing.Reference{
300 plumbing.NewReferenceFromStrings("refs/remotes/origin/master", "6ecf0ef2c2dffb796033e5a02219af86ec6584e5"),
301 plumbing.NewReferenceFromStrings("refs/remotes/origin/branch", "e8d3ffab552895c19b9fcf7aa264d277cde33881"),
302 plumbing.NewReferenceFromStrings("refs/tags/v1.0.0", "6ecf0ef2c2dffb796033e5a02219af86ec6584e5"),
303 })
304
305 c.Assert(r.s.(*memory.Storage).Objects, HasLen, 18)
306}
307
308func (s *RemoteSuite) TestFetchWithDepthChange(c *C) {
309 r := NewRemote(memory.NewStorage(), &config.RemoteConfig{
310 URLs: []string{s.GetBasicLocalRepositoryURL()},
311 })
312
313 s.testFetch(c, r, &FetchOptions{
314 Depth: 1,
315 RefSpecs: []config.RefSpec{
316 config.RefSpec("refs/heads/master:refs/heads/master"),
317 },
318 }, []*plumbing.Reference{
319 plumbing.NewReferenceFromStrings("refs/heads/master", "6ecf0ef2c2dffb796033e5a02219af86ec6584e5"),
320 })
321 c.Assert(r.s.(*memory.Storage).Commits, HasLen, 1)
322
323 s.testFetch(c, r, &FetchOptions{
324 Depth: 3,
325 RefSpecs: []config.RefSpec{
326 config.RefSpec("refs/heads/master:refs/heads/master"),
327 },
328 }, []*plumbing.Reference{
329 plumbing.NewReferenceFromStrings("refs/heads/master", "6ecf0ef2c2dffb796033e5a02219af86ec6584e5"),
330 })
331 c.Assert(r.s.(*memory.Storage).Commits, HasLen, 3)
332}
333
334func (s *RemoteSuite) testFetch(c *C, r *Remote, o *FetchOptions, expected []*plumbing.Reference) {
335 err := r.Fetch(o)
336 c.Assert(err, IsNil)
337
338 var refs int
339 l, err := r.s.IterReferences()
340 c.Assert(err, IsNil)
341 l.ForEach(func(r *plumbing.Reference) error { refs++; return nil })
342
343 c.Assert(refs, Equals, len(expected))
344
345 for _, exp := range expected {
346 r, err := r.s.Reference(exp.Name())
347 c.Assert(err, IsNil)
348 c.Assert(exp.String(), Equals, r.String())
349 }
350}
351
352func (s *RemoteSuite) TestFetchOfMissingObjects(c *C) {
353 tmp := c.MkDir()
354
355 // clone to a local temp folder
356 _, err := PlainClone(tmp, true, &CloneOptions{
357 URL: fixtures.Basic().One().DotGit().Root(),
358 })
359 c.Assert(err, IsNil)
360
361 // Delete the pack files
362 fsTmp := osfs.New(tmp)
363 err = util.RemoveAll(fsTmp, "objects/pack")
364 c.Assert(err, IsNil)
365
366 // Reopen the repo from the filesystem (with missing objects)
367 r, err := Open(filesystem.NewStorage(fsTmp, cache.NewObjectLRUDefault()), nil)
368 c.Assert(err, IsNil)
369
370 // Confirm we are missing a commit
371 _, err = r.CommitObject(plumbing.NewHash("6ecf0ef2c2dffb796033e5a02219af86ec6584e5"))
372 c.Assert(err, Equals, plumbing.ErrObjectNotFound)
373
374 // Refetch to get all the missing objects
375 err = r.Fetch(&FetchOptions{})
376 c.Assert(err, IsNil)
377
378 // Confirm we now have the commit
379 _, err = r.CommitObject(plumbing.NewHash("6ecf0ef2c2dffb796033e5a02219af86ec6584e5"))
380 c.Assert(err, IsNil)
381}
382
383func (s *RemoteSuite) TestFetchWithProgress(c *C) {
384 url := s.GetBasicLocalRepositoryURL()
385 sto := memory.NewStorage()
386 buf := bytes.NewBuffer(nil)
387
388 r := NewRemote(sto, &config.RemoteConfig{Name: "foo", URLs: []string{url}})
389
390 refspec := config.RefSpec("+refs/heads/*:refs/remotes/origin/*")
391 err := r.Fetch(&FetchOptions{
392 RefSpecs: []config.RefSpec{refspec},
393 Progress: buf,
394 })
395
396 c.Assert(err, IsNil)
397 c.Assert(sto.Objects, HasLen, 31)
398
399 c.Assert(buf.Len(), Not(Equals), 0)
400}
401
402type mockPackfileWriter struct {
403 storage.Storer
404 PackfileWriterCalled bool
405}
406
407func (m *mockPackfileWriter) PackfileWriter() (io.WriteCloser, error) {
408 m.PackfileWriterCalled = true
409 return m.Storer.(storer.PackfileWriter).PackfileWriter()
410}
411
412func (s *RemoteSuite) TestFetchWithPackfileWriter(c *C) {
413 fs := s.TemporalFilesystem(c)
414
415 fss := filesystem.NewStorage(fs, cache.NewObjectLRUDefault())
416 mock := &mockPackfileWriter{Storer: fss}
417
418 url := s.GetBasicLocalRepositoryURL()
419 r := NewRemote(mock, &config.RemoteConfig{Name: "foo", URLs: []string{url}})
420
421 refspec := config.RefSpec("+refs/heads/*:refs/remotes/origin/*")
422 err := r.Fetch(&FetchOptions{
423 RefSpecs: []config.RefSpec{refspec},
424 })
425
426 c.Assert(err, IsNil)
427
428 var count int
429 iter, err := mock.IterEncodedObjects(plumbing.AnyObject)
430 c.Assert(err, IsNil)
431
432 iter.ForEach(func(plumbing.EncodedObject) error {
433 count++
434 return nil
435 })
436
437 c.Assert(count, Equals, 31)
438 c.Assert(mock.PackfileWriterCalled, Equals, true)
439}
440
441func (s *RemoteSuite) TestFetchNoErrAlreadyUpToDate(c *C) {
442 url := s.GetBasicLocalRepositoryURL()
443 s.doTestFetchNoErrAlreadyUpToDate(c, url)
444}
445
446func (s *RemoteSuite) TestFetchNoErrAlreadyUpToDateButStillUpdateLocalRemoteRefs(c *C) {
447 r := NewRemote(memory.NewStorage(), &config.RemoteConfig{
448 URLs: []string{s.GetBasicLocalRepositoryURL()},
449 })
450
451 o := &FetchOptions{
452 RefSpecs: []config.RefSpec{
453 config.RefSpec("+refs/heads/*:refs/remotes/origin/*"),
454 },
455 }
456
457 err := r.Fetch(o)
458 c.Assert(err, IsNil)
459
460 // Simulate an out of date remote ref even though we have the new commit locally
461 r.s.SetReference(plumbing.NewReferenceFromStrings(
462 "refs/remotes/origin/master", "918c48b83bd081e863dbe1b80f8998f058cd8294",
463 ))
464
465 err = r.Fetch(o)
466 c.Assert(err, IsNil)
467
468 exp := plumbing.NewReferenceFromStrings(
469 "refs/remotes/origin/master", "6ecf0ef2c2dffb796033e5a02219af86ec6584e5",
470 )
471
472 ref, err := r.s.Reference("refs/remotes/origin/master")
473 c.Assert(err, IsNil)
474 c.Assert(exp.String(), Equals, ref.String())
475}
476
477func (s *RemoteSuite) TestFetchNoErrAlreadyUpToDateWithNonCommitObjects(c *C) {
478 fixture := fixtures.ByTag("tags").One()
479 url := s.GetLocalRepositoryURL(fixture)
480 s.doTestFetchNoErrAlreadyUpToDate(c, url)
481}
482
483func (s *RemoteSuite) doTestFetchNoErrAlreadyUpToDate(c *C, url string) {
484 r := NewRemote(memory.NewStorage(), &config.RemoteConfig{URLs: []string{url}})
485
486 o := &FetchOptions{
487 RefSpecs: []config.RefSpec{
488 config.RefSpec("+refs/heads/*:refs/remotes/origin/*"),
489 },
490 }
491
492 err := r.Fetch(o)
493 c.Assert(err, IsNil)
494 err = r.Fetch(o)
495 c.Assert(err, Equals, NoErrAlreadyUpToDate)
496}
497
498func (s *RemoteSuite) testFetchFastForward(c *C, sto storage.Storer) {
499 r := NewRemote(sto, &config.RemoteConfig{
500 URLs: []string{s.GetBasicLocalRepositoryURL()},
501 })
502
503 s.testFetch(c, r, &FetchOptions{
504 RefSpecs: []config.RefSpec{
505 config.RefSpec("+refs/heads/master:refs/heads/master"),
506 },
507 }, []*plumbing.Reference{
508 plumbing.NewReferenceFromStrings("refs/heads/master", "6ecf0ef2c2dffb796033e5a02219af86ec6584e5"),
509 })
510
511 // First make sure that we error correctly when a force is required.
512 err := r.Fetch(&FetchOptions{
513 RefSpecs: []config.RefSpec{
514 config.RefSpec("refs/heads/branch:refs/heads/master"),
515 },
516 })
517 c.Assert(err, Equals, ErrForceNeeded)
518
519 // And that forcing it fixes the problem.
520 err = r.Fetch(&FetchOptions{
521 RefSpecs: []config.RefSpec{
522 config.RefSpec("+refs/heads/branch:refs/heads/master"),
523 },
524 })
525 c.Assert(err, IsNil)
526
527 // Now test that a fast-forward, non-force fetch works.
528 r.s.SetReference(plumbing.NewReferenceFromStrings(
529 "refs/heads/master", "918c48b83bd081e863dbe1b80f8998f058cd8294",
530 ))
531 s.testFetch(c, r, &FetchOptions{
532 RefSpecs: []config.RefSpec{
533 config.RefSpec("refs/heads/master:refs/heads/master"),
534 },
535 }, []*plumbing.Reference{
536 plumbing.NewReferenceFromStrings("refs/heads/master", "6ecf0ef2c2dffb796033e5a02219af86ec6584e5"),
537 })
538}
539
540func (s *RemoteSuite) TestFetchFastForwardMem(c *C) {
541 s.testFetchFastForward(c, memory.NewStorage())
542}
543
544func (s *RemoteSuite) TestFetchFastForwardFS(c *C) {
545 fs := s.TemporalFilesystem(c)
546
547 fss := filesystem.NewStorage(fs, cache.NewObjectLRUDefault())
548
549 // This exercises `storage.filesystem.Storage.CheckAndSetReference()`.
550 s.testFetchFastForward(c, fss)
551}
552
553func (s *RemoteSuite) TestString(c *C) {
554 r := NewRemote(nil, &config.RemoteConfig{
555 Name: "foo",
556 URLs: []string{"https://github.com/git-fixtures/basic.git"},
557 })
558
559 c.Assert(r.String(), Equals, ""+
560 "foo\thttps://github.com/git-fixtures/basic.git (fetch)\n"+
561 "foo\thttps://github.com/git-fixtures/basic.git (push)",
562 )
563}
564
565func (s *RemoteSuite) TestPushToEmptyRepository(c *C) {
566 url := c.MkDir()
567
568 server, err := PlainInit(url, true)
569 c.Assert(err, IsNil)
570
571 srcFs := fixtures.Basic().One().DotGit()
572 sto := filesystem.NewStorage(srcFs, cache.NewObjectLRUDefault())
573
574 r := NewRemote(sto, &config.RemoteConfig{
575 Name: DefaultRemoteName,
576 URLs: []string{url},
577 })
578
579 rs := config.RefSpec("refs/heads/*:refs/heads/*")
580 err = r.Push(&PushOptions{
581 RefSpecs: []config.RefSpec{rs},
582 })
583 c.Assert(err, IsNil)
584
585 iter, err := r.s.IterReferences()
586 c.Assert(err, IsNil)
587
588 expected := make(map[string]string)
589 iter.ForEach(func(ref *plumbing.Reference) error {
590 if !ref.Name().IsBranch() {
591 return nil
592 }
593
594 expected[ref.Name().String()] = ref.Hash().String()
595 return nil
596 })
597 c.Assert(err, IsNil)
598
599 AssertReferences(c, server, expected)
600
601}
602
603func (s *RemoteSuite) TestPushContext(c *C) {
604 url := c.MkDir()
605
606 _, err := PlainInit(url, true)
607 c.Assert(err, IsNil)
608
609 fs := fixtures.ByURL("https://github.com/git-fixtures/tags.git").One().DotGit()
610 sto := filesystem.NewStorage(fs, cache.NewObjectLRUDefault())
611
612 r := NewRemote(sto, &config.RemoteConfig{
613 Name: DefaultRemoteName,
614 URLs: []string{url},
615 })
616
617 ctx, cancel := context.WithCancel(context.Background())
618 defer cancel()
619
620 numGoroutines := runtime.NumGoroutine()
621
622 err = r.PushContext(ctx, &PushOptions{
623 RefSpecs: []config.RefSpec{"refs/tags/*:refs/tags/*"},
624 })
625 c.Assert(err, IsNil)
626
627 eventually(c, func() bool {
628 return runtime.NumGoroutine() <= numGoroutines
629 })
630}
631
632func eventually(c *C, condition func() bool) {
633 select {
634 case <-time.After(5 * time.Second):
635 default:
636 if condition() {
637 break
638 }
639 time.Sleep(100 * time.Millisecond)
640 }
641
642 c.Assert(condition(), Equals, true)
643}
644
645func (s *RemoteSuite) TestPushContextCanceled(c *C) {
646 url := c.MkDir()
647
648 _, err := PlainInit(url, true)
649 c.Assert(err, IsNil)
650
651 fs := fixtures.ByURL("https://github.com/git-fixtures/tags.git").One().DotGit()
652 sto := filesystem.NewStorage(fs, cache.NewObjectLRUDefault())
653
654 r := NewRemote(sto, &config.RemoteConfig{
655 Name: DefaultRemoteName,
656 URLs: []string{url},
657 })
658
659 ctx, cancel := context.WithCancel(context.Background())
660 cancel()
661
662 numGoroutines := runtime.NumGoroutine()
663
664 err = r.PushContext(ctx, &PushOptions{
665 RefSpecs: []config.RefSpec{"refs/tags/*:refs/tags/*"},
666 })
667 c.Assert(err, Equals, context.Canceled)
668
669 eventually(c, func() bool {
670 return runtime.NumGoroutine() <= numGoroutines
671 })
672}
673
674func (s *RemoteSuite) TestPushTags(c *C) {
675 url := c.MkDir()
676
677 server, err := PlainInit(url, true)
678 c.Assert(err, IsNil)
679
680 fs := fixtures.ByURL("https://github.com/git-fixtures/tags.git").One().DotGit()
681 sto := filesystem.NewStorage(fs, cache.NewObjectLRUDefault())
682
683 r := NewRemote(sto, &config.RemoteConfig{
684 Name: DefaultRemoteName,
685 URLs: []string{url},
686 })
687
688 err = r.Push(&PushOptions{
689 RefSpecs: []config.RefSpec{"refs/tags/*:refs/tags/*"},
690 })
691 c.Assert(err, IsNil)
692
693 AssertReferences(c, server, map[string]string{
694 "refs/tags/lightweight-tag": "f7b877701fbf855b44c0a9e86f3fdce2c298b07f",
695 "refs/tags/annotated-tag": "b742a2a9fa0afcfa9a6fad080980fbc26b007c69",
696 "refs/tags/commit-tag": "ad7897c0fb8e7d9a9ba41fa66072cf06095a6cfc",
697 "refs/tags/blob-tag": "fe6cb94756faa81e5ed9240f9191b833db5f40ae",
698 "refs/tags/tree-tag": "152175bf7e5580299fa1f0ba41ef6474cc043b70",
699 })
700}
701
702func (s *RemoteSuite) TestPushFollowTags(c *C) {
703 url := c.MkDir()
704
705 server, err := PlainInit(url, true)
706 c.Assert(err, IsNil)
707
708 fs := fixtures.ByURL("https://github.com/git-fixtures/basic.git").One().DotGit()
709 sto := filesystem.NewStorage(fs, cache.NewObjectLRUDefault())
710
711 r := NewRemote(sto, &config.RemoteConfig{
712 Name: DefaultRemoteName,
713 URLs: []string{url},
714 })
715
716 localRepo := newRepository(sto, fs)
717 tipTag, err := localRepo.CreateTag(
718 "tip",
719 plumbing.NewHash("e8d3ffab552895c19b9fcf7aa264d277cde33881"),
720 &CreateTagOptions{
721 Message: "an annotated tag",
722 },
723 )
724 c.Assert(err, IsNil)
725
726 initialTag, err := localRepo.CreateTag(
727 "initial-commit",
728 plumbing.NewHash("b029517f6300c2da0f4b651b8642506cd6aaf45d"),
729 &CreateTagOptions{
730 Message: "a tag for the initial commit",
731 },
732 )
733 c.Assert(err, IsNil)
734
735 _, err = localRepo.CreateTag(
736 "master-tag",
737 plumbing.NewHash("6ecf0ef2c2dffb796033e5a02219af86ec6584e5"),
738 &CreateTagOptions{
739 Message: "a tag with a commit not reachable from branch",
740 },
741 )
742 c.Assert(err, IsNil)
743
744 err = r.Push(&PushOptions{
745 RefSpecs: []config.RefSpec{"+refs/heads/branch:refs/heads/branch"},
746 FollowTags: true,
747 })
748 c.Assert(err, IsNil)
749
750 AssertReferences(c, server, map[string]string{
751 "refs/heads/branch": "e8d3ffab552895c19b9fcf7aa264d277cde33881",
752 "refs/tags/tip": tipTag.Hash().String(),
753 "refs/tags/initial-commit": initialTag.Hash().String(),
754 })
755
756 AssertReferencesMissing(c, server, []string{
757 "refs/tags/master-tag",
758 })
759}
760
761func (s *RemoteSuite) TestPushNoErrAlreadyUpToDate(c *C) {
762 fs := fixtures.Basic().One().DotGit()
763 sto := filesystem.NewStorage(fs, cache.NewObjectLRUDefault())
764
765 r := NewRemote(sto, &config.RemoteConfig{
766 Name: DefaultRemoteName,
767 URLs: []string{fs.Root()},
768 })
769
770 err := r.Push(&PushOptions{
771 RefSpecs: []config.RefSpec{"refs/heads/*:refs/heads/*"},
772 })
773 c.Assert(err, Equals, NoErrAlreadyUpToDate)
774}
775
776func (s *RemoteSuite) TestPushDeleteReference(c *C) {
777 fs := fixtures.Basic().One().DotGit()
778 sto := filesystem.NewStorage(fs, cache.NewObjectLRUDefault())
779
780 url := c.MkDir()
781
782 r, err := PlainClone(url, true, &CloneOptions{
783 URL: fs.Root(),
784 })
785 c.Assert(err, IsNil)
786
787 remote, err := r.Remote(DefaultRemoteName)
788 c.Assert(err, IsNil)
789
790 err = remote.Push(&PushOptions{
791 RefSpecs: []config.RefSpec{":refs/heads/branch"},
792 })
793 c.Assert(err, IsNil)
794
795 _, err = sto.Reference(plumbing.ReferenceName("refs/heads/branch"))
796 c.Assert(err, Equals, plumbing.ErrReferenceNotFound)
797
798 _, err = r.Storer.Reference(plumbing.ReferenceName("refs/heads/branch"))
799 c.Assert(err, Equals, plumbing.ErrReferenceNotFound)
800}
801
802func (s *RemoteSuite) TestForcePushDeleteReference(c *C) {
803 fs := fixtures.Basic().One().DotGit()
804 sto := filesystem.NewStorage(fs, cache.NewObjectLRUDefault())
805
806 url := c.MkDir()
807
808 r, err := PlainClone(url, true, &CloneOptions{
809 URL: fs.Root(),
810 })
811 c.Assert(err, IsNil)
812
813 remote, err := r.Remote(DefaultRemoteName)
814 c.Assert(err, IsNil)
815
816 err = remote.Push(&PushOptions{
817 RefSpecs: []config.RefSpec{":refs/heads/branch"},
818 Force: true,
819 })
820 c.Assert(err, IsNil)
821
822 _, err = sto.Reference(plumbing.ReferenceName("refs/heads/branch"))
823 c.Assert(err, Equals, plumbing.ErrReferenceNotFound)
824
825 _, err = r.Storer.Reference(plumbing.ReferenceName("refs/heads/branch"))
826 c.Assert(err, Equals, plumbing.ErrReferenceNotFound)
827}
828
829func (s *RemoteSuite) TestPushRejectNonFastForward(c *C) {
830 fs := fixtures.Basic().One().DotGit()
831 server := filesystem.NewStorage(fs, cache.NewObjectLRUDefault())
832
833 url := c.MkDir()
834
835 r, err := PlainClone(url, true, &CloneOptions{
836 URL: fs.Root(),
837 })
838 c.Assert(err, IsNil)
839
840 remote, err := r.Remote(DefaultRemoteName)
841 c.Assert(err, IsNil)
842
843 branch := plumbing.ReferenceName("refs/heads/branch")
844 oldRef, err := server.Reference(branch)
845 c.Assert(err, IsNil)
846 c.Assert(oldRef, NotNil)
847
848 err = remote.Push(&PushOptions{RefSpecs: []config.RefSpec{
849 "refs/heads/master:refs/heads/branch",
850 }})
851 c.Assert(err, ErrorMatches, "non-fast-forward update: refs/heads/branch")
852
853 newRef, err := server.Reference(branch)
854 c.Assert(err, IsNil)
855 c.Assert(newRef, DeepEquals, oldRef)
856}
857
858func (s *RemoteSuite) TestPushForce(c *C) {
859 f := fixtures.Basic().One()
860 sto := filesystem.NewStorage(f.DotGit(), cache.NewObjectLRUDefault())
861
862 dstFs := f.DotGit()
863 dstSto := filesystem.NewStorage(dstFs, cache.NewObjectLRUDefault())
864
865 url := dstFs.Root()
866 r := NewRemote(sto, &config.RemoteConfig{
867 Name: DefaultRemoteName,
868 URLs: []string{url},
869 })
870
871 oldRef, err := dstSto.Reference(plumbing.ReferenceName("refs/heads/branch"))
872 c.Assert(err, IsNil)
873 c.Assert(oldRef, NotNil)
874
875 err = r.Push(&PushOptions{RefSpecs: []config.RefSpec{
876 config.RefSpec("+refs/heads/master:refs/heads/branch"),
877 }})
878 c.Assert(err, IsNil)
879
880 newRef, err := dstSto.Reference(plumbing.ReferenceName("refs/heads/branch"))
881 c.Assert(err, IsNil)
882 c.Assert(newRef, Not(DeepEquals), oldRef)
883}
884
885func (s *RemoteSuite) TestPushForceWithOption(c *C) {
886 f := fixtures.Basic().One()
887 sto := filesystem.NewStorage(f.DotGit(), cache.NewObjectLRUDefault())
888
889 dstFs := f.DotGit()
890 dstSto := filesystem.NewStorage(dstFs, cache.NewObjectLRUDefault())
891
892 url := dstFs.Root()
893 r := NewRemote(sto, &config.RemoteConfig{
894 Name: DefaultRemoteName,
895 URLs: []string{url},
896 })
897
898 oldRef, err := dstSto.Reference(plumbing.ReferenceName("refs/heads/branch"))
899 c.Assert(err, IsNil)
900 c.Assert(oldRef, NotNil)
901
902 err = r.Push(&PushOptions{
903 RefSpecs: []config.RefSpec{"refs/heads/master:refs/heads/branch"},
904 Force: true,
905 })
906 c.Assert(err, IsNil)
907
908 newRef, err := dstSto.Reference(plumbing.ReferenceName("refs/heads/branch"))
909 c.Assert(err, IsNil)
910 c.Assert(newRef, Not(DeepEquals), oldRef)
911}
912
913func (s *RemoteSuite) TestPushForceWithLease_success(c *C) {
914 testCases := []struct {
915 desc string
916 forceWithLease ForceWithLease
917 }{
918 {
919 desc: "no arguments",
920 forceWithLease: ForceWithLease{},
921 },
922 {
923 desc: "ref name",
924 forceWithLease: ForceWithLease{
925 RefName: plumbing.ReferenceName("refs/heads/branch"),
926 },
927 },
928 {
929 desc: "ref name and sha",
930 forceWithLease: ForceWithLease{
931 RefName: plumbing.ReferenceName("refs/heads/branch"),
932 Hash: plumbing.NewHash("e8d3ffab552895c19b9fcf7aa264d277cde33881"),
933 },
934 },
935 }
936
937 for _, tc := range testCases {
938 c.Log("Executing test cases:", tc.desc)
939
940 f := fixtures.Basic().One()
941 sto := filesystem.NewStorage(f.DotGit(), cache.NewObjectLRUDefault())
942 dstFs := f.DotGit()
943 dstSto := filesystem.NewStorage(dstFs, cache.NewObjectLRUDefault())
944
945 newCommit := plumbing.NewHashReference(
946 "refs/heads/branch", plumbing.NewHash("35e85108805c84807bc66a02d91535e1e24b38b9"),
947 )
948 c.Assert(sto.SetReference(newCommit), IsNil)
949
950 ref, err := sto.Reference("refs/heads/branch")
951 c.Assert(err, IsNil)
952 c.Log(ref.String())
953
954 url := dstFs.Root()
955 r := NewRemote(sto, &config.RemoteConfig{
956 Name: DefaultRemoteName,
957 URLs: []string{url},
958 })
959
960 oldRef, err := dstSto.Reference("refs/heads/branch")
961 c.Assert(err, IsNil)
962 c.Assert(oldRef, NotNil)
963
964 c.Assert(r.Push(&PushOptions{
965 RefSpecs: []config.RefSpec{"refs/heads/branch:refs/heads/branch"},
966 ForceWithLease: &ForceWithLease{},
967 }), IsNil)
968
969 newRef, err := dstSto.Reference("refs/heads/branch")
970 c.Assert(err, IsNil)
971 c.Assert(newRef, DeepEquals, newCommit)
972 }
973}
974
975func (s *RemoteSuite) TestPushForceWithLease_failure(c *C) {
976 testCases := []struct {
977 desc string
978 forceWithLease ForceWithLease
979 }{
980 {
981 desc: "no arguments",
982 forceWithLease: ForceWithLease{},
983 },
984 {
985 desc: "ref name",
986 forceWithLease: ForceWithLease{
987 RefName: plumbing.ReferenceName("refs/heads/branch"),
988 },
989 },
990 {
991 desc: "ref name and sha",
992 forceWithLease: ForceWithLease{
993 RefName: plumbing.ReferenceName("refs/heads/branch"),
994 Hash: plumbing.NewHash("152175bf7e5580299fa1f0ba41ef6474cc043b70"),
995 },
996 },
997 }
998
999 for _, tc := range testCases {
1000 c.Log("Executing test cases:", tc.desc)
1001
1002 f := fixtures.Basic().One()
1003 sto := filesystem.NewStorage(f.DotGit(), cache.NewObjectLRUDefault())
1004 c.Assert(sto.SetReference(
1005 plumbing.NewHashReference(
1006 "refs/heads/branch", plumbing.NewHash("35e85108805c84807bc66a02d91535e1e24b38b9"),
1007 ),
1008 ), IsNil)
1009
1010 dstFs := f.DotGit()
1011 dstSto := filesystem.NewStorage(dstFs, cache.NewObjectLRUDefault())
1012 c.Assert(dstSto.SetReference(
1013 plumbing.NewHashReference(
1014 "refs/heads/branch", plumbing.NewHash("ad7897c0fb8e7d9a9ba41fa66072cf06095a6cfc"),
1015 ),
1016 ), IsNil)
1017
1018 url := dstFs.Root()
1019 r := NewRemote(sto, &config.RemoteConfig{
1020 Name: DefaultRemoteName,
1021 URLs: []string{url},
1022 })
1023
1024 oldRef, err := dstSto.Reference("refs/heads/branch")
1025 c.Assert(err, IsNil)
1026 c.Assert(oldRef, NotNil)
1027
1028 err = r.Push(&PushOptions{
1029 RefSpecs: []config.RefSpec{"refs/heads/branch:refs/heads/branch"},
1030 ForceWithLease: &ForceWithLease{},
1031 })
1032
1033 c.Assert(err, DeepEquals, errors.New("non-fast-forward update: refs/heads/branch"))
1034
1035 newRef, err := dstSto.Reference("refs/heads/branch")
1036 c.Assert(err, IsNil)
1037 c.Assert(newRef, Not(DeepEquals), plumbing.NewHash("35e85108805c84807bc66a02d91535e1e24b38b9"))
1038 }
1039}
1040
1041func (s *RemoteSuite) TestPushPrune(c *C) {
1042 fs := fixtures.Basic().One().DotGit()
1043
1044 url := c.MkDir()
1045
1046 server, err := PlainClone(url, true, &CloneOptions{
1047 URL: fs.Root(),
1048 })
1049 c.Assert(err, IsNil)
1050
1051 dir := c.MkDir()
1052
1053 r, err := PlainClone(dir, true, &CloneOptions{
1054 URL: url,
1055 })
1056 c.Assert(err, IsNil)
1057
1058 tag, err := r.Reference(plumbing.ReferenceName("refs/tags/v1.0.0"), true)
1059 c.Assert(err, IsNil)
1060
1061 err = r.DeleteTag("v1.0.0")
1062 c.Assert(err, IsNil)
1063
1064 remote, err := r.Remote(DefaultRemoteName)
1065 c.Assert(err, IsNil)
1066
1067 ref, err := r.Reference(plumbing.ReferenceName("refs/heads/master"), true)
1068 c.Assert(err, IsNil)
1069
1070 err = remote.Push(&PushOptions{
1071 RefSpecs: []config.RefSpec{
1072 config.RefSpec("refs/heads/*:refs/heads/*"),
1073 },
1074 Prune: true,
1075 })
1076 c.Assert(err, Equals, NoErrAlreadyUpToDate)
1077
1078 AssertReferences(c, server, map[string]string{
1079 "refs/tags/v1.0.0": tag.Hash().String(),
1080 })
1081
1082 err = remote.Push(&PushOptions{
1083 RefSpecs: []config.RefSpec{
1084 config.RefSpec("*:*"),
1085 },
1086 Prune: true,
1087 })
1088 c.Assert(err, IsNil)
1089
1090 AssertReferences(c, server, map[string]string{
1091 "refs/remotes/origin/master": ref.Hash().String(),
1092 })
1093
1094 AssertReferences(c, server, map[string]string{
1095 "refs/remotes/origin/master": ref.Hash().String(),
1096 })
1097
1098 _, err = server.Reference(plumbing.ReferenceName("refs/tags/v1.0.0"), true)
1099 c.Assert(err, Equals, plumbing.ErrReferenceNotFound)
1100}
1101
1102func (s *RemoteSuite) TestPushNewReference(c *C) {
1103 fs := fixtures.Basic().One().DotGit()
1104
1105 url := c.MkDir()
1106
1107 server, err := PlainClone(url, true, &CloneOptions{
1108 URL: fs.Root(),
1109 })
1110 c.Assert(err, IsNil)
1111
1112 dir := c.MkDir()
1113
1114 r, err := PlainClone(dir, true, &CloneOptions{
1115 URL: url,
1116 })
1117 c.Assert(err, IsNil)
1118
1119 remote, err := r.Remote(DefaultRemoteName)
1120 c.Assert(err, IsNil)
1121
1122 ref, err := r.Reference(plumbing.ReferenceName("refs/heads/master"), true)
1123 c.Assert(err, IsNil)
1124
1125 err = remote.Push(&PushOptions{RefSpecs: []config.RefSpec{
1126 "refs/heads/master:refs/heads/branch2",
1127 }})
1128 c.Assert(err, IsNil)
1129
1130 AssertReferences(c, server, map[string]string{
1131 "refs/heads/branch2": ref.Hash().String(),
1132 })
1133
1134 AssertReferences(c, r, map[string]string{
1135 "refs/remotes/origin/branch2": ref.Hash().String(),
1136 })
1137}
1138
1139func (s *RemoteSuite) TestPushNewReferenceAndDeleteInBatch(c *C) {
1140 fs := fixtures.Basic().One().DotGit()
1141
1142 url := c.MkDir()
1143
1144 server, err := PlainClone(url, true, &CloneOptions{
1145 URL: fs.Root(),
1146 })
1147 c.Assert(err, IsNil)
1148
1149 dir := c.MkDir()
1150
1151 r, err := PlainClone(dir, true, &CloneOptions{
1152 URL: url,
1153 })
1154 c.Assert(err, IsNil)
1155
1156 remote, err := r.Remote(DefaultRemoteName)
1157 c.Assert(err, IsNil)
1158
1159 ref, err := r.Reference(plumbing.ReferenceName("refs/heads/master"), true)
1160 c.Assert(err, IsNil)
1161
1162 err = remote.Push(&PushOptions{RefSpecs: []config.RefSpec{
1163 "refs/heads/master:refs/heads/branch2",
1164 ":refs/heads/branch",
1165 }})
1166 c.Assert(err, IsNil)
1167
1168 AssertReferences(c, server, map[string]string{
1169 "refs/heads/branch2": ref.Hash().String(),
1170 })
1171
1172 AssertReferences(c, r, map[string]string{
1173 "refs/remotes/origin/branch2": ref.Hash().String(),
1174 })
1175
1176 _, err = server.Storer.Reference(plumbing.ReferenceName("refs/heads/branch"))
1177 c.Assert(err, Equals, plumbing.ErrReferenceNotFound)
1178}
1179
1180func (s *RemoteSuite) TestPushInvalidEndpoint(c *C) {
1181 r := NewRemote(nil, &config.RemoteConfig{Name: "foo", URLs: []string{"http://\\"}})
1182 err := r.Push(&PushOptions{RemoteName: "foo"})
1183 c.Assert(err, ErrorMatches, ".*invalid character.*")
1184}
1185
1186func (s *RemoteSuite) TestPushNonExistentEndpoint(c *C) {
1187 r := NewRemote(nil, &config.RemoteConfig{Name: "foo", URLs: []string{"ssh://non-existent/foo.git"}})
1188 err := r.Push(&PushOptions{})
1189 c.Assert(err, NotNil)
1190}
1191
1192func (s *RemoteSuite) TestPushOverriddenEndpoint(c *C) {
1193 r := NewRemote(nil, &config.RemoteConfig{Name: "origin", URLs: []string{"http://perfectly-valid-url.example.com"}})
1194 err := r.Push(&PushOptions{RemoteURL: "http://\\"})
1195 c.Assert(err, ErrorMatches, ".*invalid character.*")
1196}
1197
1198func (s *RemoteSuite) TestPushInvalidSchemaEndpoint(c *C) {
1199 r := NewRemote(nil, &config.RemoteConfig{Name: "origin", URLs: []string{"qux://foo"}})
1200 err := r.Push(&PushOptions{})
1201 c.Assert(err, ErrorMatches, ".*unsupported scheme.*")
1202}
1203
1204func (s *RemoteSuite) TestPushInvalidFetchOptions(c *C) {
1205 r := NewRemote(nil, &config.RemoteConfig{Name: "foo", URLs: []string{"qux://foo"}})
1206 invalid := config.RefSpec("^*$ñ")
1207 err := r.Push(&PushOptions{RefSpecs: []config.RefSpec{invalid}})
1208 c.Assert(err, Equals, config.ErrRefSpecMalformedSeparator)
1209}
1210
1211func (s *RemoteSuite) TestPushInvalidRefSpec(c *C) {
1212 r := NewRemote(nil, &config.RemoteConfig{
1213 Name: DefaultRemoteName,
1214 URLs: []string{"some-url"},
1215 })
1216
1217 rs := config.RefSpec("^*$**")
1218 err := r.Push(&PushOptions{
1219 RefSpecs: []config.RefSpec{rs},
1220 })
1221 c.Assert(err, Equals, config.ErrRefSpecMalformedSeparator)
1222}
1223
1224func (s *RemoteSuite) TestPushWrongRemoteName(c *C) {
1225 r := NewRemote(nil, &config.RemoteConfig{
1226 Name: DefaultRemoteName,
1227 URLs: []string{"some-url"},
1228 })
1229
1230 err := r.Push(&PushOptions{
1231 RemoteName: "other-remote",
1232 })
1233 c.Assert(err, ErrorMatches, ".*remote names don't match.*")
1234}
1235
1236func (s *RemoteSuite) TestGetHaves(c *C) {
1237 f := fixtures.Basic().One()
1238 sto := filesystem.NewStorage(f.DotGit(), cache.NewObjectLRUDefault())
1239
1240 var localRefs = []*plumbing.Reference{
1241 // Exists
1242 plumbing.NewReferenceFromStrings(
1243 "foo",
1244 "b029517f6300c2da0f4b651b8642506cd6aaf45d",
1245 ),
1246 // Exists
1247 plumbing.NewReferenceFromStrings(
1248 "bar",
1249 "b8e471f58bcbca63b07bda20e428190409c2db47",
1250 ),
1251 // Doesn't Exist
1252 plumbing.NewReferenceFromStrings(
1253 "qux",
1254 "0000000",
1255 ),
1256 }
1257
1258 l, err := getHaves(localRefs, memory.NewStorage(), sto, 0)
1259 c.Assert(err, IsNil)
1260 c.Assert(l, HasLen, 2)
1261}
1262
1263func (s *RemoteSuite) TestList(c *C) {
1264 repo := fixtures.Basic().One()
1265 remote := NewRemote(memory.NewStorage(), &config.RemoteConfig{
1266 Name: DefaultRemoteName,
1267 URLs: []string{repo.URL},
1268 })
1269
1270 refs, err := remote.List(&ListOptions{})
1271 c.Assert(err, IsNil)
1272
1273 expected := []*plumbing.Reference{
1274 plumbing.NewSymbolicReference("HEAD", "refs/heads/master"),
1275 plumbing.NewReferenceFromStrings("refs/heads/master", "6ecf0ef2c2dffb796033e5a02219af86ec6584e5"),
1276 plumbing.NewReferenceFromStrings("refs/heads/branch", "e8d3ffab552895c19b9fcf7aa264d277cde33881"),
1277 plumbing.NewReferenceFromStrings("refs/pull/1/head", "b8e471f58bcbca63b07bda20e428190409c2db47"),
1278 plumbing.NewReferenceFromStrings("refs/pull/2/head", "9632f02833b2f9613afb5e75682132b0b22e4a31"),
1279 plumbing.NewReferenceFromStrings("refs/pull/2/merge", "c37f58a130ca555e42ff96a071cb9ccb3f437504"),
1280 }
1281 c.Assert(len(refs), Equals, len(expected))
1282 for _, e := range expected {
1283 found := false
1284 for _, r := range refs {
1285 if r.Name() == e.Name() {
1286 found = true
1287 c.Assert(r, DeepEquals, e)
1288 }
1289 }
1290 c.Assert(found, Equals, true)
1291 }
1292}
1293
1294func (s *RemoteSuite) TestListPeeling(c *C) {
1295 remote := NewRemote(memory.NewStorage(), &config.RemoteConfig{
1296 Name: DefaultRemoteName,
1297 URLs: []string{"https://github.com/git-fixtures/tags.git"},
1298 })
1299
1300 for _, tc := range []struct {
1301 peelingOption PeelingOption
1302 expectPeeled bool
1303 expectNonPeeled bool
1304 }{
1305 {peelingOption: AppendPeeled, expectPeeled: true, expectNonPeeled: true},
1306 {peelingOption: IgnorePeeled, expectPeeled: false, expectNonPeeled: true},
1307 {peelingOption: OnlyPeeled, expectPeeled: true, expectNonPeeled: false},
1308 } {
1309 refs, err := remote.List(&ListOptions{
1310 PeelingOption: tc.peelingOption,
1311 })
1312 c.Assert(err, IsNil)
1313 c.Assert(len(refs) > 0, Equals, true)
1314
1315 foundPeeled, foundNonPeeled := false, false
1316 for _, ref := range refs {
1317 if strings.HasSuffix(ref.Name().String(), peeledSuffix) {
1318 foundPeeled = true
1319 } else {
1320 foundNonPeeled = true
1321 }
1322 }
1323
1324 c.Assert(foundPeeled, Equals, tc.expectPeeled)
1325 c.Assert(foundNonPeeled, Equals, tc.expectNonPeeled)
1326 }
1327}
1328
1329func (s *RemoteSuite) TestListTimeout(c *C) {
1330 remote := NewRemote(memory.NewStorage(), &config.RemoteConfig{
1331 Name: DefaultRemoteName,
1332 URLs: []string{"https://deelay.me/60000/https://httpstat.us/503"},
1333 })
1334
1335 _, err := remote.List(&ListOptions{})
1336
1337 c.Assert(err, NotNil)
1338}
1339
1340func (s *RemoteSuite) TestUpdateShallows(c *C) {
1341 hashes := []plumbing.Hash{
1342 plumbing.NewHash("0000000000000000000000000000000000000001"),
1343 plumbing.NewHash("0000000000000000000000000000000000000002"),
1344 plumbing.NewHash("0000000000000000000000000000000000000003"),
1345 plumbing.NewHash("0000000000000000000000000000000000000004"),
1346 plumbing.NewHash("0000000000000000000000000000000000000005"),
1347 plumbing.NewHash("0000000000000000000000000000000000000006"),
1348 }
1349
1350 tests := []struct {
1351 hashes []plumbing.Hash
1352 result []plumbing.Hash
1353 }{
1354 // add to empty shallows
1355 {hashes[0:2], hashes[0:2]},
1356 // add new hashes
1357 {hashes[2:4], hashes[0:4]},
1358 // add some hashes already in shallow list
1359 {hashes[2:6], hashes[0:6]},
1360 // add all hashes
1361 {hashes[0:6], hashes[0:6]},
1362 // add empty list
1363 {nil, hashes[0:6]},
1364 }
1365
1366 remote := NewRemote(memory.NewStorage(), &config.RemoteConfig{
1367 Name: DefaultRemoteName,
1368 })
1369
1370 shallows, err := remote.s.Shallow()
1371 c.Assert(err, IsNil)
1372 c.Assert(len(shallows), Equals, 0)
1373
1374 resp := new(packp.UploadPackResponse)
1375 o := &FetchOptions{
1376 Depth: 1,
1377 }
1378
1379 for _, t := range tests {
1380 resp.Shallows = t.hashes
1381 err = remote.updateShallow(o, resp)
1382 c.Assert(err, IsNil)
1383
1384 shallow, err := remote.s.Shallow()
1385 c.Assert(err, IsNil)
1386 c.Assert(len(shallow), Equals, len(t.result))
1387 c.Assert(shallow, DeepEquals, t.result)
1388 }
1389}
1390
1391func (s *RemoteSuite) TestUseRefDeltas(c *C) {
1392 url := c.MkDir()
1393
1394 _, err := PlainInit(url, true)
1395 c.Assert(err, IsNil)
1396
1397 fs := fixtures.ByURL("https://github.com/git-fixtures/tags.git").One().DotGit()
1398 sto := filesystem.NewStorage(fs, cache.NewObjectLRUDefault())
1399
1400 r := NewRemote(sto, &config.RemoteConfig{
1401 Name: DefaultRemoteName,
1402 URLs: []string{url},
1403 })
1404
1405 ar := packp.NewAdvRefs()
1406
1407 ar.Capabilities.Add(capability.OFSDelta)
1408 c.Assert(r.useRefDeltas(ar), Equals, false)
1409
1410 ar.Capabilities.Delete(capability.OFSDelta)
1411 c.Assert(r.useRefDeltas(ar), Equals, true)
1412}
1413
1414func (s *RemoteSuite) TestPushRequireRemoteRefs(c *C) {
1415 f := fixtures.Basic().One()
1416 sto := filesystem.NewStorage(f.DotGit(), cache.NewObjectLRUDefault())
1417
1418 dstFs := f.DotGit()
1419 dstSto := filesystem.NewStorage(dstFs, cache.NewObjectLRUDefault())
1420
1421 url := dstFs.Root()
1422 r := NewRemote(sto, &config.RemoteConfig{
1423 Name: DefaultRemoteName,
1424 URLs: []string{url},
1425 })
1426
1427 oldRef, err := dstSto.Reference(plumbing.ReferenceName("refs/heads/branch"))
1428 c.Assert(err, IsNil)
1429 c.Assert(oldRef, NotNil)
1430
1431 otherRef, err := dstSto.Reference(plumbing.ReferenceName("refs/heads/master"))
1432 c.Assert(err, IsNil)
1433 c.Assert(otherRef, NotNil)
1434
1435 err = r.Push(&PushOptions{
1436 RefSpecs: []config.RefSpec{"refs/heads/master:refs/heads/branch"},
1437 RequireRemoteRefs: []config.RefSpec{config.RefSpec(otherRef.Hash().String() + ":refs/heads/branch")},
1438 })
1439 c.Assert(err, ErrorMatches, "remote ref refs/heads/branch required to be .* but is .*")
1440
1441 newRef, err := dstSto.Reference(plumbing.ReferenceName("refs/heads/branch"))
1442 c.Assert(err, IsNil)
1443 c.Assert(newRef, DeepEquals, oldRef)
1444
1445 err = r.Push(&PushOptions{
1446 RefSpecs: []config.RefSpec{"refs/heads/master:refs/heads/branch"},
1447 RequireRemoteRefs: []config.RefSpec{config.RefSpec(oldRef.Hash().String() + ":refs/heads/branch")},
1448 })
1449 c.Assert(err, ErrorMatches, "non-fast-forward update: .*")
1450
1451 newRef, err = dstSto.Reference(plumbing.ReferenceName("refs/heads/branch"))
1452 c.Assert(err, IsNil)
1453 c.Assert(newRef, DeepEquals, oldRef)
1454
1455 err = r.Push(&PushOptions{
1456 RefSpecs: []config.RefSpec{"refs/heads/master:refs/heads/branch"},
1457 RequireRemoteRefs: []config.RefSpec{config.RefSpec(oldRef.Hash().String() + ":refs/heads/branch")},
1458 Force: true,
1459 })
1460 c.Assert(err, IsNil)
1461
1462 newRef, err = dstSto.Reference(plumbing.ReferenceName("refs/heads/branch"))
1463 c.Assert(err, IsNil)
1464 c.Assert(newRef, Not(DeepEquals), oldRef)
1465}
1466
1467func (s *RemoteSuite) TestFetchPrune(c *C) {
1468 fs := fixtures.Basic().One().DotGit()
1469
1470 url := c.MkDir()
1471
1472 _, err := PlainClone(url, true, &CloneOptions{
1473 URL: fs.Root(),
1474 })
1475 c.Assert(err, IsNil)
1476
1477 dir := c.MkDir()
1478
1479 r, err := PlainClone(dir, true, &CloneOptions{
1480 URL: url,
1481 })
1482 c.Assert(err, IsNil)
1483
1484 remote, err := r.Remote(DefaultRemoteName)
1485 c.Assert(err, IsNil)
1486
1487 ref, err := r.Reference(plumbing.ReferenceName("refs/heads/master"), true)
1488 c.Assert(err, IsNil)
1489
1490 err = remote.Push(&PushOptions{RefSpecs: []config.RefSpec{
1491 "refs/heads/master:refs/heads/branch",
1492 }})
1493 c.Assert(err, IsNil)
1494
1495 dirSave := c.MkDir()
1496
1497 rSave, err := PlainClone(dirSave, true, &CloneOptions{
1498 URL: url,
1499 })
1500 c.Assert(err, IsNil)
1501
1502 AssertReferences(c, rSave, map[string]string{
1503 "refs/remotes/origin/branch": ref.Hash().String(),
1504 })
1505
1506 err = remote.Push(&PushOptions{RefSpecs: []config.RefSpec{
1507 ":refs/heads/branch",
1508 }})
1509 c.Assert(err, IsNil)
1510
1511 AssertReferences(c, rSave, map[string]string{
1512 "refs/remotes/origin/branch": ref.Hash().String(),
1513 })
1514
1515 err = rSave.Fetch(&FetchOptions{Prune: true})
1516 c.Assert(err, IsNil)
1517
1518 _, err = rSave.Reference("refs/remotes/origin/branch", true)
1519 c.Assert(err, ErrorMatches, "reference not found")
1520}
1521
1522func (s *RemoteSuite) TestFetchPruneTags(c *C) {
1523 fs := fixtures.Basic().One().DotGit()
1524
1525 url := c.MkDir()
1526
1527 _, err := PlainClone(url, true, &CloneOptions{
1528 URL: fs.Root(),
1529 })
1530 c.Assert(err, IsNil)
1531
1532 dir := c.MkDir()
1533
1534 r, err := PlainClone(dir, true, &CloneOptions{
1535 URL: url,
1536 })
1537 c.Assert(err, IsNil)
1538
1539 remote, err := r.Remote(DefaultRemoteName)
1540 c.Assert(err, IsNil)
1541
1542 ref, err := r.Reference(plumbing.ReferenceName("refs/heads/master"), true)
1543 c.Assert(err, IsNil)
1544
1545 err = remote.Push(&PushOptions{RefSpecs: []config.RefSpec{
1546 "refs/heads/master:refs/tags/v1",
1547 }})
1548 c.Assert(err, IsNil)
1549
1550 dirSave := c.MkDir()
1551
1552 rSave, err := PlainClone(dirSave, true, &CloneOptions{
1553 URL: url,
1554 })
1555 c.Assert(err, IsNil)
1556
1557 AssertReferences(c, rSave, map[string]string{
1558 "refs/tags/v1": ref.Hash().String(),
1559 })
1560
1561 err = remote.Push(&PushOptions{RefSpecs: []config.RefSpec{
1562 ":refs/tags/v1",
1563 }})
1564 c.Assert(err, IsNil)
1565
1566 AssertReferences(c, rSave, map[string]string{
1567 "refs/tags/v1": ref.Hash().String(),
1568 })
1569
1570 err = rSave.Fetch(&FetchOptions{Prune: true, RefSpecs: []config.RefSpec{"refs/tags/*:refs/tags/*"}})
1571 c.Assert(err, IsNil)
1572
1573 _, err = rSave.Reference("refs/tags/v1", true)
1574 c.Assert(err, ErrorMatches, "reference not found")
1575}
1576
1577func (s *RemoteSuite) TestCanPushShasToReference(c *C) {
1578 d := c.MkDir()
1579 d, err := os.MkdirTemp(d, "TestCanPushShasToReference")
1580 c.Assert(err, IsNil)
1581 if err != nil {
1582 return
1583 }
1584
1585 // remote currently forces a plain path for path based remotes inside the PushContext function.
1586 // This makes it impossible, in the current state to use memfs.
1587 // For the sake of readability, use the same osFS everywhere and use plain git repositories on temporary files
1588 remote, err := PlainInit(filepath.Join(d, "remote"), true)
1589 c.Assert(err, IsNil)
1590 c.Assert(remote, NotNil)
1591
1592 repo, err := PlainInit(filepath.Join(d, "repo"), false)
1593 c.Assert(err, IsNil)
1594 c.Assert(repo, NotNil)
1595
1596 sha := CommitNewFile(c, repo, "README.md")
1597
1598 gitremote, err := repo.CreateRemote(&config.RemoteConfig{
1599 Name: "local",
1600 URLs: []string{filepath.Join(d, "remote")},
1601 })
1602 c.Assert(err, IsNil)
1603 if err != nil {
1604 return
1605 }
1606
1607 err = gitremote.Push(&PushOptions{
1608 RemoteName: "local",
1609 RefSpecs: []config.RefSpec{
1610 // TODO: check with short hashes that this is still respected
1611 config.RefSpec(sha.String() + ":refs/heads/branch"),
1612 },
1613 })
1614 c.Assert(err, IsNil)
1615 if err != nil {
1616 return
1617 }
1618
1619 ref, err := remote.Reference(plumbing.ReferenceName("refs/heads/branch"), false)
1620 c.Assert(err, IsNil)
1621 if err != nil {
1622 return
1623 }
1624 c.Assert(ref.Hash().String(), Equals, sha.String())
1625}
1626
1627func (s *RemoteSuite) TestFetchAfterShallowClone(c *C) {
1628 tempDir := c.MkDir()
1629 remoteUrl := filepath.Join(tempDir, "remote")
1630 repoDir := filepath.Join(tempDir, "repo")
1631
1632 // Create a new repo and add more than 1 commit (so we can have a shallow commit)
1633 remote, err := PlainInit(remoteUrl, false)
1634 c.Assert(err, IsNil)
1635 c.Assert(remote, NotNil)
1636
1637 _ = CommitNewFile(c, remote, "File1")
1638 _ = CommitNewFile(c, remote, "File2")
1639
1640 // Clone the repo with a depth of 1
1641 repo, err := PlainClone(repoDir, false, &CloneOptions{
1642 URL: remoteUrl,
1643 Depth: 1,
1644 Tags: NoTags,
1645 SingleBranch: true,
1646 ReferenceName: "master",
1647 })
1648 c.Assert(err, IsNil)
1649
1650 // Add new commits to the origin (more than 1 so that our next test hits a missing commit)
1651 _ = CommitNewFile(c, remote, "File3")
1652 sha4 := CommitNewFile(c, remote, "File4")
1653
1654 // Try fetch with depth of 1 again (note, we need to ensure no remote branch remains pointing at the old commit)
1655 r, err := repo.Remote(DefaultRemoteName)
1656 c.Assert(err, IsNil)
1657 s.testFetch(c, r, &FetchOptions{
1658 Depth: 2,
1659 Tags: NoTags,
1660
1661 RefSpecs: []config.RefSpec{
1662 "+refs/heads/master:refs/heads/master",
1663 "+refs/heads/master:refs/remotes/origin/master",
1664 },
1665 }, []*plumbing.Reference{
1666 plumbing.NewReferenceFromStrings("refs/heads/master", sha4.String()),
1667 plumbing.NewReferenceFromStrings("refs/remotes/origin/master", sha4.String()),
1668 plumbing.NewSymbolicReference("HEAD", "refs/heads/master"),
1669 })
1670
1671 // Add another commit to the origin
1672 sha5 := CommitNewFile(c, remote, "File5")
1673
1674 // Try fetch with depth of 2 this time (to reach a commit that we don't have locally)
1675 r, err = repo.Remote(DefaultRemoteName)
1676 c.Assert(err, IsNil)
1677 s.testFetch(c, r, &FetchOptions{
1678 Depth: 1,
1679 Tags: NoTags,
1680
1681 RefSpecs: []config.RefSpec{
1682 "+refs/heads/master:refs/heads/master",
1683 "+refs/heads/master:refs/remotes/origin/master",
1684 },
1685 }, []*plumbing.Reference{
1686 plumbing.NewReferenceFromStrings("refs/heads/master", sha5.String()),
1687 plumbing.NewReferenceFromStrings("refs/remotes/origin/master", sha5.String()),
1688 plumbing.NewSymbolicReference("HEAD", "refs/heads/master"),
1689 })
1690}
1691
1692func TestFetchFastForwardForCustomRef(t *testing.T) {
1693 customRef := "refs/custom/branch"
1694 // 1. Set up a remote with a URL
1695 remoteURL := t.TempDir()
1696 remoteRepo, err := PlainInit(remoteURL, true)
1697 if err != nil {
1698 t.Fatal(err)
1699 }
1700
1701 // 2. Add a commit with an empty tree to master and custom ref, also set HEAD
1702 emptyTreeID := writeEmptyTree(t, remoteRepo)
1703 writeCommitToRef(t, remoteRepo, "refs/heads/master", emptyTreeID, time.Now())
1704 writeCommitToRef(t, remoteRepo, customRef, emptyTreeID, time.Now())
1705 if err := remoteRepo.Storer.SetReference(plumbing.NewSymbolicReference(plumbing.HEAD, "refs/heads/master")); err != nil {
1706 t.Fatal(err)
1707 }
1708
1709 // 3. Clone repo, then fetch the custom ref
1710 // Note that using custom ref in ReferenceName has an IsBranch issue
1711 localRepo, err := Clone(memory.NewStorage(), memfs.New(), &CloneOptions{
1712 URL: remoteURL,
1713 })
1714 if err != nil {
1715 t.Fatal(err)
1716 }
1717 if err := localRepo.Fetch(&FetchOptions{
1718 RefSpecs: []config.RefSpec{
1719 config.RefSpec(fmt.Sprintf("%s:%s", customRef, customRef)),
1720 },
1721 }); err != nil {
1722 t.Fatal(err)
1723 }
1724
1725 // 4. Make divergent changes
1726 remoteCommitID := writeCommitToRef(t, remoteRepo, customRef, emptyTreeID, time.Now())
1727 // Consecutive calls to writeCommitToRef with time.Now() might have the same
1728 // time value, explicitly set distinct ones to ensure the commit hashes
1729 // differ
1730 writeCommitToRef(t, localRepo, customRef, emptyTreeID, time.Now().Add(time.Second))
1731
1732 // 5. Try to fetch with fast-forward only mode
1733 remote, err := localRepo.Remote(DefaultRemoteName)
1734 if err != nil {
1735 t.Fatal(err)
1736 }
1737
1738 err = remote.Fetch(&FetchOptions{RefSpecs: []config.RefSpec{
1739 config.RefSpec(fmt.Sprintf("%s:%s", customRef, customRef)),
1740 }})
1741 if !errors.Is(err, ErrForceNeeded) {
1742 t.Errorf("expected %v, got %v", ErrForceNeeded, err)
1743 }
1744
1745 // 6. Fetch with force
1746 err = remote.Fetch(&FetchOptions{RefSpecs: []config.RefSpec{
1747 config.RefSpec(fmt.Sprintf("+%s:%s", customRef, customRef)),
1748 }})
1749 if err != nil {
1750 t.Errorf("unexpected error %v", err)
1751 }
1752
1753 // 7. Assert commit ID matches
1754 ref, err := localRepo.Reference(plumbing.ReferenceName(customRef), true)
1755 if err != nil {
1756 t.Fatal(err)
1757 }
1758 if remoteCommitID != ref.Hash() {
1759 t.Errorf("expected %s, got %s", remoteCommitID.String(), ref.Hash().String())
1760 }
1761}
1762
1763func writeEmptyTree(t *testing.T, repo *Repository) plumbing.Hash {
1764 t.Helper()
1765
1766 obj := repo.Storer.NewEncodedObject()
1767 obj.SetType(plumbing.TreeObject)
1768
1769 tree := object.Tree{Entries: nil}
1770 if err := tree.Encode(obj); err != nil {
1771 t.Fatal(err)
1772 }
1773
1774 treeID, err := repo.Storer.SetEncodedObject(obj)
1775 if err != nil {
1776 t.Fatal(err)
1777 }
1778
1779 return treeID
1780}
1781
1782func writeCommitToRef(t *testing.T, repo *Repository, refName string, treeID plumbing.Hash, when time.Time) plumbing.Hash {
1783 t.Helper()
1784
1785 ref, err := repo.Reference(plumbing.ReferenceName(refName), true)
1786 if err != nil {
1787 if errors.Is(err, plumbing.ErrReferenceNotFound) {
1788 if err := repo.Storer.SetReference(plumbing.NewHashReference(plumbing.ReferenceName(refName), plumbing.ZeroHash)); err != nil {
1789 t.Fatal(err)
1790 }
1791
1792 ref, err = repo.Reference(plumbing.ReferenceName(refName), true)
1793 if err != nil {
1794 t.Fatal(err)
1795 }
1796 } else {
1797 t.Fatal(err)
1798 }
1799 }
1800
1801 commit := &object.Commit{
1802 TreeHash: treeID,
1803 Author: object.Signature{
1804 When: when,
1805 },
1806 }
1807 if !ref.Hash().IsZero() {
1808 commit.ParentHashes = []plumbing.Hash{ref.Hash()}
1809 }
1810
1811 obj := repo.Storer.NewEncodedObject()
1812 if err := commit.Encode(obj); err != nil {
1813 t.Fatal(err)
1814 }
1815
1816 commitID, err := repo.Storer.SetEncodedObject(obj)
1817 if err != nil {
1818 t.Fatal(err)
1819 }
1820
1821 newRef := plumbing.NewHashReference(plumbing.ReferenceName(refName), commitID)
1822 if err := repo.Storer.CheckAndSetReference(newRef, ref); err != nil {
1823 t.Fatal(err)
1824 }
1825
1826 return commitID
1827}