feat: allow choosing the default branch when publishing a repo
This commit is contained in:
@@ -515,6 +515,7 @@ impl Backend {
|
|||||||
let owner = public_key.to_bech32().unwrap();
|
let owner = public_key.to_bech32().unwrap();
|
||||||
let servers = grasp_servers.clone();
|
let servers = grasp_servers.clone();
|
||||||
let client = self.client.clone();
|
let client = self.client.clone();
|
||||||
|
let signer = self.signer.clone();
|
||||||
|
|
||||||
let destination = {
|
let destination = {
|
||||||
let dir_name = GitCache::sanitize_path_component(&name);
|
let dir_name = GitCache::sanitize_path_component(&name);
|
||||||
@@ -554,9 +555,7 @@ impl Backend {
|
|||||||
let commit = work.await?;
|
let commit = work.await?;
|
||||||
let commit_sha = Sha1Hash::from_str(&commit).map_err(|_| anyhow!("invalid id"))?;
|
let commit_sha = Sha1Hash::from_str(&commit).map_err(|_| anyhow!("invalid id"))?;
|
||||||
|
|
||||||
let signer = this.update(cx, |this, _cx| this.signer.clone())?;
|
let announcement = build_announcement(
|
||||||
|
|
||||||
let announcement = repository_announcement(
|
|
||||||
&repo_id,
|
&repo_id,
|
||||||
&name,
|
&name,
|
||||||
&description,
|
&description,
|
||||||
@@ -594,6 +593,7 @@ impl Backend {
|
|||||||
name: &str,
|
name: &str,
|
||||||
description: &str,
|
description: &str,
|
||||||
grasp_servers: Vec<RelayUrl>,
|
grasp_servers: Vec<RelayUrl>,
|
||||||
|
default_branch: Option<String>,
|
||||||
cx: &mut Context<Self>,
|
cx: &mut Context<Self>,
|
||||||
) -> Task<Result<Announcement, Error>> {
|
) -> Task<Result<Announcement, Error>> {
|
||||||
let name = name.trim().to_owned();
|
let name = name.trim().to_owned();
|
||||||
@@ -627,31 +627,43 @@ impl Backend {
|
|||||||
|
|
||||||
let owner = public_key.to_bech32().unwrap();
|
let owner = public_key.to_bech32().unwrap();
|
||||||
let servers = grasp_servers.clone();
|
let servers = grasp_servers.clone();
|
||||||
|
|
||||||
let client = self.client.clone();
|
let client = self.client.clone();
|
||||||
|
let signer = self.signer.clone();
|
||||||
|
|
||||||
cx.spawn(async move |this, cx| {
|
cx.spawn(async move |this, cx| {
|
||||||
let work = cx.background_spawn({
|
let (mut state, euc) = cx
|
||||||
let path = path.clone();
|
.background_spawn({
|
||||||
async move {
|
let path = path.clone();
|
||||||
let repo = Repo::open(&path)?;
|
async move {
|
||||||
let state = repo.ref_state()?;
|
let repo = Repo::open(&path)?;
|
||||||
let euc = repo.root_commit()?;
|
let state = repo.ref_state()?;
|
||||||
Ok::<_, Error>((state, euc))
|
let euc = repo.root_commit()?;
|
||||||
}
|
Ok::<_, Error>((state, euc))
|
||||||
});
|
}
|
||||||
let (state, euc) = work.await?;
|
})
|
||||||
|
.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 euc = euc.and_then(|commit| Sha1Hash::from_str(&commit).ok());
|
||||||
let announcement =
|
let ann = build_announcement(&repo_id, &name, &description, &owner, &servers, euc);
|
||||||
repository_announcement(&repo_id, &name, &description, &owner, &servers, euc);
|
|
||||||
|
|
||||||
let event = announce_repository_and_push(
|
let event = announce_repository_and_push(
|
||||||
&this,
|
&this,
|
||||||
&client,
|
&client,
|
||||||
&signer,
|
&signer,
|
||||||
announcement,
|
ann,
|
||||||
&repo_id,
|
&repo_id,
|
||||||
&owner,
|
&owner,
|
||||||
&servers,
|
&servers,
|
||||||
@@ -667,9 +679,9 @@ impl Backend {
|
|||||||
let url = format!("{base}/{owner}/{repo_id}.git");
|
let url = format!("{base}/{owner}/{repo_id}.git");
|
||||||
let path = path.clone();
|
let path = path.clone();
|
||||||
cx.background_spawn(async move {
|
cx.background_spawn(async move {
|
||||||
Repo::open(&path)
|
if let Err(e) = Repo::open(&path).and_then(|r| r.ensure_origin(&url)) {
|
||||||
.and_then(|repo| repo.ensure_origin(&url))
|
log::warn!("failed to ensure origin: {e}");
|
||||||
.ok();
|
}
|
||||||
})
|
})
|
||||||
.await;
|
.await;
|
||||||
}
|
}
|
||||||
@@ -681,13 +693,8 @@ impl Backend {
|
|||||||
Ok(naddr) => {
|
Ok(naddr) => {
|
||||||
let path = path.clone();
|
let path = path.clone();
|
||||||
cx.background_spawn(async move {
|
cx.background_spawn(async move {
|
||||||
if let Err(error) =
|
if let Err(e) = Repo::open(&path).and_then(|r| r.set_nostr_repo(&naddr)) {
|
||||||
Repo::open(&path).and_then(|repo| repo.set_nostr_repo(&naddr))
|
log::warn!("failed to record the NIP-34 marker: {e}");
|
||||||
{
|
|
||||||
log::warn!(
|
|
||||||
"failed to record the NIP-34 marker for {}: {error}",
|
|
||||||
path.display()
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.await;
|
.await;
|
||||||
@@ -742,6 +749,8 @@ impl Backend {
|
|||||||
let owner = announcement.owner.to_bech32().unwrap();
|
let owner = announcement.owner.to_bech32().unwrap();
|
||||||
let repo_id = announcement.id.clone();
|
let repo_id = announcement.id.clone();
|
||||||
let relays = announcement.relays.clone();
|
let relays = announcement.relays.clone();
|
||||||
|
let client = self.client.clone();
|
||||||
|
let signer = self.signer.clone();
|
||||||
|
|
||||||
cx.spawn(async move |this, cx| {
|
cx.spawn(async move |this, cx| {
|
||||||
let _guard = cx.on_drop(&this, {
|
let _guard = cx.on_drop(&this, {
|
||||||
@@ -779,9 +788,6 @@ impl Backend {
|
|||||||
let refs = state.refs.clone();
|
let refs = state.refs.clone();
|
||||||
let head = state.head.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() {
|
let outcome = if refs.is_empty() {
|
||||||
PushOutcome::default()
|
PushOutcome::default()
|
||||||
} else {
|
} else {
|
||||||
@@ -1145,7 +1151,7 @@ impl Backend {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn repository_announcement(
|
fn build_announcement(
|
||||||
repo_id: &str,
|
repo_id: &str,
|
||||||
name: &str,
|
name: &str,
|
||||||
description: &str,
|
description: &str,
|
||||||
|
|||||||
@@ -2,16 +2,19 @@ use std::path::PathBuf;
|
|||||||
|
|
||||||
use assets::CustomIconName;
|
use assets::CustomIconName;
|
||||||
use gpui::prelude::*;
|
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::h_flex;
|
||||||
use gpui_base::input::TextareaState;
|
use gpui_base::input::TextareaState;
|
||||||
use gpui_component::button::{Button, ButtonVariants};
|
use gpui_component::button::{Button, ButtonVariants};
|
||||||
use gpui_component::dialog::{DialogDescription, DialogFooter, DialogHeader, DialogTitle};
|
use gpui_component::dialog::{DialogDescription, DialogFooter, DialogHeader, DialogTitle};
|
||||||
use gpui_component::form::{field, v_form};
|
use gpui_component::form::{field, v_form};
|
||||||
use gpui_component::input::{Input, InputState, Textarea};
|
use gpui_component::input::{Input, InputState, Textarea};
|
||||||
|
use gpui_component::select::{Select, SelectState};
|
||||||
use gpui_component::{ActiveTheme, Disableable, WindowExt};
|
use gpui_component::{ActiveTheme, Disableable, WindowExt};
|
||||||
use settings::SettingsStore;
|
use settings::SettingsStore;
|
||||||
|
use signed_git::Repo;
|
||||||
use signed_state::Backend;
|
use signed_state::Backend;
|
||||||
|
use signed_ui::SelectOption;
|
||||||
|
|
||||||
use super::RepoDetailView;
|
use super::RepoDetailView;
|
||||||
use crate::views::dialog_state::{DialogProgress, error_row};
|
use crate::views::dialog_state::{DialogProgress, error_row};
|
||||||
@@ -48,8 +51,11 @@ pub fn open(
|
|||||||
.auto_grow(3, 5)
|
.auto_grow(3, 5)
|
||||||
.placeholder("Short description")
|
.placeholder("Short description")
|
||||||
});
|
});
|
||||||
|
let branch_select: Entity<SelectState<Vec<SelectOption>>> =
|
||||||
|
cx.new(|cx| SelectState::new(Vec::new(), None, window, cx));
|
||||||
|
|
||||||
load_user_grasp_servers(grasp_state.clone(), 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| {
|
window.open_dialog(cx, move |dialog, _window, _cx| {
|
||||||
const DESC: &str = "Publish this local repository to Nostr.";
|
const DESC: &str = "Publish this local repository to Nostr.";
|
||||||
@@ -57,6 +63,7 @@ pub fn open(
|
|||||||
let name_input = name_input.clone();
|
let name_input = name_input.clone();
|
||||||
let desc_input = desc_input.clone();
|
let desc_input = desc_input.clone();
|
||||||
let relay_input = relay_input.clone();
|
let relay_input = relay_input.clone();
|
||||||
|
let branch_select = branch_select.clone();
|
||||||
let state = state.clone();
|
let state = state.clone();
|
||||||
let grasp_state = grasp_state.clone();
|
let grasp_state = grasp_state.clone();
|
||||||
let local_path = local_path.clone();
|
let local_path = local_path.clone();
|
||||||
@@ -105,6 +112,12 @@ pub fn open(
|
|||||||
.child(local_path.display().to_string()),
|
.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)),
|
.child(grasp_servers_field(&grasp_state, &relay_input, cx)),
|
||||||
)
|
)
|
||||||
.children(error_row(&error, cx))
|
.children(error_row(&error, cx))
|
||||||
@@ -120,6 +133,7 @@ pub fn open(
|
|||||||
.on_click({
|
.on_click({
|
||||||
let name_input = name_input.clone();
|
let name_input = name_input.clone();
|
||||||
let desc_input = desc_input.clone();
|
let desc_input = desc_input.clone();
|
||||||
|
let branch_select = branch_select.clone();
|
||||||
let state = state.clone();
|
let state = state.clone();
|
||||||
let grasp_state = grasp_state.clone();
|
let grasp_state = grasp_state.clone();
|
||||||
let local_path = local_path.clone();
|
let local_path = local_path.clone();
|
||||||
@@ -129,6 +143,7 @@ pub fn open(
|
|||||||
init_repository(
|
init_repository(
|
||||||
local_path.clone(),
|
local_path.clone(),
|
||||||
(name_input.clone(), desc_input.clone()),
|
(name_input.clone(), desc_input.clone()),
|
||||||
|
branch_select.clone(),
|
||||||
state.clone(),
|
state.clone(),
|
||||||
grasp_state.clone(),
|
grasp_state.clone(),
|
||||||
view.clone(),
|
view.clone(),
|
||||||
@@ -143,9 +158,11 @@ pub fn open(
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[allow(clippy::too_many_arguments)]
|
||||||
fn init_repository(
|
fn init_repository(
|
||||||
local_path: PathBuf,
|
local_path: PathBuf,
|
||||||
inputs: (Entity<InputState>, Entity<TextareaState>),
|
inputs: (Entity<InputState>, Entity<TextareaState>),
|
||||||
|
branch_select: Entity<SelectState<Vec<SelectOption>>>,
|
||||||
state: Entity<InitRepoState>,
|
state: Entity<InitRepoState>,
|
||||||
grasp_state: Entity<GraspServersState>,
|
grasp_state: Entity<GraspServersState>,
|
||||||
view: WeakEntity<RepoDetailView>,
|
view: WeakEntity<RepoDetailView>,
|
||||||
@@ -156,6 +173,11 @@ fn init_repository(
|
|||||||
let name = name_input.read(cx).value().trim().to_owned();
|
let name = name_input.read(cx).value().trim().to_owned();
|
||||||
let description = desc_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 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() {
|
if name.is_empty() {
|
||||||
state.update(cx, |state, _| state.fail("Repository name is required"));
|
state.update(cx, |state, _| state.fail("Repository name is required"));
|
||||||
@@ -171,7 +193,14 @@ fn init_repository(
|
|||||||
|
|
||||||
let backend = Backend::global(cx);
|
let backend = Backend::global(cx);
|
||||||
let task = backend.update(cx, |backend, 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();
|
let handle = window.window_handle();
|
||||||
@@ -199,3 +228,45 @@ fn init_repository(
|
|||||||
})
|
})
|
||||||
.detach();
|
.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<SelectState<Vec<SelectOption>>>,
|
||||||
|
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<SharedString> = 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<SelectOption> = 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();
|
||||||
|
}
|
||||||
|
|||||||
@@ -828,10 +828,6 @@ impl RepoDetailView {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Open the upstream repository, the `u` tag of this fork's announcement.
|
/// 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<Self>) {
|
pub(super) fn open_upstream(&mut self, window: &mut Window, cx: &mut Context<Self>) {
|
||||||
let Some(addr) = self
|
let Some(addr) = self
|
||||||
.announcement(cx)
|
.announcement(cx)
|
||||||
@@ -1788,6 +1784,12 @@ impl RepoDetailView {
|
|||||||
/// The Files tab body, or the clone/initial-load spinner.
|
/// The Files tab body, or the clone/initial-load spinner.
|
||||||
fn render_files_tab(&self, cx: &mut Context<Self>) -> AnyElement {
|
fn render_files_tab(&self, cx: &mut Context<Self>) -> AnyElement {
|
||||||
if self.loading {
|
if self.loading {
|
||||||
|
let label = if self.store.read(cx).path.is_some() {
|
||||||
|
"Loading repository..."
|
||||||
|
} else {
|
||||||
|
"Cloning repository..."
|
||||||
|
};
|
||||||
|
|
||||||
return v_flex()
|
return v_flex()
|
||||||
.flex_1()
|
.flex_1()
|
||||||
.size_full()
|
.size_full()
|
||||||
@@ -1799,7 +1801,7 @@ impl RepoDetailView {
|
|||||||
div()
|
div()
|
||||||
.text_sm()
|
.text_sm()
|
||||||
.text_color(cx.theme().muted_foreground)
|
.text_color(cx.theme().muted_foreground)
|
||||||
.child("Cloning repository..."),
|
.child(label),
|
||||||
)
|
)
|
||||||
.into_any_element();
|
.into_any_element();
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user