Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 10 additions & 10 deletions src/vs/workbench/services/search/common/ignoreFile.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
*--------------------------------------------------------------------------------------------*/

import * as glob from '../../../../base/common/glob.js';

import { startsWithIgnoreCase } from '../../../../base/common/strings.js';

export class IgnoreFile {

Expand All @@ -13,7 +13,8 @@ export class IgnoreFile {
constructor(
contents: string,
private readonly location: string,
private readonly parent?: IgnoreFile) {
private readonly parent?: IgnoreFile,
private readonly ignoreCase = false) {
if (location[location.length - 1] === '\\') {
throw Error('Unexpected path format, do not use trailing backslashes');
}
Expand All @@ -24,7 +25,7 @@ export class IgnoreFile {
}

/**
* Updates the contents of the ignorefile. Preservering the location and parent
* Updates the contents of the ignore file. Preserving the location and parent
* @param contents The new contents of the gitignore file
*/
updateContents(contents: string) {
Expand All @@ -41,7 +42,7 @@ export class IgnoreFile {
*/
isPathIncludedInTraversal(path: string, isDir: boolean): boolean {
if (path[0] !== '/' || path[path.length - 1] === '/') {
throw Error('Unexpected path format, expectred to begin with slash and end without. got:' + path);
throw Error('Unexpected path format, expected to begin with slash and end without. got:' + path);
}

const ignored = this.isPathIgnored(path, isDir);
Expand All @@ -51,11 +52,11 @@ export class IgnoreFile {

/**
* Returns true if an arbitrary path has not been ignored.
* This is an expensive operation and should only be used ouside of traversals.
* This is an expensive operation and should only be used outside of traversals.
*/
isArbitraryPathIgnored(path: string, isDir: boolean): boolean {
if (path[0] !== '/' || path[path.length - 1] === '/') {
throw Error('Unexpected path format, expectred to begin with slash and end without. got:' + path);
throw Error('Unexpected path format, expected to begin with slash and end without. got:' + path);
}

const segments = path.split('/').filter(x => x);
Expand Down Expand Up @@ -86,10 +87,9 @@ export class IgnoreFile {
includeExpression[line] = true;
}

return glob.parse(includeExpression, { trimForExclusions });
return glob.parse(includeExpression, { trimForExclusions, ignoreCase: this.ignoreCase });
}


private parseIgnoreFile(ignoreContents: string, dirPath: string, parent: IgnoreFile | undefined): (path: string, isDir: boolean) => boolean {
const contentLines = ignoreContents
.split('\n')
Expand All @@ -102,7 +102,7 @@ export class IgnoreFile {
const fileIgnoreLines = fileLines.filter(line => !line.includes('!'));
const isFileIgnored = this.gitignoreLinesToExpression(fileIgnoreLines, dirPath, true);

// TODO: Slight hack... this naieve approach may reintroduce too many files in cases of weirdly complex .gitignores
// TODO: Slight hack... this naive approach may reintroduce too many files in cases of weirdly complex .gitignores
const fileIncludeLines = fileLines.filter(line => line.includes('!')).map(line => line.replace(/!/g, ''));
const isFileIncluded = this.gitignoreLinesToExpression(fileIncludeLines, dirPath, false);

Expand All @@ -115,7 +115,7 @@ export class IgnoreFile {
const isDirIncluded = this.gitignoreLinesToExpression(dirIncludeLines, dirPath, false);

const isPathIgnored = (path: string, isDir: boolean) => {
if (!path.startsWith(dirPath)) { return false; }
if (!(this.ignoreCase ? startsWithIgnoreCase(path, dirPath) : path.startsWith(dirPath))) { return false; }
if (isDir && isDirIgnored(path) && !isDirIncluded(path)) { return true; }
if (isFileIgnored(path) && !isFileIncluded(path)) { return true; }

Expand Down
39 changes: 29 additions & 10 deletions src/vs/workbench/services/search/test/common/ignoreFile.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,12 +7,12 @@ import assert from 'assert';
import { ensureNoDisposablesAreLeakedInTestSuite } from '../../../../../base/test/common/utils.js';
import { IgnoreFile } from '../../common/ignoreFile.js';

function runAssert(input: string, ignoreFile: string, ignoreFileLocation: string, shouldMatch: boolean, traverse: boolean) {
function runAssert(input: string, ignoreFile: string, ignoreFileLocation: string, shouldMatch: boolean, traverse: boolean, ignoreCase: boolean) {
return (prefix: string) => {
const isDir = input.endsWith('/');
const rawInput = isDir ? input.slice(0, input.length - 1) : input;

const matcher = new IgnoreFile(ignoreFile, prefix + ignoreFileLocation);
const matcher = new IgnoreFile(ignoreFile, prefix + ignoreFileLocation, undefined, ignoreCase);
if (traverse) {
const traverses = matcher.isPathIncludedInTraversal(prefix + rawInput, isDir);

Expand All @@ -34,29 +34,29 @@ function runAssert(input: string, ignoreFile: string, ignoreFileLocation: string
};
}

function assertNoTraverses(ignoreFile: string, ignoreFileLocation: string, input: string) {
const runWithPrefix = runAssert(input, ignoreFile, ignoreFileLocation, false, true);
function assertNoTraverses(ignoreFile: string, ignoreFileLocation: string, input: string, ignoreCase = false) {
const runWithPrefix = runAssert(input, ignoreFile, ignoreFileLocation, false, true, ignoreCase);

runWithPrefix('');
runWithPrefix('/someFolder');
}

function assertTraverses(ignoreFile: string, ignoreFileLocation: string, input: string) {
const runWithPrefix = runAssert(input, ignoreFile, ignoreFileLocation, true, true);
function assertTraverses(ignoreFile: string, ignoreFileLocation: string, input: string, ignoreCase = false) {
const runWithPrefix = runAssert(input, ignoreFile, ignoreFileLocation, true, true, ignoreCase);

runWithPrefix('');
runWithPrefix('/someFolder');
}

function assertIgnoreMatch(ignoreFile: string, ignoreFileLocation: string, input: string) {
const runWithPrefix = runAssert(input, ignoreFile, ignoreFileLocation, true, false);
function assertIgnoreMatch(ignoreFile: string, ignoreFileLocation: string, input: string, ignoreCase = false) {
const runWithPrefix = runAssert(input, ignoreFile, ignoreFileLocation, true, false, ignoreCase);

runWithPrefix('');
runWithPrefix('/someFolder');
}

function assertNoIgnoreMatch(ignoreFile: string, ignoreFileLocation: string, input: string) {
const runWithPrefix = runAssert(input, ignoreFile, ignoreFileLocation, false, false);
function assertNoIgnoreMatch(ignoreFile: string, ignoreFileLocation: string, input: string, ignoreCase = false) {
const runWithPrefix = runAssert(input, ignoreFile, ignoreFileLocation, false, false, ignoreCase);

runWithPrefix('');
runWithPrefix('/someFolder');
Expand Down Expand Up @@ -566,4 +566,23 @@ suite('Parsing .gitignore files', () => {
});

});

test('case-insensitive ignore files', () => {
const f1 = 'node_modules/\n';
assertNoIgnoreMatch(f1, '/', '/Node_Modules/', false);
assertIgnoreMatch(f1, '/', '/Node_Modules/', true);

const f2 = 'NODE_MODULES/\n';
assertNoIgnoreMatch(f2, '/', '/Node_Modules/', false);
assertIgnoreMatch(f2, '/', '/Node_Modules/', true);

const f3 = `
temp/*
!temp/keep
`;
assertNoIgnoreMatch(f3, '/', '/TEMP/other', false);
assertIgnoreMatch(f3, '/', '/temp/KEEP', false);
assertIgnoreMatch(f3, '/', '/TEMP/other', true);
assertNoIgnoreMatch(f3, '/', '/TEMP/KEEP', true);
});
});
Loading