Skip to main content

relay_knowledge/code/source/layout/
preview.rs

1use std::{collections::BTreeMap, path::PathBuf};
2
3use crate::{
4    code::{
5        CodeIndexError, generated_detection, languages::language_id,
6        parser::dependency_manifest_language_ids,
7    },
8    domain::{
9        CodeRepositoryExcludedPath, CodeRepositoryLanguagePreview, CodeRepositoryLargestFile,
10        CodeRepositoryRegistration, CodeRepositoryScopePreview, CodeRepositorySelector,
11    },
12};
13
14use super::{
15    discovery::discover_source_layout,
16    scoped_snapshot::{
17        filesystem_policy_for_selector, registration_allows_filesystem_ref,
18        scoped_filesystem_tree_hash, source_snapshot_for_scope,
19    },
20    selection::selection_exclusion_reason_for_source,
21};
22
23const PREVIEW_MAX_EXCLUDED_PATHS: usize = 50;
24const PREVIEW_MAX_LARGEST_FILES: usize = 10;
25const DEFAULT_TEXT_FILE_BUDGET_BYTES: usize = 512 * 1024;
26
27/// Returns a non-mutating preview of the effective repository indexing scope.
28pub fn preview_repository_scope(
29    registration: &CodeRepositoryRegistration,
30    selector: &CodeRepositorySelector,
31) -> Result<CodeRepositoryScopePreview, CodeIndexError> {
32    let root = PathBuf::from(&registration.root_path);
33    let filesystem_policy = filesystem_policy_for_selector(registration, selector);
34    let allow_filesystem_ref =
35        registration_allows_filesystem_ref(registration, &root, &selector.ref_selector)?;
36    let snapshot = source_snapshot_for_scope(
37        &root,
38        &selector.ref_selector,
39        filesystem_policy,
40        allow_filesystem_ref,
41    )?;
42    let mut selected_byte_count = 0usize;
43    let mut selected_file_count = 0usize;
44    let mut unsupported_file_count = 0usize;
45    let mut generated_or_heavy_file_count = 0usize;
46    let mut expected_degraded_file_count = 0usize;
47    let mut language_distribution = BTreeMap::<String, (usize, usize)>::new();
48    let mut largest_files = Vec::<CodeRepositoryLargestFile>::new();
49    let mut excluded_paths = Vec::<CodeRepositoryExcludedPath>::new();
50
51    let entries = snapshot.entries;
52    let source_layout = discover_source_layout(&entries);
53    let mut selected_entries = Vec::new();
54    for entry in entries {
55        if let Some(reason) = selection_exclusion_reason_for_source(
56            &entry.path,
57            registration,
58            selector,
59            &source_layout,
60            snapshot.kind,
61        ) {
62            if excluded_paths.len() < PREVIEW_MAX_EXCLUDED_PATHS {
63                excluded_paths.push(CodeRepositoryExcludedPath {
64                    path: entry.path,
65                    reason,
66                });
67            }
68            continue;
69        }
70        let language = preview_language_id(&entry.path);
71        selected_file_count += 1;
72        selected_byte_count = selected_byte_count.saturating_add(entry.byte_count);
73        let bucket = language_distribution
74            .entry(language.to_owned())
75            .or_insert((0, 0));
76        bucket.0 += 1;
77        bucket.1 = bucket.1.saturating_add(entry.byte_count);
78        let is_unsupported = language == "unknown";
79        let is_generated = generated_detection::path_has_generated_signal(&entry.path);
80        let is_heavy = entry.byte_count > DEFAULT_TEXT_FILE_BUDGET_BYTES;
81        if is_unsupported {
82            unsupported_file_count += 1;
83        }
84        if is_generated || is_heavy {
85            generated_or_heavy_file_count += 1;
86        }
87        if is_unsupported || is_heavy {
88            expected_degraded_file_count += 1;
89        }
90        largest_files.push(CodeRepositoryLargestFile {
91            path: entry.path.clone(),
92            byte_count: entry.byte_count,
93        });
94        selected_entries.push(entry);
95    }
96    let (resolved_commit_sha, tree_hash, _) = if snapshot.kind.is_filesystem() {
97        scoped_filesystem_tree_hash(&snapshot.root, &selected_entries, &selector.ref_selector)?
98    } else {
99        (
100            snapshot.resolved_commit_sha,
101            snapshot.tree_hash,
102            BTreeMap::new(),
103        )
104    };
105    largest_files.sort_by(|left, right| {
106        right
107            .byte_count
108            .cmp(&left.byte_count)
109            .then_with(|| left.path.cmp(&right.path))
110    });
111    largest_files.truncate(PREVIEW_MAX_LARGEST_FILES);
112
113    Ok(CodeRepositoryScopePreview {
114        repository_id: registration.repository_id.clone(),
115        alias: registration.alias.clone(),
116        requested_ref: selector.ref_selector.clone(),
117        resolved_commit_sha,
118        tree_hash,
119        selected_file_count,
120        selected_byte_count,
121        unsupported_file_count,
122        generated_or_heavy_file_count,
123        expected_degraded_file_count,
124        language_distribution: language_distribution
125            .into_iter()
126            .map(
127                |(language_id, (file_count, byte_count))| CodeRepositoryLanguagePreview {
128                    language_id,
129                    file_count,
130                    byte_count,
131                },
132            )
133            .collect(),
134        largest_files,
135        excluded_paths,
136    })
137}
138
139fn preview_language_id(path: &str) -> &'static str {
140    language_id(path).unwrap_or_else(|| {
141        dependency_manifest_language_ids(path)
142            .and_then(|languages| languages.first().copied())
143            .unwrap_or("unknown")
144    })
145}
146
147#[cfg(test)]
148#[path = "preview_tests.rs"]
149mod tests;