Commit 4b568ff0 authored by Ethan Reesor's avatar Ethan Reesor

Refactor Go proxy models

- Reduce coupling between GoModule and VersionFinder
- Do not fetch go.mod when blobs have already been fetched
parent 1129e14c
......@@ -14,7 +14,7 @@ module Packages
def execute
@mod.project.repository.tags
.filter { |tag| semver? tag }
.map { |tag| find_ref tag }
.map { |tag| @mod.version_by(ref: tag) }
.filter { |ver| ver.valid? }
end
......@@ -22,55 +22,23 @@ module Packages
case target
when String
if pseudo_version? target
find_pseudo_version target
semver = parse_semver(target)
commit = pseudo_version_commit(@mod.project, semver)
Packages::GoModuleVersion.new(@mod, :pseudo, commit, name: target, semver: semver)
else
mod.versions.find { |v| v.name == target }
@mod.version_by(ref: target)
end
when Gitlab::Git::Ref
find_ref target
@mod.version_by(ref: target)
when ::Commit, Gitlab::Git::Commit
find_commit target
@mod.version_by(commit: target)
else
raise ArgumentError.new 'not a valid target'
end
end
private
def find_ref(ref)
commit = ref.dereferenced_target
Packages::GoModuleVersion.new(@mod, :ref, commit, ref: ref, semver: parse_semver(ref.name))
end
def find_commit(commit)
Packages::GoModuleVersion.new(@mod, :commit, commit)
end
def find_pseudo_version(str)
semver = parse_semver(str)
raise ArgumentError.new 'target is not a pseudo-version' unless pseudo_version?(semver)
# valid pseudo-versions are
# vX.0.0-yyyymmddhhmmss-sha1337beef0, when no earlier tagged commit exists for X
# vX.Y.Z-pre.0.yyyymmddhhmmss-sha1337beef0, when most recent prior tag is vX.Y.Z-pre
# vX.Y.(Z+1)-0.yyyymmddhhmmss-sha1337beef0, when most recent prior tag is vX.Y.Z
# go discards the timestamp when resolving pseudo-versions, so we will do the same
timestamp, sha = semver.prerelease.split('-').last 2
timestamp = timestamp.split('.').last
commit = @mod.project.repository.commit_by(oid: sha)
# these errors are copied from proxy.golang.org's responses
raise ArgumentError.new 'invalid pseudo-version: unknown commit' unless commit
raise ArgumentError.new 'invalid pseudo-version: revision is shorter than canonical' unless sha.length == 12
raise ArgumentError.new 'invalid pseudo-version: does not match version-control timestamp' unless commit.committed_date.strftime('%Y%m%d%H%M%S') == timestamp
Packages::GoModuleVersion.new(@mod, :pseudo, commit, name: str, semver: semver)
end
end
end
end
......@@ -13,8 +13,19 @@ class Packages::GoModule
@versions ||= Packages::Go::VersionFinder.new(self).execute
end
def find_version(name)
Packages::Go::VersionFinder.new(self).find(name)
def version_by(ref: nil, commit: nil)
raise ArgumentError.new 'no filter specified' unless ref || commit
raise ArgumentError.new 'ref and commit are mutually exclusive' if ref && commit
if commit
return version_by_sha(commit) if commit.is_a? String
return version_by_commit(commit)
end
return version_by_name(ref) if ref.is_a? String
version_by_ref(ref)
end
def path_valid?(major)
......@@ -31,4 +42,42 @@ class Packages::GoModule
def gomod_valid?(gomod)
gomod&.split("\n", 2)&.first == "module #{@name}"
end
private
def version_by_name(name)
# avoid a Gitaly call if possible
if defined?(@versions)
v = @versions.find { |v| v.name == ref }
return v if v
end
ref = @project.repository.find_tag(name) || @project.repository.find_branch(name)
return unless ref
version_by_ref(ref)
end
def version_by_ref(ref)
# reuse existing versions
if defined?(@versions)
v = @versions.find { |v| v.ref == ref }
return v if v
end
commit = ref.dereferenced_target
semver = Packages::SemVer.parse(ref.name, prefixed: true)
Packages::GoModuleVersion.new(self, :ref, commit, ref: ref, semver: semver)
end
def version_by_sha(sha)
commit = @project.commit_by(oid: sha)
return unless ref
version_by_commit(commit)
end
def version_by_commit(commit)
Packages::GoModuleVersion.new(self, :commit, commit)
end
end
......@@ -42,7 +42,14 @@ class Packages::GoModuleVersion
end
def gomod
@gomod ||= blob_at(@mod.path + '/go.mod')
@gomod ||=
if defined?(@blobs)
blob_at(@mod.path + '/go.mod')
elsif @mod.path.empty?
@mod.project.repository.blob_at(@commit.sha, 'go.mod')&.data
else
@mod.project.repository.blob_at(@commit.sha, @mod.path + '/go.mod')&.data
end
end
def archive
......@@ -56,15 +63,12 @@ class Packages::GoModuleVersion
end
end
def files
return @files if defined?(@files)
sha = @commit.sha
tree = @mod.project.repository.tree(sha, @mod.path, recursive: true).entries.filter { |e| e.file? }
nested = tree.filter { |e| e.name == 'go.mod' && !(@mod.path == '' && e.path == 'go.mod' || e.path == @mod.path + '/go.mod') }.map { |e| e.path[0..-7] }
@files = tree.filter { |e| !nested.any? { |n| e.path.start_with? n } }
def valid?
@mod.path_valid?(major) && @mod.gomod_valid?(gomod)
end
private
def blob_at(path)
return if path.nil? || path.empty?
......@@ -73,15 +77,16 @@ class Packages::GoModuleVersion
blobs.find { |x| x.path == path }&.data
end
def valid?
@mod.path_valid?(major) && @mod.gomod_valid?(gomod)
def blobs
@blobs ||= @mod.project.repository.batch_blobs(files.map { |x| [@commit.sha, x.path] })
end
private
def blobs
return @blobs if defined?(@blobs)
def files
return @files if defined?(@files)
@blobs = @mod.project.repository.batch_blobs(files.map { |x| [@commit.sha, x.path] })
sha = @commit.sha
tree = @mod.project.repository.tree(sha, @mod.path, recursive: true).entries.filter { |e| e.file? }
nested = tree.filter { |e| e.name == 'go.mod' && !(@mod.path == '' && e.path == 'go.mod' || e.path == @mod.path + '/go.mod') }.map { |e| e.path[0..-7] }
@files = tree.filter { |e| !nested.any? { |n| e.path.start_with? n } }
end
end
......@@ -42,7 +42,7 @@ module API
def find_version
module_version = case_decode params[:module_version]
ver = find_module.find_version(module_version)
ver = ::Packages::Go::VersionFinder.new(find_module).find(module_version)
not_found! unless ver&.valid?
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment