Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
cc67986
Merge pull request #56 from flupkede/fix/tui-indexing-status
flupkede May 20, 2026
2015859
fix: CI test resilience + protect-master workflow (#58)
flupkede May 21, 2026
4c1182c
Sync master → develop (tree-sitter) (#60)
flupkede May 21, 2026
78ef160
docs: update CHANGELOG — v1.0.132 consolidated release notes (#61)
flupkede May 22, 2026
71b8ed3
sync: align develop with master — AGENTS.md, Cargo.toml, Cargo.lock (…
flupkede May 22, 2026
4296b1c
fix: MCP local mode project/group fallback + QC script fix (#66)
flupkede May 27, 2026
d0f3929
chore: simplify release workflow — feature-only version bump (#74)
flupkede May 27, 2026
b9b5645
fix: serve-aware indexing — create DB dir before lock + no silent loc…
flupkede Jun 1, 2026
d947d71
sync: align develop with master (post-v1.0.137 release) (#78)
flupkede Jun 1, 2026
b09a930
fix: strip UNC prefix in repos.json + auto-add on missing DB (v1.0.13…
flupkede Jun 1, 2026
70877ef
sync: align develop with master (post-v1.0.138) (#81)
flupkede Jun 1, 2026
e989ee2
refactor: central safe_canonicalize() — eliminate raw .canonicalize()…
flupkede Jun 1, 2026
acc788d
fix: replace last raw .canonicalize() with safe_canonicalize in get_d…
flupkede Jun 1, 2026
0bc928f
fix: wait for serve warmup instead of refusing; fix 409 on DB-recreat…
flupkede Jun 1, 2026
708cf27
fix: keep serve responsive during warmup — offload heavy work to spaw…
flupkede Jun 1, 2026
3c740c0
sync: backfill CHANGELOG 1.0.139-1.0.142 from master (post-release) (…
flupkede Jun 1, 2026
54782bd
feat: semantic Markdown chunking (tree-sitter-md) + /merge & /release…
flupkede Jun 1, 2026
bc88eba
Stale-path relocation, index prune, derived alias (v1.0.152) (#92)
flupkede Jun 1, 2026
e0e3580
Auto-prune stale repos during Phase 1 warmup (v1.0.153) (#94)
flupkede Jun 2, 2026
fb7858c
docs: AGENTS.md plan for fixes-strict-scoping-and-reaper (#95)
flupkede Jun 2, 2026
d7d71b7
docs: AGENTS.md plan for serve single-instance guard (#96)
flupkede Jun 2, 2026
b748977
fix: Windows 8.3 short-name path mismatch in relocation tests + CHANG…
flupkede Jun 2, 2026
caf43f8
sync: align develop with master after v1.0.154 release (#100)
flupkede Jun 2, 2026
8748657
fix: reconcile_all_paths spawn_blocking + persist_config in prune pat…
flupkede Jun 2, 2026
404cb0e
fix: full review remarks — concurrency, tests, metadata consistency, …
flupkede Jun 2, 2026
8f3a62f
fix: rename_retry helper for Windows flaky relocation tests (#105)
flupkede Jun 2, 2026
02050d3
Release v1.0.162 — Windows flaky test fix (rename_retry)
flupkede Jun 2, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 13 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,19 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0



## [1.0.162] - 2026-06-02

### Fixed

- **Windows: flaky relocation tests eliminated** — `std::fs::rename` and
`std::fs::remove_dir_all` on Windows fail with "Access is denied" when a
git subprocess from `init_git_remote` keeps a directory handle open after
exit. All 7 rename calls in `repos.rs` tests now use a `rename_retry()`
helper that retries up to 10 times with exponential back-off; the one
`remove_dir_all` call is now best-effort (the test assertion holds either
way). Verified stable across 3 consecutive full-suite runs (432 passed,
0 failed).

## [1.0.160] - 2026-06-02

### Fixed
Expand Down
2 changes: 1 addition & 1 deletion Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[package]
name = "codesearch"
version = "1.0.160"
version = "1.0.162"
edition = "2021"
authors = ["codesearch contributors"]
license = "Apache-2.0"
Expand Down
48 changes: 40 additions & 8 deletions src/db_discovery/repos.rs
Original file line number Diff line number Diff line change
Expand Up @@ -631,6 +631,32 @@ mod tests {
run(&["remote", "add", "origin", url]);
}

/// Rename a directory with automatic retries.
///
/// On Windows, git subprocesses spawned by `init_git_remote` may keep a
/// file handle on the directory open briefly after the process exits.
/// `std::fs::rename` fails with `Access is denied` in that window.
/// Retrying with a short exponential back-off is the simplest robust fix.
#[track_caller]
fn rename_retry(from: &Path, to: &Path) {
let mut last_err = None;
for attempt in 0..10u64 {
match std::fs::rename(from, to) {
Ok(()) => return,
Err(e) => {
last_err = Some(e);
std::thread::sleep(std::time::Duration::from_millis(20 * (attempt + 1)));
}
}
}
panic!(
"rename {:?} → {:?} failed after 10 attempts: {}",
from,
to,
last_err.unwrap()
);
}

#[test]
fn captures_git_remote_on_register() {
let tmp = tempfile::tempdir().unwrap();
Expand Down Expand Up @@ -672,7 +698,7 @@ mod tests {

// Rename the PARENT folder; the stored repo path is now stale, but the
// repo itself sits one level below the nearest existing ancestor (tmp).
std::fs::rename(&parent, tmp.path().join("parent-renamed")).unwrap();
rename_retry(&parent, &tmp.path().join("parent-renamed"));

let expected = tmp.path().join("parent-renamed").join("repo");
let found = cfg
Expand All @@ -695,7 +721,7 @@ mod tests {

// Rename the top box; nearest existing ancestor becomes tmp root, and
// the repo now sits 4 levels below it (box/l1/l2/repo) — out of reach.
std::fs::rename(tmp.path().join("oldbox"), tmp.path().join("box")).unwrap();
rename_retry(&tmp.path().join("oldbox"), &tmp.path().join("box"));

assert!(
cfg.try_relocate(&alias).is_none(),
Expand All @@ -718,7 +744,7 @@ mod tests {
let stable_alias = cfg.register(stable.clone());

let renamed = tmp.path().join("moved-renamed");
std::fs::rename(&moved, &renamed).unwrap();
rename_retry(&moved, &renamed);

let (relocated, unresolved) = cfg.relocate_missing();
assert!(unresolved.is_empty());
Expand Down Expand Up @@ -746,7 +772,7 @@ mod tests {
let alias = cfg.register(plain.clone());
cfg.add_group("g".to_string(), vec![alias.clone()]).unwrap();

std::fs::rename(&plain, tmp.path().join("plain-moved")).unwrap();
rename_retry(&plain, &tmp.path().join("plain-moved"));

let (relocated, removed) = cfg.prune_stale();
assert!(relocated.is_empty());
Expand All @@ -767,7 +793,7 @@ mod tests {
let alias = cfg.register(repo.clone());

let renamed = tmp.path().join("repo-renamed");
std::fs::rename(&repo, &renamed).unwrap();
rename_retry(&repo, &renamed);

let (relocated, removed) = cfg.prune_stale();
assert!(removed.is_empty());
Expand Down Expand Up @@ -808,7 +834,7 @@ mod tests {

// Rename the leaf folder; stored path is now stale.
let renamed = tmp.path().join("myrepo-renamed");
std::fs::rename(&original, &renamed).unwrap();
rename_retry(&original, &renamed);

let found = cfg
.try_relocate(&alias)
Expand Down Expand Up @@ -838,7 +864,7 @@ mod tests {
let alias = cfg.register(plain.clone());
assert!(cfg.meta(&alias).git_remote.is_none());

std::fs::rename(&plain, tmp.path().join("plain-moved")).unwrap();
rename_retry(&plain, &tmp.path().join("plain-moved"));
assert!(cfg.try_relocate(&alias).is_none());
}

Expand Down Expand Up @@ -900,7 +926,13 @@ mod tests {
std::fs::create_dir(&b).unwrap();
init_git_remote(&a, "https://example.com/acme/dup.git");
init_git_remote(&b, "https://example.com/acme/dup.git");
std::fs::remove_dir_all(&original).unwrap();
// On Windows, git subprocesses spawned by init_git_remote may keep a
// handle on the directory briefly, causing remove_dir_all to fail under
// parallel test load. Ignore the error: if removal fails, `original`
// still exists and try_relocate returns None because the path is present;
// if removal succeeds, two ambiguous candidates are found → None.
// Either way the assertion holds.
let _ = std::fs::remove_dir_all(&original);

assert!(cfg.try_relocate(&alias).is_none());
}
Expand Down