feat: push checkout (#14)

Reviewed-on: https://git.reya.su/reya/signed/pulls/14
This commit was merged in pull request #14.
This commit is contained in:
2026-09-06 13:14:11 +00:00
parent 33cbe42551
commit 00167c6a8d
85 changed files with 6282 additions and 4487 deletions
+72 -38
View File
@@ -11,23 +11,22 @@ state.
```mermaid
graph TD
A["New pull request dialog"] --> B{"Patch source"}
B -->|"Paste"| C["Paste git format-patch output"]
B -->|"Local checkout"| D["Browse for checkout"]
D --> E["Defaults: source = current branch, target = announced HEAD"]
E --> F["Generate: merge-base plus format-patch base..tip"]
F --> G["Apply check vs mirror clone - non-blocking warning"]
C --> H["Submit"]
F --> H
G --> H
A["New pull request panel"] --> B{"Compare source"}
B -->|"Local checkout"| C["Pick folder (or auto-prefilled from remembered checkouts)"]
B -->|"Announced fork"| D["Pick fork repo + branch"]
D --> D1["Ensure base mirror (GitCache), fetch origin"]
D1 --> D2["Import fork heads as refs/fork/<owner>/<id>/*"]
C --> E["Defaults: target = announced HEAD, source = current branch / fork main"]
E --> F["merge-base + commits + diff of target..source (Files/Commits tabs)"]
F --> H["Submit: format-patch base..tip at publish time"]
H --> I["split_patch_series: one part per commit"]
I --> J{"Any part over 60 KB?"}
J -->|"Yes"| K["Refuse with message"]
J -->|"No"| L["tip = last part's From commit"]
L --> M["Publish kind-1617 patch series: first has t root, later parts e-reply chained"]
M --> N["Build kind-1618 PR event: c = tip, e = root patch, branch-name, merge-base"]
M --> N["Build kind-1618 PR event: c = tip, e = root patch, branch-name, merge-base, clone"]
N --> O["Sign early - learn the event id"]
O --> P["Push tip to refs/nostr/event-id on every announced grasp server"]
O --> P["Push tip to refs/nostr/event-id: author /prs/ grasp servers first, then the announced servers"]
P -->|"All rejected"| Q["last_warning banner in PR list"]
P --> R["Publish kind-1618 PR event"]
Q --> R
@@ -50,47 +49,81 @@ graph TD
Key points of the write side:
- **Merge base**: only computable in the local-checkout path
(`signed_git::merge_base`); the paste path publishes none. The dialog
reuses it at submit only while the patch textarea is unchanged.
- **Compare sources** (NIP-34 / GRASP-06 native, no fork identity on the
wire):
- *Local checkout*: both branch selectors list a picked folder's
branches; all git ops run in that folder. Checkouts of the target repo
are remembered (folder pick + app clones) and matched implicitly
(origin URL or EUC against the announcement), so the panel prefills the
freshest one - no folder dialog for the common case.
- *Announced fork*: the fork's heads are fetched into the target repo's
GitCache mirror under `refs/fork/<owner-hex>/<id>/*` (private
namespace; the browser never sees them). "Merge Into" lists the
mirror's `refs/remotes/origin/*`, "Pull From" the imported fork
branches, and every git op - merge-base, range diff/commits,
format-patch, tip push - runs in the mirror, which holds both
histories. Fork candidates are announcements related to the target by
`u` tag or shared EUC, own forks first, without `clone` URLs excluded.
- **GRASP-06 hosting**: the tip is pushed under `refs/nostr/<event-id>`
(nak's convention) to the *author's* grasp servers first -
`https://<host>/prs/<author-npub>/<repo-id>.git`, resolved from the
author's kind-10317 grasp list, falling back to the settings defaults -
then to the base repository's announced grasp servers. The `clone` tag
lists those `/prs/` URLs first, then the announced clone URLs (fixed
before signing; dead URLs are inert, the patches stay the source of
truth). Contributing therefore never depends on the other project's
servers accepting a push.
- **Patch series**: each commit becomes its own kind-1617 event so no event
grows past NIP-34's 60 KB guidance; the PR's `c` tag carries the *last*
commit of the series (the tip), and each part carries its own
`commit`/`r` tags.
- **Push before publish**: the tip is pushed to every announced grasp
server under `refs/nostr/<event-id>` (nak's convention) so the announced
`clone` URLs really can serve the commit. Failure is non-fatal — the
patch events remain the source of truth — and surfaces as a
`last_warning` banner.
- **Push before publish**: failure is non-fatal - the patch events remain
the source of truth - and surfaces as a `last_warning` banner.
- **1619 updates are paste-only today** (no repo path holds the new tip's
objects), so updates are not pushed; hosting them is deferred until the
update dialog gains a local-checkout source.
## Creating a pull request - event ordering
```mermaid
sequenceDiagram
participant User
participant App
participant Checkout as Local checkout
participant Grasp as Grasp servers
participant Relays as Nostr relays
participant P as Base mirror (GitCache)
participant F as Fork grasp server
participant A as Author grasp (GRASP-06 /prs/)
participant B as Base repo grasps
participant R as Nostr relays
User->>App: pick checkout and branches, Generate
App->>Checkout: merge-base(source, target)
Checkout-->>App: base commit
App->>Checkout: format-patch base..tip
Checkout-->>App: patch series
App->>App: split series, check per-part size
User->>P: ensure mirror (fork mode) / pick local checkout
P-->>F: fetch fork heads -> refs/fork/... (fork mode)
User->>P: merge-base, range commits, range diff
User->>P: submit: format-patch base..compare-ref
loop each patch of the series
App->>Relays: publish kind-1617 (first: t root, later: e reply)
User->>R: publish kind-1617 (first: t root, later: e reply)
end
App->>App: build and sign kind-1618 PR event
App->>Grasp: push tip to refs/nostr/event-id
Grasp-->>App: accepted or rejected (best-effort)
App->>Relays: publish kind-1618 PR event
User->>User: build and sign kind-1618 (clone = /prs/ URLs + announced)
User->>A: push tip to refs/nostr/event-id (author servers, first)
User->>B: push tip to refs/nostr/event-id (best-effort)
A-->>User: accepted or rejected (all rejected -> warning)
User->>R: publish kind-1618 PR event
opt draft
App->>Relays: publish kind-1633 draft status
User->>R: publish kind-1633 draft status
end
```
## Ready to contribute (suggestions)
Local checkouts are matched to announced repositories (remembered records
freshest-first scanned matches by origin URL or EUC). While a repository's
detail panel is open, each associated checkout is checked off the main
thread: current branch vs its base (announced HEAD, else `main`, else the
first branch), commits ahead, dirty worktrees excluded. A banner in the
repository panel then offers a prefilled New PR panel for the first branch
that is ahead with **no open PR by you** proposing it (`branch-name` tag,
falling back to the `c` tip tag) - NIP-34-native dedupe, refreshed
periodically and whenever the checkouts/announcements change. The panel
never submits anything on its own; suggestions only navigate and prefill.
## Updating and merging
```mermaid
@@ -100,7 +133,7 @@ sequenceDiagram
participant Maintainer
participant Clone as Mirror clone
Note over Author,Relays: Update - PR author only
Note over Author,Relays: Update - PR author only (paste flow, no push yet)
Author->>Relays: publish revision patch series (t root-revision, e reply to original root)
Author->>Relays: publish kind-1619 update (E/P tags, c = new tip)
@@ -132,8 +165,9 @@ Reader rules that keep the flow consistent:
- **Status**: only status events by the root author or a repository
maintainer count; the newest wins, `Open` is the default.
- **Tip**: only kind-1619 updates by the PR author move the tip a
- **Tip**: only kind-1619 updates by the PR author move the tip - a
stranger's update is ignored.
- **Diff**: the patch set is preferred (NIP-34 `e`-linked chain); PRs from
other clients without patch events fall back to diffing
`merge-base..tip` in the local clone.
`merge-base..tip` in the local clone. Fetching tips from `clone` URLs
(ngit `pr checkout` analog) is not implemented yet.