From af69d18a556e1137e6c68bfea8db3a3b99e750e8 Mon Sep 17 00:00:00 2001 From: "S. M. Mohiuddin Khan Shiam" Date: Fri, 13 Jun 2025 07:29:05 +0600 Subject: [PATCH] Fix: Robust Cross-Platform Fallback for Git Hook Linking in [link-hooks.mjs](cci:7://file:///d:/Github/TypeScript/scripts/link-hooks.mjs:0:0-0:0) Previously, [scripts/link-hooks.mjs](cci:7://file:///d:/Github/TypeScript/scripts/link-hooks.mjs:0:0-0:0) attempted to install git hooks using `fs.linkSync` to create hard links from the source-controlled hook scripts into `.git/hooks`. However, this approach fails on Windows in several common scenarios, such as: - When the repository is on a FAT/FAT32 drive (no hard link support) - When the working copy and `.git` are on different volumes (`EXDEV`) - When the user lacks the required privileges (`EPERM`) This prevented contributors on such systems from setting up the required git hooks, blocking or complicating development and contribution. **This PR adds a robust fallback:** - The script first tries to create a hard link as before. - If it fails with `EXDEV` or `EPERM`, it falls back to copying the hook file using `fs.copyFileSync`, ensuring the hook is still installed and functional. - All other errors are still thrown to avoid masking real issues. This change makes `npm run setup-hooks` reliable on all platforms, improving the onboarding and contribution experience for Windows and cross-device users, while still preferring hard links where possible to keep hooks in sync. --- **Summary:** Gracefully fall back to copying git hook scripts if hard-linking fails, ensuring contributors on all platforms can set up hooks without errors. --- scripts/link-hooks.mjs | 71 ++++++++++++++++++++++++++---------------- 1 file changed, 44 insertions(+), 27 deletions(-) diff --git a/scripts/link-hooks.mjs b/scripts/link-hooks.mjs index 05e1bfc0ddb9a..e3968c718b017 100644 --- a/scripts/link-hooks.mjs +++ b/scripts/link-hooks.mjs @@ -1,27 +1,44 @@ -import fs from "fs"; -import path from "path"; -import url from "url"; - -import { findUpRoot } from "./build/findUpDir.mjs"; - -const __filename = url.fileURLToPath(new URL(import.meta.url)); -const __dirname = path.dirname(__filename); - -const hooks = [ - "post-checkout", - "pre-commit", -]; - -hooks.forEach(hook => { - const hookInSourceControl = path.resolve(__dirname, "hooks", hook); - - if (fs.existsSync(hookInSourceControl)) { - const hookInHiddenDirectory = path.resolve(findUpRoot(), ".git", "hooks", hook); - - if (fs.existsSync(hookInHiddenDirectory)) { - fs.unlinkSync(hookInHiddenDirectory); - } - - fs.linkSync(hookInSourceControl, hookInHiddenDirectory); - } -}); +import fs from "fs"; +import path from "path"; +import url from "url"; + +import { findUpRoot } from "./build/findUpDir.mjs"; + +const __filename = url.fileURLToPath(new URL(import.meta.url)); +const __dirname = path.dirname(__filename); + +const hooks = [ + "post-checkout", + "pre-commit", +]; + +hooks.forEach(hook => { + const hookInSourceControl = path.resolve(__dirname, "hooks", hook); + + if (fs.existsSync(hookInSourceControl)) { + const hookInHiddenDirectory = path.resolve(findUpRoot(), ".git", "hooks", hook); + + if (fs.existsSync(hookInHiddenDirectory)) { + fs.unlinkSync(hookInHiddenDirectory); + } + + // Hard links are ideal to keep hooks in sync with the source-controlled + // versions, but they are not always supported. In particular, they can + // fail on Windows when the working copy resides on a FAT32 drive or + // when the user lacks the required privileges, and they always fail + // across device/volume boundaries (EXDEV). Gracefully fall back to a + // normal file copy in those scenarios so that contributors on any + // platform can still set up git hooks and contribute without friction. + try { + fs.linkSync(hookInSourceControl, hookInHiddenDirectory); + } + catch (err) { + if (err && (err.code === "EXDEV" || err.code === "EPERM")) { + fs.copyFileSync(hookInSourceControl, hookInHiddenDirectory); + } + else { + throw err; + } + } + } +});