Skip to content

Commit

Permalink
Merge pull request #750 from pjbgf/peel
Browse files Browse the repository at this point in the history
git: Add support to ls-remote with peeled references. Fixes #749
  • Loading branch information
pjbgf committed May 3, 2023
2 parents 0542a30 + 19b39e1 commit 02856b8
Show file tree
Hide file tree
Showing 4 changed files with 92 additions and 8 deletions.
14 changes: 12 additions & 2 deletions _examples/ls-remote/main.go
Expand Up @@ -2,25 +2,35 @@ package main

import (
"log"
"os"

"github.com/go-git/go-git/v5"
"github.com/go-git/go-git/v5/config"
"github.com/go-git/go-git/v5/storage/memory"

. "github.com/go-git/go-git/v5/_examples"
)

// Retrieve remote tags without cloning repository
func main() {
CheckArgs("<url>")
url := os.Args[1]

Info("git ls-remote --tags %s", url)

// Create the remote with repository URL
rem := git.NewRemote(memory.NewStorage(), &config.RemoteConfig{
Name: "origin",
URLs: []string{"https://github.com/Zenika/MARCEL"},
URLs: []string{url},
})

log.Print("Fetching tags...")

// We can then use every Remote functions to retrieve wanted information
refs, err := rem.List(&git.ListOptions{})
refs, err := rem.List(&git.ListOptions{
// Returns all references, including peeled references.
PeelingOption: git.AppendPeeled,
})
if err != nil {
log.Fatal(err)
}
Expand Down
20 changes: 20 additions & 0 deletions options.go
Expand Up @@ -624,8 +624,28 @@ type ListOptions struct {
InsecureSkipTLS bool
// CABundle specify additional ca bundle with system cert pool
CABundle []byte

// PeelingOption defines how peeled objects are handled during a
// remote list.
PeelingOption PeelingOption
}

// PeelingOption represents the different ways to handle peeled references.
//
// Peeled references represent the underlying object of an annotated
// (or signed) tag. Refer to upstream documentation for more info:
// https://github.com/git/git/blob/master/Documentation/technical/reftable.txt
type PeelingOption uint8

const (
// IgnorePeeled ignores all peeled reference names. This is the default behavior.
IgnorePeeled PeelingOption = 0
// OnlyPeeled returns only peeled reference names.
OnlyPeeled PeelingOption = 1
// AppendPeeled appends peeled reference names to the reference list.
AppendPeeled PeelingOption = 2
)

// CleanOptions describes how a clean should be performed.
type CleanOptions struct {
Dir bool
Expand Down
29 changes: 23 additions & 6 deletions remote.go
Expand Up @@ -33,6 +33,7 @@ var (
ErrDeleteRefNotSupported = errors.New("server does not support delete-refs")
ErrForceNeeded = errors.New("some refs were not updated")
ErrExactSHA1NotSupported = errors.New("server does not support exact SHA1 refspec")
ErrEmptyUrls = errors.New("URLs cannot be empty")
)

type NoMatchingRefSpecError struct {
Expand All @@ -54,6 +55,9 @@ const (
// repo containing this remote, when not using the multi-ack
// protocol. Setting this to 0 means there is no limit.
maxHavesToVisitPerRef = 100

// peeledSuffix is the suffix used to build peeled reference names.
peeledSuffix = "^{}"
)

// Remote represents a connection to a remote repository.
Expand Down Expand Up @@ -1259,6 +1263,10 @@ func (r *Remote) List(o *ListOptions) (rfs []*plumbing.Reference, err error) {
}

func (r *Remote) list(ctx context.Context, o *ListOptions) (rfs []*plumbing.Reference, err error) {
if r.c == nil || len(r.c.URLs) == 0 {
return nil, ErrEmptyUrls
}

s, err := newUploadPackSession(r.c.URLs[0], o.Auth, o.InsecureSkipTLS, o.CABundle)
if err != nil {
return nil, err
Expand All @@ -1282,13 +1290,22 @@ func (r *Remote) list(ctx context.Context, o *ListOptions) (rfs []*plumbing.Refe
}

var resultRefs []*plumbing.Reference
err = refs.ForEach(func(ref *plumbing.Reference) error {
resultRefs = append(resultRefs, ref)
return nil
})
if err != nil {
return nil, err
if o.PeelingOption == AppendPeeled || o.PeelingOption == IgnorePeeled {
err = refs.ForEach(func(ref *plumbing.Reference) error {
resultRefs = append(resultRefs, ref)
return nil
})
if err != nil {
return nil, err
}
}

if o.PeelingOption == AppendPeeled || o.PeelingOption == OnlyPeeled {
for k, v := range ar.Peeled {
resultRefs = append(resultRefs, plumbing.NewReferenceFromStrings(k+"^{}", v.String()))
}
}

return resultRefs, nil
}

Expand Down
37 changes: 37 additions & 0 deletions remote_test.go
Expand Up @@ -9,6 +9,7 @@ import (
"os"
"path/filepath"
"runtime"
"strings"
"time"

"github.com/go-git/go-git/v5/config"
Expand Down Expand Up @@ -865,6 +866,7 @@ func (s *RemoteSuite) TestPushForceWithLease_success(c *C) {
c.Assert(sto.SetReference(newCommit), IsNil)

ref, err := sto.Reference("refs/heads/branch")
c.Assert(err, IsNil)
c.Log(ref.String())

url := dstFs.Root()
Expand Down Expand Up @@ -1210,6 +1212,41 @@ func (s *RemoteSuite) TestList(c *C) {
}
}

func (s *RemoteSuite) TestListPeeling(c *C) {
remote := NewRemote(memory.NewStorage(), &config.RemoteConfig{
Name: DefaultRemoteName,
URLs: []string{"https://github.com/git-fixtures/tags.git"},
})

for _, tc := range []struct {
peelingOption PeelingOption
expectPeeled bool
expectNonPeeled bool
}{
{peelingOption: AppendPeeled, expectPeeled: true, expectNonPeeled: true},
{peelingOption: IgnorePeeled, expectPeeled: false, expectNonPeeled: true},
{peelingOption: OnlyPeeled, expectPeeled: true, expectNonPeeled: false},
} {
refs, err := remote.List(&ListOptions{
PeelingOption: tc.peelingOption,
})
c.Assert(err, IsNil)
c.Assert(len(refs) > 0, Equals, true)

foundPeeled, foundNonPeeled := false, false
for _, ref := range refs {
if strings.HasSuffix(ref.Name().String(), peeledSuffix) {
foundPeeled = true
} else {
foundNonPeeled = true
}
}

c.Assert(foundPeeled, Equals, tc.expectPeeled)
c.Assert(foundNonPeeled, Equals, tc.expectNonPeeled)
}
}

func (s *RemoteSuite) TestListTimeout(c *C) {
remote := NewRemote(memory.NewStorage(), &config.RemoteConfig{
Name: DefaultRemoteName,
Expand Down

0 comments on commit 02856b8

Please sign in to comment.