pray-cli 1.19.0 → 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 +10 -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/publish.rb +12 -7
- 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 +2 -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,15 @@
|
|
|
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
|
+
|
|
3
13
|
## 1.19.0 (2026-09-18)
|
|
4
14
|
|
|
5
15
|
- Declare named `publish` remotes in Prayfile (RFC 0118). `pray publish` uses them when dest flags are omitted, and publishes only path-owned packages.
|
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
|
data/lib/pray/git_sources.rb
CHANGED
|
@@ -7,10 +7,11 @@ module Pray
|
|
|
7
7
|
GitSourceCheckout = Struct.new(:cache_directory, :revision, :subdir)
|
|
8
8
|
|
|
9
9
|
class GitSourceSet
|
|
10
|
-
def initialize(project_root, sources, lockfile, refresh:)
|
|
10
|
+
def initialize(project_root, sources, lockfile, refresh:, offline: false)
|
|
11
11
|
@project_root = project_root
|
|
12
12
|
@lockfile = lockfile
|
|
13
13
|
@refresh = refresh
|
|
14
|
+
@offline = offline
|
|
14
15
|
@sources = sources.select { |source| source.kind == "git" }.to_h { |source| [source.name, source] }
|
|
15
16
|
@checkouts = {}
|
|
16
17
|
end
|
|
@@ -21,7 +22,7 @@ module Pray
|
|
|
21
22
|
source = @sources[name]
|
|
22
23
|
return nil unless source
|
|
23
24
|
|
|
24
|
-
checkout = GitSources.prepare_one_git_source(@project_root, source, @lockfile, refresh: @refresh)
|
|
25
|
+
checkout = GitSources.prepare_one_git_source(@project_root, source, @lockfile, refresh: @refresh, offline: @offline)
|
|
25
26
|
@checkouts[name] = checkout if checkout
|
|
26
27
|
checkout
|
|
27
28
|
end
|
|
@@ -38,21 +39,21 @@ module Pray
|
|
|
38
39
|
module GitSources
|
|
39
40
|
module_function
|
|
40
41
|
|
|
41
|
-
def prepare_git_sources(project_root, sources, lockfile, refresh: false)
|
|
42
|
-
GitSourceSet.new(project_root, sources, lockfile, refresh: refresh)
|
|
42
|
+
def prepare_git_sources(project_root, sources, lockfile, refresh: false, offline: false)
|
|
43
|
+
GitSourceSet.new(project_root, sources, lockfile, refresh: refresh, offline: offline)
|
|
43
44
|
end
|
|
44
45
|
|
|
45
|
-
def prepare_one_git_source(project_root, source, lockfile, refresh:)
|
|
46
|
+
def prepare_one_git_source(project_root, source, lockfile, refresh:, offline: false)
|
|
46
47
|
clone_url = source.url.delete_prefix("git+")
|
|
47
48
|
if local_filesystem_source?(clone_url) && !local_git_repo_path(project_root, clone_url)
|
|
48
49
|
source_root = local_git_source_root(project_root, clone_url)
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
50
|
+
if source_root
|
|
51
|
+
return GitSourceCheckout.new(
|
|
52
|
+
cache_directory: source_root,
|
|
53
|
+
revision: "",
|
|
54
|
+
subdir: source.subdir
|
|
55
|
+
)
|
|
56
|
+
end
|
|
56
57
|
end
|
|
57
58
|
|
|
58
59
|
pinned_revision = refresh ? nil : pinned_revision_for_source(lockfile, source)
|
|
@@ -61,7 +62,8 @@ module Pray
|
|
|
61
62
|
clone_url,
|
|
62
63
|
refresh: refresh,
|
|
63
64
|
pinned_revision: pinned_revision,
|
|
64
|
-
sparse_subdir: source.subdir
|
|
65
|
+
sparse_subdir: source.subdir,
|
|
66
|
+
offline: offline
|
|
65
67
|
)
|
|
66
68
|
GitSourceCheckout.new(
|
|
67
69
|
cache_directory: cache_directory,
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "fileutils"
|
|
4
|
+
require_relative "error"
|
|
5
|
+
require_relative "hashing"
|
|
6
|
+
require_relative "git_run"
|
|
7
|
+
require_relative "git_clone"
|
|
8
|
+
|
|
9
|
+
module Pray
|
|
10
|
+
module GitStore
|
|
11
|
+
module_function
|
|
12
|
+
|
|
13
|
+
def ensure_global_git_db(clone_url, pinned_revision:, refresh:, offline:, working_directory:)
|
|
14
|
+
db = global_git_cache_directory(clone_url)
|
|
15
|
+
raise Error.resolution("git object cache is not configured") unless db
|
|
16
|
+
|
|
17
|
+
unless global_git_cache_ready?(db)
|
|
18
|
+
raise Error.resolution(offline_git_source_uncached(clone_url)) if offline
|
|
19
|
+
|
|
20
|
+
FileUtils.rm_rf(db) if File.exist?(db)
|
|
21
|
+
FileUtils.mkdir_p(File.dirname(db))
|
|
22
|
+
GitClone.clone_bare_git_db(working_directory, clone_url, db, quiet: false)
|
|
23
|
+
ensure_git_remote_origin(db, clone_url)
|
|
24
|
+
end
|
|
25
|
+
fetch_origin_tip(db, clone_url) if refresh && !offline
|
|
26
|
+
if pinned_revision
|
|
27
|
+
ensure_revision_in_db(db, clone_url, pinned_revision, !offline)
|
|
28
|
+
return [db, pinned_revision]
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
[db, git_head_revision(db)]
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
def global_cache_root
|
|
35
|
+
return ENV["PRAY_CACHE"] if ENV["PRAY_CACHE"]
|
|
36
|
+
return File.join(ENV["PRAY_HOME"], "cache") if ENV["PRAY_HOME"]
|
|
37
|
+
|
|
38
|
+
home = ENV["HOME"]
|
|
39
|
+
home ? File.join(home, ".cache", "pray") : nil
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
def global_git_cache_directory(clone_url)
|
|
43
|
+
root = global_cache_root
|
|
44
|
+
root ? File.join(root, "git", Hashing.sha256_prefixed(clone_url)[7, 16]) : nil
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
def global_git_cache_ready?(global_cache)
|
|
48
|
+
File.file?(File.join(global_cache, "HEAD")) || File.directory?(File.join(global_cache, ".git"))
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
def offline_git_source_uncached(clone_url)
|
|
52
|
+
"git source #{clone_url} is not cached locally and offline mode is enabled"
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
def ensure_revision_in_db(db, clone_url, revision, allow_fetch)
|
|
56
|
+
return if GitRun.try_run_git(db, "cat-file", "-e", revision)
|
|
57
|
+
unless allow_fetch
|
|
58
|
+
raise Error.resolution(
|
|
59
|
+
"git source #{db.inspect} is locked to revision #{revision}, but that commit is not available locally and offline mode is enabled"
|
|
60
|
+
)
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
ensure_git_remote_origin(db, clone_url)
|
|
64
|
+
fetch_unshallow(db) if File.file?(File.join(db, "shallow"))
|
|
65
|
+
return if GitRun.try_run_git(db, "cat-file", "-e", revision)
|
|
66
|
+
|
|
67
|
+
fetch_revision(db, revision)
|
|
68
|
+
return if GitRun.try_run_git(db, "cat-file", "-e", revision)
|
|
69
|
+
|
|
70
|
+
raise Error.resolution(
|
|
71
|
+
"git source #{db.inspect} is locked to revision #{revision}, but that commit could not be fetched"
|
|
72
|
+
)
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
def ensure_git_remote_origin(repository, clone_url)
|
|
76
|
+
if GitRun.try_run_git(repository, "remote", "get-url", "origin")
|
|
77
|
+
GitRun.run_git(repository, "remote", "set-url", "origin", clone_url)
|
|
78
|
+
else
|
|
79
|
+
GitRun.run_git(repository, "remote", "add", "origin", clone_url)
|
|
80
|
+
end
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
def fetch_origin_tip(db, clone_url)
|
|
84
|
+
ensure_git_remote_origin(db, clone_url)
|
|
85
|
+
unless GitRun.try_run_git(db, "fetch", "--depth", "1", "--filter=blob:none", "origin")
|
|
86
|
+
GitRun.run_git(db, "fetch", "--depth", "1", "origin")
|
|
87
|
+
end
|
|
88
|
+
GitRun.run_git(db, "update-ref", "HEAD", "FETCH_HEAD")
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
def fetch_unshallow(db)
|
|
92
|
+
return if GitRun.try_run_git(db, "fetch", "--unshallow", "--filter=blob:none", "origin")
|
|
93
|
+
|
|
94
|
+
GitRun.run_git(db, "fetch", "--unshallow", "origin")
|
|
95
|
+
end
|
|
96
|
+
|
|
97
|
+
def fetch_revision(db, revision)
|
|
98
|
+
return if GitRun.try_run_git(db, "fetch", "--filter=blob:none", "origin", revision)
|
|
99
|
+
|
|
100
|
+
GitRun.run_git(db, "fetch", "origin", revision)
|
|
101
|
+
end
|
|
102
|
+
|
|
103
|
+
def git_head_revision(repository)
|
|
104
|
+
output, status = GitRun.capture_git(repository, "rev-parse", "HEAD")
|
|
105
|
+
raise Error.resolution(GitRun.command_error("git rev-parse HEAD", output)) unless status.success?
|
|
106
|
+
|
|
107
|
+
revision = output.strip
|
|
108
|
+
raise Error.resolution("git repository has no HEAD revision") if revision.empty?
|
|
109
|
+
|
|
110
|
+
revision
|
|
111
|
+
end
|
|
112
|
+
end
|
|
113
|
+
end
|
data/lib/pray/publish.rb
CHANGED
|
@@ -28,9 +28,7 @@ module Pray
|
|
|
28
28
|
metadata = load_registry_package_metadata(metadata_path, package.declaration.name)
|
|
29
29
|
existing = metadata.versions.find { |entry| entry.version == package.spec.version }
|
|
30
30
|
stored_artifact = stored_package_artifact(root, artifact_path, package, existing, distribution)
|
|
31
|
-
if stored_artifact && stored_publish_matches?(
|
|
32
|
-
stored_artifact, package, signer, signer_fingerprint, existing
|
|
33
|
-
)
|
|
31
|
+
if stored_artifact && stored_publish_matches?(stored_artifact, package, existing)
|
|
34
32
|
package_names << package.declaration.name
|
|
35
33
|
write_registry_package_metadata(metadata_path, metadata)
|
|
36
34
|
next
|
|
@@ -134,10 +132,17 @@ module Pray
|
|
|
134
132
|
artifact_bytes
|
|
135
133
|
end
|
|
136
134
|
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
135
|
+
# A stored row is current when its own recorded signature still authenticates the
|
|
136
|
+
# stored artifact. The check deliberately ignores who is publishing now: a row signed
|
|
137
|
+
# by another publisher is still a valid attestation of identical bytes, and rewriting
|
|
138
|
+
# it would restate authorship without republishing anything. A row carrying no
|
|
139
|
+
# signature is not current, so publish repairs it.
|
|
140
|
+
def stored_publish_matches?(artifact_bytes, package, existing)
|
|
141
|
+
return false if existing.signer.nil? || existing.signature.nil?
|
|
142
|
+
|
|
143
|
+
existing.signature == Registry.registry_artifact_signature(
|
|
144
|
+
artifact_bytes, package.tree_hash, existing.signer
|
|
145
|
+
)
|
|
141
146
|
end
|
|
142
147
|
|
|
143
148
|
def stored_prayspec_matches?(artifact_bytes, package_root)
|
data/lib/pray/render_patch.rb
CHANGED
|
@@ -19,7 +19,7 @@ module Pray
|
|
|
19
19
|
used,
|
|
20
20
|
output
|
|
21
21
|
)
|
|
22
|
-
splice_existing_managed(remaining, fresh_managed, used, output)
|
|
22
|
+
splice_existing_managed(remaining, fresh_segments, fresh_managed, used, output)
|
|
23
23
|
append_unused_fresh_managed(fresh_segments, used, output)
|
|
24
24
|
output.end_with?("\n") ? output : "#{output}\n"
|
|
25
25
|
end
|
|
@@ -36,8 +36,8 @@ module Pray
|
|
|
36
36
|
segments.filter_map { |segment| segment[:id] if segment[:kind] == :managed }.to_h { |id| [id, true] }
|
|
37
37
|
end
|
|
38
38
|
|
|
39
|
-
def splice_existing_managed(remaining, fresh_managed, used, output)
|
|
40
|
-
remaining.
|
|
39
|
+
def splice_existing_managed(remaining, fresh_segments, fresh_managed, used, output)
|
|
40
|
+
remaining.each_with_index do |segment, index|
|
|
41
41
|
if segment[:kind] == :text
|
|
42
42
|
output << segment[:text]
|
|
43
43
|
next
|
|
@@ -45,15 +45,47 @@ module Pray
|
|
|
45
45
|
|
|
46
46
|
used[segment[:id]] = true
|
|
47
47
|
output << managed_segment(segment[:id], fresh_managed.fetch(segment[:id], segment[:body]))
|
|
48
|
+
next_segment = remaining[index + 1]
|
|
49
|
+
next unless next_segment && next_segment[:kind] == :managed
|
|
50
|
+
|
|
51
|
+
output << text_between(fresh_segments, segment[:id], next_segment[:id])
|
|
48
52
|
end
|
|
49
53
|
end
|
|
50
54
|
|
|
55
|
+
def text_between(segments, left, right)
|
|
56
|
+
copying = false
|
|
57
|
+
text = +""
|
|
58
|
+
segments.each do |segment|
|
|
59
|
+
if segment[:kind] == :managed
|
|
60
|
+
if segment[:id] == left
|
|
61
|
+
copying = true
|
|
62
|
+
text = +""
|
|
63
|
+
elsif copying
|
|
64
|
+
return (segment[:id] == right) ? text : ""
|
|
65
|
+
end
|
|
66
|
+
elsif copying
|
|
67
|
+
text << segment[:text]
|
|
68
|
+
end
|
|
69
|
+
end
|
|
70
|
+
""
|
|
71
|
+
end
|
|
72
|
+
|
|
51
73
|
def append_unused_fresh_managed(fresh_segments, used, output)
|
|
74
|
+
pending = +""
|
|
75
|
+
seen_used = false
|
|
52
76
|
fresh_segments.each do |segment|
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
77
|
+
if segment[:kind] == :managed
|
|
78
|
+
if used[segment[:id]]
|
|
79
|
+
seen_used = true
|
|
80
|
+
pending = +""
|
|
81
|
+
next
|
|
82
|
+
end
|
|
83
|
+
output << pending
|
|
84
|
+
pending = +""
|
|
85
|
+
output << managed_segment(segment[:id], segment[:body])
|
|
86
|
+
elsif seen_used
|
|
87
|
+
pending << segment[:text]
|
|
88
|
+
end
|
|
57
89
|
end
|
|
58
90
|
end
|
|
59
91
|
|
data/lib/pray/resolve.rb
CHANGED
|
@@ -59,7 +59,8 @@ module Pray
|
|
|
59
59
|
project_root,
|
|
60
60
|
manifest.sources,
|
|
61
61
|
lockfile_hints,
|
|
62
|
-
refresh: options.refresh || options.refresh_source_revisions
|
|
62
|
+
refresh: options.refresh || options.refresh_source_revisions,
|
|
63
|
+
offline: options.offline
|
|
63
64
|
)
|
|
64
65
|
source_host_keys = Trust.prepare_source_host_keys(manifest.sources)
|
|
65
66
|
|
data/lib/pray/upstream_drift.rb
CHANGED
|
@@ -10,7 +10,8 @@ module Pray
|
|
|
10
10
|
project.project_root,
|
|
11
11
|
project.manifest.sources,
|
|
12
12
|
previous,
|
|
13
|
-
refresh: options.refresh || options.refresh_source_revisions
|
|
13
|
+
refresh: options.refresh || options.refresh_source_revisions,
|
|
14
|
+
offline: options.offline
|
|
14
15
|
)
|
|
15
16
|
sources = Resolve.source_map(project.manifest.sources)
|
|
16
17
|
lines = []
|
data/lib/pray/upstream_latest.rb
CHANGED
|
@@ -15,7 +15,8 @@ module Pray
|
|
|
15
15
|
project.project_root,
|
|
16
16
|
project.manifest.sources,
|
|
17
17
|
previous,
|
|
18
|
-
refresh: options.refresh || options.refresh_source_revisions
|
|
18
|
+
refresh: options.refresh || options.refresh_source_revisions,
|
|
19
|
+
offline: options.offline
|
|
19
20
|
)
|
|
20
21
|
sources = Resolve.source_map(project.manifest.sources)
|
|
21
22
|
plans = []
|
|
@@ -10,7 +10,8 @@ module Pray
|
|
|
10
10
|
project.project_root,
|
|
11
11
|
project.manifest.sources,
|
|
12
12
|
previous,
|
|
13
|
-
refresh: options.refresh || options.refresh_source_revisions
|
|
13
|
+
refresh: options.refresh || options.refresh_source_revisions,
|
|
14
|
+
offline: options.offline
|
|
14
15
|
)
|
|
15
16
|
sources = Resolve.source_map(project.manifest.sources)
|
|
16
17
|
changed = false
|
data/lib/pray/version.rb
CHANGED
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: pray-cli
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 1.
|
|
4
|
+
version: 1.20.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Andrei Makarov
|
|
@@ -142,6 +142,7 @@ files:
|
|
|
142
142
|
- lib/pray/git_refresh.rb
|
|
143
143
|
- lib/pray/git_run.rb
|
|
144
144
|
- lib/pray/git_sources.rb
|
|
145
|
+
- lib/pray/git_store.rb
|
|
145
146
|
- lib/pray/hashing.rb
|
|
146
147
|
- lib/pray/http_body.rb
|
|
147
148
|
- lib/pray/invocation.rb
|