Skip to content

Where people and agents ship software together. The open-source git platform for the whole job: issues, agents, checks and deploys to the edge.

Commit

A pull request's page no longer waits for its merge options. The loader awaited work's merge_choices beside everything else, and that call reads the branch's commits for the default merge messages (repos inspect_commits fetches and reads a pack from the git store every time) and asked repos whether it is behind: 6 to 10 seconds for flagon-io/g1t #39 and #41 (Server-Timing: work 9 calls, 7.5 s inside, 214 ms of it the database), long enough for the page to time out. Now the merge box opens on what the repository's settings allow, as it did when the service could not answer, and takes the service's choices, with their messages, in place when they stream in (useArrived), keeping the method picked and a dialog that is open. Merge options are asked for only for an open or draft pull request; a merged or closed one never needed them. In work, merge_choices takes whether it is behind from what was stored with its mergeability for its head as it is now, as get_pull does (behind_now, else repos), beside its settings rather than after them. Checked: cargo test for g1t-work (189), the site's typecheck and unit tests (934).

syntaqxcommitted Parent8e1a370Browse files
3 files+70−110/3 viewed
+45−9
2222 Users,
2323 Wrench,
2424 } from "lucide-react";
25−import { Suspense, useState } from "react";
25+import { Suspense, useEffect, useState } from "react";
2626 import { Await, Form, Link, redirect } from "react-router";
2727
2828 import {
143143 const pullFound = work.getPull(path, number, viewer);
144144 // Its labels and milestone, with the repository's to choose from.
145145 const labelsFound = work.listLabels(path, viewer);
146− // How it may merge now, for the merge box: beside the rest.
147− const choicesFound = tab === "conversation" ? work.mergeChoices(viewer, path, number).catch(() => null) : Promise.resolve(null);
146+ // How it may merge now, for the merge box: streamed, since its default
147+ // messages are read from the branch's commits, which takes seconds. Only
148+ // one still open or a draft can merge.
149+ const choicesFound =
150+ tab === "conversation"
151+ ? pullFound
152+ .then((found) =>
153+ found.ok && (found.value.pull.status === "open" || found.value.pull.status === "draft")
154+ ? work.mergeChoices(viewer, path, number)
155+ : null,
156+ )
157+ .catch(() => null)
158+ : Promise.resolve(null);
148159 const milestonesFound = work.listMilestones(path, viewer);
149160 labelsFound.catch(() => null);
150161 milestonesFound.catch(() => null);
232243 tab === "conversation" && pull.issue != null && (found.value.issue?.pullCount ?? 0) > 1
233244 ? attemptsFor(path, viewer, pull, found.value.comments).catch(() => [] as Attempt[])
234245 : null;
235− const [labels, milestones, served] = await Promise.all([labelsFound, milestonesFound, choicesFound]);
246+ const [labels, milestones] = await Promise.all([labelsFound, milestonesFound]);
247+ // The merge box opens on what the settings allow, then takes the
248+ // service's answer, with its messages, when it arrives.
249+ const settledChoices = mergeChoicesOf(settings.ok ? settings.value : null, found.value.behind);
250+ const servedChoices = choicesFound.then((served) =>
251+ served?.ok && served.value.methods.length > 0 ? choicesFromService(served.value) : settledChoices,
252+ );
236253 const repoDefault = repo.ok ? repo.value.defaultBranch : "main";
237254 const viewerOwns = viewer != null && viewer.id === workOwner(pull).id;
238255 // The default branch's protection covers pull requests into it only.
291308 canProtect: can.manage_protection,
292309 defaultBranch: repo.ok ? repo.value.defaultBranch : "main",
293310 // What the merge box offers: methods, messages, the branch afterwards.
294− choices:
295− served?.ok && served.value.methods.length > 0
296− ? choicesFromService(served.value)
297− : mergeChoicesOf(settings.ok ? settings.value : null, found.value.behind),
311+ // At first from the settings; the service's answer is streamed.
312+ choices: settledChoices,
313+ servedChoices,
298314 // Auto-merge on: whoever may merge it, since it merges as them. Off:
299315 // they, or whoever it is for.
300316 canAutoMerge: can.merge,
670686 }
671687
672688 /** The box at the foot of the conversation saying what stands before a merge. */
689+/** `initial` until `promise` settles, then what it resolved to. */
690+function useArrived<T>(promise: Promise<T>, initial: T): T {
691+ const [arrived, setArrived] = useState<{ from: Promise<T>; value: T } | null>(null);
692+ useEffect(() => {
693+ let current = true;
694+ promise.then(
695+ (value) => current && setArrived({ from: promise, value }),
696+ () => {},
697+ );
698+ return () => {
699+ current = false;
700+ };
701+ }, [promise]);
702+ return arrived?.from === promise ? arrived.value : initial;
703+}
704+
673705 function StatusBox({ children }: { children: React.ReactNode }) {
674706 return (
675707 <div className="flex gap-3">
751783 canManage,
752784 canModerate,
753785 defaultBranch: repoDefault,
754− choices,
786+ choices: settledChoices,
787+ servedChoices,
755788 canAutoMerge,
756789 canStopAutoMerge,
757790 canChangeBranch,
758791 } = loaderData;
792+ // The merge box takes the service's choices in place when they arrive,
793+ // keeping what the viewer already picked or has open.
794+ const choices = useArrived(servedChoices, settledChoices);
759795 // The branch it merges into: what every line below names.
760796 const defaultBranch = pull.base ?? repoDefault;
761797 const base = `/${params.owner}/${params.repo}`;
+2−2
281281 let (repo, mut pull) = check!(self.pull_at(&a.repo, a.number, &a.viewer).await?);
282282 crate::fill_base(&mut pull, &repo);
283283 let base = pull.base_branch(&repo.default_branch).to_owned();
284− let (settings, requirements) = self.settings_and_requirements(&repo, &pull).await?;
285− let behind = if pull.status.is_active() { self.is_behind(&pull).await? } else { false };
284+ let ((settings, requirements), behind) =
285+ futures_util::future::try_join(self.settings_and_requirements(&repo, &pull), self.behind_now(&pull)).await?;
286286 let methods = offered(&settings, &requirements);
287287 let default_method = choose_method(None, &settings, &requirements, &base).ok();
288288 let viewer = a.viewer.as_ref();
+23−0
132132 self.timing.rpc(g1t_kit::call(&self.repos, "behind", &asked)).await
133133 }
134134
135+ /// Whether a pull request is behind, as stored with its mergeability
136+ /// when that was worked out for its head as it is now (the last push
137+ /// to either side); otherwise asked of the repos service.
138+ pub(crate) async fn behind_now(&self, pull: &Pull) -> Result<bool> {
139+ if !pull.status.is_active() || pull.head_commit.is_none() {
140+ return Ok(false);
141+ }
142+ let stored = self
143+ .timing
144+ .db(
145+ 1,
146+ self.db
147+ .prepare("SELECT behind, mergeable_key FROM pulls WHERE id = ?")
148+ .bind(&[pull.id.as_str().into()])?
149+ .first::<crate::StoredBehind>(None),
150+ )
151+ .await?;
152+ match stored.and_then(|stored| stored.for_head(pull.head_commit.as_deref())) {
153+ Some(behind) => Ok(behind),
154+ None => self.is_behind(pull).await,
155+ }
156+ }
157+
135158 /// Where a pull request lives, for a tool that knows only its id (its
136159 /// fork is named after it).
137160 pub(crate) async fn locate_pull(&self, a: LocatePullArgs) -> Result<Outcome<LocatedPull>> {

Comments

Sign in to comment.