mirror of
https://github.com/Microsoft/vscode
synced 2024-10-14 15:29:54 +00:00
git: PushErrorHandler
This commit is contained in:
parent
8b531a2246
commit
1ab3137674
|
@ -5,7 +5,7 @@
|
|||
|
||||
import { Model } from '../model';
|
||||
import { Repository as BaseRepository, Resource } from '../repository';
|
||||
import { InputBox, Git, API, Repository, Remote, RepositoryState, Branch, Ref, Submodule, Commit, Change, RepositoryUIState, Status, LogOptions, APIState, CommitOptions, RefType, RemoteSourceProvider, CredentialsProvider, BranchQuery } from './git';
|
||||
import { InputBox, Git, API, Repository, Remote, RepositoryState, Branch, Ref, Submodule, Commit, Change, RepositoryUIState, Status, LogOptions, APIState, CommitOptions, RefType, RemoteSourceProvider, CredentialsProvider, BranchQuery, PushErrorHandler } from './git';
|
||||
import { Event, SourceControlInputBox, Uri, SourceControl, Disposable, commands } from 'vscode';
|
||||
import { mapEvent } from '../util';
|
||||
import { toGitUri } from '../uri';
|
||||
|
@ -273,6 +273,10 @@ export class ApiImpl implements API {
|
|||
return this._model.registerCredentialsProvider(provider);
|
||||
}
|
||||
|
||||
registerPushErrorHandler(handler: PushErrorHandler): Disposable {
|
||||
return this._model.registerPushErrorHandler(handler);
|
||||
}
|
||||
|
||||
constructor(private _model: Model) { }
|
||||
}
|
||||
|
||||
|
|
6
extensions/git/src/api/git.d.ts
vendored
6
extensions/git/src/api/git.d.ts
vendored
|
@ -223,6 +223,10 @@ export interface CredentialsProvider {
|
|||
getCredentials(host: Uri): ProviderResult<Credentials>;
|
||||
}
|
||||
|
||||
export interface PushErrorHandler {
|
||||
handlePushError(repository: Repository, remote: Remote, refspec: string, error: Error & { gitErrorCode: GitErrorCodes }): Promise<boolean>;
|
||||
}
|
||||
|
||||
export type APIState = 'uninitialized' | 'initialized';
|
||||
|
||||
export interface API {
|
||||
|
@ -239,6 +243,7 @@ export interface API {
|
|||
|
||||
registerRemoteSourceProvider(provider: RemoteSourceProvider): Disposable;
|
||||
registerCredentialsProvider(provider: CredentialsProvider): Disposable;
|
||||
registerPushErrorHandler(handler: PushErrorHandler): Disposable;
|
||||
}
|
||||
|
||||
export interface GitExtension {
|
||||
|
@ -276,6 +281,7 @@ export const enum GitErrorCodes {
|
|||
CantOpenResource = 'CantOpenResource',
|
||||
GitNotFound = 'GitNotFound',
|
||||
CantCreatePipe = 'CantCreatePipe',
|
||||
PermissionDenied = 'PermissionDenied',
|
||||
CantAccessRemote = 'CantAccessRemote',
|
||||
RepositoryNotFound = 'RepositoryNotFound',
|
||||
RepositoryIsLocked = 'RepositoryIsLocked',
|
||||
|
|
|
@ -1627,6 +1627,8 @@ export class Repository {
|
|||
err.gitErrorCode = GitErrorCodes.RemoteConnectionError;
|
||||
} else if (/^fatal: The current branch .* has no upstream branch/.test(err.stderr || '')) {
|
||||
err.gitErrorCode = GitErrorCodes.NoUpstreamBranch;
|
||||
} else if (/Permission.*denied/.test(err.stderr || '')) {
|
||||
err.gitErrorCode = GitErrorCodes.PermissionDenied;
|
||||
}
|
||||
|
||||
throw err;
|
||||
|
|
|
@ -12,9 +12,10 @@ import * as path from 'path';
|
|||
import * as fs from 'fs';
|
||||
import * as nls from 'vscode-nls';
|
||||
import { fromGitUri } from './uri';
|
||||
import { APIState as State, RemoteSourceProvider, CredentialsProvider } from './api/git';
|
||||
import { APIState as State, RemoteSourceProvider, CredentialsProvider, PushErrorHandler } from './api/git';
|
||||
import { Askpass } from './askpass';
|
||||
import { IRemoteSourceProviderRegistry } from './remoteProvider';
|
||||
import { IPushErrorHandlerRegistry } from './pushError';
|
||||
|
||||
const localize = nls.loadMessageBundle();
|
||||
|
||||
|
@ -46,7 +47,7 @@ interface OpenRepository extends Disposable {
|
|||
repository: Repository;
|
||||
}
|
||||
|
||||
export class Model implements IRemoteSourceProviderRegistry {
|
||||
export class Model implements IRemoteSourceProviderRegistry, IPushErrorHandlerRegistry {
|
||||
|
||||
private _onDidOpenRepository = new EventEmitter<Repository>();
|
||||
readonly onDidOpenRepository: Event<Repository> = this._onDidOpenRepository.event;
|
||||
|
@ -94,6 +95,8 @@ export class Model implements IRemoteSourceProviderRegistry {
|
|||
private _onDidRemoveRemoteSourceProvider = new EventEmitter<RemoteSourceProvider>();
|
||||
readonly onDidRemoveRemoteSourceProvider = this._onDidRemoveRemoteSourceProvider.event;
|
||||
|
||||
private pushErrorHandlers = new Set<PushErrorHandler>();
|
||||
|
||||
private disposables: Disposable[] = [];
|
||||
|
||||
constructor(readonly git: Git, private readonly askpass: Askpass, private globalState: Memento, private outputChannel: OutputChannel) {
|
||||
|
@ -269,7 +272,7 @@ export class Model implements IRemoteSourceProviderRegistry {
|
|||
}
|
||||
|
||||
const dotGit = await this.git.getRepositoryDotGit(repositoryRoot);
|
||||
const repository = new Repository(this.git.open(repositoryRoot, dotGit), this, this.globalState, this.outputChannel);
|
||||
const repository = new Repository(this.git.open(repositoryRoot, dotGit), this, this, this.globalState, this.outputChannel);
|
||||
|
||||
this.open(repository);
|
||||
await repository.status();
|
||||
|
@ -485,6 +488,15 @@ export class Model implements IRemoteSourceProviderRegistry {
|
|||
return [...this.remoteSourceProviders.values()];
|
||||
}
|
||||
|
||||
registerPushErrorHandler(handler: PushErrorHandler): Disposable {
|
||||
this.pushErrorHandlers.add(handler);
|
||||
return toDisposable(() => this.pushErrorHandlers.delete(handler));
|
||||
}
|
||||
|
||||
getPushErrorHandlers(): PushErrorHandler[] {
|
||||
return [...this.pushErrorHandlers];
|
||||
}
|
||||
|
||||
dispose(): void {
|
||||
const openRepositories = [...this.openRepositories];
|
||||
openRepositories.forEach(r => r.dispose());
|
||||
|
|
12
extensions/git/src/pushError.ts
Normal file
12
extensions/git/src/pushError.ts
Normal file
|
@ -0,0 +1,12 @@
|
|||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { Disposable } from 'vscode';
|
||||
import { PushErrorHandler } from './api/git';
|
||||
|
||||
export interface IPushErrorHandlerRegistry {
|
||||
registerPushErrorHandler(provider: PushErrorHandler): Disposable;
|
||||
getPushErrorHandlers(): PushErrorHandler[];
|
||||
}
|
|
@ -17,6 +17,8 @@ import { anyEvent, combinedDisposable, debounceEvent, dispose, EmptyDisposable,
|
|||
import { IFileWatcher, watch } from './watch';
|
||||
import { Log, LogLevel } from './log';
|
||||
import { IRemoteSourceProviderRegistry } from './remoteProvider';
|
||||
import { IPushErrorHandlerRegistry } from './pushError';
|
||||
import { ApiRepository } from './api/api1';
|
||||
|
||||
const timeout = (millis: number) => new Promise(c => setTimeout(c, millis));
|
||||
|
||||
|
@ -683,6 +685,7 @@ export class Repository implements Disposable {
|
|||
constructor(
|
||||
private readonly repository: BaseRepository,
|
||||
remoteSourceProviderRegistry: IRemoteSourceProviderRegistry,
|
||||
private pushErrorHandlerRegistry: IPushErrorHandlerRegistry,
|
||||
globalState: Memento,
|
||||
outputChannel: OutputChannel
|
||||
) {
|
||||
|
@ -1181,15 +1184,15 @@ export class Repository implements Disposable {
|
|||
branch = `${head.name}:${head.upstream.name}`;
|
||||
}
|
||||
|
||||
await this.run(Operation.Push, () => this.repository.push(remote, branch, undefined, undefined, forcePushMode));
|
||||
await this.run(Operation.Push, () => this._push(remote, branch, undefined, undefined, forcePushMode));
|
||||
}
|
||||
|
||||
async pushTo(remote?: string, name?: string, setUpstream: boolean = false, forcePushMode?: ForcePushMode): Promise<void> {
|
||||
await this.run(Operation.Push, () => this.repository.push(remote, name, setUpstream, undefined, forcePushMode));
|
||||
await this.run(Operation.Push, () => this._push(remote, name, setUpstream, undefined, forcePushMode));
|
||||
}
|
||||
|
||||
async pushFollowTags(remote?: string, forcePushMode?: ForcePushMode): Promise<void> {
|
||||
await this.run(Operation.Push, () => this.repository.push(remote, undefined, false, true, forcePushMode));
|
||||
await this.run(Operation.Push, () => this._push(remote, undefined, false, true, forcePushMode));
|
||||
}
|
||||
|
||||
async blame(path: string): Promise<string> {
|
||||
|
@ -1249,7 +1252,7 @@ export class Repository implements Disposable {
|
|||
const shouldPush = this.HEAD && (typeof this.HEAD.ahead === 'number' ? this.HEAD.ahead > 0 : true);
|
||||
|
||||
if (shouldPush) {
|
||||
await this.repository.push(remoteName, pushBranch);
|
||||
await this._push(remoteName, pushBranch);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
@ -1411,6 +1414,31 @@ export class Repository implements Disposable {
|
|||
return ignored;
|
||||
}
|
||||
|
||||
private async _push(remote?: string, refspec?: string, setUpstream: boolean = false, tags = false, forcePushMode?: ForcePushMode): Promise<void> {
|
||||
try {
|
||||
await this.repository.push(remote, refspec, setUpstream, tags, forcePushMode);
|
||||
} catch (err) {
|
||||
if (!remote || !refspec) {
|
||||
throw err;
|
||||
}
|
||||
|
||||
const repository = new ApiRepository(this);
|
||||
const remoteObj = repository.state.remotes.find(r => r.name === remote);
|
||||
|
||||
if (!remoteObj) {
|
||||
throw err;
|
||||
}
|
||||
|
||||
for (const handler of this.pushErrorHandlerRegistry.getPushErrorHandlers()) {
|
||||
if (await handler.handlePushError(repository, remoteObj, refspec, err)) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
throw err;
|
||||
}
|
||||
}
|
||||
|
||||
private async run<T>(operation: Operation, runOperation: () => Promise<T> = () => Promise.resolve<any>(null)): Promise<T> {
|
||||
if (this.state !== RepositoryState.Idle) {
|
||||
throw new Error('Repository not initialized');
|
||||
|
|
Loading…
Reference in a new issue