git: Prevent crashes when looking index text for empty path (#50487)

We were trying to mitigate these by passing `.` instead of ``, but it
turns out that git2 also panics internally for that. It also just
doesn't make sense to look up the index text (or committed text) for an
empty path, because a file should always have a nonempty repo path.

Closes ZED-560

Release Notes:

- N/A
This commit is contained in:
Cole Miller authored and GitHub committed 2026-03-02 14:14:12 +00:00
1 parent c95d6d960e
commit 66f1450ed0
1 file changed
+34 -23
+34 -23
View File
@@ -1318,33 +1318,31 @@ impl GitRepository for RealGitRepository {
self.executor
.spawn(async move {
fn logic(repo: &git2::Repository, path: &RepoPath) -> Result<Option<String>> {
// This check is required because index.get_path() unwraps internally :(
let mut index = repo.index()?;
index.read(false)?;
const STAGE_NORMAL: i32 = 0;
let path = path.as_std_path();
// `RepoPath` contains a `RelPath` which normalizes `.` into an empty path
// `get_path` unwraps on empty paths though, so undo that normalization here
let path = if path.components().next().is_none() {
".".as_ref()
// git2 unwraps internally on empty paths or `.`
if path.is_empty() {
bail!("empty path has no index text");
}
let entry = index
.get_path(path.as_std_path(), STAGE_NORMAL)
.with_context(|| format!("looking up {path:?} in index"))?;
let oid = if entry.mode != GIT_MODE_SYMLINK {
entry.id
} else {
path
};
let oid = match index.get_path(path, STAGE_NORMAL) {
Some(entry) if entry.mode != GIT_MODE_SYMLINK => entry.id,
_ => return Ok(None),
return Ok(None);
};
let content = repo.find_blob(oid)?.content().to_owned();
Ok(String::from_utf8(content).ok())
}
match logic(&repo.lock(), &path) {
Ok(value) => return value,
Err(err) => log::error!("Error loading index text: {:?}", err),
}
None
logic(&repo.lock(), &path)
.context("loading index text")
.log_err()
.flatten()
})
.boxed()
}
@@ -1353,14 +1351,27 @@ impl GitRepository for RealGitRepository {
let repo = self.repository.clone();
self.executor
.spawn(async move {
let repo = repo.lock();
let head = repo.head().ok()?.peel_to_tree().log_err()?;
let entry = head.get_path(path.as_std_path()).ok()?;
if entry.filemode() == i32::from(git2::FileMode::Link) {
return None;
fn logic(repo: &git2::Repository, path: &RepoPath) -> Result<Option<String>> {
let head = repo.head()?.peel_to_tree()?;
if path.is_empty() {
return Err(anyhow!("empty path has no committed text"));
}
// git2 unwraps internally on empty paths or `.`
let entry = head.get_path(path.as_std_path())?;
if entry.filemode() == i32::from(git2::FileMode::Link) {
bail!(
"symlink has no
committed text"
);
}
let content = repo.find_blob(entry.id())?.content().to_owned();
Ok(String::from_utf8(content).ok())
}
let content = repo.find_blob(entry.id()).log_err()?.content().to_owned();
String::from_utf8(content).ok()
logic(&repo.lock(), &path)
.context("loading committed text")
.log_err()
.flatten()
})
.boxed()
}