diff --git a/crates/signed_state/src/backend.rs b/crates/signed_state/src/backend.rs index f685d02..e2aca0a 100644 --- a/crates/signed_state/src/backend.rs +++ b/crates/signed_state/src/backend.rs @@ -515,6 +515,7 @@ impl Backend { let owner = public_key.to_bech32().unwrap(); let servers = grasp_servers.clone(); let client = self.client.clone(); + let signer = self.signer.clone(); let destination = { let dir_name = GitCache::sanitize_path_component(&name); @@ -554,9 +555,7 @@ impl Backend { let commit = work.await?; let commit_sha = Sha1Hash::from_str(&commit).map_err(|_| anyhow!("invalid id"))?; - let signer = this.update(cx, |this, _cx| this.signer.clone())?; - - let announcement = repository_announcement( + let announcement = build_announcement( &repo_id, &name, &description, @@ -594,6 +593,7 @@ impl Backend { name: &str, description: &str, grasp_servers: Vec, + default_branch: Option, cx: &mut Context, ) -> Task> { let name = name.trim().to_owned(); @@ -627,31 +627,43 @@ impl Backend { let owner = public_key.to_bech32().unwrap(); let servers = grasp_servers.clone(); + let client = self.client.clone(); + let signer = self.signer.clone(); cx.spawn(async move |this, cx| { - let work = cx.background_spawn({ - let path = path.clone(); - async move { - let repo = Repo::open(&path)?; - let state = repo.ref_state()?; - let euc = repo.root_commit()?; - Ok::<_, Error>((state, euc)) - } - }); - let (state, euc) = work.await?; + let (mut state, euc) = cx + .background_spawn({ + let path = path.clone(); + async move { + let repo = Repo::open(&path)?; + let state = repo.ref_state()?; + let euc = repo.root_commit()?; + Ok::<_, Error>((state, euc)) + } + }) + .await?; - let signer = this.update(cx, |this, _cx| this.signer.clone())?; + let chosen = default_branch + .as_deref() + .map(str::trim) + .filter(|branch| !branch.is_empty()); + + if let Some(branch) = chosen { + let wanted = format!("refs/heads/{branch}"); + if state.refs.iter().any(|(name, _)| name == &wanted) { + state.head = Some(branch.to_owned()); + } + } let euc = euc.and_then(|commit| Sha1Hash::from_str(&commit).ok()); - let announcement = - repository_announcement(&repo_id, &name, &description, &owner, &servers, euc); + let ann = build_announcement(&repo_id, &name, &description, &owner, &servers, euc); let event = announce_repository_and_push( &this, &client, &signer, - announcement, + ann, &repo_id, &owner, &servers, @@ -667,9 +679,9 @@ impl Backend { let url = format!("{base}/{owner}/{repo_id}.git"); let path = path.clone(); cx.background_spawn(async move { - Repo::open(&path) - .and_then(|repo| repo.ensure_origin(&url)) - .ok(); + if let Err(e) = Repo::open(&path).and_then(|r| r.ensure_origin(&url)) { + log::warn!("failed to ensure origin: {e}"); + } }) .await; } @@ -681,13 +693,8 @@ impl Backend { Ok(naddr) => { let path = path.clone(); cx.background_spawn(async move { - if let Err(error) = - Repo::open(&path).and_then(|repo| repo.set_nostr_repo(&naddr)) - { - log::warn!( - "failed to record the NIP-34 marker for {}: {error}", - path.display() - ); + if let Err(e) = Repo::open(&path).and_then(|r| r.set_nostr_repo(&naddr)) { + log::warn!("failed to record the NIP-34 marker: {e}"); } }) .await; @@ -742,6 +749,8 @@ impl Backend { let owner = announcement.owner.to_bech32().unwrap(); let repo_id = announcement.id.clone(); let relays = announcement.relays.clone(); + let client = self.client.clone(); + let signer = self.signer.clone(); cx.spawn(async move |this, cx| { let _guard = cx.on_drop(&this, { @@ -779,9 +788,6 @@ impl Backend { let refs = state.refs.clone(); let head = state.head.clone(); - let (client, signer) = - this.update(cx, |this, _cx| (this.client.clone(), this.signer.clone()))?; - let outcome = if refs.is_empty() { PushOutcome::default() } else { @@ -1145,7 +1151,7 @@ impl Backend { } } -fn repository_announcement( +fn build_announcement( repo_id: &str, name: &str, description: &str, diff --git a/crates/workspace/src/views/repo/init_dialog.rs b/crates/workspace/src/views/repo/init_dialog.rs index afcb4e3..8b87a6a 100644 --- a/crates/workspace/src/views/repo/init_dialog.rs +++ b/crates/workspace/src/views/repo/init_dialog.rs @@ -2,16 +2,19 @@ use std::path::PathBuf; use assets::CustomIconName; use gpui::prelude::*; -use gpui::{App, Entity, WeakEntity, Window, px}; +use gpui::{App, Entity, SharedString, WeakEntity, Window, px}; use gpui_base::h_flex; use gpui_base::input::TextareaState; use gpui_component::button::{Button, ButtonVariants}; use gpui_component::dialog::{DialogDescription, DialogFooter, DialogHeader, DialogTitle}; use gpui_component::form::{field, v_form}; use gpui_component::input::{Input, InputState, Textarea}; +use gpui_component::select::{Select, SelectState}; use gpui_component::{ActiveTheme, Disableable, WindowExt}; use settings::SettingsStore; +use signed_git::Repo; use signed_state::Backend; +use signed_ui::SelectOption; use super::RepoDetailView; use crate::views::dialog_state::{DialogProgress, error_row}; @@ -48,8 +51,11 @@ pub fn open( .auto_grow(3, 5) .placeholder("Short description") }); + let branch_select: Entity>> = + cx.new(|cx| SelectState::new(Vec::new(), None, window, cx)); load_user_grasp_servers(grasp_state.clone(), window, cx); + load_branches(local_path.clone(), branch_select.clone(), window, cx); window.open_dialog(cx, move |dialog, _window, _cx| { const DESC: &str = "Publish this local repository to Nostr."; @@ -57,6 +63,7 @@ pub fn open( let name_input = name_input.clone(); let desc_input = desc_input.clone(); let relay_input = relay_input.clone(); + let branch_select = branch_select.clone(); let state = state.clone(); let grasp_state = grasp_state.clone(); let local_path = local_path.clone(); @@ -105,6 +112,12 @@ pub fn open( .child(local_path.display().to_string()), ), ) + .child( + field() + .label("Default branch") + .description("The local branch to announce as the default") + .child(Select::new(&branch_select).w_full()), + ) .child(grasp_servers_field(&grasp_state, &relay_input, cx)), ) .children(error_row(&error, cx)) @@ -120,6 +133,7 @@ pub fn open( .on_click({ let name_input = name_input.clone(); let desc_input = desc_input.clone(); + let branch_select = branch_select.clone(); let state = state.clone(); let grasp_state = grasp_state.clone(); let local_path = local_path.clone(); @@ -129,6 +143,7 @@ pub fn open( init_repository( local_path.clone(), (name_input.clone(), desc_input.clone()), + branch_select.clone(), state.clone(), grasp_state.clone(), view.clone(), @@ -143,9 +158,11 @@ pub fn open( }); } +#[allow(clippy::too_many_arguments)] fn init_repository( local_path: PathBuf, inputs: (Entity, Entity), + branch_select: Entity>>, state: Entity, grasp_state: Entity, view: WeakEntity, @@ -156,6 +173,11 @@ fn init_repository( let name = name_input.read(cx).value().trim().to_owned(); let description = desc_input.read(cx).value().trim().to_owned(); let servers = grasp_state.read(cx).grasp_servers.clone(); + let default_branch = branch_select + .read(cx) + .selected_value() + .map(|value| value.trim().to_owned()) + .filter(|branch| !branch.is_empty()); if name.is_empty() { state.update(cx, |state, _| state.fail("Repository name is required")); @@ -171,7 +193,14 @@ fn init_repository( let backend = Backend::global(cx); let task = backend.update(cx, |backend, cx| { - backend.publish_local_repo(local_path.clone(), &name, &description, servers, cx) + backend.publish_local_repo( + local_path.clone(), + &name, + &description, + servers, + default_branch, + cx, + ) }); let handle = window.window_handle(); @@ -199,3 +228,45 @@ fn init_repository( }) .detach(); } + +/// Fills the default-branch selector with the repository's local branches, +/// preselecting the checked-out one. +fn load_branches( + local_path: PathBuf, + select: Entity>>, + window: &mut Window, + cx: &mut App, +) { + let handle = window.window_handle(); + + cx.spawn(async move |cx| { + let loaded = cx + .background_spawn(async move { + let repo = Repo::open(&local_path)?; + let current: Option = repo.current_branch().map(Into::into); + Ok::<_, anyhow::Error>((repo.branches()?, current)) + }) + .await; + + let _ = cx.update_window(handle, |_, window, cx| { + let Ok((branches, current)) = loaded.map_err(|error| { + log::warn!("failed to list branches for the default branch selector: {error:#}") + }) else { + return; + }; + + let options: Vec = branches + .into_iter() + .map(|branch| SelectOption::new(branch.clone(), branch)) + .collect(); + + select.update(cx, |state, cx| { + state.set_items(options, window, cx); + if let Some(current) = current.as_ref() { + state.set_selected_value(current, window, cx); + } + }); + }); + }) + .detach(); +} diff --git a/crates/workspace/src/views/repo/mod.rs b/crates/workspace/src/views/repo/mod.rs index a16c9f4..784e692 100644 --- a/crates/workspace/src/views/repo/mod.rs +++ b/crates/workspace/src/views/repo/mod.rs @@ -828,10 +828,6 @@ impl RepoDetailView { } /// Open the upstream repository, the `u` tag of this fork's announcement. - /// - /// The announcement may not be in the local database yet. The panel opens - /// from the address and fills in when the store loads it; the store's - /// `subscribe_remote` fetches it from the bootstrap relays. pub(super) fn open_upstream(&mut self, window: &mut Window, cx: &mut Context) { let Some(addr) = self .announcement(cx) @@ -1788,6 +1784,12 @@ impl RepoDetailView { /// The Files tab body, or the clone/initial-load spinner. fn render_files_tab(&self, cx: &mut Context) -> AnyElement { if self.loading { + let label = if self.store.read(cx).path.is_some() { + "Loading repository..." + } else { + "Cloning repository..." + }; + return v_flex() .flex_1() .size_full() @@ -1799,7 +1801,7 @@ impl RepoDetailView { div() .text_sm() .text_color(cx.theme().muted_foreground) - .child("Cloning repository..."), + .child(label), ) .into_any_element(); }