Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 10 additions & 1 deletion src/managers/builtin/pipUtils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -295,7 +295,16 @@ export async function getProjectInstallable(
const p = api.getPythonProject(uri)?.uri.fsPath;
return p && fsPaths.includes(p);
})
.sort();
.sort((a, b) => {
// Sort by path depth (shallowest first) so top-level files like
// requirements.txt appear before deeply nested ones.
const depthA = a.fsPath.split(path.sep).length;
const depthB = b.fsPath.split(path.sep).length;
if (depthA !== depthB) {
return depthA - depthB;
}
return a.fsPath.localeCompare(b.fsPath);
});
Comment on lines +298 to +307
Copy link

Copilot AI Apr 14, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Depth is currently computed from the absolute fsPath. If multiple project roots are present, absolute path depth can differ due to the root’s location on disk (e.g., /mnt/x/project vs /x/project), which can reorder files across projects in a way unrelated to ‘shallower within the project’. To match the intent (‘top-level within a project’), compute depth relative to the owning project root (e.g., api.getPythonProject(uri)?.uri.fsPath) and count segments on the relative path (after normalization and trimming empty segments).

Copilot uses AI. Check for mistakes.

await Promise.all(
filtered.map(async (uri) => {
Expand Down
32 changes: 32 additions & 0 deletions src/test/managers/builtin/pipUtils.unit.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -199,4 +199,36 @@ suite('Pip Utils - getProjectInstallable', () => {
assert.ok(firstResult.uri, 'Should have a URI');
assert.ok(firstResult.uri.fsPath.startsWith(workspacePath), 'Should be in workspace directory');
});

test('should sort shallower files before deeper ones', async () => {
// Arrange: Return files at different depths, with deeper ones discovered first
findFilesStub.callsFake((pattern: string) => {
const workspacePath = Uri.file('/test/path/root').fsPath;
if (pattern === '**/*requirements*.txt') {
return Promise.resolve([
Uri.file(path.join(workspacePath, 'deep', 'nested', 'sub', 'requirements.txt')),
Uri.file(path.join(workspacePath, 'subdir', 'dev-requirements.txt')),
]);
} else if (pattern === '*requirements*.txt') {
return Promise.resolve([Uri.file(path.join(workspacePath, 'requirements.txt'))]);
} else if (pattern === '**/requirements/*.txt') {
return Promise.resolve([]);
} else if (pattern === '**/pyproject.toml') {
return Promise.resolve([]);
}
return Promise.resolve([]);
});

// Act
const workspacePath = Uri.file('/test/path/root').fsPath;
const projects = [{ name: 'workspace', uri: Uri.file(workspacePath) }];
const result = (await getProjectInstallable(mockApi as PythonEnvironmentApi, projects)).installables;

// Assert: root-level requirements.txt should come first
assert.strictEqual(result.length, 3);
const names = result.map((r) => r.name);
assert.strictEqual(names[0], 'requirements.txt', 'Root-level requirements.txt should be first');
assert.strictEqual(names[1], 'dev-requirements.txt', 'One-level-deep file should be second');
assert.strictEqual(names[2], 'requirements.txt', 'Deeply nested file should be last');
});
Comment on lines +205 to +233
Copy link

Copilot AI Apr 14, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This test hard-codes a POSIX-style absolute path (/test/path/root), which can be brittle on Windows path handling. Also, asserting ordering via name is ambiguous when multiple entries share the same basename (requirements.txt). Consider deriving workspacePath from a platform-safe absolute base (e.g., temp dir), and asserting order using result.map(r => r.uri.fsPath) or path.relative(workspacePath, r.uri.fsPath) so the root vs deep requirements.txt are unambiguous.

Copilot uses AI. Check for mistakes.
});
Loading