576 lines
19 KiB
C#
576 lines
19 KiB
C#
using System;
|
|
using System.Collections.Generic;
|
|
using System.Diagnostics;
|
|
using System.IO;
|
|
using System.Linq;
|
|
using System.Runtime.InteropServices;
|
|
using System.Text;
|
|
using System.Text.Json;
|
|
using System.Text.RegularExpressions;
|
|
using System.Threading;
|
|
using System.Threading.Tasks;
|
|
using System.Windows;
|
|
|
|
namespace AxCopilot.Services.Agent;
|
|
|
|
public class CodeReviewTool : IAgentTool
|
|
{
|
|
private class DiffFile
|
|
{
|
|
public string Path { get; set; } = "";
|
|
|
|
public string Status { get; set; } = "modified";
|
|
|
|
public int Added { get; set; }
|
|
|
|
public int Removed { get; set; }
|
|
|
|
public List<string> Hunks { get; } = new List<string>();
|
|
}
|
|
|
|
private record ReviewIssue(int Line, string Severity, string Message);
|
|
|
|
public string Name => "code_review";
|
|
|
|
public string Description => "코드 리뷰를 수행합니다. Git diff 분석, 파일 정적 검사, PR 요약을 생성합니다.\naction별 기능:\n- diff_review: git diff 출력을 분석하여 이슈/개선점을 구조화\n- file_review: 특정 파일의 코드 품질을 정적 검사\n- pr_summary: 변경사항을 PR 설명 형식으로 요약";
|
|
|
|
public ToolParameterSchema Parameters
|
|
{
|
|
get
|
|
{
|
|
ToolParameterSchema toolParameterSchema = new ToolParameterSchema();
|
|
Dictionary<string, ToolProperty> dictionary = new Dictionary<string, ToolProperty>();
|
|
ToolProperty obj = new ToolProperty
|
|
{
|
|
Type = "string",
|
|
Description = "리뷰 유형: diff_review (diff 분석), file_review (파일 검사), pr_summary (PR 요약)"
|
|
};
|
|
int num = 3;
|
|
List<string> list = new List<string>(num);
|
|
CollectionsMarshal.SetCount(list, num);
|
|
Span<string> span = CollectionsMarshal.AsSpan(list);
|
|
span[0] = "diff_review";
|
|
span[1] = "file_review";
|
|
span[2] = "pr_summary";
|
|
obj.Enum = list;
|
|
dictionary["action"] = obj;
|
|
dictionary["target"] = new ToolProperty
|
|
{
|
|
Type = "string",
|
|
Description = "대상 지정. diff_review: '--staged' 또는 빈값(working tree). file_review: 파일 경로. pr_summary: 브랜치명(선택)."
|
|
};
|
|
ToolProperty obj2 = new ToolProperty
|
|
{
|
|
Type = "string",
|
|
Description = "리뷰 초점: all (전체), bugs (버그), performance (성능), security (보안), style (스타일). 기본 all."
|
|
};
|
|
num = 5;
|
|
List<string> list2 = new List<string>(num);
|
|
CollectionsMarshal.SetCount(list2, num);
|
|
Span<string> span2 = CollectionsMarshal.AsSpan(list2);
|
|
span2[0] = "all";
|
|
span2[1] = "bugs";
|
|
span2[2] = "performance";
|
|
span2[3] = "security";
|
|
span2[4] = "style";
|
|
obj2.Enum = list2;
|
|
dictionary["focus"] = obj2;
|
|
toolParameterSchema.Properties = dictionary;
|
|
num = 1;
|
|
List<string> list3 = new List<string>(num);
|
|
CollectionsMarshal.SetCount(list3, num);
|
|
CollectionsMarshal.AsSpan(list3)[0] = "action";
|
|
toolParameterSchema.Required = list3;
|
|
return toolParameterSchema;
|
|
}
|
|
}
|
|
|
|
public async Task<ToolResult> ExecuteAsync(JsonElement args, AgentContext context, CancellationToken ct = default(CancellationToken))
|
|
{
|
|
if (!((Application.Current as App)?.SettingsService?.Settings.Llm.Code.EnableCodeReview ?? true))
|
|
{
|
|
return ToolResult.Ok("코드 리뷰가 비활성 상태입니다. 설정 → AX Agent → 코드에서 활성화하세요.");
|
|
}
|
|
JsonElement a;
|
|
string action = (args.TryGetProperty("action", out a) ? (a.GetString() ?? "") : "");
|
|
JsonElement t;
|
|
string target = (args.TryGetProperty("target", out t) ? (t.GetString() ?? "") : "");
|
|
JsonElement f;
|
|
string focus = (args.TryGetProperty("focus", out f) ? (f.GetString() ?? "all") : "all");
|
|
if (string.IsNullOrEmpty(context.WorkFolder))
|
|
{
|
|
return ToolResult.Fail("작업 폴더가 설정되어 있지 않습니다.");
|
|
}
|
|
if (1 == 0)
|
|
{
|
|
}
|
|
ToolResult result = action switch
|
|
{
|
|
"diff_review" => await DiffReviewAsync(context, target, focus, ct),
|
|
"file_review" => await FileReviewAsync(context, target, focus, ct),
|
|
"pr_summary" => await PrSummaryAsync(context, target, ct),
|
|
_ => ToolResult.Fail("지원하지 않는 action: " + action + ". diff_review, file_review, pr_summary 중 선택하세요."),
|
|
};
|
|
if (1 == 0)
|
|
{
|
|
}
|
|
return result;
|
|
}
|
|
|
|
private async Task<ToolResult> DiffReviewAsync(AgentContext ctx, string target, string focus, CancellationToken ct)
|
|
{
|
|
string diffResult = await RunGitAsync(args: string.IsNullOrEmpty(target) ? "diff" : ("diff " + target), workDir: ctx.WorkFolder, ct: ct);
|
|
if (diffResult == null)
|
|
{
|
|
return ToolResult.Fail("Git을 찾을 수 없습니다.");
|
|
}
|
|
if (string.IsNullOrWhiteSpace(diffResult))
|
|
{
|
|
return ToolResult.Ok("변경사항이 없습니다. (clean working tree)");
|
|
}
|
|
List<DiffFile> files = ParseDiffFiles(diffResult);
|
|
StringBuilder sb = new StringBuilder();
|
|
sb.AppendLine("═══ Code Review Report (diff_review) ═══\n");
|
|
int totalAdded = 0;
|
|
int totalRemoved = 0;
|
|
foreach (DiffFile file in files)
|
|
{
|
|
totalAdded += file.Added;
|
|
totalRemoved += file.Removed;
|
|
}
|
|
StringBuilder stringBuilder = sb;
|
|
StringBuilder stringBuilder2 = stringBuilder;
|
|
StringBuilder.AppendInterpolatedStringHandler handler = new StringBuilder.AppendInterpolatedStringHandler(26, 3, stringBuilder);
|
|
handler.AppendLiteral("\ud83d\udcca 파일 ");
|
|
handler.AppendFormatted(files.Count);
|
|
handler.AppendLiteral("개 변경 | +");
|
|
handler.AppendFormatted(totalAdded);
|
|
handler.AppendLiteral(" 추가 -");
|
|
handler.AppendFormatted(totalRemoved);
|
|
handler.AppendLiteral(" 삭제\n");
|
|
stringBuilder2.AppendLine(ref handler);
|
|
stringBuilder = sb;
|
|
StringBuilder stringBuilder3 = stringBuilder;
|
|
handler = new StringBuilder.AppendInterpolatedStringHandler(11, 1, stringBuilder);
|
|
handler.AppendLiteral("\ud83d\udd0d 리뷰 초점: ");
|
|
handler.AppendFormatted(focus);
|
|
handler.AppendLiteral("\n");
|
|
stringBuilder3.AppendLine(ref handler);
|
|
foreach (DiffFile file2 in files)
|
|
{
|
|
stringBuilder = sb;
|
|
StringBuilder stringBuilder4 = stringBuilder;
|
|
handler = new StringBuilder.AppendInterpolatedStringHandler(11, 2, stringBuilder);
|
|
handler.AppendLiteral("─── ");
|
|
handler.AppendFormatted(file2.Path);
|
|
handler.AppendLiteral(" (");
|
|
handler.AppendFormatted(file2.Status);
|
|
handler.AppendLiteral(") ───");
|
|
stringBuilder4.AppendLine(ref handler);
|
|
stringBuilder = sb;
|
|
StringBuilder stringBuilder5 = stringBuilder;
|
|
handler = new StringBuilder.AppendInterpolatedStringHandler(9, 2, stringBuilder);
|
|
handler.AppendLiteral(" 변경: +");
|
|
handler.AppendFormatted(file2.Added);
|
|
handler.AppendLiteral(" -");
|
|
handler.AppendFormatted(file2.Removed);
|
|
stringBuilder5.AppendLine(ref handler);
|
|
List<ReviewIssue> issues = AnalyzeDiffHunks(file2.Hunks, focus);
|
|
if (issues.Count > 0)
|
|
{
|
|
foreach (ReviewIssue issue in issues)
|
|
{
|
|
stringBuilder = sb;
|
|
StringBuilder stringBuilder6 = stringBuilder;
|
|
handler = new StringBuilder.AppendInterpolatedStringHandler(12, 3, stringBuilder);
|
|
handler.AppendLiteral(" [");
|
|
handler.AppendFormatted(issue.Severity);
|
|
handler.AppendLiteral("] Line ");
|
|
handler.AppendFormatted(issue.Line);
|
|
handler.AppendLiteral(": ");
|
|
handler.AppendFormatted(issue.Message);
|
|
stringBuilder6.AppendLine(ref handler);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
sb.AppendLine(" [OK] 정적 검사에서 특이사항 없음");
|
|
}
|
|
sb.AppendLine();
|
|
}
|
|
sb.AppendLine("═══ 위 분석 결과를 바탕으로 상세한 코드 리뷰를 작성하세요. ═══");
|
|
return ToolResult.Ok(sb.ToString());
|
|
}
|
|
|
|
private async Task<ToolResult> FileReviewAsync(AgentContext ctx, string target, string focus, CancellationToken ct)
|
|
{
|
|
if (string.IsNullOrEmpty(target))
|
|
{
|
|
return ToolResult.Fail("file_review에는 target(파일 경로)이 필요합니다.");
|
|
}
|
|
string fullPath = (Path.IsPathRooted(target) ? target : Path.Combine(ctx.WorkFolder, target));
|
|
if (!File.Exists(fullPath))
|
|
{
|
|
return ToolResult.Fail("파일을 찾을 수 없습니다: " + target);
|
|
}
|
|
if (!ctx.IsPathAllowed(fullPath))
|
|
{
|
|
return ToolResult.Fail("접근이 차단된 경로입니다: " + target);
|
|
}
|
|
string[] lines = (await File.ReadAllTextAsync(fullPath, ct)).Split('\n');
|
|
StringBuilder sb = new StringBuilder();
|
|
sb.AppendLine("═══ Code Review Report (file_review) ═══\n");
|
|
StringBuilder stringBuilder = sb;
|
|
StringBuilder stringBuilder2 = stringBuilder;
|
|
StringBuilder.AppendInterpolatedStringHandler handler = new StringBuilder.AppendInterpolatedStringHandler(7, 1, stringBuilder);
|
|
handler.AppendLiteral("\ud83d\udcc1 파일: ");
|
|
handler.AppendFormatted(target);
|
|
stringBuilder2.AppendLine(ref handler);
|
|
stringBuilder = sb;
|
|
StringBuilder stringBuilder3 = stringBuilder;
|
|
handler = new StringBuilder.AppendInterpolatedStringHandler(17, 2, stringBuilder);
|
|
handler.AppendLiteral("\ud83d\udccf ");
|
|
handler.AppendFormatted(lines.Length);
|
|
handler.AppendLiteral("줄 | \ud83d\udd0d 초점: ");
|
|
handler.AppendFormatted(focus);
|
|
handler.AppendLiteral("\n");
|
|
stringBuilder3.AppendLine(ref handler);
|
|
List<ReviewIssue> issues = AnalyzeFile(lines, focus);
|
|
if (issues.Count > 0)
|
|
{
|
|
stringBuilder = sb;
|
|
StringBuilder stringBuilder4 = stringBuilder;
|
|
handler = new StringBuilder.AppendInterpolatedStringHandler(13, 1, stringBuilder);
|
|
handler.AppendLiteral("⚠\ufe0f 발견된 이슈 ");
|
|
handler.AppendFormatted(issues.Count);
|
|
handler.AppendLiteral("개:\n");
|
|
stringBuilder4.AppendLine(ref handler);
|
|
foreach (ReviewIssue issue in issues)
|
|
{
|
|
stringBuilder = sb;
|
|
StringBuilder stringBuilder5 = stringBuilder;
|
|
handler = new StringBuilder.AppendInterpolatedStringHandler(12, 3, stringBuilder);
|
|
handler.AppendLiteral(" [");
|
|
handler.AppendFormatted(issue.Severity);
|
|
handler.AppendLiteral("] Line ");
|
|
handler.AppendFormatted(issue.Line);
|
|
handler.AppendLiteral(": ");
|
|
handler.AppendFormatted(issue.Message);
|
|
stringBuilder5.AppendLine(ref handler);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
sb.AppendLine("✅ 정적 검사에서 특이사항 없음");
|
|
}
|
|
sb.AppendLine("\n─── 파일 내용 (처음 200줄) ───");
|
|
string preview = string.Join('\n', lines.Take(200));
|
|
sb.AppendLine(preview);
|
|
if (lines.Length > 200)
|
|
{
|
|
stringBuilder = sb;
|
|
StringBuilder stringBuilder6 = stringBuilder;
|
|
handler = new StringBuilder.AppendInterpolatedStringHandler(11, 1, stringBuilder);
|
|
handler.AppendLiteral("\n... (");
|
|
handler.AppendFormatted(lines.Length - 200);
|
|
handler.AppendLiteral("줄 생략)");
|
|
stringBuilder6.AppendLine(ref handler);
|
|
}
|
|
sb.AppendLine("\n═══ 위 분석 결과와 코드를 바탕으로 상세한 리뷰를 작성하세요. ═══");
|
|
return ToolResult.Ok(sb.ToString());
|
|
}
|
|
|
|
private async Task<ToolResult> PrSummaryAsync(AgentContext ctx, string target, CancellationToken ct)
|
|
{
|
|
StringBuilder sb = new StringBuilder();
|
|
sb.AppendLine("═══ PR Summary Data ═══\n");
|
|
string log = await RunGitAsync(args: string.IsNullOrEmpty(target) ? "log --oneline -20" : ("log --oneline " + target + "..HEAD"), workDir: ctx.WorkFolder, ct: ct);
|
|
if (!string.IsNullOrWhiteSpace(log))
|
|
{
|
|
sb.AppendLine("\ud83d\udccb 커밋 이력:");
|
|
sb.AppendLine(log);
|
|
sb.AppendLine();
|
|
}
|
|
string stat = await RunGitAsync(args: string.IsNullOrEmpty(target) ? "diff --stat" : ("diff --stat " + target + "..HEAD"), workDir: ctx.WorkFolder, ct: ct);
|
|
if (!string.IsNullOrWhiteSpace(stat))
|
|
{
|
|
sb.AppendLine("\ud83d\udcca 변경 통계:");
|
|
sb.AppendLine(stat);
|
|
sb.AppendLine();
|
|
}
|
|
string status = await RunGitAsync(ctx.WorkFolder, "status --short", ct);
|
|
if (!string.IsNullOrWhiteSpace(status))
|
|
{
|
|
sb.AppendLine("\ud83d\udcc1 현재 상태:");
|
|
sb.AppendLine(status);
|
|
sb.AppendLine();
|
|
}
|
|
sb.AppendLine("═══ 위 데이터를 바탕으로 PR 제목과 설명(Summary, Changes, Test Plan)을 작성하세요. ═══");
|
|
return ToolResult.Ok(sb.ToString());
|
|
}
|
|
|
|
private static List<ReviewIssue> AnalyzeDiffHunks(List<string> hunks, string focus)
|
|
{
|
|
List<ReviewIssue> list = new List<ReviewIssue>();
|
|
int num = 0;
|
|
foreach (string hunk in hunks)
|
|
{
|
|
Match match = Regex.Match(hunk, "@@ -\\d+(?:,\\d+)? \\+(\\d+)");
|
|
if (match.Success)
|
|
{
|
|
num = int.Parse(match.Groups[1].Value);
|
|
}
|
|
else
|
|
{
|
|
if (!hunk.StartsWith('+') || hunk.StartsWith("+++"))
|
|
{
|
|
continue;
|
|
}
|
|
num++;
|
|
string text = hunk;
|
|
string text2 = text.Substring(1, text.Length - 1);
|
|
if ((focus == "all" || focus == "bugs") ? true : false)
|
|
{
|
|
if (Regex.IsMatch(text2, "catch\\s*\\{?\\s*\\}"))
|
|
{
|
|
list.Add(new ReviewIssue(num, "WARNING", "빈 catch 블록 — 예외가 무시됩니다"));
|
|
}
|
|
if (Regex.IsMatch(text2, "\\.Result\\b|\\.Wait\\(\\)"))
|
|
{
|
|
list.Add(new ReviewIssue(num, "WARNING", "동기 대기 (.Result/.Wait()) — 데드락 위험"));
|
|
}
|
|
if (Regex.IsMatch(text2, "==\\s*null") && text2.Contains('.'))
|
|
{
|
|
list.Add(new ReviewIssue(num, "INFO", "null 비교 — null 조건 연산자(?.) 사용 검토"));
|
|
}
|
|
}
|
|
if ((focus == "all" || focus == "security") ? true : false)
|
|
{
|
|
if (Regex.IsMatch(text2, "(password|secret|token|api_?key)\\s*=\\s*\"[^\"]+\"", RegexOptions.IgnoreCase))
|
|
{
|
|
list.Add(new ReviewIssue(num, "CRITICAL", "하드코딩된 비밀번호/키 감지"));
|
|
}
|
|
if (Regex.IsMatch(text2, "(TODO|FIXME|HACK|XXX)\\b", RegexOptions.IgnoreCase))
|
|
{
|
|
list.Add(new ReviewIssue(num, "INFO", "TODO/FIXME 마커 발견: " + text2.Trim()));
|
|
}
|
|
}
|
|
if ((focus == "all" || focus == "performance") ? true : false)
|
|
{
|
|
if (Regex.IsMatch(text2, "new\\s+List<.*>\\(\\).*\\.Add\\(") || Regex.IsMatch(text2, "\\.ToList\\(\\).*\\.Where\\("))
|
|
{
|
|
list.Add(new ReviewIssue(num, "INFO", "불필요한 컬렉션 할당 가능성"));
|
|
}
|
|
if (Regex.IsMatch(text2, "string\\s*\\+\\s*=|\".*\"\\s*\\+\\s*\""))
|
|
{
|
|
list.Add(new ReviewIssue(num, "INFO", "문자열 연결 — StringBuilder 사용 검토"));
|
|
}
|
|
}
|
|
bool flag = ((focus == "all" || focus == "style") ? true : false);
|
|
if (flag && text2.Length > 150)
|
|
{
|
|
list.Add(new ReviewIssue(num, "STYLE", $"긴 라인 ({text2.Length}자) — 가독성 저하"));
|
|
}
|
|
}
|
|
}
|
|
return list;
|
|
}
|
|
|
|
private static List<ReviewIssue> AnalyzeFile(string[] lines, string focus)
|
|
{
|
|
List<ReviewIssue> list = new List<ReviewIssue>();
|
|
int num = 0;
|
|
int num2 = 0;
|
|
bool flag = false;
|
|
for (int i = 0; i < lines.Length; i++)
|
|
{
|
|
string text = lines[i];
|
|
int num3 = i + 1;
|
|
string text2 = text.TrimStart();
|
|
if (Regex.IsMatch(text2, "(public|private|protected|internal|static|async|override)\\s+.*\\(.*\\)\\s*\\{?\\s*$") && !text2.Contains(';'))
|
|
{
|
|
flag = true;
|
|
num2 = num3;
|
|
}
|
|
if (text2.Contains('{'))
|
|
{
|
|
num++;
|
|
}
|
|
bool flag4;
|
|
if (text2.Contains('}'))
|
|
{
|
|
num--;
|
|
if (flag && num <= 1)
|
|
{
|
|
int num4 = num3 - num2;
|
|
bool flag2 = num4 > 60;
|
|
bool flag3 = flag2;
|
|
if (flag3)
|
|
{
|
|
flag4 = ((focus == "all" || focus == "style") ? true : false);
|
|
flag3 = flag4;
|
|
}
|
|
if (flag3)
|
|
{
|
|
list.Add(new ReviewIssue(num2, "STYLE", $"긴 메서드 ({num4}줄) — 분할 검토"));
|
|
}
|
|
flag = false;
|
|
}
|
|
}
|
|
if ((focus == "all" || focus == "bugs") ? true : false)
|
|
{
|
|
if (Regex.IsMatch(text2, "catch\\s*(\\(\\s*Exception)?\\s*\\)?\\s*\\{\\s*\\}"))
|
|
{
|
|
list.Add(new ReviewIssue(num3, "WARNING", "빈 catch 블록"));
|
|
}
|
|
if (Regex.IsMatch(text2, "\\.Result\\b|\\.Wait\\(\\)"))
|
|
{
|
|
list.Add(new ReviewIssue(num3, "WARNING", "동기 대기 (.Result/.Wait()) — 데드락 위험"));
|
|
}
|
|
}
|
|
if ((focus == "all" || focus == "security") ? true : false)
|
|
{
|
|
if (Regex.IsMatch(text2, "(password|secret|token|api_?key)\\s*=\\s*\"[^\"]+\"", RegexOptions.IgnoreCase))
|
|
{
|
|
list.Add(new ReviewIssue(num3, "CRITICAL", "하드코딩된 비밀번호/키"));
|
|
}
|
|
if (Regex.IsMatch(text2, "(TODO|FIXME|HACK|XXX)\\b", RegexOptions.IgnoreCase))
|
|
{
|
|
list.Add(new ReviewIssue(num3, "INFO", "마커: " + text2.Trim()));
|
|
}
|
|
}
|
|
flag4 = ((focus == "all" || focus == "performance") ? true : false);
|
|
if (flag4 && Regex.IsMatch(text2, "string\\s*\\+\\s*="))
|
|
{
|
|
list.Add(new ReviewIssue(num3, "INFO", "루프 내 문자열 연결 — StringBuilder 검토"));
|
|
}
|
|
flag4 = ((focus == "all" || focus == "style") ? true : false);
|
|
if (flag4 && text.Length > 150)
|
|
{
|
|
list.Add(new ReviewIssue(num3, "STYLE", $"긴 라인 ({text.Length}자)"));
|
|
}
|
|
}
|
|
bool flag5 = lines.Length > 500;
|
|
bool flag6 = flag5;
|
|
if (flag6)
|
|
{
|
|
bool flag4 = ((focus == "all" || focus == "style") ? true : false);
|
|
flag6 = flag4;
|
|
}
|
|
if (flag6)
|
|
{
|
|
list.Add(new ReviewIssue(1, "STYLE", $"큰 파일 ({lines.Length}줄) — 클래스 분할 검토"));
|
|
}
|
|
return list;
|
|
}
|
|
|
|
private static async Task<string?> RunGitAsync(string workDir, string args, CancellationToken ct)
|
|
{
|
|
string gitPath = FindGit();
|
|
if (gitPath == null)
|
|
{
|
|
return null;
|
|
}
|
|
try
|
|
{
|
|
ProcessStartInfo psi = new ProcessStartInfo(gitPath, args)
|
|
{
|
|
WorkingDirectory = workDir,
|
|
RedirectStandardOutput = true,
|
|
RedirectStandardError = true,
|
|
UseShellExecute = false,
|
|
CreateNoWindow = true,
|
|
StandardOutputEncoding = Encoding.UTF8,
|
|
StandardErrorEncoding = Encoding.UTF8
|
|
};
|
|
using CancellationTokenSource cts = CancellationTokenSource.CreateLinkedTokenSource(ct);
|
|
cts.CancelAfter(TimeSpan.FromSeconds(30.0));
|
|
using Process proc = Process.Start(psi);
|
|
if (proc == null)
|
|
{
|
|
return null;
|
|
}
|
|
string stdout = await proc.StandardOutput.ReadToEndAsync(cts.Token);
|
|
await proc.WaitForExitAsync(cts.Token);
|
|
return (stdout.Length > 12000) ? (stdout.Substring(0, 12000) + "\n... (출력 잘림)") : stdout;
|
|
}
|
|
catch
|
|
{
|
|
return null;
|
|
}
|
|
}
|
|
|
|
private static string? FindGit()
|
|
{
|
|
string[] array = new string[3] { "git", "C:\\Program Files\\Git\\bin\\git.exe", "C:\\Program Files (x86)\\Git\\bin\\git.exe" };
|
|
string[] array2 = array;
|
|
foreach (string text in array2)
|
|
{
|
|
try
|
|
{
|
|
ProcessStartInfo startInfo = new ProcessStartInfo(text, "--version")
|
|
{
|
|
RedirectStandardOutput = true,
|
|
UseShellExecute = false,
|
|
CreateNoWindow = true
|
|
};
|
|
using Process process = Process.Start(startInfo);
|
|
process?.WaitForExit(3000);
|
|
if (process != null && process.ExitCode == 0)
|
|
{
|
|
return text;
|
|
}
|
|
}
|
|
catch
|
|
{
|
|
}
|
|
}
|
|
return null;
|
|
}
|
|
|
|
private static List<DiffFile> ParseDiffFiles(string diff)
|
|
{
|
|
List<DiffFile> list = new List<DiffFile>();
|
|
DiffFile diffFile = null;
|
|
string[] array = diff.Split('\n');
|
|
foreach (string text in array)
|
|
{
|
|
if (text.StartsWith("diff --git"))
|
|
{
|
|
diffFile = new DiffFile();
|
|
list.Add(diffFile);
|
|
string[] array2 = text.Split(" b/");
|
|
diffFile.Path = ((array2.Length > 1) ? array2[1].Trim() : text);
|
|
}
|
|
else
|
|
{
|
|
if (diffFile == null)
|
|
{
|
|
continue;
|
|
}
|
|
if (text.StartsWith("new file"))
|
|
{
|
|
diffFile.Status = "added";
|
|
}
|
|
else if (text.StartsWith("deleted file"))
|
|
{
|
|
diffFile.Status = "deleted";
|
|
}
|
|
else if (text.StartsWith("@@") || text.StartsWith("+") || text.StartsWith("-"))
|
|
{
|
|
diffFile.Hunks.Add(text);
|
|
if (text.StartsWith("+") && !text.StartsWith("+++"))
|
|
{
|
|
diffFile.Added++;
|
|
}
|
|
if (text.StartsWith("-") && !text.StartsWith("---"))
|
|
{
|
|
diffFile.Removed++;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
return list;
|
|
}
|
|
}
|