291 lines
10 KiB
Markdown
291 lines
10 KiB
Markdown
# MarketAlly.GitCommitEditor
|
|
|
|
A production-ready .NET 9 library for analyzing, improving, and rewriting git commit messages using configurable quality rules and AI-powered suggestions.
|
|
|
|
## Features
|
|
|
|
- **Commit Message Analysis** - Score commit messages against configurable quality rules (subject length, conventional commits, banned phrases, etc.)
|
|
- **AI-Powered Suggestions** - Generate improved commit messages using Claude, OpenAI, Gemini, or Qwen
|
|
- **Repository Management** - Discover, register, and manage multiple git repositories
|
|
- **Commit Rewriting** - Amend latest commits or reword older commits in history with automatic backup
|
|
- **Batch Operations** - Process multiple commits across multiple repositories efficiently
|
|
- **History Health Analysis** - Comprehensive repository health scoring with cleanup recommendations
|
|
- **Push Operations** - Check push status, get tracking info, and perform force pushes
|
|
- **State Persistence** - Save and restore analysis state between sessions
|
|
- **Dependency Injection** - First-class support for Microsoft.Extensions.DependencyInjection
|
|
|
|
## Installation
|
|
|
|
```bash
|
|
dotnet add package MarketAlly.GitCommitEditor
|
|
```
|
|
|
|
## Quick Start
|
|
|
|
### With Dependency Injection (Recommended)
|
|
|
|
```csharp
|
|
using MarketAlly.GitCommitEditor.Extensions;
|
|
|
|
services.AddGitMessageImprover(options =>
|
|
{
|
|
options.WorkspaceRoot = @"C:\Projects";
|
|
options.StateFilePath = "git-state.json";
|
|
options.MaxCommitsPerRepo = 100;
|
|
options.AnalyzeSince = DateTimeOffset.Now.AddMonths(-3);
|
|
options.ExcludedAuthors = ["dependabot[bot]@users.noreply.github.com"];
|
|
|
|
// Configure commit message rules
|
|
options.Rules.MinSubjectLength = 10;
|
|
options.Rules.MaxSubjectLength = 72;
|
|
options.Rules.RequireConventionalCommit = true;
|
|
options.Rules.RequireIssueReference = false;
|
|
|
|
// Configure AI provider
|
|
options.Ai.Provider = "Claude";
|
|
options.Ai.Model = "claude-sonnet-4-20250514";
|
|
options.Ai.ApiKey = Environment.GetEnvironmentVariable("ANTHROPIC_API_KEY")!;
|
|
options.Ai.IncludeDiffContext = true;
|
|
});
|
|
```
|
|
|
|
### Without DI (Simple Usage)
|
|
|
|
```csharp
|
|
using MarketAlly.GitCommitEditor.Options;
|
|
using MarketAlly.GitCommitEditor.Services;
|
|
|
|
var options = new GitImproverOptions
|
|
{
|
|
WorkspaceRoot = @"C:\Projects",
|
|
Ai = { ApiKey = "your-api-key", Provider = "Claude" }
|
|
};
|
|
|
|
await using var service = await GitMessageImproverService.CreateAsync(options);
|
|
```
|
|
|
|
## Core Workflows
|
|
|
|
### 1. Scan and Analyze Repositories
|
|
|
|
```csharp
|
|
IGitMessageImproverService improver = ...;
|
|
|
|
// Load persisted state
|
|
await improver.LoadStateAsync();
|
|
|
|
// Discover git repos in workspace
|
|
var newRepos = await improver.ScanAndRegisterReposAsync();
|
|
Console.WriteLine($"Found {newRepos.Count} new repositories");
|
|
|
|
// Analyze all repos for commit message issues
|
|
var analyses = await improver.AnalyzeAllReposAsync(
|
|
onlyNeedsImprovement: true,
|
|
progress: new Progress<(string Repo, int Count)>(p =>
|
|
Console.WriteLine($"Analyzed {p.Count} commits in {p.Repo}")));
|
|
|
|
Console.WriteLine($"Found {analyses.Count} commits needing improvement");
|
|
```
|
|
|
|
### 2. Generate AI Suggestions
|
|
|
|
```csharp
|
|
// Generate suggestions for commits that need improvement
|
|
var commitsToImprove = analyses.Where(a => a.Quality.NeedsImprovement).ToList();
|
|
|
|
var result = await improver.GenerateSuggestionsAsync(
|
|
commitsToImprove,
|
|
progress: new Progress<int>(count =>
|
|
Console.WriteLine($"Generated {count}/{commitsToImprove.Count} suggestions")));
|
|
|
|
Console.WriteLine($"Success: {result.SuccessCount}, Failed: {result.FailedCount}");
|
|
|
|
// Or generate for a single commit
|
|
var suggestion = await improver.GenerateSuggestionAsync(analyses.First());
|
|
if (suggestion.Success)
|
|
Console.WriteLine($"Suggested: {suggestion.Suggestion}");
|
|
```
|
|
|
|
### 3. Safe Batch Rewrite (Recommended)
|
|
|
|
```csharp
|
|
// Check safety before rewriting
|
|
var safetyInfo = improver.GetRewriteSafetyInfo(repoPath, commitsToRewrite);
|
|
|
|
if (!safetyInfo.CanProceedSafely)
|
|
{
|
|
foreach (var warning in safetyInfo.GetWarnings())
|
|
Console.WriteLine($"Warning: {warning}");
|
|
}
|
|
|
|
// Execute batch rewrite with automatic backup
|
|
var result = await improver.ExecuteBatchRewriteAsync(
|
|
repoPath,
|
|
commitsToRewrite,
|
|
createBackup: true,
|
|
progress: new Progress<(int Current, int Total, string Hash)>(p =>
|
|
Console.WriteLine($"Rewriting {p.Current}/{p.Total}: {p.Hash}")));
|
|
|
|
if (result.Success)
|
|
{
|
|
Console.WriteLine($"Rewrote {result.SuccessCount} commits");
|
|
if (result.BackupBranchName != null)
|
|
Console.WriteLine($"Backup branch: {result.BackupBranchName}");
|
|
if (result.RequiresForcePush)
|
|
Console.WriteLine("Force push required to update remote");
|
|
}
|
|
```
|
|
|
|
### 4. Preview and Apply Changes (Granular Control)
|
|
|
|
```csharp
|
|
// Preview changes before applying
|
|
var operations = improver.PreviewChanges(commitsToImprove);
|
|
|
|
foreach (var op in operations)
|
|
{
|
|
Console.WriteLine($"{op.CommitHash[..7]}: {op.OriginalMessage}");
|
|
Console.WriteLine($" -> {op.NewMessage}");
|
|
}
|
|
|
|
// Apply changes (dryRun: false to actually modify commits)
|
|
var result = await improver.ApplyChangesAsync(
|
|
operations,
|
|
dryRun: false,
|
|
progress: new Progress<(int Processed, int Total)>(p =>
|
|
Console.WriteLine($"Applied {p.Processed}/{p.Total}")));
|
|
|
|
Console.WriteLine($"Success: {result.Successful}, Failed: {result.Failed}");
|
|
```
|
|
|
|
### 5. Repository History Health Analysis
|
|
|
|
```csharp
|
|
// Analyze repository health
|
|
var healthReport = await improver.AnalyzeHistoryHealthAsync(
|
|
repoPath,
|
|
new HistoryAnalysisOptions { Depth = AnalysisDepth.Standard },
|
|
progress: new Progress<AnalysisProgress>(p =>
|
|
Console.WriteLine($"Analyzing: {p.PercentComplete}%")));
|
|
|
|
Console.WriteLine($"Health Score: {healthReport.Score.OverallScore}/100 ({healthReport.Score.Grade})");
|
|
Console.WriteLine($"Issues: {healthReport.CriticalIssueCount} critical, {healthReport.ErrorCount} errors");
|
|
|
|
// Export report
|
|
var markdown = await improver.ExportHealthReportAsync(healthReport, ReportFormat.Markdown);
|
|
|
|
// Execute cleanup suggestions
|
|
if (healthReport.CleanupSuggestions?.AutomatedOperations.Any() == true)
|
|
{
|
|
var cleanupResult = await improver.ExecuteAllCleanupsAsync(
|
|
repo,
|
|
healthReport.CleanupSuggestions,
|
|
new CleanupExecutionOptions { CreateBackup = true });
|
|
|
|
Console.WriteLine($"Cleaned up {cleanupResult.Successful} issues");
|
|
}
|
|
```
|
|
|
|
### 6. Handle Pushed Commits
|
|
|
|
```csharp
|
|
// Check if commit is already pushed
|
|
bool isPushed = improver.IsCommitPushed(repoPath, commitHash);
|
|
|
|
if (isPushed)
|
|
Console.WriteLine("Warning: Commit has been pushed. Force push required after amending.");
|
|
|
|
// Get tracking info
|
|
var tracking = improver.GetTrackingInfo(repoPath);
|
|
Console.WriteLine($"Tracking: {tracking.UpstreamBranch}, Ahead: {tracking.AheadBy}, Behind: {tracking.BehindBy}");
|
|
|
|
// After amending a pushed commit, force push
|
|
var pushResult = improver.ForcePush(repoPath);
|
|
if (pushResult.Success)
|
|
Console.WriteLine("Force push successful");
|
|
else
|
|
Console.WriteLine($"Push failed: {pushResult.Message}");
|
|
```
|
|
|
|
## Configuration
|
|
|
|
### GitImproverOptions
|
|
|
|
| Property | Type | Default | Description |
|
|
|----------|------|---------|-------------|
|
|
| `WorkspaceRoot` | string | required | Root directory to scan for git repositories |
|
|
| `StateFilePath` | string | `"git-improver-state.json"` | Path to persist state between sessions |
|
|
| `MaxCommitsPerRepo` | int | `100` | Maximum commits to analyze per repository |
|
|
| `AnalyzeSince` | DateTimeOffset? | `null` | Only analyze commits after this date |
|
|
| `ExcludedAuthors` | string[] | `[]` | Author emails to exclude (e.g., bots) |
|
|
| `Rules` | CommitMessageRules | default | Commit message quality rules |
|
|
| `Ai` | AiOptions | default | AI provider configuration |
|
|
|
|
### CommitMessageRules
|
|
|
|
| Property | Type | Default | Description |
|
|
|----------|------|---------|-------------|
|
|
| `MinSubjectLength` | int | `10` | Minimum subject line length |
|
|
| `MaxSubjectLength` | int | `72` | Maximum subject line length |
|
|
| `MinBodyLength` | int | `0` | Minimum body length (0 = optional) |
|
|
| `RequireConventionalCommit` | bool | `false` | Require conventional commit format |
|
|
| `RequireIssueReference` | bool | `false` | Require issue/ticket reference |
|
|
| `BannedPhrases` | string[] | see below | Phrases that trigger warnings |
|
|
| `ConventionalTypes` | string[] | see below | Valid conventional commit types |
|
|
|
|
**Default Banned Phrases:** `fix`, `wip`, `temp`, `asdf`, `test`, `stuff`, `things`, `changes`, `update`, `misc`, `minor`, `oops`, `commit`, `save`, `checkpoint`, `progress`, `done`, `finished`
|
|
|
|
**Default Conventional Types:** `feat`, `fix`, `docs`, `style`, `refactor`, `perf`, `test`, `build`, `ci`, `chore`, `revert`
|
|
|
|
### AiOptions
|
|
|
|
| Property | Type | Default | Description |
|
|
|----------|------|---------|-------------|
|
|
| `ApiKey` | string | `""` | API key for the selected provider |
|
|
| `Provider` | string | `"Claude"` | AI provider (Claude, OpenAI, Gemini, Qwen) |
|
|
| `Model` | string | `"claude-sonnet-4-20250514"` | Model name for the provider |
|
|
| `IncludeDiffContext` | bool | `true` | Include diff in AI prompt |
|
|
| `MaxDiffLines` | int | `200` | Maximum diff lines to include |
|
|
| `MaxTokens` | int | `500` | Maximum tokens in AI response |
|
|
| `RateLimitDelayMs` | int | `500` | Delay between AI requests |
|
|
|
|
## Interfaces
|
|
|
|
The library follows Interface Segregation Principle with focused interfaces:
|
|
|
|
| Interface | Purpose |
|
|
|-----------|---------|
|
|
| `IRepositoryManager` | Repository discovery and registration |
|
|
| `ICommitAnalysisService` | Commit message analysis |
|
|
| `ISuggestionService` | AI suggestion generation |
|
|
| `ICommitRewriteService` | Commit message rewriting |
|
|
| `IGitPushService` | Push operations and tracking |
|
|
| `IHistoryHealthService` | Repository health analysis and cleanup |
|
|
| `IGitMessageImproverService` | Unified facade (composes all above) |
|
|
|
|
## Supported AI Providers
|
|
|
|
| Provider | Models |
|
|
|----------|--------|
|
|
| **Claude** | claude-sonnet-4-20250514, claude-opus-4-20250514, claude-3-5-haiku-20241022 |
|
|
| **OpenAI** | gpt-4o, gpt-4o-mini, gpt-4-turbo, gpt-3.5-turbo |
|
|
| **Gemini** | gemini-1.5-pro, gemini-1.5-flash, gemini-pro |
|
|
| **Qwen** | qwen-turbo, qwen-plus, qwen-max |
|
|
|
|
## Thread Safety
|
|
|
|
- `GitOperationsService` uses an LRU cache with TTL for repository handles
|
|
- State operations are async and support cancellation tokens
|
|
- The service implements `IDisposable` - ensure proper disposal
|
|
|
|
## Dependencies
|
|
|
|
- .NET 9.0
|
|
- MarketAlly.LibGit2Sharp (git operations)
|
|
- MarketAlly.AIPlugin (AI provider abstraction)
|
|
- Microsoft.Extensions.DependencyInjection.Abstractions
|
|
|
|
## License
|
|
|
|
MIT License - Copyright 2025 MarketAlly
|