package platforms import ( "code-review/services/types" "fmt" "log" "strings" ) // GogsEvent Gogs 平台的 webhook 事件 type GogsEvent struct { apiBase string token string Event string // 事件类型 client *httpClient Ref string `json:"ref"` Before string `json:"before"` After string `json:"after"` CompareURL string `json:"compare_url"` Commits []struct { ID string `json:"id"` Message string `json:"message"` URL string `json:"url"` Author Author `json:"author"` Committer Author `json:"committer"` Timestamp string `json:"timestamp"` } `json:"commits"` CommitDetail struct { Files []struct { Filename string `json:"filename"` Status string `json:"status"` Additions int `json:"additions"` Deletions int `json:"deletions"` Patch string `json:"patch"` } `json:"files"` } Repository struct { ID int `json:"id"` Owner Author `json:"owner"` 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"` } `json:"repository"` Pusher Author `json:"pusher"` Sender Author `json:"sender"` } type Author 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"` } // NewGogsEvent 创建 Gogs 事件 func NewGogsEvent(apiBase, token string, event string) *GogsEvent { return &GogsEvent{ apiBase: apiBase, token: token, Event: event, } } func (e *GogsEvent) ExtractChanges() (*types.CodeChanges, error) { // 检查是否有提交记录 if len(e.Commits) == 0 { log.Printf("没有提交记录,跳过代码审查") return nil, nil } // 检查是否跳过代码审查 for _, commit := range e.Commits { if strings.Contains(commit.Message, "[skip codereview]") { log.Printf("跳过代码审查: commit=%s", commit.ID) return nil, nil } } if e.client == nil { e.client = newHTTPClient(e.apiBase, e.token) 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/git/commits/%s.diff?access_token=%s", e.Repository.FullName, commit.ID, e.token) var diffContent string if err := e.client.get(apiPath, &diffContent); err != nil { log.Printf("获取提交详情失败: commit=%s, error=%v", commit.ID, err) continue } // 去除首尾空白 diffContent = strings.TrimSpace(diffContent) if diffContent == "" { log.Printf("提交没有变更内容: commit=%s", commit.ID) continue } // 解析 diff 内容 diffBlocks := strings.Split(diffContent, "diff --git ") // 去除空块 for _, block := range diffBlocks { if strings.TrimSpace(block) == "" { continue } // 移除 'diff --git ' 前缀 block = strings.TrimPrefix(block, "diff --git ") // 解析文件名和变更类型 lines := strings.Split(block, "\n") if len(lines) < 2 { continue } // 获取文件名 filename := "" status := "modified" for _, line := range lines { if strings.HasPrefix(line, "--- a/") { filename = strings.TrimPrefix(line, "--- a/") break } else if strings.HasPrefix(line, "+++ b/") { filename = strings.TrimPrefix(line, "+++ b/") break } } // 如果没有找到文件名,跳过 if filename == "" { continue } // 确定变更类型 if strings.Contains(block, "new file mode") { status = "added" } else if strings.Contains(block, "deleted file mode") { status = "deleted" } else if strings.Contains(block, "rename from") { status = "renamed" } var content strings.Builder content.WriteString(fmt.Sprintf("### 变更说明\n")) content.WriteString(fmt.Sprintf("提交信息: %s\n\n", commit.Message)) switch status { case "added": content.WriteString(fmt.Sprintf("新增文件: %s\n\n", filename)) case "modified": content.WriteString(fmt.Sprintf("修改文件: %s\n\n", filename)) case "deleted": content.WriteString(fmt.Sprintf("删除文件: %s\n\n", filename)) case "renamed": content.WriteString(fmt.Sprintf("重命名文件: %s\n\n", filename)) } content.WriteString("### 变更内容\n") content.WriteString("```diff\n") content.WriteString(block) content.WriteString("\n```\n") changes.Files = append(changes.Files, types.FileChange{ Path: filename, Content: content.String(), Type: parseFileType(status), }) } } return changes, nil } func (e *GogsEvent) PostComments(result *types.ReviewResult) error { if e.client == nil { e.client = newHTTPClient(e.apiBase, e.token) log.Printf("初始化 HTTP 客户端: url=%s", e.apiBase) } // 创建 issue 评论 issueBody := fmt.Sprintf("**代码审查报告**\n\n提交: %s\n\n", e.After) for _, comment := range result.Comments { issueBody += fmt.Sprintf("### 文件: %s\n\n%s\n\n", comment.Path, comment.Content) } if result.Summary != "" { issueBody += fmt.Sprintf("\n### 总结\n\n%s", result.Summary) } // 创建 issue createIssuePath := fmt.Sprintf("/api/v1/repos/%s/issues", e.Repository.FullName) issueData := map[string]interface{}{ "title": fmt.Sprintf("代码审查: %s", e.After[:7]), "body": issueBody, } if err := e.client.post(createIssuePath, issueData); err != nil { log.Printf("创建 issue 失败: path=%s, error=%v", createIssuePath, err) return fmt.Errorf("创建 issue 失败: %w", err) } log.Printf("成功创建 issue: path=%s, commitID=%s", createIssuePath, e.After[:7]) return nil } func (e *GogsEvent) GetPlatform() string { return "gogs" } func parseFileType(t string) types.ChangeType { switch t { case "add": return types.Added case "modify": return types.Modified case "delete": return types.Deleted default: return types.Modified } }