diff --git a/lib/gitWorkflow.js b/lib/gitWorkflow.js index 56f3d2b0d..f83d2a293 100644 --- a/lib/gitWorkflow.js +++ b/lib/gitWorkflow.js @@ -3,6 +3,7 @@ const debug = require('debug')('lint-staged:git') const path = require('path') +const chunkFiles = require('./chunkFiles') const execGit = require('./execGit') const { readFile, unlink, writeFile } = require('./file') @@ -52,14 +53,15 @@ const handleError = (error, ctx) => { } class GitWorkflow { - constructor({ allowEmpty, gitConfigDir, gitDir, stagedFileChunks }) { + constructor({ allowEmpty, gitConfigDir, gitDir, matchedFiles, maxArgLength }) { this.execGit = (args, options = {}) => execGit(args, { ...options, cwd: gitDir }) this.deletedFiles = [] this.gitConfigDir = gitConfigDir this.gitDir = gitDir this.unstagedDiff = null this.allowEmpty = allowEmpty - this.stagedFileChunks = stagedFileChunks + this.matchedFiles = matchedFiles + this.maxArgLength = maxArgLength /** * These three files hold state about an ongoing git merge @@ -235,12 +237,22 @@ class GitWorkflow { */ async applyModifications(ctx) { debug('Adding task modifications to index...') - // stagedFileChunks includes staged files that lint-staged originally detected. + // `matchedFiles` includes staged files that lint-staged originally detected and matched against a task. // Add only these files so any 3rd-party edits to other files won't be included in the commit. - // This is run "serially" to prevent race conditions because `git add` is a locking operation. - for (const stagedFiles of this.stagedFileChunks) { - await this.execGit(['add', '--', ...stagedFiles]) + const files = Array.from(this.matchedFiles) + // Chunk files for better Windows compatibility + const matchedFileChunks = chunkFiles({ + baseDir: this.gitDir, + files, + maxArgLength: this.maxArgLength + }) + + // These additions per chunk are run "serially" to prevent race conditions. + // Git add creates a lockfile in the repo causing concurrent operations to fail. + for (const files of matchedFileChunks) { + await this.execGit(['add', '--', ...files]) } + debug('Done adding task modifications to index!') const stagedFilesAfterAdd = await this.execGit(['diff', '--name-only', '--cached']) diff --git a/lib/runAll.js b/lib/runAll.js index 8d41aaf4b..59a44f7a8 100644 --- a/lib/runAll.js +++ b/lib/runAll.js @@ -125,6 +125,9 @@ const runAll = async ( const listrTasks = [] + // Set of all staged files that matched a task glob. Values in a set are unique. + const matchedFiles = new Set() + for (const [index, files] of stagedFileChunks.entries()) { const chunkTasks = generateTasks({ config, cwd, gitDir, files, relative }) const chunkListrTasks = [] @@ -137,6 +140,11 @@ const runAll = async ( shell }) + // Add files from task to match set + task.fileList.forEach(file => { + matchedFiles.add(file) + }) + hasDeprecatedGitAdd = subTasks.some(subTask => subTask.command === 'git add') chunkListrTasks.push({ @@ -188,7 +196,7 @@ const runAll = async ( return 'No tasks to run.' } - const git = new GitWorkflow({ allowEmpty, gitConfigDir, gitDir, stagedFileChunks }) + const git = new GitWorkflow({ allowEmpty, gitConfigDir, gitDir, matchedFiles, maxArgLength }) const runner = new Listr( [