mirror of
https://github.com/jesseduffield/lazygit.git
synced 2026-09-10 07:36:27 -04:00
Fix compilation errors.
This commit is contained in:
parent
233045b9cf
commit
061a1ea859
|
|
@ -121,6 +121,7 @@ func NewGitCommandAux(
|
|||
gitCommon := git_commands.NewGitCommon(cmn, version, cmd, osCommand, repoPaths, configCommands, diffRendererConfigManager)
|
||||
|
||||
svnCommands := git_commands.NewSvnCommands(gitCommon, cmd)
|
||||
gitCommon.Svn = svnCommands
|
||||
fileLoader := git_commands.NewFileLoader(gitCommon, cmd, configCommands)
|
||||
statusCommands := git_commands.NewStatusCommands(gitCommon)
|
||||
flowCommands := git_commands.NewFlowCommands(gitCommon)
|
||||
|
|
|
|||
|
|
@ -17,6 +17,7 @@ type GitCommon struct {
|
|||
config *ConfigCommands
|
||||
diffRendererConfigManager *config.DiffRendererConfigManager
|
||||
IsGitSvnRepo bool
|
||||
Svn *SvnCommands
|
||||
}
|
||||
|
||||
func (self *GitCommon) detectGitSvnRepo() {
|
||||
|
|
@ -33,7 +34,9 @@ func (self *GitCommon) detectGitSvnRepo() {
|
|||
svnDir := filepath.Join(self.repoPaths.RepoGitDirPath(), "svn")
|
||||
if info, err := os.Stat(svnDir); err == nil && info.IsDir() {
|
||||
self.IsGitSvnRepo = true
|
||||
self.Common.Log.Info("Detected Git-SVN repository (found .git/svn)")
|
||||
if self.Common != nil {
|
||||
self.Common.Log.Info("Detected Git-SVN repository (found .git/svn)")
|
||||
}
|
||||
} else {
|
||||
self.IsGitSvnRepo = false
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@ package git_commands
|
|||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
"github.com/jesseduffield/lazygit/pkg/gocui"
|
||||
"github.com/jesseduffield/lazygit/pkg/commands/models"
|
||||
|
|
@ -25,10 +26,22 @@ type SvnCommands struct {
|
|||
svnRefMappingsCache *[]SvnRefMapping
|
||||
svnUrlCache string
|
||||
svnUrlCacheExpiry time.Time
|
||||
// CheckBranchStatus 结果缓存(key 为 refType),60秒过期,
|
||||
// 避免每次界面刷新都发起 svn list 网络请求;
|
||||
// branches 与 tags 在不同 worker协程并发调用,
|
||||
// Go map 并发读写会触发不可恢复的 fatal error,故必须加锁
|
||||
statusCache map[string]map[string]models.SvnBranchStatus
|
||||
statusCacheExpiry map[string]time.Time
|
||||
statusCacheMutex sync.Mutex
|
||||
}
|
||||
|
||||
func NewSvnCommands(gitCommon *GitCommon, cmd oscommands.ICmdObjBuilder) *SvnCommands {
|
||||
return &SvnCommands{GitCommon: gitCommon, cmd: cmd}
|
||||
return &SvnCommands{
|
||||
GitCommon: gitCommon,
|
||||
cmd: cmd,
|
||||
statusCache: make(map[string]map[string]models.SvnBranchStatus),
|
||||
statusCacheExpiry: make(map[string]time.Time),
|
||||
}
|
||||
}
|
||||
|
||||
// GetSvnUrl 从git config获取SVN仓库URL
|
||||
|
|
@ -244,51 +257,81 @@ func (self *SvnCommands) Fetch() error {
|
|||
|
||||
// CheckBranchStatus 检测本地 refs 和 SVN 服务器的差异
|
||||
// refType: "branches" 或 "tags"
|
||||
// 返回值:map[branchPath]models.SvnBranchStatus, branchPath 如 "branches/proj1/xxx"
|
||||
// SVN list 使用 --non-interactive 防止网络阻塞,结果不缓存(每次进入时重新检测)
|
||||
// 返回值:map[refRelPath]models.SvnBranchStatus, refRelPath 为 ref 相对路
|
||||
//(完整 ref 去掉 “refs/remotes/git-svn/” 前缀,如 “branches/proj1/xxx”、”tags/R1.0.0”),
|
||||
// 与 RemoteBranch.Name / TrimPrefix(tag.FullRefName(), “refs/remotes/git-svn/”)格式一致
|
||||
// 结果缓存 60 秒; svn list 全部失败时返回错误(错误不缓存,下次调用自动重试)径
|
||||
func (self *SvnCommands) CheckBranchStatus(task gocui.Task, refType string) (map[string]models.SvnBranchStatus, error) {
|
||||
// 整个方法加锁: branches/tags 两个调用方在不同 worker 协程,Go map并发读写
|
||||
// 会直接触发不可恢复的 fatal error;加锁同时保护函数内
|
||||
// GetSvnUrl/GetSvnRefMappings 既有缓存在此路径上的并发访问
|
||||
self.statusCacheMutex.Lock()
|
||||
defer self.statusCacheMutex.Unlock()
|
||||
|
||||
// 0. 命中缓存直接返回(60 秒内),避免重复发起 svn list 网络请求
|
||||
if cached, ok := self.statusCache[refType]; ok && time.Now().Before(self.statusCacheExpiry[refType]) {
|
||||
return cached, nil
|
||||
}
|
||||
|
||||
svnUrl, err := self.GetSvnUrl()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// 1. 获取本地 refs
|
||||
mappings, _ := self.GetSvnRefMappings()
|
||||
|
||||
// 1. 获取本地 refs(遍历该类型所有 mapping 的 RefsPath,
|
||||
// 兼容refs 端影射到非标准路径的配置,如 tags = tags/*:refs/remotes/git-svn/releases/*)
|
||||
localRefs := make(map[string]bool)
|
||||
refsPath := "refs/remotes/git-svn/" + refType
|
||||
output, err := self.cmd.New(
|
||||
NewGitCmd("for-each-ref").Arg("--format=%(refname)").Arg(refsPath).ToArgv(),
|
||||
).DontLog().RunWithOutput()
|
||||
if err == nil {
|
||||
for _, m := range mappings {
|
||||
if m.Type != refType {
|
||||
continue;
|
||||
}
|
||||
output, refErr := self.cmd.New(
|
||||
NewGitCmd("for-each-ref").Arg("--format=%(refname)").Arg(m.RefsPath).ToArgv(),
|
||||
).DontLog().RunWithOutput()
|
||||
if refErr != nil {
|
||||
continue;
|
||||
}
|
||||
for _, line := range strings.Split(strings.TrimSpace(output), "\n") {
|
||||
if line := strings.TrimSpace(line); line != "" {
|
||||
relPath := strings.TrimPrefix(line, refsPath+"/")
|
||||
localRefs[relPath] = true
|
||||
// Key 统一为 ref 相对路径
|
||||
localRefs[strings.TrimPrefix(line, "refs/remotes/git-svn/")] = true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 2. 获取 SVN 服务器上的分支列表(遍历所有 SvnRefMappings)
|
||||
mappings, _ := self.GetSvnRefMappings()
|
||||
svnBranches := make(map[string]bool)
|
||||
svnListAttempted := false
|
||||
svnListOk := false
|
||||
for _, m := range mappings {
|
||||
if m.Type != refType {
|
||||
continue
|
||||
}
|
||||
svnListAttempted = true
|
||||
// svn list 使用 --non-interactive 防止网络不通时永久阻塞
|
||||
svnListOutput, listErr := self.cmd.New(
|
||||
NewGitCmd("svn").Arg("list").Arg("--non-interactive").Arg(svnUrl+"/"+m.SvnPath).ToArgv(),
|
||||
).DontLog().RunWithOutput()
|
||||
if listErr == nil {
|
||||
svnListOk = true
|
||||
for _, line := range strings.Split(strings.TrimSpace(svnListOutput), "\n") {
|
||||
if line := strings.TrimSpace(line); line != "" {
|
||||
name := strings.TrimSuffix(line, "/")
|
||||
relPath := m.SvnPath + "/" + name
|
||||
svnBranches[relPath] = true
|
||||
// SVN 路径正向影射为 ref 相对路径,与 localRefs 的 key 格式统一
|
||||
svnBranches[strings.TrimPrefix(m.RefsPath, "refs/remotes/git-svn/")+"/"+name] = true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// svn list 全部失败(网络不通、认证失败等)时返回错误并中止,
|
||||
// 避免把”全部 Stale”的误导性结果当作真实状态展示(错误不缓存,下次自动重时)
|
||||
if svnListAttempted && !svnListOk {
|
||||
return nil, fmt.Errorf("svn list failed for all %s paths (network or auth error?)", refType)
|
||||
}
|
||||
|
||||
// 3. 对比差异
|
||||
result := make(map[string]models.SvnBranchStatus)
|
||||
allPaths := make(map[string]bool)
|
||||
|
|
@ -315,6 +358,11 @@ func (self *SvnCommands) CheckBranchStatus(task gocui.Task, refType string) (map
|
|||
result[path] = status
|
||||
}
|
||||
|
||||
// 写入缓存(60 秒)。到达此处时 SVN 侧数据必然可信:
|
||||
// 若有过 svn list 且全部失败,上方已提前返回
|
||||
self.statusCache[refType] = result
|
||||
self.statusCacheExpiry[refType] = time.Now().Add(60 * time.Second)
|
||||
|
||||
return result, nil
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -19,9 +19,9 @@ type Tag struct {
|
|||
|
||||
func (t *Tag) FullRefName() string {
|
||||
if t.FullRefNameOverride != "" {
|
||||
return "refs/tags/" + t.RefName()
|
||||
return t.FullRefNameOverride
|
||||
}
|
||||
return ""
|
||||
return "refs/tags/" + t.RefName()
|
||||
}
|
||||
|
||||
func (t *Tag) RefName() string {
|
||||
|
|
|
|||
|
|
@ -782,7 +782,7 @@ func (self *BranchesController) newBranch(selectedBranch *models.Branch) error {
|
|||
Items: []*types.MenuItem{
|
||||
{
|
||||
LabelColumns: []string{self.c.Tr.NewBranch},
|
||||
Key: 'l',
|
||||
Keys: menuKey('l'),
|
||||
OnPress: func() error {
|
||||
return self.c.Helpers().Refs.NewBranch(
|
||||
selectedBranch.FullRefName(),
|
||||
|
|
@ -793,7 +793,7 @@ func (self *BranchesController) newBranch(selectedBranch *models.Branch) error {
|
|||
},
|
||||
{
|
||||
LabelColumns: []string{self.c.Tr.NewSvnBranch},
|
||||
Key: 's',
|
||||
Keys: menuKey('s'),
|
||||
OnPress: func() error {
|
||||
return self.newSvnBranch(selectedBranch)
|
||||
},
|
||||
|
|
@ -972,11 +972,10 @@ func (self *BranchesController)newSvnBranch(selectedBranch *models.Branch) error
|
|||
}
|
||||
|
||||
if err := self.c.Git().Svn.Fetch(); err != nil {
|
||||
self.c.ErrorMsg(fmt.Sprintf(self.c.Tr.SvnFetchFailed))
|
||||
return fmt.Errorf(self.c.Tr.SvnFetchFailed)
|
||||
}
|
||||
|
||||
self.c.Refresh(types.RefreshOptions{
|
||||
Mode: types.SYNC,
|
||||
Scope: []types.RefreshableView{types.BRANCHES, types.REMOTES},
|
||||
})
|
||||
return nil
|
||||
|
|
|
|||
|
|
@ -1126,7 +1126,7 @@ func (self *RefreshHelper) refreshTags(env refreshEnv) error {
|
|||
|
||||
// SVN 自动 stale 检测 (tags)
|
||||
if self.c.Git().Sync.GitCommon.IsSvnRepo() {
|
||||
self.checkSvnTagStatusAsync(tags)
|
||||
self.checkSvnTagStatusAsync()
|
||||
}
|
||||
|
||||
self.refreshView(self.c.Contexts().Tags, env)
|
||||
|
|
@ -1889,21 +1889,28 @@ func (self *RefreshHelper) savePullRequestsToCache(prs []*models.GithubPullReque
|
|||
}
|
||||
}
|
||||
|
||||
func (self *RefreshHelper) checkSvnTagStatusAsync(tags []*models.Tag) {
|
||||
func (self *RefreshHelper) checkSvnTagStatusAsync() {
|
||||
self.c.WithWaitingStatus(self.c.Tr.CheckingSvnStatus, func (task gocui.Task) error {
|
||||
statuses, err := self.c.Git().Svn.CheckBranchStatus(task, "tags")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
for _, tag := range tags {
|
||||
if tag.IsSvnTag() {
|
||||
if status, ok := statuses[tag.Name]; ok {
|
||||
self.c.OnUIThread(func() error {
|
||||
for _, tag := range self.c.Model().Tags {
|
||||
if !tag.IsSvnTag() {
|
||||
continue
|
||||
}
|
||||
// FullRefName() 对 SVN tag 返回 FullRefNameOverride,
|
||||
// trim 后即 ref 相对路径,与 statuses 的 key 格式一致
|
||||
relPath := strings.TrimPrefix(tag.FullRefName(), "refs/remotes/git-svn/")
|
||||
if status, ok := statuses[relPath]; ok {
|
||||
tag.StaleStatus = status
|
||||
}
|
||||
}
|
||||
}
|
||||
return self.c.Refresh(types.RefreshOptions{
|
||||
Scope: []types.RefreshableView{types.TAGS},
|
||||
// 仅重绘视图,不 Refresh(TAGS),打断无限刷新链
|
||||
self.c.PostRefreshUpdate(self.c.Contexts().Tags)
|
||||
return nil
|
||||
})
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
|
|
|||
|
|
@ -7,6 +7,7 @@ import (
|
|||
"github.com/jesseduffield/lazygit/pkg/commands/models"
|
||||
"github.com/jesseduffield/lazygit/pkg/gui/context"
|
||||
"github.com/jesseduffield/lazygit/pkg/gui/types"
|
||||
"github.com/jesseduffield/lazygit/pkg/gocui"
|
||||
"github.com/jesseduffield/lazygit/pkg/utils"
|
||||
)
|
||||
|
||||
|
|
@ -225,17 +226,17 @@ func (self *RemoteBranchesController) deleteSvnRemoteBranches(selectedBranches [
|
|||
|
||||
return self.c.Menu(types.CreateMenuOptions{
|
||||
Title: menuTitle,
|
||||
Items: []*types.MenuTitle{
|
||||
Items: []*types.MenuItem{
|
||||
{
|
||||
LabelColumns: []string{self.c.Tr.DeleteSvnLocalRef},
|
||||
Key: 'l',
|
||||
Keys: menuKey('l'),
|
||||
OnPress: func() error {
|
||||
return self.deleteSvnLocalRefs(selectedBranches)
|
||||
},
|
||||
},
|
||||
{
|
||||
LabelColumns: []string{self.c.Tr.DeleteSvnBoth},
|
||||
Key: 'b',
|
||||
Keys: menuKey('b'),
|
||||
OnPress: func() error {
|
||||
return self.confirmDeleteSvnBoth(selectedBranches)
|
||||
},
|
||||
|
|
@ -254,7 +255,6 @@ func (self *RemoteBranchesController) deleteSvnLocalRefs(selectedBranches []*mod
|
|||
}
|
||||
self.c.Contexts().RemoteBranches.CollapseRangeSelectionToTop()
|
||||
self.c.Refresh(types.RefreshOptions{
|
||||
Mode: types.ASYNC,
|
||||
Scope: []types.RefreshableView{types.BRANCHES, types.REMOTES},
|
||||
})
|
||||
return nil
|
||||
|
|
@ -279,14 +279,16 @@ func (self *RemoteBranchesController) confirmDeleteSvnBoth(selectedBranches []*m
|
|||
return self.c.WithWaitingStatus(self.c.Tr.DeletingStatus, func(task gocui.Task,) error {
|
||||
for _, branch := range selectedBranches {
|
||||
if err := self.c.Git().Svn.DeleteServerBranch(task, branch.Name); err != nil {
|
||||
return fmt.Errorf(self.c.Tr.SvnOperationFailed, map[string]string{"error": err.Error()})
|
||||
return fmt.Errorf("%s", utils.ResolvePlaceholderString(
|
||||
self.c.Tr.SvnOperationFailed,
|
||||
map[string]string{"error": err.Error()},
|
||||
))
|
||||
}
|
||||
refName := branch.RemoteName + "/" + branch.Name
|
||||
_ = self.c.Git().Svn.DeleteLocalRef(refName)
|
||||
}
|
||||
self.c.Contexts().RemoteBranches.CollapseRangeSelectionToTop()
|
||||
self.c.Refresh(types.RefreshOptions{
|
||||
Mode: types.ASYNC,
|
||||
Scope: []types.RefreshableView{types.BRANCHES, types.REMOTES},
|
||||
})
|
||||
return nil
|
||||
|
|
|
|||
|
|
@ -398,7 +398,7 @@ func (self *RemotesController) fetchAndCheckout(remote *models.Remote, branchNam
|
|||
}
|
||||
|
||||
func (self *RemotesController) notGitSvnRemote() *types.DisabledReason {
|
||||
remote := self.Context().GetSelected()
|
||||
remote := self.context().GetSelected()
|
||||
if remote != nil && remote.Name == "git-svn" {
|
||||
return &types.DisabledReason{Text: "Cannot modify git-svn remote"}
|
||||
}
|
||||
|
|
@ -411,10 +411,20 @@ func (self *RemotesController) checkSvnBranchStatusAsync() {
|
|||
if err != nil {
|
||||
return err
|
||||
}
|
||||
// 将结果写入 RemoteBranch 模型的 StaleStatus 字段
|
||||
// 通过 Refresh 触发 presentation 层重新渲染
|
||||
return self.c.Refresh(types.RefreshOptions{
|
||||
Scope: []types.RefreshableView{types.REMOTES},
|
||||
self.c.OnUIThread(func() error {
|
||||
for _, branch := range self.c.Model().RemoteBranches {
|
||||
if branch.RemoteName != "git-svn" {
|
||||
continue
|
||||
}
|
||||
// key 为 ref 相对路径,与 RemoteBranch.Name 格式一致
|
||||
if status, ok := statuses[branch.Name]; ok {
|
||||
branch.StaleStatus = status
|
||||
}
|
||||
}
|
||||
// 仅重绘视图,不 Refresh 重建模型(避免 StaleStatus 被冲掉)
|
||||
self.c.PostRefreshUpdate(self.c.Contexts().RemoteBranches)
|
||||
return nil
|
||||
})
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue