This commit is contained in:
2026-08-22 16:57:24 +07:00
parent 9bdc3daa7e
commit 385718d74d
8 changed files with 938 additions and 128 deletions
+64
View File
@@ -6,6 +6,7 @@ use nostr_sdk::prelude::*;
use signed_core::{Announcement, Deletions, RepoAddr, RepoStatus, filters, parse_state};
use crate::backend::{Backend, BackendEvent};
use crate::git_store::GitStore;
/// Delay between a refresh request and the actual re-query, so bursts of
/// events (e.g. per-event `NostrUpdate`s) collapse into one query.
@@ -293,6 +294,13 @@ impl RepoStore {
.count()
}
/// Whether `user` is the author (owner) of this repository: the public
/// key of the repository address. Only the author may manage the
/// repository's pull requests (close / reopen / merge).
pub fn is_author(&self, user: &PublicKey) -> bool {
&self.addr.public_key == user
}
/// Open an issue on this repository.
pub fn open_issue(&mut self, subject: Option<String>, content: String, cx: &mut Context<Self>) {
let builder = GitIssue {
@@ -392,6 +400,62 @@ impl RepoStore {
self.send(builder, cx);
}
/// Merge a pull request: apply its patch (`git format-patch` output) to
/// the local clone of this repository, then publish the merged status.
///
/// Only the repository author may merge. The clone is created on demand
/// from the announcement's clone URLs when the repository hasn't been
/// mirrored locally yet. Patch application runs on a background thread
/// (`git am`); failures (e.g. a patch that no longer applies) surface in
/// [`Self::last_error`] and no status is sent.
pub fn merge_pull_request(&mut self, root: &Event, cx: &mut Context<Self>) {
self.last_error = None;
let is_author = Backend::global(cx)
.read(cx)
.current_user()
.is_some_and(|user| self.is_author(&user));
if !is_author {
self.last_error = Some("Only the repository author can merge pull requests".into());
return;
}
let cache = GitStore::global(cx).cache().clone();
let addr = self.addr.clone();
let clone_urls: Vec<String> = self
.announcement
.as_ref()
.map(|a| a.clone.iter().map(ToString::to_string).collect())
.unwrap_or_default();
let patch = root.content.clone();
let root = root.clone();
let apply = cx.background_spawn(async move {
let repo = cache.ensure_clone(&addr, &clone_urls)?;
let workdir = repo
.workdir()
.ok_or_else(|| anyhow::anyhow!("repository has no worktree"))?;
signed_git::apply_patch(workdir, &patch)
});
self.tasks.push(cx.spawn(async move |this, cx| {
match apply.await {
Ok(()) => {
this.update(cx, |this, cx| {
this.set_status(&root, RepoStatus::Applied, cx);
})?;
}
Err(e) => {
this.update(cx, |this, cx| {
this.last_error = Some(e.to_string());
cx.notify();
})?;
}
}
Ok(())
}));
}
fn send(&mut self, builder: EventBuilder, cx: &mut Context<Self>) {
self.last_error = None;