Attempt to represent an atomic-origin squash commit by inserting its original change records into the target view instead of writing a new squash change (SPEC §4). Verifies that the insert reproduces the git tree for the touched paths; on divergence or missing originals it rolls back and skips the commit (SPEC §5, decision D1-A).
(
&self,
repo: &mut Repository,
parsed: &ParsedCommit,
)
| 2763 | /// tree for the touched paths; on divergence or missing originals it rolls |
| 2764 | /// back and skips the commit (SPEC §5, decision D1-A). |
| 2765 | fn try_squash_insert( |
| 2766 | &self, |
| 2767 | repo: &mut Repository, |
| 2768 | parsed: &ParsedCommit, |
| 2769 | ) -> CliResult<SquashDecision> { |
| 2770 | use atomic_repository::InsertOptions; |
| 2771 | |
| 2772 | // Merges are handled by the normal path + the phase-3 merge tag. |
| 2773 | if parsed.is_merge { |
| 2774 | return Ok(SquashDecision::NotSquash); |
| 2775 | } |
| 2776 | |
| 2777 | // Must carry Atomic-Changes trailers to be an atomic-origin squash. |
| 2778 | let message = parsed.full_message(); |
| 2779 | let original_hashes = match parse_atomic_changes_trailer(&message) { |
| 2780 | Some(h) => h, |
| 2781 | None => return Ok(SquashDecision::NotSquash), |
| 2782 | }; |
| 2783 | let pr_number = parse_pr_number(&message); |
| 2784 | |
| 2785 | // Resolve every original to a Hash and confirm it exists locally. A |
| 2786 | // missing original means we cannot reconstruct the aggregate — skip and |
| 2787 | // advise `atomic pull` rather than fabricate a squash change (SPEC §5). |
| 2788 | let mut parsed_hashes: Vec<ContentHash> = Vec::with_capacity(original_hashes.len()); |
| 2789 | for h in &original_hashes { |
| 2790 | match ContentHash::from_base32(h.as_bytes()) { |
| 2791 | Some(hash) if repo.has_change(&hash) => parsed_hashes.push(hash), |
| 2792 | _ => { |
| 2793 | print_warning(&format!( |
| 2794 | "Squash {} references change {} which is not present locally; \ |
| 2795 | skipping (run `atomic pull` to fetch it, then re-import).", |
| 2796 | parsed.short_sha, h |
| 2797 | )); |
| 2798 | return Ok(SquashDecision::Skipped); |
| 2799 | } |
| 2800 | } |
| 2801 | } |
| 2802 | |
| 2803 | let target = self.options.target_view.clone(); |
| 2804 | |
| 2805 | // Insert the originals (with dependency closure) into the target view. |
| 2806 | // Track exactly which changes we newly added, for rollback on divergence. |
| 2807 | let mut newly_applied: Vec<ContentHash> = Vec::new(); |
| 2808 | for hash in &parsed_hashes { |
| 2809 | let options = InsertOptions::default() |
| 2810 | .view(target.clone()) |
| 2811 | .apply_deps(true); |
| 2812 | match repo.insert_change_rec(hash, options) { |
| 2813 | Ok(outcome) => { |
| 2814 | newly_applied.extend(outcome.stats.applied_hashes.iter().copied()); |
| 2815 | } |
| 2816 | Err(e) => { |
| 2817 | print_warning(&format!( |
| 2818 | "Squash {}: failed to insert original {}: {}; skipping.", |
| 2819 | parsed.short_sha, |
| 2820 | &hash.to_base32()[..12], |
| 2821 | e |
| 2822 | )); |
no test coverage detected