fix(terminal): push worktree branch to remote with tracking on creation (#1753)
* fix(terminal): push worktree branch to remote with tracking on creation
Terminal worktree branches were created with --no-track and never pushed,
leaving them in an undefined state with no upstream configured. This caused
confusion when subsequently pushing commits from the worktree.
Now after git worktree add, we check for an origin remote and run
git push -u origin terminal/{name} to establish proper tracking. Push
failures are non-fatal — the worktree is still usable but a warning is
surfaced to the caller.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix(terminal): surface remote push warning via toast notification
The warning field returned by createTerminalWorktree was not consumed
by the UI, so users had no visibility when remote tracking failed to
set up. Now shows a destructive toast with translated message when
the worktree is created but the push to remote fails.
Added i18n keys for both en and fr locales.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix(terminal): separate remote check from push and surface actual error
Split the single try-catch into two: silently skip push for local-only
repos (no origin), only warn when origin exists but push fails. Show the
actual error message in the toast instead of a generic string.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
---------
Co-authored-by: Test User <test@example.com>
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -451,6 +451,9 @@ async function createTerminalWorktree(
|
||||
}
|
||||
}
|
||||
|
||||
let remoteTrackingSetUp = false;
|
||||
let remotePushWarning: string | undefined;
|
||||
|
||||
if (createGitBranch) {
|
||||
// Use --no-track to prevent the new branch from inheriting upstream tracking
|
||||
// from the base ref (e.g., origin/main). This ensures users can push with -u
|
||||
@@ -463,6 +466,44 @@ async function createTerminalWorktree(
|
||||
env: getIsolatedGitEnv(),
|
||||
});
|
||||
debugLog('[TerminalWorktree] Created worktree with branch:', branchName, 'from', baseRef);
|
||||
|
||||
// Push the new branch to remote and set up tracking so subsequent
|
||||
// git push/pull operations work correctly from the worktree.
|
||||
// This prevents branches from accumulating local-only commits with
|
||||
// no upstream configured, which causes confusion when pushing later.
|
||||
// Check if 'origin' remote exists — silently skip for local-only repos
|
||||
let hasOrigin = false;
|
||||
try {
|
||||
await execFileAsync(getToolPath('git'), ['remote', 'get-url', 'origin'], {
|
||||
cwd: projectPath,
|
||||
encoding: 'utf-8',
|
||||
timeout: 5000,
|
||||
env: getIsolatedGitEnv(),
|
||||
});
|
||||
hasOrigin = true;
|
||||
} catch {
|
||||
// No origin remote — local-only repo, nothing to push to
|
||||
debugLog('[TerminalWorktree] No origin remote found, skipping push for local-only repo');
|
||||
}
|
||||
|
||||
if (hasOrigin) {
|
||||
try {
|
||||
await execFileAsync(getToolPath('git'), ['push', '-u', 'origin', branchName], {
|
||||
cwd: worktreePath,
|
||||
encoding: 'utf-8',
|
||||
timeout: 30000,
|
||||
env: getIsolatedGitEnv(),
|
||||
});
|
||||
remoteTrackingSetUp = true;
|
||||
debugLog('[TerminalWorktree] Pushed branch to remote with tracking:', branchName);
|
||||
} catch (pushError) {
|
||||
// Worktree was created successfully — don't fail the operation,
|
||||
// but surface a warning so the user knows tracking isn't set up.
|
||||
const message = pushError instanceof Error ? pushError.message : 'Unknown push error';
|
||||
remotePushWarning = message;
|
||||
debugLog('[TerminalWorktree] Could not push to remote (worktree still usable):', message);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// Use async to avoid blocking the main process on large repos.
|
||||
await execFileAsync(getToolPath('git'), ['worktree', 'add', '--detach', worktreePath, baseRef], {
|
||||
@@ -490,12 +531,13 @@ async function createTerminalWorktree(
|
||||
taskId,
|
||||
createdAt: new Date().toISOString(),
|
||||
terminalId,
|
||||
remoteTrackingSetUp,
|
||||
};
|
||||
|
||||
saveWorktreeConfig(projectPath, name, config);
|
||||
debugLog('[TerminalWorktree] Saved config for worktree:', name);
|
||||
|
||||
return { success: true, config };
|
||||
return { success: true, config, warning: remotePushWarning };
|
||||
} catch (error) {
|
||||
debugError('[TerminalWorktree] Error creating worktree:', error);
|
||||
|
||||
|
||||
@@ -21,6 +21,7 @@ import {
|
||||
SelectValue,
|
||||
} from '../ui/select';
|
||||
import { Combobox } from '../ui/combobox';
|
||||
import { useToast } from '../../hooks/use-toast';
|
||||
import { buildBranchOptions } from '../../lib/branch-utils';
|
||||
import type { Task, TerminalWorktreeConfig, GitBranchDetail } from '../../../shared/types';
|
||||
import { useProjectStore } from '../../stores/project-store';
|
||||
@@ -84,6 +85,7 @@ export function CreateWorktreeDialog({
|
||||
onWorktreeCreated,
|
||||
}: CreateWorktreeDialogProps) {
|
||||
const { t } = useTranslation(['terminal', 'common']);
|
||||
const { toast } = useToast();
|
||||
const [name, setName] = useState('');
|
||||
const [selectedTaskId, setSelectedTaskId] = useState<string | undefined>();
|
||||
const [createGitBranch, setCreateGitBranch] = useState(true);
|
||||
@@ -225,6 +227,14 @@ export function CreateWorktreeDialog({
|
||||
setName('');
|
||||
setSelectedTaskId(undefined);
|
||||
setCreateGitBranch(true);
|
||||
// Notify user if remote tracking could not be set up
|
||||
if (result.warning) {
|
||||
toast({
|
||||
title: t('terminal:worktree.remotePushFailed'),
|
||||
description: result.warning || t('terminal:worktree.remotePushFailedDescription'),
|
||||
variant: 'destructive',
|
||||
});
|
||||
}
|
||||
} else {
|
||||
setError(result.error || t('common:errors.generic'));
|
||||
}
|
||||
|
||||
@@ -41,6 +41,8 @@
|
||||
"alreadyExists": "A worktree with this name already exists",
|
||||
"deleteTitle": "Delete Worktree?",
|
||||
"deleteDescription": "This will permanently delete the worktree and its branch. Any uncommitted changes will be lost.",
|
||||
"detached": "(detached)"
|
||||
"detached": "(detached)",
|
||||
"remotePushFailed": "Remote Tracking Not Set Up",
|
||||
"remotePushFailedDescription": "Worktree created but the branch could not be pushed to remote. You may need to run git push -u manually."
|
||||
}
|
||||
}
|
||||
|
||||
@@ -41,6 +41,8 @@
|
||||
"alreadyExists": "Un worktree avec ce nom existe deja",
|
||||
"deleteTitle": "Supprimer le Worktree?",
|
||||
"deleteDescription": "Ceci supprimera definitivement le worktree et sa branche. Les modifications non committées seront perdues.",
|
||||
"detached": "(détaché)"
|
||||
"detached": "(détaché)",
|
||||
"remotePushFailed": "Suivi distant non configure",
|
||||
"remotePushFailedDescription": "Le worktree a ete cree mais la branche n'a pas pu etre poussee vers le depot distant. Vous devrez peut-etre executer git push -u manuellement."
|
||||
}
|
||||
}
|
||||
|
||||
@@ -219,6 +219,8 @@ export interface TerminalWorktreeConfig {
|
||||
createdAt: string;
|
||||
/** Terminal ID this worktree is associated with */
|
||||
terminalId: string;
|
||||
/** Whether the branch was pushed to remote with tracking set up */
|
||||
remoteTrackingSetUp?: boolean;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -252,6 +254,8 @@ export interface TerminalWorktreeResult {
|
||||
success: boolean;
|
||||
config?: TerminalWorktreeConfig;
|
||||
error?: string;
|
||||
/** Warning when worktree was created but remote push failed */
|
||||
warning?: string;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
Reference in New Issue
Block a user