gemvault 0.1.5 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/.containerignore +10 -0
- data/.rubocop.yml +8 -1
- data/CHANGELOG.md +30 -0
- data/CLAUDE.md +5 -4
- data/Dockerfile.test +11 -1
- data/README.md +3 -11
- data/Rakefile +9 -0
- data/issues.rec +540 -17
- data/lib/bundler/plugin/vault_source.rb +48 -73
- data/lib/bundler/plugin/vaulted_gem.rb +66 -0
- data/lib/gemvault/archive_entry.rb +6 -0
- data/lib/gemvault/cli/command.rb +9 -5
- data/lib/gemvault/cli/commands/extract.rb +11 -11
- data/lib/gemvault/cli/commands/new.rb +3 -2
- data/lib/gemvault/cli/commands/remove.rb +15 -5
- data/lib/gemvault/cli/commands/upgrade.rb +37 -13
- data/lib/gemvault/dbvault.rb +12 -9
- data/lib/gemvault/deprecation.rb +26 -24
- data/lib/gemvault/gem_entry.rb +20 -24
- data/lib/gemvault/gem_extraction.rb +19 -23
- data/lib/gemvault/gem_reference/any_version.rb +6 -2
- data/lib/gemvault/gem_reference/parser.rb +44 -0
- data/lib/gemvault/gem_reference/specific_version.rb +9 -25
- data/lib/gemvault/gem_reference.rb +13 -45
- data/lib/gemvault/manifest.rb +44 -42
- data/lib/gemvault/tarball.rb +53 -0
- data/lib/gemvault/tarvault.rb +47 -56
- data/lib/gemvault/vault.rb +17 -13
- data/lib/gemvault/vault_path.rb +33 -0
- data/lib/gemvault/vault_upgrade.rb +31 -24
- data/lib/gemvault/version.rb +1 -1
- data/lib/gemvault.rb +1 -0
- data/lib/rubygems/resolver/vault_set.rb +5 -7
- data/lib/rubygems/source/vault.rb +28 -32
- data/lib/rubygems_plugin.rb +8 -6
- metadata +7 -16
- data/lib/gemvault/tar_archive.rb +0 -66
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
require "uri"
|
|
2
|
+
require "pathname"
|
|
3
|
+
|
|
4
|
+
module Gemvault
|
|
5
|
+
##
|
|
6
|
+
# Translates vault locators into filesystem paths.
|
|
7
|
+
#
|
|
8
|
+
# A locator is whatever a user hands the CLI or a package manager as "the
|
|
9
|
+
# vault": a plain filesystem path, a <tt>file://</tt> URI, or a
|
|
10
|
+
# <tt>vault://</tt> URI. Anything else passes through unchanged.
|
|
11
|
+
module VaultPath
|
|
12
|
+
SCHEMES = ["file", "vault"].freeze
|
|
13
|
+
|
|
14
|
+
module_function
|
|
15
|
+
|
|
16
|
+
# :call-seq:
|
|
17
|
+
# resolve(locator) -> Pathname
|
|
18
|
+
#
|
|
19
|
+
# Resolves +locator+ to a Pathname. Two-slash relative forms such as
|
|
20
|
+
# <tt>file://vault.gemv</tt> parse their first segment as a URI host, so
|
|
21
|
+
# host and path are rejoined.
|
|
22
|
+
def resolve(locator)
|
|
23
|
+
begin
|
|
24
|
+
uri = URI.parse(locator.to_s)
|
|
25
|
+
return Pathname(locator.to_s) unless SCHEMES.include?(uri.scheme)
|
|
26
|
+
|
|
27
|
+
Pathname([uri.host, uri.path].compact.join)
|
|
28
|
+
rescue URI::InvalidURIError
|
|
29
|
+
Pathname(locator.to_s)
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
end
|
|
33
|
+
end
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
require "pathname"
|
|
1
2
|
require "fileutils"
|
|
2
3
|
require_relative "vault"
|
|
3
4
|
require_relative "deprecation"
|
|
@@ -7,20 +8,33 @@ module Gemvault
|
|
|
7
8
|
# existing backend and rewriting it through the current-format writer, then
|
|
8
9
|
# atomically swapping the file into place. Preserves each gem's created_at.
|
|
9
10
|
class VaultUpgrade
|
|
11
|
+
include FileUtils
|
|
12
|
+
|
|
10
13
|
# The from/to/gem-count summary of an upgrade; also detects an
|
|
11
14
|
# already-current (no-op) vault.
|
|
12
|
-
Plan
|
|
13
|
-
def no_op?
|
|
14
|
-
|
|
15
|
+
class Plan < Data.define(:from_version, :to_version, :gem_count)
|
|
16
|
+
def no_op? = from_version == to_version
|
|
17
|
+
|
|
18
|
+
def to_s = "format #{from_version} -> #{to_version}"
|
|
19
|
+
|
|
20
|
+
def deconstruct_keys(_keys)
|
|
21
|
+
{ from_version:, to_version:, gem_count:, no_op: no_op? }
|
|
15
22
|
end
|
|
23
|
+
end
|
|
16
24
|
|
|
17
|
-
|
|
18
|
-
|
|
25
|
+
# Copies gems from a source vault into a target vault, preserving each
|
|
26
|
+
# gem's stored timestamp. Holds the two endpoints so the per-gem call
|
|
27
|
+
# takes only the entry.
|
|
28
|
+
class GemCopy < Data.define(:source, :target)
|
|
29
|
+
def call(entry)
|
|
30
|
+
source.with_gem_file(entry) do |gem_path|
|
|
31
|
+
target.add(gem_path, created_at: entry.created_at)
|
|
32
|
+
end
|
|
19
33
|
end
|
|
20
34
|
end
|
|
21
35
|
|
|
22
36
|
def initialize(path, backup: true)
|
|
23
|
-
@path =
|
|
37
|
+
@path = Pathname(path).expand_path
|
|
24
38
|
@backup = backup
|
|
25
39
|
end
|
|
26
40
|
|
|
@@ -44,43 +58,36 @@ module Gemvault
|
|
|
44
58
|
private
|
|
45
59
|
|
|
46
60
|
def backup!
|
|
47
|
-
backup_path = "#{@path}.bak"
|
|
48
|
-
if
|
|
49
|
-
raise Vault::Error, "Backup already exists: #{backup_path} (remove it or pass --no-backup)"
|
|
50
|
-
end
|
|
61
|
+
backup_path = Pathname("#{@path}.bak")
|
|
62
|
+
raise Vault::Error, "Backup already exists: #{backup_path} (remove it or pass --no-backup)" if backup_path.exist?
|
|
51
63
|
|
|
52
|
-
|
|
64
|
+
cp(@path, backup_path)
|
|
53
65
|
end
|
|
54
66
|
|
|
55
67
|
def rebuild(expected_count)
|
|
56
|
-
tmp = "#{@path}.upgrading"
|
|
57
|
-
|
|
68
|
+
tmp = Pathname("#{@path}.upgrading")
|
|
69
|
+
rm_f(tmp)
|
|
58
70
|
copy_current_format(tmp)
|
|
59
|
-
verify_count!(tmp
|
|
60
|
-
|
|
71
|
+
verify_count!(tmp:, expected_count:)
|
|
72
|
+
tmp.rename(@path)
|
|
61
73
|
end
|
|
62
74
|
|
|
63
75
|
def copy_current_format(tmp)
|
|
64
76
|
Deprecation.silence do
|
|
65
77
|
Vault.open(@path) do |old|
|
|
66
78
|
target = Vault.new(tmp, create: true)
|
|
67
|
-
|
|
79
|
+
copy = GemCopy.new(source: old, target:)
|
|
80
|
+
old.gem_entries.each { |entry| copy.call(entry) }
|
|
68
81
|
target.close
|
|
69
82
|
end
|
|
70
83
|
end
|
|
71
84
|
end
|
|
72
85
|
|
|
73
|
-
def
|
|
74
|
-
old.with_gem_file(entry.name, entry.version, platform: entry.platform) do |gem_path|
|
|
75
|
-
target.add(gem_path, created_at: entry.created_at)
|
|
76
|
-
end
|
|
77
|
-
end
|
|
78
|
-
|
|
79
|
-
def verify_count!(tmp, expected_count)
|
|
86
|
+
def verify_count!(tmp:, expected_count:)
|
|
80
87
|
actual = Vault.open(tmp, &:size)
|
|
81
88
|
return if actual == expected_count
|
|
82
89
|
|
|
83
|
-
|
|
90
|
+
rm_f(tmp)
|
|
84
91
|
raise Vault::Error, "Upgrade verification failed: expected #{expected_count} gems, got #{actual}"
|
|
85
92
|
end
|
|
86
93
|
end
|
data/lib/gemvault/version.rb
CHANGED
data/lib/gemvault.rb
CHANGED
|
@@ -11,7 +11,7 @@ class Gem::Resolver::VaultSet < Gem::Resolver::Set
|
|
|
11
11
|
end
|
|
12
12
|
|
|
13
13
|
def find_all(req)
|
|
14
|
-
@specs.filter_map { |
|
|
14
|
+
@specs.filter_map { |candidate| index_spec_for(candidate) if req.match?(candidate) }
|
|
15
15
|
end
|
|
16
16
|
|
|
17
17
|
def prefetch(reqs); end
|
|
@@ -22,19 +22,17 @@ class Gem::Resolver::VaultSet < Gem::Resolver::Set
|
|
|
22
22
|
|
|
23
23
|
pp.breakable
|
|
24
24
|
|
|
25
|
-
pp.seplist @specs do |
|
|
26
|
-
pp.text
|
|
25
|
+
pp.seplist @specs do |candidate|
|
|
26
|
+
pp.text candidate.full_name
|
|
27
27
|
end
|
|
28
28
|
end
|
|
29
29
|
end
|
|
30
30
|
|
|
31
31
|
private
|
|
32
32
|
|
|
33
|
-
def index_spec_for(
|
|
34
|
-
return unless req.match?(tuple)
|
|
35
|
-
|
|
33
|
+
def index_spec_for(candidate)
|
|
36
34
|
Gem::Resolver::IndexSpecification.new(
|
|
37
|
-
self,
|
|
35
|
+
self, candidate.name, candidate.version, @source, candidate.platform
|
|
38
36
|
)
|
|
39
37
|
end
|
|
40
38
|
end
|
|
@@ -1,8 +1,11 @@
|
|
|
1
1
|
require "gemvault/vault"
|
|
2
|
-
require "
|
|
2
|
+
require "gemvault/vault_path"
|
|
3
|
+
require "gemvault/gem_entry"
|
|
4
|
+
require "pathname"
|
|
3
5
|
|
|
4
6
|
##
|
|
5
|
-
# A source backed by a .gemv vault file
|
|
7
|
+
# A source backed by a .gemv vault file: a tar archive of .gem files indexed
|
|
8
|
+
# by a manifest. Legacy SQLite vaults are read transparently.
|
|
6
9
|
#
|
|
7
10
|
# Used by the gemvault RubyGems plugin to support:
|
|
8
11
|
#
|
|
@@ -10,12 +13,11 @@ require "uri"
|
|
|
10
13
|
class Gem::Source::Vault < Gem::Source
|
|
11
14
|
include Gem::UserInteraction
|
|
12
15
|
|
|
13
|
-
VAULT_URI_SCHEMES = %w[file vault].freeze
|
|
14
|
-
|
|
15
16
|
attr_reader :path
|
|
16
17
|
|
|
17
18
|
def initialize(path)
|
|
18
|
-
|
|
19
|
+
resolved = Gemvault::VaultPath.resolve(path)
|
|
20
|
+
@path = Pathname(resolved).expand_path.to_s
|
|
19
21
|
super(@path)
|
|
20
22
|
@uri = @path
|
|
21
23
|
@specs = nil
|
|
@@ -24,7 +26,7 @@ class Gem::Source::Vault < Gem::Source
|
|
|
24
26
|
def load_specs(type)
|
|
25
27
|
verbose "Loading #{type} specs from vault at #{@path}"
|
|
26
28
|
ensure_specs_loaded
|
|
27
|
-
|
|
29
|
+
select_candidates(type)
|
|
28
30
|
end
|
|
29
31
|
|
|
30
32
|
def fetch_spec(name_tuple)
|
|
@@ -38,17 +40,15 @@ class Gem::Source::Vault < Gem::Source
|
|
|
38
40
|
|
|
39
41
|
def download(spec, dir = Dir.pwd)
|
|
40
42
|
verbose "Extracting #{spec.file_name} from vault at #{@path}"
|
|
41
|
-
|
|
42
|
-
FileUtils.mkdir_p(cache_dir)
|
|
43
|
-
|
|
44
|
-
dest = File.join(cache_dir, spec.file_name)
|
|
43
|
+
dest = Pathname(dir).join("cache").tap(&:mkpath).join(spec.file_name)
|
|
45
44
|
|
|
46
45
|
Gemvault::Vault.open(@path) do |vault|
|
|
47
|
-
|
|
48
|
-
|
|
46
|
+
entry = entry_for(spec)
|
|
47
|
+
bytes = vault.gem_data(entry)
|
|
48
|
+
dest.binwrite(bytes)
|
|
49
49
|
end
|
|
50
50
|
|
|
51
|
-
dest
|
|
51
|
+
dest.to_s
|
|
52
52
|
end
|
|
53
53
|
|
|
54
54
|
def dependency_resolver_set(prerelease = nil)
|
|
@@ -91,35 +91,32 @@ class Gem::Source::Vault < Gem::Source
|
|
|
91
91
|
|
|
92
92
|
private
|
|
93
93
|
|
|
94
|
-
def
|
|
95
|
-
|
|
96
|
-
VAULT_URI_SCHEMES.include?(uri.scheme) ? uri.path : path.to_s
|
|
97
|
-
rescue URI::InvalidURIError
|
|
98
|
-
path.to_s
|
|
94
|
+
def entry_for(spec)
|
|
95
|
+
Gemvault::GemEntry.from_spec(spec)
|
|
99
96
|
end
|
|
100
97
|
|
|
101
|
-
def
|
|
98
|
+
def select_candidates(type)
|
|
102
99
|
case type
|
|
103
|
-
when :released then
|
|
104
|
-
when :prerelease then
|
|
105
|
-
when :latest then
|
|
100
|
+
when :released then released_candidates
|
|
101
|
+
when :prerelease then prerelease_candidates
|
|
102
|
+
when :latest then latest_candidates
|
|
106
103
|
else @specs.keys
|
|
107
104
|
end
|
|
108
105
|
end
|
|
109
106
|
|
|
110
|
-
def
|
|
111
|
-
@specs.keys.reject { |
|
|
107
|
+
def released_candidates
|
|
108
|
+
@specs.keys.reject { |candidate| candidate.version.prerelease? }
|
|
112
109
|
end
|
|
113
110
|
|
|
114
|
-
def
|
|
115
|
-
@specs.keys.select { |
|
|
111
|
+
def prerelease_candidates
|
|
112
|
+
@specs.keys.select { |candidate| candidate.version.prerelease? }
|
|
116
113
|
end
|
|
117
114
|
|
|
118
|
-
def
|
|
115
|
+
def latest_candidates
|
|
119
116
|
@specs.keys
|
|
120
|
-
.group_by { |
|
|
117
|
+
.group_by { |candidate| [candidate.name, candidate.platform] }
|
|
121
118
|
.values
|
|
122
|
-
.map { |
|
|
119
|
+
.map { |versions| versions.max_by(&:version) }
|
|
123
120
|
end
|
|
124
121
|
|
|
125
122
|
def ensure_specs_loaded
|
|
@@ -128,9 +125,8 @@ class Gem::Source::Vault < Gem::Source
|
|
|
128
125
|
@specs = {}
|
|
129
126
|
Gemvault::Vault.open(@path) do |vault|
|
|
130
127
|
vault.gem_entries.each do |entry|
|
|
131
|
-
spec = vault.spec_from_blob(entry
|
|
132
|
-
|
|
133
|
-
@specs[tuple] = spec
|
|
128
|
+
spec = vault.spec_from_blob(entry)
|
|
129
|
+
@specs[spec.name_tuple] = spec
|
|
134
130
|
end
|
|
135
131
|
end
|
|
136
132
|
end
|
data/lib/rubygems_plugin.rb
CHANGED
|
@@ -31,8 +31,7 @@ module Gemvault
|
|
|
31
31
|
Gem::OptionParser.accept Gem::URI::HTTP do |value|
|
|
32
32
|
next value if value.to_s.end_with?(".gemv")
|
|
33
33
|
|
|
34
|
-
|
|
35
|
-
validate_scheme!(uri, value)
|
|
34
|
+
validate_uri!(value)
|
|
36
35
|
value
|
|
37
36
|
end
|
|
38
37
|
end
|
|
@@ -40,12 +39,15 @@ module Gemvault
|
|
|
40
39
|
private
|
|
41
40
|
|
|
42
41
|
def parse_uri(value)
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
42
|
+
begin
|
|
43
|
+
Gem::URI.parse value
|
|
44
|
+
rescue Gem::URI::InvalidURIError
|
|
45
|
+
raise Gem::OptionParser::InvalidArgument, value
|
|
46
|
+
end
|
|
46
47
|
end
|
|
47
48
|
|
|
48
|
-
def
|
|
49
|
+
def validate_uri!(value)
|
|
50
|
+
uri = parse_uri(value)
|
|
49
51
|
return if VALID_URI_SCHEMES.include?(uri.scheme)
|
|
50
52
|
|
|
51
53
|
schemes = VALID_URI_SCHEMES.map { |scheme| "#{scheme}://" }
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: gemvault
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.
|
|
4
|
+
version: 0.2.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- David Gillis
|
|
@@ -9,20 +9,6 @@ bindir: exe
|
|
|
9
9
|
cert_chain: []
|
|
10
10
|
date: 1980-01-02 00:00:00.000000000 Z
|
|
11
11
|
dependencies:
|
|
12
|
-
- !ruby/object:Gem::Dependency
|
|
13
|
-
name: bundler
|
|
14
|
-
requirement: !ruby/object:Gem::Requirement
|
|
15
|
-
requirements:
|
|
16
|
-
- - ">="
|
|
17
|
-
- !ruby/object:Gem::Version
|
|
18
|
-
version: '2.0'
|
|
19
|
-
type: :runtime
|
|
20
|
-
prerelease: false
|
|
21
|
-
version_requirements: !ruby/object:Gem::Requirement
|
|
22
|
-
requirements:
|
|
23
|
-
- - ">="
|
|
24
|
-
- !ruby/object:Gem::Version
|
|
25
|
-
version: '2.0'
|
|
26
12
|
- !ruby/object:Gem::Dependency
|
|
27
13
|
name: command_kit
|
|
28
14
|
requirement: !ruby/object:Gem::Requirement
|
|
@@ -46,6 +32,7 @@ executables:
|
|
|
46
32
|
extensions: []
|
|
47
33
|
extra_rdoc_files: []
|
|
48
34
|
files:
|
|
35
|
+
- ".containerignore"
|
|
49
36
|
- ".rspec"
|
|
50
37
|
- ".rubocop.yml"
|
|
51
38
|
- ".ruby-version"
|
|
@@ -64,7 +51,9 @@ files:
|
|
|
64
51
|
- exe/gemvault
|
|
65
52
|
- issues.rec
|
|
66
53
|
- lib/bundler/plugin/vault_source.rb
|
|
54
|
+
- lib/bundler/plugin/vaulted_gem.rb
|
|
67
55
|
- lib/gemvault.rb
|
|
56
|
+
- lib/gemvault/archive_entry.rb
|
|
68
57
|
- lib/gemvault/cli.rb
|
|
69
58
|
- lib/gemvault/cli/command.rb
|
|
70
59
|
- lib/gemvault/cli/commands/add.rb
|
|
@@ -80,11 +69,13 @@ files:
|
|
|
80
69
|
- lib/gemvault/gem_extraction.rb
|
|
81
70
|
- lib/gemvault/gem_reference.rb
|
|
82
71
|
- lib/gemvault/gem_reference/any_version.rb
|
|
72
|
+
- lib/gemvault/gem_reference/parser.rb
|
|
83
73
|
- lib/gemvault/gem_reference/specific_version.rb
|
|
84
74
|
- lib/gemvault/manifest.rb
|
|
85
|
-
- lib/gemvault/
|
|
75
|
+
- lib/gemvault/tarball.rb
|
|
86
76
|
- lib/gemvault/tarvault.rb
|
|
87
77
|
- lib/gemvault/vault.rb
|
|
78
|
+
- lib/gemvault/vault_path.rb
|
|
88
79
|
- lib/gemvault/vault_session.rb
|
|
89
80
|
- lib/gemvault/vault_upgrade.rb
|
|
90
81
|
- lib/gemvault/version.rb
|
data/lib/gemvault/tar_archive.rb
DELETED
|
@@ -1,66 +0,0 @@
|
|
|
1
|
-
require "rubygems/package"
|
|
2
|
-
require "tempfile"
|
|
3
|
-
require "fileutils"
|
|
4
|
-
require_relative "manifest"
|
|
5
|
-
|
|
6
|
-
module Gemvault
|
|
7
|
-
# The tar file backing a Tarvault. Reads named entries and rewrites the
|
|
8
|
-
# whole archive atomically: tar has no index and the leading manifest entry
|
|
9
|
-
# changes size on every mutation, so mutation means a full rewrite.
|
|
10
|
-
class TarArchive
|
|
11
|
-
def initialize(path)
|
|
12
|
-
@path = path
|
|
13
|
-
end
|
|
14
|
-
|
|
15
|
-
def read(name)
|
|
16
|
-
bytes = nil
|
|
17
|
-
each_entry { |entry| bytes = entry.read if entry.full_name == name }
|
|
18
|
-
bytes
|
|
19
|
-
end
|
|
20
|
-
|
|
21
|
-
def gem_pairs
|
|
22
|
-
pairs = []
|
|
23
|
-
each_entry do |entry|
|
|
24
|
-
pairs << [entry.full_name, entry.read] unless entry.full_name == Manifest::FILENAME
|
|
25
|
-
end
|
|
26
|
-
pairs
|
|
27
|
-
end
|
|
28
|
-
|
|
29
|
-
def write(pairs)
|
|
30
|
-
tmp = Tempfile.create(["tarvault", ".tar"], File.dirname(@path))
|
|
31
|
-
write_entries(tmp, pairs)
|
|
32
|
-
tmp.flush
|
|
33
|
-
tmp.fsync
|
|
34
|
-
tmp.close
|
|
35
|
-
File.rename(tmp.path, @path)
|
|
36
|
-
rescue StandardError
|
|
37
|
-
cleanup(tmp)
|
|
38
|
-
raise
|
|
39
|
-
end
|
|
40
|
-
|
|
41
|
-
private
|
|
42
|
-
|
|
43
|
-
def each_entry(&)
|
|
44
|
-
File.open(@path, "rb") do |io|
|
|
45
|
-
Gem::Package::TarReader.new(io) { |reader| reader.each_entry(&) }
|
|
46
|
-
end
|
|
47
|
-
end
|
|
48
|
-
|
|
49
|
-
def write_entries(io, pairs)
|
|
50
|
-
Gem::Package::TarWriter.new(io) do |writer|
|
|
51
|
-
pairs.each { |name, bytes| add_entry(writer, name, bytes) }
|
|
52
|
-
end
|
|
53
|
-
end
|
|
54
|
-
|
|
55
|
-
def add_entry(writer, name, bytes)
|
|
56
|
-
writer.add_file_simple(name, 0o644, bytes.bytesize) { |dest| dest.write(bytes) }
|
|
57
|
-
end
|
|
58
|
-
|
|
59
|
-
def cleanup(tmp)
|
|
60
|
-
return unless tmp
|
|
61
|
-
|
|
62
|
-
tmp.close unless tmp.closed?
|
|
63
|
-
FileUtils.rm_f(tmp.path)
|
|
64
|
-
end
|
|
65
|
-
end
|
|
66
|
-
end
|