Add branch rename action to Git panel (#38273)

Reopening #35136, cc @launay12u

Release Notes:

- git: added `git: rename branch` action to rename a branch (`git branch
-m`)

---------

Co-authored-by: Guillaume Launay <guillaume.launay@paylead.fr>
Co-authored-by: Peter Tripp <petertripp@gmail.com>
This commit is contained in:
Cole Miller
2025-09-18 18:17:13 +00:00
committed by GitHub
co-authored by Guillaume Launay Peter Tripp
parent df50b5c14a
commit 439d31e2d4
8 changed files with 238 additions and 8 deletions
+130 -3
View File
@@ -4,20 +4,28 @@ use ::settings::Settings;
use command_palette_hooks::CommandPaletteFilter;
use commit_modal::CommitModal;
use editor::{Editor, actions::DiffClipboardWithSelectionData};
use ui::{
Headline, HeadlineSize, Icon, IconName, IconSize, IntoElement, ParentElement, Render, Styled,
StyledExt, div, h_flex, rems, v_flex,
};
mod blame_ui;
use git::{
repository::{Branch, Upstream, UpstreamTracking, UpstreamTrackingStatus},
status::{FileStatus, StatusCode, UnmergedStatus, UnmergedStatusCode},
};
use git_panel_settings::GitPanelSettings;
use gpui::{
Action, App, Context, DismissEvent, Entity, EventEmitter, FocusHandle, Focusable, Window,
actions,
Action, App, Context, DismissEvent, Entity, EventEmitter, FocusHandle, Focusable, SharedString,
Window, actions,
};
use menu::{Cancel, Confirm};
use onboarding::GitOnboardingModal;
use project::git_store::Repository;
use project_diff::ProjectDiff;
use ui::prelude::*;
use workspace::{ModalView, Workspace};
use workspace::{ModalView, Workspace, notifications::DetachAndPromptErr};
use zed_actions;
use crate::{git_panel::GitPanel, text_diff_view::TextDiffView};
@@ -202,6 +210,9 @@ pub fn init(cx: &mut App) {
workspace.register_action(|workspace, _: &git::OpenModifiedFiles, window, cx| {
open_modified_files(workspace, window, cx);
});
workspace.register_action(|workspace, _: &git::RenameBranch, window, cx| {
rename_current_branch(workspace, window, cx);
});
workspace.register_action(
|workspace, action: &DiffClipboardWithSelectionData, window, cx| {
if let Some(task) = TextDiffView::open(action, workspace, window, cx) {
@@ -245,6 +256,122 @@ pub fn git_status_icon(status: FileStatus) -> impl IntoElement {
GitStatusIcon::new(status)
}
struct RenameBranchModal {
current_branch: SharedString,
editor: Entity<Editor>,
repo: Entity<Repository>,
}
impl RenameBranchModal {
fn new(
current_branch: String,
repo: Entity<Repository>,
window: &mut Window,
cx: &mut Context<Self>,
) -> Self {
let editor = cx.new(|cx| {
let mut editor = Editor::single_line(window, cx);
editor.set_text(current_branch.clone(), window, cx);
editor
});
Self {
current_branch: current_branch.into(),
editor,
repo,
}
}
fn cancel(&mut self, _: &Cancel, _window: &mut Window, cx: &mut Context<Self>) {
cx.emit(DismissEvent);
}
fn confirm(&mut self, _: &Confirm, window: &mut Window, cx: &mut Context<Self>) {
let new_name = self.editor.read(cx).text(cx);
if new_name.is_empty() || new_name == self.current_branch.as_ref() {
cx.emit(DismissEvent);
return;
}
let repo = self.repo.clone();
let current_branch = self.current_branch.to_string();
cx.spawn(async move |_, cx| {
match repo
.update(cx, |repo, _| {
repo.rename_branch(current_branch, new_name.clone())
})?
.await
{
Ok(Ok(_)) => Ok(()),
Ok(Err(error)) => Err(error),
Err(_) => Err(anyhow::anyhow!("Operation was canceled")),
}
})
.detach_and_prompt_err("Failed to rename branch", window, cx, |_, _, _| None);
cx.emit(DismissEvent);
}
}
impl EventEmitter<DismissEvent> for RenameBranchModal {}
impl ModalView for RenameBranchModal {}
impl Focusable for RenameBranchModal {
fn focus_handle(&self, cx: &App) -> FocusHandle {
self.editor.focus_handle(cx)
}
}
impl Render for RenameBranchModal {
fn render(&mut self, _: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
v_flex()
.key_context("RenameBranchModal")
.on_action(cx.listener(Self::cancel))
.on_action(cx.listener(Self::confirm))
.elevation_2(cx)
.w(rems(34.))
.child(
h_flex()
.px_3()
.pt_2()
.pb_1()
.w_full()
.gap_1p5()
.child(Icon::new(IconName::GitBranch).size(IconSize::XSmall))
.child(
Headline::new(format!("Rename Branch ({})", self.current_branch))
.size(HeadlineSize::XSmall),
),
)
.child(div().px_3().pb_3().w_full().child(self.editor.clone()))
}
}
fn rename_current_branch(
workspace: &mut Workspace,
window: &mut Window,
cx: &mut Context<Workspace>,
) {
let Some(panel) = workspace.panel::<git_panel::GitPanel>(cx) else {
return;
};
let current_branch: Option<String> = panel.update(cx, |panel, cx| {
let repo = panel.active_repository.as_ref()?;
let repo = repo.read(cx);
repo.branch.as_ref().map(|branch| branch.name().to_string())
});
let Some(current_branch_name) = current_branch else {
return;
};
let repo = panel.read(cx).active_repository.clone();
let Some(repo) = repo else {
return;
};
workspace.toggle_modal(window, cx, |window, cx| {
RenameBranchModal::new(current_branch_name, repo, window, cx)
});
}
fn render_remote_button(
id: impl Into<SharedString>,
branch: &Branch,