fix: Resolve Git Info when using alternative shells or Windows (#22741)

This commit is contained in:
Mike Plummer
2022-07-15 23:55:21 +00:00
committed by GitHub
parent 7e4d82bfaf
commit 59c926ba77
2 changed files with 86 additions and 12 deletions
@@ -339,19 +339,21 @@ export class GitDataSource {
async #getInfoPosix (absolutePaths: readonly string[]) {
debug('getting git info for %o:', absolutePaths)
const paths = absolutePaths.map((x) => `"${path.resolve(x)}"`).join(',')
// Escape any quotes within the filepath, then surround with quotes
const paths = absolutePaths
.map((p) => `"${path.resolve(p).replace(/\"/g, '\\"')}"`).join(' ')
// for file in {one,two} is valid in bash, but for file {one} is not
// no need to use a for loop for a single file
// IFS is needed to handle paths with white space.
const cmd = absolutePaths.length === 1
? `${GIT_LOG_COMMAND} ${absolutePaths[0]}`
: `IFS=$'\n'; for file in {${paths}}; do echo $(${GIT_LOG_COMMAND} $file); done`
const cmd = paths.length === 1
? `${GIT_LOG_COMMAND} ${paths[0]}`
: `IFS=$'\n'; for file in ${paths}; do echo $(${GIT_LOG_COMMAND} $file); done`
debug('executing command `%s`:', cmd)
debug('gitBaseDir `%s`:', this.#gitBaseDir)
debug('executing command: `%s`', cmd)
debug('cwd: `%s`', this.#gitBaseDir)
const result = await execa(cmd, { shell: process.env.SHELL || '/bin/bash', cwd: this.#gitBaseDir })
const result = await execa(cmd, { shell: true, cwd: this.#gitBaseDir })
const stdout = result.stdout.split('\n')
if (result.exitCode !== 0) {
@@ -367,9 +369,21 @@ export class GitDataSource {
}
async #getInfoWindows (absolutePaths: readonly string[]) {
debug('getting git info for %o:', absolutePaths)
const paths = absolutePaths.map((x) => `"${path.resolve(x)}"`).join(',')
const cmd = `FOR %x in (${paths}) DO (${GIT_LOG_COMMAND} %x)`
const result = await execa(cmd, { shell: true, cwd: this.config.projectRoot })
debug('executing command: `%s`', cmd)
debug('cwd: `%s`', this.config.projectRoot)
const subprocess = execa(cmd, { shell: true, cwd: this.config.projectRoot })
let result
try {
result = await subprocess
} catch (err) {
result = err
}
const stdout = ensurePosixPathSeparators(result.stdout).split('\r\n') // windows uses CRLF for carriage returns
@@ -8,7 +8,8 @@ import pDefer from 'p-defer'
import chokidar from 'chokidar'
import { scaffoldMigrationProject } from '../helper'
import { GitDataSource } from '../../../src/sources/GitDataSource'
import { GitDataSource, GitInfo } from '../../../src/sources/GitDataSource'
import { toPosix } from '../../../src/util/file'
describe('GitDataSource', () => {
let git: ReturnType<typeof simpleGit>
@@ -49,9 +50,9 @@ describe('GitDataSource', () => {
// create a file and modify a file to express all
// git states we are interested in (created, unmodified, modified)
const fooSpec = path.join(e2eFolder, 'foo.cy.js')
const aRecordSpec = path.join(e2eFolder, 'a_record.cy.js')
const xhrSpec = path.join(e2eFolder, 'xhr.cy.js')
const fooSpec = toPosix(path.join(e2eFolder, 'foo.cy.js'))
const aRecordSpec = toPosix(path.join(e2eFolder, 'a_record.cy.js'))
const xhrSpec = toPosix(path.join(e2eFolder, 'xhr.cy.js'))
gitInfo = new GitDataSource({
isRunMode: false,
@@ -99,6 +100,65 @@ describe('GitDataSource', () => {
expect(modified.lastModifiedTimestamp).not.to.be.undefined
})
it(`handles files with special characters on ${os.platform()}`, async () => {
// Validates handling of edge cases from https://github.com/cypress-io/cypress/issues/22454
let filepaths = [
'file withSpace.cy.js',
'file~WithTilde.cy.js',
'file-withHyphen.cy.js',
'file_withUnderscore.cy.js',
'file;WithSemicolon.cy.js',
'file,withComma.cy.js',
'file@withAtSymbol.cy.js',
'file^withCarat.cy.js',
'file=withEqual.cy.js',
'file+withPlus.cy.js',
'file\'withOneSingleQuote.cy.js',
]
if (os.platform() !== 'win32') {
// Double quote not a legal character on NTFS
filepaths.push('file"withOneDoubleQuote.cy.js')
}
filepaths = filepaths
.map((filename) => path.join(e2eFolder, filename))
.map((filepath) => toPosix(filepath))
gitInfo = new GitDataSource({
isRunMode: false,
projectRoot: projectPath,
onBranchChange: sinon.stub(),
onGitInfoChange: sinon.stub(),
onError: sinon.stub(),
})
for (let filepath of filepaths) {
fs.createFileSync(filepath)
}
gitInfo.setSpecs(filepaths)
let results: (GitInfo | null)[] = []
do {
results = await Promise.all(filepaths.map(function (filepath) {
return gitInfo.gitInfoFor(filepath)
}))
await new Promise((resolve) => setTimeout(resolve, 100))
} while (results.some((r) => r == null))
expect(results).to.have.lengthOf(filepaths.length)
filepaths.forEach((filepath, index) => {
const result = results[index]
expect(result?.lastModifiedHumanReadable).to.match(/(a few|[0-9]) seconds? ago/)
expect(result?.statusType).to.eql('created')
})
})
it(`watches switching branches on ${os.platform()}`, async () => {
const stub = sinon.stub()
const dfd = pDefer()