Skip to content

Commit 052d208

Browse files
Merge pull request #2 from HichemTab-tech/remove-files-mentionned-in-gitignore-by-default
Uncheck files from .gitignore by default
2 parents 5b6aff8 + 107e4b1 commit 052d208

File tree

3 files changed

+567
-5
lines changed

3 files changed

+567
-5
lines changed

src/extension.ts

Lines changed: 31 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import * as vscode from 'vscode';
22
import * as fs from 'fs';
33
import * as path from 'path';
44
import { JetTreeMarkViewProvider } from './JetTreeMarkViewProvider';
5+
import { collectDirectoryGitignorePatterns, shouldExcludeByGitignore, GitignorePattern } from './gitignore-utils';
56

67
// noinspection JSUnusedGlobalSymbols
78
export function activate(ctx: vscode.ExtensionContext) {
@@ -43,9 +44,20 @@ interface TreeNodeType {
4344

4445
/**
4546
* Build a TreeNodeType *for* the directory itself, including its contents.
47+
* @param dir Directory path
48+
* @param parentPatterns Optional gitignore patterns from parent directories
49+
* @param forceUncheck
50+
* @returns TreeNodeType representing the directory
4651
*/
47-
export function buildTreeNode(dir: string): TreeNodeType {
52+
export function buildTreeNode(dir: string, parentPatterns: GitignorePattern[] = [], forceUncheck: boolean = false): TreeNodeType {
4853
const name = path.basename(dir) || dir;
54+
55+
// Collect gitignore patterns for this directory
56+
const directoryPatterns = collectDirectoryGitignorePatterns(dir);
57+
58+
// Combine with parent patterns (parent patterns take precedence)
59+
const allPatterns = [...directoryPatterns, ...parentPatterns];
60+
4961
const node: TreeNodeType = {
5062
id: dir,
5163
name,
@@ -61,17 +73,31 @@ export function buildTreeNode(dir: string): TreeNodeType {
6173

6274
for (const entry of entries) {
6375
const fullPath = path.join(dir, entry.name);
64-
if (entry.isDirectory()) {
65-
node.children!.push(buildTreeNode(fullPath));
76+
const isDirectory = entry.isDirectory();
77+
78+
// Check if this file/folder should be excluded based on gitignore patterns
79+
const shouldExclude = shouldExcludeByGitignore(fullPath, dir, allPatterns, isDirectory);
80+
81+
if (isDirectory) {
82+
// Process subdirectory, passing down the combined patterns
83+
const childNode = buildTreeNode(fullPath, allPatterns, shouldExclude || forceUncheck);
84+
85+
// If the directory itself matches gitignore patterns, mark it as unchecked
86+
if (shouldExclude || forceUncheck) {
87+
childNode.checked = false;
88+
}
89+
90+
node.children!.push(childNode);
6691
} else {
92+
// Add file node
6793
node.children!.push({
6894
id: fullPath,
6995
name: entry.name,
7096
type: 'file',
71-
checked: true
97+
checked: !shouldExclude && !forceUncheck // Set checked to false if it matches gitignore patterns
7298
});
7399
}
74100
}
75101

76102
return node;
77-
}
103+
}

src/gitignore-utils.ts

Lines changed: 199 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,199 @@
1+
import * as fs from 'fs';
2+
import * as path from 'path';
3+
4+
/**
5+
* Represents a parsed gitignore pattern
6+
*/
7+
export interface GitignorePattern {
8+
pattern: string;
9+
isNegated: boolean;
10+
isDirectory: boolean;
11+
isAbsolute: boolean;
12+
}
13+
14+
/**
15+
* Parses a .gitignore file and returns an array of patterns
16+
* @param gitignorePath Path to the .gitignore file
17+
* @returns Array of parsed gitignore patterns
18+
*/
19+
export function parseGitignoreFile(gitignorePath: string): GitignorePattern[] {
20+
if (!fs.existsSync(gitignorePath)) {
21+
return [];
22+
}
23+
24+
const content = fs.readFileSync(gitignorePath, 'utf8');
25+
return parseGitignoreContent(content);
26+
}
27+
28+
/**
29+
* Parses gitignore content and returns an array of patterns
30+
* @param content Content of the .gitignore file
31+
* @returns Array of parsed gitignore patterns
32+
*/
33+
export function parseGitignoreContent(content: string): GitignorePattern[] {
34+
return content
35+
.split('\n')
36+
.map(line => line.trim())
37+
.filter(line => line && !line.startsWith('#')) // Remove empty lines and comments
38+
.map(line => {
39+
const isNegated = line.startsWith('!');
40+
const pattern = isNegated ? line.substring(1) : line;
41+
const isDirectory = pattern.endsWith('/');
42+
const isAbsolute = pattern.startsWith('/') || pattern.startsWith('./');
43+
44+
return {
45+
pattern: isAbsolute ? pattern.substring(pattern.startsWith('./') ? 2 : 1) : pattern,
46+
isNegated,
47+
isDirectory,
48+
isAbsolute
49+
};
50+
});
51+
}
52+
53+
/**
54+
* Checks if a file or directory matches any of the gitignore patterns
55+
* @param filePath Path to the file or directory (relative to the directory containing the .gitignore)
56+
* @param patterns Array of gitignore patterns
57+
* @param isDirectory Whether the path is a directory
58+
* @returns True if the file or directory should be ignored
59+
*/
60+
export function matchesGitignorePatterns(
61+
filePath: string,
62+
patterns: GitignorePattern[],
63+
isDirectory: boolean
64+
): boolean {
65+
// Normalize path for matching
66+
const normalizedPath = filePath.replace(/\\/g, '/');
67+
68+
// Start with not ignored, then apply patterns in order
69+
let ignored = false;
70+
71+
for (const pattern of patterns) {
72+
// Skip directory-only patterns if this is a file
73+
if (pattern.isDirectory && !isDirectory) {
74+
continue;
75+
}
76+
77+
if (matchesPattern(normalizedPath, pattern, isDirectory)) {
78+
// If pattern matches, set ignored based on whether it's negated
79+
ignored = !pattern.isNegated;
80+
}
81+
}
82+
83+
return ignored;
84+
}
85+
86+
/**
87+
* Checks if a path matches a gitignore pattern
88+
* @param normalizedPath Normalized path to check
89+
* @param pattern Gitignore pattern
90+
* @param isDirectory Whether the path is a directory
91+
* @returns True if the path matches the pattern
92+
*/
93+
function matchesPattern(
94+
normalizedPath: string,
95+
pattern: GitignorePattern,
96+
isDirectory: boolean
97+
): boolean {
98+
const patternStr = pattern.pattern.replace(/\\/g, '/');
99+
100+
// For directory patterns (ending with /), also check without the trailing slash
101+
// when matching against directories
102+
if (pattern.isDirectory && isDirectory && patternStr.endsWith('/')) {
103+
const patternWithoutSlash = patternStr.slice(0, -1);
104+
if (normalizedPath === patternWithoutSlash) {
105+
return true;
106+
}
107+
}
108+
109+
// Handle exact matches
110+
if (!patternStr.includes('*')) {
111+
if (pattern.isAbsolute) {
112+
// For absolute patterns, match from the beginning
113+
return normalizedPath === patternStr ||
114+
(isDirectory && normalizedPath.startsWith(patternStr + '/'));
115+
} else {
116+
// For relative patterns, match anywhere in the path
117+
return normalizedPath === patternStr ||
118+
normalizedPath.endsWith('/' + patternStr) ||
119+
normalizedPath.includes('/' + patternStr + '/') ||
120+
(isDirectory && (
121+
normalizedPath.endsWith('/' + patternStr) ||
122+
normalizedPath.includes('/' + patternStr + '/')
123+
));
124+
}
125+
}
126+
127+
// Handle wildcard patterns
128+
const regexPattern = patternStr
129+
.replace(/\./g, '\\.') // Escape dots
130+
.replace(/\*/g, '.*') // Convert * to .*
131+
.replace(/\?/g, '.'); // Convert ? to .
132+
133+
const regex = pattern.isAbsolute
134+
? new RegExp(`^${regexPattern}$`)
135+
: new RegExp(`(^|/)${regexPattern}$`);
136+
137+
return regex.test(normalizedPath);
138+
}
139+
140+
/**
141+
* Collects gitignore patterns from a specific directory
142+
* @param dirPath Path to the directory
143+
* @returns Array of gitignore patterns from this directory
144+
*/
145+
export function collectDirectoryGitignorePatterns(dirPath: string): GitignorePattern[] {
146+
const gitignorePath = path.join(dirPath, '.gitignore');
147+
if (fs.existsSync(gitignorePath)) {
148+
return parseGitignoreFile(gitignorePath);
149+
}
150+
return [];
151+
}
152+
153+
/**
154+
* Collects all gitignore patterns that apply to a given directory
155+
* @param dirPath Path to the directory
156+
* @returns Array of gitignore patterns that apply to the directory
157+
*/
158+
export function collectGitignorePatterns(dirPath: string): GitignorePattern[] {
159+
const patterns: GitignorePattern[] = [];
160+
let currentDir = dirPath;
161+
162+
// Collect patterns from all parent directories up to the root
163+
while (true) {
164+
const gitignorePath = path.join(currentDir, '.gitignore');
165+
if (fs.existsSync(gitignorePath)) {
166+
const dirPatterns = parseGitignoreFile(gitignorePath);
167+
patterns.push(...dirPatterns);
168+
}
169+
170+
const parentDir = path.dirname(currentDir);
171+
if (parentDir === currentDir) {
172+
break; // Reached the root
173+
}
174+
currentDir = parentDir;
175+
}
176+
177+
return patterns;
178+
}
179+
180+
/**
181+
* Determines if a file or directory should be excluded based on gitignore patterns
182+
* @param fullPath Full path to the file or directory
183+
* @param baseDir Base directory for relative path calculation
184+
* @param patterns Gitignore patterns to check against
185+
* @param isDirectory Whether the path is a directory
186+
* @returns True if the file or directory should be excluded
187+
*/
188+
export function shouldExcludeByGitignore(
189+
fullPath: string,
190+
baseDir: string,
191+
patterns: GitignorePattern[],
192+
isDirectory: boolean
193+
): boolean {
194+
// Calculate path relative to the base directory
195+
const relativePath = path.relative(baseDir, fullPath).replace(/\\/g, '/');
196+
197+
// Check if the path matches any gitignore pattern
198+
return matchesGitignorePatterns(relativePath, patterns, isDirectory);
199+
}

0 commit comments

Comments
 (0)