~ knotless Search/
↑↓ move↵ open⇧↵ all results

johnandersen777.bsky.social/knotless

git that lives in your Atmosphere account

Forked from chadtmiller.com/knotless

0
0
150 lines · 5.4 KB Raw History
import type * as getFeed from '#lib/lexicons/dev/knotless/getFeed.ts';
import { clock, subject } from '#lib/format.ts';

// The kinds of event this page knows; any other is shown as it comes.
type Known = Extract<getFeed.EventView['action'], { $type: `dev.knotless.getFeed#${string}` }>;
type Pushed = Extract<Known, { $type: 'dev.knotless.getFeed#pushed' }>;

/** Seconds between one account's pushes to a repository that are shown as one. */
const ROLLED = 30 * 60;

/** What the feed can be narrowed to, as the page's `kind` names it. */
export const KINDS = [
	['pushes', 'Pushes'],
	['pulls', 'Pulls'],
	['issues', 'Issues'],
	['comments', 'Comments'],
	['stars', 'Stars'],
	['repos', 'Repositories']
] as const;
export type Kind = (typeof KINDS)[number][0];

/**
 * A ref's part of a push: the commits it brought, merges left out of the
 * list and counted apart, as are those other refs had first.
 */
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[];
	/** The commits it brought that are not merges, listed or not. */
	count: number;
	merges: number;
	elsewhere: number;
};

function branch(a: Pushed): Branch {
	const real = a.commits.filter((c) => !c.merge);
	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,
		count: real.length + unlisted,
		merges: a.commits.length - real.length,
		elsewhere: a.count - a.new
	};
}

/** A ref that moved only to bring in what other refs had. */
export const caughtUp = (b: Branch) => !b.created && !b.forced && b.count === 0;

type Base = { actor: getFeed.ActorView; repo: getFeed.RepoView; createdAt: string; since: string };
/**
 * What a line of the feed shows: an event as it came; one account's
 * pushes to a repository close together; a pull merged, with the push
 * that landed it; or stars, of one repository by many or of many by one.
 */
export type Entry = Base &
	(
		| { kind: 'event'; event: getFeed.EventView }
		| { kind: 'pushes'; branches: Branch[]; caught: string[] }
		| { kind: 'merged'; subject: string; title: string; landed: Branch | null }
		| { kind: 'starred'; actors: getFeed.ActorView[]; repos: getFeed.RepoView[] }
	);

const sameRepo = (a: getFeed.RepoView, b: getFeed.RepoView) => a.did === b.did && a.name === b.name;
const seconds = (iso: string) => Date.parse(iso) / 1000;

/**
 * `events`, newest first, as the lines that show them. A merged pull takes
 * in the push beside it that landed it, one of whose commits has its
 * title. One account's pushes to a repository, each within half an hour
 * of the next and nothing between them, are one line, as are stars in a
 * row of one repository or by one account.
 */
export function entries(events: getFeed.EventView[]): Entry[] {
	const landed = new Map<number, number>();
	events.forEach((e, i) => {
		const a = e.action as Known;
		if (a.$type !== 'dev.knotless.getFeed#changed' || a.state !== 'merged') return;
		const by = [i - 1, i + 1].find((j) => {
			const p = events[j]?.action as Known | undefined;
			return (
				p?.$type === 'dev.knotless.getFeed#pushed' &&
				sameRepo(events[j].repo, e.repo) &&
				!landed.has(j) &&
				p.commits.some((c) => subject(c.message) === a.title)
			);
		});
		if (by !== undefined) landed.set(by, i);
	});
	const into = new Map([...landed].map(([push, merge]) => [merge, push]));

	const out: Entry[] = [];
	events.forEach((e, i) => {
		if (landed.has(i)) return;
		const a = e.action as Known;
		const last = out.at(-1);
		const base = { actor: e.actor, repo: e.repo, createdAt: e.createdAt, since: e.createdAt };
		if (a.$type === 'dev.knotless.getFeed#pushed') {
			const b = branch(a);
			const rolls =
				last?.kind === 'pushes' &&
				last.actor.did === e.actor.did &&
				sameRepo(last.repo, e.repo) &&
				seconds(last.since) - seconds(e.createdAt) <= ROLLED;
			const to = rolls ? last : null;
			if (to) to.since = e.createdAt;
			const pushes = to ?? out[out.push({ ...base, kind: 'pushes', branches: [], caught: [] }) - 1];
			if (pushes.kind !== 'pushes') return;
			if (caughtUp(b)) pushes.caught.push(b.ref);
			else pushes.branches.push(b);
		} else if (a.$type === 'dev.knotless.getFeed#changed' && a.state === 'merged') {
			const push = into.get(i);
			const p = push === undefined ? null : (events[push].action as Pushed);
			out.push({
				...base,
				kind: 'merged',
				subject: a.subject,
				title: a.title,
				landed: p && branch(p)
			});
		} else if (a.$type === 'dev.knotless.getFeed#starred') {
			if (last?.kind === 'starred' && last.repos.length === 1 && sameRepo(last.repo, e.repo)) {
				if (!last.actors.some((x) => x.did === e.actor.did)) last.actors.push(e.actor);
				last.since = e.createdAt;
			} else if (
				last?.kind === 'starred' &&
				last.actors.length === 1 &&
				last.actor.did === e.actor.did
			) {
				if (!last.repos.some((r) => sameRepo(r, e.repo))) last.repos.push(e.repo);
				last.since = e.createdAt;
			} else out.push({ ...base, kind: 'starred', actors: [e.actor], repos: [e.repo] });
		} else out.push({ ...base, kind: 'event', event: e });
	});
	return out;
}

/** When a line's events happened: one time, or from the first to the last, as `17:42–45`. */
export function span(since: string, at: string): string {
	const [from, to] = [clock(since), clock(at)];
	if (from === to) return to;
	return from.slice(0, 3) === to.slice(0, 3) ? `${from}–${to.slice(3)}` : `${from}–${to}`;
}