Skip to content

Commit

Permalink
fix: use machine output to avoid escaped and quoted filenames
Browse files Browse the repository at this point in the history
  • Loading branch information
johnnywalker committed May 28, 2020
1 parent 979da5d commit ea80a3d
Show file tree
Hide file tree
Showing 2 changed files with 52 additions and 11 deletions.
28 changes: 17 additions & 11 deletions lib/gitWorkflow.js
Expand Up @@ -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 {
Expand Down Expand Up @@ -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 !== '?'
})
Expand Down
35 changes: 35 additions & 0 deletions test/gitWorkflow.spec.js
Expand Up @@ -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({
Expand Down

0 comments on commit ea80a3d

Please sign in to comment.