gitea获取diff更改 dockerfile更改 日志输出至文件
This commit is contained in:
@ -22,14 +22,20 @@ WORKDIR /app
|
|||||||
# 从构建阶段复制二进制文件
|
# 从构建阶段复制二进制文件
|
||||||
COPY --from=builder /app/ai-code-review /app/
|
COPY --from=builder /app/ai-code-review /app/
|
||||||
|
|
||||||
|
# 复制静态文件目录
|
||||||
|
COPY --from=builder /app/static /app/static
|
||||||
|
|
||||||
# 创建默认配置文件到临时位置
|
# 创建默认配置文件到临时位置
|
||||||
RUN echo 'port: 53321\nadmin_token: "token"\n\nauto_disable:\n enabled: true\n max_failures: 3\n reset_after: 30\n\nais: []\n\ngit: []' > /app/config.default.yaml
|
RUN echo 'port: 53321\nadmin_token: "token"\n\nauto_disable:\n enabled: true\n max_failures: 3\n reset_after: 30\n\nais: []\n\ngit: []' > /app/config.default.yaml
|
||||||
|
|
||||||
|
# 创建日志目录并设置权限
|
||||||
|
RUN mkdir -p /app/logs && chmod 755 /app/logs
|
||||||
|
|
||||||
# 暴露端口
|
# 暴露端口
|
||||||
EXPOSE 53321
|
EXPOSE 53321
|
||||||
|
|
||||||
# 设置卷挂载点
|
# 设置卷挂载点
|
||||||
VOLUME ["/app/config.yaml"]
|
VOLUME ["/app/config.yaml", "/app/logs"]
|
||||||
|
|
||||||
# 运行应用,如果配置文件不存在则使用默认配置
|
# 运行应用,如果配置文件不存在则使用默认配置
|
||||||
CMD ["sh", "-c", "if [ ! -f /app/config.yaml ]; then cp /app/config.default.yaml /app/config.yaml; fi && /app/ai-code-review"]
|
CMD ["sh", "-c", "if [ ! -f /app/config.yaml ]; then cp /app/config.default.yaml /app/config.yaml; fi && /app/ai-code-review"]
|
7
main.go
7
main.go
@ -5,6 +5,7 @@ import (
|
|||||||
"code-review/handlers"
|
"code-review/handlers"
|
||||||
"code-review/services"
|
"code-review/services"
|
||||||
"code-review/services/ai"
|
"code-review/services/ai"
|
||||||
|
"code-review/utils"
|
||||||
"fmt"
|
"fmt"
|
||||||
"log"
|
"log"
|
||||||
|
|
||||||
@ -12,6 +13,12 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
|
// 初始化日志系统
|
||||||
|
if err := utils.InitLogger(); err != nil {
|
||||||
|
log.Fatalf("初始化日志系统失败: %v", err)
|
||||||
|
}
|
||||||
|
defer utils.CloseLogger()
|
||||||
|
|
||||||
if err := config.LoadConfig("config.yaml"); err != nil {
|
if err := config.LoadConfig("config.yaml"); err != nil {
|
||||||
log.Fatalf("加载配置失败: %v", err)
|
log.Fatalf("加载配置失败: %v", err)
|
||||||
}
|
}
|
||||||
|
@ -45,6 +45,7 @@ func (c *Client) Chat(systemMsg, prompt string) (string, error) {
|
|||||||
var response string
|
var response string
|
||||||
var err error
|
var err error
|
||||||
|
|
||||||
|
log.Printf("AImodel=%s, prompt=%s", c.model, prompt)
|
||||||
if c.aiType == "ollama" {
|
if c.aiType == "ollama" {
|
||||||
response, err = c.ollamaChat(systemMsg, prompt)
|
response, err = c.ollamaChat(systemMsg, prompt)
|
||||||
} else {
|
} else {
|
||||||
|
@ -195,3 +195,38 @@ func (c *httpClient) postWithHeaders(path string, data interface{}, headers map[
|
|||||||
log.Printf("POST 请求成功: url=%s", url)
|
log.Printf("POST 请求成功: url=%s", url)
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (c *httpClient) getRaw(path string) (string, error) {
|
||||||
|
url := fmt.Sprintf("%s%s", c.url, path)
|
||||||
|
log.Printf("发送 GET 请求: url=%s", url)
|
||||||
|
|
||||||
|
req, err := http.NewRequest("GET", url, nil)
|
||||||
|
if err != nil {
|
||||||
|
log.Printf("创建 GET 请求失败: url=%s, error=%v", url, err)
|
||||||
|
return "", fmt.Errorf("创建请求失败: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
c.setAuthHeaders(req)
|
||||||
|
|
||||||
|
resp, err := c.client.Do(req)
|
||||||
|
if err != nil {
|
||||||
|
log.Printf("发送 GET 请求失败: url=%s, error=%v", url, err)
|
||||||
|
return "", fmt.Errorf("发送请求失败: %w", err)
|
||||||
|
}
|
||||||
|
defer resp.Body.Close()
|
||||||
|
|
||||||
|
if resp.StatusCode != http.StatusOK {
|
||||||
|
body, _ := io.ReadAll(resp.Body)
|
||||||
|
log.Printf("GET 请求返回错误状态码: url=%s, status=%d, response=%s", url, resp.StatusCode, string(body))
|
||||||
|
return "", fmt.Errorf("请求失败,状态码: %d,响应: %s", resp.StatusCode, string(body))
|
||||||
|
}
|
||||||
|
|
||||||
|
body, err := io.ReadAll(resp.Body)
|
||||||
|
if err != nil {
|
||||||
|
log.Printf("读取响应失败: url=%s, error=%v", url, err)
|
||||||
|
return "", fmt.Errorf("读取响应失败: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
log.Printf("GET 请求成功: url=%s", url)
|
||||||
|
return string(body), nil
|
||||||
|
}
|
||||||
|
@ -172,27 +172,7 @@ func NewGiteaEvent(apiBase string, auth *AuthConfig, event string) *GiteaEvent {
|
|||||||
|
|
||||||
// 定义 Gitea commit 响应的结构
|
// 定义 Gitea commit 响应的结构
|
||||||
type giteaCommitResponse struct {
|
type giteaCommitResponse struct {
|
||||||
Commit struct {
|
Diff string `json:"diff"`
|
||||||
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) {
|
func (e *GiteaEvent) ExtractChanges() (*types.CodeChanges, error) {
|
||||||
@ -202,14 +182,6 @@ func (e *GiteaEvent) ExtractChanges() (*types.CodeChanges, error) {
|
|||||||
return nil, nil
|
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 {
|
if e.client == nil {
|
||||||
e.client = newHTTPClient(e.apiBase, e.auth)
|
e.client = newHTTPClient(e.apiBase, e.auth)
|
||||||
log.Printf("初始化 HTTP 客户端: url=%s", e.apiBase)
|
log.Printf("初始化 HTTP 客户端: url=%s", e.apiBase)
|
||||||
@ -223,48 +195,69 @@ func (e *GiteaEvent) ExtractChanges() (*types.CodeChanges, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
for _, commit := range e.Commits {
|
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)
|
if strings.Contains(commit.Message, "[skip codereview]") {
|
||||||
var diffContent giteaCommitResponse
|
log.Printf("提交包含跳过标记,跳过所有文件审查: commit=%s", commit.ID)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
if err := e.client.get(apiPath, &diffContent); err != nil {
|
// 检查是否是合并提交
|
||||||
|
if strings.HasPrefix(commit.Message, "Merge remote-tracking branch") ||
|
||||||
|
strings.HasPrefix(commit.Message, "Merge branch") {
|
||||||
|
log.Printf("跳过合并提交的文件审查: commit=%s", commit.ID)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// 获取 diff 内容
|
||||||
|
apiPath := fmt.Sprintf("/api/v1/repos/%s/%s/git/commits/%s.diff", e.Repository.Owner.Login, e.Repository.Name, e.After)
|
||||||
|
diffContent, err := e.client.getRaw(apiPath)
|
||||||
|
if err != nil {
|
||||||
log.Printf("获取提交详情失败: commit=%s, error=%v", commit.ID, err)
|
log.Printf("获取提交详情失败: commit=%s, error=%v", commit.ID, err)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
// 处理每个文件的变更
|
// 解析 diff 内容
|
||||||
for _, file := range diffContent.Files {
|
diffLines := strings.Split(diffContent, "\n")
|
||||||
var content strings.Builder
|
var currentFile *types.FileChange
|
||||||
content.WriteString(fmt.Sprintf("### 变更说明\n"))
|
var currentContent strings.Builder
|
||||||
content.WriteString(fmt.Sprintf("提交信息: %s\n\n", diffContent.Commit.Message))
|
|
||||||
|
|
||||||
status := "modified"
|
for _, line := range diffLines {
|
||||||
switch file.Status {
|
if strings.HasPrefix(line, "diff --git") {
|
||||||
case "added":
|
// 保存前一个文件的内容
|
||||||
status = "added"
|
if currentFile != nil {
|
||||||
content.WriteString(fmt.Sprintf("新增文件: %s\n\n", file.Filename))
|
currentFile.Content = currentContent.String() + "```\n"
|
||||||
case "removed":
|
changes.Files = append(changes.Files, *currentFile)
|
||||||
status = "deleted"
|
}
|
||||||
content.WriteString(fmt.Sprintf("删除文件: %s\n\n", file.Filename))
|
|
||||||
case "renamed":
|
// 解析文件名
|
||||||
status = "renamed"
|
parts := strings.Split(line, " ")
|
||||||
content.WriteString(fmt.Sprintf("重命名文件: %s\n\n", file.Filename))
|
if len(parts) >= 3 {
|
||||||
default:
|
filePath := strings.TrimPrefix(parts[2], "b/")
|
||||||
content.WriteString(fmt.Sprintf("修改文件: %s\n\n", file.Filename))
|
if shouldSkipFile(filePath) {
|
||||||
|
log.Printf("跳过文件审查: file=%s", filePath)
|
||||||
|
currentFile = nil
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
currentFile = &types.FileChange{
|
||||||
|
Path: filePath,
|
||||||
|
Type: utils.ParseFileType("modified"),
|
||||||
|
}
|
||||||
|
currentContent.Reset()
|
||||||
|
currentContent.WriteString(fmt.Sprintf("### 变更说明\n"))
|
||||||
|
currentContent.WriteString(fmt.Sprintf("提交信息: %s\n\n", commit.Message))
|
||||||
|
currentContent.WriteString("### 变更内容\n")
|
||||||
|
currentContent.WriteString("```diff\n")
|
||||||
|
}
|
||||||
|
} else if currentFile != nil {
|
||||||
|
currentContent.WriteString(line + "\n")
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if file.Content != "" {
|
// 保存最后一个文件的内容
|
||||||
content.WriteString("### 变更内容\n")
|
if currentFile != nil {
|
||||||
content.WriteString("```diff\n")
|
currentFile.Content = currentContent.String() + "```\n"
|
||||||
content.WriteString(file.Content)
|
changes.Files = append(changes.Files, *currentFile)
|
||||||
content.WriteString("\n```\n")
|
|
||||||
}
|
|
||||||
|
|
||||||
changes.Files = append(changes.Files, types.FileChange{
|
|
||||||
Path: file.Filename,
|
|
||||||
Content: content.String(),
|
|
||||||
Type: utils.ParseFileType(status),
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -295,3 +288,24 @@ func (e *GiteaEvent) PostComments(result *types.ReviewResult) error {
|
|||||||
func (e *GiteaEvent) GetPlatform() string {
|
func (e *GiteaEvent) GetPlatform() string {
|
||||||
return "gitea"
|
return "gitea"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 判断是否应该跳过文件审查
|
||||||
|
func shouldSkipFile(filename string) bool {
|
||||||
|
// 跳过特定文件类型
|
||||||
|
skipExtensions := []string{".md", ".txt", ".json", ".yaml", ".yml", ".lock"}
|
||||||
|
for _, ext := range skipExtensions {
|
||||||
|
if strings.HasSuffix(filename, ext) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 跳过特定目录
|
||||||
|
skipDirs := []string{"node_modules/", "dist/", "build/", "vendor/"}
|
||||||
|
for _, dir := range skipDirs {
|
||||||
|
if strings.Contains(filename, dir) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
@ -4,6 +4,8 @@ import (
|
|||||||
"code-review/services/types"
|
"code-review/services/types"
|
||||||
"code-review/utils"
|
"code-review/utils"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"log"
|
||||||
|
"strings"
|
||||||
)
|
)
|
||||||
|
|
||||||
// GiteeEvent Gitee 平台的 webhook 事件
|
// GiteeEvent Gitee 平台的 webhook 事件
|
||||||
@ -70,6 +72,31 @@ func (e *GiteeEvent) ExtractChanges() (*types.CodeChanges, error) {
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 过滤合并提交
|
||||||
|
validCommits := make([]struct {
|
||||||
|
ID string `json:"id"`
|
||||||
|
Message string `json:"message"`
|
||||||
|
}, 0)
|
||||||
|
|
||||||
|
for _, commit := range e.PullRequest.Commits {
|
||||||
|
if strings.HasPrefix(commit.Message, "Merge remote-tracking branch") ||
|
||||||
|
strings.HasPrefix(commit.Message, "Merge branch") {
|
||||||
|
log.Printf("跳过合并提交: commit=%s", commit.ID)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
validCommits = append(validCommits, commit)
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(validCommits) == 0 {
|
||||||
|
log.Printf("没有有效的提交记录(所有提交都是合并提交),跳过代码审查")
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// 更新最后的提交ID
|
||||||
|
if len(validCommits) > 0 {
|
||||||
|
changes.CommitID = validCommits[len(validCommits)-1].ID
|
||||||
|
}
|
||||||
|
|
||||||
for _, change := range e.PullRequest.Changes {
|
for _, change := range e.PullRequest.Changes {
|
||||||
fileChange := types.FileChange{
|
fileChange := types.FileChange{
|
||||||
Path: change.Path,
|
Path: change.Path,
|
||||||
|
@ -62,11 +62,29 @@ func (e *GitlabEvent) ExtractChanges() (*types.CodeChanges, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 检查是否跳过代码审查
|
// 检查是否跳过代码审查
|
||||||
|
validCommits := make([]struct {
|
||||||
|
ID string `json:"id"`
|
||||||
|
Message string `json:"message"`
|
||||||
|
Title string `json:"title"`
|
||||||
|
}, 0)
|
||||||
|
|
||||||
for _, commit := range e.Commits {
|
for _, commit := range e.Commits {
|
||||||
if strings.Contains(commit.Message, "[skip codereview]") {
|
if strings.Contains(commit.Message, "[skip codereview]") {
|
||||||
log.Printf("跳过代码审查: commit=%s", commit.ID)
|
log.Printf("跳过代码审查: commit=%s", commit.ID)
|
||||||
return nil, nil
|
return nil, nil
|
||||||
}
|
}
|
||||||
|
// 过滤合并提交
|
||||||
|
if strings.HasPrefix(commit.Message, "Merge remote-tracking branch") ||
|
||||||
|
strings.HasPrefix(commit.Message, "Merge branch") {
|
||||||
|
log.Printf("跳过合并提交: commit=%s", commit.ID)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
validCommits = append(validCommits, commit)
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(validCommits) == 0 {
|
||||||
|
log.Printf("没有有效的提交记录(所有提交都是合并提交),跳过代码审查")
|
||||||
|
return nil, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
if e.client == nil {
|
if e.client == nil {
|
||||||
@ -81,7 +99,7 @@ func (e *GitlabEvent) ExtractChanges() (*types.CodeChanges, error) {
|
|||||||
Files: make([]types.FileChange, 0),
|
Files: make([]types.FileChange, 0),
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, commit := range e.Commits {
|
for _, commit := range validCommits {
|
||||||
// 移除 URL 中的 token
|
// 移除 URL 中的 token
|
||||||
apiPath := fmt.Sprintf("/api/v4/projects/%d/repository/commits/%s/diff",
|
apiPath := fmt.Sprintf("/api/v4/projects/%d/repository/commits/%s/diff",
|
||||||
e.Project.ID, commit.ID)
|
e.Project.ID, commit.ID)
|
||||||
|
47
utils/logger.go
Normal file
47
utils/logger.go
Normal file
@ -0,0 +1,47 @@
|
|||||||
|
package utils
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"log"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
// 日志文件句柄
|
||||||
|
logFile *os.File
|
||||||
|
// 日志目录
|
||||||
|
logDir = "logs"
|
||||||
|
)
|
||||||
|
|
||||||
|
// InitLogger 初始化日志系统
|
||||||
|
func InitLogger() error {
|
||||||
|
// 创建日志目录
|
||||||
|
if err := os.MkdirAll(logDir, 0755); err != nil {
|
||||||
|
return fmt.Errorf("创建日志目录失败: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 生成日志文件名
|
||||||
|
logFileName := filepath.Join(logDir, time.Now().Format("2006-01-02")+".log")
|
||||||
|
|
||||||
|
// 打开日志文件
|
||||||
|
file, err := os.OpenFile(logFileName, os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0644)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("打开日志文件失败: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 设置日志输出
|
||||||
|
log.SetOutput(io.MultiWriter(os.Stdout, file))
|
||||||
|
logFile = file
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// CloseLogger 关闭日志文件
|
||||||
|
func CloseLogger() {
|
||||||
|
if logFile != nil {
|
||||||
|
logFile.Close()
|
||||||
|
}
|
||||||
|
}
|
Reference in New Issue
Block a user