#feature: add AI log analysis & some bugfix

This commit is contained in:
2026-01-14 13:58:50 +08:00
parent e479ed02ea
commit ae6c169f5f
33 changed files with 3898 additions and 164 deletions

View File

@@ -66,12 +66,17 @@ class GitMonitorService
}
try {
$version = $this->jiraService->getUpcomingReleaseVersion($projectKey);
// 先从 master 分支获取当前版本号
$currentVersion = $this->getMasterVersion($repoKey, $repoConfig);
// 根据当前版本号获取下一个版本
$version = $this->jiraService->getUpcomingReleaseVersion($projectKey, $currentVersion);
if ($version) {
$payload['repositories'][$repoKey] = [
'version' => $version['version'],
'release_date' => $version['release_date'],
'branch' => 'release/' . $version['version'],
'current_version' => $currentVersion,
];
}
} catch (\Throwable $e) {
@@ -181,16 +186,23 @@ class GitMonitorService
];
foreach ($commits as $commit) {
if ($this->isDevelopMerge($path, $commit)) {
$isMerge = $this->isMergeCommit($path, $commit);
$isConflictResolution = $this->isConflictResolution($path, $commit);
// 只检测直接从 develop 合并的情况
if ($isMerge && $this->isDevelopMerge($path, $commit)) {
$issues['develop_merges'][] = $this->getCommitMetadata($path, $commit);
}
$missingFunctions = $this->detectMissingFunctions($path, $commit);
if (!empty($missingFunctions)) {
$issues['missing_functions'][] = [
'commit' => $this->getCommitMetadata($path, $commit),
'details' => $missingFunctions,
];
// 只在 merge 提交或冲突解决提交中检测缺失函数
if ($isMerge || $isConflictResolution) {
$missingFunctions = $this->detectMissingFunctions($path, $commit);
if (!empty($missingFunctions)) {
$issues['missing_functions'][] = [
'commit' => $this->getCommitMetadata($path, $commit),
'details' => $missingFunctions,
];
}
}
}
@@ -239,34 +251,52 @@ class GitMonitorService
return array_values(array_filter(array_map('trim', explode("\n", $output))));
}
private function isDevelopMerge(string $repoPath, string $commit): bool
/**
* 判断是否为 merge 提交(有多个父提交)
*/
private function isMergeCommit(string $repoPath, string $commit): bool
{
$parents = trim($this->runGit($repoPath, ['git', 'show', '-s', '--pretty=%P', $commit]));
$parentShas = array_values(array_filter(explode(' ', $parents)));
if (count($parentShas) < 2) {
return false;
return count($parentShas) >= 2;
}
/**
* 判断是否为冲突解决提交
* 通过检查 commit message 是否包含冲突相关关键词
*/
private function isConflictResolution(string $repoPath, string $commit): bool
{
$message = strtolower($this->runGit($repoPath, ['git', 'show', '-s', '--pretty=%s', $commit]));
$conflictKeywords = ['conflict', 'resolve', '冲突', '解决冲突'];
foreach ($conflictKeywords as $keyword) {
if (str_contains($message, $keyword)) {
return true;
}
}
return false;
}
/**
* 判断是否为直接从 develop 分支合并到 release 的提交
* 只检测 commit message 明确包含 "merge.*develop" "develop.*into" 的情况
*/
private function isDevelopMerge(string $repoPath, string $commit): bool
{
$message = strtolower($this->runGit($repoPath, ['git', 'show', '-s', '--pretty=%s', $commit]));
if (str_contains($message, self::DEVELOP_BRANCH)) {
// 检测 "Merge branch 'develop'" 或 "merge develop into" 等模式
// 排除 feature/xxx、hotfix/xxx 等分支的合并
if (preg_match("/merge\s+(branch\s+)?['\"]?develop['\"]?(\s+into)?/i", $message)) {
return true;
}
$target = 'origin/' . self::DEVELOP_BRANCH;
foreach ($parentShas as $parent) {
$branches = $this->runGit($repoPath, ['git', 'branch', '-r', '--contains', $parent]);
foreach (preg_split('/\R/', $branches) as $branchLine) {
$branchLine = trim(str_replace('*', '', $branchLine));
if (empty($branchLine)) {
continue;
}
if ($branchLine === $target || str_ends_with($branchLine, '/' . self::DEVELOP_BRANCH)) {
return true;
}
}
// 检测 "develop into release" 模式
if (preg_match("/develop\s+into\s+['\"]?release/i", $message)) {
return true;
}
return false;
@@ -331,11 +361,15 @@ class GitMonitorService
private function extractFunctionName(string $line): ?string
{
$trimmed = trim($line);
if (str_starts_with($trimmed, '//') || str_starts_with($trimmed, '#') || str_starts_with($trimmed, '*')) {
// 跳过注释行
if (str_starts_with($trimmed, '//') || str_starts_with($trimmed, '#') || str_starts_with($trimmed, '*') || str_starts_with($trimmed, '/*')) {
return null;
}
if (preg_match('/function\s+([A-Za-z0-9_]+)\s*\(/', $trimmed, $matches)) {
// 只匹配真正的函数定义,必须以 function 关键字开头,或者以访问修饰符开头
// 匹配: function foo(, public function foo(, private static function foo( 等
if (preg_match('/^(?:(?:public|protected|private)\s+)?(?:static\s+)?function\s+([A-Za-z_][A-Za-z0-9_]*)\s*\(/', $trimmed, $matches)) {
return $matches[1];
}
@@ -495,4 +529,29 @@ class GitMonitorService
$directory = $repoConfig['directory'] ?? $repoKey;
return $this->projectsPath . '/' . ltrim($directory, '/');
}
/**
* master 分支读取 version.txt 获取当前版本号
*/
public function getMasterVersion(string $repoKey, array $repoConfig): ?string
{
$path = $this->resolveProjectPath($repoKey, $repoConfig);
if (!is_dir($path) || !is_dir($path . DIRECTORY_SEPARATOR . '.git')) {
Log::warning('Invalid git repository path', ['repository' => $repoKey, 'path' => $path]);
return null;
}
try {
$this->runGit($path, ['git', 'fetch', 'origin', 'master']);
$version = $this->runGit($path, ['git', 'show', 'origin/master:version.txt']);
return trim($version) ?: null;
} catch (ProcessFailedException $e) {
Log::warning('Failed to read version.txt from master branch', [
'repository' => $repoKey,
'error' => $e->getMessage(),
]);
return null;
}
}
}