diff --git a/src/commands/sync.ts b/src/commands/sync.ts index 280f3f7a7..9578b07c9 100644 --- a/src/commands/sync.ts +++ b/src/commands/sync.ts @@ -230,6 +230,19 @@ function git(repoPath: string, args: string[], configs: string[] = []): string { }).trim(); } +function hasOriginRemote(repoPath: string): boolean { + try { + execFileSync('git', buildGitInvocation(repoPath, ['remote', 'get-url', 'origin']), { + encoding: 'utf-8', + timeout: 30000, + stdio: ['ignore', 'ignore', 'ignore'], + }); + return true; + } catch { + return false; + } +} + function isDetachedHead(repoPath: string): boolean { try { git(repoPath, ['symbolic-ref', '--quiet', 'HEAD']); @@ -450,7 +463,12 @@ async function performSyncInner(engine: BrainEngine, opts: SyncOpts): Promise { expect(await engine.getConfig('sync.repo_path')).toBeNull(); }); + test('first sync without origin skips git pull noise and uses local working tree', async () => { + const { performSync } = await import('../src/commands/sync.ts'); + const messages: string[] = []; + const originalError = console.error; + console.error = (...args: unknown[]) => { messages.push(args.map(String).join(' ')); }; + try { + const result = await performSync(engine, { + repoPath, + noEmbed: true, + }); + expect(result.status).toBe('first_sync'); + } finally { + console.error = originalError; + } + + expect(messages.some(m => m.includes('No origin remote') && m.includes('skipping git pull'))).toBe(true); + expect(messages.some(m => m.includes('sync.git_pull start'))).toBe(false); + expect(messages.some(m => m.includes('git pull failed'))).toBe(false); + }); + test('incremental dry-run does NOT write to DB or advance the bookmark', async () => { const { performSync } = await import('../src/commands/sync.ts'); // First do a real sync to seed the bookmark.