Files
ai-code-review/services/platforms/gitea.go

298 lines
9.0 KiB
Go

package platforms
import (
"code-review/services/types"
"code-review/utils"
"fmt"
"log"
"strings"
)
type GiteaEvent struct {
apiBase string
auth *AuthConfig
Event string
client *httpClient
Secret string `json:"secret"`
Ref string `json:"ref"`
Before string `json:"before"`
After string `json:"after"`
CompareURL string `json:"compare_url"`
Commits []struct {
ID string `json:"sha"`
Message string `json:"commit.message"`
URL string `json:"url"`
HTMLURL string `json:"html_url"`
Created string `json:"created"`
Author struct {
Active bool `json:"active"`
AvatarURL string `json:"avatar_url"`
Created string `json:"created"`
Description string `json:"description"`
Email string `json:"email"`
FollowersCount int `json:"followers_count"`
FollowingCount int `json:"following_count"`
FullName string `json:"full_name"`
HTMLURL string `json:"html_url"`
ID int `json:"id"`
IsAdmin bool `json:"is_admin"`
Language string `json:"language"`
LastLogin string `json:"last_login"`
Location string `json:"location"`
Login string `json:"login"`
LoginName string `json:"login_name"`
ProhibitLogin bool `json:"prohibit_login"`
Restricted bool `json:"restricted"`
SourceID int `json:"source_id"`
Visibility string `json:"visibility"`
Website string `json:"website"`
} `json:"author"`
Committer struct {
Active bool `json:"active"`
AvatarURL string `json:"avatar_url"`
Created string `json:"created"`
Description string `json:"description"`
Email string `json:"email"`
FollowersCount int `json:"followers_count"`
FollowingCount int `json:"following_count"`
FullName string `json:"full_name"`
HTMLURL string `json:"html_url"`
ID int `json:"id"`
IsAdmin bool `json:"is_admin"`
Language string `json:"language"`
LastLogin string `json:"last_login"`
Location string `json:"location"`
Login string `json:"login"`
LoginName string `json:"login_name"`
ProhibitLogin bool `json:"prohibit_login"`
Restricted bool `json:"restricted"`
SourceID int `json:"source_id"`
Visibility string `json:"visibility"`
Website string `json:"website"`
} `json:"committer"`
Commit struct {
Author struct {
Date string `json:"date"`
Email string `json:"email"`
Name string `json:"name"`
} `json:"author"`
Committer struct {
Date string `json:"date"`
Email string `json:"email"`
Name string `json:"name"`
} `json:"committer"`
Message string `json:"message"`
Tree struct {
Created string `json:"created"`
SHA string `json:"sha"`
URL string `json:"url"`
} `json:"tree"`
URL string `json:"url"`
Verification struct {
Payload string `json:"payload"`
Reason string `json:"reason"`
Signature string `json:"signature"`
Signer struct {
Email string `json:"email"`
Name string `json:"name"`
Username string `json:"username"`
} `json:"signer"`
Verified bool `json:"verified"`
} `json:"verification"`
} `json:"commit"`
Files []struct {
Filename string `json:"filename"`
Status string `json:"status"`
} `json:"files"`
Parents []struct {
Created string `json:"created"`
SHA string `json:"sha"`
URL string `json:"url"`
} `json:"parents"`
Stats struct {
Additions int `json:"additions"`
Deletions int `json:"deletions"`
Total int `json:"total"`
} `json:"stats"`
} `json:"commits"`
Repository struct {
ID int `json:"id"`
Name string `json:"name"`
FullName string `json:"full_name"`
Description string `json:"description"`
Private bool `json:"private"`
Fork bool `json:"fork"`
HTMLURL string `json:"html_url"`
SSHURL string `json:"ssh_url"`
CloneURL string `json:"clone_url"`
Website string `json:"website"`
StarsCount int `json:"stars_count"`
ForksCount int `json:"forks_count"`
WatchersCount int `json:"watchers_count"`
OpenIssuesCount int `json:"open_issues_count"`
DefaultBranch string `json:"default_branch"`
CreatedAt string `json:"created_at"`
UpdatedAt string `json:"updated_at"`
Owner struct {
ID int `json:"id"`
Login string `json:"login"`
FullName string `json:"full_name"`
Email string `json:"email"`
AvatarURL string `json:"avatar_url"`
Username string `json:"username"`
} `json:"owner"`
} `json:"repository"`
Pusher struct {
ID int `json:"id"`
Login string `json:"login"`
FullName string `json:"full_name"`
Email string `json:"email"`
AvatarURL string `json:"avatar_url"`
Username string `json:"username"`
} `json:"pusher"`
Sender struct {
ID int `json:"id"`
Login string `json:"login"`
FullName string `json:"full_name"`
Email string `json:"email"`
AvatarURL string `json:"avatar_url"`
Username string `json:"username"`
} `json:"sender"`
}
// NewGiteaEvent 创建 Gitea 事件
func NewGiteaEvent(apiBase string, auth *AuthConfig, event string) *GiteaEvent {
return &GiteaEvent{
apiBase: apiBase,
auth: auth,
Event: event,
}
}
// 定义 Gitea commit 响应的结构
type giteaCommitResponse struct {
Commit struct {
Message string `json:"message"`
Author struct {
Date string `json:"date"`
Email string `json:"email"`
Name string `json:"name"`
} `json:"author"`
Committer struct {
Date string `json:"date"`
Email string `json:"email"`
Name string `json:"name"`
} `json:"committer"`
} `json:"commit"`
Files []struct {
Filename string `json:"filename"`
Status string `json:"status"`
Additions int `json:"additions"`
Deletions int `json:"deletions"`
Changes int `json:"changes"`
Content string `json:"content"`
} `json:"files"`
}
func (e *GiteaEvent) ExtractChanges() (*types.CodeChanges, error) {
// 检查是否有提交记录
if len(e.Commits) == 0 {
log.Printf("没有提交记录,跳过代码审查")
return nil, nil
}
// 检查是否跳过代码审查
for _, commit := range e.Commits {
if strings.Contains(commit.Commit.Message, "[skip codereview]") {
log.Printf("跳过代码审查: commit=%s", commit.ID)
return nil, nil
}
}
if e.client == nil {
e.client = newHTTPClient(e.apiBase, e.auth)
log.Printf("初始化 HTTP 客户端: url=%s", e.apiBase)
}
changes := &types.CodeChanges{
Repository: e.Repository.FullName,
Branch: e.Ref,
CommitID: e.After,
Files: make([]types.FileChange, 0),
}
for _, commit := range e.Commits {
// 直接获取 diff 内容
apiPath := fmt.Sprintf("/api/v1/repos/%s/%s/git/commits/%s", e.Repository.Owner.Login, e.Repository.Name, e.After)
var diffContent giteaCommitResponse
if err := e.client.get(apiPath, &diffContent); err != nil {
log.Printf("获取提交详情失败: commit=%s, error=%v", commit.ID, err)
continue
}
// 处理每个文件的变更
for _, file := range diffContent.Files {
var content strings.Builder
content.WriteString(fmt.Sprintf("### 变更说明\n"))
content.WriteString(fmt.Sprintf("提交信息: %s\n\n", diffContent.Commit.Message))
status := "modified"
switch file.Status {
case "added":
status = "added"
content.WriteString(fmt.Sprintf("新增文件: %s\n\n", file.Filename))
case "removed":
status = "deleted"
content.WriteString(fmt.Sprintf("删除文件: %s\n\n", file.Filename))
case "renamed":
status = "renamed"
content.WriteString(fmt.Sprintf("重命名文件: %s\n\n", file.Filename))
default:
content.WriteString(fmt.Sprintf("修改文件: %s\n\n", file.Filename))
}
if file.Content != "" {
content.WriteString("### 变更内容\n")
content.WriteString("```diff\n")
content.WriteString(file.Content)
content.WriteString("\n```\n")
}
changes.Files = append(changes.Files, types.FileChange{
Path: file.Filename,
Content: content.String(),
Type: utils.ParseFileType(status),
})
}
}
return changes, nil
}
func (e *GiteaEvent) PostComments(result *types.ReviewResult) error {
if e.client == nil {
e.client = newHTTPClient(e.apiBase, e.auth)
}
// 创建 issue
path := fmt.Sprintf("/api/v1/repos/%s/%s/issues", e.Repository.Owner.Login, e.Repository.Name)
issueData := map[string]interface{}{
"title": fmt.Sprintf("AI 代码审查 - %s", e.After[:7]),
"body": utils.FormatReviewResult(result),
}
if err := e.client.post(path, issueData); err != nil {
log.Printf("创建 issue 失败: path=%s, error=%v", path, err)
return fmt.Errorf("创建 issue 失败: %w", err)
}
log.Printf("成功创建 issue: path=%s, commitID=%s", path, e.After[:7])
return nil
}
func (e *GiteaEvent) GetPlatform() string {
return "gitea"
}