diff --git a/.changeset/smart-queens-ring.md b/.changeset/smart-queens-ring.md new file mode 100644 index 0000000..8506cd7 --- /dev/null +++ b/.changeset/smart-queens-ring.md @@ -0,0 +1,5 @@ +--- +"simple-git-hooks": minor +--- + +feat: optimize the migration experience from `husky` diff --git a/README.md b/README.md index e300e54..40734d3 100644 --- a/README.md +++ b/README.md @@ -96,11 +96,6 @@ If you need multiple verbose commands per git hook, flexible configuration or au 3. Run the CLI script to update the git hooks with the commands from the config: ```sh - # [Optional] These 2 steps can be skipped for non-husky users - git config core.hooksPath .git/hooks/ - rm -rf .git/hooks - - # Update ./git/hooks npx simple-git-hooks ``` @@ -211,34 +206,6 @@ If you have the option to set arguments or environment variables, you can use th If these options are not available, you may need to resort to using the terminal for skipping hooks. -### When migrating from `husky` git hooks are not running - -**Why is this happening?** - -Husky might change the `core.gitHooks` value to `.husky`, this way, git hooks would search `.husky` directory instead of `.git/hooks/`. - -Read more on git configuration in [Git book](https://git-scm.com/docs/githooks) - -You can check it by running this command inside of your repo: - -`git config core.hooksPath` - -If it outputs `.husky` then this is your case - -**How to fix?** - -you need to point `core.gitHooks` value to `your-awesome-project/.git/hooks`. You can use this command: - -`git config core.hooksPath .git/hooks/` - -validate the value is set: - -`git config core.hooksPath` - -should output: `.git/hooks/` - -Then remove the `.husky` folder that are generated previously by `husky`. - ### I am getting "npx: command not found" error in a GUI git client This happens when using a node version manager such as `nodenv`, `nvm`, `mise` which require diff --git a/simple-git-hooks.js b/simple-git-hooks.js index 5ba3ddd..75cc73b 100644 --- a/simple-git-hooks.js +++ b/simple-git-hooks.js @@ -1,6 +1,7 @@ const fs = require('fs') const path = require('path') const url = require('url') +const { execSync } = require('child_process'); const CONFIG_ERROR = '[ERROR] Config was not found! Please add `.simple-git-hooks.cjs` or `.simple-git-hooks.js` or `.simple-git-hooks.mjs` or `simple-git-hooks.cjs` or `simple-git-hooks.js` or `simple-git-hooks.mjs` or `.simple-git-hooks.json` or `simple-git-hooks.json` or `simple-git-hooks` entry in package.json.\r\nCheck README for details' @@ -185,7 +186,36 @@ async function setHooksFromConfig(projectRootPath=process.cwd(), argv=process.ar } /** - * Creates or replaces an existing executable script in .git/hooks/ with provided command + * Returns the absolute path to the Git hooks directory. + * Respects user-defined core.hooksPath from Git config if present; + * otherwise defaults to /.git/hooks. + * + * @param {string} gitRoot - The absolute path to the Git project root + * @returns {string} - The resolved absolute path to the hooks directory + * @private + */ +function _getHooksDirPath(projectRoot) { + const defaultHooksDirPath = path.join(projectRoot, '.git', 'hooks') + try { + const customHooksDirPath = execSync('git config core.hooksPath', { + cwd: projectRoot, + encoding: 'utf8' + }).trim() + + if (!customHooksDirPath) { + return defaultHooksDirPath + } + + return path.isAbsolute(customHooksDirPath) + ? customHooksDirPath + : path.resolve(projectRoot, customHooksDirPath) + } catch { + return defaultHooksDirPath + } +} + +/** + * Creates or replaces an existing executable script in the git hooks directory with provided command * @param {string} hook * @param {string} command * @param {string} projectRoot @@ -200,12 +230,12 @@ function _setHook(hook, command, projectRoot=process.cwd()) { } const hookCommand = PREPEND_SCRIPT + command - const hookDirectory = gitRoot + '/hooks/' - const hookPath = path.normalize(hookDirectory + hook) + const hookDirectory = _getHooksDirPath(projectRoot) + const hookPath = path.join(hookDirectory, hook) const normalizedHookDirectory = path.normalize(hookDirectory) if (!fs.existsSync(normalizedHookDirectory)) { - fs.mkdirSync(normalizedHookDirectory) + fs.mkdirSync(normalizedHookDirectory, { recursive: true }) } fs.writeFileSync(hookPath, hookCommand) @@ -235,14 +265,14 @@ async function removeHooks(projectRoot = process.cwd()) { } /** - * Removes the pre-commit hook from .git/hooks + * Removes the pre-commit hook * @param {string} hook * @param {string} projectRoot * @private */ function _removeHook(hook, projectRoot=process.cwd()) { - const gitRoot = getGitProjectRoot(projectRoot) - const hookPath = path.normalize(gitRoot + '/hooks/' + hook) + const hookDirectory = _getHooksDirPath(projectRoot) + const hookPath = path.join(hookDirectory, hook) if (fs.existsSync(hookPath)) { fs.unlinkSync(hookPath) diff --git a/simple-git-hooks.test.js b/simple-git-hooks.test.js index cede579..04d864f 100644 --- a/simple-git-hooks.test.js +++ b/simple-git-hooks.test.js @@ -499,6 +499,38 @@ describe("Simple Git Hooks tests", () => { }); }); + describe("Custom core.hooksPath", () => { + beforeAll(() => { + execSync('git config core.hooksPath .husky'); + }); + + afterAll(() => { + execSync('git config --unset core.hooksPath'); + }); + + const TEST_HUSKY_PROJECT = PROJECT_WITH_CONF_IN_SEPARATE_JS_ALT; + + it("creates git hooks in .husky if core.hooksPath is set to .husky", async () => { + const huskyDir = path.join(TEST_HUSKY_PROJECT, ".husky"); + + if (!fs.existsSync(huskyDir)) { + fs.mkdirSync(huskyDir); + } + + await simpleGitHooks.setHooksFromConfig(TEST_HUSKY_PROJECT); + + const installedHooks = getInstalledGitHooks(huskyDir); + expect(isEqual(installedHooks, COMMON_GIT_HOOKS)).toBe(true); + }) + + it("remove git hooks in .husky if core.hooksPath is set to .husky", async () => { + await simpleGitHooks.removeHooks(TEST_HUSKY_PROJECT); + const huskyDir = path.join(TEST_HUSKY_PROJECT, ".husky"); + const installedHooks = getInstalledGitHooks(huskyDir); + expect(isEqual(installedHooks, {})).toBe(true); + }) + }) + describe("CLI tests", () => { const testCases = [ ["npx", "simple-git-hooks", "./git-hooks.js"],