Incrementally refresh the content index for a set of repo-relative paths that changed (e.g. the files touched by a recorded change) and persist the result to disk. Each path is re-read from disk: added/modified files are re-indexed and deleted files are removed, keeping the index in sync with the working copy without re-walking the whole tree. Ignored and Atomic-internal paths are skipped so buil
(
repo_root: &Path,
paths: I,
)
| 96 | /// No-op when the content index does not yet exist — building it is the job of |
| 97 | /// [`build_content_index`]; this only maintains an existing index. |
| 98 | pub fn update_content_index_paths<I, P>( |
| 99 | repo_root: &Path, |
| 100 | paths: I, |
| 101 | ) -> Result<(), ContentSearchError> |
| 102 | where |
| 103 | I: IntoIterator<Item = P>, |
| 104 | P: AsRef<Path>, |
| 105 | { |
| 106 | if !has_content_index(repo_root) { |
| 107 | return Ok(()); |
| 108 | } |
| 109 | |
| 110 | // The open + compact below race syntext's own lock-downgrade windows and |
| 111 | // any concurrent reader/writer on the same index dir, so retry the whole |
| 112 | // update on a lock conflict rather than treating it as a hard failure. |
| 113 | // Collect the paths once: the retry closure may run multiple times. |
| 114 | let paths: Vec<std::path::PathBuf> = paths |
| 115 | .into_iter() |
| 116 | .map(|p| p.as_ref().to_path_buf()) |
| 117 | .collect(); |
| 118 | with_lock_retry(|| { |
| 119 | let ignore_rules = crate::ignore::IgnoreRules::load_for_enrichment(repo_root); |
| 120 | let config = content_config(repo_root); |
| 121 | let index = Index::open(config)?; |
| 122 | |
| 123 | let mut any = false; |
| 124 | for path in &paths { |
| 125 | let rel = path.as_path(); |
| 126 | if crate::ignore::is_enrichment_internal(rel) || ignore_rules.is_ignored(rel, false) { |
| 127 | continue; |
| 128 | } |
| 129 | // syntext strips `repo_root` to derive the relative path, so hand it an |
| 130 | // absolute path. The file need not exist — a missing file is treated as |
| 131 | // a deletion and removed from the index. |
| 132 | let absolute = repo_root.join(rel); |
| 133 | index.notify_change(&absolute)?; |
| 134 | any = true; |
| 135 | } |
| 136 | |
| 137 | if !any { |
| 138 | return Ok(()); |
| 139 | } |
| 140 | |
| 141 | // Commit the pending overlay and fold it into on-disk base segments so the |
| 142 | // change survives to the next `Index::open`. When the changed set is large |
| 143 | // relative to the index (syntext caps the overlay at 50% of base docs), |
| 144 | // `compact` reports `OverlayFull`; the sanctioned recovery is a full |
| 145 | // (filtered) rebuild. |
| 146 | match index.compact() { |
| 147 | Ok(()) => Ok(()), |
| 148 | Err(IndexError::OverlayFull { .. }) => { |
| 149 | drop(index); |
| 150 | build_content_index(repo_root) |
| 151 | } |
| 152 | Err(e) => Err(e.into()), |
| 153 | } |
| 154 | }) |
| 155 | } |