Add global gitignore support to file exclusion logic
This commit is contained in:
parent
520606ccb9
commit
365b5d2365
|
|
@ -1,11 +1,12 @@
|
|||
import { afterEach, beforeEach, describe, expect, test } from 'bun:test'
|
||||
import { afterEach, beforeEach, describe, expect, mock, test } from 'bun:test'
|
||||
import { mkdirSync, rmSync, writeFileSync } from 'fs'
|
||||
import { join } from 'path'
|
||||
import { loadGitignore } from './gitignore'
|
||||
import { _resetGlobalCache, loadGitignore } from './gitignore'
|
||||
|
||||
const TEST_DIR = '/tmp/toes-gitignore-test'
|
||||
|
||||
beforeEach(() => {
|
||||
_resetGlobalCache()
|
||||
mkdirSync(TEST_DIR, { recursive: true })
|
||||
})
|
||||
|
||||
|
|
@ -171,4 +172,64 @@ describe('loadGitignore', () => {
|
|||
expect(checker.shouldExclude('build/output')).toBe(true)
|
||||
})
|
||||
})
|
||||
|
||||
describe('global gitignore', () => {
|
||||
const GLOBAL_IGNORE = '/tmp/toes-global-gitignore-test'
|
||||
|
||||
beforeEach(() => {
|
||||
mkdirSync(GLOBAL_IGNORE, { recursive: true })
|
||||
})
|
||||
|
||||
afterEach(() => {
|
||||
rmSync(GLOBAL_IGNORE, { recursive: true, force: true })
|
||||
})
|
||||
|
||||
test('should include patterns from global gitignore', () => {
|
||||
const globalIgnorePath = join(GLOBAL_IGNORE, 'ignore')
|
||||
writeFileSync(globalIgnorePath, '*.bak\n.idea/')
|
||||
|
||||
const origSpawnSync = require('child_process').spawnSync
|
||||
const spawnSyncMock = mock((cmd: string, args: string[], opts: any) => {
|
||||
if (cmd === 'git' && args.includes('core.excludesFile')) {
|
||||
return { status: 0, stdout: globalIgnorePath + '\n', stderr: '' }
|
||||
}
|
||||
return origSpawnSync(cmd, args, opts)
|
||||
})
|
||||
mock.module('child_process', () => ({ spawnSync: spawnSyncMock }))
|
||||
_resetGlobalCache()
|
||||
|
||||
const checker = loadGitignore(TEST_DIR)
|
||||
|
||||
expect(checker.shouldExclude('backup.bak')).toBe(true)
|
||||
expect(checker.shouldExclude('src/old.bak')).toBe(true)
|
||||
expect(checker.shouldExclude('.idea')).toBe(true)
|
||||
expect(checker.shouldExclude('src/index.ts')).toBe(false)
|
||||
|
||||
mock.module('child_process', () => ({ spawnSync: origSpawnSync }))
|
||||
})
|
||||
|
||||
test('should merge global and local patterns', () => {
|
||||
const globalIgnorePath = join(GLOBAL_IGNORE, 'ignore')
|
||||
writeFileSync(globalIgnorePath, '*.bak')
|
||||
writeFileSync(join(TEST_DIR, '.gitignore'), '*.log')
|
||||
|
||||
const origSpawnSync = require('child_process').spawnSync
|
||||
const spawnSyncMock = mock((cmd: string, args: string[], opts: any) => {
|
||||
if (cmd === 'git' && args.includes('core.excludesFile')) {
|
||||
return { status: 0, stdout: globalIgnorePath + '\n', stderr: '' }
|
||||
}
|
||||
return origSpawnSync(cmd, args, opts)
|
||||
})
|
||||
mock.module('child_process', () => ({ spawnSync: spawnSyncMock }))
|
||||
_resetGlobalCache()
|
||||
|
||||
const checker = loadGitignore(TEST_DIR)
|
||||
|
||||
expect(checker.shouldExclude('backup.bak')).toBe(true)
|
||||
expect(checker.shouldExclude('debug.log')).toBe(true)
|
||||
expect(checker.shouldExclude('src/index.ts')).toBe(false)
|
||||
|
||||
mock.module('child_process', () => ({ spawnSync: origSpawnSync }))
|
||||
})
|
||||
})
|
||||
})
|
||||
|
|
|
|||
|
|
@ -1,4 +1,6 @@
|
|||
import { spawnSync } from 'child_process'
|
||||
import { existsSync, readFileSync } from 'fs'
|
||||
import { homedir } from 'os'
|
||||
import { join } from 'path'
|
||||
|
||||
const ALWAYS_EXCLUDE = [
|
||||
|
|
@ -24,15 +26,46 @@ export interface GitignoreChecker {
|
|||
|
||||
export function loadGitignore(appPath: string): GitignoreChecker {
|
||||
const gitignorePath = join(appPath, '.gitignore')
|
||||
const globalPatterns = loadGlobalPatterns()
|
||||
const patterns = existsSync(gitignorePath)
|
||||
? [...ALWAYS_EXCLUDE, ...parseGitignore(readFileSync(gitignorePath, 'utf-8'))]
|
||||
: DEFAULT_PATTERNS
|
||||
? [...ALWAYS_EXCLUDE, ...globalPatterns, ...parseGitignore(readFileSync(gitignorePath, 'utf-8'))]
|
||||
: [...DEFAULT_PATTERNS, ...globalPatterns]
|
||||
|
||||
return {
|
||||
shouldExclude: (path: string) => matchesPattern(path, patterns),
|
||||
}
|
||||
}
|
||||
|
||||
export const _resetGlobalCache = () => {
|
||||
cachedGlobalPatterns = undefined
|
||||
}
|
||||
|
||||
let cachedGlobalPatterns: string[] | undefined
|
||||
|
||||
const expandHome = (filepath: string) =>
|
||||
filepath.startsWith('~/') ? join(homedir(), filepath.slice(2)) : filepath
|
||||
|
||||
function getGlobalExcludesPath(): string | null {
|
||||
const result = spawnSync('git', ['config', '--global', 'core.excludesFile'], {
|
||||
encoding: 'utf-8',
|
||||
})
|
||||
if (result.status === 0 && result.stdout.trim()) {
|
||||
return expandHome(result.stdout.trim())
|
||||
}
|
||||
const xdgHome = process.env.XDG_CONFIG_HOME || join(homedir(), '.config')
|
||||
const defaultPath = join(xdgHome, 'git', 'ignore')
|
||||
if (existsSync(defaultPath)) return defaultPath
|
||||
return null
|
||||
}
|
||||
|
||||
function loadGlobalPatterns(): string[] {
|
||||
if (cachedGlobalPatterns !== undefined) return cachedGlobalPatterns
|
||||
const path = getGlobalExcludesPath()
|
||||
cachedGlobalPatterns =
|
||||
path && existsSync(path) ? parseGitignore(readFileSync(path, 'utf-8')) : []
|
||||
return cachedGlobalPatterns
|
||||
}
|
||||
|
||||
function parseGitignore(content: string): string[] {
|
||||
return content
|
||||
.split('\n')
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user