feat: 新增分支管理、Stash贮藏、远程分支、设置页面\n\n分支管理:\n- 创建新分支\n- 删除本地分支(安全/强制)\n- 合并分支到当前分支\n\n远程分支:\n- 查看远程分支列表\n- 检出远程分支到本地\n- 删除远程分支\n\nStash 贮藏:\n- 保存当前变更(支持自定义消息)\n- 应用/弹出/删除贮藏\n- 贮藏列表管理\n\n设置:\n- Git 全局配置 (user.name / user.email)\n- 设置弹窗 UI"

This commit is contained in:
zyj
2026-03-11 09:37:58 +08:00
parent cb6b21ef98
commit 6354cbfc94
8 changed files with 1223 additions and 39 deletions

View File

@@ -820,3 +820,232 @@ func (s *AppService) PushTag(path, name string) error {
_, err := s.gitClient.PushTag(path, name)
return err
}
// --- 分支管理 ---
// CreateBranch 创建新分支
func (s *AppService) CreateBranch(path, name string) error {
if _, err := os.Stat(path); os.IsNotExist(err) {
return fmt.Errorf("项目路径不存在: %s", path)
}
name = strings.TrimSpace(name)
if name == "" {
return fmt.Errorf("分支名不能为空")
}
_, err := s.gitClient.CreateBranch(path, name)
return err
}
// DeleteBranch 删除本地分支
func (s *AppService) DeleteBranch(path, name string, force bool) error {
if _, err := os.Stat(path); os.IsNotExist(err) {
return fmt.Errorf("项目路径不存在: %s", path)
}
name = strings.TrimSpace(name)
if name == "" {
return fmt.Errorf("分支名不能为空")
}
var err error
if force {
_, err = s.gitClient.ForceDeleteBranch(path, name)
} else {
_, err = s.gitClient.DeleteBranch(path, name)
}
return err
}
// MergeBranch 合并指定分支到当前分支
func (s *AppService) MergeBranch(path, branch string) (string, error) {
if _, err := os.Stat(path); os.IsNotExist(err) {
return "", fmt.Errorf("项目路径不存在: %s", path)
}
branch = strings.TrimSpace(branch)
if branch == "" {
return "", fmt.Errorf("分支名不能为空")
}
return s.gitClient.MergeBranch(path, branch)
}
// --- 远程分支管理 ---
// GetRemoteBranches 获取远程分支列表
func (s *AppService) GetRemoteBranches(path string) ([]BranchInfo, error) {
if _, err := os.Stat(path); os.IsNotExist(err) {
return nil, fmt.Errorf("项目路径不存在: %s", path)
}
out, err := s.gitClient.RemoteBranchList(path)
if err != nil {
return nil, fmt.Errorf("获取远程分支列表失败: %w", err)
}
var branches []BranchInfo
for _, line := range strings.Split(strings.TrimSpace(out), "\n") {
name := strings.TrimSpace(line)
if name == "" || strings.Contains(name, "HEAD") {
continue
}
branches = append(branches, BranchInfo{Name: name, Current: false})
}
return branches, nil
}
// CheckoutRemoteBranch 检出远程分支到本地
func (s *AppService) CheckoutRemoteBranch(path, remoteBranch string) error {
if _, err := os.Stat(path); os.IsNotExist(err) {
return fmt.Errorf("项目路径不存在: %s", path)
}
remoteBranch = strings.TrimSpace(remoteBranch)
if remoteBranch == "" {
return fmt.Errorf("远程分支名不能为空")
}
// origin/feature -> feature
localBranch := remoteBranch
if idx := strings.Index(remoteBranch, "/"); idx != -1 {
localBranch = remoteBranch[idx+1:]
}
_, err := s.gitClient.CheckoutNewBranch(path, localBranch, remoteBranch)
return err
}
// DeleteRemoteBranch 删除远程分支
func (s *AppService) DeleteRemoteBranch(path, branch string) error {
if _, err := os.Stat(path); os.IsNotExist(err) {
return fmt.Errorf("项目路径不存在: %s", path)
}
branch = strings.TrimSpace(branch)
if branch == "" {
return fmt.Errorf("分支名不能为空")
}
// origin/feature -> feature
localName := branch
if idx := strings.Index(branch, "/"); idx != -1 {
localName = branch[idx+1:]
}
_, err := s.gitClient.DeleteRemoteBranch(path, localName)
return err
}
// --- Stash 贮藏管理 ---
// StashInfo 贮藏信息
type StashInfo struct {
Index int `json:"index"`
Ref string `json:"ref"`
Message string `json:"message"`
Timestamp int64 `json:"timestamp"`
}
// StashSave 保存当前变更到贮藏
func (s *AppService) StashSave(path, message string) error {
if _, err := os.Stat(path); os.IsNotExist(err) {
return fmt.Errorf("项目路径不存在: %s", path)
}
_, err := s.gitClient.StashSave(path, message)
return err
}
// GetStashList 获取贮藏列表
func (s *AppService) GetStashList(path string) ([]StashInfo, error) {
if _, err := os.Stat(path); os.IsNotExist(err) {
return nil, fmt.Errorf("项目路径不存在: %s", path)
}
out, err := s.gitClient.StashList(path)
if err != nil {
return nil, fmt.Errorf("获取贮藏列表失败: %w", err)
}
var stashes []StashInfo
for _, line := range strings.Split(strings.TrimSpace(out), "\n") {
if line == "" {
continue
}
parts := strings.SplitN(line, "\t", 3)
if len(parts) < 2 {
continue
}
ref := parts[0]
message := parts[1]
var ts int64
if len(parts) >= 3 {
fmt.Sscanf(parts[2], "%d", &ts)
}
var index int
fmt.Sscanf(ref, "stash@{%d}", &index)
stashes = append(stashes, StashInfo{
Index: index,
Ref: ref,
Message: message,
Timestamp: ts,
})
}
return stashes, nil
}
// StashApply 应用贮藏(不删除)
func (s *AppService) StashApply(path string, index int) error {
if _, err := os.Stat(path); os.IsNotExist(err) {
return fmt.Errorf("项目路径不存在: %s", path)
}
_, err := s.gitClient.StashApply(path, index)
return err
}
// StashPop 应用贮藏并删除
func (s *AppService) StashPop(path string, index int) error {
if _, err := os.Stat(path); os.IsNotExist(err) {
return fmt.Errorf("项目路径不存在: %s", path)
}
_, err := s.gitClient.StashPop(path, index)
return err
}
// StashDrop 删除贮藏
func (s *AppService) StashDrop(path string, index int) error {
if _, err := os.Stat(path); os.IsNotExist(err) {
return fmt.Errorf("项目路径不存在: %s", path)
}
_, err := s.gitClient.StashDrop(path, index)
return err
}
// --- 设置管理 ---
// GitConfig Git 全局配置
type GitConfig struct {
UserName string `json:"userName"`
UserEmail string `json:"userEmail"`
}
// GetGitGlobalConfig 获取 git 全局配置
func (s *AppService) GetGitGlobalConfig() (*GitConfig, error) {
name, _ := s.gitClient.GetGitGlobalConfig("user.name")
email, _ := s.gitClient.GetGitGlobalConfig("user.email")
return &GitConfig{
UserName: name,
UserEmail: email,
}, nil
}
// SetGitGlobalConfig 设置 git 全局配置
func (s *AppService) SetGitGlobalConfig(name, email string) error {
if name != "" {
if _, err := s.gitClient.SetGitGlobalConfig("user.name", name); err != nil {
return fmt.Errorf("设置 user.name 失败: %w", err)
}
}
if email != "" {
if _, err := s.gitClient.SetGitGlobalConfig("user.email", email); err != nil {
return fmt.Errorf("设置 user.email 失败: %w", err)
}
}
return nil
}
// GetAppSettings 获取应用设置
func (s *AppService) GetAppSettings() *config.Settings {
return &s.config.Settings
}
// UpdateAppSettings 更新应用设置
func (s *AppService) UpdateAppSettings(logLevel string) error {
s.config.Settings.LogLevel = logLevel
return config.SaveConfig(s.config)
}

View File

@@ -348,6 +348,101 @@ func parseNameStatus(output string) []FileChange {
return changes
}
// CreateBranch 创建新分支
func (g *GitClient) CreateBranch(path, name string) (string, error) {
return g.Run(path, "branch", name)
}
// DeleteBranch 删除本地分支 (-d 安全删除)
func (g *GitClient) DeleteBranch(path, name string) (string, error) {
return g.Run(path, "branch", "-d", name)
}
// ForceDeleteBranch 强制删除本地分支 (-D)
func (g *GitClient) ForceDeleteBranch(path, name string) (string, error) {
return g.Run(path, "branch", "-D", name)
}
// MergeBranch 合并指定分支到当前分支
func (g *GitClient) MergeBranch(path, branch string) (string, error) {
return g.Run(path, "merge", branch)
}
// DeleteRemoteBranch 删除远程分支
func (g *GitClient) DeleteRemoteBranch(path, branch string) (string, error) {
return g.Run(path, "push", "origin", "--delete", branch)
}
// RemoteBranchList 获取所有远程分支
func (g *GitClient) RemoteBranchList(path string) (string, error) {
return g.Run(path, "branch", "-r", "--format=%(refname:short)")
}
// CheckoutNewBranch 从远程分支检出新本地分支
func (g *GitClient) CheckoutNewBranch(path, localBranch, remoteBranch string) (string, error) {
return g.Run(path, "checkout", "-b", localBranch, remoteBranch)
}
// StashSave 保存当前工作区变更到贮藏
func (g *GitClient) StashSave(path, message string) (string, error) {
if message != "" {
return g.Run(path, "stash", "push", "-m", message)
}
return g.Run(path, "stash", "push")
}
// StashList 获取贮藏列表
func (g *GitClient) StashList(path string) (string, error) {
return g.Run(path, "stash", "list", "--format=%gd\t%s\t%at")
}
// StashApply 应用贮藏(不删除)
func (g *GitClient) StashApply(path string, index int) (string, error) {
return g.Run(path, "stash", "apply", fmt.Sprintf("stash@{%d}", index))
}
// StashPop 应用贮藏并删除
func (g *GitClient) StashPop(path string, index int) (string, error) {
return g.Run(path, "stash", "pop", fmt.Sprintf("stash@{%d}", index))
}
// StashDrop 删除贮藏
func (g *GitClient) StashDrop(path string, index int) (string, error) {
return g.Run(path, "stash", "drop", fmt.Sprintf("stash@{%d}", index))
}
// GetGitGlobalConfig 获取 git 全局配置
func (g *GitClient) GetGitGlobalConfig(key string) (string, error) {
ctx, cancel := context.WithTimeout(context.Background(), g.Timeout)
defer cancel()
cmd := exec.CommandContext(ctx, "git", "config", "--global", "--get", key)
hideWindow(cmd)
var stdout, stderr bytes.Buffer
cmd.Stdout = &stdout
cmd.Stderr = &stderr
err := cmd.Run()
if err != nil {
return "", nil // key 不存在不算错误
}
return strings.TrimSpace(stdout.String()), nil
}
// SetGitGlobalConfig 设置 git 全局配置
func (g *GitClient) SetGitGlobalConfig(key, value string) (string, error) {
ctx, cancel := context.WithTimeout(context.Background(), g.Timeout)
defer cancel()
cmd := exec.CommandContext(ctx, "git", "config", "--global", key, value)
hideWindow(cmd)
var stdout, stderr bytes.Buffer
cmd.Stdout = &stdout
cmd.Stderr = &stderr
err := cmd.Run()
if err != nil {
return "", fmt.Errorf("git config error: %v, stderr: %s", err, stderr.String())
}
return strings.TrimSpace(stdout.String()), nil
}
// StatusText 返回变更状态的中文描述
func (fc FileChange) StatusText() string {
switch {