From ad017e03428b71f7602c608e8c5d8b4b28df05db Mon Sep 17 00:00:00 2001 From: Cole Miller Date: Mon, 2 Mar 2026 09:14:12 -0500 Subject: [PATCH] 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 --- crates/git/src/repository.rs | 57 +++++++++++++++++++++--------------- 1 file changed, 34 insertions(+), 23 deletions(-) diff --git a/crates/git/src/repository.rs b/crates/git/src/repository.rs index 1925e84735a8020c7e1896f3cf2e7ee20ae3f712..aed08e8dc850622cff4dc96631199a039c78ac3f 100644 --- a/crates/git/src/repository.rs +++ b/crates/git/src/repository.rs @@ -1318,33 +1318,31 @@ impl GitRepository for RealGitRepository { self.executor .spawn(async move { fn logic(repo: &git2::Repository, path: &RepoPath) -> Result> { - // 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> { + 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() }