use std::{
collections::BTreeMap,
path::{Path, PathBuf},
};
mod deleted_symbols;
pub(in crate::code) mod filesystem_delta;
#[path = "full.rs"]
mod full_snapshot;
mod impact_paths;
mod incremental;
pub(in crate::code) mod plan;
pub(in crate::code) mod snapshot;
mod worktree_overlay;
use crate::domain::{
CodeFileFingerprint, CodeIndexMode, CodeIndexResourceBudget, CodeIndexSnapshot,
CodeRepositoryRegistration, CodeRepositorySelector, CodeWorkspaceDetectionConfig,
};
#[cfg(test)]
use crate::code::source::changes::GitChange;
use crate::code::{
CodeIndexError, identity, ids, parser,
parser::parse_indexed_file,
source::{
self, changes,
changes::{TrackedEntryScope, diff_changes},
git, gitlink as source_gitlink,
layout::{self as scope, scoped_source_snapshot_for_filters},
resolution::resolve_repository_ref_with_filters,
source_commit_is_filesystem, source_kind,
},
};
pub use deleted_symbols::deleted_symbol_names_for_diff;
pub(crate) use filesystem_delta::changed_paths_for_filesystem_diff;
use full_snapshot::build_full_snapshot;
#[cfg(test)]
pub(crate) use full_snapshot::mutate_next_filesystem_full_snapshot_read;
use incremental::{IncrementalSnapshotRequest, build_incremental_snapshot};
pub use plan::{
CodeIndexPlan, prepare_full_index_plan, prepare_full_index_plan_with_workspace_detection,
};
use worktree_overlay::build_worktree_overlay_snapshot;
pub(in crate::code) const MAX_INCREMENTAL_GITLINK_EXPANDED_PATHS: usize =
CodeIndexResourceBudget::DEFAULT_MAX_FILES_PER_BATCH;
pub fn build_index_snapshot(
registration: &CodeRepositoryRegistration,
selector: &CodeRepositorySelector,
mode: CodeIndexMode,
previous_hashes: Vec<CodeFileFingerprint>,
) -> Result<CodeIndexSnapshot, CodeIndexError> {
build_index_snapshot_with_base_commit(registration, selector, mode, previous_hashes, None)
}
pub(crate) fn build_index_snapshot_with_base_commit(
registration: &CodeRepositoryRegistration,
selector: &CodeRepositorySelector,
mode: CodeIndexMode,
previous_hashes: Vec<CodeFileFingerprint>,
base_resolved_commit_sha: Option<String>,
) -> Result<CodeIndexSnapshot, CodeIndexError> {
build_index_snapshot_with_workspace_detection(
registration,
selector,
mode,
previous_hashes,
base_resolved_commit_sha,
&CodeWorkspaceDetectionConfig::default(),
)
}
pub(crate) fn build_index_snapshot_with_workspace_detection(
registration: &CodeRepositoryRegistration,
selector: &CodeRepositorySelector,
mode: CodeIndexMode,
previous_hashes: Vec<CodeFileFingerprint>,
base_resolved_commit_sha: Option<String>,
workspace_detection: &CodeWorkspaceDetectionConfig,
) -> Result<CodeIndexSnapshot, CodeIndexError> {
let root = PathBuf::from(®istration.root_path);
let previous_hashes = previous_hashes
.into_iter()
.map(|fingerprint| (fingerprint.path, fingerprint.blob_hash))
.collect::<BTreeMap<_, _>>();
match mode {
CodeIndexMode::Full => {
build_full_snapshot(registration, selector, &root, workspace_detection)
}
CodeIndexMode::Incremental { base_ref, head_ref } => build_incremental_snapshot(
registration,
selector,
&root,
IncrementalSnapshotRequest {
base_ref: &base_ref,
head_ref: &head_ref,
previous_hashes: &previous_hashes,
base_resolved_commit_sha: base_resolved_commit_sha.as_deref(),
workspace_detection,
},
),
CodeIndexMode::WorktreeOverlay => build_worktree_overlay_snapshot(
registration,
selector,
&root,
&previous_hashes,
base_resolved_commit_sha.as_deref(),
workspace_detection,
),
}
}
pub fn changed_paths_for_diff(
root_path: impl AsRef<Path>,
base_ref: &str,
head_ref: &str,
) -> Result<Vec<String>, CodeIndexError> {
changed_paths_for_diff_with_filters(root_path, base_ref, head_ref, &[], &[])
}
pub fn changed_paths_for_diff_with_path_filters(
root_path: impl AsRef<Path>,
base_ref: &str,
head_ref: &str,
path_filters: &[String],
) -> Result<Vec<String>, CodeIndexError> {
changed_paths_for_diff_with_filters(root_path, base_ref, head_ref, path_filters, &[])
}
pub fn changed_paths_for_diff_with_filters(
root_path: impl AsRef<Path>,
base_ref: &str,
head_ref: &str,
path_filters: &[String],
language_filters: &[String],
) -> Result<Vec<String>, CodeIndexError> {
if source_commit_is_filesystem(base_ref) || source_commit_is_filesystem(head_ref) {
if base_ref == head_ref {
return Ok(Vec::new());
}
let base_commit = resolve_repository_ref_with_filters(
root_path.as_ref(),
base_ref,
path_filters,
language_filters,
)?;
let head_commit = resolve_repository_ref_with_filters(
root_path.as_ref(),
head_ref,
path_filters,
language_filters,
)?;
if base_commit == head_commit {
return Ok(Vec::new());
}
let snapshot = scoped_source_snapshot_for_filters(
root_path.as_ref(),
head_ref,
path_filters,
language_filters,
)?;
return Ok(snapshot
.entries
.into_iter()
.map(|entry| entry.path)
.collect());
}
if source_kind(root_path.as_ref())?.is_filesystem() {
if base_ref == head_ref {
return Ok(Vec::new());
}
let snapshot = scoped_source_snapshot_for_filters(
root_path.as_ref(),
head_ref,
path_filters,
language_filters,
)?;
return Ok(snapshot
.entries
.into_iter()
.map(|entry| entry.path)
.collect());
}
let changes = diff_changes(root_path.as_ref(), base_ref, head_ref)?;
impact_paths::paths_from_changes_with_gitlinks(
root_path.as_ref(),
base_ref,
head_ref,
changes,
path_filters,
language_filters,
)
}
#[cfg(test)]
pub(in crate::code) fn impact_paths_from_changes(changes: Vec<GitChange>) -> Vec<String> {
let mut paths = Vec::new();
for change in changes {
match change {
GitChange::AddedOrModified { path }
| GitChange::Deleted { path }
| GitChange::TypeChanged { path } => paths.push(path),
GitChange::Renamed { old_path, new_path } => {
paths.push(old_path);
paths.push(new_path);
}
GitChange::Copied { new_path, .. } => paths.push(new_path),
}
}
paths.sort();
paths.dedup();
paths
}
pub(crate) fn repository_uses_filesystem_source(
root_path: impl AsRef<Path>,
) -> Result<bool, CodeIndexError> {
Ok(source_kind(root_path.as_ref())?.is_filesystem())
}
pub(in crate::code::index) fn tracked_entry_scope_for_selector(
registration: &CodeRepositoryRegistration,
selector: &CodeRepositorySelector,
) -> TrackedEntryScope {
match scope::intersect_path_filters(®istration.path_filters, &selector.path_filters) {
Some(filters) => TrackedEntryScope::from_path_filters(filters.iter()),
None => TrackedEntryScope::empty(),
}
}