gabriel / muse public
feat patch task/supercharge-sparse-checkout #1 / 1
AI Agent gabriel · 160 days ago · Apr 18, 2026 · Diff

feat: supercharge sparse-checkout — stats, --json everywhere, security, mode switching

New features: - --json flag added to init, set, add, and disable (list already had it) - elapsed_ms and exit_code in every JSON output, consistent with snapshot/show - stats subcommand: reads HEAD snapshot, reports total_files, matching_files, excluded_files, efficiency ratio; exits 0 with zeros when no commits yet - Mode switching: init --no-cone on existing cone config now switches mode (preserving patterns) and reports switched=true/previous_mode in JSON; same-mode reinit is a no-op (switched=false)

Security hardening: - Pattern validation extended beyond ANSI: now also rejects null bytes, whitespace-only patterns, and path traversal via '..' components - _read_config_safe distinguishes absent config (None) from corrupt config (exit 1 with clear error) — load_json_file silently returned None for both

Bug fixes: - test_cmd_sparse_checkout.py: _write_files and TestApplyManifestSparse used bare hex object IDs (_sha → blob_id); TestStress same fix

Tests (56 new in test_cmd_sparse_checkout_supercharge.py): - JSON envelope on all subcommands (elapsed_ms, exit_code) - init schema: fresh/idempotent/switch cone→pattern/pattern→cone - set/add/disable schema with counts - stats: cone/pattern mode, disabled, no commits, efficiency ratio - Security: path traversal, null bytes, ANSI, whitespace-only patterns - Config corruption: malformed JSON, missing mode key, missing patterns key - Mode switching: updates config file, preserves patterns count - Stress: 1 000-file manifest, 500-pattern list

sha256:5a93032adcfb072657ab962f1743ccd88ec4fbd458dcfbbee1e083d4f91d8192 sha
+102 ~17 −2 symbols
sha256:dae50a5702a7ed77b80c3ba431025b510e0359d995c0ceaad24682ef34ab5b28 snapshot
+102
symbols added
~17
symbols modified
−2
symbols removed
0
dead code introduced
Semantic Changes 121 symbols
+ TestAddJsonSchema class class TestAddJsonSchema L324–363
+ test_add_json_added_count method method test_add_json_added_count L327–334
+ test_add_json_patterns_array method method test_add_json_patterns_array L346–354
+ test_add_json_skipped_count method method test_add_json_skipped_count L336–344
+ test_add_json_total_is_cumulative method method test_add_json_total_is_cumulative L356–363
+ TestConfigCorruption class class TestConfigCorruption L551–578
+ test_malformed_json_error_message method method test_malformed_json_error_message L560–566
+ test_malformed_json_exits_nonzero method method test_malformed_json_exits_nonzero L554–558
+ test_missing_mode_key method method test_missing_mode_key L568–572
+ test_missing_patterns_key method method test_missing_patterns_key L574–578
+ TestDisableJsonSchema class class TestDisableJsonSchema L371–395
+ test_disable_json_was_enabled_false_when_already_disabled method method test_disable_json_was_enabled_false_when_already_disabled L383–389
+ test_disable_json_was_enabled_true method method test_disable_json_was_enabled_true L374–381
+ test_disable_removes_config method method test_disable_removes_config L391–395
+ TestInitJsonSchema class class TestInitJsonSchema L216–276
+ test_fresh_init_cone_mode method method test_fresh_init_cone_mode L219–226
+ test_fresh_init_pattern_mode method method test_fresh_init_pattern_mode L228–235
+ test_idempotent_init_no_switch method method test_idempotent_init_no_switch L237–244
+ test_mode_switch_cone_to_pattern method method test_mode_switch_cone_to_pattern L246–255
+ test_mode_switch_pattern_to_cone method method test_mode_switch_pattern_to_cone L257–266
+ test_mode_switch_preserves_patterns method method test_mode_switch_preserves_patterns L268–276
+ TestJsonEnvelopeAllSubcommands class class TestJsonEnvelopeAllSubcommands L128–208
+ test_add_json_has_elapsed_ms method method test_add_json_has_elapsed_ms L162–168
+ test_add_json_has_exit_code method method test_add_json_has_exit_code L170–176
+ test_disable_json_has_elapsed_ms method method test_disable_json_has_elapsed_ms L178–184
+ test_disable_json_has_exit_code method method test_disable_json_has_exit_code L186–192
+ test_init_json_has_elapsed_ms method method test_init_json_has_elapsed_ms L131–137
+ test_init_json_has_exit_code method method test_init_json_has_exit_code L139–144
+ test_list_json_has_elapsed_ms method method test_list_json_has_elapsed_ms L194–200
+ test_list_json_has_exit_code method method test_list_json_has_exit_code L202–208
+ test_set_json_has_elapsed_ms method method test_set_json_has_elapsed_ms L146–152
+ test_set_json_has_exit_code method method test_set_json_has_exit_code L154–160
+ TestModeSwitch class class TestModeSwitch L586–610
+ test_same_mode_reinit_is_not_switch method method test_same_mode_reinit_is_not_switch L596–602
+ test_switch_preserves_patterns_count method method test_switch_preserves_patterns_count L604–610
+ test_switch_updates_config_file method method test_switch_updates_config_file L589–594
+ TestPatternEdgeCases class class TestPatternEdgeCases L618–651
+ test_empty_string_pattern_rejected method method test_empty_string_pattern_rejected L627–636
+ test_unicode_pattern_accepted method method test_unicode_pattern_accepted L646–651
+ test_very_long_pattern_accepted method method test_very_long_pattern_accepted L638–644
+ test_whitespace_only_pattern_rejected method method test_whitespace_only_pattern_rejected L621–625
+ TestSecurityValidation class class TestSecurityValidation L490–543
+ test_ansi_still_rejected method method test_ansi_still_rejected L523–527
+ test_error_message_mentions_traversal method method test_error_message_mentions_traversal L535–543
+ test_null_byte_pattern_rejected_add method method test_null_byte_pattern_rejected_add L517–521
+ test_null_byte_pattern_rejected_set method method test_null_byte_pattern_rejected_set L511–515
+ test_path_traversal_nested_rejected method method test_path_traversal_nested_rejected L505–509
+ test_path_traversal_rejected_add method method test_path_traversal_rejected_add L499–503
+ test_path_traversal_rejected_set method method test_path_traversal_rejected_set L493–497
+ test_safe_pattern_not_rejected method method test_safe_pattern_not_rejected L529–533
+ TestSetJsonSchema class class TestSetJsonSchema L284–316
+ test_set_json_patterns_array method method test_set_json_patterns_array L287–293
+ test_set_json_replaces_previous method method test_set_json_replaces_previous L303–311
+ test_set_json_total_count method method test_set_json_total_count L295–301
+ test_set_without_init_fails method method test_set_without_init_fails L313–316
+ TestStatsSubcommand class class TestStatsSubcommand L403–482
+ test_stats_disabled_means_all_match method method test_stats_disabled_means_all_match L437–446
+ test_stats_efficiency_ratio method method test_stats_efficiency_ratio L427–435
+ test_stats_has_elapsed_ms method method test_stats_has_elapsed_ms L448–454
+ test_stats_has_exit_code method method test_stats_has_exit_code L456–461
+ test_stats_no_commits_exits_cleanly method method test_stats_no_commits_exits_cleanly L463–471
+ test_stats_pattern_mode method method test_stats_pattern_mode L473–482
+ test_stats_total_files method method test_stats_total_files L418–425
+ test_stats_with_cone_filter method method test_stats_with_cone_filter L406–416
+ TestStressLargeManifest class class TestStressLargeManifest L659–697
+ test_stats_1000_file_manifest method method test_stats_1000_file_manifest L662–680
+ test_stats_500_patterns method method test_stats_500_patterns L682–697
+ _REPO_ID variable variable _REPO_ID L34–34
+ _commit function function _commit L85–103
+ _env function function _env L53–54
+ _init_repo function function _init_repo L42–50
+ _invoke function function _invoke L57–59
+ _make_repo_with_snapshot function function _make_repo_with_snapshot L106–120
+ _obj function function _obj L66–69
+ _snap function function _snap L72–82
+ _sparse_config function function _sparse_config L62–63
+ cli variable variable cli L32–32
+ CliRunner import import CliRunner L29–29
+ CommitRecord import import CommitRecord L28–28
+ Manifest import import Manifest L25–25
+ SnapshotRecord import import SnapshotRecord L28–28
+ annotations import import annotations L17–17
+ blob_id import import blob_id L25–25
+ compute_commit_id import import compute_commit_id L27–27
+ compute_snapshot_id import import compute_snapshot_id L27–27
+ datetime import import datetime L19–19
+ json import import json L20–20
+ pathlib import import pathlib L21–21
+ pytest import import pytest L23–23
+ write_commit import import write_commit L28–28
+ write_object import import write_object L26–26
+ write_snapshot import import write_snapshot L28–28
+ runner variable variable runner L31–31
~ muse/cli/commands/sparse_checkout.py .py 8 symbols added, 10 symbols modified
+ _TRAVERSAL_RE variable variable _TRAVERSAL_RE L115–115
+ _cmd_stats function function _cmd_stats L412–497
+ _read_config_safe function function _read_config_safe L162–185
+ _require_config function function _require_config L216–226
+ _validate_config_structure function function _validate_config_structure L188–213
+ _validate_patterns function function _validate_patterns L145–154
+ filter_manifest_sparse import import filter_manifest_sparse L100–100
+ time import import time L96–96
~ tests/test_cmd_sparse_checkout.py .py 1 symbol added, 2 symbols removed, 7 symbols modified
← Older Oldest on task/supercharge-sparse-checkout
All commits
Newer → Latest on task/supercharge-sparse-checkout

0 comments

No comments yet. Be the first to start the discussion.

To add a comment, use the Muse CLI: muse hub commit comment sha256:5a93032adcfb072657ab962f1743ccd88ec4fbd458dcfbbee1e083d4f91d8192 --body "your comment"