Compare commits
48 Commits
Author | SHA1 | Date | |
---|---|---|---|
|
0822dc2dc9 | ||
|
49ce78ba79 | ||
|
696a87c7a7 | ||
|
9285c36f88 | ||
|
9caa24db3b | ||
|
d162b6a2ba | ||
|
c8409dd662 | ||
|
eca3d1768e | ||
|
4e2759e69f | ||
|
0837c80915 | ||
|
34637ebe9d | ||
|
864701dea1 | ||
|
2d868b4e26 | ||
|
9de64c0d8d | ||
|
513e50f7f5 | ||
|
3cd63129a6 | ||
|
a4d54bc847 | ||
|
00c4725d2a | ||
|
92437761df | ||
|
9c4535cb52 | ||
|
e690d870fb | ||
|
e9b53b32ae | ||
|
d4916fc30d | ||
|
68959a741a | ||
|
c327898faa | ||
|
6768e02d91 | ||
|
656e7b2fc1 | ||
|
9624b2b9ad | ||
|
48d1cf9ff8 | ||
|
52d4a68d86 | ||
|
e08d238243 | ||
|
92e846002e | ||
|
708a07fec6 | ||
|
db43a4e741 | ||
|
7a37ec05c4 | ||
|
0868adad0a | ||
|
5a917d7c6f | ||
|
c2825d2276 | ||
|
d4aafd60cf | ||
|
c7f7535da8 | ||
|
2bf79f79ab | ||
|
4b37c90d04 | ||
|
a7494ce36f | ||
|
c16efb7791 | ||
|
5fed7316c6 | ||
|
fc10c1c983 | ||
|
254fe284ff | ||
|
e892000edb |
3
.gitignore
vendored
3
.gitignore
vendored
@ -15,3 +15,6 @@
|
|||||||
# Misc
|
# Misc
|
||||||
_gopath
|
_gopath
|
||||||
_gocache
|
_gocache
|
||||||
|
|
||||||
|
dist/
|
||||||
|
.idea/
|
20
.gitlab-ci.yml
Normal file
20
.gitlab-ci.yml
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
variables:
|
||||||
|
CI_REGISTRY: registry.connectone.pro
|
||||||
|
CONTAINER_IMAGE: ${CI_REGISTRY}/${CI_PROJECT_PATH}:${CI_BUILD_REF_NAME}_${CI_BUILD_REF}
|
||||||
|
CONTAINER_IMAGE_LATEST: ${CI_REGISTRY}/${CI_PROJECT_PATH}:latest
|
||||||
|
|
||||||
|
stages:
|
||||||
|
- release
|
||||||
|
|
||||||
|
release:
|
||||||
|
stage: release
|
||||||
|
image: docker:latest
|
||||||
|
before_script:
|
||||||
|
- docker login -u gitlab-ci-token -p ${CI_BUILD_TOKEN} ${CI_REGISTRY}
|
||||||
|
script:
|
||||||
|
- docker build -t ${CONTAINER_IMAGE_LATEST} .
|
||||||
|
- docker tag ${CONTAINER_IMAGE_LATEST} ${CONTAINER_IMAGE}
|
||||||
|
- docker push ${CONTAINER_IMAGE}
|
||||||
|
- docker push ${CONTAINER_IMAGE_LATEST}
|
||||||
|
only:
|
||||||
|
- master
|
39
.goreleaser.yaml
Normal file
39
.goreleaser.yaml
Normal file
@ -0,0 +1,39 @@
|
|||||||
|
# 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
|
||||||
|
goarch:
|
||||||
|
- arm64
|
||||||
|
- amd64
|
||||||
|
- 386
|
||||||
|
main: ./cmd/gomodproxy/main.go
|
||||||
|
archives:
|
||||||
|
- replacements:
|
||||||
|
darwin: Darwin
|
||||||
|
linux: Linux
|
||||||
|
windows: Windows
|
||||||
|
386: i386
|
||||||
|
amd64: x86_64
|
||||||
|
arm64: Arm64
|
||||||
|
checksum:
|
||||||
|
name_template: 'checksums.txt'
|
||||||
|
snapshot:
|
||||||
|
name_template: "{{ incpatch .Version }}-next"
|
||||||
|
universal_binaries:
|
||||||
|
- name_template: "{{.ProjectName}}_MacOS_{{.Version}}"
|
||||||
|
changelog:
|
||||||
|
sort: asc
|
||||||
|
filters:
|
||||||
|
exclude:
|
||||||
|
- '^docs:'
|
||||||
|
- '^test:'
|
12
.travis.yml
12
.travis.yml
@ -12,7 +12,17 @@ script:
|
|||||||
- go test -v ./...
|
- go test -v ./...
|
||||||
|
|
||||||
after_success:
|
after_success:
|
||||||
- printf 'FROM scratch\nADD gomodproxy /\nCMD ["/gomodproxy"]' > Dockerfile
|
- mkdir gomods
|
||||||
|
- printf 'FROM scratch\nADD gomodproxy /\nADD gomods /\nCMD ["/gomodproxy"]' > Dockerfile
|
||||||
|
- docker build -t "sixtlabs/gomodproxy-slim:latest" .
|
||||||
|
- echo "$DOCKER_PASSWORD" | docker login -u "$DOCKER_USERNAME" --password-stdin
|
||||||
|
- docker push "sixtlabs/gomodproxy-slim:latest"
|
||||||
|
- if [ ! -z $TRAVIS_TAG ] ; then
|
||||||
|
TAG=$(echo $TRAVIS_TAG | sed 's/^v//');
|
||||||
|
docker tag sixtlabs/gomodproxy-slim:latest sixtlabs/gomodproxy-slim:$TAG;
|
||||||
|
docker push sixtlabs/gomodproxy-slim:$TAG;
|
||||||
|
fi
|
||||||
|
- printf 'FROM golang\nADD gomodproxy /\nADD gomods /\nCMD ["/gomodproxy"]' > Dockerfile
|
||||||
- docker build -t "sixtlabs/gomodproxy:latest" .
|
- docker build -t "sixtlabs/gomodproxy:latest" .
|
||||||
- echo "$DOCKER_PASSWORD" | docker login -u "$DOCKER_USERNAME" --password-stdin
|
- echo "$DOCKER_PASSWORD" | docker login -u "$DOCKER_USERNAME" --password-stdin
|
||||||
- docker push "sixtlabs/gomodproxy:latest"
|
- docker push "sixtlabs/gomodproxy:latest"
|
||||||
|
18
Dockerfile
Normal file
18
Dockerfile
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
FROM golang:1.20.1-alpine3.17 as build
|
||||||
|
WORKDIR /build
|
||||||
|
RUN apk add --no-cache git gcc musl-dev
|
||||||
|
ADD go.mod go.sum ./
|
||||||
|
RUN go mod download
|
||||||
|
ADD . .
|
||||||
|
RUN go build -o gomodproxy cmd/gomodproxy/main.go && \
|
||||||
|
chmod a+x gomodproxy
|
||||||
|
|
||||||
|
FROM golang:1.20.1-alpine3.17
|
||||||
|
ENV GONOSUMDB=gitlab.connectone.pro/*
|
||||||
|
WORKDIR /opt/app
|
||||||
|
RUN apk add --no-cache tzdata ca-certificates git openssh
|
||||||
|
COPY --from=build /build/gomodproxy ./
|
||||||
|
ADD entrypoint.sh .
|
||||||
|
RUN chmod a+x *.sh
|
||||||
|
ENTRYPOINT ["/opt/app/entrypoint.sh"]
|
||||||
|
CMD ["/opt/app/gomodproxy", "--addr", ":8080"]
|
@ -2,11 +2,16 @@
|
|||||||
|
|
||||||
[](https://travis-ci.org/sixt/gomodproxy)
|
[](https://travis-ci.org/sixt/gomodproxy)
|
||||||
[](https://goreportcard.com/report/github.com/sixt/gomodproxy)
|
[](https://goreportcard.com/report/github.com/sixt/gomodproxy)
|
||||||
|
[](https://hub.docker.com/r/sixtlabs/gomodproxy/)
|
||||||
|
|
||||||
gomodproxy is a caching proxy for [Go modules].
|
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.
|
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
|
## Getting started
|
||||||
|
|
||||||
gomodproxy requires Go 1.11 or newer. There are no plans to support `vgo` or Go 1.11 beta versions.
|
gomodproxy requires Go 1.11 or newer. There are no plans to support `vgo` or Go 1.11 beta versions.
|
||||||
|
@ -16,7 +16,7 @@ import (
|
|||||||
"time"
|
"time"
|
||||||
"unicode"
|
"unicode"
|
||||||
|
|
||||||
"github.com/sixt/gomodproxy/pkg/api"
|
"gitlab.connectone.pro/github/gomodproxy/pkg/api"
|
||||||
|
|
||||||
"expvar"
|
"expvar"
|
||||||
_ "net/http/pprof"
|
_ "net/http/pprof"
|
||||||
@ -97,16 +97,19 @@ func (f *listFlag) Set(s string) error { *f = append(*f, s); return nil }
|
|||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
gitPaths := listFlag{}
|
gitPaths := listFlag{}
|
||||||
|
vcsPaths := listFlag{}
|
||||||
|
|
||||||
addr := flag.String("addr", ":0", "http server address")
|
addr := flag.String("addr", ":0", "http server address")
|
||||||
verbose := flag.Bool("v", false, "verbose logging")
|
verbose := flag.Bool("v", false, "verbose logging")
|
||||||
prometheus := flag.String("prometheus", "", "prometheus address")
|
prometheus := flag.String("prometheus", "", "prometheus address")
|
||||||
debug := flag.Bool("debug", false, "enable debug HTTP API (pprof/expvar)")
|
debug := flag.Bool("debug", false, "enable debug HTTP API (pprof/expvar)")
|
||||||
json := flag.Bool("json", false, "json structured logging")
|
useJsonLog := flag.Bool("json", false, "json structured logging")
|
||||||
dir := flag.String("dir", filepath.Join(os.Getenv("HOME"), ".gomodproxy/cache"), "modules cache directory")
|
dir := flag.String("dir", filepath.Join(os.Getenv("HOME"), ".gomodproxy/cache"), "modules cache directory")
|
||||||
gitdir := flag.String("gitdir", filepath.Join(os.Getenv("HOME"), ".gomodproxy/git"), "git cache directory")
|
gitdir := flag.String("gitdir", filepath.Join(os.Getenv("HOME"), ".gomodproxy/git"), "git cache directory")
|
||||||
memLimit := flag.Int64("mem", 256, "in-memory cache size in MB")
|
memLimit := flag.Int64("mem", 256, "in-memory cache size in MB")
|
||||||
|
workers := flag.Int("workers", 1, "number of parallel VCS workers")
|
||||||
flag.Var(&gitPaths, "git", "list of git settings")
|
flag.Var(&gitPaths, "git", "list of git settings")
|
||||||
|
flag.Var(&vcsPaths, "vcs", "list of custom VCS handlers")
|
||||||
|
|
||||||
flag.Parse()
|
flag.Parse()
|
||||||
|
|
||||||
@ -120,8 +123,8 @@ func main() {
|
|||||||
|
|
||||||
options := []api.Option{}
|
options := []api.Option{}
|
||||||
logger := func(...interface{}) {}
|
logger := func(...interface{}) {}
|
||||||
if *verbose || *json {
|
if *verbose || *useJsonLog {
|
||||||
if *json {
|
if *useJsonLog {
|
||||||
logger = jsonLog
|
logger = jsonLog
|
||||||
} else {
|
} else {
|
||||||
logger = prettyLog
|
logger = prettyLog
|
||||||
@ -134,10 +137,20 @@ func main() {
|
|||||||
if len(kv) != 2 {
|
if len(kv) != 2 {
|
||||||
log.Fatal("bad git path:", path)
|
log.Fatal("bad git path:", path)
|
||||||
}
|
}
|
||||||
options = append(options, api.Git(kv[0], kv[1]))
|
password := os.Getenv("SSH_PASSPHRASE")
|
||||||
|
options = append(options, api.GitWithEphemeralTags(kv[0], kv[1], password))
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, path := range vcsPaths {
|
||||||
|
kv := strings.SplitN(path, ":", 2)
|
||||||
|
if len(kv) != 2 {
|
||||||
|
log.Fatal("bad VCS syntax:", path)
|
||||||
|
}
|
||||||
|
options = append(options, api.CustomVCS(kv[0], kv[1]))
|
||||||
}
|
}
|
||||||
|
|
||||||
options = append(options,
|
options = append(options,
|
||||||
|
api.VCSWorkers(*workers),
|
||||||
api.GitDir(*gitdir),
|
api.GitDir(*gitdir),
|
||||||
api.Memory(logger, *memLimit*1024*1024),
|
api.Memory(logger, *memLimit*1024*1024),
|
||||||
api.CacheDir(*dir),
|
api.CacheDir(*dir),
|
||||||
|
32
docker-compose.yml
Normal file
32
docker-compose.yml
Normal file
@ -0,0 +1,32 @@
|
|||||||
|
version: '3.2'
|
||||||
|
|
||||||
|
services:
|
||||||
|
gomodproxy:
|
||||||
|
image: registry.connectone.pro/github/gomodproxy:latest
|
||||||
|
restart: unless-stopped
|
||||||
|
ports:
|
||||||
|
- "9030:8080"
|
||||||
|
volumes:
|
||||||
|
- type: bind
|
||||||
|
source: ./cache
|
||||||
|
target: /cache
|
||||||
|
bind:
|
||||||
|
propagation: shared
|
||||||
|
- type: bind
|
||||||
|
source: ./keys
|
||||||
|
target: /root/.ssh
|
||||||
|
bind:
|
||||||
|
propagation: shared
|
||||||
|
environment:
|
||||||
|
GIT_USER: ${GIT_USER}
|
||||||
|
GIT_PASS: ${GIT_PASS}
|
||||||
|
command:
|
||||||
|
- /opt/app/gomodproxy
|
||||||
|
- --dir
|
||||||
|
- /cache
|
||||||
|
- --addr
|
||||||
|
- :8080
|
||||||
|
- -git
|
||||||
|
- gitlab.connectone.pro:/root/.ssh/id_rsa
|
||||||
|
- -git
|
||||||
|
- https://gitlab.connectone.pro:${GIT_USER}:${GIT_PASS}
|
3
entrypoint.sh
Normal file
3
entrypoint.sh
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
#!/bin/sh
|
||||||
|
git config --global url."https://${GIT_USER}:${GIT_PASS}@gitlab.connectone.pro".insteadOf "https://gitlab.connectone.pro"
|
||||||
|
exec "$@"
|
34
go.mod
34
go.mod
@ -1,18 +1,26 @@
|
|||||||
module github.com/sixt/gomodproxy
|
module gitlab.connectone.pro/github/gomodproxy
|
||||||
|
|
||||||
|
go 1.20
|
||||||
|
|
||||||
|
require github.com/go-git/go-git/v5 v5.5.2
|
||||||
|
|
||||||
require (
|
require (
|
||||||
github.com/emirpasic/gods v1.11.0 // indirect
|
github.com/Microsoft/go-winio v0.5.2 // indirect
|
||||||
|
github.com/ProtonMail/go-crypto v0.0.0-20221026131551-cf6655e29de4 // indirect
|
||||||
|
github.com/acomagu/bufpipe v1.0.3 // indirect
|
||||||
|
github.com/cloudflare/circl v1.1.0 // indirect
|
||||||
|
github.com/emirpasic/gods v1.18.1 // indirect
|
||||||
|
github.com/go-git/gcfg v1.5.0 // indirect
|
||||||
|
github.com/go-git/go-billy/v5 v5.4.0 // indirect
|
||||||
|
github.com/imdario/mergo v0.3.13 // indirect
|
||||||
github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 // indirect
|
github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 // indirect
|
||||||
github.com/kevinburke/ssh_config v0.0.0-20180830205328-81db2a75821e // indirect
|
github.com/kevinburke/ssh_config v1.2.0 // indirect
|
||||||
github.com/mitchellh/go-homedir v1.0.0 // indirect
|
github.com/pjbgf/sha1cd v0.2.3 // indirect
|
||||||
github.com/pelletier/go-buffruneio v0.2.0 // indirect
|
github.com/sergi/go-diff v1.1.0 // indirect
|
||||||
github.com/sergi/go-diff v1.0.0 // indirect
|
github.com/skeema/knownhosts v1.1.0 // indirect
|
||||||
github.com/src-d/gcfg v1.3.0 // indirect
|
github.com/xanzy/ssh-agent v0.3.3 // indirect
|
||||||
github.com/xanzy/ssh-agent v0.2.0 // indirect
|
golang.org/x/crypto v0.3.0 // indirect
|
||||||
golang.org/x/crypto v0.0.0-20180910181607-0e37d006457b // indirect
|
golang.org/x/net v0.2.0 // indirect
|
||||||
golang.org/x/net v0.0.0-20180911220305-26e67e76b6c3 // indirect
|
golang.org/x/sys v0.3.0 // indirect
|
||||||
golang.org/x/text v0.3.0 // indirect
|
|
||||||
gopkg.in/src-d/go-billy.v4 v4.2.1 // indirect
|
|
||||||
gopkg.in/src-d/go-git.v4 v4.7.0
|
|
||||||
gopkg.in/warnings.v0 v0.1.2 // indirect
|
gopkg.in/warnings.v0 v0.1.2 // indirect
|
||||||
)
|
)
|
||||||
|
148
go.sum
148
go.sum
@ -1,35 +1,129 @@
|
|||||||
github.com/emirpasic/gods v1.9.0 h1:rUF4PuzEjMChMiNsVjdI+SyLu7rEqpQ5reNFnhC7oFo=
|
github.com/Microsoft/go-winio v0.5.2 h1:a9IhgEQBCUEk6QCdml9CiJGhAws+YwffDHEMp1VMrpA=
|
||||||
github.com/emirpasic/gods v1.9.0/go.mod h1:YfzfFFoVP/catgzJb4IKIqXjX78Ha8FMSDh3ymbK86o=
|
github.com/Microsoft/go-winio v0.5.2/go.mod h1:WpS1mjBmmwHBEWmogvA2mj8546UReBk4v8QkMxJ6pZY=
|
||||||
github.com/emirpasic/gods v1.11.0 h1:8KhjokrJy1+REZkLeSlnJvLKI4tRR8g65a4C07oQQ50=
|
github.com/ProtonMail/go-crypto v0.0.0-20221026131551-cf6655e29de4 h1:ra2OtmuW0AE5csawV4YXMNGNQQXvLRps3z2Z59OPO+I=
|
||||||
github.com/emirpasic/gods v1.11.0/go.mod h1:YfzfFFoVP/catgzJb4IKIqXjX78Ha8FMSDh3ymbK86o=
|
github.com/ProtonMail/go-crypto v0.0.0-20221026131551-cf6655e29de4/go.mod h1:UBYPn8k0D56RtnR8RFQMjmh4KrZzWJ5o7Z9SYjossQ8=
|
||||||
|
github.com/acomagu/bufpipe v1.0.3 h1:fxAGrHZTgQ9w5QqVItgzwj235/uYZYgbXitB+dLupOk=
|
||||||
|
github.com/acomagu/bufpipe v1.0.3/go.mod h1:mxdxdup/WdsKVreO5GpW4+M/1CE2sMG4jeGJ2sYmHc4=
|
||||||
|
github.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be h1:9AeTilPcZAjCFIImctFaOjnTIavg87rW78vTPkQqLI8=
|
||||||
|
github.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be/go.mod h1:ySMOLuWl6zY27l47sB3qLNK6tF2fkHG55UZxx8oIVo4=
|
||||||
|
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/bwesterb/go-ristretto v1.2.0/go.mod h1:fUIoIZaG73pV5biE2Blr2xEzDoMj7NFEuV9ekS419A0=
|
||||||
|
github.com/cloudflare/circl v1.1.0 h1:bZgT/A+cikZnKIwn7xL2OBj012Bmvho/o6RpRvv3GKY=
|
||||||
|
github.com/cloudflare/circl v1.1.0/go.mod h1:prBCrKB9DV4poKZY1l9zBXg2QJY7mvgRvtMxxK7fi4I=
|
||||||
|
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
|
||||||
|
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.18.1 h1:FXtiHYKDGKCW2KzwZKx0iC0PQmdlorYgdFG9jPXJ1Bc=
|
||||||
|
github.com/emirpasic/gods v1.18.1/go.mod h1:8tpGGwCnJ5H4r6BWwaV6OrWmMoPhUl5jm/FMNAnJvWQ=
|
||||||
|
github.com/gliderlabs/ssh v0.3.5 h1:OcaySEmAQJgyYcArR+gGGTHCyE7nvhEMTlYY+Dp8CpY=
|
||||||
|
github.com/gliderlabs/ssh v0.3.5/go.mod h1:8XB4KraRrX39qHhT6yxPsHedjA08I/uBVwj4xC+/+z4=
|
||||||
|
github.com/go-git/gcfg v1.5.0 h1:Q5ViNfGF8zFgyJWPqYwA7qGFoMTEiBmdlkcfRmpIMa4=
|
||||||
|
github.com/go-git/gcfg v1.5.0/go.mod h1:5m20vg6GwYabIxaOonVkTdrILxQMpEShl1xiMF4ua+E=
|
||||||
|
github.com/go-git/go-billy/v5 v5.3.1/go.mod h1:pmpqyWchKfYfrkb/UVH4otLvyi/5gJlGI4Hb3ZqZ3W0=
|
||||||
|
github.com/go-git/go-billy/v5 v5.4.0 h1:Vaw7LaSTRJOUric7pe4vnzBSgyuf2KrLsu2Y4ZpQBDE=
|
||||||
|
github.com/go-git/go-billy/v5 v5.4.0/go.mod h1:vjbugF6Fz7JIflbVpl1hJsGjSHNltrSw45YK/ukIvQg=
|
||||||
|
github.com/go-git/go-git-fixtures/v4 v4.3.1 h1:y5z6dd3qi8Hl+stezc8p3JxDkoTRqMAlKnXHuzrfjTQ=
|
||||||
|
github.com/go-git/go-git-fixtures/v4 v4.3.1/go.mod h1:8LHG1a3SRW71ettAD/jW13h8c6AqjVSeL11RAdgaqpo=
|
||||||
|
github.com/go-git/go-git/v5 v5.5.2 h1:v8lgZa5k9ylUw+OR/roJHTxR4QItsNFI5nKtAXFuynw=
|
||||||
|
github.com/go-git/go-git/v5 v5.5.2/go.mod h1:BE5hUJ5yaV2YMxhmaP4l6RBQ08kMxKSPD4BlxtH7OjI=
|
||||||
|
github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38=
|
||||||
|
github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
|
||||||
|
github.com/imdario/mergo v0.3.13 h1:lFzP57bqS/wsqKssCGmtLAb8A0wKjLGrve2q3PPVcBk=
|
||||||
|
github.com/imdario/mergo v0.3.13/go.mod h1:4lJ1jqUDcsbIECGy0RUJAXNIhg+6ocWgb1ALK2O4oXg=
|
||||||
github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 h1:BQSFePA1RWJOlocH6Fxy8MmwDt+yVQYULKfN0RoTN8A=
|
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/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99/go.mod h1:1lJo3i6rXxKeerYnT8Nvf0QmHCRC1n8sfWVwXF2Frvo=
|
||||||
github.com/kevinburke/ssh_config v0.0.0-20180830205328-81db2a75821e h1:RgQk53JHp/Cjunrr1WlsXSZpqXn+uREuHvUVcK82CV8=
|
github.com/jessevdk/go-flags v1.5.0/go.mod h1:Fw0T6WPc1dYxT4mKEZRfG5kJhaTDP9pj1c2EWnYs/m4=
|
||||||
github.com/kevinburke/ssh_config v0.0.0-20180830205328-81db2a75821e/go.mod h1:CT57kijsi8u/K/BOFA39wgDQJ9CxiF4nAY/ojJ6r6mM=
|
github.com/kevinburke/ssh_config v1.2.0 h1:x584FjTGwHzMwvHx18PXxbBVzfnxogHaAReU4gf13a4=
|
||||||
|
github.com/kevinburke/ssh_config v1.2.0/go.mod h1:CT57kijsi8u/K/BOFA39wgDQJ9CxiF4nAY/ojJ6r6mM=
|
||||||
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
|
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
|
||||||
|
github.com/kr/pretty v0.2.1 h1:Fmg33tUaq4/8ym9TJN1x7sLJnHVwhP33CNkpYV/7rwI=
|
||||||
|
github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
|
||||||
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
|
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
|
||||||
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
|
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
|
||||||
github.com/mitchellh/go-homedir v1.0.0 h1:vKb8ShqSby24Yrqr/yDYkuFz8d0WUjys40rvnGC8aR0=
|
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
|
||||||
github.com/mitchellh/go-homedir v1.0.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=
|
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
|
||||||
github.com/pelletier/go-buffruneio v0.2.0 h1:U4t4R6YkofJ5xHm3dJzuRpPZ0mr5MMCoAWooScCR7aA=
|
github.com/matryer/is v1.2.0 h1:92UTHpy8CDwaJ08GqLDzhhuixiBUUD1p3AU6PHddz4A=
|
||||||
github.com/pelletier/go-buffruneio v0.2.0/go.mod h1:JkE26KsDizTr40EUHkXVtNPvgGtbSNq5BcowyYOWdKo=
|
github.com/matryer/is v1.2.0/go.mod h1:2fLPjFQM9rhQ15aVEtbuwhJinnOqrmgXPNdZsdwlWXA=
|
||||||
github.com/sergi/go-diff v1.0.0 h1:Kpca3qRNrduNnOQeazBd0ysaKrUJiIuISHxogkT9RPQ=
|
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno=
|
||||||
github.com/sergi/go-diff v1.0.0/go.mod h1:0CfEIISq7TuYL3j771MWULgwwjU+GofnZX9QAmXWZgo=
|
github.com/pjbgf/sha1cd v0.2.3 h1:uKQP/7QOzNtKYH7UTohZLcjF5/55EnTw0jO/Ru4jZwI=
|
||||||
github.com/src-d/gcfg v1.3.0 h1:2BEDr8r0I0b8h/fOqwtxCEiq2HJu8n2JGZJQFGXWLjg=
|
github.com/pjbgf/sha1cd v0.2.3/go.mod h1:HOK9QrgzdHpbc2Kzip0Q1yi3M2MFGPADtR6HjG65m5M=
|
||||||
github.com/src-d/gcfg v1.3.0/go.mod h1:p/UMsR43ujA89BJY9duynAwIpvqEujIH/jFlfL7jWoI=
|
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
|
||||||
github.com/xanzy/ssh-agent v0.2.0 h1:Adglfbi5p9Z0BmK2oKU9nTG+zKfniSfnaMYB+ULd+Ro=
|
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||||
github.com/xanzy/ssh-agent v0.2.0/go.mod h1:0NyE30eGUDliuLEHJgYte/zncp2zdTStcOnWhgSqHD8=
|
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||||
golang.org/x/crypto v0.0.0-20180910181607-0e37d006457b h1:2b9XGzhjiYsYPnKXoEfL7klWZQIt8IfyRCz62gCqqlQ=
|
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||||
golang.org/x/crypto v0.0.0-20180910181607-0e37d006457b/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
|
github.com/sergi/go-diff v1.1.0 h1:we8PVUC3FE2uYfodKH/nBHMSetSfHDR6scGdBi+erh0=
|
||||||
golang.org/x/net v0.0.0-20180911220305-26e67e76b6c3 h1:czFLhve3vsQetD6JOJ8NZZvGQIXlnN3/yXxbT6/awxI=
|
github.com/sergi/go-diff v1.1.0/go.mod h1:STckp+ISIX8hZLjrqAeVduY0gWCT9IjLuqbuNXdaHfM=
|
||||||
golang.org/x/net v0.0.0-20180911220305-26e67e76b6c3/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
github.com/sirupsen/logrus v1.7.0/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0=
|
||||||
golang.org/x/sys v0.0.0-20180903190138-2b024373dcd9/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
github.com/skeema/knownhosts v1.1.0 h1:Wvr9V0MxhjRbl3f9nMnKnFfiWTJmtECJ9Njkea3ysW0=
|
||||||
golang.org/x/text v0.3.0 h1:g61tztE5qeGQ89tm6NTjjM9VPIm088od1l6aSorWRWg=
|
github.com/skeema/knownhosts v1.1.0/go.mod h1:sKFq3RD6/TKZkSWn8boUbDC7Qkgcv+8XXijpFO6roag=
|
||||||
|
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||||
|
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
|
||||||
|
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
|
||||||
|
github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY=
|
||||||
|
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||||
|
github.com/xanzy/ssh-agent v0.3.3 h1:+/15pJfg/RsTxqYcX6fHqOXZwwMP+2VyYWJeWM2qQFM=
|
||||||
|
github.com/xanzy/ssh-agent v0.3.3/go.mod h1:6dzNDKs0J9rVPHPhaGCukekBHKqfl+L3KghI1Bc68Uw=
|
||||||
|
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
|
||||||
|
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||||
|
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
|
||||||
|
golang.org/x/crypto v0.0.0-20220525230936-793ad666bf5e/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
|
||||||
|
golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
|
||||||
|
golang.org/x/crypto v0.0.0-20220826181053-bd7e27e6170d/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
|
||||||
|
golang.org/x/crypto v0.3.0 h1:a06MkbcxBrEFc0w0QIZWXrH/9cCX6KJyWbBOIwAn+7A=
|
||||||
|
golang.org/x/crypto v0.3.0/go.mod h1:hebNnKkNXi2UzZN1eVRvBB7co0a+JxK6XbPiWVs/3J4=
|
||||||
|
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
|
||||||
|
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||||
|
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
|
||||||
|
golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
||||||
|
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
|
||||||
|
golang.org/x/net v0.0.0-20220826154423-83b083e8dc8b/go.mod h1:YDH+HFinaLZZlnHAfSS6ZXJJ9M9t4Dl22yv3iI2vPwk=
|
||||||
|
golang.org/x/net v0.2.0 h1:sZfSu1wtKLGlWI4ZZayP0ck9Y73K1ynO6gqzTdBVdPU=
|
||||||
|
golang.org/x/net v0.2.0/go.mod h1:KqCZLdyyvdV855qA2rE3GC2aiw5xGR5TEjj8smXukLY=
|
||||||
|
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
|
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
|
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||||
|
golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
golang.org/x/sys v0.0.0-20200302150141-5c8b2ff67527/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
golang.org/x/sys v0.0.0-20210320140829-1e4c9ba3b0c4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
|
golang.org/x/sys v0.0.0-20211007075335-d3039528d8ac/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
|
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
|
golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
|
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
|
golang.org/x/sys v0.0.0-20220728004956-3c1f35247d10/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
|
golang.org/x/sys v0.0.0-20220825204002-c680a09ffe64/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
|
golang.org/x/sys v0.2.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
|
golang.org/x/sys v0.3.0 h1:w8ZOecv6NaNa/zC8944JTU3vz4u6Lagfk4RPQxv92NQ=
|
||||||
|
golang.org/x/sys v0.3.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
|
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||||
|
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
||||||
|
golang.org/x/term v0.0.0-20220722155259-a9ba230a4035/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
||||||
|
golang.org/x/term v0.2.0 h1:z85xZCsEl7bi/KwbNADeBYoOP0++7W1ipu+aGnpwzRM=
|
||||||
|
golang.org/x/term v0.2.0/go.mod h1:TVmDHMZPmdnySmBfhjOoOdhjzdE1h4u1VwSiw2l1Nuc=
|
||||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||||
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||||
gopkg.in/src-d/go-billy.v4 v4.2.1 h1:omN5CrMrMcQ+4I8bJ0wEhOBPanIRWzFC953IiXKdYzo=
|
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||||
gopkg.in/src-d/go-billy.v4 v4.2.1/go.mod h1:tm33zBoOwxjYHZIE+OV8bxTWFMJLrconzFMd38aARFk=
|
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
|
||||||
gopkg.in/src-d/go-git.v4 v4.7.0 h1:WXB+2gCoRhQiAr//IMHpIpoDsTrDgvjDORxt57e8XTA=
|
golang.org/x/text v0.4.0 h1:BrVqGRd7+k1DiOgtnFvAkoQEWQvBc25ouMJM6429SFg=
|
||||||
gopkg.in/src-d/go-git.v4 v4.7.0/go.mod h1:CzbUWqMn4pvmvndg3gnh5iZFmSsbhyhUWdI0IQ60AQo=
|
golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
|
||||||
|
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||||
|
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||||
|
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
|
||||||
|
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||||
|
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||||
|
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||||
|
gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||||
|
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
|
||||||
|
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
|
||||||
gopkg.in/warnings.v0 v0.1.2 h1:wFXVbFY8DY5/xOe1ECiWdKCzZlxgshcYVNkBHstARME=
|
gopkg.in/warnings.v0 v0.1.2 h1:wFXVbFY8DY5/xOe1ECiWdKCzZlxgshcYVNkBHstARME=
|
||||||
gopkg.in/warnings.v0 v0.1.2/go.mod h1:jksf8JmL6Qr/oQM2OXTHunEvvTAsrWBLb6OOjuVWRNI=
|
gopkg.in/warnings.v0 v0.1.2/go.mod h1:jksf8JmL6Qr/oQM2OXTHunEvvTAsrWBLb6OOjuVWRNI=
|
||||||
|
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||||
|
gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||||
|
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||||
|
gopkg.in/yaml.v3 v3.0.0 h1:hjy8E9ON/egN1tAYqKb61G10WtihqetD4sz2H+8nIeA=
|
||||||
|
gopkg.in/yaml.v3 v3.0.0/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||||
|
138
pkg/api/api.go
138
pkg/api/api.go
@ -15,8 +15,8 @@ import (
|
|||||||
"time"
|
"time"
|
||||||
"unicode"
|
"unicode"
|
||||||
|
|
||||||
"github.com/sixt/gomodproxy/pkg/store"
|
"gitlab.connectone.pro/github/gomodproxy/pkg/store"
|
||||||
"github.com/sixt/gomodproxy/pkg/vcs"
|
"gitlab.connectone.pro/github/gomodproxy/pkg/vcs"
|
||||||
)
|
)
|
||||||
|
|
||||||
type logger = func(v ...interface{})
|
type logger = func(v ...interface{})
|
||||||
@ -26,6 +26,9 @@ type api struct {
|
|||||||
gitdir string
|
gitdir string
|
||||||
vcsPaths []vcsPath
|
vcsPaths []vcsPath
|
||||||
stores []store.Store
|
stores []store.Store
|
||||||
|
semc chan struct{}
|
||||||
|
|
||||||
|
ephemeralTagStorage *vcs.EphemeralTagStorage
|
||||||
}
|
}
|
||||||
|
|
||||||
type vcsPath struct {
|
type vcsPath struct {
|
||||||
@ -41,6 +44,7 @@ var (
|
|||||||
apiInfo = regexp.MustCompile(`^/(?P<module>.*)/@v/(?P<version>.*).info$`)
|
apiInfo = regexp.MustCompile(`^/(?P<module>.*)/@v/(?P<version>.*).info$`)
|
||||||
apiMod = regexp.MustCompile(`^/(?P<module>.*)/@v/(?P<version>.*).mod$`)
|
apiMod = regexp.MustCompile(`^/(?P<module>.*)/@v/(?P<version>.*).mod$`)
|
||||||
apiZip = regexp.MustCompile(`^/(?P<module>.*)/@v/(?P<version>.*).zip$`)
|
apiZip = regexp.MustCompile(`^/(?P<module>.*)/@v/(?P<version>.*).zip$`)
|
||||||
|
apiTag = regexp.MustCompile(`^/tags/(?P<module>.*)/@v/(?P<version>.*)$`)
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
@ -53,7 +57,7 @@ var (
|
|||||||
|
|
||||||
// New returns a configured http.Handler which implements GOPROXY API.
|
// New returns a configured http.Handler which implements GOPROXY API.
|
||||||
func New(options ...Option) http.Handler {
|
func New(options ...Option) http.Handler {
|
||||||
api := &api{log: func(...interface{}) {}}
|
api := &api{log: func(...interface{}) {}, semc: make(chan struct{}, 1)}
|
||||||
for _, opt := range options {
|
for _, opt := range options {
|
||||||
opt(api)
|
opt(api)
|
||||||
}
|
}
|
||||||
@ -70,9 +74,9 @@ func GitDir(dir string) Option { return func(api *api) { api.gitdir = dir } }
|
|||||||
// Git configures API to use a specific git client when trying to download a
|
// Git 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,
|
// repository with the given prefix. Auth string can be a path to the SSK key,
|
||||||
// or a colon-separated username:password string.
|
// or a colon-separated username:password string.
|
||||||
func Git(prefix string, auth string) Option {
|
func Git(prefix, key, password string) Option {
|
||||||
a := vcs.Key(auth)
|
a := vcs.Key(key, password)
|
||||||
if creds := strings.SplitN(auth, ":", 2); len(creds) == 2 {
|
if creds := strings.SplitN(key, ":", 2); len(creds) == 2 {
|
||||||
a = vcs.Password(creds[0], creds[1])
|
a = vcs.Password(creds[0], creds[1])
|
||||||
}
|
}
|
||||||
return func(api *api) {
|
return func(api *api) {
|
||||||
@ -85,6 +89,40 @@ 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, key, password string) Option {
|
||||||
|
// TODO(bilus): Ugly but we don't want to mess with the : encoding so
|
||||||
|
// we'll work around the issue of having to pass a passphrase
|
||||||
|
// to decrypt a key.
|
||||||
|
storage := vcs.NewEphemeralTagStorage()
|
||||||
|
a := vcs.Key(key, password)
|
||||||
|
if creds := strings.SplitN(key, ":", 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{
|
||||||
|
prefix: prefix,
|
||||||
|
vcs: func(module string) vcs.VCS {
|
||||||
|
return vcs.NewCommand(api.log, cmd, module)
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Memory configures API to use in-memory cache for downloaded modules.
|
// Memory configures API to use in-memory cache for downloaded modules.
|
||||||
func Memory(log logger, limit int64) Option {
|
func Memory(log logger, limit int64) Option {
|
||||||
return func(api *api) {
|
return func(api *api) {
|
||||||
@ -99,6 +137,15 @@ func CacheDir(dir string) Option {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// VCSWorkers configures API to use at most n parallel workers when fetching
|
||||||
|
// from the VCS. The reason to restrict number of workers is to limit their
|
||||||
|
// memory usage.
|
||||||
|
func VCSWorkers(n int) Option {
|
||||||
|
return func(api *api) {
|
||||||
|
api.semc = make(chan struct{}, n)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func decodeBangs(s string) string {
|
func decodeBangs(s string) string {
|
||||||
buf := []rune{}
|
buf := []rune{}
|
||||||
bang := false
|
bang := false
|
||||||
@ -130,6 +177,7 @@ func (api *api) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
|||||||
{"info", apiInfo, api.info},
|
{"info", apiInfo, api.info},
|
||||||
{"api", apiMod, api.mod},
|
{"api", apiMod, api.mod},
|
||||||
{"zip", apiZip, api.zip},
|
{"zip", apiZip, api.zip},
|
||||||
|
{"tag", apiTag, api.tag},
|
||||||
} {
|
} {
|
||||||
if m := route.regexp.FindStringSubmatch(r.URL.Path); m != nil {
|
if m := route.regexp.FindStringSubmatch(r.URL.Path); m != nil {
|
||||||
module, version := m[1], ""
|
module, version := m[1], ""
|
||||||
@ -137,6 +185,10 @@ func (api *api) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
|||||||
version = m[2]
|
version = m[2]
|
||||||
}
|
}
|
||||||
module = decodeBangs(module)
|
module = decodeBangs(module)
|
||||||
|
if r.Method == http.MethodDelete && version != "" {
|
||||||
|
api.delete(w, r, module, version)
|
||||||
|
return
|
||||||
|
}
|
||||||
httpRequests.Add(route.id, 1)
|
httpRequests.Add(route.id, 1)
|
||||||
defer func() {
|
defer func() {
|
||||||
v := &expvar.Float{}
|
v := &expvar.Float{}
|
||||||
@ -158,7 +210,7 @@ func (api *api) vcs(ctx context.Context, module string) vcs.VCS {
|
|||||||
return path.vcs(module)
|
return path.vcs(module)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return vcs.NewGit(api.log, api.gitdir, module, vcs.NoAuth())
|
return vcs.NewGoMod(api.log, module)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (api *api) module(ctx context.Context, module string, version vcs.Version) ([]byte, time.Time, error) {
|
func (api *api) module(ctx context.Context, module string, version vcs.Version) ([]byte, time.Time, error) {
|
||||||
@ -170,6 +222,16 @@ func (api *api) module(ctx context.Context, module string, version vcs.Version)
|
|||||||
}
|
}
|
||||||
cacheMisses.Add(module, 1)
|
cacheMisses.Add(module, 1)
|
||||||
|
|
||||||
|
// wait for semaphore
|
||||||
|
api.semc <- struct{}{}
|
||||||
|
defer func() { <-api.semc }()
|
||||||
|
|
||||||
|
return api.store(ctx, module, version)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (api *api) store(ctx context.Context, module string, version vcs.Version) ([]byte, time.Time, error) {
|
||||||
|
api.log("api.store", "module", module, "version", version.String())
|
||||||
|
|
||||||
timestamp, err := api.vcs(ctx, module).Timestamp(ctx, version)
|
timestamp, err := api.vcs(ctx, module).Timestamp(ctx, version)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, time.Time{}, err
|
return nil, time.Time{}, err
|
||||||
@ -207,7 +269,7 @@ func (api *api) list(w http.ResponseWriter, r *http.Request, module, version str
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
api.log("api.list", "module", module, "error", err)
|
api.log("api.list", "module", module, "error", err)
|
||||||
httpErrors.Add(module, 1)
|
httpErrors.Add(module, 1)
|
||||||
http.Error(w, err.Error(), http.StatusInternalServerError)
|
http.Error(w, err.Error(), http.StatusNotFound)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -223,7 +285,7 @@ func (api *api) info(w http.ResponseWriter, r *http.Request, module, version str
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
api.log("api.info", "module", module, "version", version, "error", err)
|
api.log("api.info", "module", module, "version", version, "error", err)
|
||||||
httpErrors.Add(module, 1)
|
httpErrors.Add(module, 1)
|
||||||
http.Error(w, err.Error(), http.StatusInternalServerError)
|
http.Error(w, err.Error(), http.StatusNotFound)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -247,9 +309,11 @@ func (api *api) mod(w http.ResponseWriter, r *http.Request, module, version stri
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
w.Write([]byte(fmt.Sprintf("module %s\n", module)))
|
||||||
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
w.Write([]byte(fmt.Sprintf("module %s\n", module)))
|
http.Error(w, err.Error(), http.StatusBadRequest)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (api *api) zip(w http.ResponseWriter, r *http.Request, module, version string) {
|
func (api *api) zip(w http.ResponseWriter, r *http.Request, module, version string) {
|
||||||
@ -258,8 +322,60 @@ func (api *api) zip(w http.ResponseWriter, r *http.Request, module, version stri
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
api.log("api.zip", "module", module, "version", version, "error", err)
|
api.log("api.zip", "module", module, "version", version, "error", err)
|
||||||
httpErrors.Add(module, 1)
|
httpErrors.Add(module, 1)
|
||||||
http.Error(w, err.Error(), http.StatusInternalServerError)
|
http.Error(w, err.Error(), http.StatusNotFound)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
io.Copy(w, bytes.NewReader(b))
|
io.Copy(w, bytes.NewReader(b))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (api *api) delete(w http.ResponseWriter, r *http.Request, module, version string) {
|
||||||
|
for _, store := range api.stores {
|
||||||
|
if err := store.Del(r.Context(), module, vcs.Version(version)); err != nil {
|
||||||
|
http.Error(w, err.Error(), http.StatusNotFound)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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
|
||||||
|
}
|
||||||
|
|
||||||
|
// wait for semaphore
|
||||||
|
api.semc <- struct{}{}
|
||||||
|
defer func() { <-api.semc }()
|
||||||
|
|
||||||
|
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
|
||||||
|
}
|
||||||
|
|
||||||
|
_, _, err = api.store(r.Context(), module, vcs.Version(version))
|
||||||
|
if err != nil {
|
||||||
|
api.log("api.tag", "module", module, "version", version, "error", err)
|
||||||
|
http.Error(w, err.Error(), http.StatusBadRequest)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -6,7 +6,7 @@ import (
|
|||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
|
|
||||||
"github.com/sixt/gomodproxy/pkg/vcs"
|
"gitlab.connectone.pro/github/gomodproxy/pkg/vcs"
|
||||||
)
|
)
|
||||||
|
|
||||||
type disk string
|
type disk string
|
||||||
@ -47,4 +47,15 @@ func (d disk) Get(ctx context.Context, module string, version vcs.Version) (Snap
|
|||||||
return s, err
|
return s, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (d disk) Del(ctx context.Context, module string, version vcs.Version) error {
|
||||||
|
dir := string(d)
|
||||||
|
s := Snapshot{Module: module, Version: version}
|
||||||
|
err := os.Remove(filepath.Join(dir, s.Key()+".time"))
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
err = os.Remove(filepath.Join(dir, s.Key()+".zip"))
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
func (d disk) Close() error { return nil }
|
func (d disk) Close() error { return nil }
|
||||||
|
@ -5,7 +5,7 @@ import (
|
|||||||
"errors"
|
"errors"
|
||||||
"sync"
|
"sync"
|
||||||
|
|
||||||
"github.com/sixt/gomodproxy/pkg/vcs"
|
"gitlab.connectone.pro/github/gomodproxy/pkg/vcs"
|
||||||
)
|
)
|
||||||
|
|
||||||
type memory struct {
|
type memory struct {
|
||||||
@ -45,17 +45,41 @@ func (m *memory) Put(ctx context.Context, snapshot Snapshot) error {
|
|||||||
func (m *memory) Get(ctx context.Context, module string, version vcs.Version) (Snapshot, error) {
|
func (m *memory) Get(ctx context.Context, module string, version vcs.Version) (Snapshot, error) {
|
||||||
m.Lock()
|
m.Lock()
|
||||||
defer m.Unlock()
|
defer m.Unlock()
|
||||||
return m.lookup(module, version)
|
item, err := m.lookup(module, version)
|
||||||
|
if err != nil {
|
||||||
|
return Snapshot{}, err
|
||||||
|
}
|
||||||
|
return item.Snapshot, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m *memory) lookup(module string, version vcs.Version) (Snapshot, error) {
|
func (m *memory) Del(ctx context.Context, module string, version vcs.Version) error {
|
||||||
|
m.Lock()
|
||||||
|
defer m.Unlock()
|
||||||
|
item, err := m.lookup(module, version)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if item.prev == nil {
|
||||||
|
m.head = item.next
|
||||||
|
} else {
|
||||||
|
item.prev.next = item.next
|
||||||
|
}
|
||||||
|
if item.next == nil {
|
||||||
|
m.tail = item.prev
|
||||||
|
} else {
|
||||||
|
item.next.prev = item.prev
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *memory) lookup(module string, version vcs.Version) (*lruItem, error) {
|
||||||
for item := m.head; item != nil; item = item.next {
|
for item := m.head; item != nil; item = item.next {
|
||||||
if item.Module == module && item.Version == version {
|
if item.Module == module && item.Version == version {
|
||||||
m.update(item)
|
m.update(item)
|
||||||
return item.Snapshot, nil
|
return item, nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return Snapshot{}, errors.New("not found")
|
return nil, errors.New("not found")
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m *memory) insert(item *lruItem) {
|
func (m *memory) insert(item *lruItem) {
|
||||||
|
@ -4,7 +4,7 @@ import (
|
|||||||
"context"
|
"context"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/sixt/gomodproxy/pkg/vcs"
|
"gitlab.connectone.pro/github/gomodproxy/pkg/vcs"
|
||||||
)
|
)
|
||||||
|
|
||||||
type logger = func(...interface{})
|
type logger = func(...interface{})
|
||||||
@ -14,6 +14,7 @@ type logger = func(...interface{})
|
|||||||
type Store interface {
|
type Store interface {
|
||||||
Put(ctx context.Context, snapshot Snapshot) error
|
Put(ctx context.Context, snapshot Snapshot) error
|
||||||
Get(ctx context.Context, module string, version vcs.Version) (Snapshot, error)
|
Get(ctx context.Context, module string, version vcs.Version) (Snapshot, error)
|
||||||
|
Del(ctx context.Context, module string, version vcs.Version) error
|
||||||
Close() error
|
Close() error
|
||||||
}
|
}
|
||||||
|
|
||||||
|
107
pkg/vcs/cmd.go
Normal file
107
pkg/vcs/cmd.go
Normal file
@ -0,0 +1,107 @@
|
|||||||
|
package vcs
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"context"
|
||||||
|
"encoding/json"
|
||||||
|
"errors"
|
||||||
|
"io"
|
||||||
|
"io/ioutil"
|
||||||
|
"os"
|
||||||
|
"os/exec"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
type cmdVCS struct {
|
||||||
|
log logger
|
||||||
|
module string
|
||||||
|
moduleEncoded string
|
||||||
|
cmd string
|
||||||
|
}
|
||||||
|
|
||||||
|
func encodeBangs(s string) string {
|
||||||
|
buf := []byte{}
|
||||||
|
for _, r := range s {
|
||||||
|
if 'A' <= r && r <= 'Z' {
|
||||||
|
buf = append(buf, '!', byte(r+'a'-'A'))
|
||||||
|
} else {
|
||||||
|
buf = append(buf, byte(r))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return string(buf)
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewCommand(l logger, cmd string, module string) VCS {
|
||||||
|
return &cmdVCS{log: l, cmd: cmd, module: module, moduleEncoded: encodeBangs(module)}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *cmdVCS) List(ctx context.Context) ([]Version, error) {
|
||||||
|
b, err := c.exec(ctx,
|
||||||
|
"MODULE="+c.module,
|
||||||
|
"MODULE_ENCODED="+c.moduleEncoded,
|
||||||
|
"ACTION=list",
|
||||||
|
"VERSION=latest",
|
||||||
|
"FILEPATH="+c.module+"/@v/list",
|
||||||
|
"FILEPATH_ENCODED="+c.moduleEncoded+"/@v/list",
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
versions := []Version{}
|
||||||
|
for _, line := range strings.Split(string(b), "\n") {
|
||||||
|
versions = append(versions, Version(line))
|
||||||
|
}
|
||||||
|
return versions, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *cmdVCS) Timestamp(ctx context.Context, version Version) (time.Time, error) {
|
||||||
|
b, err := c.exec(ctx,
|
||||||
|
"MODULE="+c.module,
|
||||||
|
"MODULE_ENCODED="+c.moduleEncoded,
|
||||||
|
"ACTION=timestamp",
|
||||||
|
"VERSION="+version.String(),
|
||||||
|
"FILEPATH="+c.module+"/@v/"+version.String()+".info",
|
||||||
|
"FILEPATH_ENCODED="+c.moduleEncoded+"/@v/"+version.String()+".info",
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
return time.Time{}, err
|
||||||
|
}
|
||||||
|
info := struct {
|
||||||
|
Version string
|
||||||
|
Time time.Time
|
||||||
|
}{}
|
||||||
|
if json.Unmarshal(b, &info) == nil {
|
||||||
|
return info.Time, nil
|
||||||
|
}
|
||||||
|
if t, err := time.Parse(time.RFC3339, string(b)); err == nil {
|
||||||
|
return t, nil
|
||||||
|
}
|
||||||
|
if sec, err := strconv.ParseInt(string(b), 10, 64); err == nil {
|
||||||
|
return time.Unix(sec, 0), nil
|
||||||
|
}
|
||||||
|
return time.Time{}, errors.New("unknown time format")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *cmdVCS) Zip(ctx context.Context, version Version) (io.ReadCloser, error) {
|
||||||
|
b, err := c.exec(ctx,
|
||||||
|
"MODULE="+c.module,
|
||||||
|
"MODULE_ENCODED="+c.moduleEncoded,
|
||||||
|
"ACTION=zip",
|
||||||
|
"VERSION="+version.String(),
|
||||||
|
"FILEPATH="+c.module+"/@v/"+version.String()+".zip",
|
||||||
|
"FILEPATH_ENCODED="+c.moduleEncoded+"/@v/"+version.String()+".zip",
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return ioutil.NopCloser(bytes.NewReader(b)), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *cmdVCS) exec(ctx context.Context, env ...string) ([]byte, error) {
|
||||||
|
cmd := exec.Command("sh", "-c", c.cmd)
|
||||||
|
cmd.Env = append(os.Environ(), env...)
|
||||||
|
cmd.Stderr = os.Stderr
|
||||||
|
return cmd.Output()
|
||||||
|
}
|
150
pkg/vcs/git.go
150
pkg/vcs/git.go
@ -6,21 +6,21 @@ import (
|
|||||||
"context"
|
"context"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"github.com/go-git/go-git/v5"
|
||||||
|
"github.com/go-git/go-git/v5/config"
|
||||||
|
"github.com/go-git/go-git/v5/plumbing"
|
||||||
|
"github.com/go-git/go-git/v5/plumbing/object"
|
||||||
|
"github.com/go-git/go-git/v5/plumbing/transport"
|
||||||
|
"github.com/go-git/go-git/v5/plumbing/transport/http"
|
||||||
|
"github.com/go-git/go-git/v5/plumbing/transport/ssh"
|
||||||
|
"github.com/go-git/go-git/v5/storage/memory"
|
||||||
"io"
|
"io"
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
"os"
|
"os"
|
||||||
|
"path"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"gopkg.in/src-d/go-git.v4"
|
|
||||||
"gopkg.in/src-d/go-git.v4/config"
|
|
||||||
"gopkg.in/src-d/go-git.v4/plumbing"
|
|
||||||
"gopkg.in/src-d/go-git.v4/plumbing/object"
|
|
||||||
"gopkg.in/src-d/go-git.v4/plumbing/transport"
|
|
||||||
"gopkg.in/src-d/go-git.v4/plumbing/transport/http"
|
|
||||||
"gopkg.in/src-d/go-git.v4/plumbing/transport/ssh"
|
|
||||||
"gopkg.in/src-d/go-git.v4/storage/memory"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
const remoteName = "origin"
|
const remoteName = "origin"
|
||||||
@ -29,9 +29,12 @@ type gitVCS struct {
|
|||||||
log logger
|
log logger
|
||||||
dir string
|
dir string
|
||||||
module string
|
module string
|
||||||
|
prefix string
|
||||||
auth Auth
|
auth Auth
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var ErrNoMatchingVersion = errors.New("no matching versions")
|
||||||
|
|
||||||
// NewGit return a go-git VCS client implementation that provides information
|
// NewGit return a go-git VCS client implementation that provides information
|
||||||
// about the specific module using the pgiven authentication mechanism.
|
// about the specific module using the pgiven authentication mechanism.
|
||||||
func NewGit(l logger, dir string, module string, auth Auth) VCS {
|
func NewGit(l logger, dir string, module string, auth Auth) VCS {
|
||||||
@ -59,15 +62,18 @@ func (g *gitVCS) List(ctx context.Context) ([]Version, error) {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
list := []Version{}
|
list := []Version{}
|
||||||
masterHash := ""
|
masterHash := ""
|
||||||
|
tagPrefix := ""
|
||||||
|
if g.prefix != "" {
|
||||||
|
tagPrefix = g.prefix + "/"
|
||||||
|
}
|
||||||
for _, ref := range refs {
|
for _, ref := range refs {
|
||||||
name := ref.Name()
|
name := ref.Name()
|
||||||
if name == plumbing.Master {
|
if name == plumbing.Master {
|
||||||
masterHash = ref.Hash().String()
|
masterHash = ref.Hash().String()
|
||||||
} else if name.IsTag() && strings.HasPrefix(name.String(), "refs/tags/v") {
|
} else if name.IsTag() && strings.HasPrefix(name.String(), "refs/tags/"+tagPrefix+"v") {
|
||||||
list = append(list, Version(strings.TrimPrefix(name.String(), "refs/tags/")))
|
list = append(list, Version(strings.TrimPrefix(name.String(), "refs/tags/"+tagPrefix)))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -75,18 +81,55 @@ func (g *gitVCS) List(ctx context.Context) ([]Version, error) {
|
|||||||
if masterHash == "" {
|
if masterHash == "" {
|
||||||
return nil, errors.New("no tags and no master branch found")
|
return nil, errors.New("no tags and no master branch found")
|
||||||
}
|
}
|
||||||
|
|
||||||
short := masterHash[:12]
|
short := masterHash[:12]
|
||||||
t, err := g.Timestamp(ctx, Version("v0.0.0-20060102150405-"+short))
|
version, err := g.versionFromHash(ctx, short)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
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)
|
g.log("gitVCS.List", "module", g.module, "list", list)
|
||||||
return list, nil
|
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) {
|
func (g *gitVCS) Timestamp(ctx context.Context, version Version) (time.Time, error) {
|
||||||
g.log("gitVCS.Timestamp", "module", g.module, "version", version)
|
g.log("gitVCS.Timestamp", "module", g.module, "version", version)
|
||||||
ci, err := g.commit(ctx, version)
|
ci, err := g.commit(ctx, version)
|
||||||
@ -110,6 +153,11 @@ func isVendoredPackage(name string) bool {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (g *gitVCS) Zip(ctx context.Context, version Version) (io.ReadCloser, error) {
|
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)
|
g.log("gitVCS.Zip", "module", g.module, "version", version)
|
||||||
ci, err := g.commit(ctx, version)
|
ci, err := g.commit(ctx, version)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -122,31 +170,77 @@ func (g *gitVCS) Zip(ctx context.Context, version Version) (io.ReadCloser, error
|
|||||||
|
|
||||||
b := &bytes.Buffer{}
|
b := &bytes.Buffer{}
|
||||||
zw := zip.NewWriter(b)
|
zw := zip.NewWriter(b)
|
||||||
|
modules := map[string]bool{}
|
||||||
|
files := []*object.File{}
|
||||||
tree.Files().ForEach(func(f *object.File) error {
|
tree.Files().ForEach(func(f *object.File) error {
|
||||||
|
dir, file := path.Split(f.Name)
|
||||||
|
if file == "go.mod" {
|
||||||
|
modules[dir] = true
|
||||||
|
}
|
||||||
|
files = append(files, f)
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
prefix := g.prefix
|
||||||
|
if prefix != "" {
|
||||||
|
prefix = prefix + "/"
|
||||||
|
}
|
||||||
|
submodule := func(name string) bool {
|
||||||
|
for {
|
||||||
|
dir, _ := path.Split(name)
|
||||||
|
if len(dir) <= len(prefix) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if modules[dir] {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
name = dir[:len(dir)-1]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for _, f := range files {
|
||||||
// go mod strips vendored directories from the zip, and we do the same
|
// go mod strips vendored directories from the zip, and we do the same
|
||||||
// to match the checksums in the go.sum
|
// to match the checksums in the go.sum
|
||||||
if isVendoredPackage(f.Name) {
|
if isVendoredPackage(f.Name) {
|
||||||
return nil
|
continue
|
||||||
}
|
}
|
||||||
w, err := zw.Create(filepath.Join(g.module+"@"+string(version), f.Name))
|
if submodule(f.Name) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
mode, err := f.Mode.ToOSFileMode()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return nil, err
|
||||||
|
}
|
||||||
|
if !mode.IsRegular() {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
name := f.Name
|
||||||
|
if strings.HasPrefix(name, prefix) {
|
||||||
|
name = strings.TrimPrefix(name, prefix)
|
||||||
|
} else {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
w, err := zw.Create(filepath.Join(dirName, name))
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
}
|
}
|
||||||
r, err := f.Reader()
|
r, err := f.Reader()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return nil, err
|
||||||
}
|
}
|
||||||
defer r.Close()
|
defer r.Close()
|
||||||
io.Copy(w, r)
|
io.Copy(w, r)
|
||||||
return nil
|
}
|
||||||
})
|
|
||||||
zw.Close()
|
zw.Close()
|
||||||
return ioutil.NopCloser(bytes.NewBuffer(b.Bytes())), nil
|
return ioutil.NopCloser(bytes.NewBuffer(b.Bytes())), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (g *gitVCS) repo(ctx context.Context) (repo *git.Repository, err error) {
|
func (g *gitVCS) repo(ctx context.Context) (repo *git.Repository, err error) {
|
||||||
|
repoRoot, path, err := RepoRoot(ctx, g.module)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
g.prefix = path
|
||||||
if g.dir != "" {
|
if g.dir != "" {
|
||||||
dir := filepath.Join(g.dir, g.module)
|
dir := filepath.Join(g.dir, repoRoot)
|
||||||
if _, err := os.Stat(dir); os.IsNotExist(err) {
|
if _, err := os.Stat(dir); os.IsNotExist(err) {
|
||||||
os.MkdirAll(dir, 0755)
|
os.MkdirAll(dir, 0755)
|
||||||
repo, err = git.PlainInit(dir, true)
|
repo, err = git.PlainInit(dir, true)
|
||||||
@ -163,10 +257,7 @@ func (g *gitVCS) repo(ctx context.Context) (repo *git.Repository, err error) {
|
|||||||
if g.auth.Key != "" {
|
if g.auth.Key != "" {
|
||||||
schema = "ssh://"
|
schema = "ssh://"
|
||||||
}
|
}
|
||||||
repoRoot := g.module
|
g.log("repo", "url", schema+repoRoot+".git", "prefix", g.prefix)
|
||||||
if meta, err := MetaImports(ctx, g.module); err == nil {
|
|
||||||
repoRoot = meta
|
|
||||||
}
|
|
||||||
_, err = repo.CreateRemote(&config.RemoteConfig{
|
_, err = repo.CreateRemote(&config.RemoteConfig{
|
||||||
Name: remoteName,
|
Name: remoteName,
|
||||||
URLs: []string{schema + repoRoot + ".git"},
|
URLs: []string{schema + repoRoot + ".git"},
|
||||||
@ -186,10 +277,15 @@ func (g *gitVCS) commit(ctx context.Context, version Version) (*object.Commit, e
|
|||||||
err = repo.FetchContext(ctx, &git.FetchOptions{
|
err = repo.FetchContext(ctx, &git.FetchOptions{
|
||||||
RemoteName: remoteName,
|
RemoteName: remoteName,
|
||||||
Auth: auth,
|
Auth: auth,
|
||||||
|
Tags: git.AllTags,
|
||||||
})
|
})
|
||||||
if err != nil && err != git.NoErrAlreadyUpToDate {
|
if err != nil && err != git.NoErrAlreadyUpToDate {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
tagPrefix := ""
|
||||||
|
if g.prefix != "" {
|
||||||
|
tagPrefix = g.prefix + "/"
|
||||||
|
}
|
||||||
|
|
||||||
version = Version(strings.TrimSuffix(string(version), "+incompatible"))
|
version = Version(strings.TrimSuffix(string(version), "+incompatible"))
|
||||||
hash := version.Hash()
|
hash := version.Hash()
|
||||||
@ -199,7 +295,7 @@ func (g *gitVCS) commit(ctx context.Context, version Version) (*object.Commit, e
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
tags.ForEach(func(t *plumbing.Reference) error {
|
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()
|
hash = t.Hash().String()
|
||||||
annotated, err := repo.TagObject(t.Hash())
|
annotated, err := repo.TagObject(t.Hash())
|
||||||
if err == nil {
|
if err == nil {
|
||||||
@ -227,7 +323,7 @@ func (g *gitVCS) commit(ctx context.Context, version Version) (*object.Commit, e
|
|||||||
|
|
||||||
func (g *gitVCS) authMethod() (transport.AuthMethod, error) {
|
func (g *gitVCS) authMethod() (transport.AuthMethod, error) {
|
||||||
if g.auth.Key != "" {
|
if g.auth.Key != "" {
|
||||||
return ssh.NewPublicKeysFromFile("git", g.auth.Key, "")
|
return ssh.NewPublicKeysFromFile("git", g.auth.Key, g.auth.Password)
|
||||||
} else if g.auth.Username != "" {
|
} else if g.auth.Username != "" {
|
||||||
return &http.BasicAuth{Username: g.auth.Username, Password: g.auth.Password}, nil
|
return &http.BasicAuth{Username: g.auth.Username, Password: g.auth.Password}, nil
|
||||||
}
|
}
|
||||||
|
@ -10,6 +10,7 @@ import (
|
|||||||
"io"
|
"io"
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
"sort"
|
"sort"
|
||||||
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -52,6 +53,20 @@ func TestGit(t *testing.T) {
|
|||||||
Timestamp: "2018-09-21",
|
Timestamp: "2018-09-21",
|
||||||
Checksum: "YIusKhLwlEKiuwowBFdEElpP0hIGDOCaSnQaMufLB00=",
|
Checksum: "YIusKhLwlEKiuwowBFdEElpP0hIGDOCaSnQaMufLB00=",
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
// Repository that contains go.mod
|
||||||
|
Module: "bitbucket.org/gomodproxytest/nested",
|
||||||
|
Tag: "v1.0.0",
|
||||||
|
Timestamp: "2018-10-18",
|
||||||
|
Checksum: "rFPd/yPMmhjy4FdmLTySAHVcjHmZh/XP3xNrHKLiFss=",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
// Submodule that contains its own go.mod and tag
|
||||||
|
Module: "bitbucket.org/gomodproxytest/nested/child",
|
||||||
|
Tag: "v1.0.0",
|
||||||
|
Timestamp: "2018-10-18",
|
||||||
|
Checksum: "RN9U68h7BnmrsLP24VM37/Zeyb+21R8KmGZtxTUBd74=",
|
||||||
|
},
|
||||||
{
|
{
|
||||||
// Just a frequently used module from github
|
// Just a frequently used module from github
|
||||||
Module: "github.com/pkg/errors",
|
Module: "github.com/pkg/errors",
|
||||||
@ -59,12 +74,19 @@ func TestGit(t *testing.T) {
|
|||||||
Timestamp: "2016-09-29",
|
Timestamp: "2016-09-29",
|
||||||
Checksum: "WdK/asTD0HN+q6hsWO3/vpuAkAr+tw6aNJNDFFf0+qw=",
|
Checksum: "WdK/asTD0HN+q6hsWO3/vpuAkAr+tw6aNJNDFFf0+qw=",
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
// A module with symlinks, should match Go 1.11.4 algorithm fix
|
||||||
|
Module: "github.com/hashicorp/go-rootcerts",
|
||||||
|
Tag: "v0.0.0-20160503143440-6bb64b370b90",
|
||||||
|
Timestamp: "2016-05-03",
|
||||||
|
Checksum: "VBj0QYQ0u2MCJzBfeYXGexnAl17GsH1yidnoxCqqD9E=",
|
||||||
|
},
|
||||||
} {
|
} {
|
||||||
if test.Module == "" {
|
if test.Module == "" {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
auth := NoAuth()
|
auth := NoAuth()
|
||||||
if test.Tag != "" {
|
if test.Tag != "" && !strings.HasPrefix(test.Tag, "v0.0.0-") {
|
||||||
t.Run(test.Module+"/List", func(t *testing.T) {
|
t.Run(test.Module+"/List", func(t *testing.T) {
|
||||||
git := NewGit(t.Log, "", test.Module, auth)
|
git := NewGit(t.Log, "", test.Module, auth)
|
||||||
list, err := git.List(context.Background())
|
list, err := git.List(context.Background())
|
||||||
|
87
pkg/vcs/gomod.go
Normal file
87
pkg/vcs/gomod.go
Normal file
@ -0,0 +1,87 @@
|
|||||||
|
package vcs
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"context"
|
||||||
|
"encoding/json"
|
||||||
|
"io"
|
||||||
|
"io/ioutil"
|
||||||
|
"os"
|
||||||
|
"os/exec"
|
||||||
|
"path/filepath"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
type goVCS struct {
|
||||||
|
dir string
|
||||||
|
log logger
|
||||||
|
module string
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewGoMod(l logger, module string) VCS {
|
||||||
|
return &goVCS{log: l, module: module, dir: "/tmp/_go"}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (g *goVCS) List(ctx context.Context) ([]Version, error) {
|
||||||
|
if err := g.download(ctx, "latest"); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
b, err := g.file("list")
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
versions := []Version{}
|
||||||
|
for _, line := range strings.Split(string(b), "\n") {
|
||||||
|
versions = append(versions, Version(line))
|
||||||
|
}
|
||||||
|
return versions, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (g *goVCS) Timestamp(ctx context.Context, version Version) (time.Time, error) {
|
||||||
|
if err := g.download(ctx, version.String()); err != nil {
|
||||||
|
return time.Time{}, err
|
||||||
|
}
|
||||||
|
b, err := g.file(version.String() + ".info")
|
||||||
|
if err != nil {
|
||||||
|
return time.Time{}, err
|
||||||
|
}
|
||||||
|
info := struct {
|
||||||
|
Version string
|
||||||
|
Time time.Time
|
||||||
|
}{}
|
||||||
|
if json.Unmarshal(b, &info) == nil {
|
||||||
|
return info.Time, nil
|
||||||
|
}
|
||||||
|
if t, err := time.Parse(time.RFC3339, string(b)); err == nil {
|
||||||
|
return t, nil
|
||||||
|
}
|
||||||
|
if sec, err := strconv.ParseInt(string(b), 10, 64); err == nil {
|
||||||
|
return time.Unix(sec, 0), nil
|
||||||
|
}
|
||||||
|
return time.Time{}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (g *goVCS) Zip(ctx context.Context, version Version) (io.ReadCloser, error) {
|
||||||
|
if err := g.download(ctx, version.String()); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
b, err := g.file(version.String() + ".zip")
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return ioutil.NopCloser(bytes.NewReader(b)), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (g *goVCS) download(ctx context.Context, version string) error {
|
||||||
|
cmd := exec.Command("go", "mod", "download", g.module+"@"+version)
|
||||||
|
cmd.Env = append(os.Environ(), "GOPATH="+g.dir)
|
||||||
|
cmd.Stderr = os.Stderr
|
||||||
|
return cmd.Run()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (g *goVCS) file(name string) ([]byte, error) {
|
||||||
|
path := filepath.Join(g.dir, "pkg", "mod", "cache", "download", encodeBangs(g.module), "@v", name)
|
||||||
|
return ioutil.ReadFile(path)
|
||||||
|
}
|
@ -13,15 +13,20 @@ var (
|
|||||||
errMetaNotFound = errors.New("go-import meta tag not found")
|
errMetaNotFound = errors.New("go-import meta tag not found")
|
||||||
)
|
)
|
||||||
|
|
||||||
// MetaImports resolved module import path for certain hosts using the special <meta> tag.
|
func RepoRoot(ctx context.Context, module string) (root string, path string, err error) {
|
||||||
func MetaImports(ctx context.Context, module string) (string, error) {
|
// For common VCS hosters we can figure out repo root by the URL
|
||||||
if strings.HasPrefix(module, "github.com/") || strings.HasPrefix(module, "bitbucket.org/") {
|
if strings.HasPrefix(module, "github.com/") || strings.HasPrefix(module, "bitbucket.org/") {
|
||||||
return module, nil
|
parts := strings.Split(module, "/")
|
||||||
|
if len(parts) < 3 {
|
||||||
|
return "", "", errors.New("bad module name")
|
||||||
|
}
|
||||||
|
return strings.Join(parts[0:3], "/"), strings.Join(parts[3:], "/"), nil
|
||||||
}
|
}
|
||||||
|
// Otherwise we shall make a `?go-get=1` HTTP request
|
||||||
// TODO: use context
|
// TODO: use context
|
||||||
res, err := http.Get("https://" + module + "?go-get=1")
|
res, err := http.Get("https://" + module + "?go-get=1")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", err
|
return "", "", err
|
||||||
}
|
}
|
||||||
defer res.Body.Close()
|
defer res.Body.Close()
|
||||||
html := struct {
|
html := struct {
|
||||||
@ -38,21 +43,19 @@ func MetaImports(ctx context.Context, module string) (string, error) {
|
|||||||
dec.AutoClose = xml.HTMLAutoClose
|
dec.AutoClose = xml.HTMLAutoClose
|
||||||
dec.Entity = xml.HTMLEntity
|
dec.Entity = xml.HTMLEntity
|
||||||
if err := dec.Decode(&html); err != nil {
|
if err := dec.Decode(&html); err != nil {
|
||||||
return "", err
|
return "", "", err
|
||||||
}
|
}
|
||||||
for _, meta := range html.Head.Meta {
|
for _, meta := range html.Head.Meta {
|
||||||
if meta.Name == "go-import" {
|
if meta.Name == "go-import" {
|
||||||
if f := strings.Fields(meta.Content); len(f) == 3 {
|
if f := strings.Fields(meta.Content); len(f) == 3 {
|
||||||
if f[0] != module {
|
|
||||||
return "", errPrefixDoesNotMatch
|
|
||||||
}
|
|
||||||
url := f[2]
|
url := f[2]
|
||||||
if i := strings.Index(url, "://"); i >= 0 {
|
if i := strings.Index(url, "://"); i >= 0 {
|
||||||
url = url[i+3:]
|
url = url[i+3:]
|
||||||
}
|
}
|
||||||
return url, nil
|
path = strings.TrimPrefix(strings.TrimPrefix(module, f[0]), "/")
|
||||||
|
return url, path, nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return "", errMetaNotFound
|
return "", "", errMetaNotFound
|
||||||
}
|
}
|
||||||
|
@ -10,7 +10,7 @@ import (
|
|||||||
"testing"
|
"testing"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestMetaImports(t *testing.T) {
|
func TestRepoRoot(t *testing.T) {
|
||||||
var hostname string
|
var hostname string
|
||||||
http.DefaultTransport.(*http.Transport).TLSClientConfig = &tls.Config{InsecureSkipVerify: true}
|
http.DefaultTransport.(*http.Transport).TLSClientConfig = &tls.Config{InsecureSkipVerify: true}
|
||||||
ts := httptest.NewTLSServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
ts := httptest.NewTLSServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
@ -31,39 +31,47 @@ func TestMetaImports(t *testing.T) {
|
|||||||
defer ts.Close()
|
defer ts.Close()
|
||||||
hostname = strings.TrimPrefix(ts.URL, "https://")
|
hostname = strings.TrimPrefix(ts.URL, "https://")
|
||||||
|
|
||||||
if url, err := MetaImports(context.Background(), hostname+"/foo/bar"); err != nil {
|
if root, path, err := RepoRoot(context.Background(), hostname+"/foo/bar"); err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
} else if url != "example.com/foo/bar" {
|
} else if root != "example.com/foo/bar" {
|
||||||
t.Fatal(url)
|
t.Fatal(root)
|
||||||
|
} else if path != "" {
|
||||||
|
t.Fatal(path)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestMetaImportsExternal(t *testing.T) {
|
func TestRepoRootExternal(t *testing.T) {
|
||||||
if testing.Short() {
|
if testing.Short() {
|
||||||
t.Skip("testing with external VCS might be slow")
|
t.Skip("testing with external VCS might be slow")
|
||||||
}
|
}
|
||||||
for _, test := range []struct {
|
for _, test := range []struct {
|
||||||
Pkg string
|
Pkg string
|
||||||
URL string
|
Root string
|
||||||
|
Path string
|
||||||
}{
|
}{
|
||||||
// Common VCS should be resolved immediately without any checks
|
// Common VCS should be resolved immediately without any checks
|
||||||
{Pkg: "github.com/user/repo", URL: "github.com/user/repo"},
|
{Pkg: "github.com/user/repo", Root: "github.com/user/repo", Path: ""},
|
||||||
{Pkg: "bitbucket.org/user/repo", URL: "bitbucket.org/user/repo"},
|
{Pkg: "bitbucket.org/user/repo", Root: "bitbucket.org/user/repo", Path: ""},
|
||||||
|
{Pkg: "github.com/user/repo/sub/dir", Root: "github.com/user/repo", Path: "sub/dir"},
|
||||||
|
{Pkg: "bitbucket.org/user/repo/sub/dir", Root: "bitbucket.org/user/repo", Path: "sub/dir"},
|
||||||
// Otherwise, HTML meta tag should be checked
|
// Otherwise, HTML meta tag should be checked
|
||||||
{Pkg: "golang.org/x/sys", URL: "go.googlesource.com/sys"},
|
{Pkg: "golang.org/x/sys", Root: "go.googlesource.com/sys", Path: ""},
|
||||||
{Pkg: "gopkg.in/warnings.v0", URL: "gopkg.in/warnings.v0"},
|
{Pkg: "golang.org/x/sys/unix", Root: "go.googlesource.com/sys", Path: "unix"},
|
||||||
{Pkg: "gopkg.in/src-d/go-git.v4", URL: "gopkg.in/src-d/go-git.v4"},
|
{Pkg: "golang.org/x/net/websocket", Root: "go.googlesource.com/net", Path: "websocket"},
|
||||||
|
{Pkg: "gopkg.in/warnings.v0", Root: "gopkg.in/warnings.v0", Path: ""},
|
||||||
|
{Pkg: "gopkg.in/src-d/go-git.v4", Root: "gopkg.in/src-d/go-git.v4", Path: ""},
|
||||||
// On errors URL should be empty and error should be not nil
|
// On errors URL should be empty and error should be not nil
|
||||||
{Pkg: "google.com/foo", URL: ""},
|
{Pkg: "google.com/foo", Root: "", Path: ""},
|
||||||
{Pkg: "golang.org/x/sys/unix", URL: ""},
|
{Pkg: "example.com/foo", Root: "", Path: ""},
|
||||||
{Pkg: "example.com/foo", URL: ""},
|
{Pkg: "foo/bar", Root: "", Path: ""},
|
||||||
{Pkg: "foo/bar", URL: ""},
|
|
||||||
} {
|
} {
|
||||||
url, err := MetaImports(context.Background(), test.Pkg)
|
root, path, err := RepoRoot(context.Background(), test.Pkg)
|
||||||
if url != test.URL {
|
if root != test.Root {
|
||||||
t.Fatal(test, url, err)
|
t.Fatal(test, root, err)
|
||||||
|
} else if path != test.Path {
|
||||||
|
t.Fatal(test, path, err)
|
||||||
}
|
}
|
||||||
if url == "" && err == nil {
|
if root == "" && path == "" && err == nil {
|
||||||
t.Fatal(test, "error should be set if module import can not be resolved")
|
t.Fatal(test, "error should be set if module import can not be resolved")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
146
pkg/vcs/tags.go
Normal file
146
pkg/vcs/tags.go
Normal file
@ -0,0 +1,146 @@
|
|||||||
|
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)
|
||||||
|
}
|
||||||
|
v.wrapped.log("taggableVCS.Tag", "version", semVer, "short", short)
|
||||||
|
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.
|
||||||
|
allVersions := appendEphemeralVersion(remoteVersions, tags...)
|
||||||
|
// TODO(bilus): BUG - tag version 1.0.0 and then 0.1.0 - 0.1.0 is the "latest".
|
||||||
|
// sort.Slice(allVersions, func(i, j) bool { return allVersions[i].Before(allVersions[j]) })
|
||||||
|
return allVersions, 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
|
||||||
|
}
|
@ -27,6 +27,11 @@ func (v Version) Hash() string {
|
|||||||
return fields[2]
|
return fields[2]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// String returns a string representation of a version
|
||||||
|
func (v Version) String() string {
|
||||||
|
return string(v)
|
||||||
|
}
|
||||||
|
|
||||||
// Module is a source code snapshot for which one can get the commit timestamp
|
// Module is a source code snapshot for which one can get the commit timestamp
|
||||||
// or the actual ZIP with the source code in it.
|
// or the actual ZIP with the source code in it.
|
||||||
type Module interface {
|
type Module interface {
|
||||||
@ -56,4 +61,4 @@ func NoAuth() Auth { return Auth{} }
|
|||||||
func Password(username, password string) Auth { return Auth{Username: username, Password: password} }
|
func Password(username, password string) Auth { return Auth{Username: username, Password: password} }
|
||||||
|
|
||||||
// Key returns an Auth implementation that uses key file authentication mechanism.
|
// Key returns an Auth implementation that uses key file authentication mechanism.
|
||||||
func Key(key string) Auth { return Auth{Key: key} }
|
func Key(key, password string) Auth { return Auth{Key: key, Password: password} }
|
||||||
|
Loading…
x
Reference in New Issue
Block a user