Phase 2: Write changes sequentially with hash chaining.
(
&self,
repo: &mut Repository,
commits: &[ParsedCommit],
line_index: &mut ImportLineIndex,
)
| 2592 | |
| 2593 | /// Phase 2: Write changes sequentially with hash chaining. |
| 2594 | fn phase2_write( |
| 2595 | &self, |
| 2596 | repo: &mut Repository, |
| 2597 | commits: &[ParsedCommit], |
| 2598 | line_index: &mut ImportLineIndex, |
| 2599 | ) -> CliResult<(WriteStats, Vec<ImportedCommitInfo>)> { |
| 2600 | let mut stats = WriteStats::default(); |
| 2601 | let mut imported_commits = Vec::new(); |
| 2602 | let total = commits.len(); |
| 2603 | let phase2_start = Instant::now(); |
| 2604 | let mut batch_start = Instant::now(); |
| 2605 | |
| 2606 | for (idx, parsed) in commits.iter().enumerate() { |
| 2607 | // Commits created by `atomic git push` whose referenced view |
| 2608 | // state is already present add nothing — skip them entirely. |
| 2609 | if should_skip_self_push(parsed, &self.options) { |
| 2610 | stats.self_push_skipped += 1; |
| 2611 | continue; |
| 2612 | } |
| 2613 | |
| 2614 | // Progress reporting with per-batch timing |
| 2615 | if total > 100 && idx % 100 == 0 { |
| 2616 | if idx == 0 { |
| 2617 | print_info(&format!(" Writing {}/{}...", idx, total)); |
| 2618 | } else { |
| 2619 | let batch_elapsed = batch_start.elapsed(); |
| 2620 | let total_elapsed = phase2_start.elapsed(); |
| 2621 | let avg_per_commit = total_elapsed.as_secs_f64() / idx as f64; |
| 2622 | print_info(&format!( |
| 2623 | " Writing {}/{}... (last 100: {:.2}s, avg: {:.1}ms/commit)", |
| 2624 | idx, |
| 2625 | total, |
| 2626 | batch_elapsed.as_secs_f64(), |
| 2627 | avg_per_commit * 1000.0, |
| 2628 | )); |
| 2629 | } |
| 2630 | batch_start = Instant::now(); |
| 2631 | } |
| 2632 | |
| 2633 | // Fail closed on a partial import. Earlier successfully written |
| 2634 | // commits remain indexed, so an incremental retry resumes from |
| 2635 | // them instead of publishing a silent hole in the Git history. |
| 2636 | let info = self.write_commit(repo, parsed, line_index)?; |
| 2637 | if parsed.is_empty { |
| 2638 | stats.empty_commits += 1; |
| 2639 | } else if parsed.is_merge { |
| 2640 | stats.merge_commits += 1; |
| 2641 | } else { |
| 2642 | stats.changes_written += 1; |
| 2643 | } |
| 2644 | stats.files_processed += parsed.files.len(); |
| 2645 | imported_commits.push(info); |
| 2646 | } |
| 2647 | |
| 2648 | // Populate the file index for all files written during this batch. |
| 2649 | // This lets `atomic status` compare file metadata (stat + content hash) |
| 2650 | // instead of reconstructing graph content for every file — reducing |
| 2651 | // post-import status from O(files × graph_traversal) to O(files × stat). |
no test coverage detected