Skip to main content

semantic/diff/
diff_core.rs

1// SPDX-License-Identifier: Apache-2.0
2//! Public semantic diff APIs backed by the shared semantic engine.
3
4use std::path::Path;
5
6use objects::{
7    object::{ContentHash, FileChangeSet, diff_trees},
8    store::ObjectStore,
9    worktree::WorktreeStatus,
10};
11
12use super::{diff_helpers::TreeBlobContentLoader, diff_options::SemanticDiffOptions};
13use crate::{
14    cache::SemanticParseCache,
15    diff::{
16        diff_engine::SemanticEngine,
17        diff_types::{SemanticCheckOnlyResult, SemanticDiffResult, SemanticSummaryResult},
18    },
19};
20
21/// Perform a cheap semantic no-op check between two trees.
22pub fn semantic_check_only<S: ObjectStore + ?Sized>(
23    store: &S,
24    from_tree_hash: &ContentHash,
25    to_tree_hash: &ContentHash,
26    options: &SemanticDiffOptions,
27) -> Result<SemanticCheckOnlyResult, anyhow::Error> {
28    semantic_check_only_with_cache(
29        store,
30        from_tree_hash,
31        to_tree_hash,
32        options,
33        SemanticParseCache::shared(),
34    )
35}
36
37/// Perform a cheap semantic no-op check between two trees using an injected cache.
38pub fn semantic_check_only_with_cache<S: ObjectStore + ?Sized>(
39    store: &S,
40    from_tree_hash: &ContentHash,
41    to_tree_hash: &ContentHash,
42    options: &SemanticDiffOptions,
43    cache: &SemanticParseCache,
44) -> Result<SemanticCheckOnlyResult, anyhow::Error> {
45    let file_changes = diff_trees(store, from_tree_hash, to_tree_hash)?;
46    let old_loader = TreeBlobContentLoader::new(store, *from_tree_hash);
47    let new_loader = TreeBlobContentLoader::new(store, *to_tree_hash);
48    SemanticEngine::new(
49        file_changes,
50        |path| old_loader.load_content(path),
51        |path| new_loader.load_content(path),
52        options,
53        cache,
54    )
55    .check_only()
56}
57
58/// Perform a cheap semantic no-op check between a tree and worktree content.
59pub fn semantic_check_only_worktree<S: ObjectStore + ?Sized>(
60    store: &S,
61    from_tree_hash: &ContentHash,
62    worktree_root: &Path,
63    status: &WorktreeStatus,
64    options: &SemanticDiffOptions,
65) -> Result<SemanticCheckOnlyResult, anyhow::Error> {
66    semantic_check_only_worktree_with_cache(
67        store,
68        from_tree_hash,
69        worktree_root,
70        status,
71        options,
72        SemanticParseCache::shared(),
73    )
74}
75
76/// Perform a cheap semantic no-op check between a tree and worktree content using an injected cache.
77pub fn semantic_check_only_worktree_with_cache<S: ObjectStore + ?Sized>(
78    store: &S,
79    from_tree_hash: &ContentHash,
80    worktree_root: &Path,
81    status: &WorktreeStatus,
82    options: &SemanticDiffOptions,
83    cache: &SemanticParseCache,
84) -> Result<SemanticCheckOnlyResult, anyhow::Error> {
85    let old_loader = TreeBlobContentLoader::new(store, *from_tree_hash);
86    SemanticEngine::new(
87        file_changes_from_status(status),
88        |path| old_loader.load_content(path),
89        |path| load_worktree_blob_content(worktree_root, path),
90        options,
91        cache,
92    )
93    .check_only()
94}
95
96/// Perform semantic summary analysis between two trees.
97pub fn semantic_diff_summary<S: ObjectStore + ?Sized>(
98    store: &S,
99    from_tree_hash: &ContentHash,
100    to_tree_hash: &ContentHash,
101    options: &SemanticDiffOptions,
102) -> Result<SemanticSummaryResult, anyhow::Error> {
103    semantic_diff_summary_with_cache(
104        store,
105        from_tree_hash,
106        to_tree_hash,
107        options,
108        SemanticParseCache::shared(),
109    )
110}
111
112/// Perform semantic summary analysis between two trees using an injected cache.
113pub fn semantic_diff_summary_with_cache<S: ObjectStore + ?Sized>(
114    store: &S,
115    from_tree_hash: &ContentHash,
116    to_tree_hash: &ContentHash,
117    options: &SemanticDiffOptions,
118    cache: &SemanticParseCache,
119) -> Result<SemanticSummaryResult, anyhow::Error> {
120    let file_changes = diff_trees(store, from_tree_hash, to_tree_hash)?;
121    let old_loader = TreeBlobContentLoader::new(store, *from_tree_hash);
122    let new_loader = TreeBlobContentLoader::new(store, *to_tree_hash);
123    SemanticEngine::new(
124        file_changes,
125        |path| old_loader.load_content(path),
126        |path| new_loader.load_content(path),
127        options,
128        cache,
129    )
130    .summary()
131}
132
133/// Perform semantic summary between a tree and worktree content.
134pub fn semantic_diff_summary_worktree<S: ObjectStore + ?Sized>(
135    store: &S,
136    from_tree_hash: &ContentHash,
137    worktree_root: &Path,
138    status: &WorktreeStatus,
139    options: &SemanticDiffOptions,
140) -> Result<SemanticSummaryResult, anyhow::Error> {
141    semantic_diff_summary_worktree_with_cache(
142        store,
143        from_tree_hash,
144        worktree_root,
145        status,
146        options,
147        SemanticParseCache::shared(),
148    )
149}
150
151/// Perform semantic summary between a tree and worktree content using an injected cache.
152pub fn semantic_diff_summary_worktree_with_cache<S: ObjectStore + ?Sized>(
153    store: &S,
154    from_tree_hash: &ContentHash,
155    worktree_root: &Path,
156    status: &WorktreeStatus,
157    options: &SemanticDiffOptions,
158    cache: &SemanticParseCache,
159) -> Result<SemanticSummaryResult, anyhow::Error> {
160    let old_loader = TreeBlobContentLoader::new(store, *from_tree_hash);
161    SemanticEngine::new(
162        file_changes_from_status(status),
163        |path| old_loader.load_content(path),
164        |path| load_worktree_blob_content(worktree_root, path),
165        options,
166        cache,
167    )
168    .summary()
169}
170
171/// Perform semantic diff analysis between two trees.
172pub fn semantic_diff<S: ObjectStore + ?Sized>(
173    store: &S,
174    from_tree_hash: &ContentHash,
175    to_tree_hash: &ContentHash,
176    options: &SemanticDiffOptions,
177) -> Result<SemanticDiffResult, anyhow::Error> {
178    semantic_diff_with_cache(
179        store,
180        from_tree_hash,
181        to_tree_hash,
182        options,
183        SemanticParseCache::shared(),
184    )
185}
186
187/// Perform semantic diff analysis between two trees using an injected cache.
188pub fn semantic_diff_with_cache<S: ObjectStore + ?Sized>(
189    store: &S,
190    from_tree_hash: &ContentHash,
191    to_tree_hash: &ContentHash,
192    options: &SemanticDiffOptions,
193    cache: &SemanticParseCache,
194) -> Result<SemanticDiffResult, anyhow::Error> {
195    let file_changes = diff_trees(store, from_tree_hash, to_tree_hash)?;
196    let old_loader = TreeBlobContentLoader::new(store, *from_tree_hash);
197    let new_loader = TreeBlobContentLoader::new(store, *to_tree_hash);
198    SemanticEngine::new(
199        file_changes,
200        |path| old_loader.load_content(path),
201        |path| new_loader.load_content(path),
202        options,
203        cache,
204    )
205    .full()
206}
207
208/// Perform semantic diff between a tree and worktree content.
209pub fn semantic_diff_worktree<S: ObjectStore + ?Sized>(
210    store: &S,
211    from_tree_hash: &ContentHash,
212    worktree_root: &Path,
213    status: &WorktreeStatus,
214    options: &SemanticDiffOptions,
215) -> Result<SemanticDiffResult, anyhow::Error> {
216    semantic_diff_worktree_with_cache(
217        store,
218        from_tree_hash,
219        worktree_root,
220        status,
221        options,
222        SemanticParseCache::shared(),
223    )
224}
225
226/// Perform semantic diff between a tree and worktree content using an injected cache.
227pub fn semantic_diff_worktree_with_cache<S: ObjectStore + ?Sized>(
228    store: &S,
229    from_tree_hash: &ContentHash,
230    worktree_root: &Path,
231    status: &WorktreeStatus,
232    options: &SemanticDiffOptions,
233    cache: &SemanticParseCache,
234) -> Result<SemanticDiffResult, anyhow::Error> {
235    let old_loader = TreeBlobContentLoader::new(store, *from_tree_hash);
236    SemanticEngine::new(
237        file_changes_from_status(status),
238        |path| old_loader.load_content(path),
239        |path| load_worktree_blob_content(worktree_root, path),
240        options,
241        cache,
242    )
243    .full()
244}
245
246fn file_changes_from_status(status: &WorktreeStatus) -> FileChangeSet {
247    let mut file_changes = FileChangeSet::with_capacity(status.change_count());
248    for path in &status.deleted {
249        file_changes.push_deleted(path.display().to_string());
250    }
251    for path in &status.added {
252        file_changes.push_added(path.display().to_string());
253    }
254    for path in &status.modified {
255        file_changes.push_modified(path.display().to_string());
256    }
257    file_changes
258}
259
260fn load_worktree_blob_content(
261    worktree_root: &Path,
262    path: &Path,
263) -> Result<Option<String>, anyhow::Error> {
264    let worktree_path = worktree_root.join(path);
265    match std::fs::read_to_string(&worktree_path) {
266        Ok(content) => Ok(Some(content)),
267        Err(err) if err.kind() == std::io::ErrorKind::NotFound => Ok(None),
268        Err(err) => Err(err.into()),
269    }
270}