refactor module functions into methods on store types

This commit is contained in:
2026-10-03 16:05:47 +07:00
parent 49d351fe06
commit 6706e328ec
15 changed files with 1397 additions and 1348 deletions
+92 -96
View File
@@ -15,11 +15,10 @@ use signed_core::{
use signed_git::{Nip34Binding, PatchParser, Repo};
use signed_nostr::UniversalSigner;
use crate::backend::{
Backend, BackendEvent, grasp_base_url, grasp06_prs_url, pr_clone_urls, require_relay_accepted,
user_grasp_list_servers,
};
use crate::backend::{Backend, BackendEvent};
use crate::bootstrap::user_grasp_list_servers;
use crate::checkouts::CheckoutsStore;
use crate::push::{GraspPush, PushOutcome, grasp_base_url, grasp06_prs_url, pr_clone_urls};
use crate::repos::RepoListStore;
/// Maximum size of one patch event.
@@ -700,19 +699,12 @@ impl RepoStore {
return;
};
let series: Vec<String> = PatchParser::split_patch_series(&patch)
.into_iter()
.map(str::to_owned)
.collect();
let series = PatchSeries::parse(&patch);
if let Some(oversized) = series
.iter()
.find(|part| part.len() > MAX_PATCH_EVENT_BYTES)
{
if let Some(oversized) = series.oversized_length() {
self.last_error = Some(format!(
"patch too large ({} bytes; NIP-34 suggests keeping each patch under {} bytes)",
oversized.len(),
MAX_PATCH_EVENT_BYTES
oversized, MAX_PATCH_EVENT_BYTES
));
cx.notify();
return;
@@ -720,11 +712,7 @@ impl RepoStore {
// The tip of the series is its last commit.
// `git format-patch` orders patches oldest first.
let Some(current_commit) = series
.last()
.and_then(|part| patch_current_commit(part))
.and_then(|hex| hex.parse::<Sha1Hash>().ok())
else {
let Some(current_commit) = series.tip_commit() else {
self.last_error = Some(
"Patch must be `git format-patch` output with a `From <commit-id>` header".into(),
);
@@ -930,11 +918,10 @@ impl RepoStore {
}
}
let publish_result: Result<Event, Error> = async {
let output = client.send_event(&event).broadcast().await?;
require_relay_accepted(output, event)
}
.await;
let publish_result = {
let pusher = GraspPush::new(client.clone(), signer.clone());
pusher.send_accepted(event).await
};
let pr_event = match publish_result {
Ok(event) => event,
@@ -1037,29 +1024,18 @@ impl RepoStore {
return;
}
let series: Vec<String> = PatchParser::split_patch_series(&patch)
.into_iter()
.map(str::to_owned)
.collect();
if let Some(oversized) = series
.iter()
.find(|part| part.len() > MAX_PATCH_EVENT_BYTES)
{
let series = PatchSeries::parse(&patch);
if let Some(oversized) = series.oversized_length() {
self.last_error = Some(format!(
"patch too large ({} bytes; NIP-34 suggests keeping each patch under {} bytes)",
oversized.len(),
MAX_PATCH_EVENT_BYTES
oversized, MAX_PATCH_EVENT_BYTES
));
cx.notify();
return;
}
// The new tip of the PR is the last commit of the series.
let Some(current_commit) = series
.last()
.and_then(|part| patch_current_commit(part))
.and_then(|hex| hex.parse::<Sha1Hash>().ok())
else {
let Some(current_commit) = series.tip_commit() else {
self.last_error = Some(
"Patch must be `git format-patch` output with a `From <commit-id>` header".into(),
);
@@ -1129,12 +1105,10 @@ impl RepoStore {
}
};
let publish_result: Result<Event, Error> = async {
let event = builder.finalize_async(&signer).await?;
let output = client.send_event(&event).broadcast().await?;
require_relay_accepted(output, event)
}
.await;
let publish_result = {
let pusher = GraspPush::new(client.clone(), signer.clone());
pusher.publish_one(builder).await
};
if let Err(e) = publish_result {
return this.update(cx, |this, cx| {
@@ -1216,38 +1190,10 @@ impl RepoStore {
return self.action_error("Repository announcement is not loaded yet", cx);
};
self.pushing = true;
self.last_error = None;
self.last_push_warning = None;
cx.notify();
let backend = Backend::global(cx);
let push = backend.update(cx, |backend, cx| backend.push_repository(announcement, cx));
cx.spawn(async move |this, cx| {
let result = push.await;
this.update(cx, |this, cx| {
this.pushing = false;
match &result {
Ok(outcome) => {
this.last_error = None;
// A push only some grasp servers accepted is a warning:
// the repo is out of sync on the rest until it is republished.
this.last_push_warning = outcome.partial_warning();
}
Err(e) => {
this.last_error = Some(format!("Push failed: {e}"));
this.last_push_warning = None;
}
}
cx.notify();
})?;
result.map(|_| ())
})
self.run_push(push, None, cx)
}
pub fn push_checkout(
@@ -1273,17 +1219,30 @@ impl RepoStore {
// The checkout may be on a side branch.
let head = self.head.clone();
self.pushing = true;
self.last_error = None;
self.last_push_warning = None;
cx.notify();
let checkouts = CheckoutsStore::global(cx);
let backend = Backend::global(cx);
let push = backend.update(cx, |backend, cx| {
backend.push_checkout(announcement, path.clone(), head, cx)
});
self.run_push(push, Some((addr, path)), cx)
}
/// Run a backend push task, tracking progress in [`Self::pushing`] and
/// the outcome in [`Self::last_error`] and [`Self::last_push_warning`].
///
/// `pushed_checkout` names the checkout whose ready-to-push statuses
/// should be recomputed after the remote moved.
fn run_push(
&mut self,
push: Task<Result<PushOutcome, Error>>,
pushed_checkout: Option<(RepoAddr, PathBuf)>,
cx: &mut Context<Self>,
) -> Task<Result<(), Error>> {
self.pushing = true;
self.last_error = None;
self.last_push_warning = None;
cx.notify();
cx.spawn(async move |this, cx| {
let result = push.await;
@@ -1296,10 +1255,12 @@ impl RepoStore {
// A push only some grasp servers accepted is a warning:
// the repo is out of sync on the rest until it is republished.
this.last_push_warning = outcome.partial_warning();
// The remote moved, so recompute the ready-to-push statuses.
checkouts.update(cx, |store, cx| {
store.checkout_pushed(&addr, &path, cx);
});
if let Some((addr, path)) = &pushed_checkout {
// The remote moved, so recompute the ready-to-push statuses.
CheckoutsStore::global(cx).update(cx, |store, cx| {
store.checkout_pushed(addr, path, cx);
});
}
}
Err(e) => {
this.last_error = Some(format!("Push failed: {e}"));
@@ -1431,12 +1392,8 @@ impl RepoStore {
};
let task: Task<Result<(), Error>> = cx.spawn(async move |this, cx| {
let publish_result: Result<Event, Error> = async {
let event = builder.finalize_async(&signer).await?;
let output = client.send_event(&event).broadcast().await?;
require_relay_accepted(output, event)
}
.await;
let pusher = GraspPush::new(client, signer);
let publish_result = pusher.publish_one(builder).await;
if let Err(e) = publish_result {
this.update(cx, |this, cx| {
@@ -1497,6 +1454,46 @@ fn patch_current_commit(patch: &str) -> Option<&str> {
hex.split_whitespace().next().filter(|hex| hex.len() == 40)
}
/// A `git format-patch` series with the facts derived from its parts.
struct PatchSeries {
parts: Vec<String>,
}
impl PatchSeries {
fn parse(patch: &str) -> Self {
Self {
parts: PatchParser::split_patch_series(patch)
.into_iter()
.map(str::to_owned)
.collect(),
}
}
/// The byte length of the first part over the NIP-34 size suggestion.
fn oversized_length(&self) -> Option<usize> {
self.parts
.iter()
.map(String::len)
.find(|length| *length > MAX_PATCH_EVENT_BYTES)
}
/// The tip of the series is its last commit;
/// `git format-patch` orders patches oldest first.
fn tip_commit(&self) -> Option<Sha1Hash> {
self.parts
.last()
.and_then(|part| patch_current_commit(part))
.and_then(|hex| hex.parse::<Sha1Hash>().ok())
}
fn commit_of(&self, index: usize) -> Option<&str> {
self.parts
.get(index)
.and_then(|part| patch_current_commit(part))
.filter(|hex| hex.len() == 40)
}
}
/// Publish a `git format-patch` series as chained kind-1617 events.
///
/// Returns the root event, the one a PR references.
@@ -1507,15 +1504,16 @@ async fn publish_patch_series(
addr: &RepoAddr,
owner: PublicKey,
euc: Option<&str>,
series: &[String],
series: &PatchSeries,
first_marker: &str,
reply_to: Option<EventId>,
) -> Result<Event, Error> {
let pusher = GraspPush::new(client.clone(), signer.clone());
let mut root: Option<Event> = None;
let mut previous = reply_to;
for (ix, part) in series.iter().enumerate() {
let Some(commit) = patch_current_commit(part).filter(|hex| hex.len() == 40) else {
for (ix, part) in series.parts.iter().enumerate() {
let Some(commit) = series.commit_of(ix) else {
return Err(anyhow::anyhow!(
"patch {} of the series has no `From <commit-id>` header",
ix + 1
@@ -1557,9 +1555,7 @@ async fn publish_patch_series(
}
let builder = EventBuilder::new(Kind::GitPatch, part.clone()).tags(tags);
let event = builder.finalize_async(signer).await?;
let output = client.send_event(&event).broadcast().await?;
let event = require_relay_accepted(output, event)?;
let event = pusher.publish_one(builder).await?;
if root.is_none() {
root = Some(event.clone());