use crate::{constants, errors, eval, model, path, query, syntax};
pub fn resolve_trees(
app_context: &model::ApplicationContext,
config: &model::Configuration,
graft_config: Option<&model::Configuration>,
query: &str,
) -> Vec<model::TreeContext> {
let mut result = Vec::new();
let tree_query = model::TreeQuery::new(query);
let pattern = &tree_query.pattern;
if tree_query.include_gardens {
result = garden_trees(app_context, config, graft_config, pattern);
if !result.is_empty() {
return result;
}
}
let mut group_found = false;
if tree_query.include_groups {
if let Some(graft_cfg) = graft_config {
for (name, group) in &graft_cfg.groups {
if !pattern.matches(name) {
continue;
}
result.append(&mut trees_from_group(
app_context,
config,
graft_config,
None,
group,
));
group_found = true;
}
}
if !group_found {
for (name, group) in &config.groups {
if !pattern.matches(name) {
continue;
}
result.append(&mut trees_from_group(
app_context,
config,
graft_config,
None,
group,
));
}
if !result.is_empty() {
return result;
}
}
}
if tree_query.include_trees {
if syntax::is_graft(query) {
if let Ok((graft_id, remainder)) = config.get_graft_id(query) {
result.append(&mut resolve_trees(
app_context,
config,
Some(app_context.get_config(graft_id)),
remainder,
));
}
} else if let Some(graft_cfg) = graft_config {
result.append(&mut trees(graft_cfg, pattern));
} else {
result.append(&mut trees(config, pattern));
}
if !result.is_empty() {
return result;
}
}
if tree_query.is_default {
if let Some(ctx) = tree_from_path(config, &tree_query.query) {
result.push(ctx);
}
}
result
}
pub(crate) fn resolve_and_filter_trees(
app_context: &model::ApplicationContext,
config: &model::Configuration,
query: &str,
pattern: &str,
) -> Vec<model::TreeContext> {
let contexts = resolve_trees(app_context, config, None, query);
let tree_pattern = glob::Pattern::new(pattern).unwrap_or_default();
let mut result = Vec::with_capacity(contexts.len());
for context in contexts {
if tree_pattern.matches(&context.tree) {
result.push(context);
}
}
result
}
fn garden_trees(
app_context: &model::ApplicationContext,
config: &model::Configuration,
graft_config: Option<&model::Configuration>,
pattern: &glob::Pattern,
) -> Vec<model::TreeContext> {
let mut result = Vec::new();
let mut garden_found = false;
if let Some(graft_cfg) = graft_config {
for (name, garden) in &graft_cfg.gardens {
if !pattern.matches(name) {
continue;
}
result.append(&mut trees_from_garden(
app_context,
config,
graft_config,
garden,
));
garden_found = true;
}
}
if !garden_found {
for (name, garden) in &config.gardens {
if !pattern.matches(name) {
continue;
}
result.append(&mut trees_from_garden(
app_context,
config,
graft_config,
garden,
));
}
}
result
}
pub fn trees_from_garden(
app_context: &model::ApplicationContext,
config: &model::Configuration,
graft_config: Option<&model::Configuration>,
garden: &model::Garden,
) -> Vec<model::TreeContext> {
let mut result = Vec::new();
for group in &garden.groups {
let pattern = match glob::Pattern::new(group) {
Ok(value) => value,
Err(_) => continue,
};
let config_groups = match graft_config {
Some(graft_cfg) => &graft_cfg.groups,
None => &config.groups,
};
for (name, cfg_group) in config_groups {
if !pattern.matches(name) {
continue;
}
result.append(&mut trees_from_group(
app_context,
config,
graft_config,
Some(garden.get_name()),
cfg_group,
));
}
}
for tree in &garden.trees {
result.append(&mut trees_from_pattern(
app_context,
config,
graft_config,
tree,
Some(garden.get_name()),
None,
));
}
result
}
pub fn trees_from_group(
app_context: &model::ApplicationContext,
config: &model::Configuration,
graft_config: Option<&model::Configuration>,
garden: Option<&model::GardenName>,
group: &model::Group,
) -> Vec<model::TreeContext> {
let mut result = Vec::new();
for tree in &group.members {
result.append(&mut trees_from_pattern(
app_context,
config,
graft_config,
tree,
garden,
Some(group.get_name()),
));
}
result
}
pub fn tree_from_name(
config: &model::Configuration,
tree_name: &str,
garden_name: Option<&model::GardenName>,
group: Option<&model::GroupName>,
) -> Option<model::TreeContext> {
if let Some(tree) = config.trees.get(tree_name) {
return Some(model::TreeContext::new(
tree.get_name(),
config.graft_id(),
garden_name.cloned(),
group.cloned(),
));
}
if let Some(ctx) = tree_from_path(config, tree_name) {
return Some(ctx);
}
None
}
pub fn trees_from_pattern(
app_context: &model::ApplicationContext,
config: &model::Configuration,
graft_config: Option<&model::Configuration>,
tree: &str,
garden_name: Option<&model::GardenName>,
group: Option<&model::GroupName>,
) -> Vec<model::TreeContext> {
if syntax::is_graft(tree) {
if let Ok((graft_id, remainder)) = config.get_graft_id(tree) {
return trees_from_pattern(
app_context,
config,
Some(app_context.get_config(graft_id)),
remainder,
garden_name,
group,
);
}
}
let mut result = Vec::new();
let pattern = match glob::Pattern::new(tree) {
Ok(value) => value,
Err(_) => return result,
};
if let Some(graft_cfg) = graft_config {
for (tree_name, cfg_tree) in &graft_cfg.trees {
if pattern.matches(tree_name) {
result.push(model::TreeContext::new(
cfg_tree.get_name(),
graft_cfg.get_id(),
garden_name.cloned(),
group.cloned(),
));
}
}
}
for (tree_name, cfg_tree) in &config.trees {
if pattern.matches(tree_name) {
result.push(model::TreeContext::new(
cfg_tree.get_name(),
config.get_id(),
garden_name.cloned(),
group.cloned(),
));
}
}
if result.is_empty() {
if let Some(ctx) = tree_from_path(config, tree) {
result.push(ctx);
}
}
result
}
pub(crate) fn tree_from_path(
config: &model::Configuration,
path: &str,
) -> Option<model::TreeContext> {
tree_from_pathbuf(config, &std::path::PathBuf::from(path))
}
fn tree_from_pathbuf(
config: &model::Configuration,
path: &std::path::Path,
) -> Option<model::TreeContext> {
let pathbuf = match path::canonicalize(path) {
Ok(canon) => canon,
Err(_) => return None,
};
for (name, tree) in &config.trees {
let tree_path = match tree.path_as_ref() {
Ok(value) => value,
Err(_) => continue,
};
let tree_canon = match path::canonicalize(std::path::PathBuf::from(tree_path)) {
Ok(value) => value,
Err(_) => continue,
};
if pathbuf == tree_canon {
return Some(model::TreeContext::new(name, config.get_id(), None, None));
}
}
let is_dot = path
.to_str()
.map(|value| value == constants::DOT)
.unwrap_or_default();
if is_dot {
if let Some(ref dirname) = config.dirname {
for (name, tree) in &config.trees {
let tree_path = match tree.path_as_ref() {
Ok(value) => value,
Err(_) => continue,
};
let tree_canon = match path::canonicalize(std::path::PathBuf::from(tree_path)) {
Ok(value) => value,
Err(_) => continue,
};
if dirname == &tree_canon {
return Some(model::TreeContext::new(name, config.get_id(), None, None));
}
}
}
}
None
}
pub fn tree_name_from_path(
config: &model::Configuration,
path: &std::path::Path,
) -> Option<String> {
tree_name_from_abspath(config, &path::abspath(path))
}
pub(crate) fn tree_name_from_abspath(
config: &model::Configuration,
path: &std::path::Path,
) -> Option<String> {
for tree in config.trees.values() {
if !tree.path_is_valid() {
continue;
}
let tree_path_str = match tree.path_as_ref() {
Ok(path_str) => path_str,
Err(_) => continue,
};
let tree_pathbuf = std::path::PathBuf::from(tree_path_str);
if let Ok(canon_path) = path::canonicalize(tree_pathbuf) {
if canon_path == path {
return Some(tree.get_name().to_string());
}
}
}
None
}
fn trees(config: &model::Configuration, pattern: &glob::Pattern) -> Vec<model::TreeContext> {
let mut result = Vec::new();
for (tree_name, tree) in &config.trees {
if pattern.matches(tree_name) {
result.push(model::TreeContext::new(
tree.get_name(),
config.graft_id(),
None,
None,
));
}
}
result
}
pub fn tree_context(
app_context: &model::ApplicationContext,
config: &model::Configuration,
tree: &str,
garden: Option<&str>,
) -> Result<model::TreeContext, errors::GardenError> {
let mut ctx = tree_from_name(config, tree, None, None).ok_or_else(|| {
errors::GardenError::TreeNotFound {
tree: tree.to_string(),
}
})?;
if config.parent_id.is_some() {
ctx.config = config.get_id();
}
if let Some(garden_name) = garden {
let pattern = glob::Pattern::new(garden_name).map_err(|_| {
errors::GardenError::GardenPatternError {
garden: garden_name.into(),
}
})?;
let contexts = garden_trees(
app_context,
app_context.get_root_config(),
Some(config),
&pattern,
);
if contexts.is_empty() {
return Err(errors::GardenError::GardenNotFound {
garden: garden_name.to_string(),
});
}
ctx.garden = garden.map(|value| value.to_string());
let mut found = false;
for current_ctx in &contexts {
if current_ctx.tree == ctx.tree {
found = true;
break;
}
}
if !found {
return Err(errors::GardenError::InvalidGardenArgument {
tree: tree.to_string(),
garden: garden_name.to_string(),
});
}
}
Ok(ctx)
}
pub fn find_tree(
app_context: &model::ApplicationContext,
id: model::ConfigId,
tree: &str,
garden: Option<&str>,
) -> Result<model::TreeContext, errors::GardenError> {
{
let config = app_context.get_config(id);
if let Some(graft_name) = syntax::graft_basename(tree) {
if syntax::is_graft(tree) && config.contains_graft(graft_name) {
let graft = config.get_graft(graft_name)?;
let graft_id = graft
.get_id()
.ok_or(errors::GardenError::ConfigurationError(format!(
"invalid graft: {graft_name}"
)))?;
if let Some(next_graft) = syntax::trim_graft(tree) {
return find_tree(app_context, graft_id, &next_graft, garden);
}
}
}
}
let config = app_context.get_config(id);
tree_context(app_context, config, tree, garden)
}
pub(crate) fn shared_worktree_path(
app_context: &model::ApplicationContext,
config: &model::Configuration,
ctx: &model::TreeContext,
) -> String {
let config = match ctx.config {
Some(config_id) => app_context.get_config(config_id),
None => config,
};
let tree = match config.trees.get(&ctx.tree) {
Some(tree) => tree,
None => match app_context.get_root_config().trees.get(&ctx.tree) {
Some(tree) => tree,
None => return String::new(),
},
};
if tree.is_worktree {
let worktree = eval::tree_variable(
app_context,
config,
None,
&ctx.tree,
ctx.garden.as_ref(),
&tree.worktree,
);
if let Some(parent_ctx) =
query::tree_from_name(config, &worktree, ctx.garden.as_ref(), ctx.group.as_ref())
{
if let Some(path) = config
.trees
.get(&parent_ctx.tree)
.and_then(|tree| tree.path_as_ref().ok())
{
return path.to_string();
}
}
}
if let Ok(path) = tree.path_as_ref() {
return path.to_string();
}
tree.get_name().to_string()
}