mirror of
https://github.com/cypress-io/cypress.git
synced 2026-04-29 03:09:53 -05:00
fix: Resolve Git Info when using alternative shells or Windows (#22741)
This commit is contained in:
@@ -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()
|
||||
|
||||
Reference in New Issue
Block a user