Skip to content

Commit 243e7c8

Browse files
authored
Merge pull request go-git#385 from john-cai/add-follow-tags
git: add --follow-tags option for pushes
2 parents 4ec1753 + 5340c58 commit 243e7c8

File tree

4 files changed

+149
-0
lines changed

4 files changed

+149
-0
lines changed

Diff for: common_test.go

+8
Original file line numberDiff line numberDiff line change
@@ -198,3 +198,11 @@ func AssertReferences(c *C, r *Repository, expected map[string]string) {
198198
c.Assert(obtained, DeepEquals, expected)
199199
}
200200
}
201+
202+
func AssertReferencesMissing(c *C, r *Repository, expected []string) {
203+
for _, name := range expected {
204+
_, err := r.Reference(plumbing.ReferenceName(name), false)
205+
c.Assert(err, NotNil)
206+
c.Assert(err, Equals, plumbing.ErrReferenceNotFound)
207+
}
208+
}

Diff for: options.go

+3
Original file line numberDiff line numberDiff line change
@@ -213,6 +213,9 @@ type PushOptions struct {
213213
// RequireRemoteRefs only allows a remote ref to be updated if its current
214214
// value is the one specified here.
215215
RequireRemoteRefs []config.RefSpec
216+
// FollowTags will send any annotated tags with a commit target reachable from
217+
// the refs already being pushed
218+
FollowTags bool
216219
}
217220

218221
// Validate validates the fields and sets the default values.

Diff for: remote.go

+78
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import (
55
"errors"
66
"fmt"
77
"io"
8+
"strings"
89
"time"
910

1011
"github.com/go-git/go-billy/v5/osfs"
@@ -225,6 +226,77 @@ func (r *Remote) useRefDeltas(ar *packp.AdvRefs) bool {
225226
return !ar.Capabilities.Supports(capability.OFSDelta)
226227
}
227228

229+
func (r *Remote) addReachableTags(localRefs []*plumbing.Reference, remoteRefs storer.ReferenceStorer, req *packp.ReferenceUpdateRequest) error {
230+
tags := make(map[plumbing.Reference]struct{})
231+
// get a list of all tags locally
232+
for _, ref := range localRefs {
233+
if strings.HasPrefix(string(ref.Name()), "refs/tags") {
234+
tags[*ref] = struct{}{}
235+
}
236+
}
237+
238+
remoteRefIter, err := remoteRefs.IterReferences()
239+
if err != nil {
240+
return err
241+
}
242+
243+
// remove any that are already on the remote
244+
if err := remoteRefIter.ForEach(func(reference *plumbing.Reference) error {
245+
if _, ok := tags[*reference]; ok {
246+
delete(tags, *reference)
247+
}
248+
249+
return nil
250+
}); err != nil {
251+
return err
252+
}
253+
254+
for tag, _ := range tags {
255+
tagObject, err := object.GetObject(r.s, tag.Hash())
256+
var tagCommit *object.Commit
257+
if err != nil {
258+
return fmt.Errorf("get tag object: %w\n", err)
259+
}
260+
261+
if tagObject.Type() != plumbing.TagObject {
262+
continue
263+
}
264+
265+
annotatedTag, ok := tagObject.(*object.Tag)
266+
if !ok {
267+
return errors.New("could not get annotated tag object")
268+
}
269+
270+
tagCommit, err = object.GetCommit(r.s, annotatedTag.Target)
271+
if err != nil {
272+
return fmt.Errorf("get annotated tag commit: %w\n", err)
273+
}
274+
275+
// only include tags that are reachable from one of the refs
276+
// already being pushed
277+
for _, cmd := range req.Commands {
278+
if tag.Name() == cmd.Name {
279+
continue
280+
}
281+
282+
if strings.HasPrefix(cmd.Name.String(), "refs/tags") {
283+
continue
284+
}
285+
286+
c, err := object.GetCommit(r.s, cmd.New)
287+
if err != nil {
288+
return fmt.Errorf("get commit %v: %w", cmd.Name, err)
289+
}
290+
291+
if isAncestor, err := tagCommit.IsAncestor(c); err == nil && isAncestor {
292+
req.Commands = append(req.Commands, &packp.Command{Name: tag.Name(), New: tag.Hash()})
293+
}
294+
}
295+
}
296+
297+
return nil
298+
}
299+
228300
func (r *Remote) newReferenceUpdateRequest(
229301
o *PushOptions,
230302
localRefs []*plumbing.Reference,
@@ -246,6 +318,12 @@ func (r *Remote) newReferenceUpdateRequest(
246318
return nil, err
247319
}
248320

321+
if o.FollowTags {
322+
if err := r.addReachableTags(localRefs, remoteRefs, req); err != nil {
323+
return nil, err
324+
}
325+
}
326+
249327
return req, nil
250328
}
251329

Diff for: remote_test.go

+60
Original file line numberDiff line numberDiff line change
@@ -591,6 +591,66 @@ func (s *RemoteSuite) TestPushTags(c *C) {
591591
})
592592
}
593593

594+
func (s *RemoteSuite) TestPushFollowTags(c *C) {
595+
url, clean := s.TemporalDir()
596+
defer clean()
597+
598+
server, err := PlainInit(url, true)
599+
c.Assert(err, IsNil)
600+
601+
fs := fixtures.ByURL("https://github.com/git-fixtures/basic.git").One().DotGit()
602+
sto := filesystem.NewStorage(fs, cache.NewObjectLRUDefault())
603+
604+
r := NewRemote(sto, &config.RemoteConfig{
605+
Name: DefaultRemoteName,
606+
URLs: []string{url},
607+
})
608+
609+
localRepo := newRepository(sto, fs)
610+
tipTag, err := localRepo.CreateTag(
611+
"tip",
612+
plumbing.NewHash("e8d3ffab552895c19b9fcf7aa264d277cde33881"),
613+
&CreateTagOptions{
614+
Message: "an annotated tag",
615+
},
616+
)
617+
c.Assert(err, IsNil)
618+
619+
initialTag, err := localRepo.CreateTag(
620+
"initial-commit",
621+
plumbing.NewHash("b029517f6300c2da0f4b651b8642506cd6aaf45d"),
622+
&CreateTagOptions{
623+
Message: "a tag for the initial commit",
624+
},
625+
)
626+
c.Assert(err, IsNil)
627+
628+
_, err = localRepo.CreateTag(
629+
"master-tag",
630+
plumbing.NewHash("6ecf0ef2c2dffb796033e5a02219af86ec6584e5"),
631+
&CreateTagOptions{
632+
Message: "a tag with a commit not reachable from branch",
633+
},
634+
)
635+
c.Assert(err, IsNil)
636+
637+
err = r.Push(&PushOptions{
638+
RefSpecs: []config.RefSpec{"+refs/heads/branch:refs/heads/branch"},
639+
FollowTags: true,
640+
})
641+
c.Assert(err, IsNil)
642+
643+
AssertReferences(c, server, map[string]string{
644+
"refs/heads/branch": "e8d3ffab552895c19b9fcf7aa264d277cde33881",
645+
"refs/tags/tip": tipTag.Hash().String(),
646+
"refs/tags/initial-commit": initialTag.Hash().String(),
647+
})
648+
649+
AssertReferencesMissing(c, server, []string{
650+
"refs/tags/master-tag",
651+
})
652+
}
653+
594654
func (s *RemoteSuite) TestPushNoErrAlreadyUpToDate(c *C) {
595655
fs := fixtures.Basic().One().DotGit()
596656
sto := filesystem.NewStorage(fs, cache.NewObjectLRUDefault())

0 commit comments

Comments
 (0)