git: Add ability to clone remote repositories from Zed (#35606)

This PR adds preliminary git clone support through using the new
`GitClone` action. This works with SSH connections too.

- [x] Get backend working
- [x] Add a UI to interact with this

Future follow-ups:
- Polish the UI
- Have the path select prompt say "Select Repository clone target"
instead of “Open”
- Use Zed path prompt if the user has that as a setting
- Add support for cloning from a user's GitHub repositories directly

Release Notes:

- Add the ability to clone remote git repositories through the `git:
Clone` action

---------

Co-authored-by: hpmcdona <hayden_mcdonald@brown.edu>
This commit is contained in:
Anthony Eid 2025-08-11 11:09:38 -04:00 committed by GitHub
parent 12084b6677
commit 62270b33c2
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
9 changed files with 310 additions and 8 deletions

View file

@ -441,6 +441,7 @@ impl GitStore {
client.add_entity_request_handler(Self::handle_blame_buffer);
client.add_entity_message_handler(Self::handle_update_repository);
client.add_entity_message_handler(Self::handle_remove_repository);
client.add_entity_request_handler(Self::handle_git_clone);
}
pub fn is_local(&self) -> bool {
@ -1464,6 +1465,45 @@ impl GitStore {
}
}
pub fn git_clone(
&self,
repo: String,
path: impl Into<Arc<std::path::Path>>,
cx: &App,
) -> Task<Result<()>> {
let path = path.into();
match &self.state {
GitStoreState::Local { fs, .. } => {
let fs = fs.clone();
cx.background_executor()
.spawn(async move { fs.git_clone(&repo, &path).await })
}
GitStoreState::Ssh {
upstream_client,
upstream_project_id,
..
} => {
let request = upstream_client.request(proto::GitClone {
project_id: upstream_project_id.0,
abs_path: path.to_string_lossy().to_string(),
remote_repo: repo,
});
cx.background_spawn(async move {
let result = request.await?;
match result.success {
true => Ok(()),
false => Err(anyhow!("Git Clone failed")),
}
})
}
GitStoreState::Remote { .. } => {
Task::ready(Err(anyhow!("Git Clone isn't supported for remote users")))
}
}
}
async fn handle_update_repository(
this: Entity<Self>,
envelope: TypedEnvelope<proto::UpdateRepository>,
@ -1550,6 +1590,22 @@ impl GitStore {
Ok(proto::Ack {})
}
async fn handle_git_clone(
this: Entity<Self>,
envelope: TypedEnvelope<proto::GitClone>,
cx: AsyncApp,
) -> Result<proto::GitCloneResponse> {
let path: Arc<Path> = PathBuf::from(envelope.payload.abs_path).into();
let repo_name = envelope.payload.remote_repo;
let result = cx
.update(|cx| this.read(cx).git_clone(repo_name, path, cx))?
.await;
Ok(proto::GitCloneResponse {
success: result.is_ok(),
})
}
async fn handle_fetch(
this: Entity<Self>,
envelope: TypedEnvelope<proto::Fetch>,