update engine
This commit is contained in:
@@ -164,7 +164,10 @@ func (e *Engine) sortedNames() []string {
|
|||||||
return names
|
return names
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// FIX: LoadAll 加锁,避免并发读写 map
|
||||||
func (e *Engine) LoadAll() {
|
func (e *Engine) LoadAll() {
|
||||||
|
e.mu.Lock()
|
||||||
|
defer e.mu.Unlock()
|
||||||
for _, name := range e.configNames() {
|
for _, name := range e.configNames() {
|
||||||
if _, ok := e.states[name]; ok {
|
if _, ok := e.states[name]; ok {
|
||||||
continue
|
continue
|
||||||
@@ -478,7 +481,6 @@ func (e *Engine) refreshCounts(s *repoState, cfg *RepoConfig, remote string) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// safeSync 包装 syncOnce:捕获后台 goroutine 的 panic,避免整个进程被杀死。
|
// safeSync 包装 syncOnce:捕获后台 goroutine 的 panic,避免整个进程被杀死。
|
||||||
// 此前「立即同步后程序自动退出」的直接原因就是后台 goroutine panic 无 recover。
|
|
||||||
func (e *Engine) safeSync(s *repoState, trigger string) {
|
func (e *Engine) safeSync(s *repoState, trigger string) {
|
||||||
defer func() {
|
defer func() {
|
||||||
if r := recover(); r != nil {
|
if r := recover(); r != nil {
|
||||||
@@ -493,6 +495,7 @@ func (e *Engine) safeSync(s *repoState, trigger string) {
|
|||||||
e.syncOnce(s, trigger)
|
e.syncOnce(s, trigger)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// FIX: 调整同步顺序:先拉取后推送;分支切换避免强制覆盖;pull 使用重试
|
||||||
func (e *Engine) syncOnce(s *repoState, trigger string) {
|
func (e *Engine) syncOnce(s *repoState, trigger string) {
|
||||||
s.mu.Lock()
|
s.mu.Lock()
|
||||||
if s.syncing {
|
if s.syncing {
|
||||||
@@ -528,33 +531,37 @@ func (e *Engine) syncOnce(s *repoState, trigger string) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// 分支对齐:本地当前分支与配置不一致时,自动切换/创建(对齐原 bash 脚本行为)
|
// FIX: 分支对齐逻辑 – 避免使用 -B 强制覆盖,本地存在则普通切换,不存在则创建
|
||||||
// 否则 push 会因 src refspec 不存在而失败
|
|
||||||
if cur, cerr := currentBranch(cfg.RepoPath); cerr == nil && cur != cfg.Branch {
|
if cur, cerr := currentBranch(cfg.RepoPath); cerr == nil && cur != cfg.Branch {
|
||||||
tip, remoteHasBranch := remoteTip(cfg.RepoPath, remote, cfg.Branch)
|
tip, remoteHasBranch := remoteTip(cfg.RepoPath, remote, cfg.Branch)
|
||||||
switch {
|
localExists := branchExists(cfg.RepoPath, cfg.Branch)
|
||||||
case branchExists(cfg.RepoPath, cfg.Branch):
|
if localExists {
|
||||||
|
// 本地分支存在,普通切换(可能因未提交改动而失败)
|
||||||
if _, err := gitRun(cfg.RepoPath, 30*time.Second, "checkout", cfg.Branch); err != nil {
|
if _, err := gitRun(cfg.RepoPath, 30*time.Second, "checkout", cfg.Branch); err != nil {
|
||||||
e.logf(s, "warn", "切换到配置分支 %s 失败: %v(继续在 %s 上同步)", cfg.Branch, err, cur)
|
e.setFail(s, fmt.Sprintf("切换到配置分支 %s 失败,请先提交或暂存改动: %v", cfg.Branch, err))
|
||||||
} else {
|
return
|
||||||
e.logf(s, "info", "本地分支与配置不一致(%s → %s),已自动切换", cur, cfg.Branch)
|
|
||||||
}
|
}
|
||||||
case remoteHasBranch:
|
e.logf(s, "info", "本地分支与配置不一致(%s → %s),已自动切换", cur, cfg.Branch)
|
||||||
if _, err := gitRun(cfg.RepoPath, 30*time.Second, "checkout", "-B", cfg.Branch, tip); err != nil {
|
|
||||||
e.logf(s, "warn", "基于远程 %s 创建分支失败: %v(继续在 %s 上同步)", cfg.Branch, err, cur)
|
|
||||||
} else {
|
} else {
|
||||||
|
// 本地分支不存在,创建(不会覆盖任何内容)
|
||||||
|
if remoteHasBranch {
|
||||||
|
if _, err := gitRun(cfg.RepoPath, 30*time.Second, "checkout", "-b", cfg.Branch, tip); err != nil {
|
||||||
|
e.setFail(s, fmt.Sprintf("基于远程 %s/%s 创建分支失败: %v", remote, cfg.Branch, err))
|
||||||
|
return
|
||||||
|
}
|
||||||
_, _ = gitRun(cfg.RepoPath, 15*time.Second, "branch", "--set-upstream-to", remote+"/"+cfg.Branch, cfg.Branch)
|
_, _ = gitRun(cfg.RepoPath, 15*time.Second, "branch", "--set-upstream-to", remote+"/"+cfg.Branch, cfg.Branch)
|
||||||
e.logf(s, "info", "本地无分支 %s,已基于远程 %s(%s)创建并跟踪", cfg.Branch, cfg.Branch, tip[:8])
|
e.logf(s, "info", "本地无分支 %s,已基于远程 %s(%s)创建并跟踪", cfg.Branch, cfg.Branch, tip[:8])
|
||||||
}
|
|
||||||
default:
|
|
||||||
if _, err := gitRun(cfg.RepoPath, 30*time.Second, "checkout", "-B", cfg.Branch); err != nil {
|
|
||||||
e.logf(s, "warn", "创建分支 %s 失败: %v(继续在 %s 上同步)", cfg.Branch, err, cur)
|
|
||||||
} else {
|
} else {
|
||||||
|
if _, err := gitRun(cfg.RepoPath, 30*time.Second, "checkout", "-b", cfg.Branch); err != nil {
|
||||||
|
e.setFail(s, fmt.Sprintf("创建分支 %s 失败: %v", cfg.Branch, err))
|
||||||
|
return
|
||||||
|
}
|
||||||
e.logf(s, "info", "本地与远程均无分支 %s,已基于当前 HEAD(%s)创建", cfg.Branch, cur)
|
e.logf(s, "info", "本地与远程均无分支 %s,已基于当前 HEAD(%s)创建", cfg.Branch, cur)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 自动提交(若有未提交改动)
|
||||||
dirty := dirtyCount(cfg.RepoPath)
|
dirty := dirtyCount(cfg.RepoPath)
|
||||||
if dirty > 0 {
|
if dirty > 0 {
|
||||||
if cfg.AutoCommit {
|
if cfg.AutoCommit {
|
||||||
@@ -575,6 +582,7 @@ func (e *Engine) syncOnce(s *repoState, trigger string) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 计算初始 ahead/behind
|
||||||
var ahead, behind int
|
var ahead, behind int
|
||||||
var err error
|
var err error
|
||||||
tip, remoteHasBranch := remoteTip(cfg.RepoPath, remote, cfg.Branch)
|
tip, remoteHasBranch := remoteTip(cfg.RepoPath, remote, cfg.Branch)
|
||||||
@@ -585,29 +593,38 @@ func (e *Engine) syncOnce(s *repoState, trigger string) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// 远程分支不存在(空仓库或分支名不一致):把本地提交视为全部领先,推送时建立远程分支
|
// 远程分支不存在:认为所有本地提交领先
|
||||||
if out, cerr := gitRun(cfg.RepoPath, 15*time.Second, "rev-list", "--count", "HEAD"); cerr == nil {
|
if out, cerr := gitRun(cfg.RepoPath, 15*time.Second, "rev-list", "--count", "HEAD"); cerr == nil {
|
||||||
ahead, _ = strconv.Atoi(strings.TrimSpace(out))
|
ahead, _ = strconv.Atoi(strings.TrimSpace(out))
|
||||||
}
|
}
|
||||||
e.logf(s, "warn", "远程分支 %s/%s 不存在(远程仓库可能为空或分支名不一致),将推送本地 %d 个提交以建立远程分支", remote, cfg.Branch, ahead)
|
e.logf(s, "warn", "远程分支 %s/%s 不存在,将推送本地 %d 个提交以建立远程分支", remote, cfg.Branch, ahead)
|
||||||
}
|
}
|
||||||
s.mu.Lock()
|
s.mu.Lock()
|
||||||
s.ahead, s.behind, s.dirty = ahead, behind, dirty
|
s.ahead, s.behind, s.dirty = ahead, behind, dirty
|
||||||
s.mu.Unlock()
|
s.mu.Unlock()
|
||||||
e.pushStatus(s)
|
e.pushStatus(s)
|
||||||
|
|
||||||
if ahead > 0 && (cfg.Mode == "push" || cfg.Mode == "both") {
|
// FIX: 先拉取(pull),再推送(push)
|
||||||
if !e.pushAll(s, cfg, ahead) {
|
|
||||||
// 推送失败,本轮终止,避免继续拉取加剧分叉
|
|
||||||
e.setFail(s, "推送失败,本轮同步已终止(跳过拉取以避免分叉加剧)")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if behind > 0 && (cfg.Mode == "pull" || cfg.Mode == "both") {
|
if behind > 0 && (cfg.Mode == "pull" || cfg.Mode == "both") {
|
||||||
if !e.pullRemote(s, cfg, remote, behind) {
|
if !e.pullRemote(s, cfg, remote, behind) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
// 拉取后重新计算 ahead/behind
|
||||||
|
ahead, behind, _ = aheadBehind(cfg.RepoPath, remote, cfg.Branch)
|
||||||
|
s.mu.Lock()
|
||||||
|
s.ahead, s.behind = ahead, behind
|
||||||
|
s.mu.Unlock()
|
||||||
|
e.pushStatus(s)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if ahead > 0 && (cfg.Mode == "push" || cfg.Mode == "both") {
|
||||||
|
if !e.pushAll(s, cfg, ahead) {
|
||||||
|
e.setFail(s, "推送失败")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 最终刷新状态
|
||||||
ahead, behind, _ = aheadBehind(cfg.RepoPath, remote, cfg.Branch)
|
ahead, behind, _ = aheadBehind(cfg.RepoPath, remote, cfg.Branch)
|
||||||
now := time.Now().Format("2006-01-02 15:04:05")
|
now := time.Now().Format("2006-01-02 15:04:05")
|
||||||
s.mu.Lock()
|
s.mu.Lock()
|
||||||
@@ -641,18 +658,22 @@ func (e *Engine) pushAll(s *repoState, cfg *RepoConfig, n int) bool {
|
|||||||
return allOK
|
return allOK
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// FIX: pullRemote 使用 withRetry 包裹主 pull 命令
|
||||||
func (e *Engine) pullRemote(s *repoState, cfg *RepoConfig, remote string, n int) bool {
|
func (e *Engine) pullRemote(s *repoState, cfg *RepoConfig, remote string, n int) bool {
|
||||||
args := []string{"pull"}
|
args := []string{"pull"}
|
||||||
if cfg.ConflictStrategy == "rebase" {
|
if cfg.ConflictStrategy == "rebase" {
|
||||||
args = append(args, "--rebase")
|
args = append(args, "--rebase")
|
||||||
}
|
}
|
||||||
args = append(args, remote, cfg.Branch, "--no-edit")
|
args = append(args, remote, cfg.Branch, "--no-edit")
|
||||||
_, err := gitRun(cfg.RepoPath, 300*time.Second, args...)
|
_, err := e.withRetry(s, "pull", func() (string, error) {
|
||||||
|
return gitRun(cfg.RepoPath, 300*time.Second, args...)
|
||||||
|
})
|
||||||
if err == nil {
|
if err == nil {
|
||||||
e.stat(s, "pull", "success", n)
|
e.stat(s, "pull", "success", n)
|
||||||
e.logf(s, "info", "拉取成功(%d 个提交)", n)
|
e.logf(s, "info", "拉取成功(%d 个提交)", n)
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
// 处理冲突
|
||||||
files, cerr := conflictFiles(cfg.RepoPath)
|
files, cerr := conflictFiles(cfg.RepoPath)
|
||||||
if cerr == nil && len(files) > 0 {
|
if cerr == nil && len(files) > 0 {
|
||||||
if cfg.ConflictStrategy == "abort" {
|
if cfg.ConflictStrategy == "abort" {
|
||||||
|
|||||||
Reference in New Issue
Block a user