Detailed changes
@@ -407,6 +407,7 @@ impl Server {
.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::GitChangeBranch>)
+ .add_request_handler(forward_mutating_project_request::<proto::CheckForPushedCommits>)
.add_message_handler(broadcast_project_message_from_host::<proto::AdvertiseContexts>)
.add_message_handler(update_context)
.add_request_handler({
@@ -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<()> {
@@ -1265,34 +1265,20 @@ impl GitPanel {
return;
};
- // TODO: Use git merge-base to find the upstream and main branch split
- 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 confirmation = self.check_for_pushed_commits(window, cx);
let prior_head = self.load_commit_details("HEAD", cx);
let task = cx.spawn_in(window, |this, mut cx| async move {
let result = maybe!(async {
- if !confirmation.await {
- Ok(None)
- } else {
+ if let Ok(true) = confirmation.await {
let prior_head = prior_head.await?;
repo.update(&mut cx, |repo, _| repo.reset("HEAD^", ResetMode::Soft))?
.await??;
Ok(Some(prior_head))
+ } else {
+ Ok(None)
}
})
.await;
@@ -1315,6 +1301,48 @@ impl GitPanel {
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
pub fn suggest_commit_message(&self) -> Option<String> {
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_open_commit_message_buffer);
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>> {
@@ -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(
this: &Entity<Self>,
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;
GitCreateBranch git_create_branch = 313;
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;
@@ -2875,3 +2878,13 @@ message GitChangeBranch {
uint64 work_directory_id = 3;
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),
(GitCreateBranch, Background),
(GitChangeBranch, Background),
+ (CheckForPushedCommits, Background),
+ (CheckForPushedCommitsResponse, Background),
);
request_messages!(
@@ -598,6 +600,7 @@ request_messages!(
(Pull, RemoteMessageResponse),
(GitCreateBranch, Ack),
(GitChangeBranch, Ack),
+ (CheckForPushedCommits, CheckForPushedCommitsResponse),
);
entity_messages!(
@@ -701,6 +704,7 @@ entity_messages!(
Pull,
GitChangeBranch,
GitCreateBranch,
+ CheckForPushedCommits,
);
entity_messages!(