319 lines
9.7 KiB
C#
Executable File
319 lines
9.7 KiB
C#
Executable File
using LibGit2Sharp;
|
|
using Microsoft.Extensions.Logging;
|
|
using System;
|
|
using System.Collections.Generic;
|
|
using System.Linq;
|
|
using System.Text;
|
|
using System.Threading.Tasks;
|
|
|
|
namespace MarketAlly.AIPlugin.Learning
|
|
{
|
|
public class GitManager
|
|
{
|
|
private readonly string _solutionPath;
|
|
private readonly string _repositoryPath;
|
|
private readonly ILogger<GitManager> _logger;
|
|
private readonly string _correlationId;
|
|
|
|
public GitManager(string solutionPath, ILogger<GitManager>? logger = null)
|
|
{
|
|
_solutionPath = solutionPath;
|
|
_repositoryPath = FindRepositoryRoot(solutionPath);
|
|
_logger = logger ?? CreateNullLogger();
|
|
_correlationId = Guid.NewGuid().ToString("N")[..8];
|
|
|
|
_logger.LogInformation("GitManager initialized for repository: {RepositoryPath} [CorrelationId: {CorrelationId}]",
|
|
_repositoryPath, _correlationId);
|
|
}
|
|
|
|
private static ILogger<GitManager> CreateNullLogger()
|
|
{
|
|
using var loggerFactory = LoggerFactory.Create(builder => { });
|
|
return loggerFactory.CreateLogger<GitManager>();
|
|
}
|
|
|
|
private string FindRepositoryRoot(string startPath)
|
|
{
|
|
try
|
|
{
|
|
var directory = new DirectoryInfo(Path.GetDirectoryName(startPath));
|
|
|
|
while (directory != null)
|
|
{
|
|
if (Directory.Exists(Path.Combine(directory.FullName, ".git")))
|
|
{
|
|
_logger?.LogDebug("Found Git repository at: {RepositoryPath} [CorrelationId: {CorrelationId}]",
|
|
directory.FullName, _correlationId);
|
|
return directory.FullName;
|
|
}
|
|
directory = directory.Parent;
|
|
}
|
|
|
|
_logger?.LogError("Git repository not found starting from: {StartPath} [CorrelationId: {CorrelationId}]",
|
|
startPath, _correlationId);
|
|
throw new InvalidOperationException("Not a Git repository or no Git repository found in parent directories");
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
_logger?.LogError(ex, "Error finding Git repository root [CorrelationId: {CorrelationId}]", _correlationId);
|
|
throw;
|
|
}
|
|
}
|
|
|
|
public async Task<GitBranchInfo> SetupLearningBranchesAsync(Guid sessionId)
|
|
{
|
|
_logger.LogInformation("🌿 Setting up learning branches for session: {SessionId} [CorrelationId: {CorrelationId}]",
|
|
sessionId, _correlationId);
|
|
|
|
var gitInfo = new GitBranchInfo
|
|
{
|
|
SessionId = sessionId,
|
|
CreatedAt = DateTime.UtcNow
|
|
};
|
|
|
|
try
|
|
{
|
|
using var repo = new Repository(_repositoryPath);
|
|
|
|
// Check if working directory is clean
|
|
_logger.LogDebug("Checking working directory status [CorrelationId: {CorrelationId}]", _correlationId);
|
|
var status = repo.RetrieveStatus();
|
|
if (status.IsDirty)
|
|
{
|
|
_logger.LogWarning("Working directory is dirty - uncommitted changes detected [CorrelationId: {CorrelationId}]", _correlationId);
|
|
gitInfo.Success = false;
|
|
gitInfo.Error = "Working directory has uncommitted changes. Please commit or stash changes before running learning session.";
|
|
return gitInfo;
|
|
}
|
|
|
|
// Store original branch
|
|
gitInfo.OriginalBranch = repo.Head.FriendlyName;
|
|
gitInfo.OriginalCommit = repo.Head.Tip.Sha;
|
|
_logger.LogInformation("Current branch: {Branch}, commit: {Commit} [CorrelationId: {CorrelationId}]",
|
|
gitInfo.OriginalBranch, gitInfo.OriginalCommit[..8], _correlationId);
|
|
|
|
// Ensure we're on master/main
|
|
var mainBranch = repo.Branches["main"] ?? repo.Branches["master"];
|
|
if (mainBranch == null)
|
|
{
|
|
_logger.LogError("Could not find main or master branch [CorrelationId: {CorrelationId}]", _correlationId);
|
|
gitInfo.Error = "Could not find main or master branch";
|
|
gitInfo.Success = false;
|
|
return gitInfo;
|
|
}
|
|
|
|
Commands.Checkout(repo, mainBranch);
|
|
|
|
// Create or switch to AI branch
|
|
var aiBranchName = "ai-refactoring";
|
|
var aiBranch = repo.Branches[aiBranchName];
|
|
|
|
if (aiBranch == null)
|
|
{
|
|
aiBranch = repo.CreateBranch(aiBranchName);
|
|
Console.WriteLine($"🌿 Created AI branch: {aiBranchName}");
|
|
}
|
|
|
|
Commands.Checkout(repo, aiBranch);
|
|
gitInfo.AIBranch = aiBranchName;
|
|
|
|
// Create session branch
|
|
var sessionDate = DateTime.Now.ToString("yyyy-MM-dd");
|
|
var sessionBranchName = $"ai-refactoring-{sessionDate}";
|
|
|
|
// If session branch exists, create unique name
|
|
if (repo.Branches[sessionBranchName] != null)
|
|
{
|
|
var timestamp = DateTime.Now.ToString("HHmmss");
|
|
sessionBranchName = $"ai-refactoring-{sessionDate}-{timestamp}";
|
|
}
|
|
|
|
var sessionBranch = repo.CreateBranch(sessionBranchName);
|
|
Commands.Checkout(repo, sessionBranch);
|
|
gitInfo.SessionBranch = sessionBranchName;
|
|
|
|
// Create failed attempts branch
|
|
var failedBranchName = $"failed-attempts-{sessionDate}";
|
|
if (repo.Branches[failedBranchName] != null)
|
|
{
|
|
var timestamp = DateTime.Now.ToString("HHmmss");
|
|
failedBranchName = $"failed-attempts-{sessionDate}-{timestamp}";
|
|
}
|
|
|
|
var failedBranch = repo.CreateBranch(failedBranchName);
|
|
gitInfo.FailedAttemptsBranch = failedBranchName;
|
|
|
|
// Switch back to session branch
|
|
Commands.Checkout(repo, sessionBranch);
|
|
|
|
gitInfo.Success = true;
|
|
Console.WriteLine($"🌿 Git setup complete:");
|
|
Console.WriteLine($" AI Branch: {gitInfo.AIBranch}");
|
|
Console.WriteLine($" Session Branch: {gitInfo.SessionBranch}");
|
|
Console.WriteLine($" Failed Attempts Branch: {gitInfo.FailedAttemptsBranch}");
|
|
|
|
return gitInfo;
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
gitInfo.Success = false;
|
|
gitInfo.Error = ex.Message;
|
|
return gitInfo;
|
|
}
|
|
}
|
|
|
|
public async Task<bool> CommitSuccessfulIterationAsync(int iterationNumber, string summary)
|
|
{
|
|
try
|
|
{
|
|
using var repo = new Repository(_repositoryPath);
|
|
var status = repo.RetrieveStatus();
|
|
|
|
if (!status.IsDirty)
|
|
{
|
|
return false; // Nothing to commit
|
|
}
|
|
|
|
// Stage all changes
|
|
Commands.Stage(repo, "*");
|
|
|
|
// Create commit
|
|
var signature = new Signature("AI Learning System", "ai@learning.system", DateTimeOffset.Now);
|
|
var message = $"AI Learning Iteration {iterationNumber}: {summary}";
|
|
|
|
repo.Commit(message, signature, signature);
|
|
Console.WriteLine($"✅ Committed iteration {iterationNumber}: {summary}");
|
|
|
|
return await Task.FromResult(true);
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
Console.WriteLine($"❌ Failed to commit iteration {iterationNumber}: {ex.Message}");
|
|
return false;
|
|
}
|
|
}
|
|
|
|
public async Task<bool> CommitFailedAttemptAsync(FailedAttempt attempt)
|
|
{
|
|
try
|
|
{
|
|
using var repo = new Repository(_repositoryPath);
|
|
|
|
// Switch to failed attempts branch
|
|
var failedBranch = repo.Branches.FirstOrDefault(b => b.FriendlyName.StartsWith("failed-attempts-"));
|
|
if (failedBranch != null)
|
|
{
|
|
Commands.Checkout(repo, failedBranch);
|
|
|
|
var status = repo.RetrieveStatus();
|
|
if (status.IsDirty)
|
|
{
|
|
// Stage all changes
|
|
Commands.Stage(repo, "*");
|
|
|
|
// Create commit
|
|
var signature = new Signature("AI Learning System", "ai@learning.system", DateTimeOffset.Now);
|
|
var message = $"Failed Attempt {attempt.AttemptNumber}: {attempt.FixApproach} on {Path.GetFileName(attempt.FilePath)} - {attempt.Error}";
|
|
|
|
repo.Commit(message, signature, signature);
|
|
}
|
|
|
|
// Switch back to session branch
|
|
var sessionBranch = repo.Branches.FirstOrDefault(b => b.FriendlyName.StartsWith("ai-refactoring-") && b.FriendlyName.Contains(DateTime.Now.ToString("yyyy-MM-dd")));
|
|
if (sessionBranch != null)
|
|
{
|
|
Commands.Checkout(repo, sessionBranch);
|
|
}
|
|
}
|
|
|
|
return await Task.FromResult(true);
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
Console.WriteLine($"⚠️ Failed to commit failed attempt: {ex.Message}");
|
|
return false;
|
|
}
|
|
}
|
|
|
|
public async Task<bool> RollbackLastChangeAsync()
|
|
{
|
|
try
|
|
{
|
|
using var repo = new Repository(_repositoryPath);
|
|
|
|
// Reset to HEAD (undo working directory changes)
|
|
repo.Reset(ResetMode.Hard);
|
|
|
|
Console.WriteLine("🔄 Rolled back last changes");
|
|
return await Task.FromResult(true);
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
Console.WriteLine($"❌ Failed to rollback: {ex.Message}");
|
|
return false;
|
|
}
|
|
}
|
|
|
|
public async Task<bool> MergeToAIBranchIfStable()
|
|
{
|
|
// This would be called after successful compilation
|
|
// For now, we'll merge at the end of session instead
|
|
return await Task.FromResult(true);
|
|
}
|
|
|
|
public async Task<GitOperationResult> FinalMergeToAIBranchAsync()
|
|
{
|
|
try
|
|
{
|
|
using var repo = new Repository(_repositoryPath);
|
|
|
|
var aiBranch = repo.Branches["ai-refactoring"];
|
|
var sessionBranch = repo.Head;
|
|
|
|
if (aiBranch == null)
|
|
{
|
|
return new GitOperationResult { Success = false, Message = "AI branch not found" };
|
|
}
|
|
|
|
// Switch to AI branch
|
|
Commands.Checkout(repo, aiBranch);
|
|
|
|
// Merge session branch
|
|
var mergeResult = repo.Merge(sessionBranch, new Signature("AI Learning System", "ai@learning.system", DateTimeOffset.Now));
|
|
|
|
if (mergeResult.Status == MergeStatus.Conflicts)
|
|
{
|
|
return new GitOperationResult { Success = false, Message = "Merge conflicts detected - requires manual resolution" };
|
|
}
|
|
|
|
return new GitOperationResult { Success = true, Message = $"Successfully merged session to AI branch" };
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
return new GitOperationResult { Success = false, Message = ex.Message };
|
|
}
|
|
}
|
|
}
|
|
|
|
public class GitBranchInfo
|
|
{
|
|
public Guid SessionId { get; set; }
|
|
public DateTime CreatedAt { get; set; }
|
|
public bool Success { get; set; }
|
|
public string Error { get; set; }
|
|
public string OriginalBranch { get; set; }
|
|
public string OriginalCommit { get; set; }
|
|
public string AIBranch { get; set; }
|
|
public string SessionBranch { get; set; }
|
|
public string FailedAttemptsBranch { get; set; }
|
|
public bool FinalMergeSuccess { get; set; }
|
|
public string FinalMergeMessage { get; set; }
|
|
}
|
|
|
|
public class GitOperationResult
|
|
{
|
|
public bool Success { get; set; }
|
|
public string Message { get; set; }
|
|
}
|
|
}
|