agentHostGitService.ts ×3

Frontier kind: Code frontier

unlabeled · c_2e690336b6b2

1015 tests · 15711 LOC · 54 files · introduces 0 tests · 342 LOC · 1 file

Introduces — evidence that enters the hierarchy at this concept

Code
3 ranges342 lines · 1 files
Tests
0 tests

Contains — complete concept membership

All code (extent)
1532 ranges15711 lines · 54 files · Browse complete extent
All tests (intent)
1015 testsBrowse complete intent

Neighbourhood graph

The orange circle is the focus. Violet and green circles are every ancestor and descendant, broader and narrower, at any distance; blue squares and pink diamonds are the introduced files and exact introduced tests of every visible concept, not only the focus's. Arrows point from broader to narrower concepts and bridge only concepts omitted from this view. Undirected links show source or test introduction. Concept and file size follows LOC; exact test nodes use test-count units.

Introduced files, introduced tests, and structurally relevant concept specialization

In the embedded map, ordinary wheel input scrolls the page; use the visible controls to zoom and drag to pan. Open the full-screen map for canvas navigation: wheel pans, Ctrl/Command plus wheel zooms, and arrow keys pan when this region is focused. On touch screens, open the full-screen map to pan or pinch. If JavaScript or WebGL is unavailable, use the native relationship evidence on this page.

Graph controls are ready.

Interactive rendering requires JavaScript and WebGL. Use the native relationship evidence on this page while the interactive map is unavailable.

Native relationship evidence

Every exact file and test below is linked only from the concept that introduces it.

Introduced tests

Every collected test enters the hierarchy at exactly one concept.

No tests are introduced at this concept. Its intent tests are introduced by other concepts.

Introduced code

Every collected source range enters the hierarchy at exactly one concept.

1 file ranked by introduced lines: 342 introduced LOC across 3 ranges. Expand a file to inspect source; the > gutter marks introduced lines.

src/vs/platform/agentHost/common/agentHostGitService.ts 342 introduced LOC · 3 ranges

Open complete file

1 > /*--------------------------------------------------------------------------------------------- agentHostGitService.ts
2 > * Copyright (c) Microsoft Corporation. All rights reserved.
3 > * Licensed under the MIT License. See License.txt in the project root for license information.
4 > *--------------------------------------------------------------------------------------------*/
5 >
6 > import { VSBuffer } from '../../../base/common/buffer.js';
7 > import { URI } from '../../../base/common/uri.js';
8 > import { createDecorator } from '../../instantiation/common/instantiation.js';
9 > import { ISessionFileDiff, ISessionGitState } from './state/sessionState.js';
10 >
11 > /**
12 > * Provider-agnostic session-database metadata key under which agents
13 > * persist the branch they want git-driven diffs anchored to. Read by
14 > * {@link IAgentHostChangesetService} when computing per-session file diffs; absent
15 > * value means the diff falls back to anchoring at HEAD.
16 > */
17 > export const META_DIFF_BASE_BRANCH = 'agentHost.diffBaseBranch';
18 >
19 > /**
20 > * Resolves the Branch Changes base-branch **name** from its two sources, in
21 > * precedence order: the agent-persisted {@link META_DIFF_BASE_BRANCH} metadata
22 > * value, then the session git state's detected base branch. Returns `undefined`
23 > * when neither is available (callers then anchor the diff at `HEAD`).
24 > *
25 > * Shared by {@link IAgentHostChangesetService} and the review service so both
26 > * pick the same base branch.
27 > */
28 > export function resolveDiffBaseBranchName(persistedBaseBranch: string | undefined, sessionGitStateBaseBranch: string | undefined): string | undefined {
29 return persistedBaseBranch ?? sessionGitStateBaseBranch;
30 }
32 > /**
33 > * The well-known SHA-1 of git's empty tree, used as a fallback when a
34 > * repository has no commits (no `HEAD` to read into the temp index).
35 > */
36 > export const EMPTY_TREE_OBJECT = '4b825dc642cb6eb9a060e54bf8d69288fbee4904';
37 >
38 > /** Options for {@link IAgentHostGitService.computeSessionFileDiffs}. */
39 > export interface IComputeSessionFileDiffsOptions {
40 > /**
41 > * The session URI, used as the authority of the produced
42 > * `git-blob:` URIs so the resolver can find the session's working
43 > * directory.
44 > */
45 > readonly sessionUri: string;
46 > /**
47 > * The branch to diff against. Typically the worktree's start-point
48 > * branch (for worktree sessions) or the repository's default branch.
49 > * When undefined or unresolvable, the diff is taken against `HEAD`,
50 > * which surfaces uncommitted work but no committed-on-branch work.
51 > */
52 > readonly baseBranch?: string;
53 > }
54 >
55 > /** Cheap repository facts used to decide whether a branch diff is safe to compute. */
56 > export interface IBranchDiffSafetyInfo {
57 > readonly hasVirtualFileSystem: boolean;
58 > readonly baselineCommitTimestamp: number | undefined;
59 > readonly commitCount: number | undefined;
60 > readonly workspaceFileCount: number;
61 > }
62 >
63 > /** A bounded unified-diff result. */
64 > export interface IDiffPatchResult {
65 > readonly patch: string | undefined;
66 > readonly tooLarge: boolean;
67 > }
68 >
69 > /** Options for {@link IAgentHostGitService.push}. */
70 > export interface IPushOptions {
71 > /** The branch or refspec to push. Defaults to the current branch. */
72 > readonly ref?: string;
73 > /** The remote to push to. Defaults to `origin`. */
74 > readonly remote?: string;
75 > /**
76 > * When true, the push uses `-u` so the pushed branch tracks the remote
77 > * branch for subsequent fetch/push commands.
78 > */
79 > readonly setUpstream?: boolean;
80 > }
81 >
82 > /** Options for {@link IAgentHostGitService.pull}. */
83 > export interface IPullOptions {
84 > /** The branch or ref to pull. Defaults to the configured upstream. */
85 > readonly ref?: string;
86 > /** The remote to pull from. Defaults to `origin`. */
87 > readonly remote?: string;
88 > /** When true, local commits are rebased onto the fetched ref (`-r`) instead of merged. */
89 > readonly rebase?: boolean;
90 > }
91 >
92 > export const IAgentHostGitService = createDecorator<IAgentHostGitService>('agentHostGitService');
93 >
94 > export interface IRefQuery {
95 > readonly count?: number;
96 > readonly pattern?: string | string[];
97 > readonly sort?: 'alphabetically' | 'committerdate' | 'creatordate';
98 > }
99 >
100 > export type Branch = IBranch | IRemoteBranch;
101 > export type GitRef = IBranch | IRemoteBranch | ITag;
102 >
103 > export const enum GitRefType {
104 > Head,
105 > RemoteHead,
106 > DetachedHead,
107 > Tag
108 > }
109 >
110 > export interface IBranch {
111 > readonly ref: string;
112 > readonly name: string;
113 > readonly upstream?: {
114 > readonly ref: string;
115 > readonly name: string;
116 > readonly remote: string;
117 > };
118 > readonly kind: GitRefType.Head;
119 > }
120 >
121 > export interface IRemoteBranch {
122 > readonly ref: string;
123 > readonly name: string;
124 > readonly remote: string;
125 > readonly kind: GitRefType.RemoteHead;
126 > }
127 >
128 > export interface ITag {
129 > readonly ref: string;
130 > readonly name: string;
131 > readonly kind: GitRefType.Tag;
132 > }
133 >
134 > export interface IDetachedHead {
135 > readonly name: string;
136 > readonly kind: GitRefType.DetachedHead;
137 > }
138 >
139 > export interface IDefaultBranch {
140 > readonly name: string;
141 > readonly startPoint: string;
142 > }
143 >
144 > export interface IAgentHostGitService {
145 > readonly _serviceBrand: undefined;
146 > getCurrentBranch(workingDirectory: URI): Promise<string | undefined>;
147 > getDefaultBranch(workingDirectory: URI): Promise<IDefaultBranch | undefined>;
148 > getRefs(workingDirectory: URI, query?: IRefQuery): Promise<GitRef[]>;
149 > getBranches(workingDirectory: URI, query?: IRefQuery): Promise<Branch[]>;
150 > getBranch(workingDirectory: URI, name: string): Promise<Branch | undefined>;
151 > getRepositoryRoot(workingDirectory: URI): Promise<URI | undefined>;
152 > getWorktreeRoots(workingDirectory: URI): Promise<URI[]>;
153 > addWorktree(repositoryRoot: URI, worktree: URI, branchName: string, startPoint: string): Promise<void>;
154 > copyWorktreeIncludeFiles(repositoryRoot: URI, worktree: URI, globs: readonly string[]): Promise<void>;
155 > /**
156 > * Adds a worktree for an existing branch (no `-b`). Used when restoring
157 > * a worktree whose branch was preserved (e.g. unarchiving a session
158 > * whose worktree was previously cleaned up on archive).
159 > */
160 > addExistingWorktree(repositoryRoot: URI, worktree: URI, branchName: string): Promise<void>;
161 > removeWorktree(repositoryRoot: URI, worktree: URI): Promise<void>;
162 > /**
163 > * Returns true when the named branch exists in the repository
164 > * (`refs/heads/<branchName>` resolves). Used by archive cleanup to
165 > * confirm the branch is preserved before deleting the worktree, and by
166 > * the unarchive path to confirm the branch is still around before
167 > * recreating the worktree.
168 > */
169 > branchExists(repositoryRoot: URI, branchName: string): Promise<boolean>;
170 > /**
171 > * Returns true when the working tree has any tracked, staged, or
172 > * untracked changes. Used by archive cleanup to skip removing a
173 > * worktree that still contains uncommitted work.
174 > */
175 > hasUncommittedChanges(workingDirectory: URI): Promise<boolean>;
176 >
177 > /**
178 > * Stages and commits all tracked, staged, and untracked changes in the
179 > * working tree. Mirrors the Copilot CLI session PR path, which commits
180 > * uncommitted work before creating a pull request.
181 > */
182 > commitAll(workingDirectory: URI, message: string): Promise<void>;
183 >
184 > /**
185 > * Restores files in the working tree via `git restore`. When
186 > * {@link options.staged} is true, restores the index instead of the
187 > * working tree. When {@link options.ref} is provided, the contents are
188 > * taken from that ref (`--source`). An empty {@link paths} array
189 > * restores everything (`.`).
190 > */
191 > restore(workingDirectory: URI, paths: readonly string[], options?: { readonly staged?: boolean; readonly ref?: string }): Promise<void>;
192 >
193 > /**
194 > * Returns true when the named branch has an upstream tracking ref
195 > * (i.e. `<branch>@{upstream}` resolves). Used before {@link push}
196 > * to decide whether `--set-upstream` is needed.
197 > */
198 > hasUpstream(workingDirectory: URI, branchName: string): Promise<boolean>;
199 >
200 > /**
201 > * Fetches the latest changes from the remote (`origin` unless
202 > * {@link IPullOptions.remote} overrides it) and integrates them into the
203 > * current branch. When {@link IPullOptions.rebase} is true, local commits
204 > * are rebased onto the fetched ref instead of merged. When
205 > * {@link IPullOptions.ref} is provided, that ref is pulled instead of the
206 > * branch's configured upstream.
207 > */
208 > pull(workingDirectory: URI, options?: IPullOptions): Promise<void>;
209 >
210 > /**
211 > * Pushes the current branch (or {@link IPushOptions.ref}) to the remote
212 > * (`origin` unless {@link IPushOptions.remote} overrides it). When
213 > * {@link IPushOptions.setUpstream} is true, the push uses `-u` so
214 > * subsequent fetch/push commands track the remote branch.
215 > */
216 > push(workingDirectory: URI, options?: IPushOptions): Promise<void>;
217 >
218 > /**
219 > * Computes the {@link ISessionGitState} for the working directory by
220 > * shelling out to `git`. Returns undefined if the directory is not a
221 > * git work tree. Called on session open and after each turn completes
222 > * so the UI always reflects current branch/remote/change state.
223 > */
224 > getSessionGitState(workingDirectory: URI): Promise<ISessionGitState | undefined>;
225 > /** Returns fetch remote URLs with the preferred remote, then `origin`, first. */
226 > getFetchRemoteUrls(workingDirectory: URI, preferredRemote?: string): Promise<readonly string[] | undefined>;
227 > /** Returns repo-relative untracked file paths. */
228 > getUntrackedPaths(workingDirectory: URI): Promise<readonly string[] | undefined>;
229 >
230 > /**
231 > * Computes per-file diffs for the session by shelling out to `git
232 > * diff --raw --numstat --diff-filter=ADMR -z` against the merge base of
233 > * the current branch and {@link IComputeSessionFileDiffsOptions.baseBranch}
234 > * (or `HEAD` if no base branch is available). When the working tree has
235 > * untracked files, the diff is computed via a temp index so the
236 > * untracked content is included.
237 > *
238 > * Returns `undefined` when {@link workingDirectory} is not a git work
239 > * tree, so callers can fall back to other diff sources.
240 > *
241 > * Each returned {@link ISessionFileDiff} has its `before.content` set to
242 > * a `git-blob:` URI ({@link buildGitBlobUri}); `after.content` is a
243 > * `file:` URI on the working-tree path. Adds and deletes drop the
244 > * missing side.
245 > */
246 > computeSessionFileDiffs(workingDirectory: URI, options: IComputeSessionFileDiffsOptions): Promise<readonly ISessionFileDiff[] | undefined>;
247 >
248 > /**
249 > * Resolves the commit-ish the **Branch Changes** baseline is measured from:
250 > * the merge-base of `HEAD` and `baseBranch` (preferring the
251 > * `origin/<baseBranch>` remote-tracking ref when it exists), falling back to
252 > * `HEAD`, then to the empty-tree object for a repo with no commits. Returns
253 > * `undefined` only when {@link workingDirectory} is not a git work tree.
254 > *
255 > * Shared by {@link computeSessionFileDiffs} (which anchors the Branch Changes
256 > * diff here) and the review service, so both agree on the exact baseline.
257 > */
258 > resolveBranchBaselineCommit(workingDirectory: URI, baseBranch?: string): Promise<string | undefined>;
259 >
260 > /**
261 > * Reads a single git blob via `git show <ref>:<repoRelativePath>` from
262 > * the given working directory. Returns `undefined` when the blob does
263 > * not exist or the directory is not a git work tree.
264 > */
265 > showBlob(workingDirectory: URI, ref: string, repoRelativePath: string): Promise<VSBuffer | undefined>;
266 >
267 > // ---- Checkpoint plumbing (used by IAgentHostCheckpointService) -------
268 >
269 > /**
270 > * Captures the current working tree (including untracked files) as a
271 > * tree object, returning the tree OID. Uses a throwaway `GIT_INDEX_FILE`
272 > * so the user's real index is untouched. Returns `undefined` when the
273 > * directory is not a git work tree.
274 > */
275 > captureWorkingTreeAsTree(workingDirectory: URI): Promise<string | undefined>;
276 >
277 > /**
278 > * Creates a commit object from a tree (optionally chained to a parent)
279 > * and returns its OID. Does NOT update any ref.
280 > */
281 > commitTree(repositoryRoot: URI, treeOid: string, parentOid: string | undefined, message: string): Promise<string | undefined>;
282 >
283 > /**
284 > * Updates a ref to point at `newOid`. Creates the ref if missing.
285 > */
286 > updateRef(repositoryRoot: URI, ref: string, newOid: string): Promise<void>;
287 >
288 > /**
289 > * Batch-deletes the given refs via `git update-ref --stdin -z`.
290 > * Missing refs are tolerated.
291 > */
292 > deleteRefs(repositoryRoot: URI, refs: readonly string[]): Promise<void>;
293 >
294 > /**
295 > * Resolves a ref/object expression to its OID, e.g. `revParse(repo, 'refs/agents/abc/...')`
296 > * or `revParse(repo, '<commit>^{tree}')`. Returns `undefined` when the
297 > * ref does not exist.
298 > */
299 > revParse(repositoryRoot: URI, expression: string): Promise<string | undefined>;
300 >
301 > /**
302 > * Builds a new tree from `baseTreeOid` in which the single repo-relative
303 > * `path` is replaced by its content (blob + mode) from `sourceTreeOid`, or
304 > * removed when the path is absent in `sourceTreeOid`. All other paths are
305 > * copied verbatim from `baseTreeOid`. Uses a throwaway `GIT_INDEX_FILE` so
306 > * the user's real index is untouched. Returns the new tree OID, or
307 > * `undefined` on git failure.
308 > *
309 > * File-level building block for review (see `IAgentHostReviewService`): to
310 > * mark a file reviewed, overlay it from the working-tree snapshot tree; to
311 > * unmark, overlay it from the baseline tree.
312 > */
313 > overlayPathIntoTree(repositoryRoot: URI, baseTreeOid: string, path: string, sourceTreeOid: string): Promise<string | undefined>;
314 >
315 > /**
316 > * Returns the repo-relative paths that differ between two tree-ish (commit
317 > * or tree) objects via `git diff --name-only --no-renames -z`. Rename
318 > * detection is off so a rename shows as delete(old) + add(new). Returns
319 > * `undefined` on git failure (e.g. not a git work tree).
320 > */
321 > diffTreePaths(repositoryRoot: URI, fromTreeish: string, toTreeish: string): Promise<string[] | undefined>;
322 >
323 > /**
324 > * Computes per-file diffs between two refs (typically two consecutive
325 > * checkpoint refs) by shelling out to
326 > * `git diff --raw --numstat --diff-filter=ADMR -z <fromRef> <toRef>`.
327 > * Returns the same {@link ISessionFileDiff} shape as
328 > * {@link computeSessionFileDiffs}: `before.content` is a `git-blob:`
329 > * URI anchored on `fromRef`, `after.content` is a `git-blob:` URI
330 > * anchored on `toRef`. Returns `undefined` on git failure.
331 > *
332 > * Used by the changeset service to materialise per-turn diffs from
333 > * checkpoint refs when they are available — that path captures
334 > * terminal-tool edits the FileEditTracker pipeline misses.
335 > */
336 > computeFileDiffsBetweenRefs(workingDirectory: URI, options: { readonly sessionUri: string; readonly fromRef: string; readonly toRef: string }): Promise<readonly ISessionFileDiff[] | undefined>;
337 > /** Reads bounded facts needed before computing an expensive branch diff. */
338 > getBranchDiffSafetyInfo(workingDirectory: URI, baselineCommit: string): Promise<IBranchDiffSafetyInfo | undefined>;
339 > /** Computes a unified patch for paths between immutable tree-ish values. */
340 > getDiffPatchBetweenRefs(workingDirectory: URI, options: { readonly fromRef: string; readonly toRef: string; readonly paths: readonly string[]; readonly maxBuffer: number }): Promise<IDiffPatchResult | undefined>;
341 > }
342 >
343 function getCommonBranchPriority(branch: string): number {
344 if (branch === 'main') {
350 return 2;
351 }
353 > export function getBranchCompletions(branches: readonly string[], options?: { readonly query?: string; readonly limit?: number }): string[] {
354 const normalizedQuery = options?.query?.toLowerCase();
355 const filtered = normalizedQuery