From bdea2b4df80bfb00d1e4e91c5a34a4ce2dad47c2 Mon Sep 17 00:00:00 2001 From: Ladislau Szomoru <3372902+lszomoru@users.noreply.github.com> Date: Fri, 27 Mar 2026 13:56:36 +0000 Subject: [PATCH] Git - scaffold the git extension API (#305643) * Git - scaffold the git extension API * Pull request feedback --- extensions/git/src/api/api1.ts | 4 ++++ extensions/git/src/api/git.d.ts | 1 + extensions/git/src/git.ts | 20 ++++++++++++++++++++ extensions/git/src/operation.ts | 5 ++++- extensions/git/src/repository.ts | 17 +++++++++++++++++ 5 files changed, 46 insertions(+), 1 deletion(-) diff --git a/extensions/git/src/api/api1.ts b/extensions/git/src/api/api1.ts index d003348045f..18d558fdf9b 100644 --- a/extensions/git/src/api/api1.ts +++ b/extensions/git/src/api/api1.ts @@ -158,6 +158,10 @@ export class ApiRepository implements Repository { return this.#repository.clean(paths.map(p => Uri.file(p))); } + restore(paths: string[], options?: { staged?: boolean; ref?: string }) { + return this.#repository.restore(paths.map(p => Uri.file(p)), options); + } + diff(cached?: boolean) { return this.#repository.diff(cached); } diff --git a/extensions/git/src/api/git.d.ts b/extensions/git/src/api/git.d.ts index 95da0f84c74..0941959b8cc 100644 --- a/extensions/git/src/api/git.d.ts +++ b/extensions/git/src/api/git.d.ts @@ -259,6 +259,7 @@ export interface Repository { add(paths: string[]): Promise; revert(paths: string[]): Promise; clean(paths: string[]): Promise; + restore(paths: string[], options?: { staged?: boolean; ref?: string }): Promise; apply(patch: string, reverse?: boolean): Promise; apply(patch: string, options?: { allowEmpty?: boolean; reverse?: boolean; threeWay?: boolean; }): Promise; diff --git a/extensions/git/src/git.ts b/extensions/git/src/git.ts index 14e61bc3f8b..83af8866c71 100644 --- a/extensions/git/src/git.ts +++ b/extensions/git/src/git.ts @@ -2345,6 +2345,26 @@ export class Repository { } } + async restore(paths: string[], options?: { staged?: boolean; ref?: string }): Promise { + const args = ['restore']; + + if (options?.staged) { + args.push('--staged'); + } + + if (options?.ref) { + args.push('--source', options.ref); + } + + if (paths.length > 0) { + for (const chunk of splitInChunks(paths.map(p => this.sanitizeRelativePath(p)), MAX_CLI_LENGTH)) { + await this.exec([...args, '--', ...chunk]); + } + } else { + await this.exec([...args, '--', '.']); + } + } + async addRemote(name: string, url: string): Promise { const args = ['remote', 'add', name, url]; await this.exec(args); diff --git a/extensions/git/src/operation.ts b/extensions/git/src/operation.ts index 96fffa4dc87..961e397bbb8 100644 --- a/extensions/git/src/operation.ts +++ b/extensions/git/src/operation.ts @@ -52,6 +52,7 @@ export const enum OperationKind { RebaseAbort = 'RebaseAbort', RebaseContinue = 'RebaseContinue', Refresh = 'Refresh', + Restore = 'Restore', RevertFiles = 'RevertFiles', RevList = 'RevList', RevParse = 'RevParse', @@ -72,7 +73,7 @@ export type Operation = AddOperation | ApplyOperation | BlameOperation | BranchO GetBranchOperation | GetBranchesOperation | GetCommitTemplateOperation | GetObjectDetailsOperation | GetObjectFilesOperation | GetRefsOperation | GetRemoteRefsOperation | HashObjectOperation | IgnoreOperation | LogOperation | LogFileOperation | MergeOperation | MergeAbortOperation | MergeBaseOperation | MoveOperation | PostCommitCommandOperation | PullOperation | PushOperation | RemoteOperation | RenameBranchOperation | - RemoveOperation | ResetOperation | RebaseOperation | RebaseAbortOperation | RebaseContinueOperation | RefreshOperation | RevertFilesOperation | + RemoveOperation | ResetOperation | RebaseOperation | RebaseAbortOperation | RebaseContinueOperation | RefreshOperation | RestoreOperation | RevertFilesOperation | RevListOperation | RevParseOperation | SetBranchUpstreamOperation | ShowOperation | StageOperation | StatusOperation | StashOperation | SubmoduleUpdateOperation | SyncOperation | TagOperation | WorktreeOperation; @@ -121,6 +122,7 @@ export type RebaseOperation = BaseOperation & { kind: OperationKind.Rebase }; export type RebaseAbortOperation = BaseOperation & { kind: OperationKind.RebaseAbort }; export type RebaseContinueOperation = BaseOperation & { kind: OperationKind.RebaseContinue }; export type RefreshOperation = BaseOperation & { kind: OperationKind.Refresh }; +export type RestoreOperation = BaseOperation & { kind: OperationKind.Restore }; export type RevertFilesOperation = BaseOperation & { kind: OperationKind.RevertFiles }; export type RevListOperation = BaseOperation & { kind: OperationKind.RevList }; export type RevParseOperation = BaseOperation & { kind: OperationKind.RevParse }; @@ -179,6 +181,7 @@ export const Operation = { RebaseAbort: { kind: OperationKind.RebaseAbort, blocking: false, readOnly: false, remote: false, retry: false, showProgress: true } as RebaseAbortOperation, RebaseContinue: { kind: OperationKind.RebaseContinue, blocking: false, readOnly: false, remote: false, retry: false, showProgress: true } as RebaseContinueOperation, Refresh: { kind: OperationKind.Refresh, blocking: false, readOnly: false, remote: false, retry: false, showProgress: true } as RefreshOperation, + Restore: (showProgress: boolean) => ({ kind: OperationKind.Restore, blocking: false, readOnly: false, remote: false, retry: false, showProgress } as RestoreOperation), RevertFiles: (showProgress: boolean) => ({ kind: OperationKind.RevertFiles, blocking: false, readOnly: false, remote: false, retry: false, showProgress } as RevertFilesOperation), RevList: { kind: OperationKind.RevList, blocking: false, readOnly: true, remote: false, retry: false, showProgress: false } as RevListOperation, RevParse: { kind: OperationKind.RevParse, blocking: false, readOnly: true, remote: false, retry: false, showProgress: false } as RevParseOperation, diff --git a/extensions/git/src/repository.ts b/extensions/git/src/repository.ts index 657ecaa5534..56c24729257 100644 --- a/extensions/git/src/repository.ts +++ b/extensions/git/src/repository.ts @@ -1365,6 +1365,23 @@ export class Repository implements Disposable { }); } + async restore(resources: Uri[], options?: { staged?: boolean; ref?: string }): Promise { + await this.run( + Operation.Restore(!this.optimisticUpdateEnabled()), + async () => { + const resourcePaths = resources.map(r => r.fsPath); + await this.repository.restore(resourcePaths, options); + + if (options?.staged) { + // Index was modified; + this.closeDiffEditors([], resourcePaths); + } else { + // Working tree was modified; + this.closeDiffEditors(resourcePaths, []); + } + }); + } + async commit(message: string | undefined, opts: CommitOptions = Object.create(null)): Promise { const indexResources = [...this.indexGroup.resourceStates.map(r => r.resourceUri.fsPath)]; const workingGroupResources = opts.all && opts.all !== 'tracked' ?