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.

75 linesCodeBlameRaw
1/**
2 * Forking a repository: a button in the project's header beside Star, with
3 * how many forks it has, which leads to them. The button opens the fork
4 * page (routes/repo/fork.tsx); someone signed out is sent to sign in first.
5 * A private repository whose admins have not allowed forking shows it to
6 * its admins only, turned off, saying where to allow it.
7 */
8import { GitFork } from "lucide-react";
9import { Link } from "react-router";
10
11import { cn } from "../lib/cn";
12import { Hint } from "./ui/hint";
13
14const BOX = "inline-flex h-8 shrink-0 items-center overflow-hidden rounded-md border border-line text-[0.8125rem]";
15const FACE =
16 "inline-flex h-full items-center gap-1.5 px-2.5 text-fg/80 outline-none hover:bg-surface hover:text-fg focus-visible:ring-2 focus-visible:ring-inset focus-visible:ring-accent";
17
18/** What the header knows about its forks: how many (at most 100 counted) and whether it can be forked. */
19export type Forking = {
20 /** Forks the viewer can see, up to 100. */
21 count: number;
22 /** Whether there are more than `count`. */
23 more: boolean;
24 /** Whether it may be forked: public, or private with forking allowed. */
25 allowed: boolean;
26};
27
28export function ForkButton({ base, name, forking, signedIn }: { base: string; name: string; forking: Forking; signedIn: boolean }) {
29 const shown = forking.more ? `${forking.count}+` : String(forking.count);
30 const words = `${shown} ${forking.count === 1 && !forking.more ? "fork" : "forks"}`;
31 const face = (
32 <>
33 <GitFork size={14} />
34 <span className="hidden sm:inline">Fork</span>
35 </>
36 );
37 const counter = (
38 <Hint label={words}>
39 <Link
40 to={`${base}/forks`}
41 aria-label={`${words}: who forked it`}
42 className="inline-flex h-full items-center border-l border-line px-2 font-medium tabular-nums text-fg/80 outline-none hover:bg-surface hover:text-accent focus-visible:ring-2 focus-visible:ring-inset focus-visible:ring-accent"
43 >
44 {shown}
45 </Link>
46 </Hint>
47 );
48 if (!forking.allowed) {
49 return (
50 <span className={BOX}>
51 <Hint label="Forking is off for this private repository. Allow it under Settings, Repository.">
52 <Link
53 to={`${base}/settings/repository#forking`}
54 aria-label={`Forking ${name} is off: allow it in Settings`}
55 className={cn(FACE, "text-faint hover:text-muted")}
56 >
57 {face}
58 </Link>
59 </Hint>
60 {counter}
61 </span>
62 );
63 }
64 const to = signedIn ? `${base}/fork` : `/login?next=${encodeURIComponent(`${base}/fork`)}`;
65 return (
66 <span className={BOX}>
67 <Hint label={`Fork ${name} into a workspace of yours`}>
68 <Link to={to} aria-label={signedIn ? `Fork ${name}` : `Sign in to fork ${name}`} className={FACE}>
69 {face}
70 </Link>
71 </Hint>
72 {counter}
73 </span>
74 );
75}