pray-cli 1.16.0 → 1.18.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/CHANGELOG.md +21 -0
- data/lib/pray/apply_report.rb +49 -0
- data/lib/pray/cli/commands/init.rb +1 -20
- data/lib/pray/cli/commands/init_prayer.rb +76 -0
- data/lib/pray/cli/commands/init_prayer_manifest.rb +102 -0
- data/lib/pray/cli/commands/packages.rb +13 -2
- data/lib/pray/cli/commands/trust.rb +3 -3
- data/lib/pray/cli/commands/update_latest.rb +14 -16
- data/lib/pray/cli/commands/workflow.rb +5 -0
- data/lib/pray/cli/help.rb +52 -15
- data/lib/pray/cli/helpers.rb +13 -5
- data/lib/pray/cli/parse.rb +21 -1
- data/lib/pray/cli.rb +3 -1
- data/lib/pray/destination.rb +32 -2
- data/lib/pray/distribution.rb +94 -0
- data/lib/pray/format_serialize.rb +1 -4
- data/lib/pray/git_cache.rb +192 -0
- data/lib/pray/git_clone.rb +32 -0
- data/lib/pray/git_materialize.rb +76 -0
- data/lib/pray/git_run.rb +41 -0
- data/lib/pray/git_sources.rb +60 -145
- data/lib/pray/local_prayer.rb +38 -0
- data/lib/pray/lockfile.rb +8 -2
- data/lib/pray/manifest.rb +6 -3
- data/lib/pray/manifest_json.rb +1 -0
- data/lib/pray/manifest_parser_blocks.rb +15 -15
- data/lib/pray/materialize.rb +7 -1
- data/lib/pray/package_spec.rb +25 -13
- data/lib/pray/package_spec_render.rb +4 -3
- data/lib/pray/package_spec_version.rb +33 -0
- data/lib/pray/plan.rb +2 -1
- data/lib/pray/publish.rb +27 -2
- data/lib/pray/registry.rb +4 -15
- data/lib/pray/registry_http_cache.rb +17 -0
- data/lib/pray/render.rb +6 -36
- data/lib/pray/render_managed.rb +64 -0
- data/lib/pray/render_patch.rb +63 -12
- data/lib/pray/resolve.rb +6 -37
- data/lib/pray/resolve_local.rb +39 -0
- data/lib/pray/resolve_source.rb +5 -0
- data/lib/pray/serve.rb +15 -4
- data/lib/pray/serve_range.rb +36 -0
- data/lib/pray/torrent_manifest.rb +78 -0
- data/lib/pray/upstream_drift.rb +42 -0
- data/lib/pray/upstream_merge.rb +25 -0
- data/lib/pray/upstream_refresh.rb +28 -2
- data/lib/pray/verify.rb +2 -2
- data/lib/pray/version.rb +2 -2
- data/lib/pray.rb +10 -0
- metadata +17 -1
data/lib/pray/cli.rb
CHANGED
|
@@ -9,6 +9,8 @@ require_relative "cli/help"
|
|
|
9
9
|
require_relative "cli/suggest"
|
|
10
10
|
require_relative "cli/helpers"
|
|
11
11
|
require_relative "cli/commands/init"
|
|
12
|
+
require_relative "cli/commands/init_prayer_manifest"
|
|
13
|
+
require_relative "cli/commands/init_prayer"
|
|
12
14
|
require_relative "cli/commands/workflow"
|
|
13
15
|
require_relative "cli/commands/update_latest"
|
|
14
16
|
require_relative "cli/commands/packages"
|
|
@@ -82,7 +84,7 @@ module Pray
|
|
|
82
84
|
case command
|
|
83
85
|
in [:manifest] then manifest_command
|
|
84
86
|
in [:init, targets] then init_command(targets)
|
|
85
|
-
in [:prayer_init] then prayer_init_command
|
|
87
|
+
in [:prayer_init, name, directory] then prayer_init_command(name, directory)
|
|
86
88
|
in [:repo_init] then repo_init_command
|
|
87
89
|
in [:install, flags] then install_command(flags)
|
|
88
90
|
in [:add, add_args] then add_command(**add_args)
|
data/lib/pray/destination.rb
CHANGED
|
@@ -16,8 +16,7 @@ module Pray
|
|
|
16
16
|
|
|
17
17
|
def local_path_form?(value)
|
|
18
18
|
value.start_with?(".", "/") ||
|
|
19
|
-
value.end_with?(".md", ".txt", ".markdown")
|
|
20
|
-
!value.include?("/")
|
|
19
|
+
value.end_with?(".md", ".txt", ".markdown")
|
|
21
20
|
end
|
|
22
21
|
|
|
23
22
|
def destination_target_name(mode, path)
|
|
@@ -106,6 +105,7 @@ module Pray
|
|
|
106
105
|
if existing.position == "after" && local.position != "after"
|
|
107
106
|
existing.position = local.position
|
|
108
107
|
end
|
|
108
|
+
existing.file ||= local.file
|
|
109
109
|
end
|
|
110
110
|
|
|
111
111
|
def bind_package_entry(target, package_name)
|
|
@@ -154,5 +154,35 @@ module Pray
|
|
|
154
154
|
else false
|
|
155
155
|
end
|
|
156
156
|
end
|
|
157
|
+
|
|
158
|
+
def local_compose_embed?(manifest, local)
|
|
159
|
+
return false if local.file
|
|
160
|
+
|
|
161
|
+
manifest.targets.none? do |target|
|
|
162
|
+
target.mode == "tree" &&
|
|
163
|
+
target.entries.any? { |entry| entry.kind == "local" && entry.path == local.path }
|
|
164
|
+
end
|
|
165
|
+
end
|
|
166
|
+
|
|
167
|
+
def try_apply_local_pray(manifest, path, file_dest, other_package_signal, destination_index)
|
|
168
|
+
return false unless local_path_form?(path)
|
|
169
|
+
|
|
170
|
+
if file_dest
|
|
171
|
+
return false if other_package_signal
|
|
172
|
+
raise Error.parse("manifest", "a local file cannot use file:")
|
|
173
|
+
end
|
|
174
|
+
return false if other_package_signal
|
|
175
|
+
unless destination_index
|
|
176
|
+
raise Error.parse("manifest", "local pray paths are only valid inside compose")
|
|
177
|
+
end
|
|
178
|
+
mode = manifest.targets[destination_index].mode
|
|
179
|
+
unless mode == "compose"
|
|
180
|
+
raise Error.parse("manifest", "local pray paths are only valid inside compose")
|
|
181
|
+
end
|
|
182
|
+
local = ManifestLocal.new(path: path, bound: true)
|
|
183
|
+
bind_local_entry(manifest.targets[destination_index], local.path)
|
|
184
|
+
upsert_local(manifest, local)
|
|
185
|
+
true
|
|
186
|
+
end
|
|
157
187
|
end
|
|
158
188
|
end
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "json"
|
|
4
|
+
require "fileutils"
|
|
5
|
+
|
|
6
|
+
module Pray
|
|
7
|
+
DISTRIBUTION_CONFIG_SPEC = "pray-distribution-config-1"
|
|
8
|
+
TORRENT_PROTOCOL = "torrent"
|
|
9
|
+
DISTRIBUTION_CONFIG_FIELDS = %w[spec protocols bootstrap_trackers enable_dht].freeze
|
|
10
|
+
|
|
11
|
+
RegistryDistributionSettings = Struct.new(
|
|
12
|
+
:spec, :protocols, :bootstrap_trackers, :enable_dht,
|
|
13
|
+
keyword_init: true
|
|
14
|
+
) do
|
|
15
|
+
def initialize(spec: DISTRIBUTION_CONFIG_SPEC, protocols: [], bootstrap_trackers: [], enable_dht: false)
|
|
16
|
+
super
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
def allows_torrent?
|
|
20
|
+
Array(protocols).include?(TORRENT_PROTOCOL)
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
module Distribution
|
|
25
|
+
module_function
|
|
26
|
+
|
|
27
|
+
def default_settings
|
|
28
|
+
RegistryDistributionSettings.new
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
def parse_settings(text)
|
|
32
|
+
data = JSON.parse(text)
|
|
33
|
+
unless data.is_a?(Hash)
|
|
34
|
+
raise Error.parse("distribution config", "expected an object")
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
data.each_key do |key|
|
|
38
|
+
next if DISTRIBUTION_CONFIG_FIELDS.include?(key)
|
|
39
|
+
|
|
40
|
+
raise Error.parse("distribution config", "unsupported field: #{key}")
|
|
41
|
+
end
|
|
42
|
+
settings = RegistryDistributionSettings.new(
|
|
43
|
+
spec: data.fetch("spec", DISTRIBUTION_CONFIG_SPEC),
|
|
44
|
+
protocols: Array(data["protocols"]),
|
|
45
|
+
bootstrap_trackers: Array(data["bootstrap_trackers"]),
|
|
46
|
+
enable_dht: data["enable_dht"] == true
|
|
47
|
+
)
|
|
48
|
+
validate_settings!(settings)
|
|
49
|
+
settings
|
|
50
|
+
rescue JSON::ParserError => error
|
|
51
|
+
raise Error.parse("distribution config", error.message)
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
def read_settings(root)
|
|
55
|
+
path = File.join(root, "v1", "distribution.json")
|
|
56
|
+
return default_settings unless File.file?(path)
|
|
57
|
+
|
|
58
|
+
parse_settings(File.read(path))
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
def write_settings(root, settings = default_settings)
|
|
62
|
+
validate_settings!(settings)
|
|
63
|
+
path = File.join(root, "v1", "distribution.json")
|
|
64
|
+
FileUtils.mkdir_p(File.dirname(path))
|
|
65
|
+
payload = {"spec" => settings.spec, "protocols" => Array(settings.protocols)}
|
|
66
|
+
File.write(path, JSON.pretty_generate(payload))
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
def validate_settings!(settings)
|
|
70
|
+
unless settings.spec == DISTRIBUTION_CONFIG_SPEC
|
|
71
|
+
raise Error.parse("distribution config", "unsupported distribution config spec: #{settings.spec}")
|
|
72
|
+
end
|
|
73
|
+
Array(settings.protocols).each do |name|
|
|
74
|
+
next if name == TORRENT_PROTOCOL
|
|
75
|
+
|
|
76
|
+
raise Error.parse("distribution config", "unsupported protocol: #{name}")
|
|
77
|
+
end
|
|
78
|
+
if settings.enable_dht
|
|
79
|
+
raise Error.parse("distribution config", "DHT announce is not implemented")
|
|
80
|
+
end
|
|
81
|
+
return if Array(settings.bootstrap_trackers).empty? || settings.allows_torrent?
|
|
82
|
+
|
|
83
|
+
raise Error.parse("distribution config", "bootstrap_trackers requires protocols to include torrent")
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
def fetch_settings(source_url)
|
|
87
|
+
parse_settings(Registry.http_get(Registry.join_url(source_url, "v1/distribution.json")))
|
|
88
|
+
rescue Error => error
|
|
89
|
+
return default_settings if error.message.match?(/:\s*404\b/)
|
|
90
|
+
|
|
91
|
+
raise
|
|
92
|
+
end
|
|
93
|
+
end
|
|
94
|
+
end
|
|
@@ -42,10 +42,7 @@ module Pray
|
|
|
42
42
|
path = target.skills.first || ""
|
|
43
43
|
lines << %(tree "#{path}" do)
|
|
44
44
|
target.entries.each do |entry|
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
package = find_package(manifest, entry.name)
|
|
48
|
-
lines << " #{ManifestMethods.format_package_declaration(package)}" if package
|
|
45
|
+
lines << " #{format_destination_entry(entry, manifest)}"
|
|
49
46
|
end
|
|
50
47
|
lines << "end"
|
|
51
48
|
end
|
|
@@ -0,0 +1,192 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "fileutils"
|
|
4
|
+
require "pathname"
|
|
5
|
+
require_relative "error"
|
|
6
|
+
require_relative "hashing"
|
|
7
|
+
require_relative "git_run"
|
|
8
|
+
require_relative "git_clone"
|
|
9
|
+
|
|
10
|
+
module Pray
|
|
11
|
+
module GitCache
|
|
12
|
+
module_function
|
|
13
|
+
|
|
14
|
+
def ensure_git_repository(project_root, clone_url, refresh:, pinned_revision:, sparse_subdir:)
|
|
15
|
+
shared = git_source_cache_directory(project_root, clone_url)
|
|
16
|
+
ensure_shared_git_repository(project_root, clone_url, shared, refresh: refresh, pinned_revision: pinned_revision)
|
|
17
|
+
checkout = git_source_cache_directory(project_root, clone_url, sparse_subdir)
|
|
18
|
+
if checkout != shared
|
|
19
|
+
ensure_linked_worktree(shared, checkout)
|
|
20
|
+
GitClone.apply_sparse_checkout(checkout, sparse_subdir)
|
|
21
|
+
if pinned_revision
|
|
22
|
+
checkout_git_revision(checkout, clone_url, pinned_revision, refresh)
|
|
23
|
+
elsif refresh
|
|
24
|
+
GitRun.run_git(checkout, "reset", "--hard", git_head_revision(shared))
|
|
25
|
+
end
|
|
26
|
+
return [checkout, git_head_revision(checkout)]
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
GitClone.apply_sparse_checkout(shared)
|
|
30
|
+
[shared, git_head_revision(shared)]
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
def git_source_cache_directory(project_root, clone_url, subdir = nil)
|
|
34
|
+
identity = (subdir && !subdir.empty?) ? "#{clone_url}\n#{subdir}" : clone_url
|
|
35
|
+
File.join(project_root, ".pray", "cache", "git", cache_key(identity))
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
def git_source_cached_repository(project_root, clone_url)
|
|
39
|
+
shared = git_source_cache_directory(project_root, clone_url)
|
|
40
|
+
return shared if git_checkout?(shared)
|
|
41
|
+
|
|
42
|
+
cache_root = File.join(project_root, ".pray", "cache", "git")
|
|
43
|
+
return nil unless File.directory?(cache_root)
|
|
44
|
+
|
|
45
|
+
Dir.children(cache_root).each do |name|
|
|
46
|
+
path = File.join(cache_root, name)
|
|
47
|
+
return path if git_checkout?(path) && origin_matches?(path, clone_url)
|
|
48
|
+
end
|
|
49
|
+
nil
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
def git_checkout?(path)
|
|
53
|
+
File.exist?(File.join(path, ".git"))
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
def cache_key(text)
|
|
57
|
+
Hashing.sha256_prefixed(text)[7, 16]
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
def ensure_shared_git_repository(project_root, clone_url, shared, refresh:, pinned_revision:)
|
|
61
|
+
if File.directory?(File.join(shared, ".git"))
|
|
62
|
+
if pinned_revision
|
|
63
|
+
checkout_git_revision(shared, clone_url, pinned_revision, refresh)
|
|
64
|
+
elsif refresh
|
|
65
|
+
refresh_git_worktree(shared, clone_url)
|
|
66
|
+
end
|
|
67
|
+
refresh_global_from_project(clone_url, shared) if refresh
|
|
68
|
+
return
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
FileUtils.rm_rf(shared) if File.exist?(shared)
|
|
72
|
+
FileUtils.mkdir_p(File.dirname(shared))
|
|
73
|
+
seeded = seed_git_cache_from_global(clone_url, shared, project_root)
|
|
74
|
+
if seeded
|
|
75
|
+
GitRun.run_git(shared, "remote", "set-url", "origin", clone_url)
|
|
76
|
+
else
|
|
77
|
+
GitClone.clone_git_cache(project_root, clone_url, shared, quiet: false)
|
|
78
|
+
mirror_git_cache_to_global(clone_url, shared)
|
|
79
|
+
end
|
|
80
|
+
GitClone.apply_sparse_checkout(shared)
|
|
81
|
+
if pinned_revision
|
|
82
|
+
checkout_git_revision(shared, clone_url, pinned_revision, true)
|
|
83
|
+
elsif refresh && seeded
|
|
84
|
+
refresh_git_worktree(shared, clone_url)
|
|
85
|
+
end
|
|
86
|
+
refresh_global_from_project(clone_url, shared) if refresh && seeded
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
def ensure_linked_worktree(shared, checkout)
|
|
90
|
+
return if same_object_store?(shared, checkout)
|
|
91
|
+
|
|
92
|
+
FileUtils.rm_rf(checkout) if File.exist?(checkout)
|
|
93
|
+
FileUtils.mkdir_p(File.dirname(checkout))
|
|
94
|
+
return if GitRun.try_run_git(shared, "worktree", "add", "--detach", "--no-checkout", checkout)
|
|
95
|
+
|
|
96
|
+
GitRun.run_git(shared, "worktree", "add", "--detach", checkout)
|
|
97
|
+
end
|
|
98
|
+
|
|
99
|
+
def same_object_store?(shared, checkout)
|
|
100
|
+
return false unless git_checkout?(checkout)
|
|
101
|
+
|
|
102
|
+
git_common_dir(shared) == git_common_dir(checkout)
|
|
103
|
+
end
|
|
104
|
+
|
|
105
|
+
def git_common_dir(repository)
|
|
106
|
+
output, status = GitRun.capture_git(repository, "rev-parse", "--git-common-dir")
|
|
107
|
+
raise Error.resolution(GitRun.command_error("git rev-parse --git-common-dir", output)) unless status.success?
|
|
108
|
+
|
|
109
|
+
reported = output.strip
|
|
110
|
+
path = Pathname.new(reported).absolute? ? reported : File.expand_path(reported, repository)
|
|
111
|
+
File.realpath(path)
|
|
112
|
+
end
|
|
113
|
+
|
|
114
|
+
def origin_matches?(repository, clone_url)
|
|
115
|
+
output, status = GitRun.capture_git(repository, "remote", "get-url", "origin")
|
|
116
|
+
return false unless status.success?
|
|
117
|
+
|
|
118
|
+
origin = output.strip.delete_prefix("git+")
|
|
119
|
+
origin == clone_url
|
|
120
|
+
end
|
|
121
|
+
|
|
122
|
+
def global_cache_root
|
|
123
|
+
return ENV["PRAY_CACHE"] if ENV["PRAY_CACHE"]
|
|
124
|
+
return File.join(ENV["PRAY_HOME"], "cache") if ENV["PRAY_HOME"]
|
|
125
|
+
|
|
126
|
+
home = ENV["HOME"]
|
|
127
|
+
home ? File.join(home, ".cache", "pray") : nil
|
|
128
|
+
end
|
|
129
|
+
|
|
130
|
+
def global_git_cache_directory(clone_url)
|
|
131
|
+
root = global_cache_root
|
|
132
|
+
root ? File.join(root, "git", cache_key(clone_url)) : nil
|
|
133
|
+
end
|
|
134
|
+
|
|
135
|
+
def global_git_cache_ready?(global_cache)
|
|
136
|
+
File.directory?(File.join(global_cache, ".git")) || File.file?(File.join(global_cache, "HEAD"))
|
|
137
|
+
end
|
|
138
|
+
|
|
139
|
+
def seed_git_cache_from_global(clone_url, destination, working_directory)
|
|
140
|
+
global_cache = global_git_cache_directory(clone_url)
|
|
141
|
+
return false unless global_cache && global_git_cache_ready?(global_cache)
|
|
142
|
+
|
|
143
|
+
GitClone.clone_git_cache(working_directory, global_cache, destination, quiet: true)
|
|
144
|
+
true
|
|
145
|
+
end
|
|
146
|
+
|
|
147
|
+
def mirror_git_cache_to_global(clone_url, project_cache)
|
|
148
|
+
global_cache = global_git_cache_directory(clone_url)
|
|
149
|
+
return unless global_cache
|
|
150
|
+
return if global_git_cache_ready?(global_cache)
|
|
151
|
+
|
|
152
|
+
FileUtils.mkdir_p(File.dirname(global_cache))
|
|
153
|
+
FileUtils.rm_rf(global_cache) if File.exist?(global_cache)
|
|
154
|
+
GitRun.run_git(File.dirname(project_cache), "clone", "--bare", "--quiet", File.basename(project_cache), global_cache)
|
|
155
|
+
end
|
|
156
|
+
|
|
157
|
+
def fetch_origin(repository, revision = nil)
|
|
158
|
+
extra = revision ? [revision] : []
|
|
159
|
+
return if GitRun.try_run_git(repository, "fetch", "--depth", "1", "--filter=blob:none", "origin", *extra)
|
|
160
|
+
|
|
161
|
+
GitRun.run_git(repository, "fetch", "--depth", "1", "origin", *extra)
|
|
162
|
+
end
|
|
163
|
+
|
|
164
|
+
def checkout_git_revision(repository, _clone_url, revision, refresh)
|
|
165
|
+
fetch_origin(repository, revision) if refresh
|
|
166
|
+
GitRun.run_git(repository, "checkout", "--force", revision)
|
|
167
|
+
end
|
|
168
|
+
|
|
169
|
+
def refresh_git_worktree(repository, _clone_url)
|
|
170
|
+
fetch_origin(repository)
|
|
171
|
+
GitRun.run_git(repository, "reset", "--hard", "origin/HEAD")
|
|
172
|
+
end
|
|
173
|
+
|
|
174
|
+
def refresh_global_from_project(clone_url, project_cache)
|
|
175
|
+
global_cache = global_git_cache_directory(clone_url)
|
|
176
|
+
return unless global_cache
|
|
177
|
+
|
|
178
|
+
FileUtils.rm_rf(global_cache) if global_git_cache_ready?(global_cache) || File.exist?(global_cache)
|
|
179
|
+
mirror_git_cache_to_global(clone_url, project_cache)
|
|
180
|
+
end
|
|
181
|
+
|
|
182
|
+
def git_head_revision(repository)
|
|
183
|
+
output, status = GitRun.capture_git(repository, "rev-parse", "HEAD")
|
|
184
|
+
raise Error.resolution(GitRun.command_error("git rev-parse HEAD", output)) unless status.success?
|
|
185
|
+
|
|
186
|
+
revision = output.strip
|
|
187
|
+
raise Error.resolution("git repository has no HEAD revision") if revision.empty?
|
|
188
|
+
|
|
189
|
+
revision
|
|
190
|
+
end
|
|
191
|
+
end
|
|
192
|
+
end
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "git_run"
|
|
4
|
+
|
|
5
|
+
module Pray
|
|
6
|
+
module GitClone
|
|
7
|
+
module_function
|
|
8
|
+
|
|
9
|
+
def clone_git_cache(working_directory, source, destination, quiet:)
|
|
10
|
+
filtered = ["clone", "--depth", "1", "--filter=blob:none", "--sparse", source, destination]
|
|
11
|
+
filtered.insert(1, "--quiet") if quiet
|
|
12
|
+
return if GitRun.try_run_git(working_directory, *filtered)
|
|
13
|
+
|
|
14
|
+
full = ["clone", "--depth", "1", source, destination]
|
|
15
|
+
full.insert(1, "--quiet") if quiet
|
|
16
|
+
GitRun.run_git(working_directory, *full)
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
def apply_sparse_checkout(repository, subdir = nil)
|
|
20
|
+
GitRun.run_git(repository, "sparse-checkout", "init", "--cone")
|
|
21
|
+
GitRun.run_git(repository, "sparse-checkout", "set", *catalog_sparse_cones(subdir))
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
def catalog_sparse_cones(subdir)
|
|
25
|
+
if subdir && !subdir.empty?
|
|
26
|
+
["#{subdir}/v1/packages"]
|
|
27
|
+
else
|
|
28
|
+
["v1/packages", "prayers/v1/packages"]
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
end
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "git_run"
|
|
4
|
+
require_relative "error"
|
|
5
|
+
require_relative "path_safety"
|
|
6
|
+
|
|
7
|
+
module Pray
|
|
8
|
+
module GitMaterialize
|
|
9
|
+
module_function
|
|
10
|
+
|
|
11
|
+
def read_local_artifact_bytes(source_root, artifact)
|
|
12
|
+
if artifact.start_with?("file://")
|
|
13
|
+
path = PathSafety.join_under_root(source_root, artifact.delete_prefix("file://"))
|
|
14
|
+
raise Error.resolution("package artifact path escapes distribution root") unless path
|
|
15
|
+
|
|
16
|
+
return File.binread(path)
|
|
17
|
+
end
|
|
18
|
+
if artifact.match?(%r{\A[a-z][a-z0-9+.-]*:}i)
|
|
19
|
+
raise Error.integrity("remote artifact path must be relative: #{artifact}")
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
path = PathSafety.join_under_root(source_root, artifact)
|
|
23
|
+
raise Error.resolution("package artifact path escapes distribution root") unless path
|
|
24
|
+
|
|
25
|
+
materialize_catalog_file(source_root, artifact) unless File.exist?(path)
|
|
26
|
+
raise Error.resolution("package artifact missing at #{path}") unless File.exist?(path)
|
|
27
|
+
|
|
28
|
+
File.binread(path)
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
def materialize_catalog_file(source_root, relative)
|
|
32
|
+
return if File.file?(File.join(source_root, relative))
|
|
33
|
+
return unless sparse_checkout_enabled?(source_root)
|
|
34
|
+
|
|
35
|
+
toplevel = git_toplevel(source_root)
|
|
36
|
+
return unless toplevel
|
|
37
|
+
|
|
38
|
+
repo_relative = repo_relative_text(source_root, relative)
|
|
39
|
+
return unless repo_relative
|
|
40
|
+
|
|
41
|
+
cone = artifact_sparse_cone(repo_relative)
|
|
42
|
+
GitRun.run_git(toplevel, "sparse-checkout", "add", cone) if cone
|
|
43
|
+
GitRun.run_git(toplevel, "checkout", "HEAD", "--", repo_relative)
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
def sparse_checkout_enabled?(source_root)
|
|
47
|
+
output, status = GitRun.capture_git(source_root, "config", "--get", "core.sparseCheckout")
|
|
48
|
+
status.success? && output.strip == "true"
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
def git_toplevel(source_root)
|
|
52
|
+
output, status = GitRun.capture_git(source_root, "rev-parse", "--show-toplevel")
|
|
53
|
+
return unless status.success?
|
|
54
|
+
|
|
55
|
+
text = output.strip
|
|
56
|
+
text.empty? ? nil : text
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
def repo_relative_text(source_root, relative)
|
|
60
|
+
output, status = GitRun.capture_git(source_root, "rev-parse", "--show-prefix")
|
|
61
|
+
return unless status.success?
|
|
62
|
+
|
|
63
|
+
prefix = output.strip
|
|
64
|
+
prefix.empty? ? relative : File.join(prefix, relative)
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
def artifact_sparse_cone(repo_relative)
|
|
68
|
+
parts = repo_relative.split("/").reject(&:empty?)
|
|
69
|
+
return repo_relative if parts.length < 2
|
|
70
|
+
|
|
71
|
+
parts.pop
|
|
72
|
+
parts.pop if parts.last != "artifacts" && parts.length > 3
|
|
73
|
+
parts.join("/")
|
|
74
|
+
end
|
|
75
|
+
end
|
|
76
|
+
end
|
data/lib/pray/git_run.rb
ADDED
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "open3"
|
|
4
|
+
require_relative "error"
|
|
5
|
+
|
|
6
|
+
module Pray
|
|
7
|
+
module GitRun
|
|
8
|
+
module_function
|
|
9
|
+
|
|
10
|
+
def run_git(cwd, *arguments)
|
|
11
|
+
output, status = capture_git(cwd, *arguments)
|
|
12
|
+
return if status.success?
|
|
13
|
+
|
|
14
|
+
raise Error.resolution(command_error("git #{arguments.join(" ")}", output))
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def try_run_git(cwd, *arguments)
|
|
18
|
+
_output, status = capture_git(cwd, *arguments)
|
|
19
|
+
status.success?
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
def capture_git(cwd, *arguments)
|
|
23
|
+
env = ENV.to_h.merge("GIT_TERMINAL_PROMPT" => "0")
|
|
24
|
+
Open3.capture2e(
|
|
25
|
+
env,
|
|
26
|
+
"git",
|
|
27
|
+
"-c",
|
|
28
|
+
"protocol.file.allow=always",
|
|
29
|
+
"-C",
|
|
30
|
+
cwd,
|
|
31
|
+
*arguments,
|
|
32
|
+
stdin_data: ""
|
|
33
|
+
)
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
def command_error(program, output)
|
|
37
|
+
message = output.strip
|
|
38
|
+
message.empty? ? "#{program} failed" : "#{program} failed: #{message}"
|
|
39
|
+
end
|
|
40
|
+
end
|
|
41
|
+
end
|