mirror of
https://github.com/paperclipai/paperclip
synced 2026-03-25 11:21:48 +00:00
Add merge-history project import option
Co-Authored-By: Paperclip <noreply@paperclip.ing>
This commit is contained in:
@@ -115,6 +115,52 @@ function makeAttachment(overrides: Record<string, unknown> = {}) {
|
||||
} as any;
|
||||
}
|
||||
|
||||
function makeProject(overrides: Record<string, unknown> = {}) {
|
||||
return {
|
||||
id: "project-1",
|
||||
companyId: "company-1",
|
||||
goalId: null,
|
||||
name: "Project",
|
||||
description: null,
|
||||
status: "in_progress",
|
||||
leadAgentId: null,
|
||||
targetDate: null,
|
||||
color: "#22c55e",
|
||||
pauseReason: null,
|
||||
pausedAt: null,
|
||||
executionWorkspacePolicy: null,
|
||||
archivedAt: null,
|
||||
createdAt: new Date("2026-03-20T00:00:00.000Z"),
|
||||
updatedAt: new Date("2026-03-20T00:00:00.000Z"),
|
||||
...overrides,
|
||||
} as any;
|
||||
}
|
||||
|
||||
function makeProjectWorkspace(overrides: Record<string, unknown> = {}) {
|
||||
return {
|
||||
id: "workspace-1",
|
||||
companyId: "company-1",
|
||||
projectId: "project-1",
|
||||
name: "Workspace",
|
||||
sourceType: "local_path",
|
||||
cwd: "/tmp/project",
|
||||
repoUrl: "https://github.com/example/project.git",
|
||||
repoRef: "main",
|
||||
defaultRef: "main",
|
||||
visibility: "default",
|
||||
setupCommand: null,
|
||||
cleanupCommand: null,
|
||||
remoteProvider: null,
|
||||
remoteWorkspaceRef: null,
|
||||
sharedWorkspaceKey: null,
|
||||
metadata: null,
|
||||
isPrimary: true,
|
||||
createdAt: new Date("2026-03-20T00:00:00.000Z"),
|
||||
updatedAt: new Date("2026-03-20T00:00:00.000Z"),
|
||||
...overrides,
|
||||
} as any;
|
||||
}
|
||||
|
||||
describe("worktree merge history planner", () => {
|
||||
it("parses default scopes", () => {
|
||||
expect(parseWorktreeMergeScopes(undefined)).toEqual(["issues", "comments"]);
|
||||
@@ -236,6 +282,60 @@ describe("worktree merge history planner", () => {
|
||||
expect(insert.adjustments).toEqual(["clear_project_workspace"]);
|
||||
});
|
||||
|
||||
it("plans selected project imports and preserves project workspace links", () => {
|
||||
const sourceProject = makeProject({
|
||||
id: "source-project-1",
|
||||
name: "Paperclip Evals",
|
||||
goalId: "goal-1",
|
||||
});
|
||||
const sourceWorkspace = makeProjectWorkspace({
|
||||
id: "source-workspace-1",
|
||||
projectId: "source-project-1",
|
||||
cwd: "/Users/dotta/paperclip-evals",
|
||||
repoUrl: "https://github.com/paperclipai/paperclip-evals.git",
|
||||
});
|
||||
|
||||
const plan = buildWorktreeMergePlan({
|
||||
companyId: "company-1",
|
||||
companyName: "Paperclip",
|
||||
issuePrefix: "PAP",
|
||||
previewIssueCounterStart: 10,
|
||||
scopes: ["issues"],
|
||||
sourceIssues: [
|
||||
makeIssue({
|
||||
id: "issue-project-import",
|
||||
identifier: "PAP-88",
|
||||
projectId: "source-project-1",
|
||||
projectWorkspaceId: "source-workspace-1",
|
||||
}),
|
||||
],
|
||||
targetIssues: [],
|
||||
sourceComments: [],
|
||||
targetComments: [],
|
||||
sourceProjects: [sourceProject],
|
||||
sourceProjectWorkspaces: [sourceWorkspace],
|
||||
targetAgents: [],
|
||||
targetProjects: [],
|
||||
targetProjectWorkspaces: [],
|
||||
targetGoals: [{ id: "goal-1" }] as any,
|
||||
importProjectIds: ["source-project-1"],
|
||||
});
|
||||
|
||||
expect(plan.counts.projectsToImport).toBe(1);
|
||||
expect(plan.projectImports[0]).toMatchObject({
|
||||
source: { id: "source-project-1", name: "Paperclip Evals" },
|
||||
targetGoalId: "goal-1",
|
||||
workspaces: [{ id: "source-workspace-1" }],
|
||||
});
|
||||
|
||||
const insert = plan.issuePlans[0] as any;
|
||||
expect(insert.targetProjectId).toBe("source-project-1");
|
||||
expect(insert.targetProjectWorkspaceId).toBe("source-workspace-1");
|
||||
expect(insert.projectResolution).toBe("imported");
|
||||
expect(insert.mappedProjectName).toBe("Paperclip Evals");
|
||||
expect(insert.adjustments).toEqual([]);
|
||||
});
|
||||
|
||||
it("imports comments onto shared or newly imported issues while skipping existing comments", () => {
|
||||
const sharedIssue = makeIssue({ id: "issue-a", identifier: "PAP-10" });
|
||||
const newIssue = makeIssue({
|
||||
|
||||
@@ -50,7 +50,7 @@ export type PlannedIssueInsert = {
|
||||
targetProjectId: string | null;
|
||||
targetProjectWorkspaceId: string | null;
|
||||
targetGoalId: string | null;
|
||||
projectResolution: "preserved" | "cleared" | "mapped";
|
||||
projectResolution: "preserved" | "cleared" | "mapped" | "imported";
|
||||
mappedProjectName: string | null;
|
||||
adjustments: ImportAdjustment[];
|
||||
};
|
||||
@@ -173,17 +173,26 @@ export type PlannedAttachmentSkip = {
|
||||
action: "skip_existing" | "skip_missing_parent";
|
||||
};
|
||||
|
||||
export type PlannedProjectImport = {
|
||||
source: ProjectRow;
|
||||
targetLeadAgentId: string | null;
|
||||
targetGoalId: string | null;
|
||||
workspaces: ProjectWorkspaceRow[];
|
||||
};
|
||||
|
||||
export type WorktreeMergePlan = {
|
||||
companyId: string;
|
||||
companyName: string;
|
||||
issuePrefix: string;
|
||||
previewIssueCounterStart: number;
|
||||
scopes: WorktreeMergeScope[];
|
||||
projectImports: PlannedProjectImport[];
|
||||
issuePlans: Array<PlannedIssueInsert | PlannedIssueSkip>;
|
||||
commentPlans: Array<PlannedCommentInsert | PlannedCommentSkip>;
|
||||
documentPlans: Array<PlannedIssueDocumentInsert | PlannedIssueDocumentMerge | PlannedIssueDocumentSkip>;
|
||||
attachmentPlans: Array<PlannedAttachmentInsert | PlannedAttachmentSkip>;
|
||||
counts: {
|
||||
projectsToImport: number;
|
||||
issuesToInsert: number;
|
||||
issuesExisting: number;
|
||||
issueDrift: number;
|
||||
@@ -338,6 +347,8 @@ export function buildWorktreeMergePlan(input: {
|
||||
targetIssues: IssueRow[];
|
||||
sourceComments: CommentRow[];
|
||||
targetComments: CommentRow[];
|
||||
sourceProjects?: ProjectRow[];
|
||||
sourceProjectWorkspaces?: ProjectWorkspaceRow[];
|
||||
sourceDocuments?: IssueDocumentRow[];
|
||||
targetDocuments?: IssueDocumentRow[];
|
||||
sourceDocumentRevisions?: DocumentRevisionRow[];
|
||||
@@ -348,6 +359,7 @@ export function buildWorktreeMergePlan(input: {
|
||||
targetProjects: ProjectRow[];
|
||||
targetProjectWorkspaces: ProjectWorkspaceRow[];
|
||||
targetGoals: GoalRow[];
|
||||
importProjectIds?: Iterable<string>;
|
||||
projectIdOverrides?: Record<string, string | null | undefined>;
|
||||
}): WorktreeMergePlan {
|
||||
const targetIssuesById = new Map(input.targetIssues.map((issue) => [issue.id, issue]));
|
||||
@@ -357,6 +369,10 @@ export function buildWorktreeMergePlan(input: {
|
||||
const targetProjectsById = new Map(input.targetProjects.map((project) => [project.id, project]));
|
||||
const targetProjectWorkspaceIds = new Set(input.targetProjectWorkspaces.map((workspace) => workspace.id));
|
||||
const targetGoalIds = new Set(input.targetGoals.map((goal) => goal.id));
|
||||
const sourceProjectsById = new Map((input.sourceProjects ?? []).map((project) => [project.id, project]));
|
||||
const sourceProjectWorkspaces = input.sourceProjectWorkspaces ?? [];
|
||||
const sourceProjectWorkspacesByProjectId = groupBy(sourceProjectWorkspaces, (workspace) => workspace.projectId);
|
||||
const importProjectIds = new Set(input.importProjectIds ?? []);
|
||||
const scopes = new Set(input.scopes);
|
||||
|
||||
const adjustmentCounts: Record<ImportAdjustment, number> = {
|
||||
@@ -371,6 +387,34 @@ export function buildWorktreeMergePlan(input: {
|
||||
clear_attachment_agent: 0,
|
||||
};
|
||||
|
||||
const projectImports: PlannedProjectImport[] = [];
|
||||
for (const projectId of importProjectIds) {
|
||||
if (targetProjectIds.has(projectId)) continue;
|
||||
const sourceProject = sourceProjectsById.get(projectId);
|
||||
if (!sourceProject) continue;
|
||||
projectImports.push({
|
||||
source: sourceProject,
|
||||
targetLeadAgentId:
|
||||
sourceProject.leadAgentId && targetAgentIds.has(sourceProject.leadAgentId)
|
||||
? sourceProject.leadAgentId
|
||||
: null,
|
||||
targetGoalId:
|
||||
sourceProject.goalId && targetGoalIds.has(sourceProject.goalId)
|
||||
? sourceProject.goalId
|
||||
: null,
|
||||
workspaces: [...(sourceProjectWorkspacesByProjectId.get(projectId) ?? [])].sort((left, right) => {
|
||||
const primaryDelta = Number(right.isPrimary) - Number(left.isPrimary);
|
||||
if (primaryDelta !== 0) return primaryDelta;
|
||||
const createdDelta = left.createdAt.getTime() - right.createdAt.getTime();
|
||||
if (createdDelta !== 0) return createdDelta;
|
||||
return left.id.localeCompare(right.id);
|
||||
}),
|
||||
});
|
||||
}
|
||||
const importedProjectWorkspaceIds = new Set(
|
||||
projectImports.flatMap((project) => project.workspaces.map((workspace) => workspace.id)),
|
||||
);
|
||||
|
||||
const issuePlans: Array<PlannedIssueInsert | PlannedIssueSkip> = [];
|
||||
let nextPreviewIssueNumber = input.previewIssueCounterStart;
|
||||
for (const issue of sortIssuesForImport(input.sourceIssues)) {
|
||||
@@ -409,6 +453,14 @@ export function buildWorktreeMergePlan(input: {
|
||||
projectResolution = "mapped";
|
||||
mappedProjectName = targetProjectsById.get(overrideProjectId)?.name ?? null;
|
||||
}
|
||||
if (!targetProjectId && issue.projectId && importProjectIds.has(issue.projectId)) {
|
||||
const sourceProject = sourceProjectsById.get(issue.projectId);
|
||||
if (sourceProject) {
|
||||
targetProjectId = sourceProject.id;
|
||||
projectResolution = "imported";
|
||||
mappedProjectName = sourceProject.name;
|
||||
}
|
||||
}
|
||||
if (issue.projectId && !targetProjectId) {
|
||||
adjustments.push("clear_project");
|
||||
incrementAdjustment(adjustmentCounts, "clear_project");
|
||||
@@ -418,7 +470,8 @@ export function buildWorktreeMergePlan(input: {
|
||||
targetProjectId
|
||||
&& targetProjectId === issue.projectId
|
||||
&& issue.projectWorkspaceId
|
||||
&& targetProjectWorkspaceIds.has(issue.projectWorkspaceId)
|
||||
&& (targetProjectWorkspaceIds.has(issue.projectWorkspaceId)
|
||||
|| importedProjectWorkspaceIds.has(issue.projectWorkspaceId))
|
||||
? issue.projectWorkspaceId
|
||||
: null;
|
||||
if (issue.projectWorkspaceId && !targetProjectWorkspaceId) {
|
||||
@@ -672,6 +725,7 @@ export function buildWorktreeMergePlan(input: {
|
||||
}
|
||||
|
||||
const counts = {
|
||||
projectsToImport: projectImports.length,
|
||||
issuesToInsert: issuePlans.filter((plan) => plan.action === "insert").length,
|
||||
issuesExisting: issuePlans.filter((plan) => plan.action === "skip_existing").length,
|
||||
issueDrift: issuePlans.filter((plan) => plan.action === "skip_existing" && plan.driftKeys.length > 0).length,
|
||||
@@ -699,6 +753,7 @@ export function buildWorktreeMergePlan(input: {
|
||||
issuePrefix: input.issuePrefix,
|
||||
previewIssueCounterStart: input.previewIssueCounterStart,
|
||||
scopes: input.scopes,
|
||||
projectImports,
|
||||
issuePlans,
|
||||
commentPlans,
|
||||
documentPlans,
|
||||
|
||||
@@ -1488,20 +1488,34 @@ function renderMergePlan(plan: Awaited<ReturnType<typeof collectMergePlan>>["pla
|
||||
`Target: ${extras.targetPath}`,
|
||||
`Company: ${plan.companyName} (${plan.issuePrefix})`,
|
||||
"",
|
||||
"Projects",
|
||||
`- import: ${plan.counts.projectsToImport}`,
|
||||
"",
|
||||
"Issues",
|
||||
`- insert: ${plan.counts.issuesToInsert}`,
|
||||
`- already present: ${plan.counts.issuesExisting}`,
|
||||
`- shared/imported issues with drift: ${plan.counts.issueDrift}`,
|
||||
];
|
||||
|
||||
if (plan.projectImports.length > 0) {
|
||||
lines.push("");
|
||||
lines.push("Planned project imports");
|
||||
for (const project of plan.projectImports) {
|
||||
lines.push(
|
||||
`- ${project.source.name} (${project.workspaces.length} workspace${project.workspaces.length === 1 ? "" : "s"})`,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
const issueInserts = plan.issuePlans.filter((item): item is PlannedIssueInsert => item.action === "insert");
|
||||
if (issueInserts.length > 0) {
|
||||
lines.push("");
|
||||
lines.push("Planned issue imports");
|
||||
for (const issue of issueInserts) {
|
||||
const projectNote =
|
||||
issue.projectResolution === "mapped" && issue.mappedProjectName
|
||||
? ` project->${issue.mappedProjectName}`
|
||||
(issue.projectResolution === "mapped" || issue.projectResolution === "imported")
|
||||
&& issue.mappedProjectName
|
||||
? ` project->${issue.projectResolution === "imported" ? "import:" : ""}${issue.mappedProjectName}`
|
||||
: "";
|
||||
const adjustments = issue.adjustments.length > 0 ? ` [${issue.adjustments.join(", ")}]` : "";
|
||||
const prefix = `- ${issue.source.identifier ?? issue.source.id} -> ${issue.previewIdentifier} (${issue.targetStatus}${projectNote})`;
|
||||
@@ -1562,6 +1576,7 @@ async function collectMergePlan(input: {
|
||||
targetDb: ClosableDb;
|
||||
company: ResolvedMergeCompany;
|
||||
scopes: ReturnType<typeof parseWorktreeMergeScopes>;
|
||||
importProjectIds?: Iterable<string>;
|
||||
projectIdOverrides?: Record<string, string | null | undefined>;
|
||||
}) {
|
||||
const companyId = input.company.id;
|
||||
@@ -1578,6 +1593,7 @@ async function collectMergePlan(input: {
|
||||
sourceAttachmentRows,
|
||||
targetAttachmentRows,
|
||||
sourceProjectsRows,
|
||||
sourceProjectWorkspaceRows,
|
||||
targetProjectsRows,
|
||||
targetAgentsRows,
|
||||
targetProjectWorkspaceRows,
|
||||
@@ -1743,6 +1759,10 @@ async function collectMergePlan(input: {
|
||||
.select()
|
||||
.from(projects)
|
||||
.where(eq(projects.companyId, companyId)),
|
||||
input.sourceDb
|
||||
.select()
|
||||
.from(projectWorkspaces)
|
||||
.where(eq(projectWorkspaces.companyId, companyId)),
|
||||
input.targetDb
|
||||
.select()
|
||||
.from(projects)
|
||||
@@ -1779,6 +1799,8 @@ async function collectMergePlan(input: {
|
||||
targetIssues: targetIssuesRows,
|
||||
sourceComments: sourceCommentsRows,
|
||||
targetComments: targetCommentsRows,
|
||||
sourceProjects: sourceProjectsRows,
|
||||
sourceProjectWorkspaces: sourceProjectWorkspaceRows,
|
||||
sourceDocuments: sourceIssueDocumentsRows as IssueDocumentRow[],
|
||||
targetDocuments: targetIssueDocumentsRows as IssueDocumentRow[],
|
||||
sourceDocumentRevisions: sourceDocumentRevisionRows as DocumentRevisionRow[],
|
||||
@@ -1789,6 +1811,7 @@ async function collectMergePlan(input: {
|
||||
targetProjects: targetProjectsRows,
|
||||
targetProjectWorkspaces: targetProjectWorkspaceRows,
|
||||
targetGoals: targetGoalsRows,
|
||||
importProjectIds: input.importProjectIds,
|
||||
projectIdOverrides: input.projectIdOverrides,
|
||||
});
|
||||
|
||||
@@ -1800,11 +1823,16 @@ async function collectMergePlan(input: {
|
||||
};
|
||||
}
|
||||
|
||||
type ProjectMappingSelections = {
|
||||
importProjectIds: string[];
|
||||
projectIdOverrides: Record<string, string | null>;
|
||||
};
|
||||
|
||||
async function promptForProjectMappings(input: {
|
||||
plan: Awaited<ReturnType<typeof collectMergePlan>>["plan"];
|
||||
sourceProjects: Awaited<ReturnType<typeof collectMergePlan>>["sourceProjects"];
|
||||
targetProjects: Awaited<ReturnType<typeof collectMergePlan>>["targetProjects"];
|
||||
}): Promise<Record<string, string | null>> {
|
||||
}): Promise<ProjectMappingSelections> {
|
||||
const missingProjectIds = [
|
||||
...new Set(
|
||||
input.plan.issuePlans
|
||||
@@ -1813,8 +1841,11 @@ async function promptForProjectMappings(input: {
|
||||
.map((plan) => plan.source.projectId as string),
|
||||
),
|
||||
];
|
||||
if (missingProjectIds.length === 0 || input.targetProjects.length === 0) {
|
||||
return {};
|
||||
if (missingProjectIds.length === 0) {
|
||||
return {
|
||||
importProjectIds: [],
|
||||
projectIdOverrides: {},
|
||||
};
|
||||
}
|
||||
|
||||
const sourceProjectsById = new Map(input.sourceProjects.map((project) => [project.id, project]));
|
||||
@@ -1827,15 +1858,22 @@ async function promptForProjectMappings(input: {
|
||||
}));
|
||||
|
||||
const mappings: Record<string, string | null> = {};
|
||||
const importProjectIds = new Set<string>();
|
||||
for (const sourceProjectId of missingProjectIds) {
|
||||
const sourceProject = sourceProjectsById.get(sourceProjectId);
|
||||
if (!sourceProject) continue;
|
||||
const nameMatch = input.targetProjects.find(
|
||||
(project) => project.name.trim().toLowerCase() === sourceProject.name.trim().toLowerCase(),
|
||||
);
|
||||
const importSelectionValue = `__import__:${sourceProjectId}`;
|
||||
const selection = await p.select<string | null>({
|
||||
message: `Project "${sourceProject.name}" is missing in target. How should ${input.plan.issuePrefix} imports handle it?`,
|
||||
options: [
|
||||
{
|
||||
value: importSelectionValue,
|
||||
label: `Import ${sourceProject.name}`,
|
||||
hint: "Create the project and copy its workspace settings",
|
||||
},
|
||||
...(nameMatch
|
||||
? [{
|
||||
value: nameMatch.id,
|
||||
@@ -1855,10 +1893,17 @@ async function promptForProjectMappings(input: {
|
||||
if (p.isCancel(selection)) {
|
||||
throw new Error("Project mapping cancelled.");
|
||||
}
|
||||
if (selection === importSelectionValue) {
|
||||
importProjectIds.add(sourceProjectId);
|
||||
continue;
|
||||
}
|
||||
mappings[sourceProjectId] = selection;
|
||||
}
|
||||
|
||||
return mappings;
|
||||
return {
|
||||
importProjectIds: [...importProjectIds],
|
||||
projectIdOverrides: mappings,
|
||||
};
|
||||
}
|
||||
|
||||
export async function worktreeListCommand(opts: WorktreeListOptions): Promise<void> {
|
||||
@@ -1976,6 +2021,77 @@ async function applyMergePlan(input: {
|
||||
const companyId = input.company.id;
|
||||
|
||||
return await input.targetDb.transaction(async (tx) => {
|
||||
const importedProjectIds = input.plan.projectImports.map((project) => project.source.id);
|
||||
const existingImportedProjectIds = importedProjectIds.length > 0
|
||||
? new Set(
|
||||
(await tx
|
||||
.select({ id: projects.id })
|
||||
.from(projects)
|
||||
.where(inArray(projects.id, importedProjectIds)))
|
||||
.map((row) => row.id),
|
||||
)
|
||||
: new Set<string>();
|
||||
const projectImports = input.plan.projectImports.filter((project) => !existingImportedProjectIds.has(project.source.id));
|
||||
const importedWorkspaceIds = projectImports.flatMap((project) => project.workspaces.map((workspace) => workspace.id));
|
||||
const existingImportedWorkspaceIds = importedWorkspaceIds.length > 0
|
||||
? new Set(
|
||||
(await tx
|
||||
.select({ id: projectWorkspaces.id })
|
||||
.from(projectWorkspaces)
|
||||
.where(inArray(projectWorkspaces.id, importedWorkspaceIds)))
|
||||
.map((row) => row.id),
|
||||
)
|
||||
: new Set<string>();
|
||||
|
||||
let insertedProjects = 0;
|
||||
let insertedProjectWorkspaces = 0;
|
||||
for (const project of projectImports) {
|
||||
await tx.insert(projects).values({
|
||||
id: project.source.id,
|
||||
companyId,
|
||||
goalId: project.targetGoalId,
|
||||
name: project.source.name,
|
||||
description: project.source.description,
|
||||
status: project.source.status,
|
||||
leadAgentId: project.targetLeadAgentId,
|
||||
targetDate: project.source.targetDate,
|
||||
color: project.source.color,
|
||||
pauseReason: project.source.pauseReason,
|
||||
pausedAt: project.source.pausedAt,
|
||||
executionWorkspacePolicy: project.source.executionWorkspacePolicy,
|
||||
archivedAt: project.source.archivedAt,
|
||||
createdAt: project.source.createdAt,
|
||||
updatedAt: project.source.updatedAt,
|
||||
});
|
||||
insertedProjects += 1;
|
||||
|
||||
for (const workspace of project.workspaces) {
|
||||
if (existingImportedWorkspaceIds.has(workspace.id)) continue;
|
||||
await tx.insert(projectWorkspaces).values({
|
||||
id: workspace.id,
|
||||
companyId,
|
||||
projectId: project.source.id,
|
||||
name: workspace.name,
|
||||
sourceType: workspace.sourceType,
|
||||
cwd: workspace.cwd,
|
||||
repoUrl: workspace.repoUrl,
|
||||
repoRef: workspace.repoRef,
|
||||
defaultRef: workspace.defaultRef,
|
||||
visibility: workspace.visibility,
|
||||
setupCommand: workspace.setupCommand,
|
||||
cleanupCommand: workspace.cleanupCommand,
|
||||
remoteProvider: workspace.remoteProvider,
|
||||
remoteWorkspaceRef: workspace.remoteWorkspaceRef,
|
||||
sharedWorkspaceKey: workspace.sharedWorkspaceKey,
|
||||
metadata: workspace.metadata,
|
||||
isPrimary: workspace.isPrimary,
|
||||
createdAt: workspace.createdAt,
|
||||
updatedAt: workspace.updatedAt,
|
||||
});
|
||||
insertedProjectWorkspaces += 1;
|
||||
}
|
||||
}
|
||||
|
||||
const issueCandidates = input.plan.issuePlans.filter(
|
||||
(plan): plan is PlannedIssueInsert => plan.action === "insert",
|
||||
);
|
||||
@@ -2274,6 +2390,8 @@ async function applyMergePlan(input: {
|
||||
}
|
||||
|
||||
return {
|
||||
insertedProjects,
|
||||
insertedProjectWorkspaces,
|
||||
insertedIssues,
|
||||
insertedComments,
|
||||
insertedDocuments,
|
||||
@@ -2330,18 +2448,22 @@ export async function worktreeMergeHistoryCommand(sourceArg: string | undefined,
|
||||
scopes,
|
||||
});
|
||||
if (!opts.yes) {
|
||||
const projectIdOverrides = await promptForProjectMappings({
|
||||
const projectSelections = await promptForProjectMappings({
|
||||
plan: collected.plan,
|
||||
sourceProjects: collected.sourceProjects,
|
||||
targetProjects: collected.targetProjects,
|
||||
});
|
||||
if (Object.keys(projectIdOverrides).length > 0) {
|
||||
if (
|
||||
projectSelections.importProjectIds.length > 0
|
||||
|| Object.keys(projectSelections.projectIdOverrides).length > 0
|
||||
) {
|
||||
collected = await collectMergePlan({
|
||||
sourceDb: sourceHandle.db,
|
||||
targetDb: targetHandle.db,
|
||||
company,
|
||||
scopes,
|
||||
projectIdOverrides,
|
||||
importProjectIds: projectSelections.importProjectIds,
|
||||
projectIdOverrides: projectSelections.projectIdOverrides,
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -2381,7 +2503,7 @@ export async function worktreeMergeHistoryCommand(sourceArg: string | undefined,
|
||||
}
|
||||
p.outro(
|
||||
pc.green(
|
||||
`Imported ${applied.insertedIssues} issues, ${applied.insertedComments} comments, ${applied.insertedDocuments} documents (${applied.insertedDocumentRevisions} revisions, ${applied.mergedDocuments} merged), and ${applied.insertedAttachments} attachments into ${company.issuePrefix}.`,
|
||||
`Imported ${applied.insertedProjects} projects (${applied.insertedProjectWorkspaces} workspaces), ${applied.insertedIssues} issues, ${applied.insertedComments} comments, ${applied.insertedDocuments} documents (${applied.insertedDocumentRevisions} revisions, ${applied.mergedDocuments} merged), and ${applied.insertedAttachments} attachments into ${company.issuePrefix}.`,
|
||||
),
|
||||
);
|
||||
} finally {
|
||||
|
||||
Reference in New Issue
Block a user