Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
39 changes: 31 additions & 8 deletions internal/strategy/gomod/private_fetcher.go
Original file line number Diff line number Diff line change
Expand Up @@ -154,9 +154,10 @@ func (p *privateFetcher) modulePathToGitURL(modulePath string) string {
return "https://" + modulePath
}

func (p *privateFetcher) verifyCommitExists(ctx context.Context, repo *gitclone.Repository, ref string) error {
func (p *privateFetcher) verifyCommitExists(ctx context.Context, repo *gitclone.Repository, version string) error {
ref := VersionToGitRef(version)
if !repo.HasCommit(ctx, ref) {
return errors.Errorf("commit %s not found in repository %s", ref, repo.UpstreamURL())
return errors.Errorf("commit %s not found in repository %s", version, repo.UpstreamURL())
}
return nil
}
Expand Down Expand Up @@ -254,7 +255,7 @@ func (p *privateFetcher) getDefaultBranchVersion(ctx context.Context, repo *gitc
}

func (p *privateFetcher) generateInfo(ctx context.Context, repo *gitclone.Repository, version string) (io.ReadSeekCloser, error) {
commitTime, err := p.getCommitTime(ctx, repo, version)
commitTime, err := p.getCommitTime(ctx, repo, VersionToGitRef(version))
if err != nil {
return nil, err
}
Expand All @@ -275,12 +276,12 @@ func (p *privateFetcher) generateInfo(ctx context.Context, repo *gitclone.Reposi
func (p *privateFetcher) generateMod(ctx context.Context, repo *gitclone.Repository, modulePath, version string) io.ReadSeekCloser {
output, err := gitclone.WithReadLockReturn(repo, func() ([]byte, error) {
// #nosec G204 - version and repo.Path() are controlled by this package, not user input
cmd := exec.CommandContext(ctx, "git", "-C", repo.Path(), "show", fmt.Sprintf("%s:go.mod", version))
cmd := exec.CommandContext(ctx, "git", "-C", repo.Path(), "show", fmt.Sprintf("%s:go.mod", VersionToGitRef(version)))
return cmd.CombinedOutput()
})

if err != nil {
minimal := fmt.Sprintf("module %s\n\ngo 1.21\n", modulePath)
minimal := fmt.Sprintf("module %s\n", modulePath)
return newReadSeekCloser(bytes.NewReader([]byte(minimal)))
}

Expand All @@ -295,13 +296,21 @@ func (p *privateFetcher) generateZip(ctx context.Context, repo *gitclone.Reposit
defer os.RemoveAll(tmpDir) //nolint:errcheck

cloneDir := filepath.Join(tmpDir, "repo")
gitRef := VersionToGitRef(version)

// Local clone from the mirror at the requested version — git hardlinks objects by default.
// #nosec G204 - repo.Path(), version, and cloneDir are controlled by us
cmd := exec.CommandContext(ctx, "git", "clone", "--branch", version, repo.Path(), cloneDir)
// Local clone from the mirror at the requested ref — git hardlinks objects by default.
// We use --no-checkout then checkout the specific ref so that both tagged versions and
// raw commit hashes (from pseudo-versions) work; --branch only accepts branch/tag names.
// #nosec G204 - repo.Path() and cloneDir are controlled by us
cmd := exec.CommandContext(ctx, "git", "clone", "--no-checkout", repo.Path(), cloneDir)
if output, err := cmd.CombinedOutput(); err != nil {
return nil, errors.Wrapf(err, "git clone: %s", string(output))
}
// #nosec G204 - cloneDir and gitRef are controlled by us
cmd = exec.CommandContext(ctx, "git", "-C", cloneDir, "checkout", gitRef)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
cmd = exec.CommandContext(ctx, "git", "-C", cloneDir, "checkout", gitRef)
cmd = exec.CommandContext(ctx, "git", "-C", cloneDir, "checkout", "--", gitRef)

if output, err := cmd.CombinedOutput(); err != nil {
return nil, errors.Wrapf(err, "git checkout %s: %s", gitRef, string(output))
}

var buf bytes.Buffer
m := module.Version{Path: modulePath, Version: version}
Expand All @@ -323,3 +332,17 @@ func newReadSeekCloser(r *bytes.Reader) io.ReadSeekCloser {
func (r *readSeekCloser) Close() error {
return nil
}

// VersionToGitRef converts a Go module version string to a git ref. For pseudo-versions
// (e.g. v0.0.0-20160603174536-ad42235f7e24), it extracts the commit hash suffix. For
// all other versions (tagged releases, pre-releases, +incompatible), it returns the
// version unchanged.
func VersionToGitRef(version string) string {
if module.IsPseudoVersion(version) {
rev, err := module.PseudoVersionRev(version)
if err == nil {
return rev
}
}
return version
}
55 changes: 55 additions & 0 deletions internal/strategy/gomod/private_fetcher_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
package gomod_test

import (
"testing"

"github.com/alecthomas/assert/v2"

"github.com/block/cachew/internal/strategy/gomod"
)

func TestVersionToGitRef(t *testing.T) {
tests := []struct {
name string
version string
want string
}{
{
name: "TaggedVersion",
version: "v1.2.3",
want: "v1.2.3",
},
{
name: "PseudoVersion",
version: "v0.0.0-20160603174536-ad42235f7e24",
want: "ad42235f7e24",
},
{
name: "PseudoVersionWithBase",
version: "v1.2.4-0.20230101120000-abcdef123456",
want: "abcdef123456",
},
{
name: "PreReleaseVersion",
version: "v1.0.0-rc1",
want: "v1.0.0-rc1",
},
{
name: "IncompatibleVersion",
version: "v2.0.0+incompatible",
want: "v2.0.0+incompatible",
},
{
name: "BareRef",
version: "HEAD",
want: "HEAD",
},
}

for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
ref := gomod.VersionToGitRef(tt.version)
assert.Equal(t, tt.want, ref)
})
}
}