10 Commits

Author SHA1 Message Date
Marcin Bilski
6768e02d91 Fix tagging failing with 400 while no remote tags yet. 2022-01-12 12:44:09 +01:00
Marcin Bilski
656e7b2fc1 Configure goreleaser. 2022-01-05 11:42:45 +01:00
Marcin Bilski
9624b2b9ad Give up, change import paths to the fork. 2022-01-05 11:31:44 +01:00
Marcin Bilski
48d1cf9ff8 Add Dockerfile. 2022-01-05 11:30:27 +01:00
Marcin Bilski
52d4a68d86 Prevent tagging existing remote versions. 2021-12-22 21:53:45 +01:00
Marcin Bilski
e08d238243 Add support for ephemeral tags. 2021-12-22 14:04:57 +01:00
Marcin Bilski
92e846002e Do not list default 0.0.0 version for Go modules. 2021-12-22 14:01:32 +01:00
Marcin Bilski
708a07fec6 Fix broken support for monorepos. 2021-12-22 14:00:34 +01:00
Serge Zaitsev
db43a4e741 encode module name with gomod vcs provider to handle uppercase module paths 2019-11-08 10:08:36 +01:00
Serge Zaitsev
7a37ec05c4 Delete modules in case of breaking changes or invalid checksums, compatibility with go mod download (#10)
* add module invalidation via DELETE request
* add go mod download vcs provider
2019-10-07 08:10:02 +02:00
14 changed files with 324 additions and 15 deletions

2
.gitignore vendored
View File

@@ -15,3 +15,5 @@
# Misc
_gopath
_gocache
dist/

31
.goreleaser.yaml Normal file
View File

@@ -0,0 +1,31 @@
# This is an example .goreleaser.yml file with some sensible defaults.
# Make sure to check the documentation at https://goreleaser.com
before:
hooks:
# You may remove this if you don't use go modules.
- go mod tidy
# you may remove this if you don't need go generate
- go generate ./...
builds:
- env:
- CGO_ENABLED=0
goos:
- linux
- darwin
main: ./cmd/gomodproxy/main.go
archives:
- replacements:
darwin: Darwin
linux: Linux
386: i386
amd64: x86_64
checksum:
name_template: 'checksums.txt'
snapshot:
name_template: "{{ incpatch .Version }}-next"
changelog:
sort: asc
filters:
exclude:
- '^docs:'
- '^test:'

11
Dockerfile Normal file
View File

@@ -0,0 +1,11 @@
FROM golang:1.16.7 as build
ENV HOME /opt/app
COPY . $HOME
WORKDIR $HOME
RUN ls $HOME
RUN go build cmd/gomodproxy/main.go && \
go clean
FROM debian:buster
COPY --from=build /go/bin/ /go/bin/
ENTRYPOINT ["/go/bin/gomodproxy"]

View File

@@ -8,6 +8,10 @@ gomodproxy is a caching proxy for [Go modules].
Go 1.11 has introduced optional proxy support via GOPROXY environment variable. It is essential for use cases where you want to have better control over your dependencies and handle scenarios when GitHub is down or some open-source dependency has been removed.
## Releasing
See https://goreleaser.com/quick-start/
## Getting started
gomodproxy requires Go 1.11 or newer. There are no plans to support `vgo` or Go 1.11 beta versions.

View File

@@ -16,7 +16,7 @@ import (
"time"
"unicode"
"github.com/sixt/gomodproxy/pkg/api"
"github.com/bilus/gomodproxy/pkg/api"
"expvar"
_ "net/http/pprof"
@@ -137,7 +137,7 @@ func main() {
if len(kv) != 2 {
log.Fatal("bad git path:", path)
}
options = append(options, api.Git(kv[0], kv[1]))
options = append(options, api.GitWithEphemeralTags(kv[0], kv[1]))
}
for _, path := range vcsPaths {

4
go.mod
View File

@@ -1,5 +1,5 @@
module github.com/sixt/gomodproxy
module github.com/bilus/gomodproxy
go 1.13
go 1.16
require gopkg.in/src-d/go-git.v4 v4.13.1

14
go.sum
View File

@@ -1,27 +1,37 @@
github.com/alcortesm/tgz v0.0.0-20161220082320-9c5fe88206d7 h1:uSoVVbwJiQipAclBbw+8quDsfcvFjOpI5iCf4p/cqCs=
github.com/alcortesm/tgz v0.0.0-20161220082320-9c5fe88206d7/go.mod h1:6zEj6s6u/ghQa61ZWa/C2Aw3RkjiTBOix7dkqa1VLIs=
github.com/anmitsu/go-shlex v0.0.0-20161002113705-648efa622239 h1:kFOfPq6dUM1hTo4JG6LR5AXSUEsOjtdm0kw0FtQtMJA=
github.com/anmitsu/go-shlex v0.0.0-20161002113705-648efa622239/go.mod h1:2FmKhYUyUczH0OGQWaF5ceTx0UBShxjsH6f8oGKYe2c=
github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5 h1:0CwZNZbxp69SHPdPJAN/hZIm0C4OItdklCFmMRWYpio=
github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5/go.mod h1:wHh0iHkYZB8zMSxRWpUBQtwG5a7fFgvEO+odwuTv2gs=
github.com/creack/pty v1.1.7/go.mod h1:lj5s0c3V2DBrqTV7llrYr5NG6My20zk30Fl46Y7DoTY=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/emirpasic/gods v1.12.0 h1:QAUIPSaCu4G+POclxeqb3F+WPpdKqFGlw36+yOzGlrg=
github.com/emirpasic/gods v1.12.0/go.mod h1:YfzfFFoVP/catgzJb4IKIqXjX78Ha8FMSDh3ymbK86o=
github.com/flynn/go-shlex v0.0.0-20150515145356-3f9db97f8568/go.mod h1:xEzjJPgXI435gkrCt3MPfRiAkVrwSbHsst4LCFVfpJc=
github.com/gliderlabs/ssh v0.2.2 h1:6zsha5zo/TWhRhwqCD3+EarCAgZ2yN28ipRnGPnwkI0=
github.com/gliderlabs/ssh v0.2.2/go.mod h1:U7qILu1NlMHj9FlMhZLlkCdDnU1DBEAqr0aevW3Awn0=
github.com/google/go-cmp v0.3.0 h1:crn/baboCvb5fXaQ0IJ1SGTsTVrWpDsCWC8EGETZijY=
github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 h1:BQSFePA1RWJOlocH6Fxy8MmwDt+yVQYULKfN0RoTN8A=
github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99/go.mod h1:1lJo3i6rXxKeerYnT8Nvf0QmHCRC1n8sfWVwXF2Frvo=
github.com/jessevdk/go-flags v1.4.0/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI=
github.com/kevinburke/ssh_config v0.0.0-20190725054713-01f96b0aa0cd h1:Coekwdh0v2wtGp9Gmz1Ze3eVRAWJMLokvN3QjdzCHLY=
github.com/kevinburke/ssh_config v0.0.0-20190725054713-01f96b0aa0cd/go.mod h1:CT57kijsi8u/K/BOFA39wgDQJ9CxiF4nAY/ojJ6r6mM=
github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI=
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
github.com/kr/pty v1.1.8/go.mod h1:O1sed60cT9XZ5uDucP5qwvh+TE3NnUj51EiZO/lmSfw=
github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE=
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
github.com/mitchellh/go-homedir v1.1.0 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG+4E0Y=
github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=
github.com/pelletier/go-buffruneio v0.2.0/go.mod h1:JkE26KsDizTr40EUHkXVtNPvgGtbSNq5BcowyYOWdKo=
github.com/pkg/errors v0.8.1 h1:iURUrRGxPUNPdy5/HRSm+Yj6okJ6UtLINN0Q9M4+h3I=
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/sergi/go-diff v1.0.0 h1:Kpca3qRNrduNnOQeazBd0ysaKrUJiIuISHxogkT9RPQ=
github.com/sergi/go-diff v1.0.0/go.mod h1:0CfEIISq7TuYL3j771MWULgwwjU+GofnZX9QAmXWZgo=
@@ -29,6 +39,7 @@ github.com/src-d/gcfg v1.4.0 h1:xXbNR5AlLSA315x2UO+fTSSAXCDf+Ar38/6oyGbDKQ4=
github.com/src-d/gcfg v1.4.0/go.mod h1:p/UMsR43ujA89BJY9duynAwIpvqEujIH/jFlfL7jWoI=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.2.0/go.mod h1:qt09Ya8vawLte6SNmTgCsAVtYtaKzEcn8ATUoHMkEqE=
github.com/stretchr/testify v1.3.0 h1:TivCn/peBQ7UY8ooIcPgZFpTNSz0Q2U6UrFlUfqbe0Q=
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
github.com/xanzy/ssh-agent v0.2.1 h1:TCbipTQL2JiiCprBWx9frJ2eJlCYT00NmctrHxVAr70=
github.com/xanzy/ssh-agent v0.2.1/go.mod h1:mLlQY/MoOhWBj+gOGMQkOeiEvkx+8pJSI+0Bx9h2kr4=
@@ -47,12 +58,15 @@ golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7w
golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e h1:D5TXcfTk7xF7hvieo4QErS3qqCB4teTffacDWr7CI+0=
golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.2 h1:tW2bmiBqwgJj/UpqtC8EpXEZVYOwU0yG4iWbprSVAcs=
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20190729092621-ff9f1409240a/go.mod h1:jcCCGcm9btYwXyDqrUWc6MKQKKGJCWEQ3AfLSRIbEuI=
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY=
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/src-d/go-billy.v4 v4.3.2 h1:0SQA1pRztfTFx2miS8sA97XvooFeNOmvUenF4o0EcVg=
gopkg.in/src-d/go-billy.v4 v4.3.2/go.mod h1:nDjArDMp+XMs1aFAESLRjfGSgfvoYN0hDfzEk0GjC98=
gopkg.in/src-d/go-git-fixtures.v3 v3.5.0 h1:ivZFOIltbce2Mo8IjzUHAFoq/IylO9WHhNOAJK+LsJg=
gopkg.in/src-d/go-git-fixtures.v3 v3.5.0/go.mod h1:dLBcvytrw/TYZsNTWCnkNF2DSIlzWYqTe3rJR56Ac7g=
gopkg.in/src-d/go-git.v4 v4.13.1 h1:SRtFyV8Kxc0UP7aCHcijOMQGPxHSmMOPrzulQWolkYE=
gopkg.in/src-d/go-git.v4 v4.13.1/go.mod h1:nx5NYcxdKxq5fpltdHnPa2Exj4Sx0EclMWZQbYDu2z8=

View File

@@ -15,8 +15,8 @@ import (
"time"
"unicode"
"github.com/sixt/gomodproxy/pkg/store"
"github.com/sixt/gomodproxy/pkg/vcs"
"github.com/bilus/gomodproxy/pkg/store"
"github.com/bilus/gomodproxy/pkg/vcs"
)
type logger = func(v ...interface{})
@@ -27,6 +27,8 @@ type api struct {
vcsPaths []vcsPath
stores []store.Store
semc chan struct{}
ephemeralTagStorage *vcs.EphemeralTagStorage
}
type vcsPath struct {
@@ -42,6 +44,7 @@ var (
apiInfo = regexp.MustCompile(`^/(?P<module>.*)/@v/(?P<version>.*).info$`)
apiMod = regexp.MustCompile(`^/(?P<module>.*)/@v/(?P<version>.*).mod$`)
apiZip = regexp.MustCompile(`^/(?P<module>.*)/@v/(?P<version>.*).zip$`)
apiTag = regexp.MustCompile(`^/tags/(?P<module>.*)/@v/(?P<version>.*)$`)
)
var (
@@ -86,6 +89,28 @@ func Git(prefix string, auth string) Option {
}
}
// GitWithEphemeralTags configures API to use a specific git client when trying
// to download a repository with the given prefix. Auth string can be a path to
// the SSK key, or a colon-separated username:password string.
func GitWithEphemeralTags(prefix string, auth string) Option {
storage := vcs.NewEphemeralTagStorage()
a := vcs.Key(auth)
if creds := strings.SplitN(auth, ":", 2); len(creds) == 2 {
a = vcs.Password(creds[0], creds[1])
}
return func(api *api) {
api.ephemeralTagStorage = storage
api.vcsPaths = append(api.vcsPaths, vcsPath{
prefix: prefix,
vcs: func(module string) vcs.VCS {
return vcs.NewGitWithEphemeralTags(api.log, api.gitdir, module, a, storage)
},
})
}
}
func CustomVCS(prefix string, cmd string) Option {
return func(api *api) {
api.vcsPaths = append(api.vcsPaths, vcsPath{
@@ -151,6 +176,7 @@ func (api *api) ServeHTTP(w http.ResponseWriter, r *http.Request) {
{"info", apiInfo, api.info},
{"api", apiMod, api.mod},
{"zip", apiZip, api.zip},
{"tag", apiTag, api.tag},
} {
if m := route.regexp.FindStringSubmatch(r.URL.Path); m != nil {
module, version := m[1], ""
@@ -301,3 +327,35 @@ func (api *api) delete(w http.ResponseWriter, r *http.Request, module, version s
}
}
}
func (api *api) tag(w http.ResponseWriter, r *http.Request, module, version string) {
api.log("api.tag", "module", module, "version", version)
taggable, ok := api.vcs(r.Context(), module).(vcs.Taggable)
if !ok {
err := fmt.Errorf("repository for module %v is not taggable", module)
api.log("api.tag", "module", module, "version", version, "error", err)
httpErrors.Add(module, 1)
http.Error(w, err.Error(), http.StatusNotFound)
return
}
defer r.Body.Close()
req := struct {
Short string `json:"short"`
}{}
err := json.NewDecoder(r.Body).Decode(&req)
if err != nil {
api.log("api.tag", "module", module, "version", version, "error", err)
http.Error(w, err.Error(), http.StatusBadRequest)
return
}
err = taggable.Tag(r.Context(), vcs.Version(version), req.Short)
if err != nil {
api.log("api.tag", "module", module, "version", version, "error", err)
http.Error(w, err.Error(), http.StatusBadRequest)
return
}
}

View File

@@ -6,7 +6,7 @@ import (
"os"
"path/filepath"
"github.com/sixt/gomodproxy/pkg/vcs"
"github.com/bilus/gomodproxy/pkg/vcs"
)
type disk string

View File

@@ -5,7 +5,7 @@ import (
"errors"
"sync"
"github.com/sixt/gomodproxy/pkg/vcs"
"github.com/bilus/gomodproxy/pkg/vcs"
)
type memory struct {

View File

@@ -4,7 +4,7 @@ import (
"context"
"time"
"github.com/sixt/gomodproxy/pkg/vcs"
"github.com/bilus/gomodproxy/pkg/vcs"
)
type logger = func(...interface{})

View File

@@ -34,6 +34,8 @@ type gitVCS struct {
auth Auth
}
var ErrNoMatchingVersion = errors.New("no matching versions")
// NewGit return a go-git VCS client implementation that provides information
// about the specific module using the pgiven authentication mechanism.
func NewGit(l logger, dir string, module string, auth Auth) VCS {
@@ -61,7 +63,6 @@ func (g *gitVCS) List(ctx context.Context) ([]Version, error) {
if err != nil {
return nil, err
}
list := []Version{}
masterHash := ""
tagPrefix := ""
@@ -81,18 +82,55 @@ func (g *gitVCS) List(ctx context.Context) ([]Version, error) {
if masterHash == "" {
return nil, errors.New("no tags and no master branch found")
}
short := masterHash[:12]
t, err := g.Timestamp(ctx, Version("v0.0.0-20060102150405-"+short))
version, err := g.versionFromHash(ctx, short)
if err != nil {
return nil, err
}
list = []Version{Version(fmt.Sprintf("v0.0.0-%s-%s", t.Format("20060102150405"), short))}
masterCommit, err := g.commit(ctx, version)
if err != nil {
return nil, err
}
tree, err := masterCommit.Tree()
if err != nil {
return nil, err
}
// No tags while it's a module.
if g.isModule(tree) {
return nil, ErrNoMatchingVersion
}
list = []Version{version}
}
g.log("gitVCS.List", "module", g.module, "list", list)
return list, nil
}
func (g *gitVCS) versionFromHash(ctx context.Context, hash string) (Version, error) {
t, err := g.Timestamp(ctx, Version("v0.0.0-20060102150405-"+hash))
if err != nil {
return Version(""), err
}
v := Version(fmt.Sprintf("v0.0.0-%s-%s", t.Format("20060102150405"), hash))
return v, nil
}
func (g *gitVCS) isModule(tree *object.Tree) bool {
mod := "go.mod"
for path := g.prefix; path != "."; path = filepath.Dir(path) {
_, err := tree.FindEntry(filepath.Join(path, mod))
if err == nil {
return true
}
}
return false
}
func (g *gitVCS) Timestamp(ctx context.Context, version Version) (time.Time, error) {
g.log("gitVCS.Timestamp", "module", g.module, "version", version)
ci, err := g.commit(ctx, version)
@@ -116,6 +154,11 @@ func isVendoredPackage(name string) bool {
}
func (g *gitVCS) Zip(ctx context.Context, version Version) (io.ReadCloser, error) {
dirName := g.module + "@" + string(version)
return g.zipAs(ctx, version, dirName)
}
func (g *gitVCS) zipAs(ctx context.Context, version Version, dirName string) (io.ReadCloser, error) {
g.log("gitVCS.Zip", "module", g.module, "version", version)
ci, err := g.commit(ctx, version)
if err != nil {
@@ -176,7 +219,7 @@ func (g *gitVCS) Zip(ctx context.Context, version Version) (io.ReadCloser, error
} else {
continue
}
w, err := zw.Create(filepath.Join(g.module+"@"+string(version), name))
w, err := zw.Create(filepath.Join(dirName, name))
if err != nil {
return nil, err
}
@@ -240,6 +283,10 @@ func (g *gitVCS) commit(ctx context.Context, version Version) (*object.Commit, e
if err != nil && err != git.NoErrAlreadyUpToDate {
return nil, err
}
tagPrefix := ""
if g.prefix != "" {
tagPrefix = g.prefix + "/"
}
version = Version(strings.TrimSuffix(string(version), "+incompatible"))
hash := version.Hash()
@@ -249,7 +296,7 @@ func (g *gitVCS) commit(ctx context.Context, version Version) (*object.Commit, e
return nil, err
}
tags.ForEach(func(t *plumbing.Reference) error {
if t.Name().String() == "refs/tags/"+string(version) {
if t.Name().String() == path.Join("refs/tags", tagPrefix, string(version)) {
hash = t.Hash().String()
annotated, err := repo.TagObject(t.Hash())
if err == nil {

View File

@@ -82,6 +82,6 @@ func (g *goVCS) download(ctx context.Context, version string) error {
}
func (g *goVCS) file(name string) ([]byte, error) {
path := filepath.Join(g.dir, "pkg", "mod", "cache", "download", g.module, "@v", name)
path := filepath.Join(g.dir, "pkg", "mod", "cache", "download", encodeBangs(g.module), "@v", name)
return ioutil.ReadFile(path)
}

142
pkg/vcs/tags.go Normal file
View File

@@ -0,0 +1,142 @@
package vcs
import (
"context"
"fmt"
"io"
"time"
)
type ephemeralTag struct {
semVer Version
short string
}
type EphemeralTagStorage struct {
tagsByModule map[moduleName][]ephemeralTag
}
func NewEphemeralTagStorage() *EphemeralTagStorage {
return &EphemeralTagStorage{
tagsByModule: make(map[moduleName][]ephemeralTag),
}
}
func (s *EphemeralTagStorage) Tag(module string, semVer Version, short string) error {
tags := s.tagsByModule[module]
tmp := tags[:0]
for _, t := range tags {
if t.semVer != semVer {
tmp = append(tmp, t)
}
}
s.tagsByModule[module] = append(tmp, ephemeralTag{semVer, short})
return nil
}
func (s *EphemeralTagStorage) tags(module string) []ephemeralTag {
return s.tagsByModule[module]
}
type moduleName = string
type taggableVCS struct {
wrapped *gitVCS
module string
storage *EphemeralTagStorage
}
type Taggable interface {
Tag(ctx context.Context, semVer Version, short string) error
}
// NewGitWithEphemeralTags return a go-git VCS client implementation that
// provides information about the specific module using the given
// authentication mechanism while adding support to ephemeral tags.
func NewGitWithEphemeralTags(l logger, dir string, module string, auth Auth, storage *EphemeralTagStorage) VCS {
git := NewGit(l, dir, module, auth).(*gitVCS)
return &taggableVCS{
wrapped: git,
module: module,
storage: storage,
}
}
func (v *taggableVCS) safeList(ctx context.Context) ([]Version, error) {
remoteVersions, err := v.wrapped.List(ctx)
if err != nil {
// Ignore this error, we can still count on ephemeral tags.
if err != ErrNoMatchingVersion {
return nil, err
}
v.wrapped.log("No remote version tags yet:", err)
}
return remoteVersions, nil
}
func (v *taggableVCS) Tag(ctx context.Context, semVer Version, short string) error {
remoteVersions, err := v.safeList(ctx)
if err != nil {
return err
}
if versionExists(remoteVersions, semVer) {
return fmt.Errorf("remote version %s already exists for module %s", semVer, v.module)
}
return v.storage.Tag(v.module, semVer, short)
}
func (v *taggableVCS) List(ctx context.Context) ([]Version, error) {
remoteVersions, err := v.safeList(ctx)
if err != nil {
return nil, err
}
tags := v.storage.tags(v.module)
// Remote versions win.
return appendEphemeralVersion(remoteVersions, tags...), nil
}
func appendEphemeralVersion(versions []Version, tags ...ephemeralTag) []Version {
ephemeral := make([]Version, 0)
for _, tag := range tags {
if !versionExists(versions, tag.semVer) {
ephemeral = append(ephemeral, tag.semVer)
}
}
return append(versions, ephemeral...)
}
func versionExists(versions []Version, v Version) bool {
for _, v2 := range versions {
if v == v2 {
return true
}
}
return false
}
func (v *taggableVCS) Timestamp(ctx context.Context, version Version) (time.Time, error) {
version2, err := v.resolveVersion(ctx, version)
if err != nil {
return time.Time{}, err
}
return v.wrapped.Timestamp(ctx, version2)
}
func (v *taggableVCS) Zip(ctx context.Context, version Version) (io.ReadCloser, error) {
version2, err := v.resolveVersion(ctx, version)
if err != nil {
return nil, err
}
// Zip must contain the ephemeral version.
dirName := v.module + "@" + string(version)
return v.wrapped.zipAs(ctx, version2, dirName)
}
func (v *taggableVCS) resolveVersion(ctx context.Context, version Version) (Version, error) {
for _, tag := range v.storage.tags(v.module) {
if tag.semVer == version {
return v.wrapped.versionFromHash(ctx, tag.short)
}
}
return version, nil
}