基本能力编写完成

This commit is contained in:
2025-12-31 14:23:53 +08:00
parent ac5aa1eb70
commit 2b51050ca8
33 changed files with 5464 additions and 7 deletions

185
internal/git/cmd_git.go Normal file
View File

@@ -0,0 +1,185 @@
package git
import (
"context"
"fmt"
"os/exec"
"regexp"
"strconv"
"strings"
"github.com/gitcodestatic/gitcodestatic/internal/logger"
"github.com/gitcodestatic/gitcodestatic/internal/models"
)
// CmdGitManager 基于git命令的实现
type CmdGitManager struct {
gitPath string
}
// NewCmdGitManager 创建命令行Git管理器
func NewCmdGitManager(gitPath string) *CmdGitManager {
if gitPath == "" {
gitPath = "git"
}
return &CmdGitManager{gitPath: gitPath}
}
// IsAvailable 检查git命令是否可用
func (m *CmdGitManager) IsAvailable() bool {
cmd := exec.Command(m.gitPath, "--version")
err := cmd.Run()
return err == nil
}
// Clone 克隆仓库
func (m *CmdGitManager) Clone(ctx context.Context, url, localPath string, cred *models.Credential) error {
// 注入凭据到URL如果有
cloneURL := url
if cred != nil {
cloneURL = m.injectCredentials(url, cred)
}
cmd := exec.CommandContext(ctx, m.gitPath, "clone", cloneURL, localPath)
cmd.Env = append(cmd.Env, "GIT_TERMINAL_PROMPT=0") // 禁止交互式提示
output, err := cmd.CombinedOutput()
if err != nil {
// 脱敏日志
sanitizedURL := sanitizeURL(url)
logger.Logger.Error().
Err(err).
Str("url", sanitizedURL).
Str("output", string(output)).
Msg("failed to clone repository")
return fmt.Errorf("failed to clone repository: %w", err)
}
logger.Logger.Info().
Str("url", sanitizeURL(url)).
Str("local_path", localPath).
Msg("repository cloned successfully")
return nil
}
// Pull 拉取更新
func (m *CmdGitManager) Pull(ctx context.Context, localPath string, cred *models.Credential) error {
cmd := exec.CommandContext(ctx, m.gitPath, "-C", localPath, "pull")
cmd.Env = append(cmd.Env, "GIT_TERMINAL_PROMPT=0")
output, err := cmd.CombinedOutput()
if err != nil {
logger.Logger.Error().
Err(err).
Str("local_path", localPath).
Str("output", string(output)).
Msg("failed to pull repository")
return fmt.Errorf("failed to pull repository: %w", err)
}
logger.Logger.Info().
Str("local_path", localPath).
Msg("repository pulled successfully")
return nil
}
// Checkout 切换分支
func (m *CmdGitManager) Checkout(ctx context.Context, localPath, branch string) error {
cmd := exec.CommandContext(ctx, m.gitPath, "-C", localPath, "checkout", branch)
output, err := cmd.CombinedOutput()
if err != nil {
logger.Logger.Error().
Err(err).
Str("local_path", localPath).
Str("branch", branch).
Str("output", string(output)).
Msg("failed to checkout branch")
return fmt.Errorf("failed to checkout branch: %w", err)
}
logger.Logger.Info().
Str("local_path", localPath).
Str("branch", branch).
Msg("branch checked out successfully")
return nil
}
// GetCurrentBranch 获取当前分支
func (m *CmdGitManager) GetCurrentBranch(ctx context.Context, localPath string) (string, error) {
cmd := exec.CommandContext(ctx, m.gitPath, "-C", localPath, "rev-parse", "--abbrev-ref", "HEAD")
output, err := cmd.Output()
if err != nil {
return "", fmt.Errorf("failed to get current branch: %w", err)
}
branch := strings.TrimSpace(string(output))
return branch, nil
}
// GetHeadCommitHash 获取HEAD commit hash
func (m *CmdGitManager) GetHeadCommitHash(ctx context.Context, localPath string) (string, error) {
cmd := exec.CommandContext(ctx, m.gitPath, "-C", localPath, "rev-parse", "HEAD")
output, err := cmd.Output()
if err != nil {
return "", fmt.Errorf("failed to get HEAD commit hash: %w", err)
}
hash := strings.TrimSpace(string(output))
return hash, nil
}
// CountCommits 统计提交次数
func (m *CmdGitManager) CountCommits(ctx context.Context, localPath, branch, fromDate string) (int, error) {
args := []string{"-C", localPath, "rev-list", "--count"}
if fromDate != "" {
args = append(args, "--since="+fromDate)
}
args = append(args, branch)
cmd := exec.CommandContext(ctx, m.gitPath, args...)
output, err := cmd.Output()
if err != nil {
return 0, fmt.Errorf("failed to count commits: %w", err)
}
countStr := strings.TrimSpace(string(output))
count, err := strconv.Atoi(countStr)
if err != nil {
return 0, fmt.Errorf("failed to parse commit count: %w", err)
}
return count, nil
}
// injectCredentials 注入凭据到URL
func (m *CmdGitManager) injectCredentials(url string, cred *models.Credential) string {
if cred == nil || cred.Username == "" {
return url
}
// 简单的URL凭据注入仅支持https
if strings.HasPrefix(url, "https://") {
credentials := cred.Username
if cred.Password != "" {
credentials += ":" + cred.Password
}
return strings.Replace(url, "https://", "https://"+credentials+"@", 1)
}
return url
}
// sanitizeURL 脱敏URL移除用户名密码
func sanitizeURL(url string) string {
re := regexp.MustCompile(`(https?://)[^@]+@`)
return re.ReplaceAllString(url, "${1}***@")
}

31
internal/git/manager.go Normal file
View File

@@ -0,0 +1,31 @@
package git
import (
"context"
"github.com/gitcodestatic/gitcodestatic/internal/models"
)
// Manager Git管理器接口
type Manager interface {
// Clone 克隆仓库
Clone(ctx context.Context, url, localPath string, cred *models.Credential) error
// Pull 拉取更新
Pull(ctx context.Context, localPath string, cred *models.Credential) error
// Checkout 切换分支
Checkout(ctx context.Context, localPath, branch string) error
// GetCurrentBranch 获取当前分支
GetCurrentBranch(ctx context.Context, localPath string) (string, error)
// GetHeadCommitHash 获取HEAD commit hash
GetHeadCommitHash(ctx context.Context, localPath string) (string, error)
// CountCommits 统计提交次数
CountCommits(ctx context.Context, localPath, branch, fromDate string) (int, error)
// IsAvailable 检查Git是否可用
IsAvailable() bool
}