From 4b3ef3e45a64f30525324ef8d0d60c1adca2cd7a Mon Sep 17 00:00:00 2001 From: Smit Barmase Date: Fri, 28 Aug 2026 09:04:21 +0000 Subject: [PATCH] git: Fix remote URL parsing for partial clones (#63331) Git adds the clone filter after `(fetch)` in `git remote -v` for partial clones. Zed expected `(fetch)` at the end of the line, so it dropped these remotes. This fixes that. **Before:** Only regular fetch entries were recognized: ```text origin https://github.com/zed-industries/zed.git (fetch) ``` **After:** Fetch entries containing partial-clone filter metadata are also recognized: ```text origin https://github.com/zed-industries/zed.git (fetch) [blob:none] ``` Release Notes: - Fixed Zed failing to recognize Git remotes in partial clones. --- crates/git/src/repository.rs | 64 +++++++++++++++++++++++++++++++----- 1 file changed, 55 insertions(+), 9 deletions(-) diff --git a/crates/git/src/repository.rs b/crates/git/src/repository.rs index d3da1321f3..50c4fbeacd 100644 --- a/crates/git/src/repository.rs +++ b/crates/git/src/repository.rs @@ -1379,6 +1379,19 @@ pub async fn get_git_committer(cx: &AsyncApp) -> GitCommitter { .await } +fn parse_remote_urls(stdout: &str) -> HashMap { + let mut urls = HashMap::default(); + for line in stdout.lines() { + if let Some((line, suffix)) = line.rsplit_once(" (fetch)") + && (suffix.is_empty() || suffix.starts_with(" [") && suffix.ends_with(']')) + && let Some((name, url)) = line.split_once(char::is_whitespace) + { + urls.insert(name.to_string(), url.trim_start().to_string()); + } + } + urls +} + impl GitRepository for RealGitRepository { fn path(&self) -> PathBuf { self.git_dir.clone() @@ -1735,17 +1748,11 @@ impl GitRepository for RealGitRepository { let git = self.git_binary(); self.executor .spawn(async move { - let mut urls = HashMap::default(); if let Ok(stdout) = git.run(&["remote", "-v"]).await { - for line in stdout.lines() { - if let Some(line) = line.strip_suffix(" (fetch)") - && let Some((name, url)) = line.split_once(char::is_whitespace) - { - urls.insert(name.to_string(), url.trim_start().to_string()); - } - } + parse_remote_urls(&stdout) + } else { + HashMap::default() } - urls }) .boxed() } @@ -6681,6 +6688,39 @@ mod tests { } } + #[test] + fn test_parse_remote_urls() { + let stdout = concat!( + "origin\thttps://github.com/zed-industries/zed.git (fetch) [blob:none]\n", + "origin\thttps://github.com/zed-industries/zed.git (push)\n", + "upstream\t/Users/user/My Projects/upstream.git (fetch)\n", + "upstream\t/Users/user/My Projects/upstream.git (push)\n", + "a\t/x (fetch) dir (fetch)\n", + "a\t/x (fetch) dir (push)\n", + "archive\t/tmp/remote [archive].git (fetch)\n", + "archive\t/tmp/remote [archive].git (push)\n", + ); + + let remote_urls = parse_remote_urls(stdout); + assert_eq!(remote_urls.len(), 4); + assert_eq!( + remote_urls.get("origin").map(String::as_str), + Some("https://github.com/zed-industries/zed.git") + ); + assert_eq!( + remote_urls.get("upstream").map(String::as_str), + Some("/Users/user/My Projects/upstream.git") + ); + assert_eq!( + remote_urls.get("a").map(String::as_str), + Some("/x (fetch) dir") + ); + assert_eq!( + remote_urls.get("archive").map(String::as_str), + Some("/tmp/remote [archive].git") + ); + } + #[gpui::test] async fn test_remote_urls(cx: &mut TestAppContext) { disable_git_global_config(); @@ -6717,6 +6757,12 @@ mod tests { ]) .await .unwrap(); + git.run(&["config", "remote.origin.promisor", "true"]) + .await + .unwrap(); + git.run(&["config", "remote.origin.partialclonefilter", "blob:none"]) + .await + .unwrap(); let remote_urls = repo.remote_urls().await; assert_eq!(remote_urls.len(), 2);