适配gitea 取消gogs(没有对应接口支持)

This commit is contained in:
Hua
2025-04-02 14:28:56 +08:00
parent 7ec5abe67f
commit 8480656ca4
9 changed files with 297 additions and 379 deletions

View File

@ -57,6 +57,10 @@ type GitConfig struct {
Name string `mapstructure:"name"` // 平台名称
Type string `mapstructure:"type"` // 平台类型
Token string `mapstructure:"token"` // 访问令牌
Username string `mapstructure:"username"` // 用户名(用于基本认证)
Password string `mapstructure:"password"` // 密码(用于基本认证)
SudoUser string `mapstructure:"sudo_user"` // sudo 用户
TOTP string `mapstructure:"totp"` // TOTP 令牌
Secret string `mapstructure:"webhook_secret"` // webhook 密钥
APIBase string `mapstructure:"api_base"` // API 基础 URL
SignatureHeader string `mapstructure:"signature_header"` // webhook 签名的 header 名称
@ -148,7 +152,10 @@ func Save(newConfig *Config) error {
if err := yamlEncoder.Encode(configMap); err != nil {
// 如果保存失败,恢复备份
if _, err := os.Stat(backupFile); err == nil {
os.Rename(backupFile, "config.yaml")
err := os.Rename(backupFile, "config.yaml")
if err != nil {
return err
}
}
return fmt.Errorf("save config failed: %w", err)
}
@ -188,6 +195,10 @@ func (c *Config) ToMap() map[string]interface{} {
"name": platform.Name,
"type": platform.Type,
"token": platform.Token,
"username": platform.Username,
"password": platform.Password,
"sudo_user": platform.SudoUser,
"totp": platform.TOTP,
"webhook_secret": platform.Secret,
"api_base": platform.APIBase,
"signature_header": platform.SignatureHeader,
@ -237,6 +248,7 @@ func (c *Config) ToMapHtml() map[string]interface{} {
"name": platform.Name,
"type": platform.Type,
"token": platform.Token,
"username": platform.Username,
"webhook_secret": platform.Secret,
"api_base": platform.APIBase,
"signature_header": platform.SignatureHeader,

View File

@ -112,29 +112,36 @@ func (h *WebhookHandler) parseWebhookEvent(c *gin.Context, platform string) (ser
return nil, fmt.Errorf("未找到事件类型 header: %s", platformConfig.EventHeader)
}
// 创建认证配置
auth := &platforms.AuthConfig{
Token: platformConfig.Token,
Username: platformConfig.Username,
Password: platformConfig.Password,
SudoUser: platformConfig.SudoUser,
TOTP: platformConfig.TOTP,
UseBasicAuth: platformConfig.Username != "" && platformConfig.Password != "",
UseSudoHeader: platformConfig.SudoUser != "",
UseSudoParam: platformConfig.SudoUser != "",
UseTOTPHeader: platformConfig.TOTP != "",
}
var event services.WebhookEvent
switch platformConfig.Type {
case "gogs":
event = platforms.NewGogsEvent(
platformConfig.APIBase,
platformConfig.Token,
eventType,
)
case "gitea":
event = platforms.NewGiteaEvent(
platformConfig.APIBase,
platformConfig.Token,
auth,
eventType,
)
// case "gitee":
// event = &platforms.GiteeEvent{
// ApiBase: platformConfig.APIBase,
// Token: platformConfig.Token,
// }
// event = platforms.NewGiteeEvent(
// platformConfig.APIBase,
// auth,
// )
case "gitlab":
event = platforms.NewGitlabEvent(
platformConfig.APIBase,
platformConfig.Token,
auth,
eventType,
)
default:

View File

@ -9,20 +9,43 @@ import (
"net/http"
)
type AuthConfig struct {
Token string
Username string
Password string
SudoUser string
TOTP string
UseBasicAuth bool
UseSudoHeader bool
UseSudoParam bool
UseTOTPHeader bool
}
type httpClient struct {
url string
token string
auth *AuthConfig
client *http.Client
}
func newHTTPClient(baseURL, token string) *httpClient {
func newHTTPClient(baseURL string, authConfig *AuthConfig) *httpClient {
return &httpClient{
url: baseURL,
token: token,
auth: authConfig,
client: &http.Client{},
}
}
func (c *httpClient) setAuthHeaders(req *http.Request) {
if c.auth == nil {
return
}
// 设置 Token 认证
if c.auth.Token != "" {
req.Header.Set("Authorization", "token "+c.auth.Token)
}
}
func (c *httpClient) get(path string, result interface{}) error {
url := fmt.Sprintf("%s%s", c.url, path)
log.Printf("发送 GET 请求: url=%s", url)
@ -34,9 +57,7 @@ func (c *httpClient) get(path string, result interface{}) error {
}
req.Header.Set("Content-Type", "application/json")
if c.token != "" {
req.Header.Set("Authorization", "token "+c.token)
}
c.setAuthHeaders(req)
resp, err := c.client.Do(req)
if err != nil {
@ -77,9 +98,7 @@ func (c *httpClient) post(path string, data interface{}) error {
}
req.Header.Set("Content-Type", "application/json")
if c.token != "" {
req.Header.Set("Authorization", "token "+c.token)
}
c.setAuthHeaders(req)
resp, err := c.client.Do(req)
if err != nil {
@ -113,6 +132,8 @@ func (c *httpClient) getWithHeaders(path string, result interface{}, headers map
req.Header.Set(key, value)
}
c.setAuthHeaders(req)
resp, err := c.client.Do(req)
if err != nil {
log.Printf("发送 GET 请求失败: url=%s, error=%v", url, err)
@ -156,6 +177,8 @@ func (c *httpClient) postWithHeaders(path string, data interface{}, headers map[
req.Header.Set(key, value)
}
c.setAuthHeaders(req)
resp, err := c.client.Do(req)
if err != nil {
log.Printf("发送 POST 请求失败: url=%s, error=%v", url, err)

View File

@ -10,40 +10,191 @@ import (
type GiteaEvent struct {
apiBase string
token 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:"id"`
Message string `json:"message"`
ID string `json:"sha"`
Message string `json:"commit.message"`
URL string `json:"url"`
Author Author `json:"author"`
Committer Author `json:"committer"`
Timestamp string `json:"timestamp"`
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"`
HTMLURL string `json:"html_url"`
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, token string, event string) *GiteaEvent {
func NewGiteaEvent(apiBase string, auth *AuthConfig, event string) *GiteaEvent {
return &GiteaEvent{
apiBase: apiBase,
token: token,
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 {
@ -53,14 +204,14 @@ func (e *GiteaEvent) ExtractChanges() (*types.CodeChanges, error) {
// 检查是否跳过代码审查
for _, commit := range e.Commits {
if strings.Contains(commit.Message, "[skip codereview]") {
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.token)
e.client = newHTTPClient(e.apiBase, e.auth)
log.Printf("初始化 HTTP 客户端: url=%s", e.apiBase)
}
@ -73,89 +224,46 @@ func (e *GiteaEvent) ExtractChanges() (*types.CodeChanges, error) {
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
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
}
// 去除首尾空白
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"
}
// 处理每个文件的变更
for _, file := range diffContent.Files {
var content strings.Builder
content.WriteString(fmt.Sprintf("### 变更说明\n"))
content.WriteString(fmt.Sprintf("提交信息: %s\n\n", commit.Message))
content.WriteString(fmt.Sprintf("提交信息: %s\n\n", diffContent.Commit.Message))
switch status {
status := "modified"
switch file.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))
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":
content.WriteString(fmt.Sprintf("重命名文件: %s\n\n", filename))
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(block)
content.WriteString(file.Content)
content.WriteString("\n```\n")
}
changes.Files = append(changes.Files, types.FileChange{
Path: filename,
Path: file.Filename,
Content: content.String(),
Type: parseFileType(status),
Type: utils.ParseFileType(status),
})
}
}
@ -165,11 +273,11 @@ func (e *GiteaEvent) ExtractChanges() (*types.CodeChanges, error) {
func (e *GiteaEvent) PostComments(result *types.ReviewResult) error {
if e.client == nil {
e.client = newHTTPClient(e.apiBase, e.token)
e.client = newHTTPClient(e.apiBase, e.auth)
}
// 创建 issue
path := fmt.Sprintf("/api/v1/repos/%s/issues", e.Repository.FullName)
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),

View File

@ -2,11 +2,15 @@ package platforms
import (
"code-review/services/types"
"code-review/utils"
"fmt"
)
// GiteeEvent Gitee 平台的 webhook 事件
type GiteeEvent struct {
apiBase string
auth *AuthConfig
Event string
client *httpClient
Action string `json:"action"`
ActionDesc string `json:"action_desc"`
@ -43,9 +47,11 @@ type GiteeEvent struct {
} `json:"pull_request"`
}
func NewGiteeEvent(baseURL, token string) *GiteeEvent {
func NewGiteeEvent(baseURL string, auth *AuthConfig) *GiteeEvent {
return &GiteeEvent{
client: newHTTPClient(baseURL, token),
apiBase: baseURL,
auth: auth,
client: newHTTPClient(baseURL, auth),
}
}
@ -71,16 +77,7 @@ func (e *GiteeEvent) ExtractChanges() (*types.CodeChanges, error) {
OldPath: change.OldPath,
}
switch change.Type {
case "added":
fileChange.Type = types.Added
case "modified":
fileChange.Type = types.Modified
case "deleted":
fileChange.Type = types.Deleted
case "renamed":
fileChange.Type = types.Renamed
}
fileChange.Type = utils.ParseFileType(change.Type)
changes.Files = append(changes.Files, fileChange)
}
@ -90,12 +87,12 @@ func (e *GiteeEvent) ExtractChanges() (*types.CodeChanges, error) {
func (e *GiteeEvent) PostComments(result *types.ReviewResult) error {
if e.client == nil {
return fmt.Errorf("client not initialized")
e.client = newHTTPClient(e.apiBase, e.auth)
}
for _, comment := range result.Comments {
body := map[string]interface{}{
"access_token": e.client.token,
"access_token": e.auth.Token,
"body": fmt.Sprintf("**Code Review Comment**\n\nFile: %s\nLine: %d\nSeverity: %s\n\n%s",
comment.Path,
comment.Line,
@ -119,7 +116,7 @@ func (e *GiteeEvent) PostComments(result *types.ReviewResult) error {
// 发送总结评论
if result.Summary != "" {
body := map[string]interface{}{
"access_token": e.client.token,
"access_token": e.auth.Token,
"body": fmt.Sprintf("**Code Review Summary**\n\n%s", result.Summary),
}

View File

@ -11,7 +11,7 @@ import (
// GitlabEvent Gitlab 平台的 webhook 事件
type GitlabEvent struct {
apiBase string
token string
auth *AuthConfig
Event string
client *httpClient
@ -34,10 +34,10 @@ type GitlabEvent struct {
}
// NewGitlabEvent 创建 GitLab 事件实例
func NewGitlabEvent(baseURL, token string, event string) *GitlabEvent {
func NewGitlabEvent(baseURL string, auth *AuthConfig, event string) *GitlabEvent {
return &GitlabEvent{
apiBase: baseURL,
token: token,
auth: auth,
Event: event,
}
}
@ -70,7 +70,7 @@ func (e *GitlabEvent) ExtractChanges() (*types.CodeChanges, error) {
}
if e.client == nil {
e.client = newHTTPClient(e.apiBase, e.token)
e.client = newHTTPClient(e.apiBase, e.auth)
log.Printf("初始化 HTTP 客户端: url=%s", e.apiBase)
}
@ -87,7 +87,7 @@ func (e *GitlabEvent) ExtractChanges() (*types.CodeChanges, error) {
e.Project.ID, commit.ID)
headers := map[string]string{
"PRIVATE-TOKEN": e.token,
"PRIVATE-TOKEN": e.auth.Token,
}
var diffs []gitlabDiff
@ -109,7 +109,7 @@ func (e *GitlabEvent) ExtractChanges() (*types.CodeChanges, error) {
changes.Files = append(changes.Files, types.FileChange{
Path: diff.NewPath,
Content: diff.Diff,
Type: parseFileType(status),
Type: utils.ParseFileType(status),
})
}
}
@ -119,7 +119,7 @@ func (e *GitlabEvent) ExtractChanges() (*types.CodeChanges, error) {
func (e *GitlabEvent) PostComments(result *types.ReviewResult) error {
if e.client == nil {
e.client = newHTTPClient(e.apiBase, e.token)
e.client = newHTTPClient(e.apiBase, e.auth)
}
// 创建 issue
@ -130,7 +130,7 @@ func (e *GitlabEvent) PostComments(result *types.ReviewResult) error {
}
headers := map[string]string{
"PRIVATE-TOKEN": e.token,
"PRIVATE-TOKEN": e.auth.Token,
}
if err := e.client.postWithHeaders(path, issueData, headers); err != nil {

View File

@ -1,245 +0,0 @@
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
}
}

View File

@ -399,7 +399,7 @@
<select class="form-control" name="git[${index}].type" required
title="请选择 Git 平台类型">
<option value="gitlab" ${platform.type === 'gitlab' ? 'selected' : ''}>GitLab</option>
<option value="gitee" ${platform.type === 'gitee' ? 'selected' : ''}>Gitee</option>
<option value="gitea" ${platform.type === 'gitea' ? 'selected' : ''}>Gitea</option>
</select>
</div>
${createPasswordField(`git[${index}].token`, platform.token, 'Token', '请输入平台访问令牌')}
@ -510,8 +510,8 @@
token: platform.token,
webhook_secret: platform.webhook_secret,
api_base: platform.api_base,
signature_header: platform.type === 'gitlab' ? 'X-Gitlab-Token' : 'X-Gitee-Token',
event_header: platform.type === 'gitlab' ? 'X-Gitlab-Event' : 'X-Gitee-Event'
signature_header: platform.type === 'gitlab' ? 'X-Gitlab-Token' : 'X-Gitea-Signature',
event_header: platform.type === 'gitlab' ? 'X-Gitlab-Event' : 'X-Gitea-Event'
}))
};
}

View File

@ -87,3 +87,19 @@ func FormatReviewResult(result *types.ReviewResult) string {
return body.String()
}
// ParseFileType 将文件变更类型字符串转换为 ChangeType
func ParseFileType(t string) types.ChangeType {
switch t {
case "add", "added":
return types.Added
case "modify", "modified":
return types.Modified
case "delete", "deleted":
return types.Deleted
case "renamed":
return types.Renamed
default:
return types.Modified
}
}