Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 5 additions & 4 deletions crates/gitcomet-git-gix/tests/refs_integration.rs
Original file line number Diff line number Diff line change
Expand Up @@ -526,7 +526,8 @@ fn list_branches_preserves_nested_upstream_branch_names() {
);
run_git(&work_repo, &["push", "-u", "origin", "main"]);

run_git(&work_repo, &["checkout", "-b", "feature/nested/name"]);
let nested_branch = "feature/nested/name";
run_git(&work_repo, &["checkout", "-b", nested_branch]);
fs::write(work_repo.join("nested.txt"), "nested\n").unwrap();
run_git(&work_repo, &["add", "nested.txt"]);
run_git(
Expand All @@ -539,21 +540,21 @@ fn list_branches_preserves_nested_upstream_branch_names() {
"nested feature",
],
);
run_git(&work_repo, &["push", "-u", "origin", "HEAD"]);
run_git(&work_repo, &["push", "-u", "origin", nested_branch]);

let backend = GixBackend;
let opened = backend.open(&work_repo).unwrap();
let branches = opened.list_branches().unwrap();
let feature = branches
.iter()
.find(|branch| branch.name == "feature/nested/name")
.find(|branch| branch.name == nested_branch)
.expect("nested feature branch present");

assert_eq!(
feature.upstream,
Some(Upstream {
remote: "origin".to_string(),
branch: "feature/nested/name".to_string(),
branch: nested_branch.to_string(),
})
);
assert_eq!(
Expand Down
28 changes: 26 additions & 2 deletions crates/gitcomet-ui-gpui/src/view/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -342,6 +342,25 @@ impl GitCometView {
});
}

pub(in crate::view) fn register_pending_worktree_branch_removal(
&mut self,
repo_id: RepoId,
path: std::path::PathBuf,
branch: String,
) {
self.pending_worktree_branch_removals
.insert((repo_id, path), branch);
}

fn take_pending_worktree_branch_removal(
&mut self,
repo_id: RepoId,
path: &std::path::Path,
) -> Option<String> {
self.pending_worktree_branch_removals
.remove(&(repo_id, path.to_path_buf()))
}

#[cfg(test)]
pub fn new(
store: AppStore,
Expand Down Expand Up @@ -726,6 +745,7 @@ impl GitCometView {
pending_pull_reconcile_prompt: None,
pending_force_delete_branch_prompt: None,
pending_force_remove_worktree_prompt: None,
pending_worktree_branch_removals: HashMap::default(),
startup_crash_report,
#[cfg(target_os = "macos")]
recent_repos_menu_fingerprint: ui_session.recent_repos.clone(),
Expand Down Expand Up @@ -1399,11 +1419,15 @@ impl Render for GitCometView {
);
}

if let Some((repo_id, path)) = self.pending_force_remove_worktree_prompt.take()
if let Some((repo_id, path, branch)) = self.pending_force_remove_worktree_prompt.take()
&& self.active_repo_id() == Some(repo_id)
{
self.open_popover_at(
PopoverKind::ForceRemoveWorktreeConfirm { repo_id, path },
PopoverKind::ForceRemoveWorktreeConfirm {
repo_id,
path,
branch,
},
self.last_mouse_pos,
window,
cx,
Expand Down
15 changes: 12 additions & 3 deletions crates/gitcomet-ui-gpui/src/view/mod_helpers.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2041,6 +2041,7 @@ pub(super) enum PopoverKind {
ForceRemoveWorktreeConfirm {
repo_id: RepoId,
path: std::path::PathBuf,
branch: Option<String>,
},
DiscardChangesConfirm {
repo_id: RepoId,
Expand Down Expand Up @@ -2143,11 +2144,17 @@ pub(super) enum RemotePopoverKind {
#[derive(Clone, Debug, Eq, PartialEq)]
pub(super) enum WorktreePopoverKind {
SectionMenu,
Menu { path: std::path::PathBuf },
Menu {
path: std::path::PathBuf,
branch: Option<String>,
},
AddPrompt,
OpenPicker,
RemovePicker,
RemoveConfirm { path: std::path::PathBuf },
RemoveConfirm {
path: std::path::PathBuf,
branch: Option<String>,
},
}

#[derive(Clone, Debug, Eq, PartialEq)]
Expand Down Expand Up @@ -2626,7 +2633,9 @@ pub struct GitCometView {
pub(super) last_mouse_pos: Point<Pixels>,
pub(super) pending_pull_reconcile_prompt: Option<RepoId>,
pub(super) pending_force_delete_branch_prompt: Option<(RepoId, String)>,
pub(super) pending_force_remove_worktree_prompt: Option<(RepoId, std::path::PathBuf)>,
pub(super) pending_force_remove_worktree_prompt:
Option<(RepoId, std::path::PathBuf, Option<String>)>,
pub(super) pending_worktree_branch_removals: HashMap<(RepoId, std::path::PathBuf), String>,
pub(super) startup_crash_report: Option<StartupCrashReport>,
#[cfg(target_os = "macos")]
pub(super) recent_repos_menu_fingerprint: Vec<std::path::PathBuf>,
Expand Down
19 changes: 12 additions & 7 deletions crates/gitcomet-ui-gpui/src/view/panels/popover.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1441,9 +1441,12 @@ impl PopoverHost {
)
.min_w(px(160.0))
.max_w(px(320.0)),
WorktreePopoverKind::Menu { path } => self
WorktreePopoverKind::Menu { path, branch } => self
.context_menu_view(
PopoverKind::worktree(repo_id, WorktreePopoverKind::Menu { path }),
PopoverKind::worktree(
repo_id,
WorktreePopoverKind::Menu { path, branch },
),
cx,
)
.min_w(px(160.0))
Expand All @@ -1455,8 +1458,8 @@ impl PopoverHost {
WorktreePopoverKind::RemovePicker => {
worktree_remove_picker::panel(self, repo_id, cx)
}
WorktreePopoverKind::RemoveConfirm { path } => {
worktree_remove_confirm::panel(self, repo_id, path, cx)
WorktreePopoverKind::RemoveConfirm { path, branch } => {
worktree_remove_confirm::panel(self, repo_id, path, branch, cx)
}
},
RepoPopoverKind::Submodule(submodule_kind) => match submodule_kind {
Expand Down Expand Up @@ -1516,9 +1519,11 @@ impl PopoverHost {
PopoverKind::ForceDeleteBranchConfirm { repo_id, name } => {
force_delete_branch_confirm::panel(self, repo_id, name, cx)
}
PopoverKind::ForceRemoveWorktreeConfirm { repo_id, path } => {
force_remove_worktree_confirm::panel(self, repo_id, path, cx)
}
PopoverKind::ForceRemoveWorktreeConfirm {
repo_id,
path,
branch,
} => force_remove_worktree_confirm::panel(self, repo_id, path, branch, cx),
PopoverKind::DiscardChangesConfirm {
repo_id,
area,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -246,8 +246,8 @@ impl PopoverHost {
} => Some(worktree_section::model(*repo_id)),
PopoverKind::Repo {
repo_id,
kind: RepoPopoverKind::Worktree(WorktreePopoverKind::Menu { path }),
} => Some(worktree::model(*repo_id, path)),
kind: RepoPopoverKind::Worktree(WorktreePopoverKind::Menu { path, branch }),
} => Some(worktree::model(*repo_id, path, branch.as_deref())),
PopoverKind::Repo {
repo_id,
kind: RepoPopoverKind::Submodule(SubmodulePopoverKind::SectionMenu),
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,10 @@
use super::*;

pub(super) fn model(repo_id: RepoId, path: &std::path::Path) -> ContextMenuModel {
pub(super) fn model(
repo_id: RepoId,
path: &std::path::Path,
branch: Option<&str>,
) -> ContextMenuModel {
let mut items = vec![ContextMenuItem::Header("Worktree".into())];
items.push(ContextMenuItem::Label(path.display().to_string().into()));
items.push(ContextMenuItem::Separator);
Expand All @@ -24,6 +28,7 @@ pub(super) fn model(repo_id: RepoId, path: &std::path::Path) -> ContextMenuModel
repo_id,
WorktreePopoverKind::RemoveConfirm {
path: path.to_path_buf(),
branch: branch.map(ToOwned::to_owned),
},
),
}),
Expand All @@ -40,7 +45,7 @@ mod tests {
fn model_includes_open_in_new_tab() {
let repo_id = RepoId(1);
let path = std::path::PathBuf::from("/tmp/worktree");
let model = model(repo_id, &path);
let model = model(repo_id, &path, None);

let open_action = model
.items
Expand All @@ -60,4 +65,35 @@ mod tests {
ContextMenuAction::OpenRepo { path: open_path } if open_path == path
));
}

#[test]
fn model_routes_remove_through_branch_aware_confirm_when_branch_is_provided() {
let repo_id = RepoId(1);
let path = std::path::PathBuf::from("/tmp/worktree");
let model = model(repo_id, &path, Some("feature/workspace"));

let remove_action = model
.items
.iter()
.find_map(|item| match item {
ContextMenuItem::Entry { label, action, .. } if label.as_ref() == "Remove…" => {
Some((**action).clone())
}
_ => None,
})
.expect("expected Remove entry");

assert!(matches!(
remove_action,
ContextMenuAction::OpenPopover {
kind: PopoverKind::Repo {
repo_id: rid,
kind: RepoPopoverKind::Worktree(WorktreePopoverKind::RemoveConfirm {
path: remove_path,
branch: Some(branch),
}),
},
} if rid == repo_id && remove_path == path && branch == "feature/workspace"
));
}
}
13 changes: 10 additions & 3 deletions crates/gitcomet-ui-gpui/src/view/panels/popover/fingerprint.rs
Original file line number Diff line number Diff line change
Expand Up @@ -313,10 +313,15 @@ fn hash_popover_kind<H: Hasher>(kind: &PopoverKind, hasher: &mut H) {
repo_id.hash(hasher);
name.hash(hasher);
}
PopoverKind::ForceRemoveWorktreeConfirm { repo_id, path } => {
PopoverKind::ForceRemoveWorktreeConfirm {
repo_id,
path,
branch,
} => {
61u8.hash(hasher);
repo_id.hash(hasher);
path.hash(hasher);
branch.hash(hasher);
}
PopoverKind::DiscardChangesConfirm {
repo_id,
Expand Down Expand Up @@ -502,10 +507,11 @@ fn hash_repo_popover_kind<H: Hasher>(repo_id: RepoId, kind: &RepoPopoverKind, ha
16u8.hash(hasher);
repo_id.hash(hasher);
}
WorktreePopoverKind::Menu { path } => {
WorktreePopoverKind::Menu { path, branch } => {
17u8.hash(hasher);
repo_id.hash(hasher);
path.hash(hasher);
branch.hash(hasher);
}
WorktreePopoverKind::AddPrompt => {
20u8.hash(hasher);
Expand All @@ -519,10 +525,11 @@ fn hash_repo_popover_kind<H: Hasher>(repo_id: RepoId, kind: &RepoPopoverKind, ha
22u8.hash(hasher);
repo_id.hash(hasher);
}
WorktreePopoverKind::RemoveConfirm { path } => {
WorktreePopoverKind::RemoveConfirm { path, branch } => {
23u8.hash(hasher);
repo_id.hash(hasher);
path.hash(hasher);
branch.hash(hasher);
}
},
RepoPopoverKind::Submodule(submodule_kind) => match submodule_kind {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,23 @@ pub(super) fn panel(
this: &mut PopoverHost,
repo_id: RepoId,
path: std::path::PathBuf,
branch: Option<String>,
cx: &mut gpui::Context<PopoverHost>,
) -> gpui::Div {
let theme = this.theme;
let remove_branch = branch.clone();
let header: SharedString = if branch.is_some() {
"Delete worktree and branch anyway?".into()
} else {
"Delete worktree anyway?".into()
};
let description: SharedString = match branch.as_ref() {
Some(branch) => format!(
"This worktree has modified or untracked files. GitComet will force-remove it, then delete the local branch '{branch}'."
)
.into(),
None => "This worktree has modified or untracked files.".into(),
};

div()
.flex()
Expand All @@ -18,7 +32,7 @@ pub(super) fn panel(
.py_1()
.text_sm()
.font_weight(FontWeight::BOLD)
.child("Delete worktree anyway?"),
.child(header),
)
.child(div().border_t_1().border_color(theme.colors.border))
.child(
Expand All @@ -27,7 +41,7 @@ pub(super) fn panel(
.py_1()
.text_sm()
.text_color(theme.colors.text_muted)
.child("This worktree has modified or untracked files."),
.child(description),
)
.child(
div().px_2().py_1().text_sm().child(
Expand Down Expand Up @@ -67,6 +81,16 @@ pub(super) fn panel(
components::Button::new("force_remove_worktree_go", "Delete anyway")
.style(components::ButtonStyle::Danger)
.on_click(theme, cx, move |this, _e, _w, cx| {
if let Some(branch) = remove_branch.clone() {
let root_view = this.root_view.clone();
let _ = root_view.update(cx, |root, _cx| {
root.register_pending_worktree_branch_removal(
repo_id,
path.clone(),
branch,
);
});
}
this.store.dispatch(Msg::ForceRemoveWorktree {
repo_id,
path: path.clone(),
Expand Down
Loading
Loading