mirror of
https://github.com/AutoMaker-Org/automaker.git
synced 2026-03-16 21:53:07 +00:00
fix: Resolve git operation error handling and conflict detection issues
This commit is contained in:
@@ -15,7 +15,7 @@ import path from 'path';
|
||||
import { exec } from 'child_process';
|
||||
import { promisify } from 'util';
|
||||
import type { Feature, PlanningMode, ThinkingLevel } from '@automaker/types';
|
||||
import { DEFAULT_MAX_CONCURRENCY, stripProviderPrefix } from '@automaker/types';
|
||||
import { DEFAULT_MAX_CONCURRENCY, DEFAULT_MODELS, stripProviderPrefix } from '@automaker/types';
|
||||
import { resolveModelString } from '@automaker/model-resolver';
|
||||
import { createLogger, loadContextFiles, classifyError } from '@automaker/utils';
|
||||
import { getFeatureDir } from '@automaker/platform';
|
||||
@@ -213,7 +213,7 @@ export class AutoModeServiceFacade {
|
||||
[key: string]: unknown;
|
||||
}
|
||||
): Promise<void> => {
|
||||
const resolvedModel = resolveModelString(model, 'claude-sonnet-4-6');
|
||||
const resolvedModel = resolveModelString(model, DEFAULT_MODELS.claude);
|
||||
const provider = ProviderFactory.getProviderForModel(resolvedModel);
|
||||
const effectiveBareModel = stripProviderPrefix(resolvedModel);
|
||||
|
||||
|
||||
@@ -127,8 +127,8 @@ export async function popStash(
|
||||
cwd: string
|
||||
): Promise<{ success: boolean; hasConflicts: boolean; error?: string }> {
|
||||
try {
|
||||
await execGitCommand(['stash', 'pop'], cwd);
|
||||
// If execGitCommand succeeds (zero exit code), there are no conflicts
|
||||
await execGitCommandWithLockRetry(['stash', 'pop'], cwd);
|
||||
// If execGitCommandWithLockRetry succeeds (zero exit code), there are no conflicts
|
||||
return { success: true, hasConflicts: false };
|
||||
} catch (error) {
|
||||
const errorMsg = getErrorMessage(error);
|
||||
|
||||
@@ -296,6 +296,10 @@ export async function performCheckoutBranch(
|
||||
branchName,
|
||||
error: checkoutErrorMsg,
|
||||
});
|
||||
throw checkoutError;
|
||||
return {
|
||||
success: false,
|
||||
error: checkoutErrorMsg,
|
||||
stashPopConflicts: false,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -16,8 +16,8 @@
|
||||
*/
|
||||
|
||||
import { createLogger, getErrorMessage } from '@automaker/utils';
|
||||
import { getConflictFiles } from '@automaker/git-utils';
|
||||
import { execGitCommand, execGitCommandWithLockRetry, getCurrentBranch } from '../lib/git.js';
|
||||
import { execGitCommand, getConflictFiles } from '@automaker/git-utils';
|
||||
import { execGitCommandWithLockRetry, getCurrentBranch } from '../lib/git.js';
|
||||
|
||||
const logger = createLogger('PullService');
|
||||
|
||||
@@ -359,7 +359,7 @@ export async function performPull(
|
||||
// 9. If pull had conflicts, return conflict info (don't try stash pop)
|
||||
if (pullConflict) {
|
||||
return {
|
||||
success: true,
|
||||
success: false,
|
||||
branch: branchName,
|
||||
pulled: true,
|
||||
hasConflicts: true,
|
||||
|
||||
@@ -8,8 +8,7 @@
|
||||
import fs from 'fs/promises';
|
||||
import path from 'path';
|
||||
import { createLogger, getErrorMessage } from '@automaker/utils';
|
||||
import { getConflictFiles } from '@automaker/git-utils';
|
||||
import { execGitCommand, getCurrentBranch } from '../lib/git.js';
|
||||
import { execGitCommand, getCurrentBranch, getConflictFiles } from '@automaker/git-utils';
|
||||
|
||||
const logger = createLogger('RebaseService');
|
||||
|
||||
@@ -64,13 +63,13 @@ export async function runRebase(worktreePath: string, ontoBranch: string): Promi
|
||||
// Pass ontoBranch after '--' so git treats it as a ref, not an option.
|
||||
// Set LC_ALL=C so git always emits English output regardless of the system
|
||||
// locale, making text-based conflict detection reliable.
|
||||
await execGitCommand(['rebase', '--', ontoBranch], worktreePath, { LC_ALL: 'C' });
|
||||
await execGitCommand(['rebase', '--', normalizedOntoBranch], worktreePath, { LC_ALL: 'C' });
|
||||
|
||||
return {
|
||||
success: true,
|
||||
branch: currentBranch,
|
||||
ontoBranch,
|
||||
message: `Successfully rebased ${currentBranch} onto ${ontoBranch}`,
|
||||
ontoBranch: normalizedOntoBranch,
|
||||
message: `Successfully rebased ${currentBranch} onto ${normalizedOntoBranch}`,
|
||||
};
|
||||
} catch (rebaseError: unknown) {
|
||||
// Check if this is a rebase conflict. We use a multi-layer strategy so
|
||||
@@ -165,13 +164,13 @@ export async function runRebase(worktreePath: string, ontoBranch: string): Promi
|
||||
return {
|
||||
success: false,
|
||||
error: aborted
|
||||
? `Rebase of "${currentBranch}" onto "${ontoBranch}" aborted due to conflicts; no changes were applied.`
|
||||
: `Rebase of "${currentBranch}" onto "${ontoBranch}" failed due to conflicts and the abort also failed; repository may be in a dirty state.`,
|
||||
? `Rebase of "${currentBranch}" onto "${normalizedOntoBranch}" aborted due to conflicts; no changes were applied.`
|
||||
: `Rebase of "${currentBranch}" onto "${normalizedOntoBranch}" failed due to conflicts and the abort also failed; repository may be in a dirty state.`,
|
||||
hasConflicts: true,
|
||||
conflictFiles,
|
||||
aborted,
|
||||
branch: currentBranch,
|
||||
ontoBranch,
|
||||
ontoBranch: normalizedOntoBranch,
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -658,7 +658,11 @@ export function GitDiffPanel({
|
||||
}, [worktreePath, projectPath, useWorktrees, enableStaging, files, executeStagingAction]);
|
||||
|
||||
const handleUnstageAll = useCallback(async () => {
|
||||
const allPaths = files.map((f) => f.path);
|
||||
const stagedFiles = files.filter((f) => {
|
||||
const state = getStagingState(f);
|
||||
return state === 'staged' || state === 'partial';
|
||||
});
|
||||
const allPaths = stagedFiles.map((f) => f.path);
|
||||
if (allPaths.length === 0) return;
|
||||
if (enableStaging && useWorktrees && !worktreePath) {
|
||||
toast.error('Failed to unstage all files', {
|
||||
|
||||
23
libs/git-utils/src/branch.ts
Normal file
23
libs/git-utils/src/branch.ts
Normal file
@@ -0,0 +1,23 @@
|
||||
/**
|
||||
* Git branch utilities
|
||||
*/
|
||||
|
||||
import { execGitCommand } from './exec.js';
|
||||
|
||||
/**
|
||||
* Get the current branch name for a given worktree path.
|
||||
*
|
||||
* @param worktreePath - Path to the git worktree
|
||||
* @returns Promise resolving to the current branch name (trimmed)
|
||||
* @throws Error if the git command fails
|
||||
*
|
||||
* @example
|
||||
* ```typescript
|
||||
* const branch = await getCurrentBranch('/path/to/worktree');
|
||||
* console.log(branch); // 'main'
|
||||
* ```
|
||||
*/
|
||||
export async function getCurrentBranch(worktreePath: string): Promise<string> {
|
||||
const branchOutput = await execGitCommand(['rev-parse', '--abbrev-ref', 'HEAD'], worktreePath);
|
||||
return branchOutput.trim();
|
||||
}
|
||||
@@ -23,3 +23,6 @@ export {
|
||||
|
||||
// Export conflict utilities
|
||||
export { getConflictFiles } from './conflict.js';
|
||||
|
||||
// Export branch utilities
|
||||
export { getCurrentBranch } from './branch.js';
|
||||
|
||||
Reference in New Issue
Block a user