Git uncommit warning (#25977)
Adds a prompt when clicking the uncommit button when the current commit is already present on a remote branch:  Release Notes: - N/A --------- Co-authored-by: Conrad <conrad@zed.dev>
This commit is contained in:
parent
0200dda83d
commit
e505d6bf5b
6 changed files with 171 additions and 19 deletions
|
@ -407,6 +407,7 @@ impl Server {
|
||||||
.add_request_handler(forward_mutating_project_request::<proto::OpenCommitMessageBuffer>)
|
.add_request_handler(forward_mutating_project_request::<proto::OpenCommitMessageBuffer>)
|
||||||
.add_request_handler(forward_mutating_project_request::<proto::GitCreateBranch>)
|
.add_request_handler(forward_mutating_project_request::<proto::GitCreateBranch>)
|
||||||
.add_request_handler(forward_mutating_project_request::<proto::GitChangeBranch>)
|
.add_request_handler(forward_mutating_project_request::<proto::GitChangeBranch>)
|
||||||
|
.add_request_handler(forward_mutating_project_request::<proto::CheckForPushedCommits>)
|
||||||
.add_message_handler(broadcast_project_message_from_host::<proto::AdvertiseContexts>)
|
.add_message_handler(broadcast_project_message_from_host::<proto::AdvertiseContexts>)
|
||||||
.add_message_handler(update_context)
|
.add_message_handler(update_context)
|
||||||
.add_request_handler({
|
.add_request_handler({
|
||||||
|
|
|
@ -202,8 +202,12 @@ pub trait GitRepository: Send + Sync {
|
||||||
options: Option<PushOptions>,
|
options: Option<PushOptions>,
|
||||||
) -> Result<RemoteCommandOutput>;
|
) -> Result<RemoteCommandOutput>;
|
||||||
fn pull(&self, branch_name: &str, upstream_name: &str) -> Result<RemoteCommandOutput>;
|
fn pull(&self, branch_name: &str, upstream_name: &str) -> Result<RemoteCommandOutput>;
|
||||||
fn get_remotes(&self, branch_name: Option<&str>) -> Result<Vec<Remote>>;
|
|
||||||
fn fetch(&self) -> Result<RemoteCommandOutput>;
|
fn fetch(&self) -> Result<RemoteCommandOutput>;
|
||||||
|
|
||||||
|
fn get_remotes(&self, branch_name: Option<&str>) -> Result<Vec<Remote>>;
|
||||||
|
|
||||||
|
/// returns a list of remote branches that contain HEAD
|
||||||
|
fn check_for_pushed_commit(&self) -> Result<Vec<SharedString>>;
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Deserialize, JsonSchema)]
|
#[derive(Debug, Clone, Copy, PartialEq, Eq, Deserialize, JsonSchema)]
|
||||||
|
@ -781,6 +785,54 @@ impl GitRepository for RealGitRepository {
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn check_for_pushed_commit(&self) -> Result<Vec<SharedString>> {
|
||||||
|
let working_directory = self.working_directory()?;
|
||||||
|
let git_cmd = |args: &[&str]| -> Result<String> {
|
||||||
|
let output = new_std_command(&self.git_binary_path)
|
||||||
|
.current_dir(&working_directory)
|
||||||
|
.args(args)
|
||||||
|
.output()?;
|
||||||
|
if output.status.success() {
|
||||||
|
Ok(String::from_utf8(output.stdout)?)
|
||||||
|
} else {
|
||||||
|
Err(anyhow!(String::from_utf8_lossy(&output.stderr).to_string()))
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
let head = git_cmd(&["rev-parse", "HEAD"])
|
||||||
|
.context("Failed to get HEAD")?
|
||||||
|
.trim()
|
||||||
|
.to_owned();
|
||||||
|
|
||||||
|
let mut remote_branches = vec![];
|
||||||
|
let mut add_if_matching = |remote_head: &str| {
|
||||||
|
if let Ok(merge_base) = git_cmd(&["merge-base", &head, remote_head]) {
|
||||||
|
if merge_base.trim() == head {
|
||||||
|
if let Some(s) = remote_head.strip_prefix("refs/remotes/") {
|
||||||
|
remote_branches.push(s.to_owned().into());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// check the main branch of each remote
|
||||||
|
let remotes = git_cmd(&["remote"]).context("Failed to get remotes")?;
|
||||||
|
for remote in remotes.lines() {
|
||||||
|
if let Ok(remote_head) =
|
||||||
|
git_cmd(&["symbolic-ref", &format!("refs/remotes/{remote}/HEAD")])
|
||||||
|
{
|
||||||
|
add_if_matching(remote_head.trim());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ... and the remote branch that the checked-out one is tracking
|
||||||
|
if let Ok(remote_head) = git_cmd(&["rev-parse", "--symbolic-full-name", "@{u}"]) {
|
||||||
|
add_if_matching(remote_head.trim());
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(remote_branches)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(not(windows))]
|
#[cfg(not(windows))]
|
||||||
|
@ -998,6 +1050,10 @@ impl GitRepository for FakeGitRepository {
|
||||||
fn get_remotes(&self, _branch: Option<&str>) -> Result<Vec<Remote>> {
|
fn get_remotes(&self, _branch: Option<&str>) -> Result<Vec<Remote>> {
|
||||||
unimplemented!()
|
unimplemented!()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn check_for_pushed_commit(&self) -> Result<Vec<SharedString>> {
|
||||||
|
unimplemented!()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn check_path_to_repo_path_errors(relative_file_path: &Path) -> Result<()> {
|
fn check_path_to_repo_path_errors(relative_file_path: &Path) -> Result<()> {
|
||||||
|
|
|
@ -1265,34 +1265,20 @@ impl GitPanel {
|
||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
|
|
||||||
// TODO: Use git merge-base to find the upstream and main branch split
|
let confirmation = self.check_for_pushed_commits(window, cx);
|
||||||
let confirmation = Task::ready(true);
|
|
||||||
// let confirmation = if self.commit_editor.read(cx).is_empty(cx) {
|
|
||||||
// Task::ready(true)
|
|
||||||
// } else {
|
|
||||||
// let prompt = window.prompt(
|
|
||||||
// PromptLevel::Warning,
|
|
||||||
// "Uncomitting will replace the current commit message with the previous commit's message",
|
|
||||||
// None,
|
|
||||||
// &["Ok", "Cancel"],
|
|
||||||
// cx,
|
|
||||||
// );
|
|
||||||
// cx.spawn(|_, _| async move { prompt.await.is_ok_and(|i| i == 0) })
|
|
||||||
// };
|
|
||||||
|
|
||||||
let prior_head = self.load_commit_details("HEAD", cx);
|
let prior_head = self.load_commit_details("HEAD", cx);
|
||||||
|
|
||||||
let task = cx.spawn_in(window, |this, mut cx| async move {
|
let task = cx.spawn_in(window, |this, mut cx| async move {
|
||||||
let result = maybe!(async {
|
let result = maybe!(async {
|
||||||
if !confirmation.await {
|
if let Ok(true) = confirmation.await {
|
||||||
Ok(None)
|
|
||||||
} else {
|
|
||||||
let prior_head = prior_head.await?;
|
let prior_head = prior_head.await?;
|
||||||
|
|
||||||
repo.update(&mut cx, |repo, _| repo.reset("HEAD^", ResetMode::Soft))?
|
repo.update(&mut cx, |repo, _| repo.reset("HEAD^", ResetMode::Soft))?
|
||||||
.await??;
|
.await??;
|
||||||
|
|
||||||
Ok(Some(prior_head))
|
Ok(Some(prior_head))
|
||||||
|
} else {
|
||||||
|
Ok(None)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.await;
|
.await;
|
||||||
|
@ -1315,6 +1301,48 @@ impl GitPanel {
|
||||||
self.pending_commit = Some(task);
|
self.pending_commit = Some(task);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn check_for_pushed_commits(
|
||||||
|
&mut self,
|
||||||
|
window: &mut Window,
|
||||||
|
cx: &mut Context<Self>,
|
||||||
|
) -> impl Future<Output = Result<bool, anyhow::Error>> {
|
||||||
|
let repo = self.active_repository.clone();
|
||||||
|
let mut cx = window.to_async(cx);
|
||||||
|
|
||||||
|
async move {
|
||||||
|
let Some(repo) = repo else {
|
||||||
|
return Err(anyhow::anyhow!("No active repository"));
|
||||||
|
};
|
||||||
|
|
||||||
|
let pushed_to: Vec<SharedString> = repo
|
||||||
|
.update(&mut cx, |repo, _| repo.check_for_pushed_commits())?
|
||||||
|
.await??;
|
||||||
|
|
||||||
|
if pushed_to.is_empty() {
|
||||||
|
Ok(true)
|
||||||
|
} else {
|
||||||
|
#[derive(strum::EnumIter, strum::VariantNames)]
|
||||||
|
#[strum(serialize_all = "title_case")]
|
||||||
|
enum CancelUncommit {
|
||||||
|
Uncommit,
|
||||||
|
Cancel,
|
||||||
|
}
|
||||||
|
let detail = format!(
|
||||||
|
"This commit was already pushed to {}.",
|
||||||
|
pushed_to.into_iter().join(", ")
|
||||||
|
);
|
||||||
|
let result = cx
|
||||||
|
.update(|window, cx| prompt("Are you sure?", Some(&detail), window, cx))?
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
match result {
|
||||||
|
CancelUncommit::Cancel => Ok(false),
|
||||||
|
CancelUncommit::Uncommit => Ok(true),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Suggests a commit message based on the changed files and their statuses
|
/// Suggests a commit message based on the changed files and their statuses
|
||||||
pub fn suggest_commit_message(&self) -> Option<String> {
|
pub fn suggest_commit_message(&self) -> Option<String> {
|
||||||
if self.total_staged_count() != 1 {
|
if self.total_staged_count() != 1 {
|
||||||
|
|
|
@ -110,6 +110,7 @@ impl GitStore {
|
||||||
client.add_entity_request_handler(Self::handle_checkout_files);
|
client.add_entity_request_handler(Self::handle_checkout_files);
|
||||||
client.add_entity_request_handler(Self::handle_open_commit_message_buffer);
|
client.add_entity_request_handler(Self::handle_open_commit_message_buffer);
|
||||||
client.add_entity_request_handler(Self::handle_set_index_text);
|
client.add_entity_request_handler(Self::handle_set_index_text);
|
||||||
|
client.add_entity_request_handler(Self::handle_check_for_pushed_commits);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn active_repository(&self) -> Option<Entity<Repository>> {
|
pub fn active_repository(&self) -> Option<Entity<Repository>> {
|
||||||
|
@ -627,6 +628,29 @@ impl GitStore {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async fn handle_check_for_pushed_commits(
|
||||||
|
this: Entity<Self>,
|
||||||
|
envelope: TypedEnvelope<proto::CheckForPushedCommits>,
|
||||||
|
mut cx: AsyncApp,
|
||||||
|
) -> Result<proto::CheckForPushedCommitsResponse> {
|
||||||
|
let worktree_id = WorktreeId::from_proto(envelope.payload.worktree_id);
|
||||||
|
let work_directory_id = ProjectEntryId::from_proto(envelope.payload.work_directory_id);
|
||||||
|
let repository_handle =
|
||||||
|
Self::repository_for_request(&this, worktree_id, work_directory_id, &mut cx)?;
|
||||||
|
|
||||||
|
let branches = repository_handle
|
||||||
|
.update(&mut cx, |repository_handle, _| {
|
||||||
|
repository_handle.check_for_pushed_commits()
|
||||||
|
})?
|
||||||
|
.await??;
|
||||||
|
Ok(proto::CheckForPushedCommitsResponse {
|
||||||
|
pushed_to: branches
|
||||||
|
.into_iter()
|
||||||
|
.map(|commit| commit.to_string())
|
||||||
|
.collect(),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
fn repository_for_request(
|
fn repository_for_request(
|
||||||
this: &Entity<Self>,
|
this: &Entity<Self>,
|
||||||
worktree_id: WorktreeId,
|
worktree_id: WorktreeId,
|
||||||
|
@ -1423,4 +1447,30 @@ impl Repository {
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn check_for_pushed_commits(&self) -> oneshot::Receiver<Result<Vec<SharedString>>> {
|
||||||
|
self.send_job(|repo| async move {
|
||||||
|
match repo {
|
||||||
|
GitRepo::Local(git_repository) => git_repository.check_for_pushed_commit(),
|
||||||
|
GitRepo::Remote {
|
||||||
|
project_id,
|
||||||
|
client,
|
||||||
|
worktree_id,
|
||||||
|
work_directory_id,
|
||||||
|
} => {
|
||||||
|
let response = client
|
||||||
|
.request(proto::CheckForPushedCommits {
|
||||||
|
project_id: project_id.0,
|
||||||
|
worktree_id: worktree_id.to_proto(),
|
||||||
|
work_directory_id: work_directory_id.to_proto(),
|
||||||
|
})
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
let branches = response.pushed_to.into_iter().map(Into::into).collect();
|
||||||
|
|
||||||
|
Ok(branches)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -336,6 +336,9 @@ message Envelope {
|
||||||
GitGetBranches git_get_branches = 312;
|
GitGetBranches git_get_branches = 312;
|
||||||
GitCreateBranch git_create_branch = 313;
|
GitCreateBranch git_create_branch = 313;
|
||||||
GitChangeBranch git_change_branch = 314; // current max
|
GitChangeBranch git_change_branch = 314; // current max
|
||||||
|
|
||||||
|
CheckForPushedCommits check_for_pushed_commits = 315;
|
||||||
|
CheckForPushedCommitsResponse check_for_pushed_commits_response = 316; // current max
|
||||||
}
|
}
|
||||||
|
|
||||||
reserved 87 to 88;
|
reserved 87 to 88;
|
||||||
|
@ -2875,3 +2878,13 @@ message GitChangeBranch {
|
||||||
uint64 work_directory_id = 3;
|
uint64 work_directory_id = 3;
|
||||||
string branch_name = 4;
|
string branch_name = 4;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
message CheckForPushedCommits {
|
||||||
|
uint64 project_id = 1;
|
||||||
|
uint64 worktree_id = 2;
|
||||||
|
uint64 work_directory_id = 3;
|
||||||
|
}
|
||||||
|
|
||||||
|
message CheckForPushedCommitsResponse {
|
||||||
|
repeated string pushed_to = 1;
|
||||||
|
}
|
||||||
|
|
|
@ -454,6 +454,8 @@ messages!(
|
||||||
(RemoteMessageResponse, Background),
|
(RemoteMessageResponse, Background),
|
||||||
(GitCreateBranch, Background),
|
(GitCreateBranch, Background),
|
||||||
(GitChangeBranch, Background),
|
(GitChangeBranch, Background),
|
||||||
|
(CheckForPushedCommits, Background),
|
||||||
|
(CheckForPushedCommitsResponse, Background),
|
||||||
);
|
);
|
||||||
|
|
||||||
request_messages!(
|
request_messages!(
|
||||||
|
@ -598,6 +600,7 @@ request_messages!(
|
||||||
(Pull, RemoteMessageResponse),
|
(Pull, RemoteMessageResponse),
|
||||||
(GitCreateBranch, Ack),
|
(GitCreateBranch, Ack),
|
||||||
(GitChangeBranch, Ack),
|
(GitChangeBranch, Ack),
|
||||||
|
(CheckForPushedCommits, CheckForPushedCommitsResponse),
|
||||||
);
|
);
|
||||||
|
|
||||||
entity_messages!(
|
entity_messages!(
|
||||||
|
@ -701,6 +704,7 @@ entity_messages!(
|
||||||
Pull,
|
Pull,
|
||||||
GitChangeBranch,
|
GitChangeBranch,
|
||||||
GitCreateBranch,
|
GitCreateBranch,
|
||||||
|
CheckForPushedCommits,
|
||||||
);
|
);
|
||||||
|
|
||||||
entity_messages!(
|
entity_messages!(
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue