feat(path): rename-aware ambiguity resolution for suffix probe
When a bare filename like 'operators.ts' matches multiple full paths, check if they are the same file connected by renames (via BFS on mr_file_changes). If so, auto-resolve to the newest path instead of erroring. Also wires path resolution into file-history and trace commands so bare filenames work everywhere. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -288,3 +288,80 @@ fn test_exact_match_preferred_over_suffix() {
|
||||
assert_eq!(pq.value, "README.md");
|
||||
assert!(!pq.is_prefix);
|
||||
}
|
||||
|
||||
fn seed_rename(conn: &Connection, mr_id: i64, project_id: i64, old_path: &str, new_path: &str) {
|
||||
conn.execute(
|
||||
"INSERT INTO mr_file_changes (merge_request_id, project_id, old_path, new_path, change_type)
|
||||
VALUES (?1, ?2, ?3, ?4, 'renamed')",
|
||||
rusqlite::params![mr_id, project_id, old_path, new_path],
|
||||
)
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
// ─── rename-aware ambiguity resolution ──────────────────────────────────────
|
||||
|
||||
#[test]
|
||||
fn test_ambiguity_resolved_by_rename_chain() {
|
||||
let conn = setup_test_db();
|
||||
seed_project(&conn, 1);
|
||||
seed_mr(&conn, 1, 1);
|
||||
seed_mr(&conn, 2, 1);
|
||||
|
||||
// File was at src/old/operators.ts, then renamed to src/new/operators.ts
|
||||
seed_file_change(&conn, 1, 1, "src/old/operators.ts");
|
||||
seed_rename(&conn, 2, 1, "src/old/operators.ts", "src/new/operators.ts");
|
||||
|
||||
// Bare "operators.ts" matches both paths via suffix probe, but they're
|
||||
// connected by a rename — should auto-resolve to the newest path.
|
||||
let pq = build_path_query(&conn, "operators.ts", Some(1)).unwrap();
|
||||
assert_eq!(pq.value, "src/new/operators.ts");
|
||||
assert!(!pq.is_prefix);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_ambiguity_not_resolved_when_genuinely_different_files() {
|
||||
let conn = setup_test_db();
|
||||
seed_project(&conn, 1);
|
||||
seed_mr(&conn, 1, 1);
|
||||
|
||||
// Two genuinely different files with the same name (no rename connecting them)
|
||||
seed_file_change(&conn, 1, 1, "src/utils/helpers.ts");
|
||||
seed_file_change(&conn, 1, 1, "tests/utils/helpers.ts");
|
||||
|
||||
let err = build_path_query(&conn, "helpers.ts", Some(1)).unwrap_err();
|
||||
assert!(err.to_string().contains("matches multiple paths"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_ambiguity_rename_chain_with_three_hops() {
|
||||
let conn = setup_test_db();
|
||||
seed_project(&conn, 1);
|
||||
seed_mr(&conn, 1, 1);
|
||||
seed_mr(&conn, 2, 1);
|
||||
seed_mr(&conn, 3, 1);
|
||||
|
||||
// File named "config.ts" moved twice: lib/ -> src/ -> src/core/
|
||||
seed_file_change(&conn, 1, 1, "lib/config.ts");
|
||||
seed_rename(&conn, 2, 1, "lib/config.ts", "src/config.ts");
|
||||
seed_rename(&conn, 3, 1, "src/config.ts", "src/core/config.ts");
|
||||
|
||||
// "config.ts" matches lib/config.ts, src/config.ts, src/core/config.ts via suffix
|
||||
let pq = build_path_query(&conn, "config.ts", Some(1)).unwrap();
|
||||
assert_eq!(pq.value, "src/core/config.ts");
|
||||
assert!(!pq.is_prefix);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_ambiguity_rename_without_project_id_stays_ambiguous() {
|
||||
let conn = setup_test_db();
|
||||
seed_project(&conn, 1);
|
||||
seed_mr(&conn, 1, 1);
|
||||
seed_mr(&conn, 2, 1);
|
||||
|
||||
seed_file_change(&conn, 1, 1, "src/old/utils.ts");
|
||||
seed_rename(&conn, 2, 1, "src/old/utils.ts", "src/new/utils.ts");
|
||||
|
||||
// Without project_id, rename resolution is skipped → stays ambiguous
|
||||
let err = build_path_query(&conn, "utils.ts", None).unwrap_err();
|
||||
assert!(err.to_string().contains("matches multiple paths"));
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user