samagotchi 0.156.0 → 0.158.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 +70 -1
- data/README.md +1 -1
- data/bin/chi +4 -1
- data/docs/architecture.md +18 -12
- data/docs/bundle-authoring.md +5 -5
- data/docs/bundles.md +4 -1
- data/docs/cli.md +29 -24
- data/docs/configuration.md +43 -9
- data/docs/context.md +5 -3
- data/docs/guardrails.md +12 -7
- data/docs/hooks.md +53 -83
- data/docs/internals/bridge.md +84 -7
- data/docs/internals/bundles.md +69 -46
- data/docs/internals/context-telemetry.md +4 -3
- data/docs/internals/engine.md +23 -7
- data/docs/internals/participants.md +3 -2
- data/docs/internals/remote-sessions.md +5 -2
- data/docs/internals/web-frontend.md +3 -2
- data/docs/internals/working-set.md +1 -1
- data/docs/memory.md +51 -10
- data/docs/plugins.md +48 -11
- data/docs/releasing.md +6 -5
- data/docs/sessions.md +13 -13
- data/docs/sub-agent.md +17 -2
- data/docs/testing.md +56 -5
- data/lib/samagotchi/answer_command.rb +2 -1
- data/lib/samagotchi/archive_store.rb +4 -3
- data/lib/samagotchi/bootstrap_command.rb +3 -2
- data/lib/samagotchi/bridge/card_places.rb +5 -7
- data/lib/samagotchi/bridge/card_store.rb +3 -0
- data/lib/samagotchi/bridge/turn_accumulator.rb +113 -10
- data/lib/samagotchi/bridge/turn_drawings.rb +271 -0
- data/lib/samagotchi/bridge.rb +31 -6
- data/lib/samagotchi/broadcast/recipients.rb +8 -8
- data/lib/samagotchi/broadcast/triage_model.rb +1 -1
- data/lib/samagotchi/bundle_command/check.rb +41 -10
- data/lib/samagotchi/bundle_command/installed_check.rb +13 -2
- data/lib/samagotchi/bundle_command/try/run.rb +4 -1
- data/lib/samagotchi/bundle_command/try/sandbox.rb +1 -1
- data/lib/samagotchi/bundle_command/try/trace.rb +1 -1
- data/lib/samagotchi/bundle_command.rb +20 -45
- data/lib/samagotchi/bundles/known-names/manifest.yml +4 -7
- data/lib/samagotchi/bundles/known-names/{hooks/known_names.rb → plugin.rb} +17 -0
- data/lib/samagotchi/bundles/source-links/manifest.yml +4 -7
- data/lib/samagotchi/bundles/source-links/{hooks/source_links.rb → plugin.rb} +23 -4
- data/lib/samagotchi/bundles/system/config_modification_protocol.md +1 -1
- data/lib/samagotchi/bundles/system/manifest.yml +4 -4
- data/lib/samagotchi/bundles/system/memory_guide.md +7 -7
- data/lib/samagotchi/bundles/system/self_map.md +1 -1
- data/lib/samagotchi/child_move.rb +6 -6
- data/lib/samagotchi/child_reports.rb +2 -2
- data/lib/samagotchi/children_status.rb +15 -21
- data/lib/samagotchi/cli/parent_wait.rb +8 -22
- data/lib/samagotchi/config.rb +3 -0
- data/lib/samagotchi/context_command.rb +7 -5
- data/lib/samagotchi/context_sources.rb +9 -6
- data/lib/samagotchi/context_window.rb +9 -12
- data/lib/samagotchi/effective_model.rb +57 -22
- data/lib/samagotchi/engine/plugins.rb +1 -1
- data/lib/samagotchi/engine/recap.rb +8 -4
- data/lib/samagotchi/engine.rb +30 -9
- data/lib/samagotchi/events.rb +3 -2
- data/lib/samagotchi/extension_load.rb +26 -44
- data/lib/samagotchi/guardrails/load_failures.rb +3 -2
- data/lib/samagotchi/guardrails/path_resolve.rb +11 -0
- data/lib/samagotchi/guardrails/review_paths.rb +1 -5
- data/lib/samagotchi/guardrails_command.rb +1 -1
- data/lib/samagotchi/hook_routes.rb +2 -2
- data/lib/samagotchi/hooks.rb +2 -3
- data/lib/samagotchi/host_address.rb +52 -0
- data/lib/samagotchi/host_registry.rb +57 -45
- data/lib/samagotchi/kernel_loop.rb +7 -3
- data/lib/samagotchi/llm/chat_loop.rb +6 -3
- data/lib/samagotchi/llm_context_strategy.rb +27 -63
- data/lib/samagotchi/memory_bundle/asset_installer.rb +92 -177
- data/lib/samagotchi/memory_bundle/builder.rb +12 -35
- data/lib/samagotchi/memory_bundle/bundle_hook.rb +6 -31
- data/lib/samagotchi/memory_bundle/file_actions.rb +193 -0
- data/lib/samagotchi/memory_bundle/file_digest.rb +13 -0
- data/lib/samagotchi/memory_bundle/file_state.rb +152 -0
- data/lib/samagotchi/memory_bundle/installed_bundle.rb +1 -1
- data/lib/samagotchi/memory_bundle/installed_copy.rb +13 -19
- data/lib/samagotchi/memory_bundle/installed_manifest.rb +10 -17
- data/lib/samagotchi/memory_bundle/installer.rb +212 -178
- data/lib/samagotchi/memory_bundle/manifest.rb +51 -58
- data/lib/samagotchi/memory_bundle/merger.rb +69 -65
- data/lib/samagotchi/memory_bundle/plugin_ref.rb +6 -5
- data/lib/samagotchi/memory_bundle/profile.rb +5 -5
- data/lib/samagotchi/memory_bundle/provenance.rb +26 -52
- data/lib/samagotchi/memory_bundle/remerge.rb +31 -14
- data/lib/samagotchi/memory_bundle/shas.rb +5 -8
- data/lib/samagotchi/memory_bundle/shipped_update.rb +51 -11
- data/lib/samagotchi/memory_bundle/status.rb +26 -30
- data/lib/samagotchi/memory_bundle/system_bundle.rb +39 -10
- data/lib/samagotchi/memory_bundle/trash.rb +28 -18
- data/lib/samagotchi/memory_bundle/uninstaller.rb +16 -11
- data/lib/samagotchi/memory_orphans.rb +4 -4
- data/lib/samagotchi/memory_paths.rb +102 -13
- data/lib/samagotchi/memory_usage.rb +1 -1
- data/lib/samagotchi/model_lookup.rb +75 -0
- data/lib/samagotchi/model_profile.rb +1 -0
- data/lib/samagotchi/models_command.rb +1 -1
- data/lib/samagotchi/note_command.rb +1 -1
- data/lib/samagotchi/participant_mode.rb +2 -1
- data/lib/samagotchi/plugin/api.rb +42 -10
- data/lib/samagotchi/plugin/loader.rb +75 -15
- data/lib/samagotchi/plugin/sessions.rb +1 -1
- data/lib/samagotchi/project_command.rb +167 -0
- data/lib/samagotchi/project_init.rb +69 -0
- data/lib/samagotchi/project_scope.rb +6 -4
- data/lib/samagotchi/prompt_files.rb +10 -7
- data/lib/samagotchi/reasoning_history.rb +14 -20
- data/lib/samagotchi/recap_place.rb +26 -0
- data/lib/samagotchi/reply_wait/liveness.rb +38 -0
- data/lib/samagotchi/reply_wait/poll.rb +44 -15
- data/lib/samagotchi/reply_wait/target.rb +113 -0
- data/lib/samagotchi/reply_wait.rb +22 -2
- data/lib/samagotchi/run_options.rb +1 -0
- data/lib/samagotchi/sampling_settings.rb +9 -16
- data/lib/samagotchi/self_report.rb +34 -35
- data/lib/samagotchi/send_command.rb +17 -23
- data/lib/samagotchi/session.rb +349 -441
- data/lib/samagotchi/session_access/contract.rb +1 -1
- data/lib/samagotchi/session_access/local.rb +5 -6
- data/lib/samagotchi/session_access/remote.rb +5 -3
- data/lib/samagotchi/session_field.rb +134 -0
- data/lib/samagotchi/session_inbox.rb +49 -1
- data/lib/samagotchi/session_manager/emptiness.rb +2 -1
- data/lib/samagotchi/session_manager/listing.rb +29 -100
- data/lib/samagotchi/session_manager.rb +2 -1
- data/lib/samagotchi/session_metrics.rb +7 -4
- data/lib/samagotchi/session_paths.rb +57 -0
- data/lib/samagotchi/session_retention.rb +2 -2
- data/lib/samagotchi/session_row.rb +200 -0
- data/lib/samagotchi/session_setup.rb +15 -12
- data/lib/samagotchi/sessions_command.rb +11 -129
- data/lib/samagotchi/steer.rb +10 -0
- data/lib/samagotchi/steer_cut_after.rb +7 -14
- data/lib/samagotchi/system_prompt.rb +20 -11
- data/lib/samagotchi/terminal_ui/attached_loop.rb +6 -26
- data/lib/samagotchi/terminal_ui/join_replay.rb +91 -8
- data/lib/samagotchi/terminal_ui/local_link.rb +2 -2
- data/lib/samagotchi/terminal_ui.rb +3 -1
- data/lib/samagotchi/testing/bundle.rb +5 -12
- data/lib/samagotchi/testing/install.rb +5 -4
- data/lib/samagotchi/testing/recorder.rb +1 -1
- data/lib/samagotchi/testing.rb +1 -1
- data/lib/samagotchi/thinking.rb +16 -21
- data/lib/samagotchi/tool_declarations.rb +1 -1
- data/lib/samagotchi/tool_response.rb +5 -3
- data/lib/samagotchi/tool_runner.rb +18 -1
- data/lib/samagotchi/toolkit_binding.rb +1 -4
- data/lib/samagotchi/toolkit_command.rb +1 -4
- data/lib/samagotchi/tools/delegate.rb +60 -51
- data/lib/samagotchi/tools/delegate_relay.rb +5 -5
- data/lib/samagotchi/tools/delegate_result.rb +1 -1
- data/lib/samagotchi/tools/delegate_wait.rb +41 -17
- data/lib/samagotchi/tools/list_sessions.rb +7 -7
- data/lib/samagotchi/update_command.rb +11 -7
- data/lib/samagotchi/verify_fresh.rb +7 -11
- data/lib/samagotchi/version.rb +1 -1
- data/lib/samagotchi/vision_support.rb +11 -14
- data/lib/samagotchi/web/app.rb +66 -21
- data/lib/samagotchi/web/message_display.rb +57 -7
- data/lib/samagotchi/web/participant_access.rb +1 -1
- data/lib/samagotchi/web/project_list.rb +3 -2
- data/lib/samagotchi/web/public/activity.js +16 -7
- data/lib/samagotchi/web/public/app.js +29 -8
- data/lib/samagotchi/web/public/bubbles.js +8 -3
- data/lib/samagotchi/web/public/cards_place.js +15 -0
- data/lib/samagotchi/web/public/commands.js +5 -1
- data/lib/samagotchi/web/public/data.js +4 -2
- data/lib/samagotchi/web/public/history_shape.js +220 -0
- data/lib/samagotchi/web/public/index.html +6 -1
- data/lib/samagotchi/web/public/project_picker.js +86 -4
- data/lib/samagotchi/web/public/recap_stamp.js +17 -0
- data/lib/samagotchi/web/public/reload_check.js +71 -0
- data/lib/samagotchi/web/public/stage_model.js +7 -5
- data/lib/samagotchi/web/public/stage_view.js +7 -3
- data/lib/samagotchi/web/public/stream_handlers.js +8 -2
- data/lib/samagotchi/web/public/timing.js +43 -2
- data/lib/samagotchi/web/public/turn_block.js +215 -30
- data/lib/samagotchi/web/public/turn_drawing.js +43 -0
- data/lib/samagotchi/web/public/turn_events.js +10 -1
- data/lib/samagotchi/web/public/turn_pieces.js +29 -0
- data/lib/samagotchi/web/reload_check_route.rb +95 -0
- data/lib/samagotchi/web/server.rb +4 -2
- data/lib/samagotchi/web/session_access_routes.rb +3 -3
- data/lib/samagotchi/web/session_control.rb +1 -1
- data/lib/samagotchi/web/session_hub.rb +9 -17
- data/lib/samagotchi/web/session_show.rb +64 -2
- data/lib/samagotchi/web/session_summary.rb +36 -24
- data/lib/samagotchi/worker.rb +28 -5
- data/lib/samagotchi/worker_link.rb +3 -0
- metadata +22 -5
- data/lib/samagotchi/hooks/bundle_loader.rb +0 -191
- data/lib/samagotchi/send_command/wait_target.rb +0 -85
|
@@ -8,7 +8,6 @@ require "tmpdir"
|
|
|
8
8
|
require_relative "../atomic_file"
|
|
9
9
|
require_relative "../memory_paths"
|
|
10
10
|
require_relative "../version"
|
|
11
|
-
require_relative "bundle_hook"
|
|
12
11
|
require_relative "file_digest"
|
|
13
12
|
require_relative "installed_bundle"
|
|
14
13
|
require_relative "manifest"
|
|
@@ -159,6 +158,16 @@ module Samagotchi
|
|
|
159
158
|
unchecked?(recorded) || file_sha(path) == recorded_sha(recorded)
|
|
160
159
|
end
|
|
161
160
|
|
|
161
|
+
# Whether a memory on disk is what the install wrote: the entry's
|
|
162
|
+
# recorded checksum, else its base copy. Unknown counts as edited. The
|
|
163
|
+
# one rule a prune, an uninstall's refusal and chi bundle status read.
|
|
164
|
+
def self.unchanged?(path, entry, base_path)
|
|
165
|
+
recorded = entry[:checksum]
|
|
166
|
+
return sha_matches?(path, recorded) unless recorded_sha(recorded).empty?
|
|
167
|
+
|
|
168
|
+
File.exist?(base_path) && file_sha(path) == file_sha(base_path)
|
|
169
|
+
end
|
|
170
|
+
|
|
162
171
|
# The record's mirror of the manifest keys (InstalledBundle::MIRROR_KEYS)
|
|
163
172
|
# as #write records them from its kwargs: what an older chi reads, and
|
|
164
173
|
# what #adopt_manifest holds a source's manifest against.
|
|
@@ -185,12 +194,14 @@ module Samagotchi
|
|
|
185
194
|
# the record. nil keeps an entry with no base (a kept edit that has
|
|
186
195
|
# none: the user's file must never become it), with its recorded
|
|
187
196
|
# checksum.
|
|
188
|
-
# @param hooks_files [Hash] basename => path for hook file snapshots (optional)
|
|
189
197
|
# @param guardrails_files [Hash] basename => installed rule file; the
|
|
190
198
|
# sha256 of each is recorded (the Engine checks it at load)
|
|
191
199
|
# @param plugin_file [String, nil] the installed plugin file; its name
|
|
192
200
|
# and sha256 are recorded (the Engine checks it at load) with a base
|
|
193
201
|
# snapshot
|
|
202
|
+
# @param plugin_required [Boolean] the manifest's plugin: required: true
|
|
203
|
+
# (a guardrail plugin), recorded with it as "required": true, so the
|
|
204
|
+
# Engine knows it whatever becomes of the kept manifest.yml
|
|
194
205
|
# @param requires_chi [String, nil] the manifest's requirement
|
|
195
206
|
# @param needs [Array<Hash>] the manifest's needs ({command:, why:, hint:})
|
|
196
207
|
# @param marks [Array<FileStanding::Behind>] files an upgrade left behind;
|
|
@@ -216,8 +227,10 @@ module Samagotchi
|
|
|
216
227
|
# (requires_chi … setting_types) are still written from the kwargs:
|
|
217
228
|
# an older chi sharing this memories dir reads only them
|
|
218
229
|
# (docs/internals/bundles.md); a key a later manifest gains is never added
|
|
219
|
-
|
|
220
|
-
|
|
230
|
+
# The record has no hooks ("hooks" => {}: class-file hooks are retired)
|
|
231
|
+
# and an older record's hook bases go.
|
|
232
|
+
def write(files:, scope:, version:, source_path:, trust_level: nil, source_commit: nil,
|
|
233
|
+
guardrails_files: {}, plugin_file: nil, plugin_required: false, requires_chi: nil, needs: nil, marks: [], incoming_dir: nil, includes: nil,
|
|
221
234
|
scripts_files: {}, context_providers: [], block_renderers: [], file_descriptions: {}, session_settings: [], session_setting_types: {}, settings: nil, setting_types: {},
|
|
222
235
|
dev: false, manifest_from: nil)
|
|
223
236
|
FileUtils.mkdir_p(@bundle_dir)
|
|
@@ -262,40 +275,8 @@ module Samagotchi
|
|
|
262
275
|
end
|
|
263
276
|
end
|
|
264
277
|
|
|
265
|
-
#
|
|
266
|
-
|
|
267
|
-
# Incoming hooks: basename => BundleHook (or its metadata hash)
|
|
268
|
-
normalized_hooks = {}
|
|
269
|
-
if hooks.is_a?(Hash)
|
|
270
|
-
hooks.each do |k, v|
|
|
271
|
-
next unless k.is_a?(String) && !k.empty?
|
|
272
|
-
next unless v.is_a?(Hash) || v.is_a?(BundleHook)
|
|
273
|
-
|
|
274
|
-
normalized_hooks[k] = BundleHook.parse(v)
|
|
275
|
-
end
|
|
276
|
-
end
|
|
277
|
-
# Prune stale hook bases
|
|
278
|
-
existing&.hooks&.each_key do |old_key_str|
|
|
279
|
-
FileUtils.rm_f(File.join(bases_dir, old_key_str)) unless normalized_hooks.key?(old_key_str)
|
|
280
|
-
end
|
|
281
|
-
# Save base snapshots for hooks where a file path was provided, and
|
|
282
|
-
# record the sha256 of the installed file itself: the load-time check
|
|
283
|
-
# compares against it, and a declared sha can be wrong (installing
|
|
284
|
-
# only warns about that).
|
|
285
|
-
hooks_files = {} unless hooks_files.is_a?(Hash)
|
|
286
|
-
normalized_hooks.each do |k, hook|
|
|
287
|
-
src = hooks_files[k] || hooks_files[k.to_sym]
|
|
288
|
-
hook = hook.with(sha256: BundleHook.of_file(src.to_s).sha256) if src && File.exist?(src.to_s)
|
|
289
|
-
hook = hook.with(sha256: DEV_SHA) if dev
|
|
290
|
-
hooks_map[k] = hook.to_record
|
|
291
|
-
next unless src && File.exist?(src.to_s)
|
|
292
|
-
|
|
293
|
-
begin
|
|
294
|
-
File.write(File.join(bases_dir, k), File.read(src.to_s))
|
|
295
|
-
rescue StandardError
|
|
296
|
-
nil
|
|
297
|
-
end
|
|
298
|
-
end
|
|
278
|
+
# An older chi's hooks: their bases go with them (AssetInstaller#retire_hooks).
|
|
279
|
+
existing&.hooks&.each_key { |old_key_str| FileUtils.rm_f(File.join(bases_dir, old_key_str)) }
|
|
299
280
|
|
|
300
281
|
manifest_data = {
|
|
301
282
|
"name" => @name,
|
|
@@ -304,7 +285,7 @@ module Samagotchi
|
|
|
304
285
|
"source" => source_path.to_s,
|
|
305
286
|
"installed_at" => DateTime.now.iso8601,
|
|
306
287
|
"files" => merged_entries,
|
|
307
|
-
"hooks" =>
|
|
288
|
+
"hooks" => {}
|
|
308
289
|
}
|
|
309
290
|
manifest_data["dev"] = true if dev
|
|
310
291
|
if guardrails_files.is_a?(Hash) && !guardrails_files.empty?
|
|
@@ -318,6 +299,7 @@ module Samagotchi
|
|
|
318
299
|
content = File.binread(plugin_file.to_s) # the base snapshot's bytes
|
|
319
300
|
manifest_data["plugin"] = { "file" => File.basename(plugin_file.to_s),
|
|
320
301
|
"sha256" => dev ? DEV_SHA : FileDigest.sha256(plugin_file.to_s) }
|
|
302
|
+
manifest_data["plugin"]["required"] = true if plugin_required
|
|
321
303
|
FileUtils.mkdir_p(File.join(bases_dir, "plugin"))
|
|
322
304
|
File.binwrite(plugin_base_path(File.basename(plugin_file.to_s)), content)
|
|
323
305
|
end
|
|
@@ -446,7 +428,7 @@ module Samagotchi
|
|
|
446
428
|
# wrote it, or the file is gone), when +dir+ is the source of what's
|
|
447
429
|
# installed: its manifest names this bundle at the recorded version and
|
|
448
430
|
# says what the record keeps (#manifest_difference), and every sha the
|
|
449
|
-
# record has for a memory, plugin,
|
|
431
|
+
# record has for a memory, plugin, script or rule is the source
|
|
450
432
|
# file's. Then the file is copied to #kept_manifest_path and the
|
|
451
433
|
# record gains its manifest_sha, and nothing else changes (installed_at,
|
|
452
434
|
# the mirror, installed_by). A dev record is never adopted: its next
|
|
@@ -571,8 +553,8 @@ module Samagotchi
|
|
|
571
553
|
# The first manifest key in which +manifest+ says other than what the
|
|
572
554
|
# record keeps, nil when none: each mirrored key as #write records it (one
|
|
573
555
|
# the record lacks is fine only when an older chi couldn't have known it:
|
|
574
|
-
# Manifest::SINCE has it)
|
|
575
|
-
#
|
|
556
|
+
# Manifest::SINCE has it). An older record's hooks don't count: they
|
|
557
|
+
# no longer load, and the source can't list any (Manifest::HooksRetired).
|
|
576
558
|
def manifest_difference(bundle, manifest)
|
|
577
559
|
recorded = plain(bundle.mirror)
|
|
578
560
|
source = plain(self.class.mirror_fields(
|
|
@@ -581,18 +563,11 @@ module Samagotchi
|
|
|
581
563
|
session_settings: manifest.session_settings, session_setting_types: manifest.session_setting_types,
|
|
582
564
|
settings: manifest.settings, setting_types: manifest.setting_types
|
|
583
565
|
))
|
|
584
|
-
|
|
566
|
+
InstalledBundle::MIRROR_KEYS.find do |key|
|
|
585
567
|
next false if recorded[key] == source[key]
|
|
586
568
|
|
|
587
569
|
!(recorded[key].nil? && Manifest::SINCE.key?(MIRROR_SINCE.fetch(key, key)))
|
|
588
570
|
end
|
|
589
|
-
return differing if differing
|
|
590
|
-
|
|
591
|
-
same_hooks = bundle.hooks.all? do |file, hook|
|
|
592
|
-
theirs = manifest.hooks.fetch(file, BundleHook.new)
|
|
593
|
-
[hook.events, hook.on_error, hook.priority] == [theirs.events, theirs.on_error, theirs.priority]
|
|
594
|
-
end
|
|
595
|
-
same_hooks ? nil : "hooks"
|
|
596
571
|
end
|
|
597
572
|
private :manifest_difference
|
|
598
573
|
|
|
@@ -600,7 +575,7 @@ module Samagotchi
|
|
|
600
575
|
private :plain
|
|
601
576
|
|
|
602
577
|
# Each file the record has a sha for, by its path in the bundle's source
|
|
603
|
-
# => that sha: the plugin,
|
|
578
|
+
# => that sha: the plugin, scripts, rules, and each memory whose
|
|
604
579
|
# entry has a checksum and no conflict mark (its base is the bundle's text).
|
|
605
580
|
def installed_shas(bundle, manifest)
|
|
606
581
|
shas = bundle.files.each_with_object({}) do |(key, entry), acc|
|
|
@@ -610,7 +585,6 @@ module Samagotchi
|
|
|
610
585
|
file = manifest.plugin && manifest.plugin[:file].to_s
|
|
611
586
|
shas[file && File.basename(file) == bundle.plugin.file ? file : bundle.plugin.file] = bundle.plugin.sha256
|
|
612
587
|
end
|
|
613
|
-
bundle.hooks.each { |file, hook| shas["hooks/#{file}"] = hook.sha256 }
|
|
614
588
|
bundle.scripts.each { |file, sha| shas["scripts/#{file}"] = sha }
|
|
615
589
|
bundle.guardrails.each { |file, sha| shas["guardrails/#{file}"] = sha }
|
|
616
590
|
shas
|
|
@@ -3,6 +3,8 @@
|
|
|
3
3
|
require_relative "index_updater"
|
|
4
4
|
require_relative "manifest"
|
|
5
5
|
require_relative "memory_files"
|
|
6
|
+
require_relative "file_actions"
|
|
7
|
+
require_relative "file_digest"
|
|
6
8
|
require_relative "merger"
|
|
7
9
|
require_relative "provenance"
|
|
8
10
|
require_relative "trash"
|
|
@@ -17,7 +19,8 @@ module Samagotchi
|
|
|
17
19
|
# scripts, and GuardrailWiring#rules stats per tool call, so a live session
|
|
18
20
|
# could hit a transient deny.
|
|
19
21
|
#
|
|
20
|
-
# Each marked file, by
|
|
22
|
+
# Each marked file, by its FileState and REMERGE row (FileActions; a
|
|
23
|
+
# :decide cell by what Merger::Upgrade says of it):
|
|
21
24
|
# - not shipped now: its mark, base and text stay, and it reads as behind
|
|
22
25
|
# unless level or missing, as every report has it;
|
|
23
26
|
# - the shipped text isn't the one the mark names (a checkout at the same
|
|
@@ -32,7 +35,7 @@ module Samagotchi
|
|
|
32
35
|
# to miss, so the mark goes;
|
|
33
36
|
# - conflict: behind, and the mark records why, only when that changed;
|
|
34
37
|
# - fast_forward (one Merger::Upgrade#outcome lets go ahead): the next
|
|
35
|
-
# upgrade's (
|
|
38
|
+
# upgrade's (its save_first), behind until then;
|
|
36
39
|
# - install: a missing file, which isn't behind (FileStanding.behind?): the
|
|
37
40
|
# next upgrade or a restore puts it back, and a warning says so;
|
|
38
41
|
# - one it can't read or replace: left as it is, behind, saying why.
|
|
@@ -75,7 +78,8 @@ module Samagotchi
|
|
|
75
78
|
@behind = []
|
|
76
79
|
@warnings = []
|
|
77
80
|
@trash = Trash.new(@name)
|
|
78
|
-
|
|
81
|
+
@manifest = Manifest.read(dir: @source_dir)
|
|
82
|
+
shipped = MemoryFiles.select(@source_dir, @manifest)
|
|
79
83
|
marked.each { |key, entry| retry_file(key, entry, shipped) }
|
|
80
84
|
settle unless dry_run
|
|
81
85
|
Result.new(merged: @merged, settled: @settled, stamped: @stamped.map(&:file), behind: @behind, warnings: @warnings, trash_copy: @trash.copy_line)
|
|
@@ -90,24 +94,35 @@ module Samagotchi
|
|
|
90
94
|
return
|
|
91
95
|
end
|
|
92
96
|
|
|
93
|
-
|
|
94
|
-
if
|
|
97
|
+
state = view.state(key)
|
|
98
|
+
if state.source_moved?
|
|
95
99
|
@behind << @record.behind_entry(key, entry) if behind
|
|
96
100
|
return
|
|
97
101
|
end
|
|
98
102
|
|
|
99
|
-
|
|
100
|
-
case outcome.status
|
|
101
|
-
when :merged then merge_file(key, entry, outcome)
|
|
102
|
-
when :conflict then mark(key, entry, view.behind(key, outcome, @record.version))
|
|
103
|
-
when :noop, :keep then @settled << key
|
|
104
|
-
when :fast_forward then mark(key, entry, behind_for(key, FileStanding::WAITS_FOR_UPGRADE))
|
|
105
|
-
when :install then @warnings << "#{key}: #{FileStanding::FILE_MISSING}"
|
|
106
|
-
end
|
|
103
|
+
retry_row(key, entry, state, FileActions.for(:remerge, state))
|
|
107
104
|
rescue SystemCallError => e
|
|
108
105
|
stay(key, FileStanding.left_as_it_is(e.message))
|
|
109
106
|
end
|
|
110
107
|
|
|
108
|
+
# The file's REMERGE row, done. A :decide cell, and a behind one, asks
|
|
109
|
+
# Merger::Upgrade#decision for the Outcome (the text merge, the legacy
|
|
110
|
+
# guard, or why no merge is possible); a fast-forward waits for the
|
|
111
|
+
# upgrade. A missing file is put back by the next upgrade or a restore;
|
|
112
|
+
# one it can't read raises here (left as it is, saying why).
|
|
113
|
+
def retry_row(key, entry, state, action)
|
|
114
|
+
decision = view.decision(key, state) if action.do == :decide || action.behind?
|
|
115
|
+
action = FileActions.decided(decision.outcome, :remerge) if action.do == :decide
|
|
116
|
+
if action.do == :merge then merge_file(key, entry, state, decision.outcome)
|
|
117
|
+
elsif action.behind?
|
|
118
|
+
waits = decision.outcome.status == :fast_forward
|
|
119
|
+
mark(key, entry, waits ? behind_for(key, FileStanding::WAITS_FOR_UPGRADE) : view.behind(key, decision.outcome, @record.version))
|
|
120
|
+
elsif action.word == :settled then @settled << key
|
|
121
|
+
elsif state.kind == :missing then @warnings << "#{key}: #{FileStanding::FILE_MISSING}"
|
|
122
|
+
elsif state.kind == :unreadable then FileDigest.hex(state.paths[:current])
|
|
123
|
+
end
|
|
124
|
+
end
|
|
125
|
+
|
|
111
126
|
def behind_for(key, why) = FileStanding::Behind.new(file: key, version: @record.version, why: why)
|
|
112
127
|
|
|
113
128
|
# The record says it too, so chi bundle status reads as the row does. There
|
|
@@ -128,7 +143,9 @@ module Samagotchi
|
|
|
128
143
|
@stamped << behind unless entry[:incoming_sha] && @record.behind_entry(key, entry) == behind && File.exist?(@provenance.incoming_path(key))
|
|
129
144
|
end
|
|
130
145
|
|
|
131
|
-
def merge_file(key, entry, outcome)
|
|
146
|
+
def merge_file(key, entry, state, outcome)
|
|
147
|
+
warning = FileDigest.mismatch(key, state.paths[:incoming], @manifest.checksum_for(key))
|
|
148
|
+
@warnings << warning if warning
|
|
132
149
|
return @merged << key if @dry_run
|
|
133
150
|
return @merged << key if Merger.write_merged(view.paths(key)[:current], outcome, @trash)
|
|
134
151
|
|
|
@@ -14,7 +14,7 @@ module Samagotchi
|
|
|
14
14
|
|
|
15
15
|
# A sha line that doesn't match its file: +old+ is the hex the line
|
|
16
16
|
# holds ("" for a placeholder `sha256: sha256:`), nil when the entry
|
|
17
|
-
# has no sha line at all (a plugin
|
|
17
|
+
# has no sha line at all (a plugin: or scripts: entry the
|
|
18
18
|
# installer then takes unchecked; .refresh can't write one).
|
|
19
19
|
Stale = Data.define(:file, :old, :new)
|
|
20
20
|
|
|
@@ -30,7 +30,6 @@ module Samagotchi
|
|
|
30
30
|
# names, the layout otherwise untouched. The shapes:
|
|
31
31
|
# files: a.md: sha256:<hex> (dir/a.md)
|
|
32
32
|
# a.md:\n sha256: sha256:<hex> (dir/a.md, with description:)
|
|
33
|
-
# hooks: h.rb:\n sha256: sha256:<hex> (dir/hooks/h.rb)
|
|
34
33
|
# plugin: file: p.rb\n sha256: sha256:<hex> (dir/p.rb)
|
|
35
34
|
# scripts: s.rb: sha256:<hex> (dir/scripts/s.rb)
|
|
36
35
|
# Returns [text, [[relative file, old sha, new sha], ...]] for the lines
|
|
@@ -43,8 +42,9 @@ module Samagotchi
|
|
|
43
42
|
[text, changes]
|
|
44
43
|
end
|
|
45
44
|
|
|
46
|
-
# The stale sha lines of the bundle at +dir+, then the plugin
|
|
47
|
-
#
|
|
45
|
+
# The stale sha lines of the bundle at +dir+, then the plugin: and
|
|
46
|
+
# scripts: entries with no sha line, as [Stale]. (A hooks: key, which
|
|
47
|
+
# chi refuses, has no sha lines here.)
|
|
48
48
|
def stale(dir, text = File.read(File.join(dir, "manifest.yml")))
|
|
49
49
|
_, changes, unlined = scan(dir, text)
|
|
50
50
|
changes.map { |file, old, new| Stale.new(file, old, new) } +
|
|
@@ -86,11 +86,10 @@ module Samagotchi
|
|
|
86
86
|
# take a placeholder (`chi bundle check`) parse this text.
|
|
87
87
|
def parseable(text) = text.gsub(/^(\s*(?:["']?[^"':\s][^"':]*["']?: )?)sha256:(?=[ \t]*(?:#[^\r\n]*)?\r?$)/, "\\1sha256:0")
|
|
88
88
|
|
|
89
|
-
# The code files the manifest names (plugin,
|
|
89
|
+
# The code files the manifest names (plugin, scripts), as paths
|
|
90
90
|
# relative to the bundle dir.
|
|
91
91
|
def coded(parsed, plugin_file)
|
|
92
92
|
(plugin_file.is_a?(String) ? [plugin_file] : []) +
|
|
93
|
-
keys(parsed["hooks"]).map { |h| File.join("hooks", h) } +
|
|
94
93
|
keys(parsed["scripts"]).map { |s| File.join("scripts", s) }
|
|
95
94
|
end
|
|
96
95
|
private_class_method :coded
|
|
@@ -114,8 +113,6 @@ module Samagotchi
|
|
|
114
113
|
when "files"
|
|
115
114
|
m = line.match(/\A( sha256: )sha256:\h*#{TAIL}/) and return sub && [sub.delete("\"'"), m[1]]
|
|
116
115
|
m = line.match(/\A( ["']?([^"':]+)["']?: )sha256:\h*#{TAIL}/) and [m[2], m[1]]
|
|
117
|
-
when "hooks"
|
|
118
|
-
m = line.match(/\A( sha256: )sha256:\h*#{TAIL}/) and sub and [File.join("hooks", sub.delete("\"'")), m[1]]
|
|
119
116
|
when "plugin"
|
|
120
117
|
m = line.match(/\A( sha256: )sha256:\h*#{TAIL}/) and plugin_file and [plugin_file, m[1]]
|
|
121
118
|
when "scripts"
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
+
require_relative "asset_installer"
|
|
3
4
|
require_relative "installer"
|
|
4
5
|
require_relative "listing"
|
|
5
6
|
require_relative "manifest"
|
|
@@ -11,6 +12,7 @@ require_relative "remerge"
|
|
|
11
12
|
require_relative "source"
|
|
12
13
|
require_relative "status"
|
|
13
14
|
require_relative "system_bundle"
|
|
15
|
+
require_relative "trash"
|
|
14
16
|
require_relative "../memory_paths"
|
|
15
17
|
require_relative "../version"
|
|
16
18
|
|
|
@@ -25,9 +27,11 @@ module Samagotchi
|
|
|
25
27
|
# bundle that isn't installed and never downgrades.
|
|
26
28
|
#
|
|
27
29
|
# .md files take the Installer's 3-way merge: a clean text merge replaces
|
|
28
|
-
# the file (merged), a real conflict keeps the user's (behind).
|
|
29
|
-
#
|
|
30
|
-
#
|
|
30
|
+
# the file (merged), a real conflict keeps the user's (behind). Rules,
|
|
31
|
+
# the plugin and scripts are replaced, an older chi's hooks removed; an edited one (Trash.edit?: its
|
|
32
|
+
# bytes neither the recorded sha, none recorded counting as edited, nor
|
|
33
|
+
# the incoming file's) is copied to the trash first and listed in
|
|
34
|
+
# replaced (#trash_bound).
|
|
31
35
|
#
|
|
32
36
|
# A profile (a shipped meta bundle) is updated through Profile: its row
|
|
33
37
|
# is :would_update when the shipped version is newer or it has members
|
|
@@ -41,8 +45,9 @@ module Samagotchi
|
|
|
41
45
|
# a project-scope bundle: chi bundle upgrade in the project.
|
|
42
46
|
module ShippedUpdate
|
|
43
47
|
# status: :would_update, :would_merge (plan), :updated, :merged,
|
|
44
|
-
# :up_to_date, :skipped, :failed. note says why for skipped/failed,
|
|
45
|
-
# what a retry at the same version warned of
|
|
48
|
+
# :up_to_date, :skipped, :failed. note says why for skipped/failed,
|
|
49
|
+
# what a retry at the same version warned of, and an update's Checksum
|
|
50
|
+
# mismatch warnings. merged and replaced are file
|
|
46
51
|
# names, behind FileStanding::Behind; under plan they are what apply would
|
|
47
52
|
# merge, replace or leave behind.
|
|
48
53
|
# adds is nil but for a profile: the members it installs.
|
|
@@ -114,6 +119,7 @@ module Samagotchi
|
|
|
114
119
|
r.merged = installer.merged_files
|
|
115
120
|
r.behind = installer.behind
|
|
116
121
|
r.trash_copy = installer.trash_copy_line
|
|
122
|
+
r.note = mismatch_note(installer)
|
|
117
123
|
end
|
|
118
124
|
rescue StandardError => e
|
|
119
125
|
row.dup.tap do |r|
|
|
@@ -193,14 +199,19 @@ module Samagotchi
|
|
|
193
199
|
if Manifest.requires_chi_failure(manifest.requires_chi, chi_version)
|
|
194
200
|
return skip(row, "needs chi #{manifest.requires_chi}", keep_to: true)
|
|
195
201
|
end
|
|
202
|
+
|
|
203
|
+
# The upgrade would refuse it (Installer#refuse_hooks).
|
|
204
|
+
hooks = Manifest::HooksRetired.files_in(row.source_dir)
|
|
205
|
+
return skip(row, Manifest::HooksRetired.for_files(hooks).message, keep_to: true) unless hooks.empty?
|
|
196
206
|
return skip(row, "project scope: chi bundle upgrade #{ship.source} in the project") if row.scope == "project"
|
|
197
207
|
return skip(row, "unknown scope #{row.scope}: upgrade chi or reinstall") unless MemoryPaths.scope_dir(row.scope)
|
|
198
208
|
|
|
199
209
|
forecast = forecast(row, bundle)
|
|
200
210
|
row.merged = forecast.merged
|
|
201
211
|
row.behind = forecast.behind
|
|
202
|
-
row.replaced =
|
|
212
|
+
row.replaced = trash_bound(name, bundle, manifest, row.source_dir)
|
|
203
213
|
row.status = :would_update
|
|
214
|
+
row.note = planned_mismatch_note(row)
|
|
204
215
|
row
|
|
205
216
|
rescue SystemCallError => e
|
|
206
217
|
# A file it can't read: the upgrade would fail on it, so this is that
|
|
@@ -233,6 +244,20 @@ module Samagotchi
|
|
|
233
244
|
|
|
234
245
|
def note_of(remerged) = remerged.warnings.empty? ? nil : remerged.warnings.join("; ")
|
|
235
246
|
|
|
247
|
+
def mismatch_note(installer)
|
|
248
|
+
mismatches = installer.warnings.grep(/\AChecksum mismatch/)
|
|
249
|
+
mismatches.join("; ") unless mismatches.empty?
|
|
250
|
+
end
|
|
251
|
+
|
|
252
|
+
def planned_mismatch_note(row)
|
|
253
|
+
installer = Installer.new(source: row.source_dir, name: row.name, scope: row.scope, strict: true, upgrade: true, dry_run: true,
|
|
254
|
+
shipped_dir: File.dirname(row.source_dir))
|
|
255
|
+
installer.run
|
|
256
|
+
mismatch_note(installer)
|
|
257
|
+
rescue Installer::InstallError
|
|
258
|
+
nil
|
|
259
|
+
end
|
|
260
|
+
|
|
236
261
|
def profile_row(row, bundle, shipped_dir, chi_version)
|
|
237
262
|
manifest = Manifest.read(dir: row.source_dir)
|
|
238
263
|
newer = Listing.newer?(row.to, row.from)
|
|
@@ -267,14 +292,29 @@ module Samagotchi
|
|
|
267
292
|
source_dir: row.source_dir).forecast(shipped & bundle.files.keys, version: row.to)
|
|
268
293
|
end
|
|
269
294
|
|
|
270
|
-
#
|
|
271
|
-
#
|
|
295
|
+
# What the upgrade from +source_dir+ copies to the trash, as
|
|
296
|
+
# hooks/F, guardrails/F, plugin/F, scripts/F: Trash.edits over the
|
|
297
|
+
# folders AssetInstaller replaces or removes, each file against the one
|
|
298
|
+
# it puts in its place. Rules, the plugin and scripts: their whole
|
|
299
|
+
# folder, which the upgrade replaces. hooks/: an older chi's, which the
|
|
300
|
+
# upgrade removes whole (AssetInstaller#retire_hooks; no incoming).
|
|
301
|
+
def trash_bound(name, bundle, manifest, source_dir)
|
|
302
|
+
provenance = Provenance.new(name: name)
|
|
303
|
+
rules = Dir.glob(File.join(source_dir, "guardrails", "*.yml")).sort
|
|
304
|
+
.reject { |file| AssetInstaller.linked?(file, source_dir) }
|
|
305
|
+
plugin = manifest.plugin ? { manifest.plugin[:file] => File.join(source_dir, manifest.plugin[:file]) } : {}
|
|
306
|
+
scripts = manifest.scripts.keys.to_h { |file| [file, File.join(source_dir, "scripts", file)] }
|
|
307
|
+
Trash.edits(provenance.hooks_dir, "hooks", bundle.hooks.transform_values(&:sha256)) +
|
|
308
|
+
Trash.edits(provenance.guardrails_dir, "guardrails", bundle.guardrails, rules.to_h { |file| [File.basename(file), file] }) +
|
|
309
|
+
Trash.edits(provenance.plugin_dir, "plugin", bundle.plugin_shas, plugin) +
|
|
310
|
+
Trash.edits(provenance.scripts_dir, "scripts", bundle.scripts, scripts)
|
|
311
|
+
end
|
|
312
|
+
|
|
313
|
+
# Installed rules and the plugin whose sha differs from the recorded
|
|
314
|
+
# one (they didn't load), as guardrails/F, plugin/F.
|
|
272
315
|
def edited_executables(name, bundle)
|
|
273
316
|
provenance = Provenance.new(name: name)
|
|
274
317
|
edited = []
|
|
275
|
-
bundle.hooks.each do |file, hook|
|
|
276
|
-
edited << "hooks/#{file}" if edited?(File.join(provenance.hooks_dir, file), hook.sha256)
|
|
277
|
-
end
|
|
278
318
|
bundle.guardrails.each do |file, sha|
|
|
279
319
|
edited << "guardrails/#{file}" if edited?(File.join(provenance.guardrails_dir, file), sha)
|
|
280
320
|
end
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
require_relative "file_digest"
|
|
4
4
|
require_relative "provenance"
|
|
5
|
+
require_relative "file_state"
|
|
5
6
|
require_relative "manifest"
|
|
6
7
|
require_relative "../version"
|
|
7
8
|
require_relative "index_updater"
|
|
@@ -15,14 +16,15 @@ module Samagotchi
|
|
|
15
16
|
# A bundle as this chi reads it now (what chi bundle status prints):
|
|
16
17
|
# its stored record, the scope it resolves to (scope_error when it
|
|
17
18
|
# can't), the folder its files belong in, what each file looks like on
|
|
18
|
-
# disk, its installed plugin,
|
|
19
|
+
# disk, its installed plugin, the class-file hooks its record still
|
|
20
|
+
# has (retired_hooks: they don't load), and its needs
|
|
19
21
|
# with what this process's PATH found. The +files+ entries and +plugin+
|
|
20
22
|
# stay Hashes: their fields differ per case (an unchecked file has no
|
|
21
23
|
# paths at all). An entry's behind is its FileStanding::Behind, nil for a
|
|
22
24
|
# file that isn't; merge_note its FileStanding::MergeNote, nil without one;
|
|
23
25
|
# unreadable: the file is there, but this chi can't read it.
|
|
24
26
|
Report = Data.define(:provenance, :scope, :scope_error, :target_dir, :files, :plugin,
|
|
25
|
-
:
|
|
27
|
+
:retired_hooks, :needs)
|
|
26
28
|
|
|
27
29
|
# A scope this chi can't resolve (a newer chi's, a hand edit) isn't
|
|
28
30
|
# guessed: scope_error says so, target_dir is nil and each file is
|
|
@@ -41,16 +43,12 @@ module Samagotchi
|
|
|
41
43
|
files.each do |file_key_str, meta|
|
|
42
44
|
next details[file_key_str] = unchecked_file(meta) unless target_dir
|
|
43
45
|
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
base_checksum = checksum(base_path)
|
|
51
|
-
base_checksum = nil if base_checksum == :unreadable
|
|
52
|
-
modified = base_checksum && current_checksum && base_checksum != current_checksum
|
|
53
|
-
missing = !File.exist?(target_path)
|
|
46
|
+
state = FileState.of(file_key_str, record: bundle, provenance: provenance, target_dir: target_dir, source_dir: nil)
|
|
47
|
+
target_path = state.paths[:current]
|
|
48
|
+
base_path = state.paths[:base]
|
|
49
|
+
unreadable = state.kind == :unreadable
|
|
50
|
+
missing = state.kind == :missing
|
|
51
|
+
modified = %i[edited edited_no_base].include?(state.kind)
|
|
54
52
|
# A model overlay has no index line (Installer#note_overlay).
|
|
55
53
|
overlay = ModelOverlay.bundle_overlay?(file_key_str, bundle_files: files.keys, target_dir: target_dir)
|
|
56
54
|
index_present = overlay ? nil : index_has_entry?(scope, file_key_str)
|
|
@@ -59,9 +57,9 @@ module Samagotchi
|
|
|
59
57
|
behind: behind[file_key_str],
|
|
60
58
|
merge_note: notes[file_key_str],
|
|
61
59
|
unreadable: unreadable,
|
|
62
|
-
stored_checksum:
|
|
63
|
-
current_checksum:
|
|
64
|
-
base_checksum:
|
|
60
|
+
stored_checksum: meta[:checksum],
|
|
61
|
+
current_checksum: digest_or_nil(state.digests.disk),
|
|
62
|
+
base_checksum: digest_or_nil(state.digests.base),
|
|
65
63
|
modified: modified,
|
|
66
64
|
missing: missing,
|
|
67
65
|
index_present: index_present,
|
|
@@ -72,24 +70,22 @@ module Samagotchi
|
|
|
72
70
|
end
|
|
73
71
|
Report.new(provenance: bundle, scope: scope, scope_error: target_dir ? nil : "unknown scope: #{scope}",
|
|
74
72
|
target_dir: target_dir, files: details, plugin: plugin_status(provenance, bundle),
|
|
75
|
-
|
|
73
|
+
retired_hooks: retired_hooks(provenance, bundle), needs: needs_status(bundle))
|
|
76
74
|
end
|
|
77
75
|
|
|
78
|
-
#
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
:unreadable
|
|
83
|
-
end
|
|
84
|
-
private_class_method :checksum
|
|
85
|
-
|
|
86
|
-
# Why none of the bundle's hooks load (this chi doesn't meet its
|
|
87
|
-
# requires_chi, Hooks::BundleLoader), or nil: it has no hooks or meets it.
|
|
88
|
-
def self.hooks_requires_failure(bundle)
|
|
89
|
-
manifest = bundle.manifest
|
|
90
|
-
return nil if manifest.hooks.empty?
|
|
76
|
+
# A FileState digest as the Report has it: nil for none, or one that
|
|
77
|
+
# can't be read (unreadable says which).
|
|
78
|
+
def self.digest_or_nil(digest) = digest == :unreadable ? nil : digest
|
|
79
|
+
private_class_method :digest_or_nil
|
|
91
80
|
|
|
92
|
-
|
|
81
|
+
# The class-file hooks the record still has (a 0.157 install's; chi
|
|
82
|
+
# loads none, Manifest::HooksRetired), each "ok" or "missing" in
|
|
83
|
+
# hooks/, by name; {} for none.
|
|
84
|
+
# @return [Hash{String => String}]
|
|
85
|
+
def self.retired_hooks(provenance, bundle)
|
|
86
|
+
bundle.hooks.keys.sort.to_h do |file|
|
|
87
|
+
[file, File.exist?(File.join(provenance.hooks_dir, file)) ? "ok" : "missing"]
|
|
88
|
+
end
|
|
93
89
|
end
|
|
94
90
|
|
|
95
91
|
# The stored needs, each with found: from this process's PATH (the
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
require "fileutils"
|
|
4
4
|
require_relative "installer"
|
|
5
|
+
require_relative "file_digest"
|
|
5
6
|
require_relative "provenance"
|
|
6
7
|
require_relative "manifest"
|
|
7
8
|
require_relative "index_updater"
|
|
@@ -204,29 +205,54 @@ module Samagotchi
|
|
|
204
205
|
# A file from the bundle missing on disk (the user deleted it) is put
|
|
205
206
|
# back, and nothing else: at the same version a checkout can carry other
|
|
206
207
|
# text, so no other file is merged here (Remerge has its guard), and a live
|
|
207
|
-
# session's guardrails, plugin and scripts stay where they are.
|
|
208
|
+
# session's guardrails, plugin and scripts stay where they are. Which
|
|
209
|
+
# missing file is put back is its RESTORE row (FileActions): one the
|
|
210
|
+
# record doesn't own too. A put-back file is checked against the
|
|
211
|
+
# manifest's files: sha, as an installed one is (a bad copy warns).
|
|
208
212
|
def verify_files_present(gem_manifest, version, dry_run)
|
|
209
213
|
target_dir = MemoryPaths.system_dir
|
|
210
214
|
missing = MemoryFiles.select(GEM_BUNDLE_DIR, gem_manifest).reject { |key| File.exist?(File.join(target_dir, key)) }
|
|
215
|
+
missing = restored_rows(missing, target_dir) unless missing.empty?
|
|
211
216
|
return result(:up_to_date, from: version, to: version) if missing.empty?
|
|
212
|
-
return result(:restored, from: version, to: version) if dry_run
|
|
213
217
|
|
|
214
|
-
|
|
218
|
+
if dry_run
|
|
219
|
+
warnings = missing.filter_map { |key| restore_mismatch(key, gem_manifest) }
|
|
220
|
+
return result(:restored, from: version, to: version, warnings: warnings)
|
|
221
|
+
end
|
|
222
|
+
|
|
223
|
+
warnings = missing.flat_map { |key| restore_file(key, gem_manifest) }
|
|
215
224
|
result(:restored, from: version, to: version, warnings: warnings)
|
|
216
225
|
end
|
|
217
226
|
|
|
218
|
-
#
|
|
219
|
-
def
|
|
227
|
+
# The ones among +keys+ whose FileState's RESTORE row restores.
|
|
228
|
+
def restored_rows(keys, target_dir)
|
|
229
|
+
provenance = Provenance.new(name: BUNDLE_NAME)
|
|
230
|
+
record = provenance.record
|
|
231
|
+
keys.select do |key|
|
|
232
|
+
state = FileState.of(key, record: record, provenance: provenance, target_dir: target_dir, source_dir: GEM_BUNDLE_DIR)
|
|
233
|
+
FileActions.for(:restore, state)&.do == :restore
|
|
234
|
+
end
|
|
235
|
+
end
|
|
236
|
+
|
|
237
|
+
# @return [Array<String>] warnings: its bytes don't match the manifest's
|
|
238
|
+
# sha, or the file is in place and its index line isn't
|
|
239
|
+
def restore_file(key, gem_manifest)
|
|
220
240
|
source = File.join(GEM_BUNDLE_DIR, key)
|
|
221
241
|
target = File.join(MemoryPaths.system_dir, key)
|
|
242
|
+
description = gem_manifest.file_descriptions[key]
|
|
222
243
|
FileUtils.cp(source, target)
|
|
223
244
|
Provenance.new(name: BUNDLE_NAME).record_restored(key, source, description: description)
|
|
245
|
+
warnings = [restore_mismatch(key, gem_manifest)].compact
|
|
224
246
|
begin
|
|
225
247
|
IndexUpdater.update_index(SCOPE, key.delete_suffix(".md"), File.size(target), description, source: BUNDLE_NAME)
|
|
226
|
-
nil
|
|
227
248
|
rescue StandardError => e
|
|
228
|
-
"index.md: line for #{key.delete_suffix(".md")} not updated (#{e.message})"
|
|
249
|
+
warnings << "index.md: line for #{key.delete_suffix(".md")} not updated (#{e.message})"
|
|
229
250
|
end
|
|
251
|
+
warnings
|
|
252
|
+
end
|
|
253
|
+
|
|
254
|
+
def restore_mismatch(key, gem_manifest)
|
|
255
|
+
FileDigest.mismatch(key, File.join(GEM_BUNDLE_DIR, key), gem_manifest.checksum_for(key))
|
|
230
256
|
end
|
|
231
257
|
|
|
232
258
|
# An up-to-date bundle's marked files, retried. No marks: nothing is read.
|
|
@@ -246,7 +272,10 @@ module Samagotchi
|
|
|
246
272
|
owned = provenance.record&.files&.keys || []
|
|
247
273
|
forecast = Merger::Upgrade.new(provenance: provenance, target_dir: MemoryPaths.system_dir, source_dir: GEM_BUNDLE_DIR)
|
|
248
274
|
.forecast(gem_manifest.files.keys.map(&:to_s).sort & owned, version: to)
|
|
249
|
-
|
|
275
|
+
installer = upgrade_installer(dry_run: true)
|
|
276
|
+
installer.run
|
|
277
|
+
warnings = installer.warnings.grep(/\AChecksum mismatch/)
|
|
278
|
+
result(:updated, from: from, to: to, merged: forecast.merged, behind: forecast.behind, warnings: warnings)
|
|
250
279
|
end
|
|
251
280
|
|
|
252
281
|
# Provenance#adopt_manifest from the shipped dir, for a record without a
|
|
@@ -258,9 +287,9 @@ module Samagotchi
|
|
|
258
287
|
Provenance.new(name: BUNDLE_NAME).adopt_manifest(GEM_BUNDLE_DIR)
|
|
259
288
|
end
|
|
260
289
|
|
|
261
|
-
def upgrade_installer
|
|
290
|
+
def upgrade_installer(dry_run: false)
|
|
262
291
|
Installer.new(source: GEM_BUNDLE_DIR, name: BUNDLE_NAME, scope: SCOPE, force: false, strict: true, upgrade: true,
|
|
263
|
-
shipped_dir: File.dirname(GEM_BUNDLE_DIR))
|
|
292
|
+
dry_run: dry_run, shipped_dir: File.dirname(GEM_BUNDLE_DIR))
|
|
264
293
|
end
|
|
265
294
|
private_class_method :with_lock, :note_newer_installed, :install_fresh, :keep_manifest, :upgrade_existing, :verify_files_present, :restore_file, :remerge,
|
|
266
295
|
:upgrade_installer, :result, :planned_update
|