Save and apply an already-built git-import graph change. This bypasses record/globalize and eager CRDT table writes. The caller has already compiled Git's snapshot delta into line-level graph ops. `preserve_existing_tree_paths` keeps deletions and renames graph-only while a different view owns the materialized working copy.
(
&self,
change: Change,
deleted_paths: &[String],
preserve_existing_tree_paths: bool,
options: InsertOptions,
)
| 922 | /// `preserve_existing_tree_paths` keeps deletions and renames graph-only |
| 923 | /// while a different view owns the materialized working copy. |
| 924 | pub fn write_import_graph_change( |
| 925 | &self, |
| 926 | change: Change, |
| 927 | deleted_paths: &[String], |
| 928 | preserve_existing_tree_paths: bool, |
| 929 | options: InsertOptions, |
| 930 | ) -> Result<ImportWriteOutcome, RepositoryError> { |
| 931 | let mut timings = ImportWriteTimings::default(); |
| 932 | let view_name = options.view.as_deref().unwrap_or(&self.current_view); |
| 933 | |
| 934 | let mut txn = self |
| 935 | .pristine |
| 936 | .write_txn() |
| 937 | .map_err(|e| RepositoryError::Database(e.to_string()))?; |
| 938 | |
| 939 | let assemble_start = std::time::Instant::now(); |
| 940 | let mut v3_bytes = Vec::new(); |
| 941 | let hash = change |
| 942 | .serialize(&mut v3_bytes) |
| 943 | .map_err(|e| RepositoryError::Database(e.to_string()))?; |
| 944 | let (final_change, verified_hash) = Change::deserialize(&mut v3_bytes.as_slice()) |
| 945 | .map_err(|e| RepositoryError::Database(e.to_string()))?; |
| 946 | debug_assert_eq!(hash, verified_hash); |
| 947 | timings.assemble_ms = assemble_start.elapsed().as_millis(); |
| 948 | |
| 949 | // Idempotency guard for the single global GRAPH. |
| 950 | // |
| 951 | // A change is applied to the graph exactly once; views merely |
| 952 | // reference it. Because import registers and applies a change in the |
| 953 | // same transaction, an already-registered change has already been |
| 954 | // applied — re-running its graph ops here would allocate a second |
| 955 | // inode for every added path and manufacture a spurious name/order |
| 956 | // conflict on materialize. This is exactly what `git import --all` |
| 957 | // hits: a commit shared by two branches (a common ancestor) is |
| 958 | // imported once per branch. Reference the already-applied change into |
| 959 | // the target view instead — the same O(1) VIEW_CHANGES metadata |
| 960 | // operation `insert` performs — rather than re-applying it. |
| 961 | if let Some(change_id) = txn |
| 962 | .get_internal(&hash) |
| 963 | .map_err(|e| RepositoryError::Database(e.to_string()))? |
| 964 | { |
| 965 | use atomic_core::apply::compute_new_state; |
| 966 | |
| 967 | txn.put_change_deps(change_id, final_change.dependencies()) |
| 968 | .map_err(|e| RepositoryError::Database(e.to_string()))?; |
| 969 | |
| 970 | let mut view = txn |
| 971 | .open_or_create_view(view_name) |
| 972 | .map_err(|e| RepositoryError::Database(e.to_string()))?; |
| 973 | |
| 974 | // Only reference the change if this view does not already contain |
| 975 | // it, so a genuine re-import never double-advances the merkle. |
| 976 | let missing = filter_missing_in_view(&txn, &view, std::slice::from_ref(&hash)) |
| 977 | .map_err(|e| RepositoryError::Apply(e.to_string()))?; |
| 978 | if !missing.is_empty() { |
| 979 | let new_state = compute_new_state(&view.state, &hash); |
| 980 | let sequence = view.change_count + 1; |
| 981 | txn.put_change(&mut view, change_id, &hash) |
no test coverage detected