gabriel / muse public
fix patch commit task/hash-object-canonical-prefix #1 / 2
AI Agent gabriel · 164 days ago · Apr 17, 2026 · Diff

fix(commit): write objects even when unchanged if missing from store

The commit object write loop skipped objects for files unchanged from the parent commit, assuming 'they are already in the store.' This assumption fails when parent objects are absent (fresh clone without blobs, gc run, or a prior commit that failed mid-way).

Fix: skip only when parent manifest has the same ID *and* has_object() confirms the blob is actually present. Otherwise write it.

9 new TDD tests in test_commit_object_store_completeness.py cover: - All objects present after first and second commits - Unchanged file object survives across commits - Parent objects deleted before second commit are re-written (THE BUG) - apply_manifest never raises RuntimeError after a successful commit - checkout after commit with missing parent objects succeeds

sha256:ec4175257b8f37b9b7982305d2bbe1786617eb07762c6809432f4905b5c68162 sha
+80 symbols
sha256:690477746ce01ec55b19b3a056a59d7801437b2af467b3ecd49974e9b16f093b snapshot
+80
symbols added
0
dead code introduced
Semantic Changes 80 symbols
+ TestAllObjectsInStoreAfterCommit class class TestAllObjectsInStoreAfterCommit L116–232
+ test_commit_does_not_leave_partial_state_on_apply_manifest_failure method method test_commit_does_not_leave_partial_state_on_apply_manifest_failure L212–232
+ test_first_commit_stores_all_objects method method test_first_commit_stores_all_objects L117–127
+ test_parent_objects_missing_rewritten_on_next_commit method method test_parent_objects_missing_rewritten_on_next_commit L163–210
+ test_second_commit_stores_all_objects method method test_second_commit_stores_all_objects L129–140
+ test_unchanged_file_object_present_after_second_commit method method test_unchanged_file_object_present_after_second_commit L142–161
+ TestApplyManifestAfterCommit class class TestApplyManifestAfterCommit L316–342
+ test_apply_manifest_does_not_raise_after_commit method method test_apply_manifest_does_not_raise_after_commit L317–327
+ test_apply_manifest_does_not_raise_when_parent_objects_were_missing method method test_apply_manifest_does_not_raise_when_parent_objects_were_missing L329–342
+ TestCheckoutAfterCommit class class TestCheckoutAfterCommit L244–305
+ test_checkout_after_commit_with_missing_parent_objects method method test_checkout_after_commit_with_missing_parent_objects L245–276
+ test_checkout_back_and_forth_after_multi_commit_session method method test_checkout_back_and_forth_after_multi_commit_session L278–305
+ _commit function function _commit L56–57
+ _delete_all_objects function function _delete_all_objects L78–92
+ _head_snapshot_manifest function function _head_snapshot_manifest L66–75
+ _init_repo function function _init_repo L60–63
+ _invoke function function _invoke L47–53
+ CliRunner import import CliRunner L30–30
+ annotations import import annotations L23–23
+ get_head_commit_id import import get_head_commit_id L32–32
+ has_object import import has_object L31–31
+ object_path import import object_path L31–31
+ os import import os L25–25
+ pathlib import import pathlib L26–26
+ pytest import import pytest L28–28
+ read_commit import import read_commit L32–32
+ read_current_branch import import read_current_branch L32–32
+ read_snapshot import import read_snapshot L32–32
+ repo function function repo L101–105
+ runner variable variable runner L39–39
~ muse/cli/commands/commit.py .py 50 symbols added
+ _MAX_FIELD_LEN variable variable _MAX_FIELD_LEN L77–77
+ CommitRecord import import CommitRecord L54–54
+ ExitCode import import ExitCode L41–41
+ Manifest import import Manifest L54–54
+ Metadata import import Metadata L54–54
+ SemVerBump import import SemVerBump L70–70
+ SnapshotManifest import import SnapshotManifest L70–70
+ SnapshotRecord import import SnapshotRecord L54–54
+ StagePlugin import import StagePlugin L70–70
+ StructuredDelta import import StructuredDelta L70–70
+ annotations import import annotations L30–30
+ append_reflog import import append_reflog L50–50
+ apply_manifest import import apply_manifest L44–44
+ argparse import import argparse L32–32
+ classify_delta class import classify_delta L69–69
+ clear_merge_state import import clear_merge_state L42–42
+ compute_commit_id import import compute_commit_id L53–53
+ compute_snapshot_id import import compute_snapshot_id L53–53
+ datetime import import datetime L33–33
+ get_config_value import import get_config_value L40–40
+ get_head_commit_id import import get_head_commit_id L54–54
+ get_head_snapshot_id import import get_head_snapshot_id L54–54
+ has_object import import has_object L43–43
+ json import import json L34–34
+ logging import import logging L35–35
+ make_agent_identity import import make_agent_identity L45–45
+ os import import os L36–36
+ pathlib import import pathlib L37–37
+ provenance_payload import import provenance_payload L45–45
+ read_commit import import read_commit L54–54
+ read_current_branch import import read_current_branch L54–54
+ read_domain import import read_domain L71–71
+ read_merge_state import import read_merge_state L42–42
+ read_repo_id import import read_repo_id L51–51
+ read_snapshot import import read_snapshot L54–54
+ require_repo import import require_repo L51–51
+ rerere_record_resolutions import import rerere_record_resolutions L52–52
+ resolve_plugin import import resolve_plugin L71–71
+ sanitize_display import import sanitize_display L68–68
+ sanitize_provenance import import sanitize_provenance L68–68
+ sign_commit_record import import sign_commit_record L45–45
+ sys import import sys L38–38
+ validate_branch_name import import validate_branch_name L68–68
+ write_branch_ref import import write_branch_ref L54–54
+ write_commit import import write_commit L54–54
+ write_object_from_path import import write_object_from_path L43–43
+ write_snapshot import import write_snapshot L54–54
+ logger variable variable logger L73–73
+ register function function register L80–144
+ run function function run L147–563

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:ec4175257b8f37b9b7982305d2bbe1786617eb07762c6809432f4905b5c68162 --body "your comment"