wip
This commit is contained in:
@@ -163,7 +163,8 @@ impl ProfileStore {
|
||||
|
||||
/// Load recently seen profiles from the local database.
|
||||
fn load(&mut self, cx: &mut Context<Self>) {
|
||||
let client = Backend::global(cx).read(cx).client();
|
||||
let backend = Backend::global(cx);
|
||||
let client = backend.read(cx).client();
|
||||
|
||||
let work = cx.background_spawn(async move {
|
||||
let filter = Filter::new().kind(Kind::Metadata).limit(200);
|
||||
@@ -197,7 +198,8 @@ impl ProfileStore {
|
||||
|
||||
/// Re-read the latest metadata of an author from the local database.
|
||||
fn apply_author(&mut self, public_key: PublicKey, cx: &mut Context<Self>) {
|
||||
let client = Backend::global(cx).read(cx).client();
|
||||
let backend = Backend::global(cx);
|
||||
let client = backend.read(cx).client();
|
||||
|
||||
let work = cx.background_spawn(async move {
|
||||
let filter = Filter::new().kind(Kind::Metadata).author(public_key);
|
||||
@@ -238,7 +240,8 @@ impl ProfileStore {
|
||||
return;
|
||||
}
|
||||
|
||||
let client = Backend::global(cx).read(cx).client();
|
||||
let backend = Backend::global(cx);
|
||||
let client = backend.read(cx).client();
|
||||
|
||||
let work = cx.background_spawn(async move {
|
||||
let filter = Filter::new().kind(Kind::Metadata).authors(authors);
|
||||
|
||||
+157
-11
@@ -3,12 +3,14 @@ use std::collections::{HashMap, HashSet};
|
||||
use std::time::Duration;
|
||||
|
||||
use anyhow::Error;
|
||||
use bitcoin_hashes::sha1::Hash as Sha1Hash;
|
||||
use gpui::{AppContext, Context, Subscription, Task};
|
||||
use nostr::event::IntoEventBuilder;
|
||||
use nostr_sdk::prelude::*;
|
||||
use signed_core::{
|
||||
Announcement, COVER_NOTE_KIND, Deletions, RepoAddr, RepoStatus, build_state, cover_note,
|
||||
filters, labels_and_subject, parse_state, pull_request_patch, subject_override,
|
||||
filters, labels_and_subject, parse_state, pull_request_patch, pull_request_patches,
|
||||
subject_override,
|
||||
};
|
||||
|
||||
use crate::backend::{Backend, BackendEvent};
|
||||
@@ -235,7 +237,8 @@ impl RepoStore {
|
||||
fn run_refresh(&mut self, cx: &mut Context<Self>) {
|
||||
self.refreshing = true;
|
||||
|
||||
let client = Backend::global(cx).read(cx).client();
|
||||
let backend = Backend::global(cx);
|
||||
let client = backend.read(cx).client();
|
||||
let addr = self.addr.clone();
|
||||
|
||||
let work = cx.background_spawn(async move {
|
||||
@@ -623,17 +626,21 @@ impl RepoStore {
|
||||
/// carry a real commit id for other NIP-34 clients to verify and apply
|
||||
/// the proposal. The `clone` tag carries the announced mirror URLs; the
|
||||
/// linked patch is the source of truth until the commit is pushed there.
|
||||
/// `branch_name` lands in the PR's `branch-name` tag (NIP-34); `draft`
|
||||
/// publishes a kind-1633 status right after the PR event.
|
||||
pub fn open_pull_request(
|
||||
&mut self,
|
||||
subject: Option<String>,
|
||||
description: String,
|
||||
branch_name: Option<String>,
|
||||
patch: String,
|
||||
draft: bool,
|
||||
cx: &mut Context<Self>,
|
||||
) {
|
||||
self.last_error = None;
|
||||
|
||||
let Some(current_commit) =
|
||||
patch_current_commit(&patch).and_then(|hex| hex.parse::<bitcoin_hashes::Sha1>().ok())
|
||||
patch_current_commit(&patch).and_then(|hex| hex.parse::<Sha1Hash>().ok())
|
||||
else {
|
||||
self.last_error = Some(
|
||||
"Patch must be `git format-patch` output with a `From <commit-id>` header".into(),
|
||||
@@ -666,8 +673,8 @@ impl RepoStore {
|
||||
}
|
||||
let patch_builder = EventBuilder::new(Kind::GitPatch, patch).tags(patch_tags);
|
||||
|
||||
let patch_task =
|
||||
Backend::global(cx).update(cx, |backend, cx| backend.send(patch_builder, cx));
|
||||
let backend = Backend::global(cx);
|
||||
let patch_task = backend.update(cx, |backend, cx| backend.send(patch_builder, cx));
|
||||
|
||||
self.tasks.push(cx.spawn(async move |this, cx| {
|
||||
let patch_event = match patch_task.await {
|
||||
@@ -688,7 +695,7 @@ impl RepoStore {
|
||||
content: description,
|
||||
subject,
|
||||
labels: Vec::new(),
|
||||
branch_name: None,
|
||||
branch_name,
|
||||
// NIP-34: PRs carry at least one clone URL where the
|
||||
// tip commit can be downloaded; use the repository's
|
||||
// announced mirrors until a push backend exists.
|
||||
@@ -703,10 +710,146 @@ impl RepoStore {
|
||||
}
|
||||
.into_event_builder();
|
||||
|
||||
Backend::global(cx).update(cx, |backend, cx| backend.send(builder, cx))
|
||||
// NIP-34: the `r` EUC tag lets clients subscribe to all PRs of this repository.
|
||||
let builder = match this.announcement.as_ref().and_then(|a| a.euc.clone()) {
|
||||
Some(euc) => builder.tag(Tag::parse(["r", &euc]).expect("valid r tag")),
|
||||
None => builder,
|
||||
};
|
||||
|
||||
let backend = Backend::global(cx);
|
||||
backend.update(cx, |backend, cx| backend.send(builder, cx))
|
||||
})?;
|
||||
|
||||
if let Err(e) = pr_task.await {
|
||||
let pr_event = match pr_task.await {
|
||||
Ok(event) => event,
|
||||
Err(e) => {
|
||||
return this.update(cx, |this, cx| {
|
||||
this.last_error = Some(e.to_string());
|
||||
cx.notify();
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
// NIP-34: a draft PR carries a kind-1633 status event; publish
|
||||
// it right after the PR event so viewers never show it open.
|
||||
if draft {
|
||||
this.update(cx, |this, cx| {
|
||||
this.set_status(&pr_event, RepoStatus::Draft, cx);
|
||||
})?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}));
|
||||
}
|
||||
|
||||
/// Update a pull request: publish a revision patch event chained to the
|
||||
/// original root patch (`t root-revision` and a NIP-10 `e` reply, per
|
||||
/// NIP-34), then a kind-1619 PR update event carrying the new tip.
|
||||
///
|
||||
/// Only the PR author may update it; other authors must open a new PR.
|
||||
pub fn update_pull_request(&mut self, root: &Event, patch: String, cx: &mut Context<Self>) {
|
||||
self.last_error = None;
|
||||
|
||||
let backend = Backend::global(cx);
|
||||
|
||||
let Some(user) = backend.read(cx).current_user() else {
|
||||
self.last_error = Some("Sign in to update the pull request".into());
|
||||
cx.notify();
|
||||
return;
|
||||
};
|
||||
|
||||
if user != root.pubkey {
|
||||
self.last_error = Some("Only the pull request author can update it".into());
|
||||
cx.notify();
|
||||
return;
|
||||
}
|
||||
|
||||
let Some(current_commit) =
|
||||
patch_current_commit(&patch).and_then(|hex| hex.parse::<Sha1Hash>().ok())
|
||||
else {
|
||||
self.last_error = Some(
|
||||
"Patch must be `git format-patch` output with a `From <commit-id>` header".into(),
|
||||
);
|
||||
cx.notify();
|
||||
return;
|
||||
};
|
||||
|
||||
// NIP-34: the first patch of a revision replies to the original
|
||||
// root patch (the PR's `e` tag; fall back to the oldest patch of
|
||||
// the linked set for PRs without one).
|
||||
let root_patch_id = root.tags.event_ids().next().or_else(|| {
|
||||
pull_request_patches(root, self.patches.iter())
|
||||
.first()
|
||||
.map(|p| p.id)
|
||||
});
|
||||
|
||||
let commit_hex = current_commit.to_string();
|
||||
let mut patch_tags = vec![
|
||||
Tag::coordinate(self.addr.clone(), None),
|
||||
Tag::public_key(self.addr.public_key),
|
||||
Tag::parse(["t", "root-revision"]).expect("valid root-revision tag"),
|
||||
];
|
||||
if let Some(root_patch_id) = root_patch_id
|
||||
&& let Ok(tag) = Tag::parse(["e", &root_patch_id.to_hex(), "", "reply"])
|
||||
{
|
||||
patch_tags.push(tag);
|
||||
}
|
||||
// NIP-34: the `r` EUC tag lets clients subscribe to all patches of
|
||||
// this repository; `commit`/`r` tags reference the new tip.
|
||||
if let Some(euc) = self.announcement.as_ref().and_then(|a| a.euc.clone())
|
||||
&& let Ok(tag) = Tag::parse(["r", &euc])
|
||||
{
|
||||
patch_tags.push(tag);
|
||||
}
|
||||
if let Ok(tag) = Tag::parse(["commit", &commit_hex]) {
|
||||
patch_tags.push(tag);
|
||||
}
|
||||
if let Ok(tag) = Tag::parse(["r", &commit_hex]) {
|
||||
patch_tags.push(tag);
|
||||
}
|
||||
let patch_builder = EventBuilder::new(Kind::GitPatch, patch).tags(patch_tags);
|
||||
|
||||
let backend = Backend::global(cx);
|
||||
let patch_task = backend.update(cx, |backend, cx| backend.send(patch_builder, cx));
|
||||
|
||||
let root = root.clone();
|
||||
let clone: Vec<Url> = self
|
||||
.announcement
|
||||
.as_ref()
|
||||
.map(|a| a.clone.clone())
|
||||
.unwrap_or_default();
|
||||
let euc = self.announcement.as_ref().and_then(|a| a.euc.clone());
|
||||
|
||||
self.tasks.push(cx.spawn(async move |this, cx| {
|
||||
if let Err(e) = patch_task.await {
|
||||
return this.update(cx, |this, cx| {
|
||||
this.last_error = Some(e.to_string());
|
||||
cx.notify();
|
||||
});
|
||||
}
|
||||
|
||||
let update_task = this.update(cx, |this, cx| {
|
||||
let builder = GitPullRequestUpdate {
|
||||
repository: this.addr.clone(),
|
||||
pull_request_event: root.id,
|
||||
pull_request_author: root.pubkey,
|
||||
current_commit,
|
||||
clone: clone.clone(),
|
||||
merge_base: None,
|
||||
}
|
||||
.into_event_builder();
|
||||
|
||||
// NIP-34: the `r` EUC tag lets clients subscribe to all PR
|
||||
// updates of this repository; the SDK builder omits it.
|
||||
let builder = match euc.as_deref() {
|
||||
Some(euc) => builder.tag(Tag::parse(["r", euc]).expect("valid r tag")),
|
||||
None => builder,
|
||||
};
|
||||
|
||||
backend.update(cx, |backend, cx| backend.send(builder, cx))
|
||||
})?;
|
||||
|
||||
if let Err(e) = update_task.await {
|
||||
return this.update(cx, |this, cx| {
|
||||
this.last_error = Some(e.to_string());
|
||||
cx.notify();
|
||||
@@ -729,7 +872,8 @@ impl RepoStore {
|
||||
.map(Announcement::effective_maintainers)
|
||||
.unwrap_or_default();
|
||||
|
||||
let Some(user) = Backend::global(cx).read(cx).current_user() else {
|
||||
let backend = Backend::global(cx);
|
||||
let Some(user) = backend.read(cx).current_user() else {
|
||||
self.last_error = Some("Sign in to change the status".into());
|
||||
cx.notify();
|
||||
return;
|
||||
@@ -761,7 +905,8 @@ impl RepoStore {
|
||||
pub fn publish_state(&mut self, cx: &mut Context<Self>) {
|
||||
self.last_error = None;
|
||||
|
||||
let Some(user) = Backend::global(cx).read(cx).current_user() else {
|
||||
let backend = Backend::global(cx);
|
||||
let Some(user) = backend.read(cx).current_user() else {
|
||||
self.last_error = Some("Sign in to publish repository state".into());
|
||||
cx.notify();
|
||||
return;
|
||||
@@ -924,7 +1069,8 @@ fn resolve_statuses(
|
||||
.chain(pull_requests)
|
||||
.map(|root| {
|
||||
let events = by_root.get(&root.id).map(Vec::as_slice).unwrap_or(&[]);
|
||||
let status = signed_core::resolve_status(events.iter().copied(), &root.pubkey, maintainers);
|
||||
let status =
|
||||
signed_core::resolve_status(events.iter().copied(), &root.pubkey, maintainers);
|
||||
(root.id, status)
|
||||
})
|
||||
.collect()
|
||||
|
||||
@@ -202,7 +202,8 @@ impl RepoListStore {
|
||||
fn run_refresh(&mut self, cx: &mut Context<Self>) {
|
||||
self.refreshing = true;
|
||||
|
||||
let client = Backend::global(cx).read(cx).client();
|
||||
let backend = Backend::global(cx);
|
||||
let client = backend.read(cx).client();
|
||||
let author = self.author;
|
||||
|
||||
let work = cx.background_spawn(async move {
|
||||
|
||||
Reference in New Issue
Block a user