mirror of
https://github.com/microsoft/vscode.git
synced 2026-08-21 00:42:58 +01:00
* agentHost: multi-root turn changeset diffs (foundation) Compute per-turn and static (branch/session/uncommitted) changesets across all working directories of a multi-root agent host session: partition git repositories vs non-git folders, dedupe repositories shared by multiple folders, cap per-turn fan-out, and fall back to tracked edits when a git diff is unavailable. Single-folder sessions keep their existing behavior. Telemetry: - New `agentHost.changesetComputed` event (branch/session/uncommitted/turn kinds) reporting compute duration, outcome, and the resolved multi-root git topology; static and per-turn reporters funnel into the one event. - Add `isMultiRoot`/`folderCount` (and browser-projected git topology) to `agentHost.turnCompleted`, `agents/requestSent`, and `agents/sessionSummary`. - Bound the `resolveSessionRepositories` git rev-parse fan-out with a concurrency limiter. Also includes documentation clarifications and tracked-edit helper renames. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * agentHost: Implement robust worktree removal with retry logic for transient errors * agentHost: replace 20-repo changeset cap with bounded-concurrency fan-out Per-turn and branch-summary changeset diffs previously capped to the first 20 git repositories (slice(0, 20) + unbounded Promise.all), silently dropping repositories 21+ and warning about "capping". Diff every resolved repository through a per-call Limiter(5) fan-out instead, mirroring the built-in git extension, so no repository is dropped while at most 5 git processes run concurrently. - Rename MAX_TURN_DIFF_REPOSITORIES (20) to MAX_TURN_DIFF_REPOSITORY_CONCURRENCY (5) - Lower REPOSITORY_ROOT_RESOLUTION_CONCURRENCY from 8 to 5 - Remove the now-dead capHit / diffedGitFolderCount telemetry - Rewrite the cap test as a bounded-concurrency + no-truncation regression Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --------- Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
96 lines
3.8 KiB
TypeScript
96 lines
3.8 KiB
TypeScript
/*---------------------------------------------------------------------------------------------
|
|
* Copyright (c) Microsoft Corporation. All rights reserved.
|
|
* Licensed under the MIT License. See License.txt in the project root for license information.
|
|
*--------------------------------------------------------------------------------------------*/
|
|
|
|
import { Limiter } from '../../../base/common/async.js';
|
|
import { extUriBiasedIgnorePathCase } from '../../../base/common/resources.js';
|
|
import { URI } from '../../../base/common/uri.js';
|
|
import { IAgentHostGitService } from '../common/agentHostGitService.js';
|
|
|
|
/**
|
|
* Upper bound on concurrent `git rev-parse --show-toplevel` probes when
|
|
* resolving a session's working directories, so a many-folder session cannot
|
|
* spawn an unbounded number of git processes at once.
|
|
*/
|
|
const REPOSITORY_ROOT_RESOLUTION_CONCURRENCY = 5;
|
|
|
|
/**
|
|
* The git repository roots and non-git directories that a session's effective
|
|
* working directories resolve to.
|
|
*/
|
|
export interface ISessionRepositories {
|
|
/**
|
|
* The UNIQUE git repository roots backing the session's working
|
|
* directories. Deduplicated with the same case bias the rest of the agent
|
|
* host uses ({@link extUriBiasedIgnorePathCase}), so several folders rooted
|
|
* inside one repository contribute a single root. Ordered by the first
|
|
* contributing working directory, preserving the caller's input order.
|
|
*/
|
|
readonly gitRepositories: URI[];
|
|
/**
|
|
* The working directories that are not git-backed — their
|
|
* {@link IAgentHostGitService.getRepositoryRoot} returned `undefined`.
|
|
* Preserves the caller's input order.
|
|
*/
|
|
readonly nonGitDirectories: URI[];
|
|
}
|
|
|
|
/**
|
|
* Resolves working directories into unique Git repository roots and
|
|
* directories that must be handled without Git.
|
|
*
|
|
* Repository roots are ordered by their first matching input directory and
|
|
* deduplicated using {@link extUriBiasedIgnorePathCase}. Each output array
|
|
* preserves input order even though lookups run concurrently.
|
|
*
|
|
* @example
|
|
* // /work/app/ui -> /work/app
|
|
* // /work/notes -> undefined
|
|
* // /work/app/api -> /work/app
|
|
* // /work/tools -> /work/tools
|
|
* // Result:
|
|
* // gitRepositories: [/work/app, /work/tools]
|
|
* // nonGitDirectories: [/work/notes]
|
|
*
|
|
* A lookup error rejects by default. If `onRootError` is provided and returns
|
|
* normally, it receives the error and the failing directory is added to
|
|
* `nonGitDirectories`.
|
|
*/
|
|
export async function resolveSessionRepositories(workingDirectories: readonly URI[], gitService: IAgentHostGitService, onRootError?: (directory: URI, error: unknown) => void): Promise<ISessionRepositories> {
|
|
const limiter = new Limiter<{ workingDirectory: URI; repositoryRoot: URI | undefined }>(REPOSITORY_ROOT_RESOLUTION_CONCURRENCY);
|
|
let resolvedRoots: { workingDirectory: URI; repositoryRoot: URI | undefined }[];
|
|
try {
|
|
resolvedRoots = await Promise.all(workingDirectories.map(workingDirectory => limiter.queue(async () => {
|
|
try {
|
|
return { workingDirectory, repositoryRoot: await gitService.getRepositoryRoot(workingDirectory) };
|
|
} catch (err) {
|
|
if (!onRootError) {
|
|
throw err;
|
|
}
|
|
onRootError(workingDirectory, err);
|
|
return { workingDirectory, repositoryRoot: undefined };
|
|
}
|
|
})));
|
|
} finally {
|
|
limiter.dispose();
|
|
}
|
|
|
|
const gitRepositories: URI[] = [];
|
|
const nonGitDirectories: URI[] = [];
|
|
const seenRepositoryKeys = new Set<string>();
|
|
for (const { workingDirectory, repositoryRoot } of resolvedRoots) {
|
|
if (!repositoryRoot) {
|
|
nonGitDirectories.push(workingDirectory);
|
|
continue;
|
|
}
|
|
const repositoryKey = extUriBiasedIgnorePathCase.getComparisonKey(repositoryRoot);
|
|
if (seenRepositoryKeys.has(repositoryKey)) {
|
|
continue;
|
|
}
|
|
seenRepositoryKeys.add(repositoryKey);
|
|
gitRepositories.push(repositoryRoot);
|
|
}
|
|
return { gitRepositories, nonGitDirectories };
|
|
}
|