Skip to content

Commit

Permalink
Merge pull request #876 from johnnywalker/allow-spaces-in-partially-s…
Browse files Browse the repository at this point in the history
…taged

Use machine output for listing partially staged files
  • Loading branch information
iiroj committed May 29, 2020
2 parents 979da5d + 390d168 commit 21a2b41
Show file tree
Hide file tree
Showing 2 changed files with 68 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
51 changes: 51 additions & 0 deletions test/gitWorkflow.spec.js
Expand Up @@ -19,6 +19,8 @@ let tmpDir, cwd
const appendFile = async (filename, content, dir = cwd) =>
fs.appendFile(path.resolve(dir, filename), content)

const readFile = async (filename, dir = cwd) => fs.readFile(path.resolve(dir, filename))

/** Wrap execGit to always pass `gitOps` */
const execGit = async (args) => execGitBase(args, { cwd })

Expand Down Expand Up @@ -102,6 +104,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 All @@ -127,6 +164,20 @@ describe('gitWorkflow', () => {
}
`)
})
it('should checkout renamed file when hiding changes', async () => {
const gitWorkflow = new GitWorkflow({
gitDir: cwd,
gitConfigDir: path.resolve(cwd, './.git'),
})
const origContent = await readFile('README.md')
await execGit(['mv', 'README.md', 'TEST.md'])
await appendFile('TEST.md', 'added content')

gitWorkflow.partiallyStagedFiles = await gitWorkflow.getPartiallyStagedFiles()
const ctx = getInitialState()
await gitWorkflow.hideUnstagedChanges(ctx)
expect(await readFile('TEST.md')).toStrictEqual(origContent)
})
})

describe('restoreMergeStatus', () => {
Expand Down

0 comments on commit 21a2b41

Please sign in to comment.