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
35 changes: 35 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,9 @@ Supported settings:
- `prompt` Bundled prompt preset: `default`, `conventional`, `gitmoji`, `karma`
- `prompt_file` Path to a custom prompt file (relative to the config file)
- `engines.<name>.args` Argument list for the engine command (array of strings)
- `filter.max_file_lines` Maximum lines per file in diff (default: 100)
- `filter.exclude_patterns` Additional glob patterns to exclude from diff
- `filter.default_exclude_patterns` Override built-in exclude patterns

### Engines

Expand Down Expand Up @@ -117,6 +120,38 @@ prompt_file = "prompts/commit.md"

Note: `prompt` and `prompt_file` are mutually exclusive within the same config file. If both are set, an error is returned. When settings come from different layers (user config vs repo config), the later layer wins.

### Diff Filtering

When the staged diff is large, it can exceed LLM context limits or degrade commit message quality. git-ai-commit automatically filters the diff to help LLMs focus on meaningful changes.

**Default behavior:**

- Each file is limited to 100 lines (configurable via `filter.max_file_lines`)
- Lock files and generated files are excluded by default

**Default exclude patterns:**

- `**/*.lock`, `**/*-lock.json`, `**/*.lock.yaml`, `**/*-lock.yaml`, `**/*.lockfile`
- `**/*.min.js`, `**/*.min.css`, `**/*.map`
- `**/go.sum`

Example: Customize filtering

```toml
[filter]
max_file_lines = 300

# Add patterns to exclude (merged with defaults)
exclude_patterns = [
"**/vendor/**",
"**/*.generated.go",
"**/dist/**"
]

# Or replace defaults entirely
# default_exclude_patterns = ["**/my-lock.json"]
```

## Claude Code Plugin

If you use [Claude Code](https://docs.anthropic.com/en/docs/claude-code), you can integrate git-ai-commit as a plugin for a more convenient workflow.
Expand Down
52 changes: 48 additions & 4 deletions internal/app/app.go
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ func Run(context, contextFile, promptName, promptFile, engineName string, amend,
}
}

diff, err := commitDiff(amend)
diff, err := commitDiff(amend, cfg)
if err != nil {
return err
}
Expand Down Expand Up @@ -141,11 +141,55 @@ func sanitizeMessage(message string) string {
return clean
}

func commitDiff(amend bool) (string, error) {
func commitDiff(amend bool, cfg config.Config) (string, error) {
var diff string
var err error
if amend {
return git.LastCommitDiff()
diff, err = git.LastCommitDiff()
} else {
diff, err = git.StagedDiff()
}
return git.StagedDiff()
if err != nil {
return "", err
}

// Determine exclude patterns
patterns := git.DefaultExcludePatterns()
if len(cfg.Filter.DefaultExcludePatterns) > 0 {
patterns = cfg.Filter.DefaultExcludePatterns
}
patterns = append(patterns, cfg.Filter.ExcludePatterns...)

// Determine max file lines
maxLines := cfg.Filter.MaxFileLines
if maxLines == 0 {
maxLines = git.DefaultMaxFileLines
}

opts := git.Options{
MaxFileLines: maxLines,
ExcludePatterns: patterns,
}
result := git.Filter(diff, opts)

if result.Truncated || len(result.ExcludedFiles) > 0 {
return result.Diff + formatFilterNotice(result), nil
}
return result.Diff, nil
}

func formatFilterNotice(result git.Result) string {
var parts []string
if len(result.ExcludedFiles) > 0 {
parts = append(parts, fmt.Sprintf("Excluded files: %s", strings.Join(result.ExcludedFiles, ", ")))
}
if len(result.TruncatedFiles) > 0 {
parts = append(parts, fmt.Sprintf("Truncated files: %s", strings.Join(result.TruncatedFiles, ", ")))
}
if len(parts) == 0 {
return ""
}
return "\n\n[Filter notice: " + strings.Join(parts, "; ") + "]"
}

func stageChanges(addAll bool, includeFiles []string) (func(), error) {
Expand Down
29 changes: 29 additions & 0 deletions internal/config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,18 +17,27 @@ type Config struct {
Prompt string `toml:"prompt"`
PromptFile string `toml:"prompt_file"`
Engines map[string]EngineConfig `toml:"engines"`
Filter FilterConfig `toml:"filter"`

// ResolvedPrompt holds the final prompt text after loading from preset or file.
// This is not read from config files directly.
ResolvedPrompt string `toml:"-"`
}

// FilterConfig holds diff filtering configuration.
type FilterConfig struct {
MaxFileLines int `toml:"max_file_lines"` // Max lines per file (0 = use default)
DefaultExcludePatterns []string `toml:"default_exclude_patterns"` // Override built-in defaults
ExcludePatterns []string `toml:"exclude_patterns"` // Additional patterns to exclude
}

// rawConfig is the TOML structure used to detect mutual exclusivity in a single layer.
type rawConfig struct {
DefaultEngine string `toml:"engine"`
Prompt string `toml:"prompt"`
PromptFile string `toml:"prompt_file"`
Engines map[string]EngineConfig `toml:"engines"`
Filter FilterConfig `toml:"filter"`
}

type EngineConfig struct {
Expand Down Expand Up @@ -129,6 +138,16 @@ func Load() (Config, error) {
cfg.Engines[name] = ec
}
}
// Merge filter config from repo
if repoCfg.Filter.MaxFileLines != 0 {
cfg.Filter.MaxFileLines = repoCfg.Filter.MaxFileLines
}
if len(repoCfg.Filter.DefaultExcludePatterns) > 0 {
cfg.Filter.DefaultExcludePatterns = repoCfg.Filter.DefaultExcludePatterns
}
if len(repoCfg.Filter.ExcludePatterns) > 0 {
cfg.Filter.ExcludePatterns = append(cfg.Filter.ExcludePatterns, repoCfg.Filter.ExcludePatterns...)
}
}
}

Expand Down Expand Up @@ -181,6 +200,16 @@ func loadConfigLayer(data []byte, cfg *Config, source string) error {
cfg.Engines[name] = ec
}
}
// Merge filter config
if raw.Filter.MaxFileLines != 0 {
cfg.Filter.MaxFileLines = raw.Filter.MaxFileLines
}
if len(raw.Filter.DefaultExcludePatterns) > 0 {
cfg.Filter.DefaultExcludePatterns = raw.Filter.DefaultExcludePatterns
}
if len(raw.Filter.ExcludePatterns) > 0 {
cfg.Filter.ExcludePatterns = append(cfg.Filter.ExcludePatterns, raw.Filter.ExcludePatterns...)
}
return nil
}

Expand Down
Loading