Diffinery

codician/diffinerypublic

Open-source, agent-first GitHub and GitHub Actions alternative that runs on your own Cloudflare account: git hosting, pull requests, code review and drop-in GitHub Actions YAML on Workers, Durable Objects, R2 and Containers.

37 lines · 1.6 KBRaw History
1/**
2 * Tree entry names the server refuses to write (and leaves out of archives):
3 * empty names, `.` and `..`, names with `/`, `\` or NUL, and anything a
4 * checkout on Windows or macOS would treat as `.git` (`.GIT`, `.git.`,
5 * `.git ` , `git~1`, `.g‌it`). Git's own fsck rejects the same names.
6 */
7
8// Characters HFS+ ignores when comparing names (zero-width joiners, direction marks, BOM).
9const HFS_IGNORABLE = /[‌-‏‪-‮-]/g;
10
11export function isDotGitName(name: string): boolean {
12 const n = name.replace(HFS_IGNORABLE, "").toLowerCase().replace(/[. ]+$/, "");
13 return n === ".git" || n === "git~1";
14}
15
16/** Why a single tree entry name is unsafe, or null when it is fine. */
17export function unsafeTreeName(name: string): string | null {
18 if (!name) return "empty name";
19 if (name === "." || name === "..") return `'${name}' is not a valid name`;
20 if (name.includes("/") || name.includes("\\") || name.includes("\0")) return "names can't contain '/', '\\' or NUL";
21 if (isDotGitName(name)) return "'.git' is reserved";
22 if (name.length > 255) return "name is too long";
23 return null;
24}
25
26/** Why a slash-separated path is unsafe to write, or null. */
27export function unsafeTreePath(path: string): string | null {
28 if (path.includes("\\") || path.includes("\0")) return "paths can't contain '\\' or NUL";
29 const parts = path.split("/").filter((p) => p !== "");
30 if (!parts.length) return "empty path";
31 if (path.length > 4096) return "path is too long";
32 for (const p of parts) {
33 const why = unsafeTreeName(p);
34 if (why) return why;
35 }
36 return null;
37}