add pull request viewer
This commit is contained in:
@@ -3,7 +3,9 @@ use std::time::Duration;
|
||||
use anyhow::Error;
|
||||
use gpui::{AppContext, Context, Subscription, Task};
|
||||
use nostr_sdk::prelude::*;
|
||||
use signed_core::{Announcement, Deletions, RepoAddr, RepoStatus, filters, parse_state};
|
||||
use signed_core::{
|
||||
Announcement, Deletions, RepoAddr, RepoStatus, filters, parse_state, pull_request_patch,
|
||||
};
|
||||
|
||||
use crate::backend::{Backend, BackendEvent};
|
||||
use crate::git_store::GitStore;
|
||||
@@ -211,6 +213,7 @@ impl RepoStore {
|
||||
});
|
||||
|
||||
self.tasks.retain(|task| !task.is_ready());
|
||||
|
||||
self.tasks.push(cx.spawn(async move |this, cx| {
|
||||
let (announcement, state, issues, patches, pull_requests, statuses, comments) =
|
||||
match work.await {
|
||||
@@ -277,7 +280,7 @@ impl RepoStore {
|
||||
|
||||
/// Number of open issues: issues whose resolved status is
|
||||
/// [`RepoStatus::Open`] (issues without status events default to open).
|
||||
pub fn open_issue_count(&self) -> usize {
|
||||
pub fn issue_count(&self) -> usize {
|
||||
self.issues
|
||||
.iter()
|
||||
.filter(|issue| self.status_of(issue) == RepoStatus::Open)
|
||||
@@ -287,7 +290,7 @@ impl RepoStore {
|
||||
/// Number of open pull requests: root PR events (not PR updates, whose
|
||||
/// status is carried by the root) with a resolved status of
|
||||
/// [`RepoStatus::Open`].
|
||||
pub fn open_pull_request_count(&self) -> usize {
|
||||
pub fn pull_request_count(&self) -> usize {
|
||||
self.pull_requests
|
||||
.iter()
|
||||
.filter(|pr| pr.kind == Kind::GitPullRequest && self.status_of(pr) == RepoStatus::Open)
|
||||
@@ -336,52 +339,81 @@ impl RepoStore {
|
||||
self.send(builder, cx);
|
||||
}
|
||||
|
||||
/// Open a pull request on this repository: a root PR event whose content
|
||||
/// is the `git format-patch` output of the proposed changes.
|
||||
/// Open a pull request on this repository: a root PR event (kind 1618)
|
||||
/// whose content is the markdown description, plus a root patch event
|
||||
/// (kind 1617) carrying the `git format-patch` output, which the PR
|
||||
/// references via an `e` tag (NIP-34).
|
||||
///
|
||||
/// The branch metadata (branch name, clone URL, merge base, root patch)
|
||||
/// isn't known to the UI yet and is left empty; the proposed commit is
|
||||
/// parsed from the patch's `From <commit>` header, falling back to an
|
||||
/// empty hash for hand-written content.
|
||||
/// The patch is published first and the PR is sent once the patch
|
||||
/// event's id is known, so the two always arrive together. The branch
|
||||
/// metadata (branch name, clone URL, merge base) isn't known to the UI
|
||||
/// yet and is left empty; the proposed commit is parsed from the patch's
|
||||
/// `From <commit>` header, falling back to an empty hash for hand-written
|
||||
/// content.
|
||||
pub fn open_pull_request(
|
||||
&mut self,
|
||||
subject: Option<String>,
|
||||
content: String,
|
||||
description: String,
|
||||
patch: String,
|
||||
cx: &mut Context<Self>,
|
||||
) {
|
||||
let current_commit = patch_current_commit(&content)
|
||||
self.last_error = None;
|
||||
|
||||
let current_commit = patch_current_commit(&patch)
|
||||
.and_then(|hex| hex.parse().ok())
|
||||
.unwrap_or_else(|| bitcoin_hashes::Sha1::from_byte_array([0u8; 20]));
|
||||
|
||||
let builder = GitPullRequest {
|
||||
repository: self.addr.clone(),
|
||||
content,
|
||||
subject,
|
||||
labels: Vec::new(),
|
||||
branch_name: None,
|
||||
clone: Vec::new(),
|
||||
current_commit,
|
||||
root_patch_event: None,
|
||||
merge_base: None,
|
||||
}
|
||||
.into_event_builder();
|
||||
|
||||
self.send(builder, cx);
|
||||
}
|
||||
|
||||
/// Send a root patch (`git format-patch` output) to this repository.
|
||||
pub fn send_root_patch(&mut self, patch: String, cx: &mut Context<Self>) {
|
||||
let Ok(root_marker) = Tag::parse(["t", "root"]) else {
|
||||
return;
|
||||
};
|
||||
|
||||
let builder = EventBuilder::new(Kind::GitPatch, patch).tags([
|
||||
let patch_builder = EventBuilder::new(Kind::GitPatch, patch).tags([
|
||||
Tag::coordinate(self.addr.clone(), None),
|
||||
Tag::public_key(self.addr.public_key),
|
||||
root_marker,
|
||||
]);
|
||||
|
||||
self.send(builder, cx);
|
||||
let patch_task =
|
||||
Backend::global(cx).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 {
|
||||
Ok(event) => event,
|
||||
Err(e) => {
|
||||
return this.update(cx, |this, cx| {
|
||||
this.last_error = Some(e.to_string());
|
||||
cx.notify();
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
// The PR references the patch event so viewers can find the
|
||||
// patch without carrying it inline.
|
||||
let pr_task = this.update(cx, |this, cx| {
|
||||
let builder = GitPullRequest {
|
||||
repository: this.addr.clone(),
|
||||
content: description,
|
||||
subject,
|
||||
labels: Vec::new(),
|
||||
branch_name: None,
|
||||
clone: Vec::new(),
|
||||
current_commit,
|
||||
root_patch_event: Some(patch_event.id),
|
||||
merge_base: None,
|
||||
}
|
||||
.into_event_builder();
|
||||
|
||||
Backend::global(cx).update(cx, |backend, cx| backend.send(builder, cx))
|
||||
})?;
|
||||
|
||||
if let Err(e) = pr_task.await {
|
||||
return this.update(cx, |this, cx| {
|
||||
this.last_error = Some(e.to_string());
|
||||
cx.notify();
|
||||
});
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}));
|
||||
}
|
||||
|
||||
/// Set the status of a root event (requires being the root author or a maintainer).
|
||||
@@ -400,8 +432,9 @@ 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.
|
||||
/// Merge a pull request: apply its patch (the content of the linked
|
||||
/// root patch event) 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
|
||||
@@ -427,7 +460,7 @@ impl RepoStore {
|
||||
.as_ref()
|
||||
.map(|a| a.clone.iter().map(ToString::to_string).collect())
|
||||
.unwrap_or_default();
|
||||
let patch = root.content.clone();
|
||||
let patch = pull_request_patch(root, self.patches.iter());
|
||||
let root = root.clone();
|
||||
|
||||
let apply = cx.background_spawn(async move {
|
||||
|
||||
Reference in New Issue
Block a user