fix(core): git hashing handles "unusual" pathname characters
See: https://git-scm.com/docs/git-status#_pathname_format_notes_and_z
This commit is contained in:
parent
9d863a7073
commit
26a383912a
@ -1,5 +1,5 @@
|
|||||||
import { dirSync } from 'tmp';
|
import { dirSync } from 'tmp';
|
||||||
import { rmdirSync } from 'fs-extra';
|
import { removeSync } from 'fs-extra';
|
||||||
import { execSync } from 'child_process';
|
import { execSync } from 'child_process';
|
||||||
import { getFileHashes } from './git-hasher';
|
import { getFileHashes } from './git-hasher';
|
||||||
|
|
||||||
@ -14,7 +14,7 @@ describe('git-hasher', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
afterEach(() => {
|
afterEach(() => {
|
||||||
rmdirSync(dir, { recursive: true });
|
removeSync(dir);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should work', () => {
|
it('should work', () => {
|
||||||
@ -112,6 +112,28 @@ describe('git-hasher', () => {
|
|||||||
expect([...getFileHashes(dir).keys()]).toEqual([`${dir}/moda.txt`]);
|
expect([...getFileHashes(dir).keys()]).toEqual([`${dir}/moda.txt`]);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('should handle special characters in filenames', () => {
|
||||||
|
run(`echo AAA > "a-ū".txt`);
|
||||||
|
run(`echo BBB > "b-ū".txt`);
|
||||||
|
run(`git add .`);
|
||||||
|
run(`git commit -am init`);
|
||||||
|
expect([...getFileHashes(dir).keys()]).toEqual([
|
||||||
|
`${dir}/a-ū.txt`,
|
||||||
|
`${dir}/b-ū.txt`,
|
||||||
|
]);
|
||||||
|
|
||||||
|
run(`mv a-ū.txt moda-ū.txt`);
|
||||||
|
run(`git add .`);
|
||||||
|
run(`echo modified >> moda-ū.txt`);
|
||||||
|
expect([...getFileHashes(dir).keys()]).toEqual([
|
||||||
|
`${dir}/b-ū.txt`,
|
||||||
|
`${dir}/moda-ū.txt`,
|
||||||
|
]);
|
||||||
|
|
||||||
|
run(`rm "moda-ū.txt"`);
|
||||||
|
expect([...getFileHashes(dir).keys()]).toEqual([`${dir}/b-ū.txt`]);
|
||||||
|
});
|
||||||
|
|
||||||
function run(command: string) {
|
function run(command: string) {
|
||||||
return execSync(command, { cwd: dir, stdio: ['pipe', 'pipe', 'pipe'] });
|
return execSync(command, { cwd: dir, stdio: ['pipe', 'pipe', 'pipe'] });
|
||||||
}
|
}
|
||||||
|
|||||||
@ -6,7 +6,7 @@ function parseGitLsTree(output: string): Map<string, string> {
|
|||||||
const changes: Map<string, string> = new Map<string, string>();
|
const changes: Map<string, string> = new Map<string, string>();
|
||||||
if (output) {
|
if (output) {
|
||||||
const gitRegex: RegExp = /([0-9]{6})\s(blob|commit)\s([a-f0-9]{40})\s*(.*)/;
|
const gitRegex: RegExp = /([0-9]{6})\s(blob|commit)\s([a-f0-9]{40})\s*(.*)/;
|
||||||
output.split('\n').forEach((line) => {
|
output.split('\0').forEach((line) => {
|
||||||
if (line) {
|
if (line) {
|
||||||
const matches: RegExpMatchArray | null = line.match(gitRegex);
|
const matches: RegExpMatchArray | null = line.match(gitRegex);
|
||||||
if (matches && matches[3] && matches[4]) {
|
if (matches && matches[3] && matches[4]) {
|
||||||
@ -27,31 +27,18 @@ function parseGitStatus(output: string): Map<string, string> {
|
|||||||
if (!output) {
|
if (!output) {
|
||||||
return changes;
|
return changes;
|
||||||
}
|
}
|
||||||
output
|
|
||||||
.trim()
|
|
||||||
.split('\n')
|
|
||||||
.forEach((line) => {
|
|
||||||
const [changeType, ...filenames] = line
|
|
||||||
.trim()
|
|
||||||
.match(/(?:[^\s"]+|"[^"]*")+/g)
|
|
||||||
.map((r) => (r.startsWith('"') ? r.substring(1, r.length - 1) : r))
|
|
||||||
.filter((r) => !!r);
|
|
||||||
|
|
||||||
if (changeType && filenames && filenames.length > 0) {
|
var chunks = output.split('\0');
|
||||||
// the before filename we mark as deleted, so we remove it from the map
|
for (let i = 0; i < chunks.length; i++) {
|
||||||
// changeType can be A/D/R/RM etc
|
const chunk = chunks[i];
|
||||||
// if it R and RM, we need to split the output into before and after
|
if (chunk.length) {
|
||||||
// the before part gets marked as deleted
|
const change = chunk[0];
|
||||||
if (changeType[0] === 'R') {
|
if (change === 'R') {
|
||||||
changes.set(filenames[0], 'D');
|
changes.set(chunks[++i], 'D');
|
||||||
changes.set(filenames[filenames.length - 1], changeType);
|
|
||||||
} else if (changeType === '??') {
|
|
||||||
changes.set(filenames.join(' '), changeType);
|
|
||||||
} else {
|
|
||||||
changes.set(filenames[filenames.length - 1], changeType);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
});
|
changes.set(chunk.substring(2).trim(), change);
|
||||||
|
}
|
||||||
|
}
|
||||||
return changes;
|
return changes;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -92,7 +79,9 @@ function getGitHashForFiles(
|
|||||||
}
|
}
|
||||||
|
|
||||||
function gitLsTree(path: string): Map<string, string> {
|
function gitLsTree(path: string): Map<string, string> {
|
||||||
return parseGitLsTree(spawnProcess('git', ['ls-tree', 'HEAD', '-r'], path));
|
return parseGitLsTree(
|
||||||
|
spawnProcess('git', ['ls-tree', 'HEAD', '-r', '-z'], path)
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function gitStatus(
|
function gitStatus(
|
||||||
@ -101,7 +90,7 @@ function gitStatus(
|
|||||||
const deletedFiles: string[] = [];
|
const deletedFiles: string[] = [];
|
||||||
const filesToHash: string[] = [];
|
const filesToHash: string[] = [];
|
||||||
parseGitStatus(
|
parseGitStatus(
|
||||||
spawnProcess('git', ['status', '-s', '-u', '.'], path)
|
spawnProcess('git', ['status', '-s', '-u', '-z', '.'], path)
|
||||||
).forEach((changeType: string, filename: string) => {
|
).forEach((changeType: string, filename: string) => {
|
||||||
if (changeType !== 'D') {
|
if (changeType !== 'D') {
|
||||||
filesToHash.push(filename);
|
filesToHash.push(filename);
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user