kettle-family 1.2.90 → 1.2.92
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
- checksums.yaml.gz.sig +0 -0
- data/CHANGELOG.md +29 -1
- data/README.md +17 -1
- data/lib/kettle/family/appraisal_bootstrap.rb +2 -1
- data/lib/kettle/family/command_runner.rb +4 -4
- data/lib/kettle/family/report.rb +7 -2
- data/lib/kettle/family/version.rb +1 -1
- data/lib/kettle/family/workflow.rb +173 -36
- data.tar.gz.sig +0 -0
- metadata +4 -4
- metadata.gz.sig +0 -0
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: c1646ca3577c370624800077609c69c0decd4a50ad6924e82cfe3c050c1757df
|
|
4
|
+
data.tar.gz: 361e0ac1cd39a97b442d3296635055b5bd34c24ced7d3944cec4a0984df6babb
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: fa89492878000e8fa0b08b244b95a432ddb96e11130da0d4655087580b63260242b00f28a3f76af32c3a9dc8d9d2830f815f22f960f5c835b4b3af851196b510
|
|
7
|
+
data.tar.gz: 1a801e2a97fdb2726e8ac282f88686e87aff42b680b54e2798b09bd70aa4e6be3678e9bf45625ab057a3d2210e99cd59eeeba31fd2761880f27cabc2b53a1566
|
checksums.yaml.gz.sig
CHANGED
|
Binary file
|
data/CHANGELOG.md
CHANGED
|
@@ -30,6 +30,30 @@ Please file a bug if you notice a violation of semantic versioning.
|
|
|
30
30
|
|
|
31
31
|
### Security
|
|
32
32
|
|
|
33
|
+
## [1.2.92] - 2026-09-07
|
|
34
|
+
|
|
35
|
+
- TAG: [v1.2.92][1.2.92t]
|
|
36
|
+
- COVERAGE: 93.52% -- 6144/6570 lines in 35 files
|
|
37
|
+
- BRANCH COVERAGE: 77.98% -- 2490/3193 branches in 35 files
|
|
38
|
+
- 32.41% documented
|
|
39
|
+
|
|
40
|
+
### Fixed
|
|
41
|
+
|
|
42
|
+
- Preserve pre-existing monorepo member changes while templating in parallel worktrees, reject concurrent member edits without overwriting them, and count only fully materialized members as successful.
|
|
43
|
+
|
|
44
|
+
## [1.2.91] - 2026-09-06
|
|
45
|
+
|
|
46
|
+
- TAG: [v1.2.91][1.2.91t]
|
|
47
|
+
- COVERAGE: 93.58% -- 6109/6528 lines in 35 files
|
|
48
|
+
- BRANCH COVERAGE: 78.06% -- 2483/3181 branches in 35 files
|
|
49
|
+
- 32.41% documented
|
|
50
|
+
|
|
51
|
+
### Fixed
|
|
52
|
+
|
|
53
|
+
- Trust Mise configurations in disposable worktrees before member commands run.
|
|
54
|
+
|
|
55
|
+
- Remove retired appraisal declarations cleanly from files without a trailing newline during template bootstrap.
|
|
56
|
+
|
|
33
57
|
## [1.2.90] - 2026-09-06
|
|
34
58
|
|
|
35
59
|
- TAG: [v1.2.90][1.2.90t]
|
|
@@ -2592,7 +2616,11 @@ Please file a bug if you notice a violation of semantic versioning.
|
|
|
2592
2616
|
- Fixed CI load failures on engines without compatible `pty` support by falling back to Open3 for interactive release commands.
|
|
2593
2617
|
- Fixed Ruby 3.2 version-bump support by loading Prism lazily and wiring the Prism gem only for MRI versions that need it.
|
|
2594
2618
|
|
|
2595
|
-
[Unreleased]: https://github.com/kettle-dev/kettle-family/compare/v1.2.
|
|
2619
|
+
[Unreleased]: https://github.com/kettle-dev/kettle-family/compare/v1.2.92...HEAD
|
|
2620
|
+
[1.2.92]: https://github.com/kettle-dev/kettle-family/compare/v1.2.91...v1.2.92
|
|
2621
|
+
[1.2.92t]: https://github.com/kettle-dev/kettle-family/releases/tag/v1.2.92
|
|
2622
|
+
[1.2.91]: https://github.com/kettle-dev/kettle-family/compare/v1.2.90...v1.2.91
|
|
2623
|
+
[1.2.91t]: https://github.com/kettle-dev/kettle-family/releases/tag/v1.2.91
|
|
2596
2624
|
[1.2.90]: https://github.com/kettle-dev/kettle-family/compare/v1.2.89...v1.2.90
|
|
2597
2625
|
[1.2.90t]: https://github.com/kettle-dev/kettle-family/releases/tag/v1.2.90
|
|
2598
2626
|
[1.2.89]: https://github.com/kettle-dev/kettle-family/compare/v1.2.88...v1.2.89
|
data/README.md
CHANGED
|
@@ -281,6 +281,22 @@ canonical development lockfile and CI checkout share that path graph. In both
|
|
|
281
281
|
cases, release normalization rejects every path outside the configured allow
|
|
282
282
|
list.
|
|
283
283
|
|
|
284
|
+
### Template concurrency
|
|
285
|
+
|
|
286
|
+
Executed `kettle-jem` templating for monorepo members uses detached worktrees
|
|
287
|
+
when a dependency wave contains multiple ready members and the worker budget is
|
|
288
|
+
greater than one. Before a worker starts, Family copies that member's current
|
|
289
|
+
tracked and untracked state into its worktree. This preserves development
|
|
290
|
+
lockfile changes produced by local dependency updates instead of assuming that
|
|
291
|
+
the primary checkout matches `HEAD`.
|
|
292
|
+
|
|
293
|
+
After the worker succeeds, Family verifies that the same member in the primary
|
|
294
|
+
checkout has not changed since the snapshot. It then materializes the worker's
|
|
295
|
+
final member-scoped state and serializes the normal template commit. Changes
|
|
296
|
+
outside the member root are rejected, as are concurrent edits to that member;
|
|
297
|
+
in either case the primary member is left untouched. Template summaries count
|
|
298
|
+
a member only after worktree materialization and any required commit succeed.
|
|
299
|
+
|
|
284
300
|
### Test concurrency
|
|
285
301
|
|
|
286
302
|
`kettle-family test --execute` runs independent sibling repositories in
|
|
@@ -1121,7 +1137,7 @@ Thanks for RTFM. ☺️
|
|
|
1121
1137
|
[📌gitmoji]: https://gitmoji.dev
|
|
1122
1138
|
[📌gitmoji-img]: https://img.shields.io/badge/gitmoji_commits-%20%F0%9F%98%9C%20%F0%9F%98%8D-34495e.svg?style=flat-square
|
|
1123
1139
|
[🧮kloc]: https://www.youtube.com/watch?v=dQw4w9WgXcQ
|
|
1124
|
-
[🧮kloc-img]: https://img.shields.io/badge/KLOC-6.
|
|
1140
|
+
[🧮kloc-img]: https://img.shields.io/badge/KLOC-6.570-FFDD67.svg?style=for-the-badge&logo=YouTube&logoColor=blue
|
|
1125
1141
|
[🔐security]: https://github.com/kettle-dev/kettle-family/blob/main/SECURITY.md
|
|
1126
1142
|
[🔐security-img]: https://img.shields.io/badge/security-policy-259D6C.svg?style=flat
|
|
1127
1143
|
[📄copyright-notice-explainer]: https://opensource.stackexchange.com/questions/5778/why-do-licenses-such-as-the-mit-license-specify-a-single-year
|
|
@@ -66,7 +66,8 @@ module Kettle
|
|
|
66
66
|
def obsolete_appraisal_edit(path, source, node)
|
|
67
67
|
return unless legacy_appraisal_call?(node)
|
|
68
68
|
|
|
69
|
-
|
|
69
|
+
preceding_newline = source.rindex("\n", node.location.start_offset - 1)
|
|
70
|
+
line_start = preceding_newline ? preceding_newline + 1 : 0
|
|
70
71
|
line_end = source.index("\n", node.location.end_offset) || source.length
|
|
71
72
|
line_end += 1 if line_end < source.length
|
|
72
73
|
if final_standalone_call?(source, node, line_start, line_end)
|
|
@@ -230,8 +230,8 @@ module Kettle
|
|
|
230
230
|
@otp_coordinator = otp_coordinator
|
|
231
231
|
end
|
|
232
232
|
|
|
233
|
-
def call(member:, phase:, command:, env: {}, interactive: false, stdout_line_handler: nil, log_path: nil, passthrough_output: true)
|
|
234
|
-
argv = command_argv(member: member, command: command, env: env)
|
|
233
|
+
def call(member:, phase:, command:, env: {}, interactive: false, raw: false, stdout_line_handler: nil, log_path: nil, passthrough_output: true)
|
|
234
|
+
argv = command_argv(member: member, command: command, env: env, raw: raw)
|
|
235
235
|
process_env = process_env(member: member, env: env)
|
|
236
236
|
spawn_options = process_options
|
|
237
237
|
return skipped_result(member: member, phase: phase, argv: argv) unless execute
|
|
@@ -598,9 +598,9 @@ module Kettle
|
|
|
598
598
|
nil
|
|
599
599
|
end
|
|
600
600
|
|
|
601
|
-
def command_argv(member:, command:, env: {})
|
|
601
|
+
def command_argv(member:, command:, env: {}, raw: false)
|
|
602
602
|
argv = normalize_command(command)
|
|
603
|
-
return argv
|
|
603
|
+
return argv if raw || !mise_configured?(member)
|
|
604
604
|
|
|
605
605
|
command_env = nonsensitive_env(env)
|
|
606
606
|
unset_env, set_env = command_env.partition { |_key, value| value.nil? }
|
data/lib/kettle/family/report.rb
CHANGED
|
@@ -332,11 +332,16 @@ module Kettle
|
|
|
332
332
|
template_results = results.select { |result| result.phase == "template" }
|
|
333
333
|
return if template_results.empty?
|
|
334
334
|
|
|
335
|
+
successful_names = summary_succeeded
|
|
336
|
+
template_results.select! do |result|
|
|
337
|
+
!result.ok? || successful_names.include?(result.member_name)
|
|
338
|
+
end
|
|
339
|
+
|
|
335
340
|
changed_files = template_results.sum { |result| template_changed_file_count(result) }
|
|
336
341
|
outcome_counts = template_results.map { |result| template_file_outcomes(result) }
|
|
337
342
|
lines << "template summary:"
|
|
338
|
-
lines << " #{
|
|
339
|
-
if outcome_counts.all?
|
|
343
|
+
lines << " #{successful_names.length}/#{selected_members.length} members ok"
|
|
344
|
+
if outcome_counts.any? && outcome_counts.all?
|
|
340
345
|
lines << " #{outcome_counts.sum { |outcomes| outcomes.fetch(:checksum_hits) }} checksum hits"
|
|
341
346
|
protected = outcome_counts.sum { |outcomes| outcomes.fetch(:checksum_protected) }
|
|
342
347
|
lines << " #{protected} checksum-protected changes" if protected.positive?
|
|
@@ -5,7 +5,7 @@ module Kettle
|
|
|
5
5
|
# Version namespace for this gem.
|
|
6
6
|
module Version
|
|
7
7
|
# Current gem version.
|
|
8
|
-
VERSION = "1.2.
|
|
8
|
+
VERSION = "1.2.92"
|
|
9
9
|
end
|
|
10
10
|
# Current gem version exposed at the traditional constant location.
|
|
11
11
|
VERSION = Version::VERSION # Traditional Constant Location
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
require "io/console"
|
|
4
4
|
require "fileutils"
|
|
5
|
+
require "digest"
|
|
5
6
|
require "json"
|
|
6
7
|
require "etc"
|
|
7
8
|
require "open3"
|
|
@@ -717,11 +718,14 @@ module Kettle
|
|
|
717
718
|
results << result
|
|
718
719
|
break unless result.ok?
|
|
719
720
|
|
|
720
|
-
|
|
721
|
+
entry = {
|
|
721
722
|
member: relocate_member_to_worktree(member, worktree_root, source_root: config.root),
|
|
722
723
|
original_member: member,
|
|
723
724
|
worktree_root: worktree_root
|
|
724
725
|
}
|
|
726
|
+
entries << entry
|
|
727
|
+
results.concat(worktree_mise_trust_results(entry, runner: runner, phase: "test_member_worktree_mise_trust"))
|
|
728
|
+
break unless results.last.ok?
|
|
725
729
|
end
|
|
726
730
|
[entries, results]
|
|
727
731
|
end
|
|
@@ -1038,11 +1042,18 @@ module Kettle
|
|
|
1038
1042
|
results << result
|
|
1039
1043
|
break unless result.ok?
|
|
1040
1044
|
|
|
1041
|
-
|
|
1045
|
+
entry = {
|
|
1042
1046
|
member: relocate_member_to_worktree(member, worktree_root, source_root: config.root),
|
|
1043
1047
|
original_member: member,
|
|
1044
1048
|
worktree_root: worktree_root
|
|
1045
1049
|
}
|
|
1050
|
+
entries << entry
|
|
1051
|
+
prepare_monorepo_template_worktree(entry)
|
|
1052
|
+
results.concat(worktree_mise_trust_results(entry, runner: runner, phase: "template_member_worktree_mise_trust"))
|
|
1053
|
+
break unless results.last.ok?
|
|
1054
|
+
rescue => error
|
|
1055
|
+
results << template_worktree_failure_result(member, error, phase: "template_member_worktree_seed")
|
|
1056
|
+
break
|
|
1046
1057
|
end
|
|
1047
1058
|
[entries, results]
|
|
1048
1059
|
end
|
|
@@ -1096,39 +1107,64 @@ module Kettle
|
|
|
1096
1107
|
def materialize_monorepo_template_changes(entry)
|
|
1097
1108
|
original_member = entry.fetch(:original_member)
|
|
1098
1109
|
worktree_root = entry.fetch(:worktree_root)
|
|
1099
|
-
relative_root =
|
|
1110
|
+
relative_root = monorepo_template_relative_root(original_member)
|
|
1100
1111
|
paths = monorepo_template_worktree_changed_paths(worktree_root)
|
|
1101
|
-
return template_worktree_failure_result(original_member, paths) if paths.is_a?(String)
|
|
1112
|
+
return template_worktree_failure_result(original_member, paths, phase: "template_worktree_materialize") if paths.is_a?(String)
|
|
1102
1113
|
|
|
1103
1114
|
invalid_paths = paths.reject { |path| path == relative_root || path.start_with?("#{relative_root}/") }
|
|
1104
1115
|
unless invalid_paths.empty?
|
|
1105
1116
|
return template_worktree_failure_result(
|
|
1106
1117
|
original_member,
|
|
1107
|
-
"isolated template worker modified shared path(s): #{invalid_paths.sort.join(", ")}; only #{relative_root}/ may be materialized"
|
|
1118
|
+
"isolated template worker modified shared path(s): #{invalid_paths.sort.join(", ")}; only #{relative_root}/ may be materialized",
|
|
1119
|
+
phase: "template_worktree_materialize"
|
|
1108
1120
|
)
|
|
1109
1121
|
end
|
|
1110
1122
|
|
|
1111
|
-
|
|
1112
|
-
|
|
1113
|
-
|
|
1114
|
-
|
|
1115
|
-
|
|
1116
|
-
|
|
1117
|
-
|
|
1118
|
-
|
|
1119
|
-
|
|
1120
|
-
source = Shellwords.escape(worktree_root)
|
|
1121
|
-
scope = Shellwords.escape(relative_root)
|
|
1122
|
-
["sh", "-lc", "git -C #{source} diff --binary HEAD -- #{scope} | git apply --whitespace=nowarn"]
|
|
1123
|
+
initial_state = entry.fetch(:primary_member_state)
|
|
1124
|
+
current_state = monorepo_template_member_state(config.root, relative_root)
|
|
1125
|
+
changed_primary_paths = changed_monorepo_template_state_paths(initial_state, current_state)
|
|
1126
|
+
unless changed_primary_paths.empty?
|
|
1127
|
+
return template_worktree_failure_result(
|
|
1128
|
+
original_member,
|
|
1129
|
+
"primary member changed while isolated template worker ran: #{changed_primary_paths.join(", ")}",
|
|
1130
|
+
phase: "template_worktree_materialize"
|
|
1131
|
+
)
|
|
1123
1132
|
end
|
|
1124
|
-
result = runner.call(member: control_member, phase: "template_worktree_materialize", command: command)
|
|
1125
|
-
return result unless result.ok?
|
|
1126
1133
|
|
|
1127
|
-
|
|
1128
|
-
|
|
1129
|
-
|
|
1134
|
+
materialized_paths = (entry.fetch(:primary_changed_paths) + paths).uniq.sort
|
|
1135
|
+
sync_monorepo_template_paths(worktree_root, config.root, materialized_paths)
|
|
1136
|
+
template_worktree_success_result(
|
|
1137
|
+
original_member,
|
|
1138
|
+
phase: "template_worktree_materialize",
|
|
1139
|
+
stdout: "#{materialized_paths.length} path(s) materialized from isolated worktree"
|
|
1140
|
+
)
|
|
1130
1141
|
rescue => error
|
|
1131
|
-
template_worktree_failure_result(original_member, error)
|
|
1142
|
+
template_worktree_failure_result(original_member, error, phase: "template_worktree_materialize")
|
|
1143
|
+
end
|
|
1144
|
+
|
|
1145
|
+
def prepare_monorepo_template_worktree(entry)
|
|
1146
|
+
original_member = entry.fetch(:original_member)
|
|
1147
|
+
relative_root = monorepo_template_relative_root(original_member)
|
|
1148
|
+
changed_paths = monorepo_template_worktree_changed_paths(config.root)
|
|
1149
|
+
raise Error, changed_paths if changed_paths.is_a?(String)
|
|
1150
|
+
|
|
1151
|
+
member_changed_paths = changed_paths.select do |path|
|
|
1152
|
+
path == relative_root || path.start_with?("#{relative_root}/")
|
|
1153
|
+
end
|
|
1154
|
+
sync_monorepo_template_paths(config.root, entry.fetch(:worktree_root), member_changed_paths)
|
|
1155
|
+
primary_state = monorepo_template_member_state(config.root, relative_root)
|
|
1156
|
+
worktree_state = monorepo_template_member_state(entry.fetch(:worktree_root), relative_root)
|
|
1157
|
+
unless primary_state == worktree_state
|
|
1158
|
+
changed = changed_monorepo_template_state_paths(primary_state, worktree_state)
|
|
1159
|
+
raise Error, "isolated template worker seed differs from primary member: #{changed.join(", ")}"
|
|
1160
|
+
end
|
|
1161
|
+
|
|
1162
|
+
entry[:primary_changed_paths] = member_changed_paths
|
|
1163
|
+
entry[:primary_member_state] = primary_state
|
|
1164
|
+
end
|
|
1165
|
+
|
|
1166
|
+
def monorepo_template_relative_root(member)
|
|
1167
|
+
Pathname.new(member.root).relative_path_from(Pathname.new(config.root)).to_s
|
|
1132
1168
|
end
|
|
1133
1169
|
|
|
1134
1170
|
def monorepo_template_worktree_changed_paths(worktree_root)
|
|
@@ -1149,6 +1185,28 @@ module Kettle
|
|
|
1149
1185
|
git_worktree_paths(worktree_root, %w[ls-files --others --exclude-standard -z])
|
|
1150
1186
|
end
|
|
1151
1187
|
|
|
1188
|
+
def monorepo_template_member_state(root, relative_root)
|
|
1189
|
+
paths = git_worktree_paths(root, ["ls-files", "--cached", "--others", "--exclude-standard", "-z", "--", relative_root])
|
|
1190
|
+
raise Error, paths if paths.is_a?(String)
|
|
1191
|
+
|
|
1192
|
+
paths.to_h { |path| [path, monorepo_template_path_signature(root, path)] }
|
|
1193
|
+
end
|
|
1194
|
+
|
|
1195
|
+
def monorepo_template_path_signature(root, path)
|
|
1196
|
+
full_path = File.join(root, path)
|
|
1197
|
+
stat = File.lstat(full_path)
|
|
1198
|
+
return ["symlink", File.readlink(full_path)] if stat.symlink?
|
|
1199
|
+
return ["file", stat.mode & 0o777, Digest::SHA256.file(full_path).hexdigest] if stat.file?
|
|
1200
|
+
|
|
1201
|
+
[stat.ftype, stat.mode & 0o777]
|
|
1202
|
+
rescue Errno::ENOENT
|
|
1203
|
+
["missing"]
|
|
1204
|
+
end
|
|
1205
|
+
|
|
1206
|
+
def changed_monorepo_template_state_paths(before, after)
|
|
1207
|
+
(before.keys | after.keys).select { |path| before[path] != after[path] }.sort
|
|
1208
|
+
end
|
|
1209
|
+
|
|
1152
1210
|
def git_worktree_paths(worktree_root, arguments)
|
|
1153
1211
|
stdout, stderr, status = Open3.capture3("git", *arguments, chdir: worktree_root)
|
|
1154
1212
|
return stdout.split("\0").reject(&:empty?) if status.success?
|
|
@@ -1156,14 +1214,19 @@ module Kettle
|
|
|
1156
1214
|
"git #{arguments.join(" ")} failed in #{worktree_root}: #{stderr.strip}"
|
|
1157
1215
|
end
|
|
1158
1216
|
|
|
1159
|
-
def
|
|
1160
|
-
|
|
1161
|
-
|
|
1217
|
+
def sync_monorepo_template_paths(source_root, destination_root, paths)
|
|
1218
|
+
paths.each do |path|
|
|
1219
|
+
source = File.join(source_root, path)
|
|
1220
|
+
destination = File.join(destination_root, path)
|
|
1221
|
+
FileUtils.rm_rf(destination)
|
|
1222
|
+
next unless File.exist?(source) || File.symlink?(source)
|
|
1162
1223
|
|
|
1163
|
-
source = File.join(worktree_root, path)
|
|
1164
|
-
destination = File.join(primary_root, path)
|
|
1165
1224
|
FileUtils.mkdir_p(File.dirname(destination))
|
|
1166
|
-
|
|
1225
|
+
if File.symlink?(source)
|
|
1226
|
+
File.symlink(File.readlink(source), destination)
|
|
1227
|
+
else
|
|
1228
|
+
FileUtils.cp_r(source, destination, preserve: true)
|
|
1229
|
+
end
|
|
1167
1230
|
end
|
|
1168
1231
|
end
|
|
1169
1232
|
|
|
@@ -1178,12 +1241,28 @@ module Kettle
|
|
|
1178
1241
|
end
|
|
1179
1242
|
end
|
|
1180
1243
|
|
|
1181
|
-
def
|
|
1244
|
+
def template_worktree_success_result(member, phase:, stdout:)
|
|
1245
|
+
CommandResult.new(
|
|
1246
|
+
member_name: member.name,
|
|
1247
|
+
phase: phase,
|
|
1248
|
+
command: ["internal", phase.tr("_", "-")],
|
|
1249
|
+
workdir: member.root,
|
|
1250
|
+
status: 0,
|
|
1251
|
+
success: true,
|
|
1252
|
+
stdout: stdout,
|
|
1253
|
+
stderr: "",
|
|
1254
|
+
elapsed_seconds: 0.0,
|
|
1255
|
+
skipped: false,
|
|
1256
|
+
reason: nil
|
|
1257
|
+
)
|
|
1258
|
+
end
|
|
1259
|
+
|
|
1260
|
+
def template_worktree_failure_result(member, error, phase: "template_member_worktree")
|
|
1182
1261
|
message = error.is_a?(Exception) ? "#{error.class}: #{error.message}" : error.to_s
|
|
1183
1262
|
CommandResult.new(
|
|
1184
1263
|
member_name: member.name,
|
|
1185
|
-
phase:
|
|
1186
|
-
command: ["internal", "
|
|
1264
|
+
phase: phase,
|
|
1265
|
+
command: ["internal", phase.tr("_", "-")],
|
|
1187
1266
|
workdir: member.root,
|
|
1188
1267
|
status: 1,
|
|
1189
1268
|
success: false,
|
|
@@ -1435,7 +1514,11 @@ module Kettle
|
|
|
1435
1514
|
outcomes << add
|
|
1436
1515
|
break unless add.ok?
|
|
1437
1516
|
|
|
1438
|
-
|
|
1517
|
+
entry = {branch: branch, member: relocate_member_to_worktree(member, worktree_root), worktree_root: worktree_root}
|
|
1518
|
+
entries << entry
|
|
1519
|
+
trust_results = worktree_mise_trust_results(entry.merge(original_member: member), runner: runner, phase: "template_worktree_mise_trust")
|
|
1520
|
+
outcomes.concat(trust_results)
|
|
1521
|
+
break unless trust_results.all?(&:ok?)
|
|
1439
1522
|
end
|
|
1440
1523
|
|
|
1441
1524
|
if outcomes.all?(&:ok?)
|
|
@@ -1491,6 +1574,45 @@ module Kettle
|
|
|
1491
1574
|
end
|
|
1492
1575
|
end
|
|
1493
1576
|
|
|
1577
|
+
# A git worktree has a distinct absolute path, and Mise deliberately
|
|
1578
|
+
# tracks trust by config path. Trust every active config between the
|
|
1579
|
+
# worktree root and the member command directory before invoking a
|
|
1580
|
+
# command there. This bootstrap must run raw: wrapping it in `mise exec`
|
|
1581
|
+
# would require the trust it is establishing.
|
|
1582
|
+
def worktree_mise_trust_results(entry, runner:, phase:)
|
|
1583
|
+
member = entry.fetch(:member)
|
|
1584
|
+
original_member = entry.fetch(:original_member, member)
|
|
1585
|
+
worktree_root = entry.fetch(:worktree_root)
|
|
1586
|
+
mise_config_roots_in_worktree(worktree_root, member.root).map do |root|
|
|
1587
|
+
result = runner.call(
|
|
1588
|
+
member: member,
|
|
1589
|
+
phase: phase,
|
|
1590
|
+
command: ["mise", "trust", "-C", root],
|
|
1591
|
+
raw: true
|
|
1592
|
+
)
|
|
1593
|
+
result.member_name = original_member.name
|
|
1594
|
+
result
|
|
1595
|
+
end
|
|
1596
|
+
end
|
|
1597
|
+
|
|
1598
|
+
def mise_config_roots_in_worktree(worktree_root, member_root)
|
|
1599
|
+
root_path = Pathname.new(worktree_root).realpath
|
|
1600
|
+
member_path = Pathname.new(member_root).realpath
|
|
1601
|
+
return [] unless member_path.to_s.start_with?("#{root_path}#{File::SEPARATOR}") || member_path == root_path
|
|
1602
|
+
|
|
1603
|
+
current = member_path
|
|
1604
|
+
roots = []
|
|
1605
|
+
loop do
|
|
1606
|
+
roots << current.to_s if %w[mise.toml .mise.toml].any? { |name| File.file?(current.join(name)) }
|
|
1607
|
+
break if current == root_path
|
|
1608
|
+
|
|
1609
|
+
current = current.parent
|
|
1610
|
+
end
|
|
1611
|
+
roots.reverse
|
|
1612
|
+
rescue Errno::ENOENT
|
|
1613
|
+
[]
|
|
1614
|
+
end
|
|
1615
|
+
|
|
1494
1616
|
def template_branch_worktree_entries_results(entries)
|
|
1495
1617
|
wave_jobs = template_jobs(entries)
|
|
1496
1618
|
queue = Queue.new
|
|
@@ -2038,7 +2160,10 @@ module Kettle
|
|
|
2038
2160
|
results << result
|
|
2039
2161
|
break unless result.ok?
|
|
2040
2162
|
|
|
2041
|
-
|
|
2163
|
+
entry = {member: relocate_member_to_worktree(member, root, source_root: config.root), original_member: member, worktree_root: root}
|
|
2164
|
+
entries << entry
|
|
2165
|
+
results.concat(worktree_mise_trust_results(entry, runner: runner, phase: "release_worktree_mise_trust"))
|
|
2166
|
+
break unless results.last.ok?
|
|
2042
2167
|
end
|
|
2043
2168
|
[entries, results]
|
|
2044
2169
|
end
|
|
@@ -4296,13 +4421,25 @@ module Kettle
|
|
|
4296
4421
|
def emit_template_progress_summary(results, progress:)
|
|
4297
4422
|
return unless progress
|
|
4298
4423
|
|
|
4299
|
-
|
|
4424
|
+
successful_names = successful_template_member_names(results)
|
|
4425
|
+
template_results = results.select do |result|
|
|
4426
|
+
result.phase == "template" && (!result.ok? || successful_names.include?(result.member_name))
|
|
4427
|
+
end
|
|
4300
4428
|
changed_files = template_results.sum { |result| template_changed_file_count(result) }
|
|
4301
4429
|
outcome_counts = template_results.map { |result| template_file_outcomes(result) }
|
|
4302
4430
|
progress.stop
|
|
4303
4431
|
progress.summary(template_progress_summary_label(template_results, changed_files, outcome_counts))
|
|
4304
4432
|
end
|
|
4305
4433
|
|
|
4434
|
+
def successful_template_member_names(results)
|
|
4435
|
+
results.group_by(&:member_name).filter_map do |member_name, member_results|
|
|
4436
|
+
next unless member_results.any? { |result| result.phase == "template" }
|
|
4437
|
+
next unless member_results.all?(&:ok?)
|
|
4438
|
+
|
|
4439
|
+
member_name
|
|
4440
|
+
end
|
|
4441
|
+
end
|
|
4442
|
+
|
|
4306
4443
|
def emit_release_progress_summary(results, progress:)
|
|
4307
4444
|
return unless progress
|
|
4308
4445
|
|
|
@@ -4763,7 +4900,7 @@ module Kettle
|
|
|
4763
4900
|
|
|
4764
4901
|
def template_progress_summary_label(template_results, changed_files, outcome_counts)
|
|
4765
4902
|
label = "template summary: #{template_results.count(&:ok?)}/#{members.length} members ok"
|
|
4766
|
-
return "#{label}, #{template_summary_label(changed_files)}"
|
|
4903
|
+
return "#{label}, #{template_summary_label(changed_files)}" if outcome_counts.empty? || !outcome_counts.all?
|
|
4767
4904
|
|
|
4768
4905
|
totals = outcome_counts.each_with_object({checksum_hits: 0, checksum_protected: 0, unchanged: 0}) do |outcomes, memo|
|
|
4769
4906
|
memo[:checksum_hits] += outcomes.fetch(:checksum_hits)
|
data.tar.gz.sig
CHANGED
|
Binary file
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: kettle-family
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 1.2.
|
|
4
|
+
version: 1.2.92
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Peter H. Boling
|
|
@@ -443,10 +443,10 @@ licenses:
|
|
|
443
443
|
- AGPL-3.0-only
|
|
444
444
|
metadata:
|
|
445
445
|
homepage_uri: https://kettle-family.galtzo.com
|
|
446
|
-
source_code_uri: https://github.com/kettle-dev/kettle-family/tree/v1.2.
|
|
447
|
-
changelog_uri: https://github.com/kettle-dev/kettle-family/blob/v1.2.
|
|
446
|
+
source_code_uri: https://github.com/kettle-dev/kettle-family/tree/v1.2.92
|
|
447
|
+
changelog_uri: https://github.com/kettle-dev/kettle-family/blob/v1.2.92/CHANGELOG.md
|
|
448
448
|
bug_tracker_uri: https://github.com/kettle-dev/kettle-family/issues
|
|
449
|
-
documentation_uri: https://www.rubydoc.info/gems/kettle-family/1.2.
|
|
449
|
+
documentation_uri: https://www.rubydoc.info/gems/kettle-family/1.2.92
|
|
450
450
|
funding_uri: https://github.com/sponsors/pboling
|
|
451
451
|
wiki_uri: https://github.com/kettle-dev/kettle-family/wiki
|
|
452
452
|
news_uri: https://www.railsbling.com/tags/kettle-family
|
metadata.gz.sig
CHANGED
|
Binary file
|