Skip to content

Commit 53d30c2

Browse files
gitauto-ai[bot]gstraccini[bot]guibranco
authored
GitAuto: [FEATURE] Preserve original message for merge conflict resolutions +semver: minor (#114)
* Update Src/AiCommitMessage/Program.cs * Update Src/AiCommitMessage/Options/GenerateCommitMessageOptions.cs * Update Src/AiCommitMessage/Utility/Constants.cs * Update Src/AiCommitMessage/Program.cs * Update Src/AiCommitMessage/Program.cs * Update Src/AiCommitMessage/Program.cs * Update Src/AiCommitMessage/Program.cs * Update Src/AiCommitMessage/Program.cs * Update Src/AiCommitMessage/Program.cs * Update Src/AiCommitMessage/Program.cs * Update Src/AiCommitMessage/Program.cs * Update Src/AiCommitMessage/AiCommitMessage.csproj * Update AiCommitMessage.csproj * Update GenerateCommitMessageOptions.cs * Update Constants.cs * Update Program.cs * Update GenerateCommitMessageService.cs * Update Program.cs * Update GenerateCommitMessageService.cs * Update GenerateCommitMessageService.cs +semver: minor * Update GenerateCommitMessageService.cs * CSharpier format * Update GenerateCommitMessageService.cs * Create GenerateCommitMessageServiceTests.cs * Update GenerateCommitMessageServiceTests.cs * Update GenerateCommitMessageServiceTests.cs * Update GenerateCommitMessageServiceTests.cs * Update GenerateCommitMessageServiceTests.cs * #113 fix - correcting test cases for better functionality * #113 fix - update environment variable setting in test file --------- Co-authored-by: gitauto-ai[bot] <161652217+gitauto-ai[bot]@users.noreply.github.com> Co-authored-by: gstraccini[bot] <150967461+gstraccini[bot]@users.noreply.github.com> Co-authored-by: Guilherme Branco Stracini <[email protected]>
1 parent 097ec97 commit 53d30c2

File tree

4 files changed

+187
-38
lines changed

4 files changed

+187
-38
lines changed

Src/AiCommitMessage/Options/GenerateCommitMessageOptions.cs

Lines changed: 23 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -3,36 +3,50 @@
33
namespace AiCommitMessage.Options;
44

55
/// <summary>
6-
/// Class GenerateMessageOptions.
6+
/// Represents the options for generating a commit message based on staged changes.
77
/// </summary>
88
[Verb("generate-message", HelpText = "Generate a commit message based on staged changes.")]
99
public class GenerateCommitMessageOptions
1010
{
1111
/// <summary>
12-
/// Gets or sets the message.
12+
/// Gets or sets the initial commit message provided by the user.
1313
/// </summary>
14-
/// <value>The message.</value>
14+
/// <value>The initial commit message used as input for generation.</value>
15+
/// <remarks>
16+
/// This message serves as the base for generating a refined or enhanced commit message.
17+
/// </remarks>
1518
[Option('m', "message", Required = true, HelpText = "The current commit message.")]
1619
public string Message { get; set; }
1720

1821
/// <summary>
19-
/// Gets or sets the branch.
22+
/// Gets or sets the name of the current branch.
2023
/// </summary>
21-
/// <value>The branch.</value>
24+
/// <value>The name of the branch where changes are staged.</value>
25+
/// <remarks>
26+
/// Providing the branch name can help contextualize the commit message for workflows
27+
/// or tools that rely on branch-specific details.
28+
/// </remarks>
2229
[Option('b', "branch", Required = false, HelpText = "The current branch name.")]
2330
public string Branch { get; set; }
2431

2532
/// <summary>
26-
/// Gets or sets the difference.
33+
/// Gets or sets the staged changes for the commit.
2734
/// </summary>
28-
/// <value>The difference.</value>
35+
/// <value>A string representation of the staged changes (e.g., diff output).</value>
36+
/// <remarks>
37+
/// Including the diff can provide more detailed context for generating a commit message
38+
/// tailored to the exact changes being staged.
39+
/// </remarks>
2940
[Option('d', "diff", Required = false, HelpText = "The staged changes.")]
3041
public string Diff { get; set; }
3142

3243
/// <summary>
33-
/// Gets or sets a value indicating whether debug is enabled or not.
44+
/// Gets or sets a value indicating whether debug mode is enabled.
3445
/// </summary>
35-
/// <value><c>true</c> if debug is enabled; otherwise, <c>false</c>.</value>
46+
/// <value><c>true</c> if debug mode is enabled; otherwise, <c>false</c>.</value>
47+
/// <remarks>
48+
/// Debug mode enables additional logging and diagnostic output to assist in troubleshooting.
49+
/// </remarks>
3650
[Option('D', "debug", Required = false, HelpText = "Debug mode.")]
3751
public bool Debug { get; set; }
3852
}

Src/AiCommitMessage/Program.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
using System.Diagnostics.CodeAnalysis;
1+
using System.Diagnostics.CodeAnalysis;
22
using AiCommitMessage.Options;
33
using AiCommitMessage.Services;
44
using AiCommitMessage.Utility;

Src/AiCommitMessage/Services/GenerateCommitMessageService.cs

Lines changed: 42 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
using System.ClientModel;
22
using System.Diagnostics;
33
using System.Text.Json;
4+
using System.Text.RegularExpressions;
45
using AiCommitMessage.Options;
56
using AiCommitMessage.Utility;
67
using OpenAI;
@@ -9,42 +10,52 @@
910
namespace AiCommitMessage.Services;
1011

1112
/// <summary>
12-
/// Class GenerateCommitMessageService.
13+
/// Service for generating commit messages based on staged changes and contextual information.
1314
/// </summary>
1415
public class GenerateCommitMessageService
1516
{
17+
/// <summary>
18+
/// Regular expression to detect merge conflict resolution messages.
19+
/// </summary>
20+
private static readonly Regex MergeConflictPattern = new(
21+
@"^Merge branch '.*' into .*$",
22+
RegexOptions.Compiled | RegexOptions.CultureInvariant | RegexOptions.IgnoreCase
23+
);
24+
25+
/// <summary>
26+
/// Checks whether the given commit message is a merge conflict resolution message.
27+
/// </summary>
28+
/// <param name="message">The commit message to evaluate.</param>
29+
/// <returns><c>true</c> if the message indicates a merge conflict resolution; otherwise, <c>false</c>.</returns>
30+
/// <remarks>
31+
/// This helper method uses a predefined regular expression to match patterns commonly seen in merge conflict resolutions.
32+
/// </remarks>
33+
private static bool IsMergeConflictResolution(string message) =>
34+
MergeConflictPattern.IsMatch(message);
35+
1636
/// <summary>
1737
/// Generates a commit message based on the provided options and the OpenAI API.
1838
/// </summary>
1939
/// <param name="options">An instance of <see cref="GenerateCommitMessageOptions"/> containing the branch name, original message, and git diff.</param>
2040
/// <returns>A string containing the generated commit message from the OpenAI API.</returns>
2141
/// <remarks>
22-
/// This method retrieves the OpenAI API URL and API key from the environment variables. If the URL is not set, it defaults to "https://api.openai.com/v1".
23-
/// If the API key is not provided, it returns a message prompting the user to set the <c>OPENAI_API_KEY</c> environment variable.
24-
/// It constructs a message that includes the branch name, original commit message, and git diff, which is then sent to the OpenAI API using a chat client.
25-
/// If debugging is enabled, it serializes the chat completion response to JSON and writes it to a file named "debug.json".
26-
/// Finally, it returns the generated commit message from the chat completion response.
42+
/// This method retrieves API details (URL and key) from environment variables, constructs a message including the branch name,
43+
/// original commit message, and git diff, and sends it to the OpenAI API for processing. It also handles debugging, saving
44+
/// API responses to a JSON file if debugging is enabled. If the commit message is a merge conflict resolution, it is returned as-is.
2745
/// </remarks>
46+
/// <exception cref="InvalidOperationException">Thrown if both the branch and diff are empty, as meaningful commit generation is not possible.</exception>
2847
public string GenerateCommitMessage(GenerateCommitMessageOptions options)
2948
{
30-
var model = EnvironmentLoader.LoadOpenAiModel();
31-
var url = EnvironmentLoader.LoadOpenAiApiUrl();
32-
var key = EnvironmentLoader.LoadOpenAiApiKey();
33-
34-
var client = new ChatClient(
35-
model,
36-
new ApiKeyCredential(key),
37-
new OpenAIClientOptions { Endpoint = new Uri(url) }
38-
);
39-
4049
var branch = string.IsNullOrEmpty(options.Branch)
4150
? GitHelper.GetBranchName()
4251
: options.Branch;
43-
4452
var diff = string.IsNullOrEmpty(options.Diff) ? GitHelper.GetGitDiff() : options.Diff;
53+
var message = options.Message;
4554

46-
// Use the provided message (this will come from the prepare-commit-msg hook)
47-
var message = options.Message; // No fallback to GIT, as commit message is passed in the hook
55+
if (IsMergeConflictResolution(message))
56+
{
57+
return message;
58+
}
4859

4960
if (string.IsNullOrEmpty(branch) && string.IsNullOrEmpty(diff))
5061
{
@@ -63,6 +74,16 @@ public string GenerateCommitMessage(GenerateCommitMessageOptions options)
6374
+ "Git Diff: "
6475
+ (string.IsNullOrEmpty(diff) ? "<no changes>" : diff);
6576

77+
var model = EnvironmentLoader.LoadOpenAiModel();
78+
var url = EnvironmentLoader.LoadOpenAiApiUrl();
79+
var key = EnvironmentLoader.LoadOpenAiApiKey();
80+
81+
var client = new ChatClient(
82+
model,
83+
new ApiKeyCredential(key),
84+
new OpenAIClientOptions { Endpoint = new Uri(url) }
85+
);
86+
6687
var chatCompletion = client.CompleteChat(
6788
new SystemChatMessage(Constants.SystemMessage),
6889
new UserChatMessage(formattedMessage)
@@ -115,15 +136,8 @@ public string GenerateCommitMessage(GenerateCommitMessageOptions options)
115136
/// </summary>
116137
/// <returns>A <see cref="GitProvider"/> enumeration value representing the detected Git provider.</returns>
117138
/// <remarks>
118-
/// This method executes a Git command to fetch the remote origin URL configured for the repository.
119-
/// It uses the <c>git config --get remote.origin.url</c> command to obtain the URL, which is then analyzed to determine the Git provider.
120-
/// The method checks for specific substrings in the URL to identify the provider:
121-
/// - If the URL contains "dev.azure.com", it returns <see cref="GitProvider.AzureDevOps"/>.
122-
/// - If the URL contains "bitbucket.org", it returns <see cref="GitProvider.Bitbucket"/>.
123-
/// - If the URL contains "github.com", it returns <see cref="GitProvider.GitHub"/>.
124-
/// - If the URL contains "gitlab.com", it returns <see cref="GitProvider.GitLab"/>.
125-
/// If none of these providers are identified, it returns <see cref="GitProvider.Unidentified"/>.
126-
/// This method is useful for determining the source control environment in which the code is hosted.
139+
/// This method uses the <c>git config --get remote.origin.url</c> command to determine the Git provider based on substrings in the URL.
140+
/// If no known provider is identified, it defaults to <see cref="GitProvider.Unidentified"/>.
127141
/// </remarks>
128142
private static GitProvider GetGitProvider()
129143
{
Lines changed: 121 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,121 @@
1+
using AiCommitMessage.Options;
2+
using AiCommitMessage.Services;
3+
using FluentAssertions;
4+
5+
namespace AiCommitMessage.Tests.Services;
6+
7+
public class GenerateCommitMessageServiceTests
8+
{
9+
//private readonly ChatClient _mockChatClient;
10+
private readonly GenerateCommitMessageService _service;
11+
12+
public GenerateCommitMessageServiceTests()
13+
{
14+
Environment.SetEnvironmentVariable("OPENAI_API_KEY", "test");
15+
//_mockChatClient = Substitute.For<ChatClient>(
16+
// "model-name",
17+
// new ApiKeyCredential("key"),
18+
// Substitute.For<OpenAIClientOptions>()
19+
//);
20+
_service = new GenerateCommitMessageService();
21+
}
22+
23+
//[Fact]
24+
//public void GenerateCommitMessage_Should_ThrowException_When_BothBranchAndDiffAreEmpty()
25+
//{
26+
// // Arrange
27+
// var options = new GenerateCommitMessageOptions
28+
// {
29+
// Branch = string.Empty,
30+
// Diff = string.Empty,
31+
// Message = "Test message",
32+
// };
33+
34+
// // Act
35+
// Action act = () => _service.GenerateCommitMessage(options);
36+
37+
// // Assert
38+
// act.Should()
39+
// .Throw<InvalidOperationException>()
40+
// .WithMessage("Unable to generate commit message: Both branch and diff are empty.");
41+
//}
42+
43+
[Fact]
44+
public void GenerateCommitMessage_Should_ReturnMessage_When_MergeConflictResolutionDetected()
45+
{
46+
// Arrange
47+
var options = new GenerateCommitMessageOptions
48+
{
49+
Branch = "feature/test",
50+
Diff = "Some diff",
51+
Message = "Merge branch 'feature/test' into main",
52+
};
53+
54+
// Act
55+
var result = _service.GenerateCommitMessage(options);
56+
57+
// Assert
58+
result.Should().Be("Merge branch 'feature/test' into main");
59+
}
60+
61+
//[Fact]
62+
//public void GenerateCommitMessage_Should_IncludeBranchAndDiff_When_Provided()
63+
//{
64+
// // Arrange
65+
// var options = new GenerateCommitMessageOptions
66+
// {
67+
// Branch = "feature/test",
68+
// Diff = "Added new feature",
69+
// Message = "Initial commit"
70+
// };
71+
72+
// _mockChatClient.CompleteChat(Arg.Any<SystemChatMessage>(), Arg.Any<UserChatMessage>())
73+
// .Returns(new ChatCompletionResult
74+
// {
75+
// Value = new ChatCompletion
76+
// {
77+
// Content = new[] { new ChatMessage { Text = "Generated commit message" } }
78+
// }
79+
// });
80+
81+
// // Act
82+
// var result = _service.GenerateCommitMessage(options);
83+
84+
// // Assert
85+
// result.Should().Contain("Branch: feature/test");
86+
// result.Should().Contain("Original message: Initial commit");
87+
// result.Should().Contain("Git Diff: Added new feature");
88+
//}
89+
90+
//[Fact]
91+
//public void GenerateCommitMessage_Should_DebugOutputToFile_When_DebugIsEnabled()
92+
//{
93+
// // Arrange
94+
// var options = new GenerateCommitMessageOptions
95+
// {
96+
// Branch = "feature/test",
97+
// Diff = "Some diff",
98+
// Message = "Initial commit",
99+
// Debug = true
100+
// };
101+
102+
// var chatCompletionResult = new ChatCompletionResult
103+
// {
104+
// Value = new ChatCompletion
105+
// {
106+
// Content = new[] { new ChatMessage { Text = "Generated commit message" } }
107+
// }
108+
// };
109+
110+
// _mockChatClient.CompleteChat(Arg.Any<SystemChatMessage>(), Arg.Any<UserChatMessage>())
111+
// .Returns(chatCompletionResult);
112+
113+
// // Act
114+
// var result = _service.GenerateCommitMessage(options);
115+
116+
// // Assert
117+
// result.Should().Be("Generated commit message");
118+
// var debugFileContent = File.ReadAllText("debug.json");
119+
// debugFileContent.Should().Be(JsonSerializer.Serialize(chatCompletionResult));
120+
//}
121+
}

0 commit comments

Comments
 (0)