Git uncommit warning (#25977)

Adds a prompt when clicking the uncommit button when the current commit
is already present on a remote branch:

![screenshot showing
prompt](https://github.com/user-attachments/assets/d6421875-588e-4db0-aee0-a92f36bce94b)

Release Notes:

- N/A

---------

Co-authored-by: Conrad <conrad@zed.dev>
This commit is contained in:
Julia Ryan 2025-03-05 15:56:51 -08:00 committed by GitHub
parent 0200dda83d
commit e505d6bf5b
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
6 changed files with 171 additions and 19 deletions

View file

@ -202,8 +202,12 @@ pub trait GitRepository: Send + Sync {
options: Option<PushOptions>,
) -> 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 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)]
@ -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))]
@ -998,6 +1050,10 @@ impl GitRepository for FakeGitRepository {
fn get_remotes(&self, _branch: Option<&str>) -> Result<Vec<Remote>> {
unimplemented!()
}
fn check_for_pushed_commit(&self) -> Result<Vec<SharedString>> {
unimplemented!()
}
}
fn check_path_to_repo_path_errors(relative_file_path: &Path) -> Result<()> {