mirror of
https://github.com/AutoMaker-Org/automaker.git
synced 2026-01-30 06:12:03 +00:00
Merge pull request #477 from AutoMaker-Org/fix/dynamic-branch-references
fix: use dynamic branch references instead of hardcoded origin/main
This commit is contained in:
@@ -13,7 +13,7 @@ import { promisify } from 'util';
|
||||
import path from 'path';
|
||||
import * as secureFs from '../../../lib/secure-fs.js';
|
||||
import { isGitRepo } from '@automaker/git-utils';
|
||||
import { getErrorMessage, logError, normalizePath } from '../common.js';
|
||||
import { getErrorMessage, logError, normalizePath, execEnv, isGhCliAvailable } from '../common.js';
|
||||
import { readAllWorktreeMetadata, type WorktreePRInfo } from '../../../lib/worktree-metadata.js';
|
||||
import { createLogger } from '@automaker/utils';
|
||||
|
||||
@@ -121,6 +121,52 @@ async function scanWorktreesDirectory(
|
||||
return discovered;
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetch open PRs from GitHub and create a map of branch name to PR info.
|
||||
* This allows detecting PRs that were created outside the app.
|
||||
*/
|
||||
async function fetchGitHubPRs(projectPath: string): Promise<Map<string, WorktreePRInfo>> {
|
||||
const prMap = new Map<string, WorktreePRInfo>();
|
||||
|
||||
try {
|
||||
// Check if gh CLI is available
|
||||
const ghAvailable = await isGhCliAvailable();
|
||||
if (!ghAvailable) {
|
||||
return prMap;
|
||||
}
|
||||
|
||||
// Fetch open PRs from GitHub
|
||||
const { stdout } = await execAsync(
|
||||
'gh pr list --state open --json number,title,url,state,headRefName,createdAt --limit 1000',
|
||||
{ cwd: projectPath, env: execEnv, timeout: 15000 }
|
||||
);
|
||||
|
||||
const prs = JSON.parse(stdout || '[]') as Array<{
|
||||
number: number;
|
||||
title: string;
|
||||
url: string;
|
||||
state: string;
|
||||
headRefName: string;
|
||||
createdAt: string;
|
||||
}>;
|
||||
|
||||
for (const pr of prs) {
|
||||
prMap.set(pr.headRefName, {
|
||||
number: pr.number,
|
||||
url: pr.url,
|
||||
title: pr.title,
|
||||
state: pr.state,
|
||||
createdAt: pr.createdAt,
|
||||
});
|
||||
}
|
||||
} catch (error) {
|
||||
// Silently fail - PR detection is optional
|
||||
logger.warn(`Failed to fetch GitHub PRs: ${getErrorMessage(error)}`);
|
||||
}
|
||||
|
||||
return prMap;
|
||||
}
|
||||
|
||||
export function createListHandler() {
|
||||
return async (req: Request, res: Response): Promise<void> => {
|
||||
try {
|
||||
@@ -241,11 +287,23 @@ export function createListHandler() {
|
||||
}
|
||||
}
|
||||
|
||||
// Add PR info from metadata for each worktree
|
||||
// Add PR info from metadata or GitHub for each worktree
|
||||
// Only fetch GitHub PRs if includeDetails is requested (performance optimization)
|
||||
const githubPRs = includeDetails
|
||||
? await fetchGitHubPRs(projectPath)
|
||||
: new Map<string, WorktreePRInfo>();
|
||||
|
||||
for (const worktree of worktrees) {
|
||||
const metadata = allMetadata.get(worktree.branch);
|
||||
if (metadata?.pr) {
|
||||
// Use stored metadata (more complete info)
|
||||
worktree.pr = metadata.pr;
|
||||
} else if (includeDetails) {
|
||||
// Fall back to GitHub PR detection only when includeDetails is requested
|
||||
const githubPR = githubPRs.get(worktree.branch);
|
||||
if (githubPR) {
|
||||
worktree.pr = githubPR;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -328,20 +328,6 @@ export function BoardView() {
|
||||
fetchBranches();
|
||||
}, [currentProject, worktreeRefreshKey]);
|
||||
|
||||
// Calculate unarchived card counts per branch
|
||||
const branchCardCounts = useMemo(() => {
|
||||
return hookFeatures.reduce(
|
||||
(counts, feature) => {
|
||||
if (feature.status !== 'completed') {
|
||||
const branch = feature.branchName ?? 'main';
|
||||
counts[branch] = (counts[branch] || 0) + 1;
|
||||
}
|
||||
return counts;
|
||||
},
|
||||
{} as Record<string, number>
|
||||
);
|
||||
}, [hookFeatures]);
|
||||
|
||||
// Custom collision detection that prioritizes columns over cards
|
||||
const collisionDetectionStrategy = useCallback((args: any) => {
|
||||
// First, check if pointer is within a column
|
||||
@@ -426,6 +412,22 @@ export function BoardView() {
|
||||
const selectedWorktreeBranch =
|
||||
currentWorktreeBranch || worktrees.find((w) => w.isMain)?.branch || 'main';
|
||||
|
||||
// Calculate unarchived card counts per branch
|
||||
const branchCardCounts = useMemo(() => {
|
||||
// Use primary worktree branch as default for features without branchName
|
||||
const primaryBranch = worktrees.find((w) => w.isMain)?.branch || 'main';
|
||||
return hookFeatures.reduce(
|
||||
(counts, feature) => {
|
||||
if (feature.status !== 'completed') {
|
||||
const branch = feature.branchName ?? primaryBranch;
|
||||
counts[branch] = (counts[branch] || 0) + 1;
|
||||
}
|
||||
return counts;
|
||||
},
|
||||
{} as Record<string, number>
|
||||
);
|
||||
}, [hookFeatures, worktrees]);
|
||||
|
||||
// Helper function to add and select a worktree
|
||||
const addAndSelectWorktree = useCallback(
|
||||
(worktreeResult: { path: string; branch: string }) => {
|
||||
@@ -724,10 +726,11 @@ export function BoardView() {
|
||||
[handleAddFeature, handleStartImplementation, defaultSkipTests]
|
||||
);
|
||||
|
||||
// Handler for resolving conflicts - creates a feature to pull from origin/main and resolve conflicts
|
||||
// Handler for resolving conflicts - creates a feature to pull from the remote branch and resolve conflicts
|
||||
const handleResolveConflicts = useCallback(
|
||||
async (worktree: WorktreeInfo) => {
|
||||
const description = `Pull latest from origin/main and resolve conflicts. Merge origin/main into the current branch (${worktree.branch}), resolving any merge conflicts that arise. After resolving conflicts, ensure the code compiles and tests pass.`;
|
||||
const remoteBranch = `origin/${worktree.branch}`;
|
||||
const description = `Pull latest from ${remoteBranch} and resolve conflicts. Merge ${remoteBranch} into the current branch (${worktree.branch}), resolving any merge conflicts that arise. After resolving conflicts, ensure the code compiles and tests pass.`;
|
||||
|
||||
// Create the feature
|
||||
const featureData = {
|
||||
@@ -1710,6 +1713,7 @@ export function BoardView() {
|
||||
onOpenChange={setShowCreatePRDialog}
|
||||
worktree={selectedWorktreeForAction}
|
||||
projectPath={currentProject?.path || null}
|
||||
defaultBaseBranch={selectedWorktreeBranch}
|
||||
onCreated={(prUrl) => {
|
||||
// If a PR was created and we have the worktree branch, update all features on that branch with the PR URL
|
||||
if (prUrl && selectedWorktreeForAction?.branch) {
|
||||
|
||||
@@ -30,6 +30,8 @@ interface CreatePRDialogProps {
|
||||
worktree: WorktreeInfo | null;
|
||||
projectPath: string | null;
|
||||
onCreated: (prUrl?: string) => void;
|
||||
/** Default base branch for the PR (defaults to 'main' if not provided) */
|
||||
defaultBaseBranch?: string;
|
||||
}
|
||||
|
||||
export function CreatePRDialog({
|
||||
@@ -38,10 +40,11 @@ export function CreatePRDialog({
|
||||
worktree,
|
||||
projectPath,
|
||||
onCreated,
|
||||
defaultBaseBranch = 'main',
|
||||
}: CreatePRDialogProps) {
|
||||
const [title, setTitle] = useState('');
|
||||
const [body, setBody] = useState('');
|
||||
const [baseBranch, setBaseBranch] = useState('main');
|
||||
const [baseBranch, setBaseBranch] = useState(defaultBaseBranch);
|
||||
const [commitMessage, setCommitMessage] = useState('');
|
||||
const [isDraft, setIsDraft] = useState(false);
|
||||
const [isLoading, setIsLoading] = useState(false);
|
||||
@@ -59,7 +62,7 @@ export function CreatePRDialog({
|
||||
setTitle('');
|
||||
setBody('');
|
||||
setCommitMessage('');
|
||||
setBaseBranch('main');
|
||||
setBaseBranch(defaultBaseBranch);
|
||||
setIsDraft(false);
|
||||
setError(null);
|
||||
// Also reset result states when opening for a new worktree
|
||||
@@ -74,7 +77,7 @@ export function CreatePRDialog({
|
||||
setTitle('');
|
||||
setBody('');
|
||||
setCommitMessage('');
|
||||
setBaseBranch('main');
|
||||
setBaseBranch(defaultBaseBranch);
|
||||
setIsDraft(false);
|
||||
setError(null);
|
||||
setPrUrl(null);
|
||||
@@ -82,7 +85,7 @@ export function CreatePRDialog({
|
||||
setShowBrowserFallback(false);
|
||||
operationCompletedRef.current = false;
|
||||
}
|
||||
}, [open, worktree?.path]);
|
||||
}, [open, worktree?.path, defaultBaseBranch]);
|
||||
|
||||
const handleCreate = async () => {
|
||||
if (!worktree) return;
|
||||
|
||||
@@ -217,27 +217,20 @@ export function WorktreeActionsDropdown({
|
||||
)}
|
||||
</DropdownMenuItem>
|
||||
</TooltipWrapper>
|
||||
{!worktree.isMain && (
|
||||
<TooltipWrapper
|
||||
showTooltip={!!gitOpsDisabledReason}
|
||||
tooltipContent={gitOpsDisabledReason}
|
||||
<TooltipWrapper showTooltip={!!gitOpsDisabledReason} tooltipContent={gitOpsDisabledReason}>
|
||||
<DropdownMenuItem
|
||||
onClick={() => canPerformGitOps && onResolveConflicts(worktree)}
|
||||
disabled={!canPerformGitOps}
|
||||
className={cn(
|
||||
'text-xs text-purple-500 focus:text-purple-600',
|
||||
!canPerformGitOps && 'opacity-50 cursor-not-allowed'
|
||||
)}
|
||||
>
|
||||
<DropdownMenuItem
|
||||
onClick={() => canPerformGitOps && onResolveConflicts(worktree)}
|
||||
disabled={!canPerformGitOps}
|
||||
className={cn(
|
||||
'text-xs text-purple-500 focus:text-purple-600',
|
||||
!canPerformGitOps && 'opacity-50 cursor-not-allowed'
|
||||
)}
|
||||
>
|
||||
<GitMerge className="w-3.5 h-3.5 mr-2" />
|
||||
Pull & Resolve Conflicts
|
||||
{!canPerformGitOps && (
|
||||
<AlertCircle className="w-3 h-3 ml-auto text-muted-foreground" />
|
||||
)}
|
||||
</DropdownMenuItem>
|
||||
</TooltipWrapper>
|
||||
)}
|
||||
<GitMerge className="w-3.5 h-3.5 mr-2" />
|
||||
Pull & Resolve Conflicts
|
||||
{!canPerformGitOps && <AlertCircle className="w-3 h-3 ml-auto text-muted-foreground" />}
|
||||
</DropdownMenuItem>
|
||||
</TooltipWrapper>
|
||||
<DropdownMenuSeparator />
|
||||
{/* Open in editor - split button: click main area for default, chevron for other options */}
|
||||
{effectiveDefaultEditor && (
|
||||
@@ -332,7 +325,7 @@ export function WorktreeActionsDropdown({
|
||||
</TooltipWrapper>
|
||||
)}
|
||||
{/* Show PR info and Address Comments button if PR exists */}
|
||||
{!worktree.isMain && hasPR && worktree.pr && (
|
||||
{hasPR && worktree.pr && (
|
||||
<>
|
||||
<DropdownMenuItem
|
||||
onClick={() => {
|
||||
|
||||
Reference in New Issue
Block a user