feat: 多 remote 远程仓库支持 (v0.3.1)\n\n后端:\n- 新增 RemoteList/AddRemote/RemoveRemote 远程仓库管理\n- 所有远程操作(Push/Pull/Fetch/PushTag/DeleteTag/DeleteRemoteBranch等)支持指定 remote\n- 新增 PushTo/PullFrom/FetchRemote/PushTagTo/DeleteRemoteTagFrom 等方法\n- BatchPull/BatchPush 支持 remote 参数\n\n前端:\n- 顶部信息栏新增 remote 选择器下拉菜单\n- 支持切换当前操作的远程仓库\n- 支持添加/删除远程仓库\n- Pull/Push/Fetch/标签推送/远程分支删除等操作均使用选中的 remote
Some checks failed
Release / build-windows (push) Has been cancelled
Some checks failed
Release / build-windows (push) Has been cancelled
This commit is contained in:
@@ -307,9 +307,17 @@ func (s *AppService) GetPlatformInfo(name string) (*PlatformInfo, error) {
|
||||
|
||||
// ProjectStatus 项目状态信息
|
||||
type ProjectStatus struct {
|
||||
Branch string `json:"branch"`
|
||||
RemoteURL string `json:"remoteUrl"`
|
||||
ChangedFiles []FileInfo `json:"changedFiles"`
|
||||
Branch string `json:"branch"`
|
||||
RemoteURL string `json:"remoteUrl"`
|
||||
Remotes []RemoteItem `json:"remotes"`
|
||||
CurrentRemote string `json:"currentRemote"`
|
||||
ChangedFiles []FileInfo `json:"changedFiles"`
|
||||
}
|
||||
|
||||
// RemoteItem 远程仓库信息
|
||||
type RemoteItem struct {
|
||||
Name string `json:"name"`
|
||||
URL string `json:"url"`
|
||||
}
|
||||
|
||||
// FileInfo 文件信息
|
||||
@@ -346,10 +354,25 @@ func (s *AppService) GetProjectStatus(path string) (*ProjectStatus, error) {
|
||||
|
||||
remoteURL, _ := s.gitClient.RemoteURL(path)
|
||||
|
||||
// 获取所有 remote 列表
|
||||
var remotes []RemoteItem
|
||||
remoteList, remoteErr := s.gitClient.RemoteList(path)
|
||||
if remoteErr == nil {
|
||||
for _, r := range remoteList {
|
||||
remotes = append(remotes, RemoteItem{Name: r.Name, URL: r.URL})
|
||||
}
|
||||
}
|
||||
currentRemote := "origin"
|
||||
if len(remotes) > 0 {
|
||||
currentRemote = remotes[0].Name
|
||||
}
|
||||
|
||||
return &ProjectStatus{
|
||||
Branch: strings.TrimSpace(branch),
|
||||
RemoteURL: remoteURL,
|
||||
ChangedFiles: []FileInfo{},
|
||||
Branch: strings.TrimSpace(branch),
|
||||
RemoteURL: remoteURL,
|
||||
Remotes: remotes,
|
||||
CurrentRemote: currentRemote,
|
||||
ChangedFiles: []FileInfo{},
|
||||
}, nil
|
||||
}
|
||||
|
||||
@@ -515,22 +538,28 @@ func (s *AppService) GetFileDiffStaged(projectPath, filePath string) (string, er
|
||||
return s.gitClient.DiffStagedFile(projectPath, filePath)
|
||||
}
|
||||
|
||||
// PullProject 拉取项目(当前分支)
|
||||
func (s *AppService) PullProject(path string) (string, error) {
|
||||
// PullProject 拉取项目(当前分支,指定 remote)
|
||||
func (s *AppService) PullProject(path, remote string) (string, error) {
|
||||
if remote == "" {
|
||||
remote = "origin"
|
||||
}
|
||||
branch, err := s.gitClient.Branch(path)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("获取当前分支失败: %w", err)
|
||||
}
|
||||
return s.gitClient.Run(path, "pull", "origin", strings.TrimSpace(branch))
|
||||
return s.gitClient.PullFrom(path, remote, strings.TrimSpace(branch))
|
||||
}
|
||||
|
||||
// PushProject 推送项目(当前分支)
|
||||
func (s *AppService) PushProject(path string) (string, error) {
|
||||
// PushProject 推送项目(当前分支,指定 remote)
|
||||
func (s *AppService) PushProject(path, remote string) (string, error) {
|
||||
if remote == "" {
|
||||
remote = "origin"
|
||||
}
|
||||
branch, err := s.gitClient.Branch(path)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("获取当前分支失败: %w", err)
|
||||
}
|
||||
return s.gitClient.Run(path, "push", "origin", strings.TrimSpace(branch))
|
||||
return s.gitClient.PushTo(path, remote, strings.TrimSpace(branch))
|
||||
}
|
||||
|
||||
// GetCommitDiff 获取指定提交的 diff
|
||||
@@ -581,9 +610,12 @@ func (s *AppService) GetCommitFileDiff(path, hash, filePath string) (string, err
|
||||
return s.gitClient.CommitFileDiff(path, hash, filePath)
|
||||
}
|
||||
|
||||
// FetchProject 拉取远程信息
|
||||
func (s *AppService) FetchProject(path string) (string, error) {
|
||||
return s.gitClient.Fetch(path)
|
||||
// FetchProject 拉取远程信息(指定 remote,空则 fetch --all)
|
||||
func (s *AppService) FetchProject(path, remote string) (string, error) {
|
||||
if remote == "" {
|
||||
return s.gitClient.Fetch(path)
|
||||
}
|
||||
return s.gitClient.FetchRemote(path, remote)
|
||||
}
|
||||
|
||||
// CommitLog 提交记录
|
||||
@@ -791,7 +823,7 @@ func (s *AppService) CreateTag(path, name, message string) error {
|
||||
}
|
||||
|
||||
// DeleteTag 删除标签(本地+远程)
|
||||
func (s *AppService) DeleteTag(path, name string) error {
|
||||
func (s *AppService) DeleteTag(path, name, remote string) error {
|
||||
if _, err := os.Stat(path); os.IsNotExist(err) {
|
||||
return fmt.Errorf("项目路径不存在: %s", path)
|
||||
}
|
||||
@@ -799,17 +831,20 @@ func (s *AppService) DeleteTag(path, name string) error {
|
||||
if name == "" {
|
||||
return fmt.Errorf("标签名不能为空")
|
||||
}
|
||||
if remote == "" {
|
||||
remote = "origin"
|
||||
}
|
||||
// 删除本地标签
|
||||
if _, err := s.gitClient.DeleteTag(path, name); err != nil {
|
||||
return fmt.Errorf("删除本地标签失败: %w", err)
|
||||
}
|
||||
// 尝试删除远程标签(忽略错误,可能未推送过)
|
||||
s.gitClient.DeleteRemoteTag(path, name)
|
||||
s.gitClient.DeleteRemoteTagFrom(path, remote, name)
|
||||
return nil
|
||||
}
|
||||
|
||||
// PushTag 推送标签到远程
|
||||
func (s *AppService) PushTag(path, name string) error {
|
||||
func (s *AppService) PushTag(path, name, remote string) error {
|
||||
if _, err := os.Stat(path); os.IsNotExist(err) {
|
||||
return fmt.Errorf("项目路径不存在: %s", path)
|
||||
}
|
||||
@@ -817,7 +852,10 @@ func (s *AppService) PushTag(path, name string) error {
|
||||
if name == "" {
|
||||
return fmt.Errorf("标签名不能为空")
|
||||
}
|
||||
_, err := s.gitClient.PushTag(path, name)
|
||||
if remote == "" {
|
||||
remote = "origin"
|
||||
}
|
||||
_, err := s.gitClient.PushTagTo(path, remote, name)
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -869,11 +907,17 @@ func (s *AppService) MergeBranch(path, branch string) (string, error) {
|
||||
// --- 远程分支管理 ---
|
||||
|
||||
// GetRemoteBranches 获取远程分支列表
|
||||
func (s *AppService) GetRemoteBranches(path string) ([]BranchInfo, error) {
|
||||
func (s *AppService) GetRemoteBranches(path, remote string) ([]BranchInfo, error) {
|
||||
if _, err := os.Stat(path); os.IsNotExist(err) {
|
||||
return nil, fmt.Errorf("项目路径不存在: %s", path)
|
||||
}
|
||||
out, err := s.gitClient.RemoteBranchList(path)
|
||||
var out string
|
||||
var err error
|
||||
if remote == "" {
|
||||
out, err = s.gitClient.RemoteBranchList(path)
|
||||
} else {
|
||||
out, err = s.gitClient.RemoteBranchListByRemote(path, remote)
|
||||
}
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("获取远程分支列表失败: %w", err)
|
||||
}
|
||||
@@ -907,7 +951,7 @@ func (s *AppService) CheckoutRemoteBranch(path, remoteBranch string) error {
|
||||
}
|
||||
|
||||
// DeleteRemoteBranch 删除远程分支
|
||||
func (s *AppService) DeleteRemoteBranch(path, branch string) error {
|
||||
func (s *AppService) DeleteRemoteBranch(path, branch, remote string) error {
|
||||
if _, err := os.Stat(path); os.IsNotExist(err) {
|
||||
return fmt.Errorf("项目路径不存在: %s", path)
|
||||
}
|
||||
@@ -915,12 +959,15 @@ func (s *AppService) DeleteRemoteBranch(path, branch string) error {
|
||||
if branch == "" {
|
||||
return fmt.Errorf("分支名不能为空")
|
||||
}
|
||||
if remote == "" {
|
||||
remote = "origin"
|
||||
}
|
||||
// origin/feature -> feature
|
||||
localName := branch
|
||||
if idx := strings.Index(branch, "/"); idx != -1 {
|
||||
localName = branch[idx+1:]
|
||||
}
|
||||
_, err := s.gitClient.DeleteRemoteBranch(path, localName)
|
||||
_, err := s.gitClient.DeleteRemoteBranchFrom(path, remote, localName)
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -1050,6 +1097,54 @@ func (s *AppService) UpdateAppSettings(logLevel string) error {
|
||||
return config.SaveConfig(s.config)
|
||||
}
|
||||
|
||||
// --- 远程仓库管理 ---
|
||||
|
||||
// GetRemotes 获取项目所有远程仓库列表
|
||||
func (s *AppService) GetRemotes(path string) ([]RemoteItem, error) {
|
||||
if _, err := os.Stat(path); os.IsNotExist(err) {
|
||||
return nil, fmt.Errorf("项目路径不存在: %s", path)
|
||||
}
|
||||
remoteList, err := s.gitClient.RemoteList(path)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("获取远程仓库列表失败: %w", err)
|
||||
}
|
||||
var remotes []RemoteItem
|
||||
for _, r := range remoteList {
|
||||
remotes = append(remotes, RemoteItem{Name: r.Name, URL: r.URL})
|
||||
}
|
||||
return remotes, nil
|
||||
}
|
||||
|
||||
// AddRemote 添加远程仓库
|
||||
func (s *AppService) AddRemote(path, name, url string) error {
|
||||
if _, err := os.Stat(path); os.IsNotExist(err) {
|
||||
return fmt.Errorf("项目路径不存在: %s", path)
|
||||
}
|
||||
name = strings.TrimSpace(name)
|
||||
url = strings.TrimSpace(url)
|
||||
if name == "" {
|
||||
return fmt.Errorf("远程名称不能为空")
|
||||
}
|
||||
if url == "" {
|
||||
return fmt.Errorf("远程地址不能为空")
|
||||
}
|
||||
_, err := s.gitClient.AddRemote(path, name, url)
|
||||
return err
|
||||
}
|
||||
|
||||
// RemoveRemote 删除远程仓库
|
||||
func (s *AppService) RemoveRemote(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.RemoveRemote(path, name)
|
||||
return err
|
||||
}
|
||||
|
||||
// --- 冲突处理 ---
|
||||
|
||||
// ConflictFileInfo 冲突文件信息
|
||||
@@ -1211,7 +1306,10 @@ type BatchPullResult struct {
|
||||
}
|
||||
|
||||
// BatchPull 批量拉取指定项目
|
||||
func (s *AppService) BatchPull(paths []string) []BatchPullResult {
|
||||
func (s *AppService) BatchPull(paths []string, remote string) []BatchPullResult {
|
||||
if remote == "" {
|
||||
remote = "origin"
|
||||
}
|
||||
var results []BatchPullResult
|
||||
for _, path := range paths {
|
||||
result := BatchPullResult{Path: path}
|
||||
@@ -1229,7 +1327,7 @@ func (s *AppService) BatchPull(paths []string) []BatchPullResult {
|
||||
results = append(results, result)
|
||||
continue
|
||||
}
|
||||
_, err = s.gitClient.Run(path, "pull", "origin", strings.TrimSpace(branch))
|
||||
_, err = s.gitClient.PullFrom(path, remote, strings.TrimSpace(branch))
|
||||
if err != nil {
|
||||
result.Message = err.Error()
|
||||
} else {
|
||||
@@ -1242,7 +1340,10 @@ func (s *AppService) BatchPull(paths []string) []BatchPullResult {
|
||||
}
|
||||
|
||||
// BatchPush 批量推送指定项目
|
||||
func (s *AppService) BatchPush(paths []string) []BatchPullResult {
|
||||
func (s *AppService) BatchPush(paths []string, remote string) []BatchPullResult {
|
||||
if remote == "" {
|
||||
remote = "origin"
|
||||
}
|
||||
var results []BatchPullResult
|
||||
for _, path := range paths {
|
||||
result := BatchPullResult{Path: path}
|
||||
@@ -1259,7 +1360,7 @@ func (s *AppService) BatchPush(paths []string) []BatchPullResult {
|
||||
results = append(results, result)
|
||||
continue
|
||||
}
|
||||
_, err = s.gitClient.Run(path, "push", "origin", strings.TrimSpace(branch))
|
||||
_, err = s.gitClient.PushTo(path, remote, strings.TrimSpace(branch))
|
||||
if err != nil {
|
||||
result.Message = err.Error()
|
||||
} else {
|
||||
|
||||
@@ -60,6 +60,21 @@ func (g *GitClient) Push(path string) (string, error) {
|
||||
return g.Run(path, "push")
|
||||
}
|
||||
|
||||
// PushTo 推送到指定远程
|
||||
func (g *GitClient) PushTo(path, remote, branch string) (string, error) {
|
||||
return g.Run(path, "push", remote, branch)
|
||||
}
|
||||
|
||||
// PullFrom 从指定远程拉取
|
||||
func (g *GitClient) PullFrom(path, remote, branch string) (string, error) {
|
||||
return g.Run(path, "pull", remote, branch)
|
||||
}
|
||||
|
||||
// FetchRemote 拉取指定远程信息
|
||||
func (g *GitClient) FetchRemote(path, remote string) (string, error) {
|
||||
return g.Run(path, "fetch", remote)
|
||||
}
|
||||
|
||||
func (g *GitClient) Status(path string) (string, error) {
|
||||
return g.Run(path, "status")
|
||||
}
|
||||
@@ -93,6 +108,47 @@ func (g *GitClient) Fetch(path string) (string, error) {
|
||||
return g.Run(path, "fetch", "--all")
|
||||
}
|
||||
|
||||
// RemoteList 获取所有远程仓库列表 (git remote -v)
|
||||
type RemoteInfo struct {
|
||||
Name string
|
||||
URL string
|
||||
}
|
||||
|
||||
func (g *GitClient) RemoteList(path string) ([]RemoteInfo, error) {
|
||||
out, err := g.Run(path, "remote", "-v")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
seen := make(map[string]bool)
|
||||
var remotes []RemoteInfo
|
||||
for _, line := range strings.Split(strings.TrimSpace(out), "\n") {
|
||||
if line == "" {
|
||||
continue
|
||||
}
|
||||
parts := strings.Fields(line)
|
||||
if len(parts) < 2 {
|
||||
continue
|
||||
}
|
||||
name := parts[0]
|
||||
if seen[name] {
|
||||
continue
|
||||
}
|
||||
seen[name] = true
|
||||
remotes = append(remotes, RemoteInfo{Name: name, URL: parts[1]})
|
||||
}
|
||||
return remotes, nil
|
||||
}
|
||||
|
||||
// AddRemote 添加远程仓库
|
||||
func (g *GitClient) AddRemote(path, name, url string) (string, error) {
|
||||
return g.Run(path, "remote", "add", name, url)
|
||||
}
|
||||
|
||||
// RemoveRemote 删除远程仓库
|
||||
func (g *GitClient) RemoveRemote(path, name string) (string, error) {
|
||||
return g.Run(path, "remote", "remove", name)
|
||||
}
|
||||
|
||||
func (g *GitClient) RemoteURL(path string) (string, error) {
|
||||
out, err := g.Run(path, "config", "--get", "remote.origin.url")
|
||||
if err != nil {
|
||||
@@ -141,11 +197,16 @@ func (g *GitClient) Log(path string, count int) (string, error) {
|
||||
)
|
||||
}
|
||||
|
||||
// UnpushedCommits 获取当前分支上未推送到远程的提交哈希列表
|
||||
// UnpushedCommits 获取当前分支上未推送到远程的提交哈希列表(默认 origin)
|
||||
func (g *GitClient) UnpushedCommits(path, branch string) (string, error) {
|
||||
return g.Run(path, "rev-list", "origin/"+branch+"..HEAD")
|
||||
}
|
||||
|
||||
// UnpushedCommitsTo 获取当前分支上未推送到指定远程的提交哈希列表
|
||||
func (g *GitClient) UnpushedCommitsTo(path, remote, branch string) (string, error) {
|
||||
return g.Run(path, "rev-list", remote+"/"+branch+"..HEAD")
|
||||
}
|
||||
|
||||
// RevertCommit 撤回指定提交(创建一个反向提交)
|
||||
func (g *GitClient) RevertCommit(path, hash string) (string, error) {
|
||||
return g.Run(path, "revert", "--no-edit", hash)
|
||||
@@ -167,16 +228,26 @@ func (g *GitClient) DeleteTag(path, name string) (string, error) {
|
||||
return g.Run(path, "tag", "-d", name)
|
||||
}
|
||||
|
||||
// PushTag 推送标签到远程
|
||||
// PushTag 推送标签到远程(默认 origin)
|
||||
func (g *GitClient) PushTag(path, name string) (string, error) {
|
||||
return g.Run(path, "push", "origin", name)
|
||||
}
|
||||
|
||||
// DeleteRemoteTag 删除远程标签
|
||||
// PushTagTo 推送标签到指定远程
|
||||
func (g *GitClient) PushTagTo(path, remote, name string) (string, error) {
|
||||
return g.Run(path, "push", remote, name)
|
||||
}
|
||||
|
||||
// DeleteRemoteTag 删除远程标签(默认 origin)
|
||||
func (g *GitClient) DeleteRemoteTag(path, name string) (string, error) {
|
||||
return g.Run(path, "push", "origin", "--delete", name)
|
||||
}
|
||||
|
||||
// DeleteRemoteTagFrom 删除指定远程的标签
|
||||
func (g *GitClient) DeleteRemoteTagFrom(path, remote, name string) (string, error) {
|
||||
return g.Run(path, "push", remote, "--delete", name)
|
||||
}
|
||||
|
||||
// BranchList 获取所有本地分支
|
||||
func (g *GitClient) BranchList(path string) (string, error) {
|
||||
return g.Run(path, "branch", "--format=%(refname:short)\t%(HEAD)")
|
||||
@@ -368,16 +439,26 @@ func (g *GitClient) MergeBranch(path, branch string) (string, error) {
|
||||
return g.Run(path, "merge", branch)
|
||||
}
|
||||
|
||||
// DeleteRemoteBranch 删除远程分支
|
||||
// DeleteRemoteBranch 删除远程分支(默认 origin)
|
||||
func (g *GitClient) DeleteRemoteBranch(path, branch string) (string, error) {
|
||||
return g.Run(path, "push", "origin", "--delete", branch)
|
||||
}
|
||||
|
||||
// DeleteRemoteBranchFrom 删除指定远程的分支
|
||||
func (g *GitClient) DeleteRemoteBranchFrom(path, remote, branch string) (string, error) {
|
||||
return g.Run(path, "push", remote, "--delete", branch)
|
||||
}
|
||||
|
||||
// RemoteBranchList 获取所有远程分支
|
||||
func (g *GitClient) RemoteBranchList(path string) (string, error) {
|
||||
return g.Run(path, "branch", "-r", "--format=%(refname:short)")
|
||||
}
|
||||
|
||||
// RemoteBranchListByRemote 获取指定远程的分支
|
||||
func (g *GitClient) RemoteBranchListByRemote(path, remote string) (string, error) {
|
||||
return g.Run(path, "branch", "-r", "--list", remote+"/*", "--format=%(refname:short)")
|
||||
}
|
||||
|
||||
// CheckoutNewBranch 从远程分支检出新本地分支
|
||||
func (g *GitClient) CheckoutNewBranch(path, localBranch, remoteBranch string) (string, error) {
|
||||
return g.Run(path, "checkout", "-b", localBranch, remoteBranch)
|
||||
|
||||
Reference in New Issue
Block a user