Skip to content

feat: implement high-performance dual-engine gitignore parser #433

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 2 commits into from
Jul 31, 2025
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
149 changes: 149 additions & 0 deletions GITIGNORE_ENGINE_IMPLEMENTATION.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,149 @@
# 高性能双引擎 Gitignore 解析器实现

## 概述

本实现为 AutoDev 项目添加了一个高性能的双引擎 Gitignore 解析器,用于替换现有的 gitignore 相关逻辑。该解决方案提供了自定义高性能引擎和第三方库引擎的双重保障。

## 架构设计

### 核心组件

1. **IgnoreEngine** - 忽略引擎接口
2. **IgnoreRule** - 忽略规则接口
3. **GitIgnoreFlagWrapper** - 双引擎包装器
4. **HomeSpunIgnoreEngine** - 自定义高性能引擎
5. **BasjesIgnoreEngine** - 第三方库引擎
6. **IgnoreEngineFactory** - 工厂类

### 支持组件

- **PatternConverter** - 模式转换器(gitignore 模式到正则表达式)
- **IgnorePatternCache** - 模式缓存
- **ThreadSafeMatcher** - 线程安全匹配器
- **HomeSpunIgnoreRule** - 自定义规则实现
- **GitIgnoreUtil** - 工具类

## 功能特性

### 1. 双引擎架构
- **主引擎**: HomeSpunIgnoreEngine(自定义高性能实现)
- **备用引擎**: BasjesIgnoreEngine(基于 nl.basjes.gitignore 库)
- **动态切换**: 通过功能开关 `enableHomeSpunGitIgnore` 控制

### 2. 高性能优化
- **预编译正则表达式**: 避免重复编译
- **并发缓存**: 使用 ConcurrentHashMap 缓存编译后的模式
- **线程安全**: 所有组件都是线程安全的
- **错误恢复**: 主引擎失败时自动切换到备用引擎

### 3. 完整的 Gitignore 支持
- 基本通配符(`*`, `?`)
- 双星通配符(`**`)
- 目录模式(以 `/` 结尾)
- 否定模式(以 `!` 开头)
- 根路径模式(以 `/` 开头)
- 注释和空行处理

## 文件结构

```
core/src/main/kotlin/cc/unitmesh/devti/vcs/gitignore/
├── IgnoreEngine.kt # 核心接口
├── IgnoreRule.kt # 规则接口
├── GitIgnoreFlagWrapper.kt # 双引擎包装器
├── HomeSpunIgnoreEngine.kt # 自定义引擎
├── BasjesIgnoreEngine.kt # 第三方库引擎
├── IgnoreEngineFactory.kt # 工厂类
├── PatternConverter.kt # 模式转换器
├── IgnorePatternCache.kt # 模式缓存
├── ThreadSafeMatcher.kt # 线程安全匹配器
├── HomeSpunIgnoreRule.kt # 自定义规则实现
├── GitIgnoreUtil.kt # 工具类
└── InvalidGitIgnorePatternException.kt # 异常类
```

## 集成点

### 1. 设置配置
在 `AutoDevCoderSettingService` 中添加了功能开关:
```kotlin
var enableHomeSpunGitIgnore by property(true)
```

### 2. 现有代码更新
更新了以下文件以使用新的 gitignore 引擎:
- `ProjectFileUtil.kt`
- `DirInsCommand.kt`
- `WorkspaceFileSearchPopup.kt`
- `LocalSearchInsCommand.kt`

### 3. 依赖管理
添加了第三方库依赖:
```kotlin
implementation("nl.basjes.gitignore:gitignore-reader:1.6.0")
```

## 使用方法

### 基本使用
```kotlin
// 通过工厂创建引擎
val engine = IgnoreEngineFactory.createEngine(IgnoreEngineFactory.EngineType.HOMESPUN)

// 加载 gitignore 内容
engine.loadFromContent(gitIgnoreContent)

// 检查文件是否被忽略
val isIgnored = engine.isIgnored("path/to/file.txt")
```

### 项目集成使用
```kotlin
// 使用工具类(推荐)
val isIgnored = GitIgnoreUtil.isIgnored(project, virtualFile)

// 或者使用文件路径
val isIgnored = GitIgnoreUtil.isIgnored(project, "src/main/App.java")
```

## 测试

实现了全面的测试套件:
- `IgnoreEngineTest` - 引擎功能测试
- `PatternConverterTest` - 模式转换测试
- `GitIgnoreFlagWrapperTest` - 双引擎包装器测试

## 性能优势

1. **预编译模式**: 避免运行时重复编译正则表达式
2. **缓存机制**: 编译后的模式被缓存以供重用
3. **并发优化**: 使用线程安全的数据结构
4. **错误恢复**: 主引擎失败时的快速切换机制

## 配置选项

用户可以通过 AutoDev 设置面板控制:
- 启用/禁用自定义高性能引擎
- 查看引擎统计信息
- 重新加载 gitignore 规则

## 向后兼容性

- 完全向后兼容现有的 gitignore 功能
- 无需修改现有的 .gitignore 文件
- 平滑的功能切换,无需重启 IDE

## 故障排除

如果遇到问题:
1. 检查功能开关设置
2. 查看引擎统计信息
3. 尝试切换到备用引擎
4. 检查 gitignore 文件语法

## 未来扩展

- 支持更多的忽略文件格式
- 添加性能监控和分析
- 实现更高级的缓存策略
- 支持自定义忽略规则
3 changes: 3 additions & 0 deletions build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -480,6 +480,9 @@ project(":core") {
// YAML parsing for edit_file command
implementation("org.yaml:snakeyaml:2.2")

// gitignore parsing library for fallback engine
implementation("nl.basjes.gitignore:gitignore-reader:1.6.0")

implementation("org.jetbrains.kotlinx:kotlinx-serialization-json:1.6.3")
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -448,8 +448,17 @@ class WorkspaceFileSearchPopup(

private fun shouldAddFile(file: VirtualFile, loadedPaths: Set<String>): Boolean {
val fileIndex = ProjectFileIndex.getInstance(project)

// Use new high-performance gitignore engine for ignore checking
val isIgnored = try {
cc.unitmesh.devti.vcs.gitignore.GitIgnoreUtil.isIgnored(project, file)
} catch (e: Exception) {
// Fallback to original ignore checking
fileIndex.isUnderIgnored(file)
}

return file.canBeAdded(project) &&
!fileIndex.isUnderIgnored(file) &&
!isIgnored &&
fileIndex.isInContent(file) &&
file.path !in loadedPaths
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ class AutoDevCoderSettingService(
var enableAutoScrollInSketch by property(false)
var enableDiffViewer by property(true)
var teamPromptsDir by property("prompts") { it.isEmpty() }
var enableHomeSpunGitIgnore by property(true)

override fun copy(): AutoDevCoderSettings {
val state = AutoDevCoderSettings()
Expand Down
12 changes: 10 additions & 2 deletions core/src/main/kotlin/cc/unitmesh/devti/util/ProjectFileUtil.kt
Original file line number Diff line number Diff line change
Expand Up @@ -64,8 +64,16 @@ fun VirtualFile.relativePath(project: Project): String {
}

fun isIgnoredByVcs(project: Project?, file: VirtualFile?): Boolean {
val ignoreManager = VcsIgnoreManager.getInstance(project!!)
return ignoreManager.isPotentiallyIgnoredFile(file!!)
if (project == null || file == null) return false

// Use new high-performance gitignore engine
return try {
cc.unitmesh.devti.vcs.gitignore.GitIgnoreUtil.isIgnored(project, file)
} catch (e: Exception) {
// Fallback to original VCS ignore manager if new engine fails
val ignoreManager = VcsIgnoreManager.getInstance(project)
ignoreManager.isPotentiallyIgnoredFile(file)
}
}

fun virtualFile(editor: Editor?): VirtualFile? {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
package cc.unitmesh.devti.vcs.gitignore

import nl.basjes.gitignore.GitIgnore

/**
* Wrapper around the nl.basjes.gitignore library to implement the IgnoreEngine interface.
* This serves as the fallback engine for the dual-engine architecture.
*/
class BasjesIgnoreEngine : IgnoreEngine {
private var gitIgnore: GitIgnore = GitIgnore("")
private val rules = mutableListOf<String>()

override fun isIgnored(filePath: String): Boolean {
return try {
gitIgnore.isIgnoredFile(filePath)
} catch (e: Exception) {
// If the library fails, default to not ignored
false
}
}

override fun addRule(pattern: String) {
rules.add(pattern)
rebuildGitIgnore()
}

override fun removeRule(pattern: String) {
rules.remove(pattern)
rebuildGitIgnore()
}

override fun getRules(): List<String> {
return rules.toList()
}

override fun clearRules() {
rules.clear()
gitIgnore = GitIgnore("")
}

override fun loadFromContent(gitIgnoreContent: String) {
clearRules()

val lines = gitIgnoreContent.lines()
for (line in lines) {
val trimmedLine = line.trim()
if (trimmedLine.isNotEmpty()) {
rules.add(trimmedLine)
}
}

rebuildGitIgnore()
}

/**
* Rebuilds the internal GitIgnore instance with current rules.
* This is necessary because the nl.basjes.gitignore library doesn't support
* dynamic rule addition/removal.
*/
private fun rebuildGitIgnore() {
val content = rules.joinToString("\n")
gitIgnore = GitIgnore(content)
}

/**
* Gets the number of active rules.
*
* @return the number of rules
*/
fun getRuleCount(): Int = rules.size

/**
* Gets statistics about the engine for debugging/monitoring.
*
* @return a map of statistics
*/
fun getStatistics(): Map<String, Any> {
return mapOf(
"ruleCount" to getRuleCount(),
"engineType" to "Basjes"
)
}
}
Loading
Loading