diff --git a/packages/amazonq/.changes/next-release/Bug Fix-037e19bd-95f5-4cdf-8408-6eb273ff7ebd.json b/packages/amazonq/.changes/next-release/Bug Fix-037e19bd-95f5-4cdf-8408-6eb273ff7ebd.json new file mode 100644 index 00000000000..72ae0a18bde --- /dev/null +++ b/packages/amazonq/.changes/next-release/Bug Fix-037e19bd-95f5-4cdf-8408-6eb273ff7ebd.json @@ -0,0 +1,4 @@ +{ + "type": "Bug Fix", + "description": "/dev and /doc: Multi-root workspace with duplicate files causes infinite 'Uploading code...' loop" +} diff --git a/packages/amazonq/test/unit/amazonqFeatureDev/util/files.test.ts b/packages/amazonq/test/unit/amazonqFeatureDev/util/files.test.ts index 754558fd9cf..4351e7832e6 100644 --- a/packages/amazonq/test/unit/amazonqFeatureDev/util/files.test.ts +++ b/packages/amazonq/test/unit/amazonqFeatureDev/util/files.test.ts @@ -15,6 +15,8 @@ import { fs, AmazonqCreateUpload, ZipStream } from 'aws-core-vscode/shared' import { MetricName, Span } from 'aws-core-vscode/telemetry' import sinon from 'sinon' import { CodeWhispererSettings } from 'aws-core-vscode/codewhisperer' +import { CurrentWsFolders } from 'aws-core-vscode/amazonq' +import path from 'path' const testDevfilePrepareRepo = async (devfileEnabled: boolean) => { const files: Record = { @@ -44,19 +46,24 @@ const testDevfilePrepareRepo = async (devfileEnabled: boolean) => { .stub(CodeWhispererSettings.instance, 'getAutoBuildSetting') .returns(devfileEnabled ? { [workspace.uri.fsPath]: true } : {}) - await testPrepareRepoData(workspace, expectedFiles) + await testPrepareRepoData([workspace], expectedFiles) } const testPrepareRepoData = async ( - workspace: vscode.WorkspaceFolder, + workspaces: vscode.WorkspaceFolder[], expectedFiles: string[], expectedTelemetryMetrics?: Array<{ metricName: MetricName; value: any }> ) => { expectedFiles.sort((a, b) => a.localeCompare(b)) const telemetry = new TelemetryHelper() - const result = await prepareRepoData([workspace.uri.fsPath], [workspace], telemetry, { - record: () => {}, - } as unknown as Span) + const result = await prepareRepoData( + workspaces.map((ws) => ws.uri.fsPath), + workspaces as CurrentWsFolders, + telemetry, + { + record: () => {}, + } as unknown as Span + ) assert.strictEqual(Buffer.isBuffer(result.zipFileBuffer), true) // checksum is not the same across different test executions because some unique random folder names are generated @@ -87,7 +94,7 @@ describe('file utils', () => { await folder.write('file2.md', 'test content') const workspace = getWorkspaceFolder(folder.path) - await testPrepareRepoData(workspace, ['file1.md', 'file2.md']) + await testPrepareRepoData([workspace], ['file1.md', 'file2.md']) }) it('prepareRepoData ignores denied file extensions', async function () { @@ -96,7 +103,7 @@ describe('file utils', () => { const workspace = getWorkspaceFolder(folder.path) await testPrepareRepoData( - workspace, + [workspace], [], [{ metricName: 'amazonq_bundleExtensionIgnored', value: { filenameExt: 'mp4', count: 1 } }] ) @@ -126,5 +133,18 @@ describe('file utils', () => { ContentLengthError ) }) + + it('prepareRepoData properly handles multi-root workspaces', async function () { + const folder = await TestFolder.create() + const testFilePath = 'innerFolder/file.md' + await folder.write(testFilePath, 'test content') + + // Add a folder and its subfolder to the workspace + const workspace1 = getWorkspaceFolder(folder.path) + const workspace2 = getWorkspaceFolder(folder.path + '/innerFolder') + const folderName = path.basename(folder.path) + + await testPrepareRepoData([workspace1, workspace2], [`${folderName}_${workspace1.name}/${testFilePath}`]) + }) }) }) diff --git a/packages/core/src/amazonq/util/files.ts b/packages/core/src/amazonq/util/files.ts index 19081fe1654..d97b706596f 100644 --- a/packages/core/src/amazonq/util/files.ts +++ b/packages/core/src/amazonq/util/files.ts @@ -48,8 +48,14 @@ export async function prepareRepoData( let totalBytes = 0 const ignoredExtensionMap = new Map() + const addedFilePaths = new Set() for (const file of files) { + if (addedFilePaths.has(file.zipFilePath)) { + continue + } + addedFilePaths.add(file.zipFilePath) + let fileSize try { fileSize = (await fs.stat(file.fileUri)).size