Skip to content
Merged
Show file tree
Hide file tree
Changes from 8 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions crates/collab/src/rpc.rs
Original file line number Diff line number Diff line change
Expand Up @@ -439,6 +439,10 @@ impl Server {
.add_request_handler(forward_mutating_project_request::<proto::GitCreateWorktree>)
.add_request_handler(disallow_guest_request::<proto::GitRemoveWorktree>)
.add_request_handler(disallow_guest_request::<proto::GitRenameWorktree>)
.add_request_handler(forward_mutating_project_request::<proto::GitEditRef>)
.add_request_handler(forward_mutating_project_request::<proto::GitRepairWorktrees>)
.add_request_handler(disallow_guest_request::<proto::GitCreateArchiveCheckpoint>)
.add_request_handler(disallow_guest_request::<proto::GitRestoreArchiveCheckpoint>)
.add_request_handler(forward_mutating_project_request::<proto::CheckForPushedCommits>)
.add_request_handler(forward_mutating_project_request::<proto::ToggleLspLogs>)
.add_message_handler(broadcast_project_message_from_host::<proto::LanguageServerLog>)
Expand Down
28 changes: 18 additions & 10 deletions crates/fs/src/fake_git_repo.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,8 @@ use git::{
repository::{
AskPassDelegate, Branch, CommitDataReader, CommitDetails, CommitOptions,
CreateWorktreeTarget, FetchOptions, GRAPH_CHUNK_SIZE, GitRepository,
GitRepositoryCheckpoint, InitialGraphCommitData, LogOrder, LogSource, PushOptions, Remote,
RepoPath, ResetMode, SearchCommitArgs, Worktree,
GitRepositoryCheckpoint, InitialGraphCommitData, LogOrder, LogSource, PushOptions, RefEdit,
Remote, RepoPath, ResetMode, SearchCommitArgs, Worktree,
},
stash::GitStash,
status::{
Expand Down Expand Up @@ -109,6 +109,20 @@ impl FakeGitRepository {
.boxed()
}

fn edit_ref(&self, edit: RefEdit) -> BoxFuture<'_, Result<()>> {
self.with_state_async(true, move |state| {
match edit {
RefEdit::Update { ref_name, commit } => {
state.refs.insert(ref_name, commit);
}
RefEdit::Delete { ref_name } => {
state.refs.remove(&ref_name);
}
}
Ok(())
})
}

/// Scans `.git/worktrees/*/gitdir` to find the admin entry directory for a
/// worktree at the given checkout path. Used when the working tree directory
/// has already been deleted and we can't read its `.git` pointer file.
Expand Down Expand Up @@ -1437,17 +1451,11 @@ impl GitRepository for FakeGitRepository {
}

fn update_ref(&self, ref_name: String, commit: String) -> BoxFuture<'_, Result<()>> {
self.with_state_async(true, move |state| {
state.refs.insert(ref_name, commit);
Ok(())
})
self.edit_ref(RefEdit::Update { ref_name, commit })
}

fn delete_ref(&self, ref_name: String) -> BoxFuture<'_, Result<()>> {
self.with_state_async(true, move |state| {
state.refs.remove(&ref_name);
Ok(())
})
self.edit_ref(RefEdit::Delete { ref_name })
}

fn repair_worktrees(&self) -> BoxFuture<'_, Result<()>> {
Expand Down
48 changes: 32 additions & 16 deletions crates/git/src/repository.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1043,6 +1043,25 @@ pub struct RealGitRepository {
is_trusted: Arc<AtomicBool>,
}

#[derive(Debug)]
pub enum RefEdit {
Update { ref_name: String, commit: String },
Delete { ref_name: String },
}

impl RefEdit {
fn into_args(self) -> Vec<OsString> {
match self {
Self::Update { ref_name, commit } => {
vec!["update-ref".into(), ref_name.into(), commit.into()]
}
Self::Delete { ref_name } => {
vec!["update-ref".into(), "-d".into(), ref_name.into()]
}
}
}
}

impl RealGitRepository {
pub fn new(
dotgit_path: &Path,
Expand Down Expand Up @@ -1089,6 +1108,17 @@ impl RealGitRepository {
))
}

fn edit_ref(&self, edit: RefEdit) -> BoxFuture<'_, Result<()>> {
let git_binary = self.git_binary();
self.executor
.spawn(async move {
let args = edit.into_args();
git_binary?.run(&args).await?;
Ok(())
})
.boxed()
}

async fn any_git_binary_help_output(&self) -> SharedString {
if let Some(output) = self.any_git_binary_help_output.lock().clone() {
return output;
Expand Down Expand Up @@ -2316,25 +2346,11 @@ impl GitRepository for RealGitRepository {
}

fn update_ref(&self, ref_name: String, commit: String) -> BoxFuture<'_, Result<()>> {
let git_binary = self.git_binary();
self.executor
.spawn(async move {
let args: Vec<OsString> = vec!["update-ref".into(), ref_name.into(), commit.into()];
git_binary?.run(&args).await?;
Ok(())
})
.boxed()
self.edit_ref(RefEdit::Update { ref_name, commit })
}

fn delete_ref(&self, ref_name: String) -> BoxFuture<'_, Result<()>> {
let git_binary = self.git_binary();
self.executor
.spawn(async move {
let args: Vec<OsString> = vec!["update-ref".into(), "-d".into(), ref_name.into()];
git_binary?.run(&args).await?;
Ok(())
})
.boxed()
self.edit_ref(RefEdit::Delete { ref_name })
}

fn repair_worktrees(&self) -> BoxFuture<'_, Result<()>> {
Expand Down
171 changes: 144 additions & 27 deletions crates/project/src/git_store.rs
Original file line number Diff line number Diff line change
Expand Up @@ -563,7 +563,9 @@ impl GitStore {
client.add_entity_request_handler(Self::handle_reset);
client.add_entity_request_handler(Self::handle_show);
client.add_entity_request_handler(Self::handle_create_checkpoint);
client.add_entity_request_handler(Self::handle_create_archive_checkpoint);
client.add_entity_request_handler(Self::handle_restore_checkpoint);
client.add_entity_request_handler(Self::handle_restore_archive_checkpoint);
client.add_entity_request_handler(Self::handle_compare_checkpoints);
client.add_entity_request_handler(Self::handle_diff_checkpoints);
client.add_entity_request_handler(Self::handle_load_commit_diff);
Expand All @@ -589,6 +591,8 @@ impl GitStore {
client.add_entity_request_handler(Self::handle_remove_worktree);
client.add_entity_request_handler(Self::handle_rename_worktree);
client.add_entity_request_handler(Self::handle_get_head_sha);
client.add_entity_request_handler(Self::handle_edit_ref);
client.add_entity_request_handler(Self::handle_repair_worktrees);
}

pub fn is_local(&self) -> bool {
Expand Down Expand Up @@ -2519,6 +2523,46 @@ impl GitStore {
Ok(proto::GitGetHeadShaResponse { sha: head_sha })
}

async fn handle_edit_ref(
this: Entity<Self>,
envelope: TypedEnvelope<proto::GitEditRef>,
mut cx: AsyncApp,
) -> Result<proto::Ack> {
let repository_id = RepositoryId::from_proto(envelope.payload.repository_id);
let repository_handle = Self::repository_for_request(&this, repository_id, &mut cx)?;
let ref_name = envelope.payload.ref_name;
let commit = match envelope.payload.action {
Some(proto::git_edit_ref::Action::UpdateToCommit(sha)) => Some(sha),
Some(proto::git_edit_ref::Action::Delete(_)) => None,
None => anyhow::bail!("GitEditRef missing action"),
};

repository_handle
.update(&mut cx, |repository_handle, _| {
repository_handle.edit_ref(ref_name, commit)
})
.await??;

Ok(proto::Ack {})
}

async fn handle_repair_worktrees(
this: Entity<Self>,
envelope: TypedEnvelope<proto::GitRepairWorktrees>,
mut cx: AsyncApp,
) -> Result<proto::Ack> {
let repository_id = RepositoryId::from_proto(envelope.payload.repository_id);
let repository_handle = Self::repository_for_request(&this, repository_id, &mut cx)?;

repository_handle
.update(&mut cx, |repository_handle, _| {
repository_handle.repair_worktrees()
})
.await??;

Ok(proto::Ack {})
}

async fn handle_get_branches(
this: Entity<Self>,
envelope: TypedEnvelope<proto::GitGetBranches>,
Expand Down Expand Up @@ -2705,6 +2749,26 @@ impl GitStore {
})
}

async fn handle_create_archive_checkpoint(
this: Entity<Self>,
envelope: TypedEnvelope<proto::GitCreateArchiveCheckpoint>,
mut cx: AsyncApp,
) -> Result<proto::GitCreateArchiveCheckpointResponse> {
let repository_id = RepositoryId::from_proto(envelope.payload.repository_id);
let repository_handle = Self::repository_for_request(&this, repository_id, &mut cx)?;

let (staged_commit_sha, unstaged_commit_sha) = repository_handle
.update(&mut cx, |repository, _| {
repository.create_archive_checkpoint()
})
.await??;

Ok(proto::GitCreateArchiveCheckpointResponse {
staged_commit_sha,
unstaged_commit_sha,
})
}

async fn handle_restore_checkpoint(
this: Entity<Self>,
envelope: TypedEnvelope<proto::GitRestoreCheckpoint>,
Expand All @@ -2726,6 +2790,25 @@ impl GitStore {
Ok(proto::Ack {})
}

async fn handle_restore_archive_checkpoint(
this: Entity<Self>,
envelope: TypedEnvelope<proto::GitRestoreArchiveCheckpoint>,
mut cx: AsyncApp,
) -> Result<proto::Ack> {
let repository_id = RepositoryId::from_proto(envelope.payload.repository_id);
let repository_handle = Self::repository_for_request(&this, repository_id, &mut cx)?;
let staged_commit_sha = envelope.payload.staged_commit_sha;
let unstaged_commit_sha = envelope.payload.unstaged_commit_sha;

repository_handle
.update(&mut cx, |repository, _| {
repository.restore_archive_checkpoint(staged_commit_sha, unstaged_commit_sha)
})
.await??;

Ok(proto::Ack {})
}

async fn handle_compare_checkpoints(
this: Entity<Self>,
envelope: TypedEnvelope<proto::GitCompareCheckpoints>,
Expand Down Expand Up @@ -6147,59 +6230,86 @@ impl Repository {
})
}

pub fn update_ref(
fn edit_ref(
&mut self,
ref_name: String,
commit: String,
commit: Option<String>,
) -> oneshot::Receiver<Result<()>> {
let id = self.id;
self.send_job(None, move |repo, _cx| async move {
match repo {
RepositoryState::Local(LocalRepositoryState { backend, .. }) => {
backend.update_ref(ref_name, commit).await
}
RepositoryState::Remote(_) => {
anyhow::bail!("update_ref is not supported for remote repositories")
RepositoryState::Local(LocalRepositoryState { backend, .. }) => match commit {
Some(commit) => backend.update_ref(ref_name, commit).await,
None => backend.delete_ref(ref_name).await,
},
RepositoryState::Remote(RemoteRepositoryState { project_id, client }) => {
let action = match commit {
Some(sha) => proto::git_edit_ref::Action::UpdateToCommit(sha),
None => {
proto::git_edit_ref::Action::Delete(proto::git_edit_ref::DeleteRef {})
}
};
client
.request(proto::GitEditRef {
project_id: project_id.0,
repository_id: id.to_proto(),
ref_name,
action: Some(action),
})
.await?;
Ok(())
}
}
})
}

pub fn update_ref(
&mut self,
ref_name: String,
commit: String,
) -> oneshot::Receiver<Result<()>> {
self.edit_ref(ref_name, Some(commit))
}

pub fn delete_ref(&mut self, ref_name: String) -> oneshot::Receiver<Result<()>> {
self.send_job(None, move |repo, _cx| async move {
match repo {
RepositoryState::Local(LocalRepositoryState { backend, .. }) => {
backend.delete_ref(ref_name).await
}
RepositoryState::Remote(_) => {
anyhow::bail!("delete_ref is not supported for remote repositories")
}
}
})
self.edit_ref(ref_name, None)
}

pub fn repair_worktrees(&mut self) -> oneshot::Receiver<Result<()>> {
let id = self.id;
self.send_job(None, move |repo, _cx| async move {
match repo {
RepositoryState::Local(LocalRepositoryState { backend, .. }) => {
backend.repair_worktrees().await
}
RepositoryState::Remote(_) => {
anyhow::bail!("repair_worktrees is not supported for remote repositories")
RepositoryState::Remote(RemoteRepositoryState { project_id, client }) => {
client
.request(proto::GitRepairWorktrees {
project_id: project_id.0,
repository_id: id.to_proto(),
})
.await?;
Ok(())
}
}
})
}

pub fn create_archive_checkpoint(&mut self) -> oneshot::Receiver<Result<(String, String)>> {
let id = self.id;
self.send_job(None, move |repo, _cx| async move {
match repo {
RepositoryState::Local(LocalRepositoryState { backend, .. }) => {
backend.create_archive_checkpoint().await
}
RepositoryState::Remote(_) => {
anyhow::bail!(
"create_archive_checkpoint is not supported for remote repositories"
)
RepositoryState::Remote(RemoteRepositoryState { project_id, client }) => {
let response = client
.request(proto::GitCreateArchiveCheckpoint {
project_id: project_id.0,
repository_id: id.to_proto(),
})
.await?;
Ok((response.staged_commit_sha, response.unstaged_commit_sha))
}
}
})
Expand All @@ -6210,17 +6320,24 @@ impl Repository {
staged_sha: String,
unstaged_sha: String,
) -> oneshot::Receiver<Result<()>> {
let id = self.id;
self.send_job(None, move |repo, _cx| async move {
match repo {
RepositoryState::Local(LocalRepositoryState { backend, .. }) => {
backend
.restore_archive_checkpoint(staged_sha, unstaged_sha)
.await
}
RepositoryState::Remote(_) => {
anyhow::bail!(
"restore_archive_checkpoint is not supported for remote repositories"
)
RepositoryState::Remote(RemoteRepositoryState { project_id, client }) => {
client
.request(proto::GitRestoreArchiveCheckpoint {
project_id: project_id.0,
repository_id: id.to_proto(),
staged_commit_sha: staged_sha,
unstaged_commit_sha: unstaged_sha,
})
.await?;
Ok(())
}
}
})
Expand Down
Loading
Loading