diff --git a/lib/resolveGitRepo.js b/lib/resolveGitRepo.js index 5ee5d4107..fa064986a 100644 --- a/lib/resolveGitRepo.js +++ b/lib/resolveGitRepo.js @@ -1,48 +1,48 @@ 'use strict' const normalize = require('normalize-path') +const fs = require('fs').promises const path = require('path') +const debugLog = require('debug')('lint-staged:resolveGitRepo') + const execGit = require('./execGit') const { readBufferFromFile } = require('./file') /** * Resolve path to the .git directory, with special handling for - * submodules + * submodules and worktrees */ -const resolveGitConfigDir = async ({ gitDir, isSubmodule }) => { +const resolveGitConfigDir = async gitDir => { const defaultDir = path.resolve(gitDir, '.git') - if (!isSubmodule) return normalize(defaultDir) - - const buffer = await readBufferFromFile(defaultDir) - const dotGit = buffer.toString() - const gitConfigDir = path.resolve(gitDir, dotGit.replace(/^gitdir: /, '').trim()) - return normalize(gitConfigDir) + const stats = await fs.lstat(defaultDir) + // If .git is a directory, use it + if (stats.isDirectory()) return defaultDir + // Otherwise .git is a file containing path to real location + const file = (await readBufferFromFile(defaultDir)).toString() + return path.resolve(gitDir, file.replace(/^gitdir: /, '')).trim() } /** * Resolve git directory and possible submodule paths */ -module.exports = async function resolveGitRepo(options = {}) { +module.exports = async function resolveGitRepo(cwd) { try { + debugLog('Resolving git repo from `%s`', cwd) // git cli uses GIT_DIR to fast track its response however it might be set to a different path // depending on where the caller initiated this from, hence clear GIT_DIR + debugLog('Deleting GIT_DIR from env with value `%s`', process.env.GIT_DIR) delete process.env.GIT_DIR - // The git repo root directory; this points to the root of a submodule instead of the parent - const gitDir = await execGit(['rev-parse', '--show-toplevel'], options) - - // A super-project working tree exists only in submodules; poinst to the parent root - const superprojectWorkingTree = await execGit( - ['rev-parse', '--show-superproject-working-tree'], - options - ) + const gitDir = normalize(await execGit(['rev-parse', '--show-toplevel'], { cwd })) + const gitConfigDir = normalize(await resolveGitConfigDir(gitDir)) - const isSubmodule = !!superprojectWorkingTree - const gitConfigDir = await resolveGitConfigDir({ gitDir, isSubmodule }) + debugLog('Resolved git directory to be `%s`', gitDir) + debugLog('Resolved git config directory to be `%s`', gitConfigDir) - return { gitDir: normalize(gitDir), gitConfigDir, isSubmodule } + return { gitDir, gitConfigDir } } catch (error) { - return { error, gitDir: null } + debugLog('Failed to resolve git repo with error:', error) + return { error, gitDir: null, gitConfigDir: null } } } diff --git a/lib/runAll.js b/lib/runAll.js index 3a4f0e41c..8d3a9ae37 100644 --- a/lib/runAll.js +++ b/lib/runAll.js @@ -55,11 +55,8 @@ module.exports = async function runAll( ) { debugLog('Running all linter scripts') - const { gitDir, gitConfigDir, isSubmodule } = await resolveGitRepo({ cwd }) + const { gitDir, gitConfigDir } = await resolveGitRepo(cwd) if (!gitDir) throw new Error('Current directory is not a git directory!') - debugLog('Resolved git directory to be `%s`', gitDir) - debugLog('Resolved git config directory to be `%s`', gitConfigDir) - if (isSubmodule) debugLog('Current git directory is a submodule') const files = await getStagedFiles({ cwd: gitDir }) if (!files) throw new Error('Unable to get staged files!') diff --git a/test/runAll.unmocked.spec.js b/test/runAll.unmocked.spec.js index 0c6233fd6..fd96f6502 100644 --- a/test/runAll.unmocked.spec.js +++ b/test/runAll.unmocked.spec.js @@ -777,6 +777,30 @@ describe('runAll', () => { expect(await execGit(['log', '-1', '--pretty=%B'], { cwd: submoduleDir })).toMatch('test') expect(await readFile('test.js', submoduleDir)).toEqual(testJsFilePretty) }) + + it('should handle git worktrees', async () => { + // create a new branch and add it as worktree + const workTreeDir = path.resolve(cwd, 'worktree') + await execGit(['branch', 'test']) + await execGit(['worktree', 'add', workTreeDir, 'test']) + + // Stage pretty file + await appendFile('test.js', testJsFilePretty, workTreeDir) + await execGit(['add', 'test.js'], { cwd: workTreeDir }) + + // Run lint-staged with `prettier --list-different` and commit pretty file + await runAll({ + config: { '*.js': 'prettier --list-different' }, + cwd: workTreeDir, + quiet: true + }) + await execGit(['commit', '-m test'], { cwd: workTreeDir }) + + // Nothing is wrong, so a new commit is created + expect(await execGit(['rev-list', '--count', 'HEAD'], { cwd: workTreeDir })).toEqual('2') + expect(await execGit(['log', '-1', '--pretty=%B'], { cwd: workTreeDir })).toMatch('test') + expect(await readFile('test.js', workTreeDir)).toEqual(testJsFilePretty) + }) }) describe('runAll', () => {