Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

git: Add support to ls-remote with peeled references. Fixes #749 #750

Merged
merged 1 commit into from
May 3, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
14 changes: 12 additions & 2 deletions _examples/ls-remote/main.go
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
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