基本能力编写完成
This commit is contained in:
185
internal/git/cmd_git.go
Normal file
185
internal/git/cmd_git.go
Normal 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
31
internal/git/manager.go
Normal 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
|
||||
}
|
||||
Reference in New Issue
Block a user