diff --git a/lib/gitWorkflow.js b/lib/gitWorkflow.js index bf24e1436..a5838ab3e 100644 --- a/lib/gitWorkflow.js +++ b/lib/gitWorkflow.js @@ -19,19 +19,20 @@ const MERGE_HEAD = 'MERGE_HEAD' const MERGE_MODE = 'MERGE_MODE' const MERGE_MSG = 'MERGE_MSG' -// In git status, renames are presented as `from` -> `to`. +// In git status machine output, renames are presented as `to`NUL`from` // When diffing, both need to be taken into account, but in some cases on the `to`. -const RENAME = / -> / +// eslint-disable-next-line no-control-regex +const RENAME = /\x00/ /** - * From list of files, split renames and flatten into two files `from` -> `to`. + * From list of files, split renames and flatten into two files `to`NUL`from`. * @param {string[]} files * @param {Boolean} [includeRenameFrom=true] Whether or not to include the `from` renamed file, which is no longer on disk */ const processRenames = (files, includeRenameFrom = true) => files.reduce((flattened, file) => { if (RENAME.test(file)) { - const [from, to] = file.split(RENAME) + const [to, from] = file.split(RENAME) if (includeRenameFrom) flattened.push(from) flattened.push(to) } else { @@ -158,15 +159,20 @@ class GitWorkflow { */ async getPartiallyStagedFiles() { debug('Getting partially staged files...') - const status = await this.execGit(['status', '--porcelain']) + const status = await this.execGit(['status', '-z']) + /** + * See https://git-scm.com/docs/git-status#_short_format + * Entries returned in machine format are separated by a NUL character. + * The first letter of each entry represents current index status, + * and second the working tree. Index and working tree status codes are + * separated from the file name by a space. If an entry includes a + * renamed file, the file names are separated by a NUL character + * (e.g. `to`\0`from`) + */ const partiallyStaged = status - .split('\n') + // eslint-disable-next-line no-control-regex + .split(/\x00(?=[ AMDRCU?!]{2} |$)/) .filter((line) => { - /** - * See https://git-scm.com/docs/git-status#_short_format - * The first letter of the line represents current index status, - * and second the working tree - */ const [index, workingTree] = line return index !== ' ' && workingTree !== ' ' && index !== '?' && workingTree !== '?' }) diff --git a/test/gitWorkflow.spec.js b/test/gitWorkflow.spec.js index 01ab41eda..7ab711871 100644 --- a/test/gitWorkflow.spec.js +++ b/test/gitWorkflow.spec.js @@ -102,6 +102,41 @@ describe('gitWorkflow', () => { }) }) + describe('getPartiallyStagedFiles', () => { + it('should return unquoted files', async () => { + const gitWorkflow = new GitWorkflow({ + gitDir: cwd, + gitConfigDir: path.resolve(cwd, './.git'), + }) + await appendFile('file with spaces.txt', 'staged content') + await appendFile('file_without_spaces.txt', 'staged content') + await execGit(['add', 'file with spaces.txt']) + await execGit(['add', 'file_without_spaces.txt']) + await appendFile('file with spaces.txt', 'not staged content') + await appendFile('file_without_spaces.txt', 'not staged content') + + expect(await gitWorkflow.getPartiallyStagedFiles()).toStrictEqual([ + 'file with spaces.txt', + 'file_without_spaces.txt', + ]) + }) + it('should include to and from for renamed files', async () => { + const gitWorkflow = new GitWorkflow({ + gitDir: cwd, + gitConfigDir: path.resolve(cwd, './.git'), + }) + await appendFile('original.txt', 'test content') + await execGit(['add', 'original.txt']) + await execGit(['commit', '-m "Add original.txt"']) + await appendFile('original.txt', 'additional content') + await execGit(['mv', 'original.txt', 'renamed.txt']) + + expect(await gitWorkflow.getPartiallyStagedFiles()).toStrictEqual([ + 'renamed.txt\u0000original.txt', + ]) + }) + }) + describe('hideUnstagedChanges', () => { it('should handle errors', async () => { const gitWorkflow = new GitWorkflow({