Skip to content

Commit c673fad

Browse files
committed
Normalize and expand relative-paths to absolute paths
1 parent 515b4df commit c673fad

File tree

2 files changed

+58
-7
lines changed

2 files changed

+58
-7
lines changed

crates/chat-cli/src/cli/chat/cli/clear.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -57,7 +57,7 @@ impl ClearArgs {
5757
session.tool_uses.clear();
5858
session.pending_tool_index = None;
5959
session.tool_turn_start_time = None;
60-
60+
6161
execute!(
6262
session.stderr,
6363
style::SetForegroundColor(Color::Green),

crates/chat-cli/src/util/directories.rs

Lines changed: 57 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -185,7 +185,45 @@ pub fn canonicalizes_path(os: &Os, path_as_str: &str) -> Result<String> {
185185
let context = |input: &str| Ok(os.env.get(input).ok());
186186
let home_dir = || os.env.home().map(|p| p.to_string_lossy().to_string());
187187

188-
Ok(shellexpand::full_with_context(path_as_str, home_dir, context)?.to_string())
188+
let expanded = shellexpand::full_with_context(path_as_str, home_dir, context)?;
189+
let path_buf = if !expanded.starts_with("/") {
190+
// Convert relative paths to absolute paths
191+
let current_dir = os.env.current_dir()?;
192+
current_dir.join(expanded.as_ref() as &str)
193+
} else {
194+
// Already absolute path
195+
PathBuf::from(expanded.as_ref() as &str)
196+
};
197+
198+
// Try canonicalize first, fallback to manual normalization if it fails
199+
match path_buf.canonicalize() {
200+
Ok(normalized) => Ok(normalized.as_path().to_string_lossy().to_string()),
201+
Err(_) => {
202+
// If canonicalize fails (e.g., path doesn't exist), do manual normalization
203+
let normalized = normalize_path(&path_buf);
204+
Ok(normalized.to_string_lossy().to_string())
205+
},
206+
}
207+
}
208+
209+
/// Manually normalize a path by resolving . and .. components
210+
fn normalize_path(path: &std::path::Path) -> std::path::PathBuf {
211+
let mut components = Vec::new();
212+
for component in path.components() {
213+
match component {
214+
std::path::Component::CurDir => {
215+
// Skip current directory components
216+
},
217+
std::path::Component::ParentDir => {
218+
// Pop the last component for parent directory
219+
components.pop();
220+
},
221+
_ => {
222+
components.push(component);
223+
},
224+
}
225+
}
226+
components.iter().collect()
189227
}
190228

191229
/// Given a globset builder and a path, build globs for both the file and directory patterns
@@ -449,24 +487,37 @@ mod tests {
449487

450488
// Test environment variable expansion
451489
let result = canonicalizes_path(&test_os, "$TEST_VAR/path").unwrap();
452-
assert_eq!(result, "test_value/path");
490+
assert_eq!(result, "/test_value/path");
453491

454492
// Test combined expansion
455493
let result = canonicalizes_path(&test_os, "~/$TEST_VAR").unwrap();
456494
assert_eq!(result, "/home/testuser/test_value");
457495

496+
// Test ~, . and .. expansion
497+
let result = canonicalizes_path(&test_os, "~/./.././testuser").unwrap();
498+
assert_eq!(result, "/home/testuser");
499+
458500
// Test absolute path (no expansion needed)
459501
let result = canonicalizes_path(&test_os, "/absolute/path").unwrap();
460502
assert_eq!(result, "/absolute/path");
461503

462-
// Test relative path (no expansion needed)
504+
// Test ~, . and .. expansion for a path that does not exist
505+
let result = canonicalizes_path(&test_os, "~/./.././testuser/new/path/../../new").unwrap();
506+
assert_eq!(result, "/home/testuser/new");
507+
508+
// Test path with . and ..
509+
let result = canonicalizes_path(&test_os, "/absolute/./../path").unwrap();
510+
assert_eq!(result, "/path");
511+
512+
// Test relative path (which should be expanded because now all inputs are converted to
513+
// absolute)
463514
let result = canonicalizes_path(&test_os, "relative/path").unwrap();
464-
assert_eq!(result, "relative/path");
515+
assert_eq!(result, "/relative/path");
465516

466517
// Test glob prefixed paths
467518
let result = canonicalizes_path(&test_os, "**/path").unwrap();
468-
assert_eq!(result, "**/path");
519+
assert_eq!(result, "/**/path");
469520
let result = canonicalizes_path(&test_os, "**/middle/**/path").unwrap();
470-
assert_eq!(result, "**/middle/**/path");
521+
assert_eq!(result, "/**/middle/**/path");
471522
}
472523
}

0 commit comments

Comments
 (0)