Compare commits
13 Commits
container-
...
git-rename
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
f5728ead57 | ||
|
|
a564db269e | ||
|
|
4b568f4437 | ||
|
|
bf3e738e91 | ||
|
|
a1a8ae3c0a | ||
|
|
52d3cbeb89 | ||
|
|
0a8a50a34b | ||
|
|
ebf0143041 | ||
|
|
4c0c6ffe12 | ||
|
|
db92d6ab5c | ||
|
|
cbc8394afb | ||
|
|
79800c9523 | ||
|
|
03c02c02fd |
@@ -1,5 +1,5 @@
|
||||
use crate::{FakeFs, FakeFsEntry, Fs};
|
||||
use anyhow::{Context as _, Result};
|
||||
use anyhow::{Context as _, Result, bail};
|
||||
use collections::{HashMap, HashSet};
|
||||
use futures::future::{self, BoxFuture, join_all};
|
||||
use git::{
|
||||
@@ -350,6 +350,19 @@ impl GitRepository for FakeGitRepository {
|
||||
})
|
||||
}
|
||||
|
||||
fn rename_branch(&self, branch: String, new_name: String) -> BoxFuture<'_, Result<()>> {
|
||||
self.with_state_async(true, move |state| {
|
||||
if !state.branches.remove(&branch) {
|
||||
bail!("no such branch: {branch}");
|
||||
}
|
||||
state.branches.insert(new_name.clone());
|
||||
if state.current_branch_name == Some(branch) {
|
||||
state.current_branch_name = Some(new_name);
|
||||
}
|
||||
Ok(())
|
||||
})
|
||||
}
|
||||
|
||||
fn blame(&self, path: RepoPath, _content: Rope) -> BoxFuture<'_, Result<git::blame::Blame>> {
|
||||
self.with_state_async(false, move |state| {
|
||||
state
|
||||
|
||||
@@ -10,6 +10,7 @@ pub use crate::remote::*;
|
||||
use anyhow::{Context as _, Result};
|
||||
pub use git2 as libgit;
|
||||
use gpui::{Action, actions};
|
||||
pub use repository::RemoteCommandOutput;
|
||||
pub use repository::WORK_DIRECTORY_REPO_PATH;
|
||||
use schemars::JsonSchema;
|
||||
use serde::{Deserialize, Serialize};
|
||||
@@ -98,6 +99,18 @@ actions!(
|
||||
]
|
||||
);
|
||||
|
||||
/// Renames a git branch.
|
||||
#[derive(Clone, Debug, Default, PartialEq, Deserialize, JsonSchema, Action)]
|
||||
#[action(namespace = git)]
|
||||
#[serde(deny_unknown_fields)]
|
||||
pub struct RenameBranch {
|
||||
/// The branch to rename.
|
||||
///
|
||||
/// Default: the current branch.
|
||||
#[serde(default)]
|
||||
pub branch: Option<String>,
|
||||
}
|
||||
|
||||
/// Restores a file to its last committed state, discarding local changes.
|
||||
#[derive(Clone, Debug, Default, PartialEq, Deserialize, JsonSchema, Action)]
|
||||
#[action(namespace = git, deprecated_aliases = ["editor::RevertFile"])]
|
||||
|
||||
@@ -343,6 +343,7 @@ pub trait GitRepository: Send + Sync {
|
||||
|
||||
fn change_branch(&self, name: String) -> BoxFuture<'_, Result<()>>;
|
||||
fn create_branch(&self, name: String) -> BoxFuture<'_, Result<()>>;
|
||||
fn rename_branch(&self, branch: String, new_name: String) -> BoxFuture<'_, Result<()>>;
|
||||
|
||||
fn reset(
|
||||
&self,
|
||||
@@ -1048,19 +1049,22 @@ impl GitRepository for RealGitRepository {
|
||||
let working_directory = self.working_directory();
|
||||
let git_binary_path = self.git_binary_path.clone();
|
||||
let executor = self.executor.clone();
|
||||
let name_clone = name.clone();
|
||||
let branch = self.executor.spawn(async move {
|
||||
let repo = repo.lock();
|
||||
let branch = if let Ok(branch) = repo.find_branch(&name, BranchType::Local) {
|
||||
let branch = if let Ok(branch) = repo.find_branch(&name_clone, BranchType::Local) {
|
||||
branch
|
||||
} else if let Ok(revision) = repo.find_branch(&name, BranchType::Remote) {
|
||||
let (_, branch_name) = name.split_once("/").context("Unexpected branch format")?;
|
||||
} else if let Ok(revision) = repo.find_branch(&name_clone, BranchType::Remote) {
|
||||
let (_, branch_name) = name_clone
|
||||
.split_once("/")
|
||||
.context("Unexpected branch format")?;
|
||||
let revision = revision.get();
|
||||
let branch_commit = revision.peel_to_commit()?;
|
||||
let mut branch = repo.branch(branch_name, &branch_commit, false)?;
|
||||
branch.set_upstream(Some(&name))?;
|
||||
let mut branch = repo.branch(&branch_name, &branch_commit, false)?;
|
||||
branch.set_upstream(Some(&name_clone))?;
|
||||
branch
|
||||
} else {
|
||||
anyhow::bail!("Branch not found");
|
||||
anyhow::bail!("Branch '{}' not found", name_clone);
|
||||
};
|
||||
|
||||
Ok(branch
|
||||
@@ -1076,7 +1080,6 @@ impl GitRepository for RealGitRepository {
|
||||
GitBinary::new(git_binary_path, working_directory?, executor)
|
||||
.run(&["checkout", &branch])
|
||||
.await?;
|
||||
|
||||
anyhow::Ok(())
|
||||
})
|
||||
.boxed()
|
||||
@@ -1094,6 +1097,21 @@ impl GitRepository for RealGitRepository {
|
||||
.boxed()
|
||||
}
|
||||
|
||||
fn rename_branch(&self, branch: String, new_name: String) -> BoxFuture<'_, Result<()>> {
|
||||
let git_binary_path = self.git_binary_path.clone();
|
||||
let working_directory = self.working_directory();
|
||||
let executor = self.executor.clone();
|
||||
|
||||
self.executor
|
||||
.spawn(async move {
|
||||
GitBinary::new(git_binary_path, working_directory?, executor)
|
||||
.run(&["branch", "-m", &branch, &new_name])
|
||||
.await?;
|
||||
anyhow::Ok(())
|
||||
})
|
||||
.boxed()
|
||||
}
|
||||
|
||||
fn blame(&self, path: RepoPath, content: Rope) -> BoxFuture<'_, Result<crate::blame::Blame>> {
|
||||
let working_directory = self.working_directory();
|
||||
let git_binary_path = self.git_binary_path.clone();
|
||||
|
||||
@@ -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};
|
||||
@@ -192,6 +200,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) {
|
||||
@@ -235,6 +246,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,
|
||||
|
||||
@@ -396,6 +396,7 @@ impl GitStore {
|
||||
client.add_entity_request_handler(Self::handle_get_default_branch);
|
||||
client.add_entity_request_handler(Self::handle_change_branch);
|
||||
client.add_entity_request_handler(Self::handle_create_branch);
|
||||
client.add_entity_request_handler(Self::handle_rename_branch);
|
||||
client.add_entity_request_handler(Self::handle_git_init);
|
||||
client.add_entity_request_handler(Self::handle_push);
|
||||
client.add_entity_request_handler(Self::handle_pull);
|
||||
@@ -1903,6 +1904,25 @@ impl GitStore {
|
||||
Ok(proto::Ack {})
|
||||
}
|
||||
|
||||
async fn handle_rename_branch(
|
||||
this: Entity<Self>,
|
||||
envelope: TypedEnvelope<proto::GitRenameBranch>,
|
||||
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 branch = envelope.payload.branch;
|
||||
let new_name = envelope.payload.new_name;
|
||||
|
||||
repository_handle
|
||||
.update(&mut cx, |repository_handle, _| {
|
||||
repository_handle.rename_branch(branch, new_name)
|
||||
})?
|
||||
.await??;
|
||||
|
||||
Ok(proto::Ack {})
|
||||
}
|
||||
|
||||
async fn handle_show(
|
||||
this: Entity<Self>,
|
||||
envelope: TypedEnvelope<proto::GitShow>,
|
||||
@@ -4155,6 +4175,36 @@ impl Repository {
|
||||
)
|
||||
}
|
||||
|
||||
pub fn rename_branch(
|
||||
&mut self,
|
||||
branch: String,
|
||||
new_name: String,
|
||||
) -> oneshot::Receiver<Result<()>> {
|
||||
let id = self.id;
|
||||
self.send_job(
|
||||
Some(format!("git branch -m {branch} {new_name}").into()),
|
||||
move |repo, _cx| async move {
|
||||
match repo {
|
||||
RepositoryState::Local { backend, .. } => {
|
||||
backend.rename_branch(branch, new_name).await
|
||||
}
|
||||
RepositoryState::Remote { project_id, client } => {
|
||||
client
|
||||
.request(proto::GitRenameBranch {
|
||||
project_id: project_id.0,
|
||||
repository_id: id.to_proto(),
|
||||
branch,
|
||||
new_name,
|
||||
})
|
||||
.await?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
pub fn check_for_pushed_commits(&mut self) -> oneshot::Receiver<Result<Vec<SharedString>>> {
|
||||
let id = self.id;
|
||||
self.send_job(None, move |repo, _cx| async move {
|
||||
|
||||
@@ -182,6 +182,13 @@ message GitChangeBranch {
|
||||
string branch_name = 4;
|
||||
}
|
||||
|
||||
message GitRenameBranch {
|
||||
uint64 project_id = 1;
|
||||
uint64 repository_id = 2;
|
||||
string branch = 3;
|
||||
string new_name = 4;
|
||||
}
|
||||
|
||||
message GitDiff {
|
||||
uint64 project_id = 1;
|
||||
reserved 2;
|
||||
|
||||
@@ -413,7 +413,9 @@ message Envelope {
|
||||
ExternalAgentsUpdated external_agents_updated = 375;
|
||||
|
||||
ExternalAgentLoadingStatusUpdated external_agent_loading_status_updated = 376;
|
||||
NewExternalAgentVersionAvailable new_external_agent_version_available = 377; // current max
|
||||
NewExternalAgentVersionAvailable new_external_agent_version_available = 377;
|
||||
|
||||
GitRenameBranch git_rename_branch = 378; // current max
|
||||
}
|
||||
|
||||
reserved 87 to 88;
|
||||
|
||||
@@ -301,6 +301,7 @@ messages!(
|
||||
(AskPassResponse, Background),
|
||||
(GitCreateBranch, Background),
|
||||
(GitChangeBranch, Background),
|
||||
(GitRenameBranch, Background),
|
||||
(CheckForPushedCommits, Background),
|
||||
(CheckForPushedCommitsResponse, Background),
|
||||
(GitDiff, Background),
|
||||
@@ -484,6 +485,7 @@ request_messages!(
|
||||
(AskPassRequest, AskPassResponse),
|
||||
(GitCreateBranch, Ack),
|
||||
(GitChangeBranch, Ack),
|
||||
(GitRenameBranch, Ack),
|
||||
(CheckForPushedCommits, CheckForPushedCommitsResponse),
|
||||
(GitDiff, GitDiffResponse),
|
||||
(GitInit, Ack),
|
||||
@@ -638,6 +640,7 @@ entity_messages!(
|
||||
Pull,
|
||||
AskPassRequest,
|
||||
GitChangeBranch,
|
||||
GitRenameBranch,
|
||||
GitCreateBranch,
|
||||
CheckForPushedCommits,
|
||||
GitDiff,
|
||||
|
||||
Reference in New Issue
Block a user