Skip to content

Commit b94965b

Browse files
committed
Fixed: Make glob sort deterministic when entries share identical mtimes
Add lexicographic path tie-breaker to all three comparator closures (sort_by and select_nth_unstable_by) so results are stable across runs.
1 parent e6d159a commit b94965b

1 file changed

Lines changed: 50 additions & 3 deletions

File tree

  • src/llm-coding-tools-core/src/tools

src/llm-coding-tools-core/src/tools/glob.rs

Lines changed: 50 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -231,10 +231,10 @@ pub fn glob_files<R: PathResolver>(
231231
let truncated = total > limit;
232232

233233
if total <= limit {
234-
entries.sort_by(|a, b| b.1.cmp(&a.1));
234+
entries.sort_by(|a, b| b.1.cmp(&a.1).then_with(|| a.0.cmp(&b.0)));
235235
} else {
236-
entries.select_nth_unstable_by(limit - 1, |a, b| b.1.cmp(&a.1));
237-
entries[..limit].sort_by(|a, b| b.1.cmp(&a.1));
236+
entries.select_nth_unstable_by(limit - 1, |a, b| b.1.cmp(&a.1).then_with(|| a.0.cmp(&b.0)));
237+
entries[..limit].sort_by(|a, b| b.1.cmp(&a.1).then_with(|| a.0.cmp(&b.0)));
238238
}
239239

240240
let files: Vec<String> = entries[..limit.min(total)]
@@ -427,6 +427,53 @@ mod tests {
427427
);
428428
}
429429

430+
#[rstest]
431+
#[case::within_limit(
432+
&["c.txt", "a.txt", "b.txt"],
433+
1000,
434+
vec!["a.txt", "b.txt", "c.txt"],
435+
false,
436+
)]
437+
#[case::truncated(
438+
&["e.txt", "c.txt", "a.txt", "d.txt", "b.txt"],
439+
3,
440+
vec!["a.txt", "b.txt", "c.txt"],
441+
true,
442+
)]
443+
fn glob_sorts_deterministically_with_identical_mtimes(
444+
#[case] files: &[&str],
445+
#[case] limit: usize,
446+
#[case] expected: Vec<&str>,
447+
#[case] expected_truncated: bool,
448+
) {
449+
let dir = TempDir::new().unwrap();
450+
let base = dir.path();
451+
let resolver = AbsolutePathResolver;
452+
453+
let same_time = SystemTime::UNIX_EPOCH + Duration::from_secs(100);
454+
for name in files {
455+
let f = File::create(base.join(name)).unwrap();
456+
f.set_times(FileTimes::new().set_modified(same_time))
457+
.unwrap();
458+
}
459+
460+
let result = glob_files(
461+
&resolver,
462+
GlobRequest {
463+
pattern: "**/*.txt".to_string(),
464+
path: base.to_str().unwrap().to_string(),
465+
},
466+
&GlobSettings::new().with_limit(limit).unwrap(),
467+
)
468+
.unwrap();
469+
470+
assert_eq!(
471+
result.files, expected,
472+
"entries with identical mtimes must be sorted lexicographically by path"
473+
);
474+
assert_eq!(result.truncated, expected_truncated);
475+
}
476+
430477
#[test]
431478
fn glob_returns_forward_slash_paths() {
432479
// Patterns and returned paths use forward slashes on all platforms

0 commit comments

Comments
 (0)