1use std::collections::HashSet;
4
5use crate::schema;
6
7use crate::EngineError;
8use crate::config::Config;
9use crate::document::{SourceInfo, assemble};
10use crate::invariants::{InvariantReport, check_all};
11use crate::lang::LanguageRegistry;
12use crate::plan;
13use crate::ports::{
14 AttributeSource, DiffSource, ObjectReader, ObjectWriter, RangeResolver, RecountSource,
15 TreeBuilder, TreeResolver, WorkingCopy,
16};
17
18pub struct PipelineOutput {
19 pub base: String,
20 pub head: String,
21 pub report: InvariantReport,
22 pub document: Option<schema::PlanDocument>,
24 pub view: crate::model::DiffView,
28}
29
30pub fn resolve_range<G: RangeResolver>(
37 git: &G,
38 spec: &[&str],
39) -> Result<plan::ReviewSource, EngineError> {
40 let parsed = plan::parse_range(spec)?;
41 let head_spec = parsed.head_spec().to_string();
42 let (base, head) = match &parsed {
43 plan::RangeSpec::Direct { base, head } => (base.clone(), head.clone()),
44 plan::RangeSpec::MergeBase { a, b } => (git.merge_base(a, b)?, b.clone()),
45 };
46 Ok(plan::ReviewSource::range(base, head, head_spec))
47}
48
49pub fn resolve_picked<G>(
56 git: &G,
57 base: String,
58 include_worktree: bool,
59) -> Result<plan::ReviewSource, EngineError>
60where
61 G: TreeBuilder + WorkingCopy,
62{
63 if !include_worktree {
64 return Ok(plan::ReviewSource::range(
65 base,
66 HEAD_SPEC.to_string(),
67 HEAD_SPEC.to_string(),
68 ));
69 }
70 let head = crate::worktree::worktree_tree(git)?;
71 Ok(plan::ReviewSource {
72 identity_base: Some(base.clone()),
73 base,
74 head,
75 kind: schema::SourceKind::Worktree,
76 head_spec: WORKTREE_SPEC.to_string(),
77 })
78}
79
80const HEAD_SPEC: &str = "HEAD";
83const WORKTREE_SPEC: &str = "WORKTREE";
84
85pub fn run_pipeline<G>(
91 git: &G,
92 base_rev: &str,
93 head_rev: &str,
94 kind: schema::SourceKind,
95 config: &Config,
96 langs: &LanguageRegistry,
97) -> Result<PipelineOutput, EngineError>
98where
99 G: RangeResolver
100 + DiffSource
101 + AttributeSource
102 + ObjectReader
103 + ObjectWriter
104 + TreeResolver
105 + TreeBuilder
106 + RecountSource,
107{
108 run_core(git, base_rev, head_rev, kind, config, langs)
109}
110
111pub fn run_grouped_pipeline<G, C>(
118 git: &G,
119 base_rev: &str,
120 head_rev: &str,
121 kind: schema::SourceKind,
122 config: &Config,
123 langs: &LanguageRegistry,
124 grouping: &crate::grouping::GroupingOptions<C>,
125) -> Result<PipelineOutput, EngineError>
126where
127 C: crate::ports::GroupingCache,
128 G: RangeResolver
129 + DiffSource
130 + AttributeSource
131 + ObjectReader
132 + ObjectWriter
133 + TreeResolver
134 + TreeBuilder
135 + RecountSource,
136{
137 let mut out = run_core_with_progress(
138 git,
139 base_rev,
140 head_rev,
141 kind,
142 config,
143 langs,
144 grouping.progress,
145 )?;
146
147 if let Some(core_doc) = &out.document {
148 let mut grouped = crate::grouping::run(
149 core_doc,
150 &out.view,
151 grouping.backend,
152 grouping.cache,
153 &langs.fingerprint(),
154 grouping.progress,
155 )?;
156 if let Some(f) = grouping.progress {
158 f(crate::grouping::Progress::Ordering);
159 }
160 crate::ordering::apply(&mut grouped, &out.view, langs);
161 out.document = Some(grouped);
162 }
163 if let Some(f) = grouping.progress {
164 f(crate::grouping::Progress::Done);
165 }
166 Ok(out)
167}
168
169fn run_core<G>(
170 git: &G,
171 base_rev: &str,
172 head_rev: &str,
173 kind: schema::SourceKind,
174 config: &Config,
175 langs: &LanguageRegistry,
176) -> Result<PipelineOutput, EngineError>
177where
178 G: RangeResolver
179 + DiffSource
180 + AttributeSource
181 + ObjectReader
182 + ObjectWriter
183 + TreeResolver
184 + TreeBuilder
185 + RecountSource,
186{
187 run_core_with_progress(git, base_rev, head_rev, kind, config, langs, None)
188}
189
190fn run_core_with_progress<G>(
191 git: &G,
192 base_rev: &str,
193 head_rev: &str,
194 kind: schema::SourceKind,
195 config: &Config,
196 langs: &LanguageRegistry,
197 progress: Option<&(dyn Fn(crate::grouping::Progress) + Send + Sync)>,
198) -> Result<PipelineOutput, EngineError>
199where
200 G: RangeResolver
201 + DiffSource
202 + AttributeSource
203 + ObjectReader
204 + ObjectWriter
205 + TreeResolver
206 + TreeBuilder
207 + RecountSource,
208{
209 if let Some(f) = progress {
210 f(crate::grouping::Progress::Enumerating);
211 }
212 let base = git.resolve_endpoint(base_rev)?;
215 let head = git.resolve_endpoint(head_rev)?;
216
217 let raw_records = git.raw_records(&base, &head)?;
220 let canonical_patch = git.canonical_patch(&base, &head)?;
221 let rename_records = git.rename_records(&base, &head)?;
222
223 let mut view = plan::build_view(&plan::Enumeration {
226 raw_records: &raw_records,
227 canonical_patch: &canonical_patch,
228 rename_records: &rename_records,
229 })?;
230
231 let attr_marked = attr_marked_paths(git, config, &view)?;
233 if let Some(f) = progress {
234 f(crate::grouping::Progress::Classifying);
235 }
236 let part = plan::classify(&mut view, config, &attr_marked, langs);
237
238 let report = check_all(git, &base, &head, &view)?;
240 let document = if report.all_ok() {
241 Some(assemble(
242 &view,
243 &part,
244 &SourceInfo {
245 kind,
246 base: base.clone(),
247 head: head.clone(),
248 },
249 &report,
250 )?)
251 } else {
252 None
253 };
254
255 Ok(PipelineOutput {
256 base,
257 head,
258 report,
259 document,
260 view,
261 })
262}
263
264fn attr_marked_paths<G: AttributeSource>(
268 git: &G,
269 config: &Config,
270 view: &crate::model::DiffView,
271) -> Result<HashSet<Vec<u8>>, EngineError> {
272 let mut marked = HashSet::new();
273 let paths: Vec<&[u8]> = view.files.iter().map(|f| f.path.as_slice()).collect();
274 for attr in &config.attributes {
275 for answer in git.check_attr(attr, &paths)? {
276 if plan::attr_marks_generated(&answer.value) {
279 marked.insert(answer.path);
280 }
281 }
282 }
283 Ok(marked)
284}