git: Fix panic on duplicate status entries in git status parsing (#49191)

## Summary

Fixes **ZED-2XA** — "Unexpected duplicated status entries: Untracked and
Untracked" crash.

**Impact:** 22 occurrences, 3 users affected (Sentry). The panic was
introduced in #23483 (2025-01-22) which added the `dedup_by` logic for
handling deleted-in-index + untracked file combinations.

No related GitHub issues were found filed against this crash.

## Root Cause

`GitStatus::from_str` sorts entries by path and then calls `dedup_by` to
merge duplicate entries. The only handled case was `(INDEX_DELETED,
Untracked)` — all other duplicates hit a catch-all `panic!`. In
practice, git can produce duplicate `??` (untracked) entries for the
same path, which triggered this crash.

## Fix

- Identical duplicate statuses (e.g., `Untracked, Untracked`) are now
silently deduplicated (keep one)
- Other unexpected duplicate combinations log a warning instead of
crashing
- Added a regression test that parses `"?? file.txt\0?? file.txt"` and
verifies it produces a single entry

## Verification

- Reproduction test passes: `cargo test -p git --
test_duplicate_untracked_entries`
- Full crate tests pass: `cargo test -p git` (20/20)
- Clippy clean: `./script/clippy`

Release Notes:

- Fixed a crash when git produces duplicate status entries for the same
file path

Co-authored-by: factory-droid[bot] <138933559+factory-droid[bot]@users.noreply.github.com>
This commit is contained in:
morgankreyandfactory-droid[bot] authored and GitHub committed 2026-02-24 15:16:24 -05:00
1 parent 0d3fbfafc8
commit f870128f7b
1 file changed
+17 -2
+17 -2
View File
@@ -475,7 +475,12 @@ impl FromStr for GitStatus {
}
.into();
}
_ => panic!("Unexpected duplicated status entries: {a_status:?} and {b_status:?}"),
(x, y) if x == y => {}
_ => {
log::warn!(
"Unexpected duplicated status entries: {a_status:?} and {b_status:?}"
);
}
}
true
});
@@ -580,9 +585,19 @@ mod tests {
use crate::{
repository::RepoPath,
status::{TreeDiff, TreeDiffStatus},
status::{FileStatus, GitStatus, TreeDiff, TreeDiffStatus},
};
#[test]
fn test_duplicate_untracked_entries() {
// Regression test for ZED-2XA: git can produce duplicate untracked entries
// for the same path. This should deduplicate them instead of panicking.
let input = "?? file.txt\0?? file.txt";
let status: GitStatus = input.parse().unwrap();
assert_eq!(status.entries.len(), 1);
assert_eq!(status.entries[0].1, FileStatus::Untracked);
}
#[test]
fn test_tree_diff_parsing() {
let input = ":000000 100644 0000000000000000000000000000000000000000 0062c311b8727c3a2e3cd7a41bc9904feacf8f98 A\x00.zed/settings.json\x00".to_owned() +