Skip to content

feat(config): load imports from global and explicit config paths#440

Draft
rpendleton wants to merge 2 commits into
jdx:mainfrom
rpendleton:rpendleton/allow-imports-in-global-config
Draft

feat(config): load imports from global and explicit config paths#440
rpendleton wants to merge 2 commits into
jdx:mainfrom
rpendleton:rpendleton/allow-imports-in-global-config

Conversation

@rpendleton
Copy link
Copy Markdown
Contributor

Previously, the import directive was only honored during recursive project config discovery. Now the global config and explicitly provided --config paths also resolve their imports, and config-files surfaces global imports too.

@greptile-apps
Copy link
Copy Markdown

greptile-apps Bot commented Apr 25, 2026

Greptile Summary

This PR extends the import directive to be honoured in three previously unsupported contexts: the global config (~/.config/fnox/config.toml), explicitly-provided --config paths, and config-files output. The refactor consolidates import loading into a new load_with_imports helper, replaces the two in-lined import loops in load_recursive with per-file calls to that helper, and adds a normalize_path utility (with unit tests) for consistent path deduplication in config-files.

Confidence Score: 5/5

Safe to merge; no correctness bugs found in the changed paths.

All P0/P1 findings from prior review rounds (non-recursive nested imports) have been acknowledged. The new code is logically correct for the advertised single-level import behaviour, the deduplication in config-files is consistent, and both integration and unit tests cover the new paths.

No files require special attention.

Important Files Changed

Filename Overview
src/config.rs Introduces load_with_imports helper; wires it into load_global, load_smart (explicit paths), and every file loaded inside load_recursive. The import-loading loops that were previously inlined in the root and non-root branches of load_recursive are removed cleanly. Logic and merge priority are preserved for the common single-file case.
src/commands/config_files.rs Extracts import-printing into print_imports_from_partial and read_partial_config helpers; adds a normalize_path function with unit tests; wires global-config imports into run(). The refactor reduces duplication and is well-tested.
test/config_recursion.bats Adds an integration test confirming that an explicit --config path resolves its import declarations correctly.
test/global_config.bats Adds two integration tests: one confirming that global config imports are loaded by the engine, and one confirming that config-files surfaces the imported path.

Flowchart

%%{init: {'theme': 'neutral'}}%%
flowchart TD
    A[load_smart] -->|explicit path| B[load_with_imports]
    A -->|default filename| C[load_with_recursion]
    C --> D[load_recursive]
    D -->|per file in dir| B
    D -->|root = true| E[load_global]
    D -->|at FS root| E
    E --> B
    B --> F[load file]
    F --> G{import list?}
    G -->|yes| H[load_import for each]
    H --> I[merge: import_config < file_config]
    G -->|no| J[return config]
    I --> J
Loading

Reviews (3): Last reviewed commit: "fix(config): collapse /.. to / in normal..." | Re-trigger Greptile

Comment thread src/config.rs
Comment thread src/commands/config_files.rs
Copy link
Copy Markdown
Contributor

@gemini-code-assist gemini-code-assist Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request improves configuration loading by ensuring imports are processed for global and explicit config files. It refactors import printing in the config-files command and adds a normalize_path utility for path deduplication, supported by new integration tests. Feedback suggests making import loading recursive to handle nested imports and improving path normalization for root directory edge cases.

Comment thread src/config.rs
Comment on lines +408 to +418
fn load_with_imports(path: &Path) -> Result<Self> {
let mut config = Self::load(path)?;
let base_dir = path.parent().unwrap_or_else(|| Path::new("."));

for import_path in &config.import.clone() {
let import_config = Self::load_import(import_path, base_dir)?;
config = Self::merge_configs(import_config, config)?;
}

Ok(config)
}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

The current implementation of load_with_imports only resolves one level of imports. If an imported file itself contains an import directive, those nested imports will not be loaded because load_import calls Self::load (which doesn't process imports) rather than Self::load_with_imports. While this matches the previous behavior in load_recursive, it limits the utility of imports in global and explicit config paths. Consider making this recursive, though you would need to handle potential circular imports (e.g., by passing a HashSet of visited paths).

Comment on lines +107 to +132
fn normalize_path(path: PathBuf) -> PathBuf {
let mut out = PathBuf::new();
for component in path.components() {
match component {
Component::Prefix(_) | Component::RootDir => out.push(component),
Component::CurDir => {}
Component::ParentDir => {
let can_pop = matches!(out.components().next_back(), Some(Component::Normal(_)));
if can_pop {
out.pop();
} else {
out.push("..");
}
}
Component::Normal(c) => out.push(c),
}
}
if out.as_os_str().is_empty() {
PathBuf::from(".")
} else {
out
}
}

#[cfg(test)]
mod tests {
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

The normalize_path function provides lexical normalization which is useful for deduplicating paths in the HashSet. However, it doesn't handle the case where .. is applied to a root directory (e.g., /.. remains /.. instead of collapsing to /). While unlikely to affect config file discovery in practice, a more robust implementation or using a crate like path-clean (if dependencies are allowed) might be safer for general path handling.

Copy link
Copy Markdown
Contributor Author

@rpendleton rpendleton Apr 25, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in 7cb803b, though I wondered if using something like path-clean would be preferable. I didn't want to add a dependency without further discussion though.

Comment thread src/config.rs
let base_dir = path.parent().unwrap_or_else(|| Path::new("."));

for import_path in &config.import.clone() {
let import_config = Self::load_import(import_path, base_dir)?;
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

One thing worth calling out on this line: since we're using load_import directly, imports aren't resolved transitively. If the imported file declares its own import, those won't be loaded. So, while this PR widens where import works (global config and explicit --config paths), it doesn't make it work everywhere.

I considered making it transitive, but that pulls in edge cases like cycle detection. If we do want to support that, a cleaner approach might be a shared config walker that does a partial parse and resolves imports recursively in one place, and then both config.rs and config_files.rs could rely on that.

For now, I figured I'd open this as a draft to discuss the direction before committing to anything.

@rpendleton rpendleton force-pushed the rpendleton/allow-imports-in-global-config branch from 2f9c200 to ee15447 Compare April 25, 2026 03:29
rpendleton and others added 2 commits April 24, 2026 21:41
Previously, the import directive was only honored during recursive
project config discovery. Now the global config and explicitly
provided --config paths also resolve their imports, and config-files
surfaces global imports too.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@rpendleton rpendleton force-pushed the rpendleton/allow-imports-in-global-config branch from ee15447 to 7cb803b Compare April 25, 2026 03:41
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant