chore: and reconcile pushes with server only refs
Rust / build (macos-latest, stable) (push) Canceled after 0s
Rust / build (ubuntu-latest, stable) (push) Canceled after 0s
Rust / build (windows-latest, stable) (push) Canceled after 0s

This commit is contained in:
2026-10-16 20:07:13 +07:00
parent a2e7907193
commit 4b4b8b5cf2
8 changed files with 546 additions and 82 deletions
+141 -1
View File
@@ -15,10 +15,12 @@ use gpui_base::{Button as BaseButton, Disableable, Popover};
use gpui_component::alert::Alert;
use gpui_component::button::{Button, ButtonVariants};
use gpui_component::combobox::{Combobox, ComboboxEvent};
use gpui_component::dialog::{DialogDescription, DialogFooter, DialogHeader, DialogTitle};
use gpui_component::menu::{DropdownMenu, PopupMenu};
use gpui_component::spinner::Spinner;
use gpui_component::{
ActiveTheme, Colorize, Icon, IconName, Sizable, StyledExt, ThemeStyled, h_flex, v_flex,
ActiveTheme, Colorize, Icon, IconName, Sizable, StyledExt, ThemeStyled, WindowExt, h_flex,
v_flex,
};
use metadata::ProfileDirectory;
use nostr::nips::nip19::Nip19Coordinate;
@@ -72,6 +74,7 @@ pub(crate) enum RepoAction {
SendPatch,
About,
Push,
ResetRemote,
Delete,
}
@@ -841,6 +844,63 @@ impl RepoDetailView {
self.tasks.push(task);
}
pub(super) fn reset_remote(&mut self, window: &mut Window, cx: &mut Context<Self>) {
let Some(announcement) = self.announcement(cx).cloned() else {
return;
};
let store = self.store.clone();
let mirror = Mirrors::path(&announcement.addr());
let urls: Vec<String> = announcement
.clone
.iter()
.map(|url| url.to_string())
.collect();
let probe = cx.background_spawn(async move {
let mut deleted: Vec<String> = Vec::new();
let repo = Repo::open(&mirror)?;
let local: HashSet<String> = repo
.ref_state()?
.refs
.into_iter()
.map(|(name, _)| name)
.collect();
for url in &urls {
let Ok(remote_refs) = repo.ls_remote(url) else {
continue;
};
for (name, _) in remote_refs {
if !local.contains(&name) && !deleted.contains(&name) {
deleted.push(name);
}
}
}
deleted.sort();
Ok::<_, Error>(deleted)
});
cx.spawn_in(window, async move |this, cx| {
let deleted = probe.await;
this.update_in(cx, |this, window, cx| match deleted {
Ok(deleted) => open_reset_remote_dialog(store, deleted, window, cx),
Err(error) => {
this.error = Some(error.to_string().into());
cx.notify();
}
})?;
Ok::<(), Error>(())
})
.detach();
}
/// Delete the repository from nostr, announcement, state and activity.
pub(super) fn delete_repository(&mut self, _window: &mut Window, cx: &mut Context<Self>) {
let task = self
@@ -971,6 +1031,7 @@ impl RepoDetailView {
}
}
RepoAction::Push => this.push_repository(window, cx),
RepoAction::ResetRemote => this.reset_remote(window, cx),
RepoAction::Delete => this.delete_repository(window, cx),
}),
)
@@ -1173,6 +1234,16 @@ impl RepoDetailView {
.child(Icon::new(CustomIconName::Init))
.child("Republish")
})
.menu_element(
Box::new(RepoAction::ResetRemote),
|_, _| {
h_flex()
.gap_2()
.text_sm()
.child(Icon::new(IconName::RefreshCw))
.child("Reset remote to local")
},
)
.separator()
.menu_element(Box::new(RepoAction::Delete), |_, cx| {
h_flex()
@@ -2043,6 +2114,75 @@ impl RepoDetailView {
}
}
/// Confirmation for the remote reset, listing the server refs it deletes.
fn open_reset_remote_dialog(
store: Entity<RepoStore>,
deleted: Vec<String>,
window: &mut Window,
cx: &mut App,
) {
const DESC: &str = "Grasp servers are overwritten to match the local mirror. \
Refs that do not exist locally are deleted from the servers. \
This cannot be undone.";
window.open_dialog(cx, move |dialog, _window, _cx| {
let deleted = deleted.clone();
let store = store.clone();
dialog
.w(px(480.))
.keyboard(true)
.close_button(true)
.content(move |body, _window, cx| {
let deleted = deleted.clone();
let store = store.clone();
body.child(
DialogHeader::new()
.child(DialogTitle::new().child("Reset remote to local"))
.child(DialogDescription::new().child(DESC)),
)
.child(if deleted.is_empty() {
div()
.text_sm()
.text_color(cx.theme().muted_foreground)
.child("No server refs will be deleted.")
.into_any_element()
} else {
v_flex()
.gap_1()
.children(deleted.iter().map(|name| {
div()
.text_sm()
.font_family(cx.theme().mono_font_family.clone())
.child(name.clone())
}))
.into_any_element()
})
.child(
DialogFooter::new()
.justify_end()
.child(Button::new("cancel-reset-remote").label("Cancel").on_click(
|_, window, cx| {
window.close_dialog(cx);
},
))
.child(
Button::new("confirm-reset-remote")
.danger()
.label("Reset remote")
.on_click(move |_, window, cx| {
store.update(cx, |store, cx| {
store.reset_repository(cx).detach_and_log_err(cx);
});
window.close_dialog(cx);
}),
),
)
})
});
}
fn nostr_clone_url(announcement: &Announcement, nip05: Option<&str>) -> SharedString {
let owner = announcement.owner;
let user = nip05