From cd4d0c9710dd96f139d86de85657f5349dd7fecf Mon Sep 17 00:00:00 2001 From: Avni2000 Date: Thu, 12 Feb 2026 18:57:19 -0600 Subject: [PATCH 1/7] [FIX] Status bar subcribes to git, activates on conflict --- src/extension.ts | 70 +++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 69 insertions(+), 1 deletion(-) diff --git a/src/extension.ts b/src/extension.ts index 0bb570e..ad239cf 100644 --- a/src/extension.ts +++ b/src/extension.ts @@ -33,6 +33,23 @@ let lastResolvedDetails: { // Event emitter to trigger decoration refresh const decorationChangeEmitter = new vscode.EventEmitter(); +interface GitRepositoryState { + onDidChange: vscode.Event; +} + +interface GitRepository { + state?: GitRepositoryState; +} + +interface GitAPI { + repositories: GitRepository[]; + onDidOpenRepository: vscode.Event; +} + +interface GitExtensionExports { + getAPI(version: 1): GitAPI; +} + /** * Get the file URI for the currently active notebook. * Handles both notebook editor and text editor cases. @@ -81,6 +98,57 @@ async function updateStatusBar(): Promise { } } +function registerGitStateWatchers(context: vscode.ExtensionContext): void { + const extension = vscode.extensions.getExtension('vscode.git'); + if (!extension) { + return; + } + + const watchedRepositories = new WeakSet(); + const attachRepositoryWatcher = (repository: GitRepository): void => { + if (!repository?.state || watchedRepositories.has(repository)) { + return; + } + watchedRepositories.add(repository); + context.subscriptions.push( + repository.state.onDidChange(() => { + decorationChangeEmitter.fire(undefined); + void updateStatusBar(); + }) + ); + }; + + const registerWithApi = (api: GitAPI): void => { + for (const repository of api.repositories) { + attachRepositoryWatcher(repository); + } + + context.subscriptions.push( + api.onDidOpenRepository((repository) => { + attachRepositoryWatcher(repository); + decorationChangeEmitter.fire(undefined); + void updateStatusBar(); + }) + ); + }; + + const onGitReady = (exports: GitExtensionExports | undefined): void => { + if (!exports?.getAPI) { + return; + } + registerWithApi(exports.getAPI(1)); + }; + + if (extension.isActive) { + onGitReady(extension.exports); + return; + } + + void extension.activate().then((exports) => { + onGitReady(exports as GitExtensionExports | undefined); + }); +} + export function activate(context: vscode.ExtensionContext) { console.log('MergeNB extension is now active'); const isTestMode = process.env.MERGENB_TEST_MODE === 'true'; @@ -99,6 +167,7 @@ export function activate(context: vscode.ExtensionContext) { // Initial status bar update updateStatusBar(); + registerGitStateWatchers(context); // Listen for resolution success events context.subscriptions.push( @@ -256,4 +325,3 @@ export function deactivate() { webServer.stop(); } } - From f6da6d4fa4e51ad7099cd370638b2b56692cb520 Mon Sep 17 00:00:00 2001 From: Avni2000 Date: Thu, 12 Feb 2026 18:57:24 -0600 Subject: [PATCH 2/7] [FIX] Dedup code --- src/extension.ts | 35 ++++++++++++++++------------------- 1 file changed, 16 insertions(+), 19 deletions(-) diff --git a/src/extension.ts b/src/extension.ts index ad239cf..8886b6b 100644 --- a/src/extension.ts +++ b/src/extension.ts @@ -20,8 +20,6 @@ import { getWebServer } from './web'; let resolver: NotebookConflictResolver; let statusBarItem: vscode.StatusBarItem; -let currentFileHasConflicts = false; -let successTimeout: NodeJS.Timeout | undefined; let lastResolvedDetails: { uri: string; resolvedNotebook: unknown; @@ -78,7 +76,6 @@ async function updateStatusBar(): Promise { if (!activeUri) { statusBarItem.hide(); - currentFileHasConflicts = false; return; } @@ -91,17 +88,15 @@ async function updateStatusBar(): Promise { statusBarItem.command = 'merge-nb.findConflicts'; statusBarItem.backgroundColor = new vscode.ThemeColor('statusBarItem.warningBackground'); statusBarItem.show(); - currentFileHasConflicts = true; } else { statusBarItem.hide(); - currentFileHasConflicts = false; } } -function registerGitStateWatchers(context: vscode.ExtensionContext): void { +function registerGitStateWatchers(context: vscode.ExtensionContext): boolean { const extension = vscode.extensions.getExtension('vscode.git'); if (!extension) { - return; + return false; } const watchedRepositories = new WeakSet(); @@ -141,12 +136,13 @@ function registerGitStateWatchers(context: vscode.ExtensionContext): void { if (extension.isActive) { onGitReady(extension.exports); - return; + return true; } void extension.activate().then((exports) => { onGitReady(exports as GitExtensionExports | undefined); }); + return true; } export function activate(context: vscode.ExtensionContext) { @@ -167,7 +163,7 @@ export function activate(context: vscode.ExtensionContext) { // Initial status bar update updateStatusBar(); - registerGitStateWatchers(context); + const usingGitApiWatchers = registerGitStateWatchers(context); // Listen for resolution success events context.subscriptions.push( @@ -306,16 +302,17 @@ export function activate(context: vscode.ExtensionContext) { }) ); - // Also watch for Git repository changes - const gitWatcher = vscode.workspace.createFileSystemWatcher('**/.git/index'); - context.subscriptions.push( - gitWatcher, - gitWatcher.onDidChange(async () => { - // Git index changed, refresh all notebook decorations - decorationChangeEmitter.fire(undefined); - updateStatusBar(); - }) - ); + // Fallback for environments where the built-in Git extension API is unavailable. + if (!usingGitApiWatchers) { + const gitWatcher = vscode.workspace.createFileSystemWatcher('**/.git/index'); + context.subscriptions.push( + gitWatcher, + gitWatcher.onDidChange(() => { + decorationChangeEmitter.fire(undefined); + void updateStatusBar(); + }) + ); + } } export function deactivate() { From 4f5eabaabd4ee6caed32f046fcaebec4f32879a2 Mon Sep 17 00:00:00 2001 From: Avni <77120766+Avni2000@users.noreply.github.com> Date: Thu, 12 Feb 2026 19:04:07 -0600 Subject: [PATCH 3/7] [FIX] Allow for fallback Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com> --- src/extension.ts | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/src/extension.ts b/src/extension.ts index 8886b6b..cd026f8 100644 --- a/src/extension.ts +++ b/src/extension.ts @@ -139,10 +139,11 @@ function registerGitStateWatchers(context: vscode.ExtensionContext): boolean { return true; } - void extension.activate().then((exports) => { - onGitReady(exports as GitExtensionExports | undefined); - }); - return true; + extension.activate().then( + (exports) => onGitReady(exports as GitExtensionExports | undefined), + (err) => console.warn('[MergeNB] Git extension activation failed:', err) + ); + return false; } export function activate(context: vscode.ExtensionContext) { From 6df7f27eea040f30d9bf2a494b14be9b7076af78 Mon Sep 17 00:00:00 2001 From: Avni2000 Date: Thu, 12 Feb 2026 19:35:53 -0600 Subject: [PATCH 4/7] [FIX] Properly checks git (works!) --- src/extension.ts | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/src/extension.ts b/src/extension.ts index cd026f8..059e554 100644 --- a/src/extension.ts +++ b/src/extension.ts @@ -20,6 +20,7 @@ import { getWebServer } from './web'; let resolver: NotebookConflictResolver; let statusBarItem: vscode.StatusBarItem; +let statusBarVisible = false; let lastResolvedDetails: { uri: string; resolvedNotebook: unknown; @@ -76,6 +77,7 @@ async function updateStatusBar(): Promise { if (!activeUri) { statusBarItem.hide(); + statusBarVisible = false; return; } @@ -88,8 +90,10 @@ async function updateStatusBar(): Promise { statusBarItem.command = 'merge-nb.findConflicts'; statusBarItem.backgroundColor = new vscode.ThemeColor('statusBarItem.warningBackground'); statusBarItem.show(); + statusBarVisible = true; } else { statusBarItem.hide(); + statusBarVisible = false; } } @@ -257,6 +261,14 @@ export function activate(context: vscode.ExtensionContext) { return webServer.isRunning() ? webServer.getPort() : 0; }) ); + context.subscriptions.push( + vscode.commands.registerCommand('merge-nb.getStatusBarState', () => { + return { + visible: statusBarVisible, + text: statusBarItem.text, + }; + }) + ); } // Register file decoration for notebooks with conflicts From 3bf6cedb8e29b3957121c5b0d00a32d536b94055 Mon Sep 17 00:00:00 2001 From: Avni2000 Date: Thu, 12 Feb 2026 19:53:23 -0600 Subject: [PATCH 5/7] [FIX] void updateStatusBar --- src/extension.ts | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/extension.ts b/src/extension.ts index 059e554..e492aaa 100644 --- a/src/extension.ts +++ b/src/extension.ts @@ -167,7 +167,7 @@ export function activate(context: vscode.ExtensionContext) { ); // Initial status bar update - updateStatusBar(); + void updateStatusBar(); const usingGitApiWatchers = registerGitStateWatchers(context); // Listen for resolution success events @@ -303,15 +303,15 @@ export function activate(context: vscode.ExtensionContext) { fileWatcher, fileWatcher.onDidChange(uri => { decorationChangeEmitter.fire(uri); - updateStatusBar(); + void updateStatusBar(); }), fileWatcher.onDidCreate(uri => { decorationChangeEmitter.fire(uri); - updateStatusBar(); + void updateStatusBar(); }), fileWatcher.onDidDelete(uri => { decorationChangeEmitter.fire(uri); - updateStatusBar(); + void updateStatusBar(); }) ); From 946f789aad6a2a023e07c9e9bff2a3dbfdb21e92 Mon Sep 17 00:00:00 2001 From: Avni2000 Date: Thu, 12 Feb 2026 20:02:25 -0600 Subject: [PATCH 6/7] [REFACTOR] Move extension.ts to git api instead --- package.json | 5 +++- src/extension.ts | 62 ++++++++++---------------------------------- src/typings/git.d.ts | 26 +++++++++++++++++++ 3 files changed, 43 insertions(+), 50 deletions(-) create mode 100644 src/typings/git.d.ts diff --git a/package.json b/package.json index 66960c1..2e06b24 100644 --- a/package.json +++ b/package.json @@ -17,6 +17,9 @@ "onLanguage:jupyter-notebook", "workspaceContains:**/*.ipynb" ], + "extensionDependencies": [ + "vscode.git" + ], "main": "./dist/extension.js", "contributes": { "commands": [ @@ -165,4 +168,4 @@ "markdown-it": "^14.1.0", "ws": "^8.19.0" } -} \ No newline at end of file +} diff --git a/src/extension.ts b/src/extension.ts index e492aaa..9d921a0 100644 --- a/src/extension.ts +++ b/src/extension.ts @@ -17,6 +17,7 @@ import * as vscode from 'vscode'; import { NotebookConflictResolver, ConflictedNotebook, onDidResolveConflict, onDidResolveConflictWithDetails } from './resolver'; import * as gitIntegration from './gitIntegration'; import { getWebServer } from './web'; +import type { API as GitAPI, GitExtension, Repository } from './typings/git'; let resolver: NotebookConflictResolver; let statusBarItem: vscode.StatusBarItem; @@ -32,23 +33,6 @@ let lastResolvedDetails: { // Event emitter to trigger decoration refresh const decorationChangeEmitter = new vscode.EventEmitter(); -interface GitRepositoryState { - onDidChange: vscode.Event; -} - -interface GitRepository { - state?: GitRepositoryState; -} - -interface GitAPI { - repositories: GitRepository[]; - onDidOpenRepository: vscode.Event; -} - -interface GitExtensionExports { - getAPI(version: 1): GitAPI; -} - /** * Get the file URI for the currently active notebook. * Handles both notebook editor and text editor cases. @@ -97,14 +81,15 @@ async function updateStatusBar(): Promise { } } -function registerGitStateWatchers(context: vscode.ExtensionContext): boolean { - const extension = vscode.extensions.getExtension('vscode.git'); +function registerGitStateWatchers(context: vscode.ExtensionContext): void { + const extension = vscode.extensions.getExtension('vscode.git'); if (!extension) { - return false; + console.warn('[MergeNB] vscode.git extension was not found; Git state watchers are disabled.'); + return; } - const watchedRepositories = new WeakSet(); - const attachRepositoryWatcher = (repository: GitRepository): void => { + const watchedRepositories = new WeakSet(); + const attachRepositoryWatcher = (repository: Repository): void => { if (!repository?.state || watchedRepositories.has(repository)) { return; } @@ -131,23 +116,13 @@ function registerGitStateWatchers(context: vscode.ExtensionContext): boolean { ); }; - const onGitReady = (exports: GitExtensionExports | undefined): void => { - if (!exports?.getAPI) { - return; - } - registerWithApi(exports.getAPI(1)); - }; - - if (extension.isActive) { - onGitReady(extension.exports); - return true; + const api = extension.exports?.getAPI(1); + if (!api) { + console.warn('[MergeNB] Git extension API unavailable; Git state watchers are disabled.'); + return; } - extension.activate().then( - (exports) => onGitReady(exports as GitExtensionExports | undefined), - (err) => console.warn('[MergeNB] Git extension activation failed:', err) - ); - return false; + registerWithApi(api); } export function activate(context: vscode.ExtensionContext) { @@ -168,7 +143,7 @@ export function activate(context: vscode.ExtensionContext) { // Initial status bar update void updateStatusBar(); - const usingGitApiWatchers = registerGitStateWatchers(context); + registerGitStateWatchers(context); // Listen for resolution success events context.subscriptions.push( @@ -315,17 +290,6 @@ export function activate(context: vscode.ExtensionContext) { }) ); - // Fallback for environments where the built-in Git extension API is unavailable. - if (!usingGitApiWatchers) { - const gitWatcher = vscode.workspace.createFileSystemWatcher('**/.git/index'); - context.subscriptions.push( - gitWatcher, - gitWatcher.onDidChange(() => { - decorationChangeEmitter.fire(undefined); - void updateStatusBar(); - }) - ); - } } export function deactivate() { diff --git a/src/typings/git.d.ts b/src/typings/git.d.ts new file mode 100644 index 0000000..c0c95f5 --- /dev/null +++ b/src/typings/git.d.ts @@ -0,0 +1,26 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. + *--------------------------------------------------------------------------------------------*/ + +import type { Event, Uri } from 'vscode'; + +export interface RepositoryState { + readonly onDidChange: Event; +} + +export interface Repository { + readonly rootUri: Uri; + readonly state: RepositoryState; +} + +export interface API { + readonly repositories: Repository[]; + readonly onDidOpenRepository: Event; +} + +export interface GitExtension { + readonly enabled: boolean; + readonly onDidChangeEnablement: Event; + getAPI(version: 1): API; +} From aa4a805261e8d623ee12aeb5c37778b9a9e9216e Mon Sep 17 00:00:00 2001 From: Avni2000 Date: Thu, 12 Feb 2026 20:04:53 -0600 Subject: [PATCH 7/7] [FIX] Use full VSCode git typings --- src/typings/git.d.ts | 484 ++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 482 insertions(+), 2 deletions(-) diff --git a/src/typings/git.d.ts b/src/typings/git.d.ts index c0c95f5..287dd43 100644 --- a/src/typings/git.d.ts +++ b/src/typings/git.d.ts @@ -1,26 +1,506 @@ /*--------------------------------------------------------------------------------------------- * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. + * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import type { Event, Uri } from 'vscode'; +import { Uri, Event, Disposable, ProviderResult, Command, CancellationToken, SourceControlHistoryItem } from 'vscode'; +export { ProviderResult } from 'vscode'; + +export interface Git { + readonly path: string; +} + +export interface InputBox { + value: string; +} + +export const enum ForcePushMode { + Force, + ForceWithLease, + ForceWithLeaseIfIncludes, +} + +export const enum RefType { + Head, + RemoteHead, + Tag +} + +export interface Ref { + readonly type: RefType; + readonly name?: string; + readonly commit?: string; + readonly commitDetails?: Commit; + readonly remote?: string; +} + +export interface UpstreamRef { + readonly remote: string; + readonly name: string; + readonly commit?: string; +} + +export interface Branch extends Ref { + readonly upstream?: UpstreamRef; + readonly ahead?: number; + readonly behind?: number; +} + +export interface CommitShortStat { + readonly files: number; + readonly insertions: number; + readonly deletions: number; +} + +export interface Commit { + readonly hash: string; + readonly message: string; + readonly parents: string[]; + readonly authorDate?: Date; + readonly authorName?: string; + readonly authorEmail?: string; + readonly commitDate?: Date; + readonly shortStat?: CommitShortStat; +} + +export interface Submodule { + readonly name: string; + readonly path: string; + readonly url: string; +} + +export interface Remote { + readonly name: string; + readonly fetchUrl?: string; + readonly pushUrl?: string; + readonly isReadOnly: boolean; +} + +export interface Worktree { + readonly name: string; + readonly path: string; + readonly ref: string; + readonly main: boolean; + readonly detached: boolean; +} + +export const enum Status { + INDEX_MODIFIED, + INDEX_ADDED, + INDEX_DELETED, + INDEX_RENAMED, + INDEX_COPIED, + + MODIFIED, + DELETED, + UNTRACKED, + IGNORED, + INTENT_TO_ADD, + INTENT_TO_RENAME, + TYPE_CHANGED, + + ADDED_BY_US, + ADDED_BY_THEM, + DELETED_BY_US, + DELETED_BY_THEM, + BOTH_ADDED, + BOTH_DELETED, + BOTH_MODIFIED +} + +export interface Change { + + /** + * Returns either `originalUri` or `renameUri`, depending + * on whether this change is a rename change. When + * in doubt always use `uri` over the other two alternatives. + */ + readonly uri: Uri; + readonly originalUri: Uri; + readonly renameUri: Uri | undefined; + readonly status: Status; +} + +export interface DiffChange extends Change { + readonly insertions: number; + readonly deletions: number; +} + +export type RepositoryKind = 'repository' | 'submodule' | 'worktree'; export interface RepositoryState { + readonly HEAD: Branch | undefined; + readonly refs: Ref[]; + readonly remotes: Remote[]; + readonly submodules: Submodule[]; + readonly worktrees: Worktree[]; + readonly rebaseCommit: Commit | undefined; + + readonly mergeChanges: Change[]; + readonly indexChanges: Change[]; + readonly workingTreeChanges: Change[]; + readonly untrackedChanges: Change[]; + + readonly onDidChange: Event; +} + +export interface RepositoryUIState { + readonly selected: boolean; readonly onDidChange: Event; } +export interface RepositoryAccessDetails { + readonly rootUri: Uri; + readonly lastAccessTime: number; +} + +/** + * Log options. + */ +export interface LogOptions { + /** Max number of log entries to retrieve. If not specified, the default is 32. */ + readonly maxEntries?: number; + readonly path?: string; + /** A commit range, such as "0a47c67f0fb52dd11562af48658bc1dff1d75a38..0bb4bdea78e1db44d728fd6894720071e303304f" */ + readonly range?: string; + readonly reverse?: boolean; + readonly sortByAuthorDate?: boolean; + readonly shortStats?: boolean; + readonly author?: string; + readonly grep?: string; + readonly refNames?: string[]; + readonly maxParents?: number; + readonly skip?: number; +} + +export interface CommitOptions { + all?: boolean | 'tracked'; + amend?: boolean; + signoff?: boolean; + /** + * true - sign the commit + * false - do not sign the commit + * undefined - use the repository/global git config + */ + signCommit?: boolean; + empty?: boolean; + noVerify?: boolean; + requireUserConfig?: boolean; + useEditor?: boolean; + verbose?: boolean; + /** + * string - execute the specified command after the commit operation + * undefined - execute the command specified in git.postCommitCommand + * after the commit operation + * null - do not execute any command after the commit operation + */ + postCommitCommand?: string | null; +} + +export interface FetchOptions { + remote?: string; + ref?: string; + all?: boolean; + prune?: boolean; + depth?: number; +} + +export interface InitOptions { + defaultBranch?: string; +} + +export interface CloneOptions { + parentPath?: Uri; + /** + * ref is only used if the repository cache is missed. + */ + ref?: string; + recursive?: boolean; + /** + * If no postCloneAction is provided, then the users setting for git.openAfterClone is used. + */ + postCloneAction?: 'none'; +} + +export interface RefQuery { + readonly contains?: string; + readonly count?: number; + readonly pattern?: string | string[]; + readonly sort?: 'alphabetically' | 'committerdate' | 'creatordate'; +} + +export interface BranchQuery extends RefQuery { + readonly remote?: boolean; +} + export interface Repository { + readonly rootUri: Uri; + readonly inputBox: InputBox; readonly state: RepositoryState; + readonly ui: RepositoryUIState; + readonly kind: RepositoryKind; + + readonly onDidCommit: Event; + readonly onDidCheckout: Event; + + getConfigs(): Promise<{ key: string; value: string; }[]>; + getConfig(key: string): Promise; + setConfig(key: string, value: string): Promise; + unsetConfig(key: string): Promise; + getGlobalConfig(key: string): Promise; + + getObjectDetails(treeish: string, path: string): Promise<{ mode: string, object: string, size: number }>; + detectObjectType(object: string): Promise<{ mimetype: string, encoding?: string }>; + buffer(ref: string, path: string): Promise; + show(ref: string, path: string): Promise; + getCommit(ref: string): Promise; + + add(paths: string[]): Promise; + revert(paths: string[]): Promise; + clean(paths: string[]): Promise; + + apply(patch: string, reverse?: boolean): Promise; + apply(patch: string, options?: { allowEmpty?: boolean; reverse?: boolean; threeWay?: boolean; }): Promise; + diff(cached?: boolean): Promise; + diffWithHEAD(): Promise; + diffWithHEAD(path: string): Promise; + diffWithHEADShortStats(path?: string): Promise; + diffWith(ref: string): Promise; + diffWith(ref: string, path: string): Promise; + diffIndexWithHEAD(): Promise; + diffIndexWithHEAD(path: string): Promise; + diffIndexWithHEADShortStats(path?: string): Promise; + diffIndexWith(ref: string): Promise; + diffIndexWith(ref: string, path: string): Promise; + diffBlobs(object1: string, object2: string): Promise; + diffBetween(ref1: string, ref2: string): Promise; + diffBetween(ref1: string, ref2: string, path: string): Promise; + diffBetweenPatch(ref1: string, ref2: string, path?: string): Promise; + diffBetweenWithStats(ref1: string, ref2: string, path?: string): Promise; + + hashObject(data: string): Promise; + + createBranch(name: string, checkout: boolean, ref?: string): Promise; + deleteBranch(name: string, force?: boolean): Promise; + getBranch(name: string): Promise; + getBranches(query: BranchQuery, cancellationToken?: CancellationToken): Promise; + getBranchBase(name: string): Promise; + setBranchUpstream(name: string, upstream: string): Promise; + + checkIgnore(paths: string[]): Promise>; + + getRefs(query: RefQuery, cancellationToken?: CancellationToken): Promise; + + getMergeBase(ref1: string, ref2: string): Promise; + + tag(name: string, message: string, ref?: string | undefined): Promise; + deleteTag(name: string): Promise; + + status(): Promise; + checkout(treeish: string): Promise; + + addRemote(name: string, url: string): Promise; + removeRemote(name: string): Promise; + renameRemote(name: string, newName: string): Promise; + + fetch(options?: FetchOptions): Promise; + fetch(remote?: string, ref?: string, depth?: number): Promise; + pull(unshallow?: boolean): Promise; + push(remoteName?: string, branchName?: string, setUpstream?: boolean, force?: ForcePushMode): Promise; + + blame(path: string): Promise; + log(options?: LogOptions): Promise; + + commit(message: string, opts?: CommitOptions): Promise; + merge(ref: string): Promise; + mergeAbort(): Promise; + + createStash(options?: { message?: string; includeUntracked?: boolean; staged?: boolean }): Promise; + applyStash(index?: number): Promise; + popStash(index?: number): Promise; + dropStash(index?: number): Promise; + + createWorktree(options?: { path?: string; commitish?: string; branch?: string }): Promise; + deleteWorktree(path: string, options?: { force?: boolean }): Promise; + + migrateChanges(sourceRepositoryPath: string, options?: { confirmation?: boolean; deleteFromSource?: boolean; untracked?: boolean }): Promise; +} + +export interface RemoteSource { + readonly name: string; + readonly description?: string; + readonly url: string | string[]; +} + +export interface RemoteSourceProvider { + readonly name: string; + readonly icon?: string; // codicon name + readonly supportsQuery?: boolean; + getRemoteSources(query?: string): ProviderResult; + getBranches?(url: string): ProviderResult; + publishRepository?(repository: Repository): Promise; +} + +export interface RemoteSourcePublisher { + readonly name: string; + readonly icon?: string; // codicon name + publishRepository(repository: Repository): Promise; +} + +export interface Credentials { + readonly username: string; + readonly password: string; +} + +export interface CredentialsProvider { + getCredentials(host: Uri): ProviderResult; +} + +export interface PostCommitCommandsProvider { + getCommands(repository: Repository): Command[]; +} + +export interface PushErrorHandler { + handlePushError(repository: Repository, remote: Remote, refspec: string, error: Error & { gitErrorCode: GitErrorCodes }): Promise; +} + +export interface BranchProtection { + readonly remote: string; + readonly rules: BranchProtectionRule[]; +} + +export interface BranchProtectionRule { + readonly include?: string[]; + readonly exclude?: string[]; +} + +export interface BranchProtectionProvider { + onDidChangeBranchProtection: Event; + provideBranchProtection(): BranchProtection[]; +} + +export interface AvatarQueryCommit { + readonly hash: string; + readonly authorName?: string; + readonly authorEmail?: string; +} + +export interface AvatarQuery { + readonly commits: AvatarQueryCommit[]; + readonly size: number; +} + +export interface SourceControlHistoryItemDetailsProvider { + provideAvatar(repository: Repository, query: AvatarQuery): ProviderResult>; + provideHoverCommands(repository: Repository): ProviderResult; + provideMessageLinks(repository: Repository, message: string): ProviderResult; +} + +export type APIState = 'uninitialized' | 'initialized'; + +export interface PublishEvent { + repository: Repository; + branch?: string; } export interface API { + readonly state: APIState; + readonly onDidChangeState: Event; + readonly onDidPublish: Event; + readonly git: Git; readonly repositories: Repository[]; + readonly recentRepositories: Iterable; readonly onDidOpenRepository: Event; + readonly onDidCloseRepository: Event; + + toGitUri(uri: Uri, ref: string): Uri; + getRepository(uri: Uri): Repository | null; + getRepositoryRoot(uri: Uri): Promise; + getRepositoryWorkspace(uri: Uri): Promise; + init(root: Uri, options?: InitOptions): Promise; + /** + * Checks the cache of known cloned repositories, and clones if the repository is not found. + * Make sure to pass `postCloneAction` 'none' if you want to have the uri where you can find the repository returned. + * @returns The URI of a folder or workspace file which, when opened, will open the cloned repository. + */ + clone(uri: Uri, options?: CloneOptions): Promise; + openRepository(root: Uri): Promise; + + registerRemoteSourcePublisher(publisher: RemoteSourcePublisher): Disposable; + registerRemoteSourceProvider(provider: RemoteSourceProvider): Disposable; + registerCredentialsProvider(provider: CredentialsProvider): Disposable; + registerPostCommitCommandsProvider(provider: PostCommitCommandsProvider): Disposable; + registerPushErrorHandler(handler: PushErrorHandler): Disposable; + registerBranchProtectionProvider(root: Uri, provider: BranchProtectionProvider): Disposable; + registerSourceControlHistoryItemDetailsProvider(provider: SourceControlHistoryItemDetailsProvider): Disposable; } export interface GitExtension { + readonly enabled: boolean; readonly onDidChangeEnablement: Event; + + /** + * Returns a specific API version. + * + * Throws error if git extension is disabled. You can listen to the + * [GitExtension.onDidChangeEnablement](#GitExtension.onDidChangeEnablement) event + * to know when the extension becomes enabled/disabled. + * + * @param version Version number. + * @returns API instance + */ getAPI(version: 1): API; } + +export const enum GitErrorCodes { + BadConfigFile = 'BadConfigFile', + BadRevision = 'BadRevision', + AuthenticationFailed = 'AuthenticationFailed', + NoUserNameConfigured = 'NoUserNameConfigured', + NoUserEmailConfigured = 'NoUserEmailConfigured', + NoRemoteRepositorySpecified = 'NoRemoteRepositorySpecified', + NotAGitRepository = 'NotAGitRepository', + NotASafeGitRepository = 'NotASafeGitRepository', + NotAtRepositoryRoot = 'NotAtRepositoryRoot', + Conflict = 'Conflict', + StashConflict = 'StashConflict', + UnmergedChanges = 'UnmergedChanges', + PushRejected = 'PushRejected', + ForcePushWithLeaseRejected = 'ForcePushWithLeaseRejected', + ForcePushWithLeaseIfIncludesRejected = 'ForcePushWithLeaseIfIncludesRejected', + RemoteConnectionError = 'RemoteConnectionError', + DirtyWorkTree = 'DirtyWorkTree', + CantOpenResource = 'CantOpenResource', + GitNotFound = 'GitNotFound', + CantCreatePipe = 'CantCreatePipe', + PermissionDenied = 'PermissionDenied', + CantAccessRemote = 'CantAccessRemote', + RepositoryNotFound = 'RepositoryNotFound', + RepositoryIsLocked = 'RepositoryIsLocked', + BranchNotFullyMerged = 'BranchNotFullyMerged', + NoRemoteReference = 'NoRemoteReference', + InvalidBranchName = 'InvalidBranchName', + BranchAlreadyExists = 'BranchAlreadyExists', + NoLocalChanges = 'NoLocalChanges', + NoStashFound = 'NoStashFound', + LocalChangesOverwritten = 'LocalChangesOverwritten', + NoUpstreamBranch = 'NoUpstreamBranch', + IsInSubmodule = 'IsInSubmodule', + WrongCase = 'WrongCase', + CantLockRef = 'CantLockRef', + CantRebaseMultipleBranches = 'CantRebaseMultipleBranches', + PatchDoesNotApply = 'PatchDoesNotApply', + NoPathFound = 'NoPathFound', + UnknownPath = 'UnknownPath', + EmptyCommitMessage = 'EmptyCommitMessage', + BranchFastForwardRejected = 'BranchFastForwardRejected', + BranchNotYetBorn = 'BranchNotYetBorn', + TagConflict = 'TagConflict', + CherryPickEmpty = 'CherryPickEmpty', + CherryPickConflict = 'CherryPickConflict', + WorktreeContainsChanges = 'WorktreeContainsChanges', + WorktreeAlreadyExists = 'WorktreeAlreadyExists', + WorktreeBranchAlreadyUsed = 'WorktreeBranchAlreadyUsed' +}