1use crate::{constants, errors, eval, model, path, query, syntax};
2
3pub fn resolve_trees(
12 app_context: &model::ApplicationContext,
13 config: &model::Configuration,
14 graft_config: Option<&model::Configuration>,
15 query: &str,
16) -> Vec<model::TreeContext> {
17 let mut result = Vec::new();
18 let tree_query = model::TreeQuery::new(query);
19 let pattern = &tree_query.pattern;
20
21 if tree_query.include_gardens {
22 result = garden_trees(app_context, config, graft_config, pattern);
23 if !result.is_empty() {
24 return result;
25 }
26 }
27
28 let mut group_found = false;
29 if tree_query.include_groups {
30 if let Some(graft_cfg) = graft_config {
31 for (name, group) in &graft_cfg.groups {
32 if !pattern.matches(name) {
34 continue;
35 }
36 result.append(&mut trees_from_group(
38 app_context,
39 config,
40 graft_config,
41 None,
42 group,
43 ));
44 group_found = true;
45 }
46 }
47 if !group_found {
48 for (name, group) in &config.groups {
49 if !pattern.matches(name) {
51 continue;
52 }
53 result.append(&mut trees_from_group(
55 app_context,
56 config,
57 graft_config,
58 None,
59 group,
60 ));
61 }
62 if !result.is_empty() {
63 return result;
64 }
65 }
66 }
67
68 if tree_query.include_trees {
71 if syntax::is_graft(query) {
72 if let Ok((graft_id, remainder)) = config.get_graft_id(query) {
73 result.append(&mut resolve_trees(
74 app_context,
75 config,
76 Some(app_context.get_config(graft_id)),
77 remainder,
78 ));
79 }
80 } else if let Some(graft_cfg) = graft_config {
81 result.append(&mut trees(graft_cfg, pattern));
82 } else {
83 result.append(&mut trees(config, pattern));
84 }
85 if !result.is_empty() {
86 return result;
87 }
88 }
89
90 if tree_query.is_default {
95 if let Some(ctx) = tree_from_path(config, &tree_query.query) {
96 result.push(ctx);
97 }
98 }
99
100 result
101}
102
103pub(crate) fn resolve_and_filter_trees(
113 app_context: &model::ApplicationContext,
114 config: &model::Configuration,
115 query: &str,
116 pattern: &str,
117) -> Vec<model::TreeContext> {
118 let contexts = resolve_trees(app_context, config, None, query);
119 let tree_pattern = glob::Pattern::new(pattern).unwrap_or_default();
120 let mut result = Vec::with_capacity(contexts.len());
121 for context in contexts {
122 if tree_pattern.matches(&context.tree) {
123 result.push(context);
124 }
125 }
126
127 result
128}
129
130fn garden_trees(
135 app_context: &model::ApplicationContext,
136 config: &model::Configuration,
137 graft_config: Option<&model::Configuration>,
138 pattern: &glob::Pattern,
139) -> Vec<model::TreeContext> {
140 let mut result = Vec::new();
141 let mut garden_found = false;
142 if let Some(graft_cfg) = graft_config {
143 for (name, garden) in &graft_cfg.gardens {
144 if !pattern.matches(name) {
145 continue;
146 }
147 result.append(&mut trees_from_garden(
148 app_context,
149 config,
150 graft_config,
151 garden,
152 ));
153 garden_found = true;
154 }
155 }
156 if !garden_found {
157 for (name, garden) in &config.gardens {
158 if !pattern.matches(name) {
159 continue;
160 }
161 result.append(&mut trees_from_garden(
162 app_context,
163 config,
164 graft_config,
165 garden,
166 ));
167 }
168 }
169
170 result
171}
172
173pub fn trees_from_garden(
175 app_context: &model::ApplicationContext,
176 config: &model::Configuration,
177 graft_config: Option<&model::Configuration>,
178 garden: &model::Garden,
179) -> Vec<model::TreeContext> {
180 let mut result = Vec::new();
181
182 for group in &garden.groups {
184 let pattern = match glob::Pattern::new(group) {
186 Ok(value) => value,
187 Err(_) => continue,
188 };
189 let config_groups = match graft_config {
191 Some(graft_cfg) => &graft_cfg.groups,
192 None => &config.groups,
193 };
194 for (name, cfg_group) in config_groups {
195 if !pattern.matches(name) {
196 continue;
197 }
198 result.append(&mut trees_from_group(
200 app_context,
201 config,
202 graft_config,
203 Some(garden.get_name()),
204 cfg_group,
205 ));
206 }
207 }
208
209 for tree in &garden.trees {
211 result.append(&mut trees_from_pattern(
212 app_context,
213 config,
214 graft_config,
215 tree,
216 Some(garden.get_name()),
217 None,
218 ));
219 }
220
221 result
222}
223
224pub fn trees_from_group(
226 app_context: &model::ApplicationContext,
227 config: &model::Configuration,
228 graft_config: Option<&model::Configuration>,
229 garden: Option<&model::GardenName>,
230 group: &model::Group,
231) -> Vec<model::TreeContext> {
232 let mut result = Vec::new();
233 for tree in &group.members {
234 result.append(&mut trees_from_pattern(
235 app_context,
236 config,
237 graft_config,
238 tree,
239 garden,
240 Some(group.get_name()),
241 ));
242 }
243
244 result
245}
246
247pub fn tree_from_name(
254 config: &model::Configuration,
255 tree_name: &str,
256 garden_name: Option<&model::GardenName>,
257 group: Option<&model::GroupName>,
258) -> Option<model::TreeContext> {
259 if let Some(tree) = config.trees.get(tree_name) {
261 return Some(model::TreeContext::new(
262 tree.get_name(),
263 config.graft_id(),
264 garden_name.cloned(),
265 group.cloned(),
266 ));
267 }
268
269 if let Some(ctx) = tree_from_path(config, tree_name) {
273 return Some(ctx);
274 }
275
276 None
277}
278
279pub fn trees_from_pattern(
285 app_context: &model::ApplicationContext,
286 config: &model::Configuration,
287 graft_config: Option<&model::Configuration>,
288 tree: &str,
289 garden_name: Option<&model::GardenName>,
290 group: Option<&model::GroupName>,
291) -> Vec<model::TreeContext> {
292 if syntax::is_graft(tree) {
293 if let Ok((graft_id, remainder)) = config.get_graft_id(tree) {
295 return trees_from_pattern(
296 app_context,
297 config,
298 Some(app_context.get_config(graft_id)),
299 remainder,
300 garden_name,
301 group,
302 );
303 }
304 }
305
306 let mut result = Vec::new();
308 let pattern = match glob::Pattern::new(tree) {
309 Ok(value) => value,
310 Err(_) => return result,
311 };
312
313 if let Some(graft_cfg) = graft_config {
314 for (tree_name, cfg_tree) in &graft_cfg.trees {
315 if pattern.matches(tree_name) {
316 result.push(model::TreeContext::new(
318 cfg_tree.get_name(),
319 graft_cfg.get_id(),
320 garden_name.cloned(),
321 group.cloned(),
322 ));
323 }
324 }
325 }
326 for (tree_name, cfg_tree) in &config.trees {
327 if pattern.matches(tree_name) {
328 result.push(model::TreeContext::new(
330 cfg_tree.get_name(),
331 config.get_id(),
332 garden_name.cloned(),
333 group.cloned(),
334 ));
335 }
336 }
337
338 if result.is_empty() {
342 if let Some(ctx) = tree_from_path(config, tree) {
343 result.push(ctx);
344 }
345 }
346
347 result
348}
349
350pub(crate) fn tree_from_path(
352 config: &model::Configuration,
353 path: &str,
354) -> Option<model::TreeContext> {
355 tree_from_pathbuf(config, &std::path::PathBuf::from(path))
356}
357
358fn tree_from_pathbuf(
360 config: &model::Configuration,
361 path: &std::path::Path,
362) -> Option<model::TreeContext> {
363 let pathbuf = match path::canonicalize(path) {
365 Ok(canon) => canon,
366 Err(_) => return None,
367 };
368 for (name, tree) in &config.trees {
369 let tree_path = match tree.path_as_ref() {
370 Ok(value) => value,
371 Err(_) => continue,
372 };
373 let tree_canon = match path::canonicalize(std::path::PathBuf::from(tree_path)) {
374 Ok(value) => value,
375 Err(_) => continue,
376 };
377 if pathbuf == tree_canon {
378 return Some(model::TreeContext::new(name, config.get_id(), None, None));
379 }
380 }
381
382 let is_dot = path
387 .to_str()
388 .map(|value| value == constants::DOT)
389 .unwrap_or_default();
390 if is_dot {
391 if let Some(ref dirname) = config.dirname {
392 for (name, tree) in &config.trees {
393 let tree_path = match tree.path_as_ref() {
394 Ok(value) => value,
395 Err(_) => continue,
396 };
397 let tree_canon = match path::canonicalize(std::path::PathBuf::from(tree_path)) {
398 Ok(value) => value,
399 Err(_) => continue,
400 };
401 if dirname == &tree_canon {
402 return Some(model::TreeContext::new(name, config.get_id(), None, None));
403 }
404 }
405 }
406 }
407
408 None
409}
410
411pub fn tree_name_from_path(
413 config: &model::Configuration,
414 path: &std::path::Path,
415) -> Option<String> {
416 tree_name_from_abspath(config, &path::abspath(path))
417}
418
419pub(crate) fn tree_name_from_abspath(
421 config: &model::Configuration,
422 path: &std::path::Path,
423) -> Option<String> {
424 for tree in config.trees.values() {
426 if !tree.path_is_valid() {
428 continue;
429 }
430 let tree_path_str = match tree.path_as_ref() {
431 Ok(path_str) => path_str,
432 Err(_) => continue,
433 };
434 let tree_pathbuf = std::path::PathBuf::from(tree_path_str);
436 if let Ok(canon_path) = path::canonicalize(tree_pathbuf) {
437 if canon_path == path {
438 return Some(tree.get_name().to_string());
440 }
441 }
442 }
443
444 None
445}
446
447fn trees(config: &model::Configuration, pattern: &glob::Pattern) -> Vec<model::TreeContext> {
449 let mut result = Vec::new();
450 for (tree_name, tree) in &config.trees {
451 if pattern.matches(tree_name) {
452 result.push(model::TreeContext::new(
453 tree.get_name(),
454 config.graft_id(),
455 None,
456 None,
457 ));
458 }
459 }
460
461 result
462}
463
464pub fn tree_context(
467 app_context: &model::ApplicationContext,
468 config: &model::Configuration,
469 tree: &str,
470 garden: Option<&str>,
471) -> Result<model::TreeContext, errors::GardenError> {
472 let mut ctx = tree_from_name(config, tree, None, None).ok_or_else(|| {
473 errors::GardenError::TreeNotFound {
474 tree: tree.to_string(),
475 }
476 })?;
477 if config.parent_id.is_some() {
481 ctx.config = config.get_id();
482 }
483
484 if let Some(garden_name) = garden {
485 let pattern = glob::Pattern::new(garden_name).map_err(|_| {
486 errors::GardenError::GardenPatternError {
487 garden: garden_name.into(),
488 }
489 })?;
490 let contexts = garden_trees(
491 app_context,
492 app_context.get_root_config(),
493 Some(config),
494 &pattern,
495 );
496
497 if contexts.is_empty() {
498 return Err(errors::GardenError::GardenNotFound {
499 garden: garden_name.to_string(),
500 });
501 }
502
503 ctx.garden = garden.map(|value| value.to_string());
504 let mut found = false;
505 for current_ctx in &contexts {
506 if current_ctx.tree == ctx.tree {
507 found = true;
508 break;
509 }
510 }
511
512 if !found {
513 return Err(errors::GardenError::InvalidGardenArgument {
514 tree: tree.to_string(),
515 garden: garden_name.to_string(),
516 });
517 }
518 }
519
520 Ok(ctx)
521}
522
523pub fn find_tree(
524 app_context: &model::ApplicationContext,
525 id: model::ConfigId,
526 tree: &str,
527 garden: Option<&str>,
528) -> Result<model::TreeContext, errors::GardenError> {
529 {
530 let config = app_context.get_config(id);
531 if let Some(graft_name) = syntax::graft_basename(tree) {
532 if syntax::is_graft(tree) && config.contains_graft(graft_name) {
533 let graft = config.get_graft(graft_name)?;
534 let graft_id = graft
535 .get_id()
536 .ok_or(errors::GardenError::ConfigurationError(format!(
537 "invalid graft: {graft_name}"
538 )))?;
539 if let Some(next_graft) = syntax::trim_graft(tree) {
540 return find_tree(app_context, graft_id, &next_graft, garden);
541 }
542 }
543 }
544 }
545
546 let config = app_context.get_config(id);
547 tree_context(app_context, config, tree, garden)
548}
549
550pub(crate) fn shared_worktree_path(
552 app_context: &model::ApplicationContext,
553 config: &model::Configuration,
554 ctx: &model::TreeContext,
555) -> String {
556 let config = match ctx.config {
557 Some(config_id) => app_context.get_config(config_id),
558 None => config,
559 };
560 let tree = match config.trees.get(&ctx.tree) {
561 Some(tree) => tree,
562 None => match app_context.get_root_config().trees.get(&ctx.tree) {
563 Some(tree) => tree,
564 None => return String::new(),
565 },
566 };
567 if tree.is_worktree {
568 let worktree = eval::tree_variable(
569 app_context,
570 config,
571 None,
572 &ctx.tree,
573 ctx.garden.as_ref(),
574 &tree.worktree,
575 );
576 if let Some(parent_ctx) =
577 query::tree_from_name(config, &worktree, ctx.garden.as_ref(), ctx.group.as_ref())
578 {
579 if let Some(path) = config
580 .trees
581 .get(&parent_ctx.tree)
582 .and_then(|tree| tree.path_as_ref().ok())
583 {
584 return path.to_string();
585 }
586 }
587 }
588
589 if let Ok(path) = tree.path_as_ref() {
590 return path.to_string();
591 }
592
593 tree.get_name().to_string()
594}