Merge remote-tracking branch 'origin/main'
This commit is contained in:
@@ -98,20 +98,6 @@ const normalizeBranchName = (value: string): string => {
|
||||
.replace(/^\/+|\/+$/g, '');
|
||||
};
|
||||
|
||||
const slugifyWorktreeName = (value: string): string => {
|
||||
return value
|
||||
.trim()
|
||||
.replace(/^refs\/heads\//, '')
|
||||
.replace(/^heads\//, '')
|
||||
.replace(/\s+/g, '-')
|
||||
.replace(/^\/+|\/+$/g, '')
|
||||
.split('/').join('-')
|
||||
.replace(/[^A-Za-z0-9._-]+/g, '-')
|
||||
.replace(/-+/g, '-')
|
||||
.replace(/^-+|-+$/g, '')
|
||||
.slice(0, 80);
|
||||
};
|
||||
|
||||
const sanitizeRemoteName = (value: string): string => {
|
||||
const normalized = String(value || '')
|
||||
.trim()
|
||||
@@ -165,10 +151,14 @@ const resolvePrWorktreeConfig = (pr: GitHubPullRequestSummary, localBranches: st
|
||||
const ownerFromLabel = String(pr.headLabel || '').split(':')[0]?.trim();
|
||||
const remoteSeed = pr.headRepo?.owner || ownerFromLabel || 'pr-head';
|
||||
const remoteName = `pr-${sanitizeRemoteName(remoteSeed)}`;
|
||||
const remoteUrl = pr.headRepo?.sshUrl || pr.headRepo?.cloneUrl || '';
|
||||
// Prefer HTTPS so anonymous public fetches do not require SSH agent setup.
|
||||
const remoteUrl = pr.headRepo?.cloneUrl || pr.headRepo?.sshUrl || '';
|
||||
|
||||
if (!remoteUrl) {
|
||||
throw new Error('PR head repository URL is unavailable');
|
||||
throw new Error(
|
||||
'PR head repository URL is unavailable. The fork may have been deleted; '
|
||||
+ 'push the branch to a reachable repository and try again.'
|
||||
);
|
||||
}
|
||||
|
||||
return {
|
||||
@@ -182,6 +172,20 @@ const resolvePrWorktreeConfig = (pr: GitHubPullRequestSummary, localBranches: st
|
||||
};
|
||||
};
|
||||
|
||||
const slugifyWorktreeName = (value: string): string => {
|
||||
return value
|
||||
.trim()
|
||||
.replace(/^refs\/heads\//, '')
|
||||
.replace(/^heads\//, '')
|
||||
.replace(/\s+/g, '-')
|
||||
.replace(/^\/+|\/+$/g, '')
|
||||
.split('/').join('-')
|
||||
.replace(/[^A-Za-z0-9._-]+/g, '-')
|
||||
.replace(/-+/g, '-')
|
||||
.replace(/^-+|-+$/g, '')
|
||||
.slice(0, 80);
|
||||
};
|
||||
|
||||
interface NewWorktreeDialogProps {
|
||||
open: boolean;
|
||||
onOpenChange: (open: boolean) => void;
|
||||
@@ -898,7 +902,7 @@ export function NewWorktreeDialog({
|
||||
...(sourceBranch && mode === 'new-branch' ? { startRef: sourceBranch } : {}),
|
||||
};
|
||||
})();
|
||||
|
||||
|
||||
const resolvedArgs = await withWorktreeUpstreamDefaults(projectDirectory, args);
|
||||
|
||||
const metadata = await createWorktree(projectRef, resolvedArgs);
|
||||
|
||||
@@ -11,6 +11,8 @@ type WorktreeListEntry = {
|
||||
|
||||
const listCalls: string[] = [];
|
||||
const listResolvers: Array<(value: WorktreeListEntry[]) => void> = [];
|
||||
const createPayloads: unknown[] = [];
|
||||
const validatePayloads: unknown[] = [];
|
||||
const createdWorktree = {
|
||||
head: 'abc123',
|
||||
name: 'feature',
|
||||
@@ -80,7 +82,14 @@ mock.module('@/lib/gitApi', () => ({
|
||||
listResolvers.push(resolve);
|
||||
});
|
||||
},
|
||||
create: mock(() => Promise.resolve(createdWorktreeResult)),
|
||||
create: mock((_directory: string, payload: unknown) => {
|
||||
createPayloads.push(payload);
|
||||
return Promise.resolve(createdWorktreeResult);
|
||||
}),
|
||||
validate: mock((_directory: string, payload: unknown) => {
|
||||
validatePayloads.push(payload);
|
||||
return Promise.resolve({ ok: true, errors: [] });
|
||||
}),
|
||||
remove: mock(() => Promise.resolve({ success: true })),
|
||||
},
|
||||
},
|
||||
@@ -91,6 +100,7 @@ const {
|
||||
getLatestWorktreeMetadata,
|
||||
listProjectWorktrees,
|
||||
partitionWorktreesByRegisteredProject,
|
||||
validateWorktreeCreate,
|
||||
worktreeMapsEqual,
|
||||
} = await import('./worktreeManager');
|
||||
|
||||
@@ -108,6 +118,8 @@ describe('worktreeManager list invalidation', () => {
|
||||
beforeEach(() => {
|
||||
listCalls.length = 0;
|
||||
listResolvers.length = 0;
|
||||
createPayloads.length = 0;
|
||||
validatePayloads.length = 0;
|
||||
bootstrapWatcherCalls.length = 0;
|
||||
bootstrapWatcherOptions.length = 0;
|
||||
createdWorktreeResult = createdWorktree;
|
||||
@@ -372,3 +384,56 @@ describe('partitionWorktreesByRegisteredProject', () => {
|
||||
expect(result.get('/repo')?.map((entry) => entry.path)).toEqual(['/worktrees/loose']);
|
||||
});
|
||||
});
|
||||
|
||||
describe('worktreeManager fork remote payload wiring', () => {
|
||||
beforeEach(() => {
|
||||
listCalls.length = 0;
|
||||
listResolvers.length = 0;
|
||||
createPayloads.length = 0;
|
||||
validatePayloads.length = 0;
|
||||
bootstrapWatcherCalls.length = 0;
|
||||
bootstrapWatcherOptions.length = 0;
|
||||
createdWorktreeResult = createdWorktree;
|
||||
sessionState.availableWorktreesByProject = new Map();
|
||||
sessionState.availableWorktrees = [];
|
||||
sessionState.worktreeMetadata = new Map();
|
||||
attachmentState.attachments = new Map();
|
||||
});
|
||||
|
||||
test('validate and create forward ensureRemoteName/Url for a fork head', async () => {
|
||||
const project = { id: 'project-1', path: '/repo' };
|
||||
const args = {
|
||||
mode: 'existing' as const,
|
||||
branchName: 'feature/login',
|
||||
worktreeName: 'pr-42',
|
||||
existingBranch: 'remotes/pr-alice/feature/login',
|
||||
setUpstream: true as const,
|
||||
upstreamRemote: 'pr-alice',
|
||||
upstreamBranch: 'feature/login',
|
||||
ensureRemoteName: 'pr-alice',
|
||||
ensureRemoteUrl: 'https://github.com/alice/openchamber.git',
|
||||
};
|
||||
|
||||
const validation = await validateWorktreeCreate(project, args);
|
||||
expect(validation.ok).toBe(true);
|
||||
expect(validatePayloads).toHaveLength(1);
|
||||
const validated = validatePayloads[0] as Record<string, unknown>;
|
||||
expect(validated.mode).toBe('existing');
|
||||
expect(validated.existingBranch).toBe('remotes/pr-alice/feature/login');
|
||||
expect(validated.ensureRemoteName).toBe('pr-alice');
|
||||
expect(validated.ensureRemoteUrl).toBe('https://github.com/alice/openchamber.git');
|
||||
expect('pullRequest' in validated).toBe(false);
|
||||
|
||||
await createWorktree(project, {
|
||||
...args,
|
||||
returnAfterDirectoryCreated: true,
|
||||
});
|
||||
expect(createPayloads).toHaveLength(1);
|
||||
const created = createPayloads[0] as Record<string, unknown>;
|
||||
expect(created.existingBranch).toBe('remotes/pr-alice/feature/login');
|
||||
expect(created.ensureRemoteName).toBe('pr-alice');
|
||||
expect(created.ensureRemoteUrl).toBe('https://github.com/alice/openchamber.git');
|
||||
expect(created.setUpstream).toBe(true);
|
||||
expect('pullRequest' in created).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user