pray-cli 1.18.1 → 1.20.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 +14 -0
- data/lib/pray/cli/commands/distribution.rb +53 -5
- data/lib/pray/cli/commands/init.rb +26 -0
- data/lib/pray/cli/commands/packages.rb +6 -1
- data/lib/pray/cli/help.rb +7 -5
- data/lib/pray/cli/parse.rb +11 -4
- data/lib/pray/format_manifest.rb +1 -0
- data/lib/pray/format_serialize.rb +17 -0
- data/lib/pray/git_cache.rb +16 -144
- data/lib/pray/git_clone.rb +4 -8
- data/lib/pray/git_materialize.rb +86 -34
- data/lib/pray/git_sources.rb +15 -13
- data/lib/pray/git_store.rb +113 -0
- data/lib/pray/manifest.rb +7 -1
- data/lib/pray/manifest_json.rb +10 -0
- data/lib/pray/manifest_parser.rb +3 -0
- data/lib/pray/manifest_parser_publish.rb +52 -0
- data/lib/pray/publish.rb +12 -7
- data/lib/pray/publish_remote.rb +76 -0
- data/lib/pray/publish_select.rb +110 -0
- data/lib/pray/render_patch.rb +39 -7
- data/lib/pray/resolve.rb +2 -1
- data/lib/pray/upstream_drift.rb +2 -1
- data/lib/pray/upstream_latest.rb +2 -1
- data/lib/pray/upstream_refresh.rb +2 -1
- data/lib/pray/version.rb +2 -2
- metadata +5 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 4eb951f0ca0abc5ac0aa68ab27e63e4f778c92453fddb9a7cca0115dac1526d9
|
|
4
|
+
data.tar.gz: 28ba1073042c0c2a77138e35a9ae80688cb1a6075dd16cba5ee1b9aef2c9c3af
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 260a99edc903d99d235b2d9dd40904490fbcc392d67a07a4323d43b1359fead4428976745e438e17767948a1494262daa97b87650965ffc22366267c8faf5226
|
|
7
|
+
data.tar.gz: f031ff7ebaeceee86829abac7e2f99f060be902ebc586c00e5ad67135d25958248ac7c4fd5425a12068b67d1a01ebd747cacf53501f2c81cc68f3bdcbcfd838d
|
data/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,19 @@
|
|
|
1
1
|
# CHANGELOG
|
|
2
2
|
|
|
3
|
+
## 1.20.0 (2026-09-18)
|
|
4
|
+
|
|
5
|
+
- Fetch a locked git catalog revision during `pray install` when the local cache does not have that commit yet. `pray install --offline` still refuses a missing commit.
|
|
6
|
+
- Refuse `pray install --offline` when a git catalog is not already in the local cache, instead of cloning it. A global cache seed still works.
|
|
7
|
+
- Honour a locked git revision for a local file catalog. Install does not read the origin worktree after that checkout fails.
|
|
8
|
+
- Keep git catalog objects in the shared cache. The project catalog is a file tree without a git directory.
|
|
9
|
+
- When a locked git revision is missing from a shallow cache, fetch the missing history instead of only that one commit.
|
|
10
|
+
- Keep an unchanged published version when a later `pray publish` uses a different publisher label.
|
|
11
|
+
- Keep `pray drift` clean after `pray install` when a compose block adds a local file after a package dest already had.
|
|
12
|
+
|
|
13
|
+
## 1.19.0 (2026-09-18)
|
|
14
|
+
|
|
15
|
+
- Declare named `publish` remotes in Prayfile (RFC 0118). `pray publish` uses them when dest flags are omitted, and publishes only path-owned packages.
|
|
16
|
+
|
|
3
17
|
## 1.18.1 (2026-09-17)
|
|
4
18
|
|
|
5
19
|
- Fix `pray package` and `pray publish` when the CLI cannot create a staging directory.
|
|
@@ -2,16 +2,64 @@
|
|
|
2
2
|
|
|
3
3
|
module Pray
|
|
4
4
|
module CLI
|
|
5
|
-
def publish_command(roots:, servers:)
|
|
5
|
+
def publish_command(roots:, servers:, to: [], dry_run: false)
|
|
6
6
|
project = resolve_current_project
|
|
7
|
-
|
|
8
|
-
|
|
7
|
+
dests = PublishSelect.resolve_publish_destinations(
|
|
8
|
+
project.manifest.publish_remotes || [],
|
|
9
|
+
PublishSelect::PublishCliDest.new(to: to, roots: roots, servers: servers),
|
|
10
|
+
Dir.pwd
|
|
11
|
+
)
|
|
12
|
+
if dry_run
|
|
13
|
+
print_publish_plan(project, dests)
|
|
14
|
+
return
|
|
15
|
+
end
|
|
16
|
+
dests.each do |dest|
|
|
17
|
+
packages = selected_publish_packages(project, dest.packages)
|
|
18
|
+
filtered = project.dup
|
|
19
|
+
filtered.packages = packages
|
|
20
|
+
Publish.publish_to_root(filtered, dest.root) if dest.root
|
|
21
|
+
Publish.publish_to_server(filtered, dest.server) if dest.server
|
|
22
|
+
end
|
|
9
23
|
end
|
|
10
24
|
|
|
11
|
-
def
|
|
25
|
+
def selected_publish_packages(project, listed)
|
|
26
|
+
allowed = PublishRemote.allowed_publish_names(project.manifest, listed)
|
|
27
|
+
selected = project.packages.select { |package| allowed.include?(package.declaration.name) }
|
|
28
|
+
if selected.empty?
|
|
29
|
+
raise Error.usage("no path packages to publish; remote dependencies are not published")
|
|
30
|
+
end
|
|
31
|
+
selected.each { |package| package.spec.require_release_version! }
|
|
32
|
+
selected
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
def print_publish_plan(project, dests)
|
|
36
|
+
dests.each do |dest|
|
|
37
|
+
packages = selected_publish_packages(project, dest.packages)
|
|
38
|
+
target = dest.root || dest.server || dest.name
|
|
39
|
+
packages.each do |package|
|
|
40
|
+
puts "publish #{package.declaration.name} #{package.spec.version} -> #{dest.name} (#{target})"
|
|
41
|
+
end
|
|
42
|
+
end
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
def serve_command(root:, host:, port:, stdio:, to: nil)
|
|
12
46
|
raise Error.unsupported("serve --stdio is not implemented yet in pray-cli Ruby") if stdio
|
|
13
47
|
|
|
14
|
-
Serve.run_server(root: root, host: host, port: port)
|
|
48
|
+
Serve.run_server(root: optional_path_remote_root(to, root), host: host, port: port)
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
def optional_path_remote_root(to, root)
|
|
52
|
+
remotes = begin
|
|
53
|
+
Pray.parse_manifest(Pray.read_manifest_text(manifest_path)).publish_remotes || []
|
|
54
|
+
rescue Error
|
|
55
|
+
return root if to.nil?
|
|
56
|
+
|
|
57
|
+
raise
|
|
58
|
+
end
|
|
59
|
+
return root if remotes.empty? && to.nil?
|
|
60
|
+
|
|
61
|
+
cli_root = (root == ".") ? nil : root
|
|
62
|
+
PublishSelect.resolve_path_remote(remotes, to, cli_root, Dir.pwd)
|
|
15
63
|
end
|
|
16
64
|
end
|
|
17
65
|
end
|
|
@@ -34,6 +34,32 @@ module Pray
|
|
|
34
34
|
FileUtils.mkdir_p(File.join(distribution_root, "v1", "artifacts"))
|
|
35
35
|
Publish.write_registry_index(distribution_root, RegistryIndex.new)
|
|
36
36
|
Distribution.write_settings(distribution_root)
|
|
37
|
+
maybe_declare_publish_remote(Dir.pwd, distribution_root)
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
def maybe_declare_publish_remote(project_root, distribution_root)
|
|
41
|
+
manifest_path_value = File.join(project_root, "Prayfile")
|
|
42
|
+
return unless File.exist?(manifest_path_value)
|
|
43
|
+
|
|
44
|
+
text = File.read(manifest_path_value)
|
|
45
|
+
manifest = Pray.parse_manifest(text)
|
|
46
|
+
return unless (manifest.publish_remotes || []).empty?
|
|
47
|
+
|
|
48
|
+
relative = if distribution_root == project_root
|
|
49
|
+
"."
|
|
50
|
+
else
|
|
51
|
+
prefix = project_root.end_with?("/") ? project_root : "#{project_root}/"
|
|
52
|
+
distribution_root.start_with?(prefix) ? distribution_root.delete_prefix(prefix).tr("\\", "/") : "prayers"
|
|
53
|
+
end
|
|
54
|
+
statement = %(publish "prayers", path: "#{relative}")
|
|
55
|
+
lines = text.split("\n")
|
|
56
|
+
insertion = lines.rindex { |line| line.lstrip.start_with?("source ") }
|
|
57
|
+
insertion ||= lines.index { |line| line.lstrip.start_with?("prayfile ") }
|
|
58
|
+
insertion = insertion ? insertion + 1 : 1
|
|
59
|
+
lines.insert(insertion, statement)
|
|
60
|
+
updated = lines.join("\n")
|
|
61
|
+
updated += "\n" unless updated.end_with?("\n")
|
|
62
|
+
File.write(manifest_path_value, updated)
|
|
37
63
|
end
|
|
38
64
|
|
|
39
65
|
def repo_distribution_root(root)
|
|
@@ -53,7 +53,12 @@ module Pray
|
|
|
53
53
|
|
|
54
54
|
def package_command
|
|
55
55
|
project = resolve_current_project
|
|
56
|
-
project.
|
|
56
|
+
allowed = PublishRemote.path_owned_package_names(project.manifest)
|
|
57
|
+
packages = project.packages.select { |package| allowed.include?(package.declaration.name) }
|
|
58
|
+
if packages.empty?
|
|
59
|
+
raise Error.usage("no path packages to package; remote dependencies are not packed")
|
|
60
|
+
end
|
|
61
|
+
packages.each do |package|
|
|
57
62
|
package.spec.require_release_version!
|
|
58
63
|
output_path = Archive.package_archive_path(package.declaration.name, package.spec.version)
|
|
59
64
|
Archive.write_package_archive(package, output_path)
|
data/lib/pray/cli/help.rb
CHANGED
|
@@ -23,9 +23,9 @@ module Pray
|
|
|
23
23
|
].freeze
|
|
24
24
|
|
|
25
25
|
DISTRIBUTION_COMMANDS = [
|
|
26
|
-
"publish --root PATH
|
|
26
|
+
"publish [--root PATH|--server URL|--to NAME] [--dry-run]",
|
|
27
27
|
"login --server URL --email EMAIL",
|
|
28
|
-
"serve [--root PATH] [--host HOST] [--port PORT] [--stdio]",
|
|
28
|
+
"serve [--root PATH | --to NAME] [--host HOST] [--port PORT] [--stdio]",
|
|
29
29
|
"sync [--root PATH] [--peer URL ...]",
|
|
30
30
|
"confess <package> | --from-lock SPAN_ID [--accepted|--rejected]"
|
|
31
31
|
].freeze
|
|
@@ -146,9 +146,11 @@ module Pray
|
|
|
146
146
|
"vendor" => "copy resolved packages locally\n\nUsage: pray vendor",
|
|
147
147
|
"clean" => "remove local cache and vendor trees, or only unused registry entries\n\nUsage: pray clean [--unused]",
|
|
148
148
|
"publish" => <<~TEXT.strip,
|
|
149
|
-
upload packages to a registry or local root
|
|
149
|
+
upload path packages to a registry or local root
|
|
150
150
|
|
|
151
|
-
Usage: pray publish --root PATH [--server URL ...]
|
|
151
|
+
Usage: pray publish [--root PATH] [--server URL ...] [--to NAME] [--dry-run]
|
|
152
|
+
|
|
153
|
+
Prayfile publish remotes supply dests when flags are omitted.
|
|
152
154
|
TEXT
|
|
153
155
|
"login" => <<~TEXT.strip,
|
|
154
156
|
authenticate to a registry server
|
|
@@ -158,7 +160,7 @@ module Pray
|
|
|
158
160
|
"serve" => <<~TEXT.strip,
|
|
159
161
|
run a local registry server
|
|
160
162
|
|
|
161
|
-
Usage: pray serve [--root PATH] [--host HOST] [--port PORT] [--stdio]
|
|
163
|
+
Usage: pray serve [--root PATH | --to NAME] [--host HOST] [--port PORT] [--stdio]
|
|
162
164
|
TEXT
|
|
163
165
|
"sync" => <<~TEXT.strip,
|
|
164
166
|
sync packages with peer registries
|
data/lib/pray/cli/parse.rb
CHANGED
|
@@ -141,26 +141,33 @@ module Pray
|
|
|
141
141
|
def parse_publish_arguments(arguments)
|
|
142
142
|
roots = []
|
|
143
143
|
servers = []
|
|
144
|
+
to = []
|
|
145
|
+
dry_run = false
|
|
144
146
|
while (argument = arguments.shift)
|
|
145
147
|
case argument
|
|
146
148
|
when "--root"
|
|
147
149
|
roots << arguments.shift
|
|
148
150
|
when "--server"
|
|
149
151
|
servers << arguments.shift
|
|
152
|
+
when "--to"
|
|
153
|
+
to << arguments.shift
|
|
154
|
+
when "--dry-run"
|
|
155
|
+
dry_run = true
|
|
156
|
+
when "--signing-key"
|
|
157
|
+
arguments.shift
|
|
150
158
|
else
|
|
151
159
|
raise Error.unsupported("unexpected publish argument: #{argument}")
|
|
152
160
|
end
|
|
153
161
|
end
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
{roots: roots, servers: servers}
|
|
162
|
+
{roots: roots, servers: servers, to: to, dry_run: dry_run}
|
|
157
163
|
end
|
|
158
164
|
|
|
159
165
|
def parse_serve_arguments(arguments)
|
|
160
|
-
options = {root: ".", host: "127.0.0.1", port: 7429, stdio: false}
|
|
166
|
+
options = {root: ".", to: nil, host: "127.0.0.1", port: 7429, stdio: false}
|
|
161
167
|
while (argument = arguments.shift)
|
|
162
168
|
case argument
|
|
163
169
|
when "--root" then options[:root] = arguments.shift
|
|
170
|
+
when "--to" then options[:to] = arguments.shift
|
|
164
171
|
when "--host" then options[:host] = arguments.shift
|
|
165
172
|
when "--port" then options[:port] = Integer(arguments.shift)
|
|
166
173
|
when "--stdio" then options[:stdio] = true
|
data/lib/pray/format_manifest.rb
CHANGED
|
@@ -79,6 +79,7 @@ module Pray
|
|
|
79
79
|
targets: [],
|
|
80
80
|
packages: manifest.packages.map { |package| clone_package(package) },
|
|
81
81
|
local: manifest.local.map { |local| local.dup },
|
|
82
|
+
publish_remotes: (manifest.publish_remotes || []).map(&:dup),
|
|
82
83
|
symbols: manifest.symbols.dup,
|
|
83
84
|
render: manifest.render.dup
|
|
84
85
|
)
|
|
@@ -12,6 +12,11 @@ module Pray
|
|
|
12
12
|
manifest.sources.each { |source| lines << format_source(source) }
|
|
13
13
|
end
|
|
14
14
|
|
|
15
|
+
unless (manifest.publish_remotes || []).empty?
|
|
16
|
+
lines << ""
|
|
17
|
+
manifest.publish_remotes.each { |remote| lines << format_publish_remote(remote) }
|
|
18
|
+
end
|
|
19
|
+
|
|
15
20
|
unless manifest.symbols.empty?
|
|
16
21
|
lines << ""
|
|
17
22
|
lines << "pray do"
|
|
@@ -109,6 +114,18 @@ module Pray
|
|
|
109
114
|
parts.join(", ")
|
|
110
115
|
end
|
|
111
116
|
|
|
117
|
+
def format_publish_remote(remote)
|
|
118
|
+
parts = [%(publish "#{remote.name}")]
|
|
119
|
+
parts << %(path: "#{remote.path}") if remote.path
|
|
120
|
+
parts << %("#{remote.url}") if remote.url
|
|
121
|
+
return parts.join(", ") if remote.packages.empty?
|
|
122
|
+
|
|
123
|
+
lines = ["#{parts.join(", ")} do"]
|
|
124
|
+
remote.packages.each { |name| lines << %( pray "#{name}") }
|
|
125
|
+
lines << "end"
|
|
126
|
+
lines.join("\n")
|
|
127
|
+
end
|
|
128
|
+
|
|
112
129
|
def format_destination_entry(entry, manifest)
|
|
113
130
|
if entry.kind == "local"
|
|
114
131
|
return %(pray "#{entry.path}")
|
data/lib/pray/git_cache.rb
CHANGED
|
@@ -1,33 +1,26 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
require "fileutils"
|
|
4
|
-
require "pathname"
|
|
5
3
|
require_relative "error"
|
|
6
4
|
require_relative "hashing"
|
|
7
5
|
require_relative "git_run"
|
|
8
|
-
require_relative "
|
|
6
|
+
require_relative "git_store"
|
|
7
|
+
require_relative "git_materialize"
|
|
9
8
|
|
|
10
9
|
module Pray
|
|
11
10
|
module GitCache
|
|
12
11
|
module_function
|
|
13
12
|
|
|
14
|
-
def ensure_git_repository(project_root, clone_url, refresh:, pinned_revision:, sparse_subdir:)
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
end
|
|
26
|
-
return [checkout, git_head_revision(checkout)]
|
|
27
|
-
end
|
|
28
|
-
|
|
29
|
-
GitClone.apply_sparse_checkout(shared)
|
|
30
|
-
[shared, git_head_revision(shared)]
|
|
13
|
+
def ensure_git_repository(project_root, clone_url, refresh:, pinned_revision:, sparse_subdir:, offline: false)
|
|
14
|
+
db, revision = GitStore.ensure_global_git_db(
|
|
15
|
+
clone_url,
|
|
16
|
+
pinned_revision: pinned_revision,
|
|
17
|
+
refresh: refresh,
|
|
18
|
+
offline: offline,
|
|
19
|
+
working_directory: project_root
|
|
20
|
+
)
|
|
21
|
+
catalog = git_source_cache_directory(project_root, clone_url, sparse_subdir)
|
|
22
|
+
GitMaterialize.materialize_catalog_tree(db, catalog, revision, sparse_subdir, refresh)
|
|
23
|
+
[catalog, revision]
|
|
31
24
|
end
|
|
32
25
|
|
|
33
26
|
def git_source_cache_directory(project_root, clone_url, subdir = nil)
|
|
@@ -36,6 +29,9 @@ module Pray
|
|
|
36
29
|
end
|
|
37
30
|
|
|
38
31
|
def git_source_cached_repository(project_root, clone_url)
|
|
32
|
+
global_cache = GitStore.global_git_cache_directory(clone_url)
|
|
33
|
+
return global_cache if global_cache && GitStore.global_git_cache_ready?(global_cache)
|
|
34
|
+
|
|
39
35
|
shared = git_source_cache_directory(project_root, clone_url)
|
|
40
36
|
return shared if git_checkout?(shared)
|
|
41
37
|
|
|
@@ -57,60 +53,6 @@ module Pray
|
|
|
57
53
|
Hashing.sha256_prefixed(text)[7, 16]
|
|
58
54
|
end
|
|
59
55
|
|
|
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
56
|
def origin_matches?(repository, clone_url)
|
|
115
57
|
output, status = GitRun.capture_git(repository, "remote", "get-url", "origin")
|
|
116
58
|
return false unless status.success?
|
|
@@ -118,75 +60,5 @@ module Pray
|
|
|
118
60
|
origin = output.strip.delete_prefix("git+")
|
|
119
61
|
origin == clone_url
|
|
120
62
|
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
63
|
end
|
|
192
64
|
end
|
data/lib/pray/git_clone.rb
CHANGED
|
@@ -6,21 +6,17 @@ module Pray
|
|
|
6
6
|
module GitClone
|
|
7
7
|
module_function
|
|
8
8
|
|
|
9
|
-
def
|
|
10
|
-
|
|
9
|
+
def clone_bare_git_db(working_directory, source, destination, quiet:)
|
|
10
|
+
local = source.start_with?("file://", "/") ? ["--no-local"] : []
|
|
11
|
+
filtered = ["clone", "--bare", "--depth", "1", "--filter=blob:none", *local, source, destination]
|
|
11
12
|
filtered.insert(1, "--quiet") if quiet
|
|
12
13
|
return if GitRun.try_run_git(working_directory, *filtered)
|
|
13
14
|
|
|
14
|
-
full = ["clone", "--depth", "1", source, destination]
|
|
15
|
+
full = ["clone", "--bare", "--depth", "1", *local, source, destination]
|
|
15
16
|
full.insert(1, "--quiet") if quiet
|
|
16
17
|
GitRun.run_git(working_directory, *full)
|
|
17
18
|
end
|
|
18
19
|
|
|
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
20
|
def catalog_sparse_cones(subdir)
|
|
25
21
|
if subdir && !subdir.empty?
|
|
26
22
|
["#{subdir}/v1/packages"]
|
data/lib/pray/git_materialize.rb
CHANGED
|
@@ -1,11 +1,17 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
require "fileutils"
|
|
4
|
+
require "open3"
|
|
4
5
|
require_relative "error"
|
|
6
|
+
require_relative "git_clone"
|
|
7
|
+
require_relative "git_run"
|
|
5
8
|
require_relative "path_safety"
|
|
6
9
|
|
|
7
10
|
module Pray
|
|
8
11
|
module GitMaterialize
|
|
12
|
+
REVISION_MARKER = ".pray-revision"
|
|
13
|
+
GIT_DIR_MARKER = ".pray-git-dir"
|
|
14
|
+
|
|
9
15
|
module_function
|
|
10
16
|
|
|
11
17
|
def read_local_artifact_bytes(source_root, artifact)
|
|
@@ -28,49 +34,95 @@ module Pray
|
|
|
28
34
|
File.binread(path)
|
|
29
35
|
end
|
|
30
36
|
|
|
31
|
-
def
|
|
32
|
-
return if
|
|
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
|
|
37
|
+
def materialize_catalog_tree(git_dir, dest, revision, subdir, refresh)
|
|
38
|
+
return if !refresh && catalog_matches?(dest, git_dir, revision)
|
|
40
39
|
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
40
|
+
FileUtils.rm_rf(dest) if File.exist?(dest)
|
|
41
|
+
FileUtils.mkdir_p(dest)
|
|
42
|
+
unpacked = GitClone.catalog_sparse_cones(subdir).any? do |prefix|
|
|
43
|
+
unpack_archive_prefix(git_dir, dest, revision, prefix)
|
|
44
|
+
end
|
|
45
|
+
raise Error.resolution("no pray distribution root in git source at revision #{revision}") unless unpacked
|
|
45
46
|
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
status.success? && output.strip == "true"
|
|
47
|
+
File.write(File.join(dest, REVISION_MARKER), revision)
|
|
48
|
+
File.write(File.join(dest, GIT_DIR_MARKER), git_dir)
|
|
49
49
|
end
|
|
50
50
|
|
|
51
|
-
def
|
|
52
|
-
|
|
53
|
-
return unless status.success?
|
|
51
|
+
def materialize_catalog_file(source_root, relative)
|
|
52
|
+
return if File.file?(File.join(source_root, relative))
|
|
54
53
|
|
|
55
|
-
|
|
56
|
-
|
|
54
|
+
markers = find_catalog_markers(source_root)
|
|
55
|
+
return unless markers
|
|
56
|
+
|
|
57
|
+
full_path = File.join(source_root, relative)
|
|
58
|
+
repo_relative = full_path.delete_prefix(markers.fetch(:work_tree)).delete_prefix("/")
|
|
59
|
+
GitRun.run_git(
|
|
60
|
+
markers.fetch(:git_dir),
|
|
61
|
+
"--work-tree",
|
|
62
|
+
markers.fetch(:work_tree),
|
|
63
|
+
"checkout",
|
|
64
|
+
markers.fetch(:revision),
|
|
65
|
+
"--",
|
|
66
|
+
repo_relative
|
|
67
|
+
)
|
|
57
68
|
end
|
|
58
69
|
|
|
59
|
-
def
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
70
|
+
def catalog_matches?(dest, git_dir, revision)
|
|
71
|
+
revision_path = File.join(dest, REVISION_MARKER)
|
|
72
|
+
git_dir_path = File.join(dest, GIT_DIR_MARKER)
|
|
73
|
+
return false unless File.file?(revision_path) && File.file?(git_dir_path)
|
|
74
|
+
return false unless File.read(revision_path).strip == revision
|
|
75
|
+
return false unless File.read(git_dir_path).strip == git_dir
|
|
76
|
+
|
|
77
|
+
File.directory?(File.join(dest, "v1", "packages")) ||
|
|
78
|
+
File.directory?(File.join(dest, "prayers", "v1", "packages")) ||
|
|
79
|
+
Dir.children(dest).any? { |name| File.directory?(File.join(dest, name, "v1", "packages")) }
|
|
80
|
+
rescue Errno::ENOENT
|
|
81
|
+
false
|
|
65
82
|
end
|
|
66
83
|
|
|
67
|
-
def
|
|
68
|
-
|
|
69
|
-
|
|
84
|
+
def unpack_archive_prefix(git_dir, dest, revision, prefix)
|
|
85
|
+
env = ENV.to_h.merge("GIT_TERMINAL_PROMPT" => "0")
|
|
86
|
+
stdout, _stderr, status = Open3.capture3(
|
|
87
|
+
env,
|
|
88
|
+
"git",
|
|
89
|
+
"-c",
|
|
90
|
+
"protocol.file.allow=always",
|
|
91
|
+
"-C",
|
|
92
|
+
git_dir,
|
|
93
|
+
"archive",
|
|
94
|
+
"--format=tar",
|
|
95
|
+
revision,
|
|
96
|
+
"--",
|
|
97
|
+
prefix,
|
|
98
|
+
stdin_data: ""
|
|
99
|
+
)
|
|
100
|
+
return false unless status.success? && !stdout.empty?
|
|
101
|
+
|
|
102
|
+
_ignored, extract_status = Open3.capture2(env, "tar", "-x", "-C", dest, stdin_data: stdout)
|
|
103
|
+
raise Error.resolution("failed to unpack git catalog archive") unless extract_status.success?
|
|
104
|
+
|
|
105
|
+
true
|
|
106
|
+
end
|
|
70
107
|
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
108
|
+
def find_catalog_markers(start)
|
|
109
|
+
current = start
|
|
110
|
+
8.times do
|
|
111
|
+
revision_path = File.join(current, REVISION_MARKER)
|
|
112
|
+
git_dir_path = File.join(current, GIT_DIR_MARKER)
|
|
113
|
+
if File.file?(revision_path) && File.file?(git_dir_path)
|
|
114
|
+
revision = File.read(revision_path).strip
|
|
115
|
+
git_dir = File.read(git_dir_path).strip
|
|
116
|
+
return if revision.empty? || !File.exist?(git_dir)
|
|
117
|
+
|
|
118
|
+
return {git_dir: git_dir, revision: revision, work_tree: current}
|
|
119
|
+
end
|
|
120
|
+
parent = File.dirname(current)
|
|
121
|
+
return if parent == current
|
|
122
|
+
|
|
123
|
+
current = parent
|
|
124
|
+
end
|
|
125
|
+
nil
|
|
74
126
|
end
|
|
75
127
|
end
|
|
76
128
|
end
|