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