Skip to content
Open
Show file tree
Hide file tree
Changes from 11 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
23 changes: 23 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@

# ![GIT Hooks + OpenAI - Generate GIT commit messages from OpenAI](https://raw.githubusercontent.com/guibranco/dotnet-aicommitmessage/main/docs/images/splash.png)

🧠 🤖 This tool generates AI-powered commit messages via Git hooks, automating meaningful message suggestions from OpenAI and others to improve commit quality and efficiency.
Expand Down Expand Up @@ -224,6 +225,28 @@
- Use fallback commit message generation (either the provided message or a placeholder)
- Continue to work with branch name processing and issue number extraction

### Ignore API Errors

In environments where API calls may occasionally fail due to network restrictions, timeouts, or temporary service issues, you can configure the tool to gracefully handle these errors instead of failing completely. Set the following environment variable:

```bash
export DOTNET_AICOMMITMESSAGE_IGNORE_API_ERRORS=true
```

Or on Windows:

```cmd
set DOTNET_AICOMMITMESSAGE_IGNORE_API_ERRORS=true
```

When this option is enabled, the tool will:
- Catch and suppress API-related exceptions (network errors, timeouts, authentication failures, etc.)

Check notice on line 243 in README.md

View check run for this annotation

Codacy Production / Codacy Static Code Analysis

README.md#L243

Lists should be surrounded by blank lines
- Display a warning message when an API error occurs but is ignored
- Fall back to using the original commit message with branch name processing and issue number extraction
- Continue the commit process without interruption

This is particularly useful in CI/CD pipelines or developer environments where occasional API issues shouldn't block the commit process.


### Contributors

Expand Down
31 changes: 29 additions & 2 deletions Src/AiCommitMessage/Services/GenerateCommitMessageService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,7 @@
/// API responses to a JSON file if debugging is enabled. If the commit message is a merge conflict resolution or contains a skip AI flag, it returns as-is.
/// </remarks>
/// <exception cref="InvalidOperationException">Thrown if both the branch and diff are empty, or if the staged changes exceed 10KB in size.</exception>
public string GenerateCommitMessage(GenerateCommitMessageOptions options)

Check warning on line 67 in Src/AiCommitMessage/Services/GenerateCommitMessageService.cs

View workflow job for this annotation

GitHub Actions / SonarCloud Analysis

Make 'GenerateCommitMessage' a static method. (https://rules.sonarsource.com/csharp/RSPEC-2325)
{
var branch = string.IsNullOrEmpty(options.Branch)
? GitHelper.GetBranchName()
Expand Down Expand Up @@ -121,10 +121,21 @@
+ (string.IsNullOrEmpty(diff) ? "<no changes>" : diff);

var model = EnvironmentLoader.LoadModelName();
return GenerateWithModel(model, formattedMessage, branch, message, options.Debug);

try
{
return GenerateWithModel(model, formattedMessage, branch, message, options.Debug);
}
catch (Exception ex) when (EnvironmentLoader.ShouldIgnoreApiErrors() && IsApiException(ex))
{
Output.WarningLine(
"⚠️ AI API error occurred but was ignored due to DOTNET_AICOMMITMESSAGE_IGNORE_API_ERRORS setting. Falling back to original message."
);
return PostProcess(message, branch, message);
}
}

private static string FilterPackageLockDiff(string diff)

Check warning on line 138 in Src/AiCommitMessage/Services/GenerateCommitMessageService.cs

View workflow job for this annotation

GitHub Actions / SonarCloud Analysis

Refactor this method to reduce its Cognitive Complexity from 24 to the 15 allowed. (https://rules.sonarsource.com/csharp/RSPEC-3776)
{
if (string.IsNullOrEmpty(diff))
{
Expand Down Expand Up @@ -156,7 +167,7 @@
if (parts.Length >= 4)
{
var pathB = parts[3].StartsWith("b/") ? parts[3].Substring(2) : parts[3];
foreach (var pattern in ignoredPatterns)

Check warning on line 170 in Src/AiCommitMessage/Services/GenerateCommitMessageService.cs

View workflow job for this annotation

GitHub Actions / SonarCloud Analysis

Loops should be simplified using the "Where" LINQ method (https://rules.sonarsource.com/csharp/RSPEC-3267)
{
if (pathB.EndsWith(pattern))
{
Expand Down Expand Up @@ -400,7 +411,7 @@
{
var processStartInfo = new ProcessStartInfo
{
FileName = "git",

Check warning on line 414 in Src/AiCommitMessage/Services/GenerateCommitMessageService.cs

View workflow job for this annotation

GitHub Actions / SonarCloud Analysis

Make sure the "PATH" variable only contains fixed, unwriteable directories. (https://rules.sonarsource.com/csharp/RSPEC-4036)
Arguments = "config --get remote.origin.url",
RedirectStandardOutput = true,
UseShellExecute = false,
Expand Down Expand Up @@ -433,4 +444,20 @@

return GitProvider.Unidentified;
}
}

/// <summary>
/// Determines whether the specified exception is an API-related exception.
/// </summary>
/// <param name="exception">The exception to check.</param>
/// <returns><c>true</c> if the exception is API-related; otherwise, <c>false</c>.</returns>
private static bool IsApiException(Exception exception)
{
return exception is HttpRequestException
|| exception is TaskCanceledException
|| exception is InvalidOperationException
|| exception is ClientResultException
|| exception is RequestFailedException
|| exception.InnerException is HttpRequestException
|| exception.InnerException is TaskCanceledException;
}
}
7 changes: 7 additions & 0 deletions Src/AiCommitMessage/Utility/EnvironmentLoader.cs
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,13 @@ public static bool LoadOptionalEmoji() =>
public static bool IsApiDisabled() =>
bool.Parse(GetEnvironmentVariable("DOTNET_AICOMMITMESSAGE_DISABLE_API", "false"));

/// <summary>
/// Checks if API errors should be ignored via environment variable.
/// </summary>
/// <returns><c>true</c> if API errors should be ignored, <c>false</c> otherwise.</returns>
public static bool ShouldIgnoreApiErrors() =>
bool.Parse(GetEnvironmentVariable("DOTNET_AICOMMITMESSAGE_IGNORE_API_ERRORS", "false"));

/// <summary>
/// Decrypts the specified encrypted text.
/// </summary>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -77,4 +77,44 @@ public void SettingsService_Should_WorkCorrectly_When_ApiDisabled()
);
}
}
}

/// <summary>
/// Tests the complete workflow when API errors are ignored.
/// </summary>
[Fact]
public void CompleteWorkflow_Should_WorkCorrectly_When_ApiErrorsIgnored()
{
// Arrange
Environment.SetEnvironmentVariable(
"DOTNET_AICOMMITMESSAGE_IGNORE_API_ERRORS",
"true",
EnvironmentVariableTarget.Process
);

var service = new GenerateCommitMessageService();
var options = new GenerateCommitMessageOptions
{
Branch = "feature/285-ignore-api-errors",
Diff = "Added new environment variable support",
Message = "Add option to ignore API errors -skipai", // Use skipai to avoid actual API calls
};

try
{
// Act
var result = service.GenerateCommitMessage(options);

// Assert
result.Should().Be("#285 Add option to ignore API errors");
}
finally
{
// Cleanup
Environment.SetEnvironmentVariable(
"DOTNET_AICOMMITMESSAGE_IGNORE_API_ERRORS",
null,
EnvironmentVariableTarget.Process
);
}
}
}
69 changes: 69 additions & 0 deletions Tests/AiCommitMessage.Tests/Services/ApiErrorHandlingTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
using System.ClientModel;
using AiCommitMessage.Options;
using AiCommitMessage.Services;
using Azure;
using FluentAssertions;

namespace AiCommitMessage.Tests.Services;

/// <summary>
/// Tests for API error handling functionality in GenerateCommitMessageService.
/// </summary>
public class ApiErrorHandlingTests
{
private readonly GenerateCommitMessageService _service;

public ApiErrorHandlingTests()
{
_service = new GenerateCommitMessageService();
}

/// <summary>
/// Tests that the service handles API errors gracefully when ignore API errors is enabled.
/// </summary>
[Fact]
public void GenerateCommitMessage_Should_HandleApiErrors_When_IgnoreApiErrorsEnabled()
{
// Arrange
Environment.SetEnvironmentVariable(
"DOTNET_AICOMMITMESSAGE_IGNORE_API_ERRORS",
"true",
EnvironmentVariableTarget.Process
);
Environment.SetEnvironmentVariable(
"DOTNET_AICOMMITMESSAGE_DISABLE_API",
"false",
EnvironmentVariableTarget.Process
);

var options = new GenerateCommitMessageOptions
{
Branch = "feature/285-test",
Diff = "Some diff",
Message = "Test commit -skipai", // Use skipai to avoid actual API calls but test the flow
};

try
{
// Act
var result = _service.GenerateCommitMessage(options);

// Assert
result.Should().Be("#285 Test commit");
}
finally
{
// Cleanup
Environment.SetEnvironmentVariable(
"DOTNET_AICOMMITMESSAGE_IGNORE_API_ERRORS",
null,
EnvironmentVariableTarget.Process
);
Environment.SetEnvironmentVariable(
"DOTNET_AICOMMITMESSAGE_DISABLE_API",
null,
EnvironmentVariableTarget.Process
);
}
}
}
84 changes: 84 additions & 0 deletions Tests/AiCommitMessage.Tests/Utility/EnvironmentLoaderTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -88,4 +88,88 @@ public void IsApiDisabled_Should_ReturnFalse_When_EnvironmentVariableIsFalse()
);
}
}

/// <summary>
/// Tests that ShouldIgnoreApiErrors returns false when the environment variable is not set.
/// </summary>
[Fact]
public void ShouldIgnoreApiErrors_Should_ReturnFalse_When_EnvironmentVariableNotSet()
{
// Arrange
Environment.SetEnvironmentVariable(
"DOTNET_AICOMMITMESSAGE_IGNORE_API_ERRORS",
null,
EnvironmentVariableTarget.Process
);

// Act
var result = EnvironmentLoader.ShouldIgnoreApiErrors();

// Assert
result.Should().BeFalse();
}

/// <summary>
/// Tests that ShouldIgnoreApiErrors returns true when the environment variable is set to "true".
/// </summary>
[Fact]
public void ShouldIgnoreApiErrors_Should_ReturnTrue_When_EnvironmentVariableIsTrue()
{
// Arrange
Environment.SetEnvironmentVariable(
"DOTNET_AICOMMITMESSAGE_IGNORE_API_ERRORS",
"true",
EnvironmentVariableTarget.Process
);

try
{
// Act
var result = EnvironmentLoader.ShouldIgnoreApiErrors();

// Assert
result.Should().BeTrue();
}
finally
{
// Cleanup
Environment.SetEnvironmentVariable(
"DOTNET_AICOMMITMESSAGE_IGNORE_API_ERRORS",
null,
EnvironmentVariableTarget.Process
);
}
}

/// <summary>
/// Tests that ShouldIgnoreApiErrors returns false when the environment variable is set to "false".
/// </summary>
[Fact]
public void ShouldIgnoreApiErrors_Should_ReturnFalse_When_EnvironmentVariableIsFalse()
{
// Arrange
Environment.SetEnvironmentVariable(
"DOTNET_AICOMMITMESSAGE_IGNORE_API_ERRORS",
"false",
EnvironmentVariableTarget.Process
);

try
{
// Act
var result = EnvironmentLoader.ShouldIgnoreApiErrors();

// Assert
result.Should().BeFalse();
}
finally
{
// Cleanup
Environment.SetEnvironmentVariable(
"DOTNET_AICOMMITMESSAGE_IGNORE_API_ERRORS",
null,
EnvironmentVariableTarget.Process
);
}
}
}
Loading