diff --git a/src/commands/sync.ts b/src/commands/sync.ts index 08c798e..f3bf027 100644 --- a/src/commands/sync.ts +++ b/src/commands/sync.ts @@ -12,6 +12,7 @@ import { getCurrentBranch, checkout, pullBranch, + fetchAndUpdateBranch, rebaseBranch, mergeBranch, deleteBranch, @@ -41,12 +42,11 @@ async function runSync(options: { all?: boolean }): Promise { // ── Step 1: Always pull default branch ─────────────────────────────────────── const startBranch = await getCurrentBranch(); - if (startBranch !== defaultBranch) { - await checkout(defaultBranch); - } - await withSpinner(`Pulling ${defaultBranch}...`, () => pullBranch(defaultBranch)); - if (startBranch !== defaultBranch) { - await checkout(startBranch); + if (startBranch === defaultBranch) { + await withSpinner(`Pulling ${defaultBranch}...`, () => pullBranch(defaultBranch)); + } else { + // Update local default branch ref via fetch without checking it out + await withSpinner(`Pulling ${defaultBranch}...`, () => fetchAndUpdateBranch(defaultBranch)); } console.log(theme.success(` ${symbols.success} Pulled latest ${defaultBranch}`)); @@ -148,6 +148,18 @@ async function runSync(options: { all?: boolean }): Promise { const diverged = await hasDiverged(branch.branchName, defaultBranch); if (!diverged) continue; + const isCurrentBranch = branch.branchName === currentBranch; + + // For non-current branches without a worktree we can't rebase/merge without checkout + if (!isCurrentBranch && !branch.worktreePath) { + console.log( + theme.muted( + ` ${symbols.arrow} ${branch.branchName} is behind ${defaultBranch} — switch to it to update`, + ), + ); + continue; + } + const action = await select<'rebase' | 'merge' | 'skip'>({ message: `${defaultBranch} has new commits not in ${branch.branchName}. What do you want to do?`, options: [ @@ -159,18 +171,17 @@ async function runSync(options: { all?: boolean }): Promise { if (action === 'skip') continue; - if (currentBranch !== branch.branchName) { - await checkout(branch.branchName); - } + // Run in the worktree directory for non-current branches; current branch uses process cwd + const cwd = isCurrentBranch ? undefined : (branch.worktreePath ?? undefined); try { if (action === 'rebase') { - await rebaseBranch(defaultBranch); + await rebaseBranch(defaultBranch, cwd); console.log( theme.success(` ${symbols.success} Rebased ${branch.branchName} onto ${defaultBranch}`), ); } else { - await mergeBranch(defaultBranch); + await mergeBranch(defaultBranch, true, cwd); console.log( theme.success(` ${symbols.success} Merged ${defaultBranch} into ${branch.branchName}`), ); @@ -181,10 +192,6 @@ async function runSync(options: { all?: boolean }): Promise { theme.warning(` ${symbols.warning} ${action} failed for ${branch.branchName}: ${msg}`), ); } - - if (currentBranch !== branch.branchName) { - await checkout(currentBranch); - } } await configManager.saveBranches(projectId, branchesFile); diff --git a/src/git/index.ts b/src/git/index.ts index df35a9e..5ed3a3f 100644 --- a/src/git/index.ts +++ b/src/git/index.ts @@ -141,9 +141,9 @@ export async function removeWorktree(worktreePath: string): Promise { if (result.exitCode !== 0) throw new GitError(`git worktree remove failed: ${result.stderr}`); } -export async function mergeBranch(branch: string, noFF = true): Promise { +export async function mergeBranch(branch: string, noFF = true, cwd?: string): Promise { const args = noFF ? ['merge', '--no-ff', branch] : ['merge', branch]; - const result = await execa('git', args, { reject: false }); + const result = await execa('git', args, { reject: false, ...(cwd ? { cwd } : {}) }); if (result.exitCode !== 0) throw new GitError(`git merge failed: ${result.stderr}`); } @@ -188,7 +188,7 @@ export async function getWorktreePathForBranch(branch: string): Promise { - const result = await execa('git', ['rebase', onto], { reject: false }); +export async function rebaseBranch(onto: string, cwd?: string): Promise { + const result = await execa('git', ['rebase', onto], { reject: false, ...(cwd ? { cwd } : {}) }); if (result.exitCode !== 0) throw new GitError(`git rebase failed: ${result.stderr}`); }