chadtmiller.com/knotless
git that lives in your Atmosphere account
1
10
A push in the feed links to the commit it left, which outlives its branch
crates/knotless-serve/src/web/feed.rs+3−0
| @@ -72,6 +72,8 @@ pub enum Action { | ||
#[serde(rename = "ref")] | ||
refname: String, | ||
created: bool, | ||
/// The commit the ref was left at, which outlives the ref. | ||
to: String, | ||
#[serde(skip_serializing_if = "std::ops::Not::not")] | ||
forced: bool, | ||
count: usize, | ||
| @@ -447,6 +449,7 @@ async fn events( | ||
Action::Pushed { | ||
refname: last.refname.clone(), | ||
created: first.from.is_none(), | ||
to: to.clone(), | ||
forced: !replaced.is_empty(), | ||
count: brought.len(), | ||
new: new.len(), | ||
crates/knotless-serve/tests/web.rs+2−0
| @@ -2086,6 +2086,8 @@ fn a_forced_push() { | ||
"{pushed}" | ||
); | ||
assert_eq!(pushed["commits"][0]["sha"], b.as_str()); | ||
// Where it left the ref, for a link that outlives the ref. | ||
assert_eq!(pushed["to"], b.as_str()); | ||
// One that only moved forward was not forced. | ||
owner.commit_file(&work, "c", "c\n"); | ||
owner.git(&work, &["push", "-q", &url, "main"]); | ||
docs/reference/api.md+3−2
| @@ -220,7 +220,7 @@ Answers `events` (`createdAt`, `actor` `{did, handle, avatar?}`, `repo` | ||
| `$type` | Fields | | ||
| --- | --- | | ||
| `dev.knotless.getFeed#created` | `description?`, `forkOf?` | | ||
| `dev.knotless.getFeed#pushed` | `ref`, `created`, `forced?`, `count`, `new`, `commits` (`sha`, `message`, `merge?`) | | ||
| `dev.knotless.getFeed#pushed` | `ref`, `created`, `to?`, `forced?`, `count`, `new`, `commits` (`sha`, `message`, `merge?`) | | ||
| `dev.knotless.getFeed#deleted` | `ref` | | ||
| `dev.knotless.getFeed#opened` | `subject`, `title` | | ||
| `dev.knotless.getFeed#commented` | `subject`, `title`, `text`, `verdict?` | | ||
| @@ -231,7 +231,8 @@ A push's `actor` is the account that pushed: a ref's claim or record that | ||
names a `pusher` the repository lists among its collaborators or runners | ||
is theirs, and any other is the owner's. `actor` and `filter=following` | ||
choose pushes by that account too. Pushes to one ref by one account within | ||
an hour of each other are one event. A push walks | ||
an hour of each other are one event. `to` is the commit the last of them left | ||
the ref at, which a page links to as the ref may since have been deleted. A push walks | ||
at most 100 commits and lists at most 10; `count` is what it brought, and | ||
`new` those no other ref had first. `forced` is a push that left commits | ||
behind, as a rebase or a rewrite does: its `count` and `new` leave out the | ||
lexicons/dev/knotless/getFeed.json+4−0
| @@ -76,6 +76,10 @@ | ||
"properties": { | ||
"ref": { "type": "string", "description": "The full ref name, such as refs/heads/main." }, | ||
"created": { "type": "boolean", "description": "Whether the ref was new." }, | ||
"to": { | ||
"type": "string", | ||
"description": "The commit the ref was left at, in hex: what the push brought, which a page can link to after the ref is gone." | ||
}, | ||
"forced": { "type": "boolean", "description": "Whether the ref moved to where it no longer reaches some of what it did, as a rebase or a rewrite does. Absent when not." }, | ||
"count": { "type": "integer", "minimum": 0, "maximum": 100, "description": "Commits the push brought; 100 means at least that many. A forced push's leave out those it brought again: the same author, at the same time, with the same message as a commit it left behind." }, | ||
"new": { "type": "integer", "minimum": 0, "maximum": 100, "description": "Of those, the commits no other ref of the repository had before, and no event before it on the same page lists; the rest are only counted." }, | ||
web/src/lib/molecules/FeedItem.svelte+6−4
| @@ -25,7 +25,9 @@ | ||
type Known = Extract<getFeed.EventView['action'], { $type: `dev.knotless.getFeed#${string}` }>; | ||
const a = $derived(x.kind === 'event' ? (x.event.action as Known) : null); | ||
const repo = $derived(repoHref(x.repo.handle, x.repo.name)); | ||
const log = (ref: string) => `${repo}/commits/${urlPath(shortRef(ref))}`; | ||
// A branch is often gone once its pull lands, so its log is of the | ||
// commit the push left it at, which stays. | ||
const log = (b: { ref: string; to?: string }) => `${repo}/commits/${b.to ?? urlPath(shortRef(b.ref))}`; | ||
| ||
/** | ||
* A lone event's sentence after who did it: words, then what it was done | ||
| @@ -80,7 +82,7 @@ | ||
| ||
{#snippet where()}{' on '}<RepoLink owner={x.repo.handle} name={x.repo.name} />{/snippet} | ||
{#snippet who()}{#if !own}<a class="who" href="/{urlPath(x.actor.handle)}">{x.actor.handle}</a>{/if}{own ? '' : ' '}{/snippet} | ||
{#snippet rest(b: { count: number; merges: number; elsewhere: number; ref: string }, listed: number)}<p class="feed-rest">{#if b.count > listed}<span class="feed-badge">+{b.count - listed} more</span>{/if}{#if b.merges}<span class="feed-badge">{b.merges === 1 ? '1 merge' : `${b.merges} merges`}</span>{/if}{#if b.elsewhere}<span class="feed-badge">{`+${b.elsewhere} from other branches`}</span>{/if}<a class="feed-log" href={log(b.ref)}>Commits →</a></p>{/snippet} | ||
{#snippet rest(b: { count: number; merges: number; elsewhere: number; ref: string; to?: string }, listed: number)}<p class="feed-rest">{#if b.count > listed}<span class="feed-badge">+{b.count - listed} more</span>{/if}{#if b.merges}<span class="feed-badge">{b.merges === 1 ? '1 merge' : `${b.merges} merges`}</span>{/if}{#if b.elsewhere}<span class="feed-badge">{`+${b.elsewhere} from other branches`}</span>{/if}<a class="feed-log" href={log(b)}>Commits →</a></p>{/snippet} | ||
| ||
<li> | ||
<Time at={x.createdAt} show={(at) => span(x.since, at)} /> | ||
| @@ -95,12 +97,12 @@ | ||
{:else if x.kind === 'pushes'} | ||
<p>{@render who()}{x.branches.length ? `pushed to ${x.branches.length + x.caught.length} branches` : `brought ${x.caught.length} branches up to date`}{@render where()}</p> | ||
<ul class="feed-branches"> | ||
{#each x.branches as b, i (i)}<li><Ref name={shortRef(b.ref)} /><span class="clip">{b.commits[0] ? subject(b.commits[0].message) : ''}</span><span class="feed-end"><span class="feed-count">{b.created ? `new · ${b.count}` : b.count}</span><a class="feed-log" href={log(b.ref)}>Commits →</a></span></li>{/each} | ||
{#each x.branches as b, i (i)}<li><Ref name={shortRef(b.ref)} /><span class="clip">{b.commits[0] ? subject(b.commits[0].message) : ''}</span><span class="feed-end"><span class="feed-count">{b.created ? `new · ${b.count}` : b.count}</span><a class="feed-log" href={log(b)}>Commits →</a></span></li>{/each} | ||
{#each x.caught as ref, i (i)}<li class="caught"><Ref name={shortRef(ref)} /><span class="clip">brought up to date</span></li>{/each} | ||
</ul> | ||
{:else if x.kind === 'merged'} | ||
<p>{@render who()}merged pull <a href={itemHref(repo, x.subject)}>{x.title}</a>{@render where()}</p> | ||
{#if x.landed}<p class="feed-rest">{`${commits(x.landed.count)} to `}<Ref name={shortRef(x.landed.ref)} /><a class="feed-log" href={log(x.landed.ref)}>Commits →</a></p>{/if} | ||
{#if x.landed}<p class="feed-rest">{`${commits(x.landed.count)} to `}<Ref name={shortRef(x.landed.ref)} /><a class="feed-log" href={log(x.landed)}>Commits →</a></p>{/if} | ||
{:else if x.kind === 'starred' && x.actors.length > 1} | ||
<Crowd actors={x.actors} /> | ||
<p><a class="who" href="/{urlPath(x.actor.handle)}">{x.actor.handle}</a>{` and ${others === 1 ? '1 other' : `${others} others`} starred `}<RepoLink owner={x.repo.handle} name={x.repo.name} /></p> | ||
web/src/lib/feed.ts+3−0
| @@ -25,6 +25,8 @@ export type Kind = (typeof KINDS)[number][0]; | ||
*/ | ||
export type Branch = { | ||
ref: string; | ||
/** The commit the ref was left at, absent from an older answer. */ | ||
to?: string; | ||
created: boolean; | ||
forced: boolean; | ||
commits: getFeed.CommitView[]; | ||
| @@ -39,6 +41,7 @@ function branch(a: Pushed): Branch { | ||
const unlisted = Math.max(0, a.new - a.commits.length); | ||
return { | ||
ref: a.ref, | ||
to: a.to, | ||
created: a.created, | ||
forced: !!a.forced, | ||
commits: real, | ||