You've already forked agentic-coding-workflow
git 逻辑
This commit is contained in:
@@ -212,31 +212,43 @@ func (s *workspaceService) Sync(ctx context.Context, c Caller, wsID uuid.UUID) (
|
||||
} else if !canWrite {
|
||||
return nil, errs.New(errs.CodeForbidden, "no write access")
|
||||
}
|
||||
if ws.SyncStatus == SyncStatusCloning || ws.SyncStatus == SyncStatusSyncing {
|
||||
return nil, errs.New(errs.CodeWorkspaceSyncInProgress, "sync already in progress")
|
||||
}
|
||||
if _, err := s.repo.UpdateWorkspaceSyncStatus(ctx, wsID, SyncStatusSyncing, ws.LastSyncedAt, ""); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
// 先解密凭据,再抢锁置 syncing:避免置 syncing 后 load 失败留下残留。
|
||||
cred, err := s.loadDecryptedCredential(ctx, wsID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
fetchCtx, cancel := context.WithTimeout(ctx, s.fetchTimout)
|
||||
defer cancel()
|
||||
fetchErr := s.gitr.Fetch(fetchCtx, ws.MainPath, cred)
|
||||
if fetchErr != nil {
|
||||
errMsg := summarizeErr(fetchErr)
|
||||
_, _ = s.repo.UpdateWorkspaceSyncStatus(ctx, wsID, SyncStatusError, ws.LastSyncedAt, errMsg)
|
||||
s.audit(ctx, &c.UserID, "workspace.sync_failed", "workspace", wsID.String(), map[string]any{"err": errMsg})
|
||||
return nil, errs.Wrap(fetchErr, errs.CodeGitCmdFailed, "git fetch failed")
|
||||
}
|
||||
now := time.Now().UTC()
|
||||
updated, err := s.repo.UpdateWorkspaceSyncStatus(ctx, wsID, SyncStatusIdle, &now, "")
|
||||
// 原子 CAS 抢锁(WHERE sync_status IN ('idle','error')),消除"读后写"TOCTOU。
|
||||
// 抢到才继续,否则说明已有 clone/sync 在进行中。
|
||||
acquired, err := s.repo.MarkSyncing(ctx, wsID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
s.audit(ctx, &c.UserID, "workspace.sync", "workspace", wsID.String(), nil)
|
||||
if !acquired {
|
||||
return nil, errs.New(errs.CodeWorkspaceSyncInProgress, "sync already in progress")
|
||||
}
|
||||
// 状态回写与请求生命周期解耦:客户端断开/请求超时也要能写回状态,避免永久卡 syncing。
|
||||
writeCtx := context.WithoutCancel(ctx)
|
||||
fetchCtx, cancel := context.WithTimeout(ctx, s.fetchTimout)
|
||||
defer cancel()
|
||||
if fetchErr := s.gitr.Fetch(fetchCtx, ws.MainPath, cred); fetchErr != nil {
|
||||
errMsg := summarizeErr(fetchErr)
|
||||
_, _ = s.repo.UpdateWorkspaceSyncStatus(writeCtx, wsID, SyncStatusError, ws.LastSyncedAt, errMsg)
|
||||
s.audit(writeCtx, &c.UserID, "workspace.sync_failed", "workspace", wsID.String(), map[string]any{"err": errMsg})
|
||||
return nil, errs.Wrap(fetchErr, errs.CodeGitCmdFailed, "git fetch failed")
|
||||
}
|
||||
// fetch 只更新远端引用,需 ff-only merge 把 main 工作区当前分支快进到远端。
|
||||
if mergeErr := s.gitr.MergeFFOnly(fetchCtx, ws.MainPath); mergeErr != nil {
|
||||
errMsg := summarizeErr(mergeErr)
|
||||
_, _ = s.repo.UpdateWorkspaceSyncStatus(writeCtx, wsID, SyncStatusError, ws.LastSyncedAt, errMsg)
|
||||
s.audit(writeCtx, &c.UserID, "workspace.sync_failed", "workspace", wsID.String(), map[string]any{"err": errMsg})
|
||||
return nil, errs.Wrap(mergeErr, errs.CodeGitCmdFailed, "git merge --ff-only failed")
|
||||
}
|
||||
now := time.Now().UTC()
|
||||
updated, err := s.repo.UpdateWorkspaceSyncStatus(writeCtx, wsID, SyncStatusIdle, &now, "")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
s.audit(writeCtx, &c.UserID, "workspace.sync", "workspace", wsID.String(), nil)
|
||||
return updated, nil
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user