├── .npmignore ├── .gitignore ├── .travis.yml ├── bin ├── uninstall.js └── install.js ├── src ├── utils │ ├── find-parent.js │ ├── is.js │ ├── find-hooks-dir.js │ └── get-hook-script.js ├── hooks.json ├── runner.js ├── uninstall.js └── install.js ├── appveyor.yml ├── README.md ├── HOOKS.md ├── LICENSE ├── package.json ├── CHANGELOG.md └── __tests__ └── index.js /.npmignore: -------------------------------------------------------------------------------- 1 | test/ 2 | .travis.yml 3 | 4 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | node_modules 3 | tmp -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | os: 2 | - linux 3 | - osx 4 | language: node_js 5 | node_js: 6 | - "stable" 7 | - "4" 8 | script: 9 | - npm test 10 | - HUSKY_IGNORE_CI=true node bin/install 11 | - bash -n .git/hooks/pre-commit 12 | -------------------------------------------------------------------------------- /bin/uninstall.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | // Run when package is uninstalled 4 | const path = require('path') 5 | const uninstallFrom = require('../src/uninstall') 6 | 7 | console.log('husky') 8 | console.log('uninstalling Git hooks') 9 | 10 | const depDir = path.join(__dirname, '..') 11 | uninstallFrom(depDir) 12 | -------------------------------------------------------------------------------- /src/utils/find-parent.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const fs = require('fs') 4 | const path = require('path') 5 | 6 | module.exports = function findParent(currentDir, name) { 7 | const dirs = currentDir.split(path.sep) 8 | 9 | while (dirs.pop()) { 10 | const dir = dirs.join(path.sep) 11 | 12 | if (fs.existsSync(path.join(dir, name))) { 13 | return path.resolve(dir) 14 | } 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /src/hooks.json: -------------------------------------------------------------------------------- 1 | [ 2 | "applypatch-msg", 3 | "pre-applypatch", 4 | "post-applypatch", 5 | "pre-commit", 6 | "prepare-commit-msg", 7 | "commit-msg", 8 | "post-commit", 9 | "pre-rebase", 10 | "post-checkout", 11 | "post-merge", 12 | "pre-push", 13 | "pre-receive", 14 | "update", 15 | "post-receive", 16 | "post-update", 17 | "push-to-checkout", 18 | "pre-auto-gc", 19 | "post-rewrite", 20 | "sendemail-validate" 21 | ] 22 | -------------------------------------------------------------------------------- /appveyor.yml: -------------------------------------------------------------------------------- 1 | # Test against the latest version of this Node.js version 2 | environment: 3 | nodejs_version: "8" 4 | 5 | # Install scripts. (runs after repo cloning) 6 | install: 7 | # Get the latest stable version of Node.js or io.js 8 | - ps: Install-Product node $env:nodejs_version 9 | # install modules 10 | - npm install 11 | 12 | # Post-install test scripts. 13 | test_script: 14 | # Output useful info for debugging. 15 | - node --version 16 | - npm --version 17 | # run tests 18 | - npm test 19 | 20 | # Don't actually build. 21 | build: off 22 | -------------------------------------------------------------------------------- /src/runner.js: -------------------------------------------------------------------------------- 1 | const fs = require('fs') 2 | const path = require('path') 3 | const execa = require('execa') 4 | 5 | const cwd = process.cwd() 6 | const pkg = fs.readFileSync(path.join(cwd, 'package.json')) 7 | const hooks = JSON.parse(pkg).gitHooks 8 | if (!hooks) { 9 | process.exit(0) 10 | } 11 | 12 | const hook = process.argv[2] 13 | const command = hooks[hook] 14 | if (!command) { 15 | process.exit(0) 16 | } 17 | 18 | console.log(` > running ${hook} hook: ${command}`) 19 | try { 20 | execa.shellSync(command, { stdio: 'inherit' }) 21 | } catch (e) { 22 | process.exit(1) 23 | } 24 | -------------------------------------------------------------------------------- /src/utils/is.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const fs = require('fs') 4 | 5 | function readFile(filename) { 6 | return fs.readFileSync(filename, 'utf-8') 7 | } 8 | 9 | function huskyOrYorkie(filename) { 10 | const data = readFile(filename) 11 | return data.indexOf('#husky') !== -1 || data.indexOf('#yorkie') !== -1 12 | } 13 | 14 | function ghooks(filename) { 15 | const data = readFile(filename) 16 | return data.indexOf('// Generated by ghooks. Do not edit this file.') !== -1 17 | } 18 | 19 | function preCommit(filename) { 20 | const data = readFile(filename) 21 | return data.indexOf('./node_modules/pre-commit/hook') !== -1 22 | } 23 | 24 | module.exports = { 25 | huskyOrYorkie, 26 | ghooks, 27 | preCommit 28 | } 29 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # yorkie 2 | 3 | > Git hooks made easy 4 | 5 | This is a fork of [husky](https://github.com/typicode/husky) with a few changes: 6 | 7 | - Prioritizes `package.json` located next to `.git` directory, instead of hard-coded upward search. This avoids the problem when a root package in a lerna monorepo and a sub package both depends on husky, it gets confused and double-updates the root git hooks with wrong paths. 8 | 9 | - Changed where hooks are read from in `package.json`: 10 | 11 | **Before** 12 | 13 | ``` json 14 | { 15 | "scripts": { 16 | "precommit": "foo" 17 | } 18 | } 19 | ``` 20 | 21 | **After** 22 | 23 | ``` json 24 | { 25 | "gitHooks": { 26 | "pre-commit": "foo" 27 | } 28 | } 29 | ``` 30 | -------------------------------------------------------------------------------- /bin/install.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | // Run when package is installed 4 | const path = require('path') 5 | const isCI = require('is-ci') 6 | const installFrom = require('../src/install') 7 | 8 | if (isCI && !process.env.HUSKY_IGNORE_CI && !process.env.YORKIE_IGNORE_CI) { 9 | console.log('CI detected, skipping Git hooks installation') 10 | process.exit(0) 11 | } 12 | 13 | if (process.env.HUSKY_SKIP_INSTALL || process.env.YORKIE_SKIP_INSTALL) { 14 | console.log( 15 | `env variable HUSKY_SKIP_INSTALL is set to ${process.env 16 | .HUSKY_SKIP_INSTALL}, skipping Git hooks installation` 17 | ) 18 | process.exit(0) 19 | } 20 | 21 | console.log('setting up Git hooks') 22 | 23 | const depDir = path.join(__dirname, '..') 24 | installFrom(depDir) 25 | -------------------------------------------------------------------------------- /src/uninstall.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const fs = require('fs') 4 | const hooks = require('./hooks.json') 5 | const findParent = require('./utils/find-parent') 6 | const findHooksDir = require('./utils/find-hooks-dir') 7 | const is = require('./utils/is') 8 | 9 | function removeHook(dir, name) { 10 | const filename = `${dir}/${name}` 11 | 12 | if (fs.existsSync(filename) && is.huskyOrYorkie(filename)) { 13 | fs.unlinkSync(`${dir}/${name}`) 14 | } 15 | } 16 | 17 | function uninstallFrom(huskyDir) { 18 | try { 19 | const hooksDir = findHooksDir(findParent(huskyDir, '.git')) 20 | 21 | hooks.forEach(function(hookName) { 22 | removeHook(hooksDir, hookName) 23 | }) 24 | console.log('done\n') 25 | } catch (e) { 26 | console.error(e) 27 | } 28 | } 29 | 30 | module.exports = uninstallFrom 31 | -------------------------------------------------------------------------------- /src/utils/find-hooks-dir.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const fs = require('fs') 4 | const path = require('path') 5 | const findParent = require('./find-parent') 6 | 7 | function findHooksDir(dir) { 8 | if (dir) { 9 | let gitDir = path.join(dir, '.git') 10 | if (!fs.existsSync(gitDir)) { 11 | return 12 | } 13 | 14 | const stats = fs.lstatSync(gitDir) 15 | 16 | if (stats.isFile()) { 17 | // Expect following format 18 | // git: pathToGit 19 | // On Windows pathToGit can contain ':' (example "gitdir: C:/Some/Path") 20 | const gitFileData = fs.readFileSync(gitDir, 'utf-8') 21 | gitDir = gitFileData 22 | .split(':') 23 | .slice(1) 24 | .join(':') 25 | .trim() 26 | } 27 | 28 | return path.resolve(dir, gitDir, 'hooks') 29 | } 30 | } 31 | 32 | module.exports = findHooksDir 33 | -------------------------------------------------------------------------------- /HOOKS.md: -------------------------------------------------------------------------------- 1 | # Hooks 2 | 3 | Husky supports all git hooks (https://git-scm.com/docs/githooks). Simply add the corresponding `npm script` to your `package.json`. 4 | 5 | | Git hook | npm script | 6 | | -------- | ---------- | 7 | | applypatch-msg | applypatchmsg | 8 | | commit-msg | commitmsg | 9 | | post-applypatch | postapplypatch | 10 | | post-checkout | postcheckout | 11 | | post-commit | postcommit | 12 | | post-merge | postmerge | 13 | | post-receive | postreceive | 14 | | post-rewrite | postrewrite | 15 | | post-update | postupdate | 16 | | pre-applypatch | preapplypatch | 17 | | pre-auto-gc | preautogc | 18 | | pre-commit | precommit | 19 | | pre-push | prepush | 20 | | pre-rebase | prerebase | 21 | | pre-receive | prereceive | 22 | | prepare-commit-msg | preparecommitmsg | 23 | | push-to-checkout | pushtocheckout | 24 | | update | update | 25 | | sendemail-validate | sendemailvalidate | 26 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2014 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "yorkie", 3 | "version": "2.0.0", 4 | "description": "githooks management forked from husky", 5 | "engines": { 6 | "node": ">=4" 7 | }, 8 | "scripts": { 9 | "test": "jest", 10 | "format": "prettier --single-quote --no-semi --write **/*.js", 11 | "install": "node bin/install.js", 12 | "uninstall": "node bin/uninstall.js" 13 | }, 14 | "repository": { 15 | "type": "git", 16 | "url": "git://github.com/yyx990803/yorkie.git" 17 | }, 18 | "keywords": [ 19 | "git", 20 | "hook", 21 | "hooks", 22 | "pre-commit", 23 | "precommit", 24 | "post-commit", 25 | "postcommit", 26 | "pre-push", 27 | "prepush", 28 | "post-merge", 29 | "postmerge", 30 | "test" 31 | ], 32 | "authors": [ 33 | "Typicode ", 34 | "Evan You" 35 | ], 36 | "license": "MIT", 37 | "bugs": { 38 | "url": "https://github.com/yyx990803/yorkie/issues" 39 | }, 40 | "homepage": "https://github.com/yyx990803/yorkie", 41 | "devDependencies": { 42 | "jest": "^20.0.4", 43 | "mkdirp": "^0.5.1", 44 | "prettier": "^1.4.4", 45 | "rimraf": "^2.6.1", 46 | "tempy": "^0.1.0" 47 | }, 48 | "dependencies": { 49 | "execa": "^0.8.0", 50 | "is-ci": "^1.0.10", 51 | "normalize-path": "^1.0.0", 52 | "strip-indent": "^2.0.0" 53 | }, 54 | "standard": { 55 | "env": { 56 | "jest": true 57 | } 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # CHANGELOG 2 | 3 | ## Unreleased 4 | 5 | * Support `sendemail-validate` hook [#173](https://github.com/typicode/husky/pull/173) 6 | * Support `HUSKY_SKIP_INSTALL` environment variable for skipping git hooks installation 7 | * Drop `nvm` installed with `brew` support as it's not supported by `nvm` itself (see [creationix/nvm#important-notes](https://github.com/creationix/nvm#important-notes)), `nvm` standard installation is still supported though 8 | 9 | ## 0.14.3 10 | 11 | * Fix handle space in `PATH` [#150](https://github.com/typicode/husky/pull/114) 12 | 13 | ## 0.14.2 14 | 15 | * Fix handle space in `HOME` 16 | 17 | ## 0.14.1 18 | 19 | * Fix Git hooks install on Windows 20 | * Fix hook script when `nvm` was installed with Brew 21 | 22 | ## 0.14.0 23 | 24 | * Fix `npm@5` `Error: Cannot find module` warning when uninstalling 25 | * Drop `Node 0.12` support 26 | * Don't reload `nvm` if it's already in `PATH` 27 | * Add Git worktree support [#114](https://github.com/typicode/husky/pull/114) 28 | * Hide irrelevant `--no-verify` message for `prepare-commit-msg` [#137](https://github.com/typicode/husky/issues/137) 29 | 30 | ## 0.13.4 31 | 32 | * Add Node version to husky output 33 | 34 | ## 0.13.3 35 | 36 | * Revert `Fixes issue with OS X + brew where nvm was loaded even when npm was already present` that was introduced in `v0.13.0` as it was preventing Husky to load `nvm` in some cases [#106](https://github.com/typicode/husky/issues/106) 37 | 38 | ## 0.13.2 39 | 40 | * Fixes issue [#103](https://github.com/typicode/husky/issues/103) 41 | 42 | ## 0.13.1 43 | 44 | * Makes it easier for projects to transition from [ghooks](https://github.com/gtramontina/ghooks) by detecting ghooks installed scripts and automatically migrating them 45 | 46 | ## 0.13.0 47 | 48 | * Makes `husky` a little less verbose by default 49 | * Fixes issue with `OS X + brew` where `nvm` was loaded even when `npm` was already present 50 | * Fixes issue with Git `v1.9` on Windows 51 | * Prevents Git hooks being installed when husky is in a sub `node_modules` directory (i.e. `./node_modules/A/node_modules/husky`) 52 | 53 | ## 0.12.0 54 | 55 | * Adds Git submodule support 56 | * Adds Cygwin support 57 | * Improves edge cases support (`.git` not found and `git` not in `PATH`) 58 | * If `npm` is already present in path, doesn't load `nvm` default or `.nvmrc` version, which makes things faster in terminal. In GUI apps, the behavior is unchanged. 59 | -------------------------------------------------------------------------------- /src/utils/get-hook-script.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const normalize = require('normalize-path') 4 | const stripIndent = require('strip-indent') 5 | const pkg = require('../../package.json') 6 | 7 | function platformSpecific() { 8 | // On OS X and Linux, try to use nvm if it's installed 9 | if (process.platform === 'win32') { 10 | // Add 11 | // Node standard installation path /c/Program Files/nodejs 12 | // for GUI apps 13 | // https://github.com/typicode/yorkie/issues/49 14 | return stripIndent( 15 | ` 16 | # Node standard installation 17 | export PATH="$PATH:/c/Program Files/nodejs"` 18 | ) 19 | } else { 20 | // Using normalize to support ' in path 21 | // https://github.com/typicode/yorkie/issues/117 22 | const home = normalize(process.env.HOME) 23 | 24 | return stripIndent( 25 | ` 26 | # Add common path where Node can be found 27 | # Brew standard installation path /usr/local/bin 28 | # Node standard installation path /usr/local 29 | export PATH="$PATH:/usr/local/bin:/usr/local" 30 | 31 | # Try to load nvm using path of standard installation 32 | load_nvm ${home}/.nvm 33 | run_nvm` 34 | ) 35 | 36 | return arr.join('\n') 37 | } 38 | } 39 | 40 | module.exports = function getHookScript(hookName, relativePath, runnerPath) { 41 | // On Windows normalize path (i.e. convert \ to /) 42 | const normalizedPath = normalize(relativePath) 43 | 44 | const noVerifyMessage = 45 | hookName === 'prepare-commit-msg' 46 | ? '(cannot be bypassed with --no-verify due to Git specs)' 47 | : '(add --no-verify to bypass)' 48 | 49 | return [ 50 | stripIndent( 51 | ` 52 | #!/bin/sh 53 | #yorkie ${pkg.version} 54 | 55 | command_exists () { 56 | command -v "$1" >/dev/null 2>&1 57 | } 58 | 59 | has_hook_script () { 60 | [ -f package.json ] && cat package.json | grep -q "\\"$1\\"[[:space:]]*:" 61 | } 62 | 63 | # OS X and Linux only 64 | load_nvm () { 65 | # If nvm is not loaded, load it 66 | command_exists nvm || { 67 | export NVM_DIR="$1" 68 | [ -s "$1/nvm.sh" ] && . "$1/nvm.sh" 69 | } 70 | } 71 | 72 | # OS X and Linux only 73 | run_nvm () { 74 | # If nvm has been loaded correctly, use project .nvmrc 75 | command_exists nvm && [ -f .nvmrc ] && nvm use 76 | } 77 | 78 | cd "${normalizedPath}" 79 | 80 | # Check if ${hookName} is defined, skip if not 81 | has_hook_script ${hookName} || exit 0` 82 | ).trim(), 83 | 84 | platformSpecific(), 85 | 86 | stripIndent( 87 | ` 88 | # Export Git hook params 89 | export GIT_PARAMS="$*" 90 | 91 | # Run hook 92 | node "${runnerPath}" ${hookName} || { 93 | echo 94 | echo "${hookName} hook failed ${noVerifyMessage}" 95 | exit 1 96 | } 97 | ` 98 | ) 99 | ].join('\n') 100 | } 101 | -------------------------------------------------------------------------------- /src/install.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const fs = require('fs') 4 | const path = require('path') 5 | const findParent = require('./utils/find-parent') 6 | const findHooksDir = require('./utils/find-hooks-dir') 7 | const getHookScript = require('./utils/get-hook-script') 8 | const is = require('./utils/is') 9 | const hooks = require('./hooks.json') 10 | 11 | const SKIP = 'SKIP' 12 | const UPDATE = 'UPDATE' 13 | const MIGRATE_GHOOKS = 'MIGRATE_GHOOKS' 14 | const MIGRATE_PRE_COMMIT = 'MIGRATE_PRE_COMMIT' 15 | const CREATE = 'CREATE' 16 | 17 | function write(filename, data) { 18 | fs.writeFileSync(filename, data) 19 | fs.chmodSync(filename, parseInt('0755', 8)) 20 | } 21 | 22 | function createHook(depDir, projectDir, hooksDir, hookName, runnerPath) { 23 | const filename = path.join(hooksDir, hookName) 24 | 25 | let packageDir 26 | // prioritize package.json next to .git 27 | // this avoids double-install in lerna monorepos where both root and sub 28 | // package depends on this module 29 | if (fs.existsSync(path.join(projectDir, 'package.json'))) { 30 | packageDir = projectDir 31 | } else { 32 | packageDir = findParent(depDir, 'package.json') 33 | } 34 | 35 | // In order to support projects with package.json in a different directory 36 | // than .git, find relative path from project directory to package.json 37 | const relativePath = path.join('.', path.relative(projectDir, packageDir)) 38 | 39 | const hookScript = getHookScript(hookName, relativePath, runnerPath) 40 | 41 | // Create hooks directory if needed 42 | if (!fs.existsSync(hooksDir)) fs.mkdirSync(hooksDir) 43 | 44 | if (!fs.existsSync(filename)) { 45 | write(filename, hookScript) 46 | return CREATE 47 | } 48 | 49 | if (is.ghooks(filename)) { 50 | write(filename, hookScript) 51 | return MIGRATE_GHOOKS 52 | } 53 | 54 | if (is.preCommit(filename)) { 55 | write(filename, hookScript) 56 | return MIGRATE_PRE_COMMIT 57 | } 58 | 59 | if (is.huskyOrYorkie(filename)) { 60 | write(filename, hookScript) 61 | return UPDATE 62 | } 63 | 64 | return SKIP 65 | } 66 | 67 | function installFrom(depDir) { 68 | try { 69 | const isInSubNodeModule = (depDir.match(/node_modules/g) || []).length > 1 70 | if (isInSubNodeModule) { 71 | return console.log( 72 | "trying to install from sub 'node_module' directory,", 73 | 'skipping Git hooks installation' 74 | ) 75 | } 76 | 77 | const projectDir = findParent(depDir, 'package.json') 78 | const hooksDir = findHooksDir(projectDir) 79 | const runnerPath = './node_modules/yorkie/src/runner.js' 80 | 81 | if (hooksDir) { 82 | hooks 83 | .map(function(hookName) { 84 | return { 85 | hookName: hookName, 86 | action: createHook(depDir, projectDir, hooksDir, hookName, runnerPath) 87 | } 88 | }) 89 | .forEach(function(item) { 90 | switch (item.action) { 91 | case MIGRATE_GHOOKS: 92 | console.log(`migrating existing ghooks ${item.hookName} script`) 93 | break 94 | case MIGRATE_PRE_COMMIT: 95 | console.log( 96 | `migrating existing pre-commit ${item.hookName} script` 97 | ) 98 | break 99 | case UPDATE: 100 | break 101 | case SKIP: 102 | console.log(`skipping ${item.hookName} hook (existing user hook)`) 103 | break 104 | case CREATE: 105 | break 106 | default: 107 | console.error('Unknown action') 108 | } 109 | }) 110 | console.log('done\n') 111 | } else { 112 | console.log("can't find .git directory, skipping Git hooks installation") 113 | } 114 | } catch (e) { 115 | console.error(e) 116 | } 117 | } 118 | 119 | module.exports = installFrom 120 | -------------------------------------------------------------------------------- /__tests__/index.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const fs = require('fs') 4 | const path = require('path') 5 | const mkdirp = require('mkdirp') 6 | const rimraf = require('rimraf') 7 | const tempy = require('tempy') 8 | const installFrom = require('../src/install') 9 | const uninstallFrom = require('../src/uninstall') 10 | 11 | function install(rootDir, dir) { 12 | installFrom(path.join(rootDir, dir)) 13 | } 14 | 15 | function uninstall(rootDir, dir) { 16 | uninstallFrom(path.join(rootDir, dir)) 17 | } 18 | 19 | function mkdir(rootDir, dir) { 20 | mkdirp.sync(path.join(rootDir, dir)) 21 | } 22 | 23 | function writeFile(dir, filePath, data) { 24 | fs.writeFileSync(path.join(dir, filePath), data) 25 | } 26 | 27 | function readFile(dir, filePath) { 28 | return fs.readFileSync(path.join(dir, filePath), 'utf-8') 29 | } 30 | 31 | function exists(dir, filePath) { 32 | return fs.existsSync(path.join(dir, filePath)) 33 | } 34 | 35 | describe('yorkie', () => { 36 | let dir 37 | beforeEach(() => (dir = tempy.directory())) 38 | afterEach(() => rimraf.sync(dir)) 39 | 40 | it('should support basic layout', () => { 41 | mkdir(dir, '.git/hooks') 42 | mkdir(dir, 'node_modules/yorkie') 43 | writeFile(dir, 'package.json', '{}') 44 | 45 | install(dir, '/node_modules/yorkie') 46 | const hook = readFile(dir, '.git/hooks/pre-commit') 47 | 48 | expect(hook).toMatch('#yorkie') 49 | expect(hook).toMatch('cd "."') 50 | expect(hook).toMatch(`node "./node_modules/yorkie/src/runner.js" pre-commit`) 51 | expect(hook).toMatch('--no-verify') 52 | 53 | const prepareCommitMsg = readFile(dir, '.git/hooks/prepare-commit-msg') 54 | expect(prepareCommitMsg).toMatch('cannot be bypassed') 55 | 56 | uninstall(dir, 'node_modules/yorkie') 57 | expect(exists(dir, '.git/hooks/pre-push')).toBeFalsy() 58 | }) 59 | 60 | it('should not install git hooks when installed in sub directory', () => { 61 | mkdir(dir, '.git/hooks') 62 | mkdir(dir, 'A/B/node_modules/yorkie') 63 | writeFile(dir, 'A/B/package.json', '{}') 64 | 65 | install(dir, 'A/B/node_modules/yorkie') 66 | expect(exists(dir, '.git/hooks/pre-commit')).toBeFalsy() 67 | }) 68 | 69 | it('should support git submodule', () => { 70 | mkdir(dir, '.git/modules/A/B') 71 | mkdir(dir, 'A/B/node_modules/yorkie') 72 | writeFile(dir, 'package.json', '{}') 73 | writeFile(dir, 'A/B/package.json', '{}') 74 | writeFile(dir, 'A/B/.git', 'git: ../../.git/modules/A/B') 75 | 76 | install(dir, 'A/B/node_modules/yorkie') 77 | const hook = readFile(dir, '.git/modules/A/B/hooks/pre-commit') 78 | 79 | expect(hook).toMatch('cd "."') 80 | 81 | uninstall(dir, 'A/B/node_modules/yorkie') 82 | expect(exists(dir, '.git/hooks/pre-push')).toBeFalsy() 83 | }) 84 | 85 | it('should not install git hooks in submodule sub directory', () => { 86 | mkdir(dir, '.git/modules/A/B') 87 | mkdir(dir, 'A/B/C/node_modules/yorkie') 88 | writeFile(dir, 'package.json', '{}') 89 | writeFile(dir, 'A/B/C/package.json', '{}') 90 | writeFile(dir, 'A/B/.git', 'git: ../../.git/modules/A/B') 91 | 92 | install(dir, 'A/B/C/node_modules/yorkie') 93 | expect(exists(dir, '.git/modules/A/B/hooks/pre-commit')).toBeFalsy() 94 | }) 95 | 96 | it('should support git worktrees', () => { 97 | mkdir(dir, '.git/worktrees/B') 98 | mkdir(dir, 'A/B/node_modules/yorkie') 99 | writeFile(dir, 'package.json', '{}') 100 | writeFile(dir, 'A/B/package.json', '{}') 101 | 102 | // Git path for worktrees is absolute 103 | const absolutePath = path.join(dir, '.git/worktrees/B') 104 | writeFile(dir, 'A/B/.git', `git: ${absolutePath}`) 105 | 106 | install(dir, 'A/B/node_modules/yorkie') 107 | const hook = readFile(dir, '.git/worktrees/B/hooks/pre-commit') 108 | 109 | expect(hook).toMatch('cd "."') 110 | 111 | uninstall(dir, 'A/B/node_modules/yorkie') 112 | expect(exists(dir, '.git/hooks/pre-commit')).toBeFalsy() 113 | }) 114 | 115 | it('should not modify user hooks', () => { 116 | mkdir(dir, '.git/hooks') 117 | mkdir(dir, 'node_modules/yorkie') 118 | writeFile(dir, '.git/hooks/pre-push', 'foo') 119 | 120 | // Verify that it's not overwritten 121 | install(dir, 'node_modules/yorkie') 122 | const hook = readFile(dir, '.git/hooks/pre-push') 123 | expect(hook).toBe('foo') 124 | 125 | uninstall(dir, 'node_modules/yorkie') 126 | expect(exists(dir, '.git/hooks/pre-push')).toBeTruthy() 127 | }) 128 | 129 | it('should not install from /node_modules/A/node_modules', () => { 130 | mkdir(dir, '.git/hooks') 131 | mkdir(dir, 'node_modules/A/node_modules/yorkie') 132 | 133 | install(dir, 'node_modules/A/node_modules/yorkie') 134 | expect(exists(dir, '.git/hooks/pre-push')).toBeFalsy() 135 | }) 136 | 137 | it("should not crash if there's no .git directory", () => { 138 | mkdir(dir, 'node_modules/yorkie') 139 | 140 | expect(() => install(dir, 'node_modules/yorkie')).not.toThrow() 141 | expect(() => uninstall(dir, 'node_modules/yorkie')).not.toThrow() 142 | }) 143 | 144 | it('should migrate existing scripts (ghooks)', () => { 145 | mkdir(dir, '.git/hooks') 146 | writeFile(dir, 'package.json', '{}') 147 | mkdir(dir, '/node_modules/yorkie') 148 | writeFile( 149 | dir, 150 | '.git/hooks/pre-commit', 151 | '// Generated by ghooks. Do not edit this file.' 152 | ) 153 | 154 | install(dir, 'node_modules/yorkie') 155 | const hook = readFile(dir, '.git/hooks/pre-commit') 156 | expect(hook).toMatch('yorkie') 157 | expect(hook).not.toMatch('ghooks') 158 | }) 159 | 160 | it('should migrate existing scripts (pre-commit)', () => { 161 | mkdir(dir, '.git/hooks') 162 | writeFile(dir, 'package.json', '{}') 163 | mkdir(dir, '/node_modules/yorkie') 164 | writeFile(dir, '.git/hooks/pre-commit', './node_modules/pre-commit/hook') 165 | 166 | install(dir, 'node_modules/yorkie') 167 | const hook = readFile(dir, '.git/hooks/pre-commit') 168 | expect(hook).toMatch('yorkie') 169 | expect(hook).not.toMatch('./node_modules/pre-commit/hook') 170 | }) 171 | }) 172 | --------------------------------------------------------------------------------