mirror of
https://github.com/ckaczor/azuredatastudio.git
synced 2026-02-16 10:58:30 -05:00
Merge VS Code 1.30.1 (#4092)
This commit is contained in:
@@ -1,65 +0,0 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
'use strict';
|
||||
|
||||
import { Model } from './model';
|
||||
import { Repository as ModelRepository } from './repository';
|
||||
import { Uri, SourceControlInputBox } from 'vscode';
|
||||
|
||||
export interface InputBox {
|
||||
value: string;
|
||||
}
|
||||
|
||||
export class InputBoxImpl implements InputBox {
|
||||
set value(value: string) { this.inputBox.value = value; }
|
||||
get value(): string { return this.inputBox.value; }
|
||||
constructor(private inputBox: SourceControlInputBox) { }
|
||||
}
|
||||
|
||||
export interface Repository {
|
||||
readonly rootUri: Uri;
|
||||
readonly inputBox: InputBox;
|
||||
}
|
||||
|
||||
export class RepositoryImpl implements Repository {
|
||||
|
||||
readonly rootUri: Uri;
|
||||
readonly inputBox: InputBox;
|
||||
|
||||
constructor(repository: ModelRepository) {
|
||||
this.rootUri = Uri.file(repository.root);
|
||||
this.inputBox = new InputBoxImpl(repository.inputBox);
|
||||
}
|
||||
}
|
||||
|
||||
export interface API {
|
||||
getRepositories(): Promise<Repository[]>;
|
||||
getGitPath(): Promise<string>;
|
||||
}
|
||||
|
||||
export class APIImpl implements API {
|
||||
|
||||
constructor(private model: Model) { }
|
||||
|
||||
async getGitPath(): Promise<string> {
|
||||
return this.model.git.path;
|
||||
}
|
||||
|
||||
async getRepositories(): Promise<Repository[]> {
|
||||
return this.model.repositories.map(repository => new RepositoryImpl(repository));
|
||||
}
|
||||
}
|
||||
|
||||
export class NoopAPIImpl implements API {
|
||||
|
||||
async getGitPath(): Promise<string> {
|
||||
throw new Error('Git model not found');
|
||||
}
|
||||
|
||||
async getRepositories(): Promise<Repository[]> {
|
||||
throw new Error('Git model not found');
|
||||
}
|
||||
}
|
||||
208
extensions/git/src/api/api1.ts
Normal file
208
extensions/git/src/api/api1.ts
Normal file
@@ -0,0 +1,208 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
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 } from './git';
|
||||
import { Event, SourceControlInputBox, Uri, SourceControl } from 'vscode';
|
||||
import { mapEvent } from '../util';
|
||||
|
||||
class ApiInputBox implements InputBox {
|
||||
set value(value: string) { this._inputBox.value = value; }
|
||||
get value(): string { return this._inputBox.value; }
|
||||
constructor(private _inputBox: SourceControlInputBox) { }
|
||||
}
|
||||
|
||||
export class ApiChange implements Change {
|
||||
|
||||
get uri(): Uri { return this.resource.resourceUri; }
|
||||
get originalUri(): Uri { return this.resource.original; }
|
||||
get renameUri(): Uri | undefined { return this.resource.renameResourceUri; }
|
||||
get status(): Status { return this.resource.type; }
|
||||
|
||||
constructor(private readonly resource: Resource) { }
|
||||
}
|
||||
|
||||
export class ApiRepositoryState implements RepositoryState {
|
||||
|
||||
get HEAD(): Branch | undefined { return this._repository.HEAD; }
|
||||
get refs(): Ref[] { return [...this._repository.refs]; }
|
||||
get remotes(): Remote[] { return [...this._repository.remotes]; }
|
||||
get submodules(): Submodule[] { return [...this._repository.submodules]; }
|
||||
get rebaseCommit(): Commit | undefined { return this._repository.rebaseCommit; }
|
||||
|
||||
get mergeChanges(): Change[] { return this._repository.mergeGroup.resourceStates.map(r => new ApiChange(r)); }
|
||||
get indexChanges(): Change[] { return this._repository.indexGroup.resourceStates.map(r => new ApiChange(r)); }
|
||||
get workingTreeChanges(): Change[] { return this._repository.workingTreeGroup.resourceStates.map(r => new ApiChange(r)); }
|
||||
|
||||
readonly onDidChange: Event<void> = this._repository.onDidRunGitStatus;
|
||||
|
||||
constructor(private _repository: BaseRepository) { }
|
||||
}
|
||||
|
||||
export class ApiRepositoryUIState implements RepositoryUIState {
|
||||
|
||||
get selected(): boolean { return this._sourceControl.selected; }
|
||||
|
||||
readonly onDidChange: Event<void> = mapEvent<boolean, void>(this._sourceControl.onDidChangeSelection, () => null);
|
||||
|
||||
constructor(private _sourceControl: SourceControl) { }
|
||||
}
|
||||
|
||||
export class ApiRepository implements Repository {
|
||||
|
||||
readonly rootUri: Uri = Uri.file(this._repository.root);
|
||||
readonly inputBox: InputBox = new ApiInputBox(this._repository.inputBox);
|
||||
readonly state: RepositoryState = new ApiRepositoryState(this._repository);
|
||||
readonly ui: RepositoryUIState = new ApiRepositoryUIState(this._repository.sourceControl);
|
||||
|
||||
constructor(private _repository: BaseRepository) { }
|
||||
|
||||
apply(patch: string, reverse?: boolean): Promise<void> {
|
||||
return this._repository.apply(patch, reverse);
|
||||
}
|
||||
|
||||
getConfigs(): Promise<{ key: string; value: string; }[]> {
|
||||
return this._repository.getConfigs();
|
||||
}
|
||||
|
||||
getConfig(key: string): Promise<string> {
|
||||
return this._repository.getConfig(key);
|
||||
}
|
||||
|
||||
setConfig(key: string, value: string): Promise<string> {
|
||||
return this._repository.setConfig(key, value);
|
||||
}
|
||||
|
||||
getObjectDetails(treeish: string, path: string): Promise<{ mode: string; object: string; size: number; }> {
|
||||
return this._repository.getObjectDetails(treeish, path);
|
||||
}
|
||||
|
||||
detectObjectType(object: string): Promise<{ mimetype: string, encoding?: string }> {
|
||||
return this._repository.detectObjectType(object);
|
||||
}
|
||||
|
||||
buffer(ref: string, filePath: string): Promise<Buffer> {
|
||||
return this._repository.buffer(ref, filePath);
|
||||
}
|
||||
|
||||
show(ref: string, path: string): Promise<string> {
|
||||
return this._repository.show(ref, path);
|
||||
}
|
||||
|
||||
getCommit(ref: string): Promise<Commit> {
|
||||
return this._repository.getCommit(ref);
|
||||
}
|
||||
|
||||
clean(paths: string[]) {
|
||||
return this._repository.clean(paths.map(p => Uri.file(p)));
|
||||
}
|
||||
|
||||
diff(cached?: boolean) {
|
||||
return this._repository.diff(cached);
|
||||
}
|
||||
|
||||
diffWithHEAD(path: string): Promise<string> {
|
||||
return this._repository.diffWithHEAD(path);
|
||||
}
|
||||
|
||||
diffWith(ref: string, path: string): Promise<string> {
|
||||
return this._repository.diffWith(ref, path);
|
||||
}
|
||||
|
||||
diffIndexWithHEAD(path: string): Promise<string> {
|
||||
return this._repository.diffIndexWithHEAD(path);
|
||||
}
|
||||
|
||||
diffIndexWith(ref: string, path: string): Promise<string> {
|
||||
return this._repository.diffIndexWith(ref, path);
|
||||
}
|
||||
|
||||
diffBlobs(object1: string, object2: string): Promise<string> {
|
||||
return this._repository.diffBlobs(object1, object2);
|
||||
}
|
||||
|
||||
diffBetween(ref1: string, ref2: string, path: string): Promise<string> {
|
||||
return this._repository.diffBetween(ref1, ref2, path);
|
||||
}
|
||||
|
||||
hashObject(data: string): Promise<string> {
|
||||
return this._repository.hashObject(data);
|
||||
}
|
||||
|
||||
createBranch(name: string, checkout: boolean, ref?: string | undefined): Promise<void> {
|
||||
return this._repository.branch(name, checkout, ref);
|
||||
}
|
||||
|
||||
deleteBranch(name: string, force?: boolean): Promise<void> {
|
||||
return this._repository.deleteBranch(name, force);
|
||||
}
|
||||
|
||||
getBranch(name: string): Promise<Branch> {
|
||||
return this._repository.getBranch(name);
|
||||
}
|
||||
|
||||
setBranchUpstream(name: string, upstream: string): Promise<void> {
|
||||
return this._repository.setBranchUpstream(name, upstream);
|
||||
}
|
||||
|
||||
getMergeBase(ref1: string, ref2: string): Promise<string> {
|
||||
return this._repository.getMergeBase(ref1, ref2);
|
||||
}
|
||||
|
||||
status(): Promise<void> {
|
||||
return this._repository.status();
|
||||
}
|
||||
|
||||
checkout(treeish: string): Promise<void> {
|
||||
return this._repository.checkout(treeish);
|
||||
}
|
||||
|
||||
addRemote(name: string, url: string): Promise<void> {
|
||||
return this._repository.addRemote(name, url);
|
||||
}
|
||||
|
||||
removeRemote(name: string): Promise<void> {
|
||||
return this._repository.removeRemote(name);
|
||||
}
|
||||
|
||||
fetch(remote?: string | undefined, ref?: string | undefined): Promise<void> {
|
||||
return this._repository.fetch(remote, ref);
|
||||
}
|
||||
|
||||
pull(): Promise<void> {
|
||||
return this._repository.pull();
|
||||
}
|
||||
|
||||
push(remoteName?: string, branchName?: string, setUpstream: boolean = false): Promise<void> {
|
||||
return this._repository.pushTo(remoteName, branchName, setUpstream);
|
||||
}
|
||||
}
|
||||
|
||||
export class ApiGit implements Git {
|
||||
|
||||
get path(): string { return this._model.git.path; }
|
||||
|
||||
constructor(private _model: Model) { }
|
||||
}
|
||||
|
||||
export class ApiImpl implements API {
|
||||
|
||||
readonly git = new ApiGit(this._model);
|
||||
|
||||
get onDidOpenRepository(): Event<Repository> {
|
||||
return mapEvent(this._model.onDidOpenRepository, r => new ApiRepository(r));
|
||||
}
|
||||
|
||||
get onDidCloseRepository(): Event<Repository> {
|
||||
return mapEvent(this._model.onDidCloseRepository, r => new ApiRepository(r));
|
||||
}
|
||||
|
||||
get repositories(): Repository[] {
|
||||
return this._model.repositories.map(r => new ApiRepository(r));
|
||||
}
|
||||
|
||||
constructor(private _model: Model) { }
|
||||
}
|
||||
76
extensions/git/src/api/extension.ts
Normal file
76
extensions/git/src/api/extension.ts
Normal file
@@ -0,0 +1,76 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { Model } from '../model';
|
||||
import { GitExtension, Repository, API } from './git';
|
||||
import { ApiRepository, ApiImpl } from './api1';
|
||||
import { Event, EventEmitter } from 'vscode';
|
||||
import { latchEvent } from '../util';
|
||||
|
||||
export function deprecated(_target: any, key: string, descriptor: any): void {
|
||||
if (typeof descriptor.value !== 'function') {
|
||||
throw new Error('not supported');
|
||||
}
|
||||
|
||||
const fn = descriptor.value;
|
||||
descriptor.value = function () {
|
||||
console.warn(`Git extension API method '${key}' is deprecated.`);
|
||||
return fn.apply(this, arguments);
|
||||
};
|
||||
}
|
||||
|
||||
export class GitExtensionImpl implements GitExtension {
|
||||
|
||||
enabled: boolean = false;
|
||||
|
||||
private _onDidChangeEnablement = new EventEmitter<boolean>();
|
||||
readonly onDidChangeEnablement: Event<boolean> = latchEvent(this._onDidChangeEnablement.event);
|
||||
|
||||
private _model: Model | undefined = undefined;
|
||||
|
||||
set model(model: Model | undefined) {
|
||||
this._model = model;
|
||||
|
||||
this.enabled = !!model;
|
||||
this._onDidChangeEnablement.fire(this.enabled);
|
||||
}
|
||||
|
||||
constructor(model?: Model) {
|
||||
if (model) {
|
||||
this.enabled = true;
|
||||
this._model = model;
|
||||
}
|
||||
}
|
||||
|
||||
@deprecated
|
||||
async getGitPath(): Promise<string> {
|
||||
if (!this._model) {
|
||||
throw new Error('Git model not found');
|
||||
}
|
||||
|
||||
return this._model.git.path;
|
||||
}
|
||||
|
||||
@deprecated
|
||||
async getRepositories(): Promise<Repository[]> {
|
||||
if (!this._model) {
|
||||
throw new Error('Git model not found');
|
||||
}
|
||||
|
||||
return this._model.repositories.map(repository => new ApiRepository(repository));
|
||||
}
|
||||
|
||||
getAPI(version: number): API {
|
||||
if (!this._model) {
|
||||
throw new Error('Git model not found');
|
||||
}
|
||||
|
||||
if (version !== 1) {
|
||||
throw new Error(`No API version ${version} found.`);
|
||||
}
|
||||
|
||||
return new ApiImpl(this._model);
|
||||
}
|
||||
}
|
||||
217
extensions/git/src/api/git.d.ts
vendored
Normal file
217
extensions/git/src/api/git.d.ts
vendored
Normal file
@@ -0,0 +1,217 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { Uri, SourceControlInputBox, Event, CancellationToken } from 'vscode';
|
||||
|
||||
export interface Git {
|
||||
readonly path: string;
|
||||
}
|
||||
|
||||
export interface InputBox {
|
||||
value: string;
|
||||
}
|
||||
|
||||
export const enum RefType {
|
||||
Head,
|
||||
RemoteHead,
|
||||
Tag
|
||||
}
|
||||
|
||||
export interface Ref {
|
||||
readonly type: RefType;
|
||||
readonly name?: string;
|
||||
readonly commit?: string;
|
||||
readonly remote?: string;
|
||||
}
|
||||
|
||||
export interface UpstreamRef {
|
||||
readonly remote: string;
|
||||
readonly name: string;
|
||||
}
|
||||
|
||||
export interface Branch extends Ref {
|
||||
readonly upstream?: UpstreamRef;
|
||||
readonly ahead?: number;
|
||||
readonly behind?: number;
|
||||
}
|
||||
|
||||
export interface Commit {
|
||||
readonly hash: string;
|
||||
readonly message: string;
|
||||
readonly parents: string[];
|
||||
}
|
||||
|
||||
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 const enum Status {
|
||||
INDEX_MODIFIED,
|
||||
INDEX_ADDED,
|
||||
INDEX_DELETED,
|
||||
INDEX_RENAMED,
|
||||
INDEX_COPIED,
|
||||
|
||||
MODIFIED,
|
||||
DELETED,
|
||||
UNTRACKED,
|
||||
IGNORED,
|
||||
|
||||
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 RepositoryState {
|
||||
readonly HEAD: Branch | undefined;
|
||||
readonly refs: Ref[];
|
||||
readonly remotes: Remote[];
|
||||
readonly submodules: Submodule[];
|
||||
readonly rebaseCommit: Commit | undefined;
|
||||
|
||||
readonly mergeChanges: Change[];
|
||||
readonly indexChanges: Change[];
|
||||
readonly workingTreeChanges: Change[];
|
||||
|
||||
readonly onDidChange: Event<void>;
|
||||
}
|
||||
|
||||
export interface RepositoryUIState {
|
||||
readonly selected: boolean;
|
||||
readonly onDidChange: Event<void>;
|
||||
}
|
||||
|
||||
export interface Repository {
|
||||
|
||||
readonly rootUri: Uri;
|
||||
readonly inputBox: InputBox;
|
||||
readonly state: RepositoryState;
|
||||
readonly ui: RepositoryUIState;
|
||||
|
||||
getConfigs(): Promise<{ key: string; value: string; }[]>;
|
||||
getConfig(key: string): Promise<string>;
|
||||
setConfig(key: string, value: string): Promise<string>;
|
||||
|
||||
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<Buffer>;
|
||||
show(ref: string, path: string): Promise<string>;
|
||||
getCommit(ref: string): Promise<Commit>;
|
||||
|
||||
clean(paths: string[]): Promise<void>;
|
||||
|
||||
apply(patch: string, reverse?: boolean): Promise<void>;
|
||||
diff(cached?: boolean): Promise<string>;
|
||||
diffWithHEAD(path: string): Promise<string>;
|
||||
diffWith(ref: string, path: string): Promise<string>;
|
||||
diffIndexWithHEAD(path: string): Promise<string>;
|
||||
diffIndexWith(ref: string, path: string): Promise<string>;
|
||||
diffBlobs(object1: string, object2: string): Promise<string>;
|
||||
diffBetween(ref1: string, ref2: string, path: string): Promise<string>;
|
||||
|
||||
hashObject(data: string): Promise<string>;
|
||||
|
||||
createBranch(name: string, checkout: boolean, ref?: string): Promise<void>;
|
||||
deleteBranch(name: string, force?: boolean): Promise<void>;
|
||||
getBranch(name: string): Promise<Branch>;
|
||||
setBranchUpstream(name: string, upstream: string): Promise<void>;
|
||||
|
||||
getMergeBase(ref1: string, ref2: string): Promise<string>;
|
||||
|
||||
status(): Promise<void>;
|
||||
checkout(treeish: string): Promise<void>;
|
||||
|
||||
addRemote(name: string, url: string): Promise<void>;
|
||||
removeRemote(name: string): Promise<void>;
|
||||
|
||||
fetch(remote?: string, ref?: string): Promise<void>;
|
||||
pull(): Promise<void>;
|
||||
push(remoteName?: string, branchName?: string, setUpstream?: boolean): Promise<void>;
|
||||
}
|
||||
|
||||
export interface API {
|
||||
readonly git: Git;
|
||||
readonly repositories: Repository[];
|
||||
readonly onDidOpenRepository: Event<Repository>;
|
||||
readonly onDidCloseRepository: Event<Repository>;
|
||||
}
|
||||
|
||||
export interface GitExtension {
|
||||
|
||||
readonly enabled: boolean;
|
||||
readonly onDidChangeEnablement: Event<boolean>;
|
||||
|
||||
/**
|
||||
* Returns a specific API version.
|
||||
*
|
||||
* Throws error if git extension is disabled. You can listed 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',
|
||||
AuthenticationFailed = 'AuthenticationFailed',
|
||||
NoUserNameConfigured = 'NoUserNameConfigured',
|
||||
NoUserEmailConfigured = 'NoUserEmailConfigured',
|
||||
NoRemoteRepositorySpecified = 'NoRemoteRepositorySpecified',
|
||||
NotAGitRepository = 'NotAGitRepository',
|
||||
NotAtRepositoryRoot = 'NotAtRepositoryRoot',
|
||||
Conflict = 'Conflict',
|
||||
StashConflict = 'StashConflict',
|
||||
UnmergedChanges = 'UnmergedChanges',
|
||||
PushRejected = 'PushRejected',
|
||||
RemoteConnectionError = 'RemoteConnectionError',
|
||||
DirtyWorkTree = 'DirtyWorkTree',
|
||||
CantOpenResource = 'CantOpenResource',
|
||||
GitNotFound = 'GitNotFound',
|
||||
CantCreatePipe = 'CantCreatePipe',
|
||||
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',
|
||||
}
|
||||
@@ -3,8 +3,6 @@
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
'use strict';
|
||||
|
||||
import * as http from 'http';
|
||||
import * as fs from 'fs';
|
||||
import * as nls from 'vscode-nls';
|
||||
|
||||
@@ -3,8 +3,6 @@
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
'use strict';
|
||||
|
||||
import { Disposable, window, InputBoxOptions } from 'vscode';
|
||||
import { denodeify } from './util';
|
||||
import * as path from 'path';
|
||||
|
||||
@@ -3,13 +3,11 @@
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
'use strict';
|
||||
|
||||
import { workspace, Disposable, EventEmitter, Memento, window, MessageItem, ConfigurationTarget } from 'vscode';
|
||||
import { GitErrorCodes } from './git';
|
||||
import { Repository, Operation } from './repository';
|
||||
import { eventToPromise, filterEvent, onceEvent } from './util';
|
||||
import * as nls from 'vscode-nls';
|
||||
import { GitErrorCodes } from './api/git';
|
||||
|
||||
const localize = nls.loadMessageBundle();
|
||||
|
||||
@@ -103,7 +101,7 @@ export class AutoFetcher {
|
||||
}
|
||||
|
||||
try {
|
||||
await this.repository.fetch();
|
||||
await this.repository.fetchDefault();
|
||||
} catch (err) {
|
||||
if (err.gitErrorCode === GitErrorCodes.AuthenticationFailed) {
|
||||
this.disable();
|
||||
|
||||
569
extensions/git/src/commands.ts
Normal file → Executable file
569
extensions/git/src/commands.ts
Normal file → Executable file
@@ -3,11 +3,9 @@
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
'use strict';
|
||||
|
||||
import { Uri, commands, Disposable, window, workspace, QuickPickItem, OutputChannel, Range, WorkspaceEdit, Position, LineChange, SourceControlResourceState, TextDocumentShowOptions, ViewColumn, ProgressLocation, TextEditor, MessageOptions } from 'vscode';
|
||||
import { Ref, RefType, Git, GitErrorCodes, Branch } from './git';
|
||||
import { Repository, Resource, Status, CommitOptions, ResourceGroupType } from './repository';
|
||||
import { Uri, commands, Disposable, window, workspace, QuickPickItem, OutputChannel, Range, WorkspaceEdit, Position, LineChange, SourceControlResourceState, TextDocumentShowOptions, ViewColumn, ProgressLocation, TextEditor, MessageOptions, WorkspaceFolder } from 'vscode';
|
||||
import { Git, CommitOptions, Stash, ForcePushMode } from './git';
|
||||
import { Repository, Resource, ResourceGroupType } from './repository';
|
||||
import { Model } from './model';
|
||||
import { toGitUri, fromGitUri } from './uri';
|
||||
import { grep, isDescendant, pathEquals } from './util';
|
||||
@@ -17,20 +15,20 @@ import { lstat, Stats } from 'fs';
|
||||
import * as os from 'os';
|
||||
import TelemetryReporter from 'vscode-extension-telemetry';
|
||||
import * as nls from 'vscode-nls';
|
||||
import { Ref, RefType, Branch, GitErrorCodes, Status } from './api/git';
|
||||
|
||||
const localize = nls.loadMessageBundle();
|
||||
|
||||
class CheckoutItem implements QuickPickItem {
|
||||
|
||||
protected get shortCommit(): string { return (this.ref.commit || '').substr(0, 8); }
|
||||
protected get treeish(): string | undefined { return this.ref.name; }
|
||||
get label(): string { return this.ref.name || this.shortCommit; }
|
||||
get description(): string { return this.shortCommit; }
|
||||
|
||||
constructor(protected ref: Ref) { }
|
||||
|
||||
async run(repository: Repository): Promise<void> {
|
||||
const ref = this.treeish;
|
||||
const ref = this.ref.name;
|
||||
|
||||
if (!ref) {
|
||||
return;
|
||||
@@ -53,13 +51,12 @@ class CheckoutRemoteHeadItem extends CheckoutItem {
|
||||
return localize('remote branch at', "Remote branch at {0}", this.shortCommit);
|
||||
}
|
||||
|
||||
protected get treeish(): string | undefined {
|
||||
async run(repository: Repository): Promise<void> {
|
||||
if (!this.ref.name) {
|
||||
return;
|
||||
}
|
||||
|
||||
const match = /^[^/]+\/(.*)$/.exec(this.ref.name);
|
||||
return match ? match[1] : this.ref.name;
|
||||
await repository.checkoutTracking(this.ref.name);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -99,6 +96,8 @@ class CreateBranchItem implements QuickPickItem {
|
||||
get label(): string { return localize('create branch', '$(plus) Create new branch'); }
|
||||
get description(): string { return ''; }
|
||||
|
||||
get alwaysShow(): boolean { return true; }
|
||||
|
||||
async run(repository: Repository): Promise<void> {
|
||||
await this.cc.branch(repository);
|
||||
}
|
||||
@@ -119,7 +118,7 @@ interface Command {
|
||||
const Commands: Command[] = [];
|
||||
|
||||
function command(commandId: string, options: CommandOptions = {}): Function {
|
||||
return (target: any, key: string, descriptor: any) => {
|
||||
return (_target: any, key: string, descriptor: any) => {
|
||||
if (!(typeof descriptor.value === 'function')) {
|
||||
throw new Error('not supported');
|
||||
}
|
||||
@@ -137,20 +136,34 @@ const ImageMimetypes = [
|
||||
'image/bmp'
|
||||
];
|
||||
|
||||
async function categorizeResourceByResolution(resources: Resource[]): Promise<{ merge: Resource[], resolved: Resource[], unresolved: Resource[] }> {
|
||||
async function categorizeResourceByResolution(resources: Resource[]): Promise<{ merge: Resource[], resolved: Resource[], unresolved: Resource[], deletionConflicts: Resource[] }> {
|
||||
const selection = resources.filter(s => s instanceof Resource) as Resource[];
|
||||
const merge = selection.filter(s => s.resourceGroupType === ResourceGroupType.Merge);
|
||||
const isBothAddedOrModified = (s: Resource) => s.type === Status.BOTH_MODIFIED || s.type === Status.BOTH_ADDED;
|
||||
const isAnyDeleted = (s: Resource) => s.type === Status.DELETED_BY_THEM || s.type === Status.DELETED_BY_US;
|
||||
const possibleUnresolved = merge.filter(isBothAddedOrModified);
|
||||
const promises = possibleUnresolved.map(s => grep(s.resourceUri.fsPath, /^<{7}|^={7}|^>{7}/));
|
||||
const unresolvedBothModified = await Promise.all<boolean>(promises);
|
||||
const resolved = possibleUnresolved.filter((s, i) => !unresolvedBothModified[i]);
|
||||
const resolved = possibleUnresolved.filter((_s, i) => !unresolvedBothModified[i]);
|
||||
const deletionConflicts = merge.filter(s => isAnyDeleted(s));
|
||||
const unresolved = [
|
||||
...merge.filter(s => !isBothAddedOrModified(s)),
|
||||
...possibleUnresolved.filter((s, i) => unresolvedBothModified[i])
|
||||
...merge.filter(s => !isBothAddedOrModified(s) && !isAnyDeleted(s)),
|
||||
...possibleUnresolved.filter((_s, i) => unresolvedBothModified[i])
|
||||
];
|
||||
|
||||
return { merge, resolved, unresolved };
|
||||
return { merge, resolved, unresolved, deletionConflicts };
|
||||
}
|
||||
|
||||
enum PushType {
|
||||
Push,
|
||||
PushTo,
|
||||
PushTags,
|
||||
}
|
||||
|
||||
interface PushOptions {
|
||||
pushType: PushType;
|
||||
forcePush?: boolean;
|
||||
silent?: boolean;
|
||||
}
|
||||
|
||||
export class CommandCenter {
|
||||
@@ -181,7 +194,20 @@ export class CommandCenter {
|
||||
|
||||
@command('git.openResource')
|
||||
async openResource(resource: Resource): Promise<void> {
|
||||
await this._openResource(resource, undefined, true, false);
|
||||
const repository = this.model.getRepository(resource.resourceUri);
|
||||
|
||||
if (!repository) {
|
||||
return;
|
||||
}
|
||||
|
||||
const config = workspace.getConfiguration('git', Uri.file(repository.root));
|
||||
const openDiffOnClick = config.get<boolean>('openDiffOnClick');
|
||||
|
||||
if (openDiffOnClick) {
|
||||
await this._openResource(resource, undefined, true, false);
|
||||
} else {
|
||||
await this.openFile(resource);
|
||||
}
|
||||
}
|
||||
|
||||
private async _openResource(resource: Resource, preview?: boolean, preserveFocus?: boolean, preserveSelection?: boolean): Promise<void> {
|
||||
@@ -203,7 +229,10 @@ export class CommandCenter {
|
||||
right = toGitUri(resource.resourceUri, resource.resourceGroupType === ResourceGroupType.Index ? 'index' : 'wt', { submoduleOf: repository.root });
|
||||
}
|
||||
} else {
|
||||
left = await this.getLeftResource(resource);
|
||||
if (resource.type !== Status.DELETED_BY_THEM) {
|
||||
left = await this.getLeftResource(resource);
|
||||
}
|
||||
|
||||
right = await this.getRightResource(resource);
|
||||
}
|
||||
|
||||
@@ -230,7 +259,7 @@ export class CommandCenter {
|
||||
}
|
||||
|
||||
if (!left) {
|
||||
await commands.executeCommand<void>('vscode.open', right, opts);
|
||||
await commands.executeCommand<void>('vscode.open', right, opts, title);
|
||||
} else {
|
||||
await commands.executeCommand<void>('vscode.diff', left, right, title, opts);
|
||||
}
|
||||
@@ -287,6 +316,7 @@ export class CommandCenter {
|
||||
case Status.DELETED_BY_THEM:
|
||||
return this.getURI(resource.resourceUri, '');
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
|
||||
private async getRightResource(resource: Resource): Promise<Uri | undefined> {
|
||||
@@ -298,10 +328,15 @@ export class CommandCenter {
|
||||
return this.getURI(resource.resourceUri, '');
|
||||
|
||||
case Status.INDEX_DELETED:
|
||||
case Status.DELETED_BY_THEM:
|
||||
case Status.DELETED:
|
||||
return this.getURI(resource.resourceUri, 'HEAD');
|
||||
|
||||
case Status.DELETED_BY_US:
|
||||
return this.getURI(resource.resourceUri, '~3');
|
||||
|
||||
case Status.DELETED_BY_THEM:
|
||||
return this.getURI(resource.resourceUri, '~2');
|
||||
|
||||
case Status.MODIFIED:
|
||||
case Status.UNTRACKED:
|
||||
case Status.IGNORED:
|
||||
@@ -324,6 +359,7 @@ export class CommandCenter {
|
||||
case Status.BOTH_MODIFIED:
|
||||
return resource.resourceUri;
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
|
||||
private getTitle(resource: Resource): string {
|
||||
@@ -332,13 +368,18 @@ export class CommandCenter {
|
||||
switch (resource.type) {
|
||||
case Status.INDEX_MODIFIED:
|
||||
case Status.INDEX_RENAMED:
|
||||
case Status.DELETED_BY_THEM:
|
||||
return `${basename} (Index)`;
|
||||
|
||||
case Status.MODIFIED:
|
||||
case Status.BOTH_ADDED:
|
||||
case Status.BOTH_MODIFIED:
|
||||
return `${basename} (Working Tree)`;
|
||||
|
||||
case Status.DELETED_BY_US:
|
||||
return `${basename} (Theirs)`;
|
||||
|
||||
case Status.DELETED_BY_THEM:
|
||||
return `${basename} (Ours)`;
|
||||
}
|
||||
|
||||
return '';
|
||||
@@ -454,21 +495,27 @@ export class CommandCenter {
|
||||
|
||||
@command('git.init')
|
||||
async init(): Promise<void> {
|
||||
let path: string | undefined;
|
||||
let repositoryPath: string | undefined = undefined;
|
||||
let askToOpen = true;
|
||||
|
||||
if (workspace.workspaceFolders && workspace.workspaceFolders.length > 1) {
|
||||
if (workspace.workspaceFolders) {
|
||||
const placeHolder = localize('init', "Pick workspace folder to initialize git repo in");
|
||||
const items = workspace.workspaceFolders.map(folder => ({ label: folder.name, description: folder.uri.fsPath, folder }));
|
||||
const pick = { label: localize('choose', "Choose Folder...") };
|
||||
const items: { label: string, folder?: WorkspaceFolder }[] = [
|
||||
...workspace.workspaceFolders.map(folder => ({ label: folder.name, description: folder.uri.fsPath, folder })),
|
||||
pick
|
||||
];
|
||||
const item = await window.showQuickPick(items, { placeHolder, ignoreFocusOut: true });
|
||||
|
||||
if (!item) {
|
||||
return;
|
||||
} else if (item.folder) {
|
||||
repositoryPath = item.folder.uri.fsPath;
|
||||
askToOpen = false;
|
||||
}
|
||||
|
||||
path = item.folder.uri.fsPath;
|
||||
}
|
||||
|
||||
if (!path) {
|
||||
if (!repositoryPath) {
|
||||
const homeUri = Uri.file(os.homedir());
|
||||
const defaultUri = workspace.workspaceFolders && workspace.workspaceFolders.length > 0
|
||||
? Uri.file(workspace.workspaceFolders[0].uri.fsPath)
|
||||
@@ -497,11 +544,40 @@ export class CommandCenter {
|
||||
}
|
||||
}
|
||||
|
||||
path = uri.fsPath;
|
||||
repositoryPath = uri.fsPath;
|
||||
|
||||
if (workspace.workspaceFolders && workspace.workspaceFolders.some(w => w.uri.toString() === uri.toString())) {
|
||||
askToOpen = false;
|
||||
}
|
||||
}
|
||||
|
||||
await this.git.init(path);
|
||||
await this.model.openRepository(path);
|
||||
await this.git.init(repositoryPath);
|
||||
|
||||
const choices = [];
|
||||
let message = localize('proposeopen init', "Would you like to open the initialized repository?");
|
||||
const open = localize('openrepo', "Open Repository");
|
||||
choices.push(open);
|
||||
|
||||
if (!askToOpen) {
|
||||
return;
|
||||
}
|
||||
|
||||
const addToWorkspace = localize('add', "Add to Workspace");
|
||||
if (workspace.workspaceFolders) {
|
||||
message = localize('proposeopen2 init', "Would you like to open the initialized repository, or add it to the current workspace?");
|
||||
choices.push(addToWorkspace);
|
||||
}
|
||||
|
||||
const result = await window.showInformationMessage(message, ...choices);
|
||||
const uri = Uri.file(repositoryPath);
|
||||
|
||||
if (result === open) {
|
||||
commands.executeCommand('vscode.openFolder', uri);
|
||||
} else if (result === addToWorkspace) {
|
||||
workspace.updateWorkspaceFolders(workspace.workspaceFolders!.length, 0, { uri });
|
||||
} else {
|
||||
await this.model.openRepository(repositoryPath);
|
||||
}
|
||||
}
|
||||
|
||||
@command('git.openRepository', { repository: false })
|
||||
@@ -562,22 +638,27 @@ export class CommandCenter {
|
||||
return;
|
||||
}
|
||||
|
||||
const preview = uris.length === 1 ? true : false;
|
||||
const activeTextEditor = window.activeTextEditor;
|
||||
for (const uri of uris) {
|
||||
const opts: TextDocumentShowOptions = {
|
||||
preserveFocus,
|
||||
preview,
|
||||
preview: false,
|
||||
viewColumn: ViewColumn.Active
|
||||
};
|
||||
|
||||
const document = await workspace.openTextDocument(uri);
|
||||
|
||||
// Check if active text editor has same path as other editor. we cannot compare via
|
||||
// URI.toString() here because the schemas can be different. Instead we just go by path.
|
||||
if (activeTextEditor && activeTextEditor.document.uri.path === uri.path) {
|
||||
// preserve not only selection but also visible range
|
||||
opts.selection = activeTextEditor.selection;
|
||||
const previousVisibleRanges = activeTextEditor.visibleRanges;
|
||||
const editor = await window.showTextDocument(document, opts);
|
||||
editor.revealRange(previousVisibleRanges[0]);
|
||||
} else {
|
||||
await window.showTextDocument(document, opts);
|
||||
}
|
||||
|
||||
await commands.executeCommand<void>('vscode.open', uri, opts);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -589,6 +670,7 @@ export class CommandCenter {
|
||||
@command('git.openHEADFile')
|
||||
async openHEADFile(arg?: Resource | Uri): Promise<void> {
|
||||
let resource: Resource | undefined = undefined;
|
||||
const preview = !(arg instanceof Resource);
|
||||
|
||||
if (arg instanceof Resource) {
|
||||
resource = arg;
|
||||
@@ -609,12 +691,18 @@ export class CommandCenter {
|
||||
return;
|
||||
}
|
||||
|
||||
return await commands.executeCommand<void>('vscode.open', HEAD);
|
||||
const opts: TextDocumentShowOptions = {
|
||||
preview
|
||||
};
|
||||
|
||||
return await commands.executeCommand<void>('vscode.open', HEAD, opts);
|
||||
}
|
||||
|
||||
@command('git.openChange')
|
||||
async openChange(arg?: Resource | Uri, ...resourceStates: SourceControlResourceState[]): Promise<void> {
|
||||
const preserveFocus = arg instanceof Resource;
|
||||
const preview = !(arg instanceof Resource);
|
||||
|
||||
const preserveSelection = arg instanceof Uri || !arg;
|
||||
let resources: Resource[] | undefined = undefined;
|
||||
|
||||
@@ -641,7 +729,6 @@ export class CommandCenter {
|
||||
return;
|
||||
}
|
||||
|
||||
const preview = resources.length === 1 ? undefined : false;
|
||||
for (const resource of resources) {
|
||||
await this._openResource(resource, preview, preserveFocus, preserveSelection);
|
||||
}
|
||||
@@ -666,7 +753,7 @@ export class CommandCenter {
|
||||
}
|
||||
|
||||
const selection = resourceStates.filter(s => s instanceof Resource) as Resource[];
|
||||
const { resolved, unresolved } = await categorizeResourceByResolution(selection);
|
||||
const { resolved, unresolved, deletionConflicts } = await categorizeResourceByResolution(selection);
|
||||
|
||||
if (unresolved.length > 0) {
|
||||
const message = unresolved.length > 1
|
||||
@@ -681,6 +768,20 @@ export class CommandCenter {
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
await this.runByRepository(deletionConflicts.map(r => r.resourceUri), async (repository, resources) => {
|
||||
for (const resource of resources) {
|
||||
await this._stageDeletionConflict(repository, resource);
|
||||
}
|
||||
});
|
||||
} catch (err) {
|
||||
if (/Cancelled/.test(err.message)) {
|
||||
return;
|
||||
}
|
||||
|
||||
throw err;
|
||||
}
|
||||
|
||||
const workingTree = selection.filter(s => s.resourceGroupType === ResourceGroupType.WorkingTree);
|
||||
const scmResources = [...workingTree, ...resolved, ...unresolved];
|
||||
|
||||
@@ -696,7 +797,19 @@ export class CommandCenter {
|
||||
@command('git.stageAll', { repository: true })
|
||||
async stageAll(repository: Repository): Promise<void> {
|
||||
const resources = repository.mergeGroup.resourceStates.filter(s => s instanceof Resource) as Resource[];
|
||||
const { merge, unresolved } = await categorizeResourceByResolution(resources);
|
||||
const { merge, unresolved, deletionConflicts } = await categorizeResourceByResolution(resources);
|
||||
|
||||
try {
|
||||
for (const deletionConflict of deletionConflicts) {
|
||||
await this._stageDeletionConflict(repository, deletionConflict.resourceUri);
|
||||
}
|
||||
} catch (err) {
|
||||
if (/Cancelled/.test(err.message)) {
|
||||
return;
|
||||
}
|
||||
|
||||
throw err;
|
||||
}
|
||||
|
||||
if (unresolved.length > 0) {
|
||||
const message = unresolved.length > 1
|
||||
@@ -714,6 +827,41 @@ export class CommandCenter {
|
||||
await repository.add([]);
|
||||
}
|
||||
|
||||
private async _stageDeletionConflict(repository: Repository, uri: Uri): Promise<void> {
|
||||
const uriString = uri.toString();
|
||||
const resource = repository.mergeGroup.resourceStates.filter(r => r.resourceUri.toString() === uriString)[0];
|
||||
|
||||
if (!resource) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (resource.type === Status.DELETED_BY_THEM) {
|
||||
const keepIt = localize('keep ours', "Keep Our Version");
|
||||
const deleteIt = localize('delete', "Delete File");
|
||||
const result = await window.showInformationMessage(localize('deleted by them', "File '{0}' was deleted by them and modified by us.\n\nWhat would you like to do?", path.basename(uri.fsPath)), { modal: true }, keepIt, deleteIt);
|
||||
|
||||
if (result === keepIt) {
|
||||
await repository.add([uri]);
|
||||
} else if (result === deleteIt) {
|
||||
await repository.rm([uri]);
|
||||
} else {
|
||||
throw new Error('Cancelled');
|
||||
}
|
||||
} else if (resource.type === Status.DELETED_BY_US) {
|
||||
const keepIt = localize('keep theirs', "Keep Their Version");
|
||||
const deleteIt = localize('delete', "Delete File");
|
||||
const result = await window.showInformationMessage(localize('deleted by us', "File '{0}' was deleted by us and modified by them.\n\nWhat would you like to do?", path.basename(uri.fsPath)), { modal: true }, keepIt, deleteIt);
|
||||
|
||||
if (result === keepIt) {
|
||||
await repository.add([uri]);
|
||||
} else if (result === deleteIt) {
|
||||
await repository.rm([uri]);
|
||||
} else {
|
||||
throw new Error('Cancelled');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@command('git.stageChange')
|
||||
async stageChange(uri: Uri, changes: LineChange[], index: number): Promise<void> {
|
||||
const textEditor = window.visibleTextEditors.filter(e => e.document.uri.toString() === uri.toString())[0];
|
||||
@@ -916,10 +1064,20 @@ export class CommandCenter {
|
||||
message = localize('confirm delete', "Are you sure you want to DELETE {0}?", path.basename(scmResources[0].resourceUri.fsPath));
|
||||
yes = localize('delete file', "Delete file");
|
||||
} else {
|
||||
message = localize('confirm discard', "Are you sure you want to discard changes in {0}?", path.basename(scmResources[0].resourceUri.fsPath));
|
||||
if (scmResources[0].type === Status.DELETED) {
|
||||
yes = localize('restore file', "Restore file");
|
||||
message = localize('confirm restore', "Are you sure you want to restore {0}?", path.basename(scmResources[0].resourceUri.fsPath));
|
||||
} else {
|
||||
message = localize('confirm discard', "Are you sure you want to discard changes in {0}?", path.basename(scmResources[0].resourceUri.fsPath));
|
||||
}
|
||||
}
|
||||
} else {
|
||||
message = localize('confirm discard multiple', "Are you sure you want to discard changes in {0} files?", scmResources.length);
|
||||
if (scmResources.every(resource => resource.type === Status.DELETED)) {
|
||||
yes = localize('restore files', "Restore files");
|
||||
message = localize('confirm restore multiple', "Are you sure you want to restore {0} files?", scmResources.length);
|
||||
} else {
|
||||
message = localize('confirm discard multiple', "Are you sure you want to discard changes in {0} files?", scmResources.length);
|
||||
}
|
||||
|
||||
if (untrackedCount > 0) {
|
||||
message = `${message}\n\n${localize('warn untracked', "This will DELETE {0} untracked files!", untrackedCount)}`;
|
||||
@@ -1071,10 +1229,13 @@ export class CommandCenter {
|
||||
}
|
||||
|
||||
if (
|
||||
// no changes
|
||||
(noStagedChanges && noUnstagedChanges)
|
||||
// or no staged changes and not `all`
|
||||
|| (!opts.all && noStagedChanges)
|
||||
(
|
||||
// no changes
|
||||
(noStagedChanges && noUnstagedChanges)
|
||||
// or no staged changes and not `all`
|
||||
|| (!opts.all && noStagedChanges)
|
||||
)
|
||||
&& !opts.empty
|
||||
) {
|
||||
window.showInformationMessage(localize('no changes', "There are no changes to commit."));
|
||||
return false;
|
||||
@@ -1088,6 +1249,17 @@ export class CommandCenter {
|
||||
|
||||
await repository.commit(message, opts);
|
||||
|
||||
const postCommitCommand = config.get<'none' | 'push' | 'sync'>('postCommitCommand');
|
||||
|
||||
switch (postCommitCommand) {
|
||||
case 'push':
|
||||
await this._push(repository, { pushType: PushType.Push, silent: true });
|
||||
break;
|
||||
case 'sync':
|
||||
await this.sync(repository);
|
||||
break;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -1167,6 +1339,33 @@ export class CommandCenter {
|
||||
await this.commitWithAnyInput(repository, { all: true, amend: true });
|
||||
}
|
||||
|
||||
@command('git.commitEmpty', { repository: true })
|
||||
async commitEmpty(repository: Repository): Promise<void> {
|
||||
const root = Uri.file(repository.root);
|
||||
const config = workspace.getConfiguration('git', root);
|
||||
const shouldPrompt = config.get<boolean>('confirmEmptyCommits') === true;
|
||||
|
||||
if (shouldPrompt) {
|
||||
const message = localize('confirm emtpy commit', "Are you sure you want to create an empty commit?");
|
||||
const yes = localize('yes', "Yes");
|
||||
const neverAgain = localize('yes never again', "Yes, Don't Show Again");
|
||||
const pick = await window.showWarningMessage(message, { modal: true }, yes, neverAgain);
|
||||
|
||||
if (pick === neverAgain) {
|
||||
await config.update('confirmEmptyCommits', false, true);
|
||||
} else if (pick !== yes) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
await this.commitWithAnyInput(repository, { empty: true });
|
||||
}
|
||||
|
||||
@command('git.restoreCommitTemplate', { repository: true })
|
||||
async restoreCommitTemplate(repository: Repository): Promise<void> {
|
||||
repository.inputBox.value = await repository.getCommitTemplate();
|
||||
}
|
||||
|
||||
@command('git.undoCommit', { repository: true })
|
||||
async undoCommit(repository: Repository): Promise<void> {
|
||||
const HEAD = repository.HEAD;
|
||||
@@ -1189,9 +1388,10 @@ export class CommandCenter {
|
||||
}
|
||||
|
||||
@command('git.checkout', { repository: true })
|
||||
async checkout(repository: Repository, treeish: string): Promise<void> {
|
||||
async checkout(repository: Repository, treeish: string): Promise<boolean> {
|
||||
if (typeof treeish === 'string') {
|
||||
return await repository.checkout(treeish);
|
||||
await repository.checkout(treeish);
|
||||
return true;
|
||||
}
|
||||
|
||||
const config = workspace.getConfiguration('git');
|
||||
@@ -1215,26 +1415,49 @@ export class CommandCenter {
|
||||
const choice = await window.showQuickPick(picks, { placeHolder });
|
||||
|
||||
if (!choice) {
|
||||
return;
|
||||
return false;
|
||||
}
|
||||
|
||||
await choice.run(repository);
|
||||
return true;
|
||||
}
|
||||
|
||||
@command('git.branch', { repository: true })
|
||||
async branch(repository: Repository): Promise<void> {
|
||||
const config = workspace.getConfiguration('git');
|
||||
const branchValidationRegex = config.get<string>('branchValidationRegex')!;
|
||||
const branchWhitespaceChar = config.get<string>('branchWhitespaceChar')!;
|
||||
const validateName = new RegExp(branchValidationRegex);
|
||||
const sanitize = (name: string) => {
|
||||
name = name.trim();
|
||||
|
||||
if (!name) {
|
||||
return name;
|
||||
}
|
||||
|
||||
return name.replace(/^\.|\/\.|\.\.|~|\^|:|\/$|\.lock$|\.lock\/|\\|\*|\s|^\s*$|\.$|\[|\]$/g, branchWhitespaceChar);
|
||||
};
|
||||
|
||||
const result = await window.showInputBox({
|
||||
placeHolder: localize('branch name', "Branch name"),
|
||||
prompt: localize('provide branch name', "Please provide a branch name"),
|
||||
ignoreFocusOut: true
|
||||
ignoreFocusOut: true,
|
||||
validateInput: (name: string) => {
|
||||
if (validateName.test(sanitize(name))) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return localize('branch name format invalid', "Branch name needs to match regex: {0}", branchValidationRegex);
|
||||
}
|
||||
});
|
||||
|
||||
if (!result) {
|
||||
const name = sanitize(result || '');
|
||||
|
||||
if (!name) {
|
||||
return;
|
||||
}
|
||||
|
||||
const name = result.replace(/^\.|\/\.|\.\.|~|\^|:|\/$|\.lock$|\.lock\/|\\|\*|\s|^\s*$|\.$/g, '-');
|
||||
await repository.branch(name);
|
||||
await repository.branch(name, true);
|
||||
}
|
||||
|
||||
@command('git.deleteBranch', { repository: true })
|
||||
@@ -1354,7 +1577,28 @@ export class CommandCenter {
|
||||
return;
|
||||
}
|
||||
|
||||
await repository.fetch();
|
||||
await repository.fetchDefault();
|
||||
}
|
||||
|
||||
@command('git.fetchPrune', { repository: true })
|
||||
async fetchPrune(repository: Repository): Promise<void> {
|
||||
if (repository.remotes.length === 0) {
|
||||
window.showWarningMessage(localize('no remotes to fetch', "This repository has no remotes configured to fetch from."));
|
||||
return;
|
||||
}
|
||||
|
||||
await repository.fetchPrune();
|
||||
}
|
||||
|
||||
|
||||
@command('git.fetchAll', { repository: true })
|
||||
async fetchAll(repository: Repository): Promise<void> {
|
||||
if (repository.remotes.length === 0) {
|
||||
window.showWarningMessage(localize('no remotes to fetch', "This repository has no remotes configured to fetch from."));
|
||||
return;
|
||||
}
|
||||
|
||||
await repository.fetchAll();
|
||||
}
|
||||
|
||||
@command('git.pullFrom', { repository: true })
|
||||
@@ -1377,7 +1621,8 @@ export class CommandCenter {
|
||||
const remoteRefs = repository.refs;
|
||||
const remoteRefsFiltered = remoteRefs.filter(r => (r.remote === remotePick.label));
|
||||
const branchPicks = remoteRefsFiltered.map(r => ({ label: r.name })) as { label: string; description: string }[];
|
||||
const branchPick = await window.showQuickPick(branchPicks, { placeHolder });
|
||||
const branchPlaceHolder = localize('pick branch pull', "Pick a branch to pull from");
|
||||
const branchPick = await window.showQuickPick(branchPicks, { placeHolder: branchPlaceHolder });
|
||||
|
||||
if (!branchPick) {
|
||||
return;
|
||||
@@ -1412,76 +1657,118 @@ export class CommandCenter {
|
||||
await repository.pullWithRebase(repository.HEAD);
|
||||
}
|
||||
|
||||
@command('git.push', { repository: true })
|
||||
async push(repository: Repository): Promise<void> {
|
||||
private async _push(repository: Repository, pushOptions: PushOptions) {
|
||||
const remotes = repository.remotes;
|
||||
|
||||
if (remotes.length === 0) {
|
||||
window.showWarningMessage(localize('no remotes to push', "Your repository has no remotes configured to push to."));
|
||||
if (!pushOptions.silent) {
|
||||
window.showWarningMessage(localize('no remotes to push', "Your repository has no remotes configured to push to."));
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
const config = workspace.getConfiguration('git', Uri.file(repository.root));
|
||||
let forcePushMode: ForcePushMode | undefined = undefined;
|
||||
|
||||
if (pushOptions.forcePush) {
|
||||
if (!config.get<boolean>('allowForcePush')) {
|
||||
await window.showErrorMessage(localize('force push not allowed', "Force push is not allowed, please enable it with the 'git.allowForcePush' setting."));
|
||||
return;
|
||||
}
|
||||
|
||||
forcePushMode = config.get<boolean>('useForcePushWithLease') === true ? ForcePushMode.ForceWithLease : ForcePushMode.Force;
|
||||
|
||||
if (config.get<boolean>('confirmForcePush')) {
|
||||
const message = localize('confirm force push', "You are about to force push your changes, this can be destructive and could inadvertedly overwrite changes made by others.\n\nAre you sure to continue?");
|
||||
const yes = localize('ok', "OK");
|
||||
const neverAgain = localize('never ask again', "OK, Don't Ask Again");
|
||||
const pick = await window.showWarningMessage(message, { modal: true }, yes, neverAgain);
|
||||
|
||||
if (pick === neverAgain) {
|
||||
config.update('confirmForcePush', false, true);
|
||||
} else if (pick !== yes) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (pushOptions.pushType === PushType.PushTags) {
|
||||
await repository.pushTags(undefined, forcePushMode);
|
||||
|
||||
window.showInformationMessage(localize('push with tags success', "Successfully pushed with tags."));
|
||||
return;
|
||||
}
|
||||
|
||||
if (!repository.HEAD || !repository.HEAD.name) {
|
||||
window.showWarningMessage(localize('nobranch', "Please check out a branch to push to a remote."));
|
||||
if (!pushOptions.silent) {
|
||||
window.showWarningMessage(localize('nobranch', "Please check out a branch to push to a remote."));
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
await repository.push(repository.HEAD);
|
||||
} catch (err) {
|
||||
if (err.gitErrorCode !== GitErrorCodes.NoUpstreamBranch) {
|
||||
throw err;
|
||||
}
|
||||
if (pushOptions.pushType === PushType.Push) {
|
||||
try {
|
||||
await repository.push(repository.HEAD, forcePushMode);
|
||||
} catch (err) {
|
||||
if (err.gitErrorCode !== GitErrorCodes.NoUpstreamBranch) {
|
||||
throw err;
|
||||
}
|
||||
|
||||
if (pushOptions.silent) {
|
||||
return;
|
||||
}
|
||||
|
||||
const branchName = repository.HEAD.name;
|
||||
const message = localize('confirm publish branch', "The branch '{0}' has no upstream branch. Would you like to publish this branch?", branchName);
|
||||
const yes = localize('ok', "OK");
|
||||
const pick = await window.showWarningMessage(message, { modal: true }, yes);
|
||||
|
||||
if (pick === yes) {
|
||||
await this.publish(repository);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
const branchName = repository.HEAD.name;
|
||||
const message = localize('confirm publish branch', "The branch '{0}' has no upstream branch. Would you like to publish this branch?", branchName);
|
||||
const yes = localize('ok', "OK");
|
||||
const pick = await window.showWarningMessage(message, { modal: true }, yes);
|
||||
const picks = remotes.filter(r => r.pushUrl !== undefined).map(r => ({ label: r.name, description: r.pushUrl! }));
|
||||
const placeHolder = localize('pick remote', "Pick a remote to publish the branch '{0}' to:", branchName);
|
||||
const pick = await window.showQuickPick(picks, { placeHolder });
|
||||
|
||||
if (pick === yes) {
|
||||
await this.publish(repository);
|
||||
if (!pick) {
|
||||
return;
|
||||
}
|
||||
|
||||
await repository.pushTo(pick.label, branchName, undefined, forcePushMode);
|
||||
}
|
||||
}
|
||||
|
||||
@command('git.push', { repository: true })
|
||||
async push(repository: Repository): Promise<void> {
|
||||
await this._push(repository, { pushType: PushType.Push });
|
||||
}
|
||||
|
||||
@command('git.pushForce', { repository: true })
|
||||
async pushForce(repository: Repository): Promise<void> {
|
||||
await this._push(repository, { pushType: PushType.Push, forcePush: true });
|
||||
}
|
||||
|
||||
@command('git.pushWithTags', { repository: true })
|
||||
async pushWithTags(repository: Repository): Promise<void> {
|
||||
const remotes = repository.remotes;
|
||||
await this._push(repository, { pushType: PushType.PushTags });
|
||||
}
|
||||
|
||||
if (remotes.length === 0) {
|
||||
window.showWarningMessage(localize('no remotes to push', "Your repository has no remotes configured to push to."));
|
||||
return;
|
||||
}
|
||||
|
||||
await repository.pushTags();
|
||||
|
||||
window.showInformationMessage(localize('push with tags success', "Successfully pushed with tags."));
|
||||
@command('git.pushWithTagsForce', { repository: true })
|
||||
async pushWithTagsForce(repository: Repository): Promise<void> {
|
||||
await this._push(repository, { pushType: PushType.PushTags, forcePush: true });
|
||||
}
|
||||
|
||||
@command('git.pushTo', { repository: true })
|
||||
async pushTo(repository: Repository): Promise<void> {
|
||||
const remotes = repository.remotes;
|
||||
await this._push(repository, { pushType: PushType.PushTo });
|
||||
}
|
||||
|
||||
if (remotes.length === 0) {
|
||||
window.showWarningMessage(localize('no remotes to push', "Your repository has no remotes configured to push to."));
|
||||
return;
|
||||
}
|
||||
|
||||
if (!repository.HEAD || !repository.HEAD.name) {
|
||||
window.showWarningMessage(localize('nobranch', "Please check out a branch to push to a remote."));
|
||||
return;
|
||||
}
|
||||
|
||||
const branchName = repository.HEAD.name;
|
||||
const picks = remotes.filter(r => r.pushUrl !== undefined).map(r => ({ label: r.name, description: r.pushUrl! }));
|
||||
const placeHolder = localize('pick remote', "Pick a remote to publish the branch '{0}' to:", branchName);
|
||||
const pick = await window.showQuickPick(picks, { placeHolder });
|
||||
|
||||
if (!pick) {
|
||||
return;
|
||||
}
|
||||
|
||||
await repository.pushTo(pick.label, branchName);
|
||||
@command('git.pushToForce', { repository: true })
|
||||
async pushToForce(repository: Repository): Promise<void> {
|
||||
await this._push(repository, { pushType: PushType.PushTo, forcePush: true });
|
||||
}
|
||||
|
||||
private async _sync(repository: Repository, rebase: boolean): Promise<void> {
|
||||
@@ -1627,22 +1914,14 @@ export class CommandCenter {
|
||||
|
||||
@command('git.stashPop', { repository: true })
|
||||
async stashPop(repository: Repository): Promise<void> {
|
||||
const stashes = await repository.getStashes();
|
||||
|
||||
if (stashes.length === 0) {
|
||||
window.showInformationMessage(localize('no stashes', "There are no stashes to restore."));
|
||||
return;
|
||||
}
|
||||
|
||||
const picks = stashes.map(r => ({ label: `#${r.index}: ${r.description}`, description: '', details: '', id: r.index }));
|
||||
const placeHolder = localize('pick stash to pop', "Pick a stash to pop");
|
||||
const choice = await window.showQuickPick(picks, { placeHolder });
|
||||
const stash = await this.pickStash(repository, placeHolder);
|
||||
|
||||
if (!choice) {
|
||||
if (!stash) {
|
||||
return;
|
||||
}
|
||||
|
||||
await repository.popStash(choice.id);
|
||||
await repository.popStash(stash.index);
|
||||
}
|
||||
|
||||
@command('git.stashPopLatest', { repository: true })
|
||||
@@ -1650,13 +1929,50 @@ export class CommandCenter {
|
||||
const stashes = await repository.getStashes();
|
||||
|
||||
if (stashes.length === 0) {
|
||||
window.showInformationMessage(localize('no stashes', "There are no stashes to restore."));
|
||||
window.showInformationMessage(localize('no stashes', "There are no stashes in the repository."));
|
||||
return;
|
||||
}
|
||||
|
||||
await repository.popStash();
|
||||
}
|
||||
|
||||
@command('git.stashApply', { repository: true })
|
||||
async stashApply(repository: Repository): Promise<void> {
|
||||
const placeHolder = localize('pick stash to apply', "Pick a stash to apply");
|
||||
const stash = await this.pickStash(repository, placeHolder);
|
||||
|
||||
if (!stash) {
|
||||
return;
|
||||
}
|
||||
|
||||
await repository.applyStash(stash.index);
|
||||
}
|
||||
|
||||
@command('git.stashApplyLatest', { repository: true })
|
||||
async stashApplyLatest(repository: Repository): Promise<void> {
|
||||
const stashes = await repository.getStashes();
|
||||
|
||||
if (stashes.length === 0) {
|
||||
window.showInformationMessage(localize('no stashes', "There are no stashes in the repository."));
|
||||
return;
|
||||
}
|
||||
|
||||
await repository.applyStash();
|
||||
}
|
||||
|
||||
private async pickStash(repository: Repository, placeHolder: string): Promise<Stash | undefined> {
|
||||
const stashes = await repository.getStashes();
|
||||
|
||||
if (stashes.length === 0) {
|
||||
window.showInformationMessage(localize('no stashes', "There are no stashes in the repository."));
|
||||
return;
|
||||
}
|
||||
|
||||
const picks = stashes.map(stash => ({ label: `#${stash.index}: ${stash.description}`, description: '', details: '', stash }));
|
||||
const result = await window.showQuickPick(picks, { placeHolder });
|
||||
return result && result.stash;
|
||||
}
|
||||
|
||||
private createCommand(id: string, key: string, method: Function, options: CommandOptions): (...args: any[]) => any {
|
||||
const result = (...args: any[]) => {
|
||||
let result: Promise<any>;
|
||||
@@ -1700,6 +2016,11 @@ export class CommandCenter {
|
||||
let message: string;
|
||||
let type: 'error' | 'warning' = 'error';
|
||||
|
||||
const choices = new Map<string, () => void>();
|
||||
const openOutputChannelChoice = localize('open git log', "Open Git Log");
|
||||
const outputChannel = this.outputChannel as OutputChannel;
|
||||
choices.set(openOutputChannelChoice, () => outputChannel.show());
|
||||
|
||||
switch (err.gitErrorCode) {
|
||||
case GitErrorCodes.DirtyWorkTree:
|
||||
message = localize('clean repo', "Please clean your repository working tree before checkout.");
|
||||
@@ -1712,6 +2033,16 @@ export class CommandCenter {
|
||||
type = 'warning';
|
||||
options.modal = false;
|
||||
break;
|
||||
case GitErrorCodes.StashConflict:
|
||||
message = localize('stash merge conflicts', "There were merge conflicts while applying the stash.");
|
||||
type = 'warning';
|
||||
options.modal = false;
|
||||
break;
|
||||
case GitErrorCodes.NoUserNameConfigured:
|
||||
case GitErrorCodes.NoUserEmailConfigured:
|
||||
message = localize('missing user info', "Make sure you configure your 'user.name' and 'user.email' in git.");
|
||||
choices.set(localize('learn more', "Learn More"), () => commands.executeCommand('vscode.open', Uri.parse('https://git-scm.com/book/en/v2/Getting-Started-First-Time-Git-Setup')));
|
||||
break;
|
||||
default:
|
||||
const hint = (err.stderr || err.message || String(err))
|
||||
.replace(/^error: /mi, '')
|
||||
@@ -1732,14 +2063,17 @@ export class CommandCenter {
|
||||
return;
|
||||
}
|
||||
|
||||
const outputChannel = this.outputChannel as OutputChannel;
|
||||
const openOutputChannelChoice = localize('open git log', "Open Git Log");
|
||||
const choice = type === 'error'
|
||||
? await window.showErrorMessage(message, options, openOutputChannelChoice)
|
||||
: await window.showWarningMessage(message, options, openOutputChannelChoice);
|
||||
const allChoices = Array.from(choices.keys());
|
||||
const result = type === 'error'
|
||||
? await window.showErrorMessage(message, options, ...allChoices)
|
||||
: await window.showWarningMessage(message, options, ...allChoices);
|
||||
|
||||
if (choice === openOutputChannelChoice) {
|
||||
outputChannel.show();
|
||||
if (result) {
|
||||
const resultFn = choices.get(result);
|
||||
|
||||
if (resultFn) {
|
||||
resultFn();
|
||||
}
|
||||
}
|
||||
});
|
||||
};
|
||||
@@ -1778,6 +2112,7 @@ export class CommandCenter {
|
||||
return repository.workingTreeGroup.resourceStates.filter(r => r.resourceUri.toString() === uriString)[0]
|
||||
|| repository.indexGroup.resourceStates.filter(r => r.resourceUri.toString() === uriString)[0];
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
|
||||
private runByRepository<T>(resource: Uri, fn: (repository: Repository, resource: Uri) => Promise<T>): Promise<T[]>;
|
||||
|
||||
@@ -3,8 +3,6 @@
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
'use strict';
|
||||
|
||||
import { workspace, Uri, Disposable, Event, EventEmitter, window } from 'vscode';
|
||||
import { debounce, throttle } from './decorators';
|
||||
import { fromGitUri, toGitUri } from './uri';
|
||||
@@ -92,7 +90,11 @@ export class GitContentProvider {
|
||||
return '';
|
||||
}
|
||||
|
||||
return await repository.diff(path, { cached: ref === 'index' });
|
||||
if (ref === 'index') {
|
||||
return await repository.diffIndexWithHEAD(path);
|
||||
} else {
|
||||
return await repository.diffWithHEAD(path);
|
||||
}
|
||||
}
|
||||
|
||||
const repository = this.model.getRepository(uri);
|
||||
@@ -112,6 +114,8 @@ export class GitContentProvider {
|
||||
const uriString = fileUri.toString();
|
||||
const [indexStatus] = repository.indexGroup.resourceStates.filter(r => r.resourceUri.toString() === uriString);
|
||||
ref = indexStatus ? '' : 'HEAD';
|
||||
} else if (/^~\d$/.test(ref)) {
|
||||
ref = `:${ref[1]}`;
|
||||
}
|
||||
|
||||
try {
|
||||
|
||||
@@ -3,15 +3,13 @@
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
'use strict';
|
||||
|
||||
import { window, workspace, Uri, Disposable, Event, EventEmitter, DecorationData, DecorationProvider, ThemeColor } from 'vscode';
|
||||
import * as path from 'path';
|
||||
import { Repository, GitResourceGroup, Status } from './repository';
|
||||
import { Repository, GitResourceGroup } from './repository';
|
||||
import { Model } from './model';
|
||||
import { debounce } from './decorators';
|
||||
import { filterEvent, dispose, anyEvent, fireEvent } from './util';
|
||||
import { GitErrorCodes } from './git';
|
||||
import { GitErrorCodes, Status } from './api/git';
|
||||
|
||||
type Callback = { resolve: (status: boolean) => void, reject: (err: any) => void };
|
||||
|
||||
@@ -56,6 +54,7 @@ class GitIgnoreDecorationProvider implements DecorationProvider {
|
||||
color: new ThemeColor('gitDecoration.ignoredResourceForeground')
|
||||
};
|
||||
}
|
||||
return undefined;
|
||||
});
|
||||
}
|
||||
|
||||
@@ -93,7 +92,7 @@ class GitDecorationProvider implements DecorationProvider {
|
||||
|
||||
private static SubmoduleDecorationData: DecorationData = {
|
||||
title: 'Submodule',
|
||||
abbreviation: 'S',
|
||||
letter: 'S',
|
||||
color: new ThemeColor('gitDecoration.submoduleResourceForeground')
|
||||
};
|
||||
|
||||
|
||||
@@ -3,12 +3,10 @@
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
'use strict';
|
||||
|
||||
import { done } from './util';
|
||||
|
||||
function decorate(decorator: (fn: Function, key: string) => Function): Function {
|
||||
return (target: any, key: string, descriptor: any) => {
|
||||
return (_target: any, key: string, descriptor: any) => {
|
||||
let fnKey: string | null = null;
|
||||
let fn: Function | null = null;
|
||||
|
||||
|
||||
@@ -3,13 +3,11 @@
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
'use strict';
|
||||
|
||||
import * as jschardet from 'jschardet';
|
||||
|
||||
jschardet.Constants.MINIMUM_THRESHOLD = 0.2;
|
||||
|
||||
function detectEncodingByBOM(buffer: NodeBuffer): string | null {
|
||||
function detectEncodingByBOM(buffer: Buffer): string | null {
|
||||
if (!buffer || buffer.length < 2) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@@ -3,8 +3,6 @@
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
'use strict';
|
||||
|
||||
import * as fs from 'fs';
|
||||
import * as path from 'path';
|
||||
import * as os from 'os';
|
||||
@@ -16,6 +14,7 @@ import * as filetype from 'file-type';
|
||||
import { assign, groupBy, denodeify, IDisposable, toDisposable, dispose, mkdirp, readBytes, detectUnicodeEncoding, Encoding, onceEvent } from './util';
|
||||
import { CancellationToken } from 'vscode';
|
||||
import { detectEncoding } from './encoding';
|
||||
import { Ref, RefType, Branch, Remote, GitErrorCodes } from './api/git';
|
||||
|
||||
const readfile = denodeify<string, string | null, string>(fs.readFile);
|
||||
|
||||
@@ -31,40 +30,15 @@ export interface IFileStatus {
|
||||
rename?: string;
|
||||
}
|
||||
|
||||
export interface Remote {
|
||||
name: string;
|
||||
fetchUrl?: string;
|
||||
pushUrl?: string;
|
||||
isReadOnly: boolean;
|
||||
}
|
||||
|
||||
export interface Stash {
|
||||
index: number;
|
||||
description: string;
|
||||
}
|
||||
|
||||
export enum RefType {
|
||||
Head,
|
||||
RemoteHead,
|
||||
Tag
|
||||
}
|
||||
|
||||
export interface Ref {
|
||||
type: RefType;
|
||||
name?: string;
|
||||
commit?: string;
|
||||
remote?: string;
|
||||
}
|
||||
|
||||
export interface UpstreamRef {
|
||||
remote: string;
|
||||
name: string;
|
||||
}
|
||||
|
||||
export interface Branch extends Ref {
|
||||
upstream?: UpstreamRef;
|
||||
ahead?: number;
|
||||
behind?: number;
|
||||
interface MutableRemote extends Remote {
|
||||
fetchUrl?: string;
|
||||
pushUrl?: string;
|
||||
isReadOnly: boolean;
|
||||
}
|
||||
|
||||
function parseVersion(raw: string): string {
|
||||
@@ -309,37 +283,6 @@ export interface IGitOptions {
|
||||
env?: any;
|
||||
}
|
||||
|
||||
export const GitErrorCodes = {
|
||||
BadConfigFile: 'BadConfigFile',
|
||||
AuthenticationFailed: 'AuthenticationFailed',
|
||||
NoUserNameConfigured: 'NoUserNameConfigured',
|
||||
NoUserEmailConfigured: 'NoUserEmailConfigured',
|
||||
NoRemoteRepositorySpecified: 'NoRemoteRepositorySpecified',
|
||||
NotAGitRepository: 'NotAGitRepository',
|
||||
NotAtRepositoryRoot: 'NotAtRepositoryRoot',
|
||||
Conflict: 'Conflict',
|
||||
UnmergedChanges: 'UnmergedChanges',
|
||||
PushRejected: 'PushRejected',
|
||||
RemoteConnectionError: 'RemoteConnectionError',
|
||||
DirtyWorkTree: 'DirtyWorkTree',
|
||||
CantOpenResource: 'CantOpenResource',
|
||||
GitNotFound: 'GitNotFound',
|
||||
CantCreatePipe: 'CantCreatePipe',
|
||||
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',
|
||||
};
|
||||
|
||||
function getGitErrorCode(stderr: string): string | undefined {
|
||||
if (/Another git process seems to be running in this repository|If no other git process is currently running/.test(stderr)) {
|
||||
return GitErrorCodes.RepositoryIsLocked;
|
||||
@@ -427,6 +370,10 @@ export class Git {
|
||||
return await this._exec(args, options);
|
||||
}
|
||||
|
||||
async exec2(args: string[], options: SpawnOptions = {}): Promise<IExecutionResult<string>> {
|
||||
return await this._exec(args, options);
|
||||
}
|
||||
|
||||
stream(cwd: string, args: string[], options: SpawnOptions = {}): cp.ChildProcess {
|
||||
options = assign({ cwd }, options || {});
|
||||
return this.spawn(args, options);
|
||||
@@ -674,8 +621,17 @@ export function parseLsFiles(raw: string): LsFilesElement[] {
|
||||
.map(([, mode, object, stage, file]) => ({ mode, object, stage, file }));
|
||||
}
|
||||
|
||||
export interface DiffOptions {
|
||||
cached?: boolean;
|
||||
export interface CommitOptions {
|
||||
all?: boolean;
|
||||
amend?: boolean;
|
||||
signoff?: boolean;
|
||||
signCommit?: boolean;
|
||||
empty?: boolean;
|
||||
}
|
||||
|
||||
export enum ForcePushMode {
|
||||
Force,
|
||||
ForceWithLease
|
||||
}
|
||||
|
||||
export class Repository {
|
||||
@@ -706,7 +662,7 @@ export class Repository {
|
||||
return this.git.spawn(args, options);
|
||||
}
|
||||
|
||||
async config(scope: string, key: string, value: any, options: SpawnOptions): Promise<string> {
|
||||
async config(scope: string, key: string, value: any = null, options: SpawnOptions = {}): Promise<string> {
|
||||
const args = ['config'];
|
||||
|
||||
if (scope) {
|
||||
@@ -720,7 +676,25 @@ export class Repository {
|
||||
}
|
||||
|
||||
const result = await this.run(args, options);
|
||||
return result.stdout;
|
||||
return result.stdout.trim();
|
||||
}
|
||||
|
||||
async getConfigs(scope: string): Promise<{ key: string; value: string; }[]> {
|
||||
const args = ['config'];
|
||||
|
||||
if (scope) {
|
||||
args.push('--' + scope);
|
||||
}
|
||||
|
||||
args.push('-l');
|
||||
|
||||
const result = await this.run(args);
|
||||
const lines = result.stdout.trim().split(/\r|\r\n|\n/);
|
||||
|
||||
return lines.map(entry => {
|
||||
const equalsIndex = entry.indexOf('=');
|
||||
return { key: entry.substr(0, equalsIndex), value: entry.substr(equalsIndex + 1) };
|
||||
});
|
||||
}
|
||||
|
||||
async bufferString(object: string, encoding: string = 'utf8', autoGuessEncoding = false): Promise<string> {
|
||||
@@ -848,19 +822,78 @@ export class Repository {
|
||||
}
|
||||
}
|
||||
|
||||
async diff(path: string, options: DiffOptions = {}): Promise<string> {
|
||||
async apply(patch: string, reverse?: boolean): Promise<void> {
|
||||
const args = ['apply', patch];
|
||||
|
||||
if (reverse) {
|
||||
args.push('-R');
|
||||
}
|
||||
|
||||
await this.run(args);
|
||||
}
|
||||
|
||||
async diff(cached = false): Promise<string> {
|
||||
const args = ['diff'];
|
||||
|
||||
if (options.cached) {
|
||||
if (cached) {
|
||||
args.push('--cached');
|
||||
}
|
||||
|
||||
args.push('--', path);
|
||||
|
||||
const result = await this.run(args);
|
||||
return result.stdout;
|
||||
}
|
||||
|
||||
async diffWithHEAD(path: string): Promise<string> {
|
||||
const args = ['diff', '--', path];
|
||||
const result = await this.run(args);
|
||||
return result.stdout;
|
||||
}
|
||||
|
||||
async diffWith(ref: string, path: string): Promise<string> {
|
||||
const args = ['diff', ref, '--', path];
|
||||
const result = await this.run(args);
|
||||
return result.stdout;
|
||||
}
|
||||
|
||||
async diffIndexWithHEAD(path: string): Promise<string> {
|
||||
const args = ['diff', '--cached', '--', path];
|
||||
const result = await this.run(args);
|
||||
return result.stdout;
|
||||
}
|
||||
|
||||
async diffIndexWith(ref: string, path: string): Promise<string> {
|
||||
const args = ['diff', '--cached', ref, '--', path];
|
||||
const result = await this.run(args);
|
||||
return result.stdout;
|
||||
}
|
||||
|
||||
async diffBlobs(object1: string, object2: string): Promise<string> {
|
||||
const args = ['diff', object1, object2];
|
||||
const result = await this.run(args);
|
||||
return result.stdout;
|
||||
}
|
||||
|
||||
async diffBetween(ref1: string, ref2: string, path: string): Promise<string> {
|
||||
const args = ['diff', `${ref1}...${ref2}`, '--', path];
|
||||
const result = await this.run(args);
|
||||
|
||||
return result.stdout.trim();
|
||||
}
|
||||
|
||||
async getMergeBase(ref1: string, ref2: string): Promise<string> {
|
||||
const args = ['merge-base', ref1, ref2];
|
||||
const result = await this.run(args);
|
||||
|
||||
return result.stdout.trim();
|
||||
}
|
||||
|
||||
async hashObject(data: string): Promise<string> {
|
||||
const args = ['hash-object', '-w', '--stdin'];
|
||||
const result = await this.run(args, { input: data });
|
||||
|
||||
return result.stdout.trim();
|
||||
}
|
||||
|
||||
async add(paths: string[]): Promise<void> {
|
||||
const args = ['add', '-A', '--'];
|
||||
|
||||
@@ -873,6 +906,18 @@ export class Repository {
|
||||
await this.run(args);
|
||||
}
|
||||
|
||||
async rm(paths: string[]): Promise<void> {
|
||||
const args = ['rm', '--'];
|
||||
|
||||
if (!paths || !paths.length) {
|
||||
return;
|
||||
}
|
||||
|
||||
args.push(...paths);
|
||||
|
||||
await this.run(args);
|
||||
}
|
||||
|
||||
async stage(path: string, data: string): Promise<void> {
|
||||
const child = this.stream(['hash-object', '--stdin', '-w', '--path', path], { stdio: [null, null, null] });
|
||||
child.stdin.end(data, 'utf8');
|
||||
@@ -899,9 +944,13 @@ export class Repository {
|
||||
await this.run(['update-index', '--cacheinfo', mode, hash, path]);
|
||||
}
|
||||
|
||||
async checkout(treeish: string, paths: string[]): Promise<void> {
|
||||
async checkout(treeish: string, paths: string[], opts: { track?: boolean } = Object.create(null)): Promise<void> {
|
||||
const args = ['checkout', '-q'];
|
||||
|
||||
if (opts.track) {
|
||||
args.push('--track');
|
||||
}
|
||||
|
||||
if (treeish) {
|
||||
args.push(treeish);
|
||||
}
|
||||
@@ -914,7 +963,7 @@ export class Repository {
|
||||
try {
|
||||
await this.run(args);
|
||||
} catch (err) {
|
||||
if (/Please, commit your changes or stash them/.test(err.stderr || '')) {
|
||||
if (/Please,? commit your changes or stash them/.test(err.stderr || '')) {
|
||||
err.gitErrorCode = GitErrorCodes.DirtyWorkTree;
|
||||
}
|
||||
|
||||
@@ -922,7 +971,7 @@ export class Repository {
|
||||
}
|
||||
}
|
||||
|
||||
async commit(message: string, opts: { all?: boolean, amend?: boolean, signoff?: boolean, signCommit?: boolean } = Object.create(null)): Promise<void> {
|
||||
async commit(message: string, opts: CommitOptions = Object.create(null)): Promise<void> {
|
||||
const args = ['commit', '--quiet', '--allow-empty-message', '--file', '-'];
|
||||
|
||||
if (opts.all) {
|
||||
@@ -940,6 +989,9 @@ export class Repository {
|
||||
if (opts.signCommit) {
|
||||
args.push('-S');
|
||||
}
|
||||
if (opts.empty) {
|
||||
args.push('--allow-empty');
|
||||
}
|
||||
|
||||
try {
|
||||
await this.run(args, { input: message || '' });
|
||||
@@ -981,8 +1033,13 @@ export class Repository {
|
||||
throw commitErr;
|
||||
}
|
||||
|
||||
async branch(name: string, checkout: boolean): Promise<void> {
|
||||
async branch(name: string, checkout: boolean, ref?: string): Promise<void> {
|
||||
const args = checkout ? ['checkout', '-q', '-b', name] : ['branch', '-q', name];
|
||||
|
||||
if (ref) {
|
||||
args.push(ref);
|
||||
}
|
||||
|
||||
await this.run(args);
|
||||
}
|
||||
|
||||
@@ -996,6 +1053,11 @@ export class Repository {
|
||||
await this.run(args);
|
||||
}
|
||||
|
||||
async setBranchUpstream(name: string, upstream: string): Promise<void> {
|
||||
const args = ['branch', '--set-upstream-to', upstream, name];
|
||||
await this.run(args);
|
||||
}
|
||||
|
||||
async deleteRef(ref: string): Promise<void> {
|
||||
const args = ['update-ref', '-d', ref];
|
||||
await this.run(args);
|
||||
@@ -1052,14 +1114,7 @@ export class Repository {
|
||||
}
|
||||
|
||||
async reset(treeish: string, hard: boolean = false): Promise<void> {
|
||||
const args = ['reset'];
|
||||
|
||||
if (hard) {
|
||||
args.push('--hard');
|
||||
}
|
||||
|
||||
args.push(treeish);
|
||||
|
||||
const args = ['reset', hard ? '--hard' : '--soft', treeish];
|
||||
await this.run(args);
|
||||
}
|
||||
|
||||
@@ -1093,9 +1148,36 @@ export class Repository {
|
||||
}
|
||||
}
|
||||
|
||||
async fetch(): Promise<void> {
|
||||
async addRemote(name: string, url: string): Promise<void> {
|
||||
const args = ['remote', 'add', name, url];
|
||||
await this.run(args);
|
||||
}
|
||||
|
||||
async removeRemote(name: string): Promise<void> {
|
||||
const args = ['remote', 'rm', name];
|
||||
await this.run(args);
|
||||
}
|
||||
|
||||
async fetch(options: { remote?: string, ref?: string, all?: boolean, prune?: boolean } = {}): Promise<void> {
|
||||
const args = ['fetch'];
|
||||
|
||||
if (options.remote) {
|
||||
args.push(options.remote);
|
||||
|
||||
if (options.ref) {
|
||||
args.push(options.ref);
|
||||
}
|
||||
} else if (options.all) {
|
||||
args.push('--all');
|
||||
}
|
||||
|
||||
if (options.prune) {
|
||||
args.push('--prune');
|
||||
}
|
||||
|
||||
|
||||
try {
|
||||
await this.run(['fetch']);
|
||||
await this.run(args);
|
||||
} catch (err) {
|
||||
if (/No remote repository specified\./.test(err.stderr || '')) {
|
||||
err.gitErrorCode = GitErrorCodes.NoRemoteRepositorySpecified;
|
||||
@@ -1131,15 +1213,25 @@ export class Repository {
|
||||
} else if (/Pull is not possible because you have unmerged files|Cannot pull with rebase: You have unstaged changes|Your local changes to the following files would be overwritten|Please, commit your changes before you can merge/i.test(err.stderr)) {
|
||||
err.stderr = err.stderr.replace(/Cannot pull with rebase: You have unstaged changes/i, 'Cannot pull with rebase, you have unstaged changes');
|
||||
err.gitErrorCode = GitErrorCodes.DirtyWorkTree;
|
||||
} else if (/cannot lock ref|unable to update local ref/i.test(err.stderr || '')) {
|
||||
err.gitErrorCode = GitErrorCodes.CantLockRef;
|
||||
} else if (/cannot rebase onto multiple branches/i.test(err.stderr || '')) {
|
||||
err.gitErrorCode = GitErrorCodes.CantRebaseMultipleBranches;
|
||||
}
|
||||
|
||||
throw err;
|
||||
}
|
||||
}
|
||||
|
||||
async push(remote?: string, name?: string, setUpstream: boolean = false, tags = false): Promise<void> {
|
||||
async push(remote?: string, name?: string, setUpstream: boolean = false, tags = false, forcePushMode?: ForcePushMode): Promise<void> {
|
||||
const args = ['push'];
|
||||
|
||||
if (forcePushMode === ForcePushMode.ForceWithLease) {
|
||||
args.push('--force-with-lease');
|
||||
} else if (forcePushMode === ForcePushMode.Force) {
|
||||
args.push('--force');
|
||||
}
|
||||
|
||||
if (setUpstream) {
|
||||
args.push('-u');
|
||||
}
|
||||
@@ -1194,9 +1286,17 @@ export class Repository {
|
||||
}
|
||||
|
||||
async popStash(index?: number): Promise<void> {
|
||||
try {
|
||||
const args = ['stash', 'pop'];
|
||||
const args = ['stash', 'pop'];
|
||||
await this.popOrApplyStash(args, index);
|
||||
}
|
||||
|
||||
async applyStash(index?: number): Promise<void> {
|
||||
const args = ['stash', 'apply'];
|
||||
await this.popOrApplyStash(args, index);
|
||||
}
|
||||
|
||||
private async popOrApplyStash(args: string[], index?: number): Promise<void> {
|
||||
try {
|
||||
if (typeof index === 'number') {
|
||||
args.push(`stash@{${index}}`);
|
||||
}
|
||||
@@ -1207,6 +1307,8 @@ export class Repository {
|
||||
err.gitErrorCode = GitErrorCodes.NoStashFound;
|
||||
} else if (/error: Your local changes to the following files would be overwritten/.test(err.stderr || '')) {
|
||||
err.gitErrorCode = GitErrorCodes.LocalChangesOverwritten;
|
||||
} else if (/^CONFLICT/m.test(err.stdout || '')) {
|
||||
err.gitErrorCode = GitErrorCodes.StashConflict;
|
||||
}
|
||||
|
||||
throw err;
|
||||
@@ -1316,7 +1418,7 @@ export class Repository {
|
||||
async getRemotes(): Promise<Remote[]> {
|
||||
const result = await this.run(['remote', '--verbose']);
|
||||
const lines = result.stdout.trim().split('\n').filter(l => !!l);
|
||||
const remotes: Remote[] = [];
|
||||
const remotes: MutableRemote[] = [];
|
||||
|
||||
for (const line of lines) {
|
||||
const parts = line.split(/\s/);
|
||||
@@ -1348,6 +1450,10 @@ export class Repository {
|
||||
async getBranch(name: string): Promise<Branch> {
|
||||
if (name === 'HEAD') {
|
||||
return this.getHEAD();
|
||||
} else if (/^@/.test(name)) {
|
||||
const symbolicFullNameResult = await this.run(['rev-parse', '--symbolic-full-name', name]);
|
||||
const symbolicFullName = symbolicFullNameResult.stdout.trim();
|
||||
name = symbolicFullName || name;
|
||||
}
|
||||
|
||||
const result = await this.run(['rev-parse', name]);
|
||||
|
||||
@@ -1,57 +0,0 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
'use strict';
|
||||
|
||||
function* filter<T>(it: IterableIterator<T>, condition: (t: T, i: number) => boolean): IterableIterator<T> {
|
||||
let i = 0;
|
||||
for (let t of it) {
|
||||
if (condition(t, i++)) {
|
||||
yield t;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function* map<T, R>(it: IterableIterator<T>, fn: (t: T, i: number) => R): IterableIterator<R> {
|
||||
let i = 0;
|
||||
for (let t of it) {
|
||||
yield fn(t, i++);
|
||||
}
|
||||
}
|
||||
|
||||
export interface FunctionalIterator<T> extends Iterable<T> {
|
||||
filter(condition: (t: T, i: number) => boolean): FunctionalIterator<T>;
|
||||
map<R>(fn: (t: T, i: number) => R): FunctionalIterator<R>;
|
||||
toArray(): T[];
|
||||
}
|
||||
|
||||
class FunctionalIteratorImpl<T> implements FunctionalIterator<T> {
|
||||
|
||||
constructor(private iterator: IterableIterator<T>) { }
|
||||
|
||||
filter(condition: (t: T, i: number) => boolean): FunctionalIterator<T> {
|
||||
return new FunctionalIteratorImpl(filter(this.iterator, condition));
|
||||
}
|
||||
|
||||
map<R>(fn: (t: T, i: number) => R): FunctionalIterator<R> {
|
||||
return new FunctionalIteratorImpl(map<T, R>(this.iterator, fn));
|
||||
}
|
||||
|
||||
toArray(): T[] {
|
||||
return Array.from(this.iterator);
|
||||
}
|
||||
|
||||
[Symbol.iterator](): IterableIterator<T> {
|
||||
return this.iterator;
|
||||
}
|
||||
}
|
||||
|
||||
export function iterate<T>(obj: T[] | IterableIterator<T>): FunctionalIterator<T> {
|
||||
if (Array.isArray(obj)) {
|
||||
return new FunctionalIteratorImpl(obj[Symbol.iterator]());
|
||||
}
|
||||
|
||||
return new FunctionalIteratorImpl(obj);
|
||||
}
|
||||
@@ -3,11 +3,10 @@
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
'use strict';
|
||||
|
||||
import * as nls from 'vscode-nls';
|
||||
const localize = nls.loadMessageBundle();
|
||||
import { ExtensionContext, workspace, window, Disposable, commands, OutputChannel } from 'vscode';
|
||||
|
||||
import { ExtensionContext, workspace, window, Disposable, commands, OutputChannel } from 'vscode'; // {{SQL CARBON EDIT}} - remove unused imports
|
||||
import { findGit, Git, IGit } from './git';
|
||||
import { Model } from './model';
|
||||
import { CommandCenter } from './commands';
|
||||
@@ -16,8 +15,12 @@ import { GitDecorations } from './decorationProvider';
|
||||
import { Askpass } from './askpass';
|
||||
import { toDisposable, filterEvent, eventToPromise } from './util';
|
||||
import TelemetryReporter from 'vscode-extension-telemetry';
|
||||
import { API, NoopAPIImpl, APIImpl } from './api';
|
||||
import { GitExtension } from './api/git';
|
||||
import { GitProtocolHandler } from './protocolHandler';
|
||||
import { GitExtensionImpl } from './api/extension';
|
||||
// {{SQL CARBON EDIT}} - remove unused imports
|
||||
// import * as path from 'path';
|
||||
// import * as fs from 'fs';
|
||||
|
||||
const deactivateTasks: { (): Promise<any>; }[] = [];
|
||||
|
||||
@@ -69,7 +72,57 @@ async function createModel(context: ExtensionContext, outputChannel: OutputChann
|
||||
return model;
|
||||
}
|
||||
|
||||
export async function activate(context: ExtensionContext): Promise<API> {
|
||||
// {{SQL CARBON EDIT}} - Comment out function that is unused due to our edit below
|
||||
// async function isGitRepository(folder: WorkspaceFolder): Promise<boolean> {
|
||||
// if (folder.uri.scheme !== 'file') {
|
||||
// return false;
|
||||
// }
|
||||
|
||||
// const dotGit = path.join(folder.uri.fsPath, '.git');
|
||||
|
||||
// try {
|
||||
// const dotGitStat = await new Promise<fs.Stats>((c, e) => fs.stat(dotGit, (err, stat) => err ? e(err) : c(stat)));
|
||||
// return dotGitStat.isDirectory();
|
||||
// } catch (err) {
|
||||
// return false;
|
||||
// }
|
||||
// }
|
||||
|
||||
// {{SQL CARBON EDIT}} - Comment out function that is unused due to our edit below
|
||||
// async function warnAboutMissingGit(): Promise<void> {
|
||||
// const config = workspace.getConfiguration('git');
|
||||
// const shouldIgnore = config.get<boolean>('ignoreMissingGitWarning') === true;
|
||||
|
||||
// if (shouldIgnore) {
|
||||
// return;
|
||||
// }
|
||||
|
||||
// if (!workspace.workspaceFolders) {
|
||||
// return;
|
||||
// }
|
||||
|
||||
// const areGitRepositories = await Promise.all(workspace.workspaceFolders.map(isGitRepository));
|
||||
|
||||
// if (areGitRepositories.every(isGitRepository => !isGitRepository)) {
|
||||
// return;
|
||||
// }
|
||||
|
||||
// const download = localize('downloadgit', "Download Git");
|
||||
// const neverShowAgain = localize('neverShowAgain', "Don't Show Again");
|
||||
// const choice = await window.showWarningMessage(
|
||||
// localize('notfound', "Git not found. Install it or configure it using the 'git.path' setting."),
|
||||
// download,
|
||||
// neverShowAgain
|
||||
// );
|
||||
|
||||
// if (choice === download) {
|
||||
// commands.executeCommand('vscode.open', Uri.parse('https://git-scm.com/'));
|
||||
// } else if (choice === neverShowAgain) {
|
||||
// await config.update('ignoreMissingGitWarning', true, true);
|
||||
// }
|
||||
// }
|
||||
|
||||
export async function activate(context: ExtensionContext): Promise<GitExtension> {
|
||||
const disposables: Disposable[] = [];
|
||||
context.subscriptions.push(new Disposable(() => Disposable.from(...disposables).dispose()));
|
||||
|
||||
@@ -77,7 +130,7 @@ export async function activate(context: ExtensionContext): Promise<API> {
|
||||
commands.registerCommand('git.showOutput', () => outputChannel.show());
|
||||
disposables.push(outputChannel);
|
||||
|
||||
const { name, version, aiKey } = require(context.asAbsolutePath('./package.json')) as { name: string, version: string, aiKey: string };
|
||||
const { name, version, aiKey } = require('../package.json') as { name: string, version: string, aiKey: string };
|
||||
const telemetryReporter = new TelemetryReporter(name, version, aiKey);
|
||||
deactivateTasks.push(() => telemetryReporter.dispose());
|
||||
|
||||
@@ -87,46 +140,32 @@ export async function activate(context: ExtensionContext): Promise<API> {
|
||||
if (!enabled) {
|
||||
const onConfigChange = filterEvent(workspace.onDidChangeConfiguration, e => e.affectsConfiguration('git'));
|
||||
const onEnabled = filterEvent(onConfigChange, () => workspace.getConfiguration('git', null).get<boolean>('enabled') === true);
|
||||
await eventToPromise(onEnabled);
|
||||
const result = new GitExtensionImpl();
|
||||
|
||||
eventToPromise(onEnabled).then(async () => result.model = await createModel(context, outputChannel, telemetryReporter, disposables));
|
||||
return result;
|
||||
}
|
||||
|
||||
try {
|
||||
const model = await createModel(context, outputChannel, telemetryReporter, disposables);
|
||||
return new APIImpl(model);
|
||||
return new GitExtensionImpl(model);
|
||||
} catch (err) {
|
||||
if (!/Git installation not found/.test(err.message || '')) {
|
||||
throw err;
|
||||
}
|
||||
|
||||
// {{SQL CARBON EDIT}} turn-off Git missing prompt
|
||||
//const config = workspace.getConfiguration('git');
|
||||
//const shouldIgnore = config.get<boolean>('ignoreMissingGitWarning') === true;
|
||||
// console.warn(err.message);
|
||||
// outputChannel.appendLine(err.message);
|
||||
|
||||
// if (!shouldIgnore) {
|
||||
// console.warn(err.message);
|
||||
// outputChannel.appendLine(err.message);
|
||||
// outputChannel.show();
|
||||
// warnAboutMissingGit();
|
||||
|
||||
// const download = localize('downloadgit', "Download Git");
|
||||
// const neverShowAgain = localize('neverShowAgain', "Don't Show Again");
|
||||
// const choice = await window.showWarningMessage(
|
||||
// localize('notfound', "Git not found. Install it or configure it using the 'git.path' setting."),
|
||||
// download,
|
||||
// neverShowAgain
|
||||
// );
|
||||
|
||||
// if (choice === download) {
|
||||
// commands.executeCommand('vscode.open', Uri.parse('https://git-scm.com/'));
|
||||
// } else if (choice === neverShowAgain) {
|
||||
// await config.update('ignoreMissingGitWarning', true, true);
|
||||
// }
|
||||
// }
|
||||
|
||||
return new NoopAPIImpl();
|
||||
return new GitExtensionImpl();
|
||||
}
|
||||
}
|
||||
|
||||
async function checkGitVersion(info: IGit): Promise<void> {
|
||||
// {{SQL CARBON EDIT}} - Rename info to _info to prevent error due to unused variable
|
||||
async function checkGitVersion(_info: IGit): Promise<void> {
|
||||
|
||||
// {{SQL CARBON EDIT}}
|
||||
// remove Git version check for azuredatastudio
|
||||
|
||||
@@ -3,17 +3,16 @@
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
'use strict';
|
||||
|
||||
import { workspace, WorkspaceFoldersChangeEvent, Uri, window, Event, EventEmitter, QuickPickItem, Disposable, SourceControl, SourceControlResourceGroup, TextEditor, Memento, OutputChannel } from 'vscode';
|
||||
import { Repository, RepositoryState } from './repository';
|
||||
import { memoize, sequentialize, debounce } from './decorators';
|
||||
import { dispose, anyEvent, filterEvent, isDescendant, firstIndex } from './util';
|
||||
import { Git, GitErrorCodes } from './git';
|
||||
import { Git } from './git';
|
||||
import * as path from 'path';
|
||||
import * as fs from 'fs';
|
||||
import * as nls from 'vscode-nls';
|
||||
import { fromGitUri } from './uri';
|
||||
import { GitErrorCodes } from './api/git';
|
||||
|
||||
const localize = nls.loadMessageBundle();
|
||||
|
||||
@@ -107,6 +106,18 @@ export class Model {
|
||||
children
|
||||
.filter(child => child !== '.git')
|
||||
.forEach(child => this.openRepository(path.join(root, child)));
|
||||
|
||||
const folderConfig = workspace.getConfiguration('git', folder.uri);
|
||||
const paths = folderConfig.get<string[]>('scanRepositories') || [];
|
||||
|
||||
for (const possibleRepositoryPath of paths) {
|
||||
if (path.isAbsolute(possibleRepositoryPath)) {
|
||||
console.warn(localize('not supported', "Absolute paths not supported in 'git.scanRepositories' setting."));
|
||||
continue;
|
||||
}
|
||||
|
||||
this.openRepository(path.join(root, possibleRepositoryPath));
|
||||
}
|
||||
} catch (err) {
|
||||
// noop
|
||||
}
|
||||
@@ -241,17 +252,28 @@ export class Model {
|
||||
const changeListener = repository.onDidChangeRepository(uri => this._onDidChangeRepository.fire({ repository, uri }));
|
||||
const originalResourceChangeListener = repository.onDidChangeOriginalResource(uri => this._onDidChangeOriginalResource.fire({ repository, uri }));
|
||||
|
||||
const shouldDetectSubmodules = workspace
|
||||
.getConfiguration('git', Uri.file(repository.root))
|
||||
.get<boolean>('detectSubmodules') as boolean;
|
||||
|
||||
const submodulesLimit = workspace
|
||||
.getConfiguration('git', Uri.file(repository.root))
|
||||
.get<number>('detectSubmodulesLimit') as number;
|
||||
|
||||
const checkForSubmodules = () => {
|
||||
if (!shouldDetectSubmodules) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (repository.submodules.length > submodulesLimit) {
|
||||
window.showWarningMessage(localize('too many submodules', "The '{0}' repository has {1} submodules which won't be opened automatically. You can still open each one individually by opening a file within.", path.basename(repository.root), repository.submodules.length));
|
||||
statusListener.dispose();
|
||||
}
|
||||
|
||||
this.scanSubmodules(repository, submodulesLimit);
|
||||
repository.submodules
|
||||
.slice(0, submodulesLimit)
|
||||
.map(r => path.join(repository.root, r.path))
|
||||
.forEach(p => this.eventuallyScanPossibleGitRepository(p));
|
||||
};
|
||||
|
||||
const statusListener = repository.onDidRunGitStatus(checkForSubmodules);
|
||||
@@ -273,21 +295,6 @@ export class Model {
|
||||
this._onDidOpenRepository.fire(repository);
|
||||
}
|
||||
|
||||
private scanSubmodules(repository: Repository, limit: number): void {
|
||||
const shouldScanSubmodules = workspace
|
||||
.getConfiguration('git', Uri.file(repository.root))
|
||||
.get<boolean>('detectSubmodules') === true;
|
||||
|
||||
if (!shouldScanSubmodules) {
|
||||
return;
|
||||
}
|
||||
|
||||
repository.submodules
|
||||
.slice(0, limit)
|
||||
.map(r => path.join(repository.root, r.path))
|
||||
.forEach(p => this.eventuallyScanPossibleGitRepository(p));
|
||||
}
|
||||
|
||||
close(repository: Repository): void {
|
||||
const openRepository = this.getOpenRepository(repository);
|
||||
|
||||
|
||||
@@ -3,8 +3,6 @@
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
'use strict';
|
||||
|
||||
import { UriHandler, Uri, window, Disposable, commands } from 'vscode';
|
||||
import { dispose } from './util';
|
||||
import * as querystring from 'querystring';
|
||||
|
||||
@@ -3,10 +3,8 @@
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
'use strict';
|
||||
|
||||
import { commands, Uri, Command, EventEmitter, Event, scm, SourceControl, SourceControlInputBox, SourceControlResourceGroup, SourceControlResourceState, SourceControlResourceDecorations, SourceControlInputBoxValidation, Disposable, ProgressLocation, window, workspace, WorkspaceEdit, ThemeColor, DecorationData, Memento, SourceControlInputBoxValidationType } from 'vscode';
|
||||
import { Repository as BaseRepository, Ref, Branch, Remote, Commit, GitErrorCodes, Stash, RefType, GitError, Submodule, DiffOptions } from './git';
|
||||
import { Repository as BaseRepository, Commit, Stash, GitError, Submodule, CommitOptions, ForcePushMode } from './git';
|
||||
import { anyEvent, filterEvent, eventToPromise, dispose, find, isDescendant, IDisposable, onceEvent, EmptyDisposable, debounceEvent } from './util';
|
||||
import { memoize, throttle, debounce } from './decorators';
|
||||
import { toGitUri } from './uri';
|
||||
@@ -15,6 +13,7 @@ import * as path from 'path';
|
||||
import * as nls from 'vscode-nls';
|
||||
import * as fs from 'fs';
|
||||
import { StatusBarCommands } from './statusbar';
|
||||
import { Branch, Ref, Remote, RefType, GitErrorCodes, Status } from './api/git';
|
||||
|
||||
const timeout = (millis: number) => new Promise(c => setTimeout(c, millis));
|
||||
|
||||
@@ -25,33 +24,12 @@ function getIconUri(iconName: string, theme: string): Uri {
|
||||
return Uri.file(path.join(iconsRootPath, theme, `${iconName}.svg`));
|
||||
}
|
||||
|
||||
export enum RepositoryState {
|
||||
export const enum RepositoryState {
|
||||
Idle,
|
||||
Disposed
|
||||
}
|
||||
|
||||
export enum Status {
|
||||
INDEX_MODIFIED,
|
||||
INDEX_ADDED,
|
||||
INDEX_DELETED,
|
||||
INDEX_RENAMED,
|
||||
INDEX_COPIED,
|
||||
|
||||
MODIFIED,
|
||||
DELETED,
|
||||
UNTRACKED,
|
||||
IGNORED,
|
||||
|
||||
ADDED_BY_US,
|
||||
ADDED_BY_THEM,
|
||||
DELETED_BY_US,
|
||||
DELETED_BY_THEM,
|
||||
BOTH_ADDED,
|
||||
BOTH_DELETED,
|
||||
BOTH_MODIFIED
|
||||
}
|
||||
|
||||
export enum ResourceGroupType {
|
||||
export const enum ResourceGroupType {
|
||||
Merge,
|
||||
Index,
|
||||
WorkingTree
|
||||
@@ -123,6 +101,7 @@ export class Resource implements SourceControlResourceState {
|
||||
case Status.DELETED_BY_US: return Resource.Icons[theme].Conflict;
|
||||
case Status.BOTH_ADDED: return Resource.Icons[theme].Conflict;
|
||||
case Status.BOTH_MODIFIED: return Resource.Icons[theme].Conflict;
|
||||
default: throw new Error('Unknown git status: ' + this.type);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -197,15 +176,19 @@ export class Resource implements SourceControlResourceState {
|
||||
return 'U';
|
||||
case Status.IGNORED:
|
||||
return 'I';
|
||||
case Status.DELETED_BY_THEM:
|
||||
return 'D';
|
||||
case Status.DELETED_BY_US:
|
||||
return 'D';
|
||||
case Status.INDEX_COPIED:
|
||||
case Status.BOTH_DELETED:
|
||||
case Status.ADDED_BY_US:
|
||||
case Status.DELETED_BY_THEM:
|
||||
case Status.ADDED_BY_THEM:
|
||||
case Status.DELETED_BY_US:
|
||||
case Status.BOTH_ADDED:
|
||||
case Status.BOTH_MODIFIED:
|
||||
return 'C';
|
||||
default:
|
||||
throw new Error('Unknown git status: ' + this.type);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -233,6 +216,8 @@ export class Resource implements SourceControlResourceState {
|
||||
case Status.BOTH_ADDED:
|
||||
case Status.BOTH_MODIFIED:
|
||||
return new ThemeColor('gitDecoration.conflictingResourceForeground');
|
||||
default:
|
||||
throw new Error('Unknown git status: ' + this.type);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -259,10 +244,10 @@ export class Resource implements SourceControlResourceState {
|
||||
|
||||
get resourceDecoration(): DecorationData {
|
||||
const title = this.tooltip;
|
||||
const abbreviation = this.letter;
|
||||
const letter = this.letter;
|
||||
const color = this.color;
|
||||
const priority = this.priority;
|
||||
return { bubble: true, source: 'git.resource', title, abbreviation, color, priority };
|
||||
return { bubble: true, source: 'git.resource', title, letter, color, priority };
|
||||
}
|
||||
|
||||
constructor(
|
||||
@@ -274,16 +259,24 @@ export class Resource implements SourceControlResourceState {
|
||||
) { }
|
||||
}
|
||||
|
||||
export enum Operation {
|
||||
export const enum Operation {
|
||||
Status = 'Status',
|
||||
Config = 'Config',
|
||||
Diff = 'Diff',
|
||||
MergeBase = 'MergeBase',
|
||||
Add = 'Add',
|
||||
Remove = 'Remove',
|
||||
RevertFiles = 'RevertFiles',
|
||||
Commit = 'Commit',
|
||||
Clean = 'Clean',
|
||||
Branch = 'Branch',
|
||||
GetBranch = 'GetBranch',
|
||||
SetBranchUpstream = 'SetBranchUpstream',
|
||||
HashObject = 'HashObject',
|
||||
Checkout = 'Checkout',
|
||||
CheckoutTracking = 'CheckoutTracking',
|
||||
Reset = 'Reset',
|
||||
Remote = 'Remote',
|
||||
Fetch = 'Fetch',
|
||||
Pull = 'Pull',
|
||||
Push = 'Push',
|
||||
@@ -302,6 +295,7 @@ export enum Operation {
|
||||
GetObjectDetails = 'GetObjectDetails',
|
||||
SubmoduleUpdate = 'SubmoduleUpdate',
|
||||
RebaseContinue = 'RebaseContinue',
|
||||
Apply = 'Apply'
|
||||
}
|
||||
|
||||
function isReadOnly(operation: Operation): boolean {
|
||||
@@ -310,6 +304,7 @@ function isReadOnly(operation: Operation): boolean {
|
||||
case Operation.GetCommitTemplate:
|
||||
case Operation.CheckIgnore:
|
||||
case Operation.GetObjectDetails:
|
||||
case Operation.MergeBase:
|
||||
return true;
|
||||
default:
|
||||
return false;
|
||||
@@ -381,13 +376,6 @@ class OperationsImpl implements Operations {
|
||||
}
|
||||
}
|
||||
|
||||
export interface CommitOptions {
|
||||
all?: boolean;
|
||||
amend?: boolean;
|
||||
signoff?: boolean;
|
||||
signCommit?: boolean;
|
||||
}
|
||||
|
||||
export interface GitResourceGroup extends SourceControlResourceGroup {
|
||||
resourceStates: Resource[];
|
||||
}
|
||||
@@ -399,11 +387,32 @@ export interface OperationResult {
|
||||
|
||||
class ProgressManager {
|
||||
|
||||
private enabled = false;
|
||||
private disposable: IDisposable = EmptyDisposable;
|
||||
|
||||
constructor(repository: Repository) {
|
||||
const start = onceEvent(filterEvent(repository.onDidChangeOperations, () => repository.operations.shouldShowProgress()));
|
||||
const end = onceEvent(filterEvent(debounceEvent(repository.onDidChangeOperations, 300), () => !repository.operations.shouldShowProgress()));
|
||||
constructor(private repository: Repository) {
|
||||
const onDidChange = filterEvent(workspace.onDidChangeConfiguration, e => e.affectsConfiguration('git', Uri.file(this.repository.root)));
|
||||
onDidChange(_ => this.updateEnablement());
|
||||
this.updateEnablement();
|
||||
}
|
||||
|
||||
private updateEnablement(): void {
|
||||
const config = workspace.getConfiguration('git', Uri.file(this.repository.root));
|
||||
|
||||
if (config.get<boolean>('showProgress')) {
|
||||
this.enable();
|
||||
} else {
|
||||
this.disable();
|
||||
}
|
||||
}
|
||||
|
||||
private enable(): void {
|
||||
if (this.enabled) {
|
||||
return;
|
||||
}
|
||||
|
||||
const start = onceEvent(filterEvent(this.repository.onDidChangeOperations, () => this.repository.operations.shouldShowProgress()));
|
||||
const end = onceEvent(filterEvent(debounceEvent(this.repository.onDidChangeOperations, 300), () => !this.repository.operations.shouldShowProgress()));
|
||||
|
||||
const setup = () => {
|
||||
this.disposable = start(() => {
|
||||
@@ -413,17 +422,26 @@ class ProgressManager {
|
||||
};
|
||||
|
||||
setup();
|
||||
this.enabled = true;
|
||||
}
|
||||
|
||||
private disable(): void {
|
||||
if (!this.enabled) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.disposable.dispose();
|
||||
this.disposable = EmptyDisposable;
|
||||
this.enabled = false;
|
||||
}
|
||||
|
||||
dispose(): void {
|
||||
this.disposable.dispose();
|
||||
this.disable();
|
||||
}
|
||||
}
|
||||
|
||||
export class Repository implements Disposable {
|
||||
|
||||
private static readonly InputValidationLength = 72;
|
||||
|
||||
private _onDidChangeRepository = new EventEmitter<Uri>();
|
||||
readonly onDidChangeRepository: Event<Uri> = this._onDidChangeRepository.event;
|
||||
|
||||
@@ -521,6 +539,7 @@ export class Repository implements Disposable {
|
||||
|
||||
private isRepositoryHuge = false;
|
||||
private didWarnAboutLimit = false;
|
||||
private isFreshRepository: boolean | undefined = undefined;
|
||||
private disposables: Disposable[] = [];
|
||||
|
||||
constructor(
|
||||
@@ -530,15 +549,27 @@ export class Repository implements Disposable {
|
||||
const fsWatcher = workspace.createFileSystemWatcher('**');
|
||||
this.disposables.push(fsWatcher);
|
||||
|
||||
const onWorkspaceChange = anyEvent(fsWatcher.onDidChange, fsWatcher.onDidCreate, fsWatcher.onDidDelete);
|
||||
const onRepositoryChange = filterEvent(onWorkspaceChange, uri => isDescendant(repository.root, uri.fsPath));
|
||||
const onRelevantRepositoryChange = filterEvent(onRepositoryChange, uri => !/\/\.git(\/index\.lock)?$/.test(uri.path));
|
||||
const workspaceFilter = (uri: Uri) => isDescendant(repository.root, uri.fsPath);
|
||||
const onWorkspaceDelete = filterEvent(fsWatcher.onDidDelete, workspaceFilter);
|
||||
const onWorkspaceChange = filterEvent(anyEvent(fsWatcher.onDidChange, fsWatcher.onDidCreate), workspaceFilter);
|
||||
const onRepositoryDotGitDelete = filterEvent(onWorkspaceDelete, uri => /\/\.git$/.test(uri.path));
|
||||
const onRepositoryChange = anyEvent(onWorkspaceDelete, onWorkspaceChange);
|
||||
|
||||
// relevant repository changes are:
|
||||
// - DELETE .git folder
|
||||
// - ANY CHANGE within .git folder except .git itself and .git/index.lock
|
||||
const onRelevantRepositoryChange = anyEvent(
|
||||
onRepositoryDotGitDelete,
|
||||
filterEvent(onRepositoryChange, uri => !/\/\.git(\/index\.lock)?$/.test(uri.path))
|
||||
);
|
||||
|
||||
onRelevantRepositoryChange(this.onFSChange, this, this.disposables);
|
||||
|
||||
const onRelevantGitChange = filterEvent(onRelevantRepositoryChange, uri => /\/\.git\//.test(uri.path));
|
||||
onRelevantGitChange(this._onDidChangeRepository.fire, this._onDidChangeRepository, this.disposables);
|
||||
|
||||
this._sourceControl = scm.createSourceControl('git', 'Git', Uri.file(repository.root));
|
||||
const root = Uri.file(repository.root);
|
||||
this._sourceControl = scm.createSourceControl('git', 'Git', root);
|
||||
this._sourceControl.inputBox.placeholder = localize('commitMessage', "Message (press {0} to commit)");
|
||||
this._sourceControl.acceptInputCommand = { command: 'git.commitWithInput', title: localize('commit', "Commit"), arguments: [this._sourceControl] };
|
||||
this._sourceControl.quickDiffProvider = this;
|
||||
@@ -549,8 +580,16 @@ export class Repository implements Disposable {
|
||||
this._indexGroup = this._sourceControl.createResourceGroup('index', localize('staged changes', "Staged Changes"));
|
||||
this._workingTreeGroup = this._sourceControl.createResourceGroup('workingTree', localize('changes', "Changes"));
|
||||
|
||||
const updateIndexGroupVisibility = () => {
|
||||
const config = workspace.getConfiguration('git', root);
|
||||
this.indexGroup.hideWhenEmpty = !config.get<boolean>('alwaysShowStagedChangesResourceGroup');
|
||||
};
|
||||
|
||||
const onConfigListener = filterEvent(workspace.onDidChangeConfiguration, e => e.affectsConfiguration('git.alwaysShowStagedChangesResourceGroup', root));
|
||||
onConfigListener(updateIndexGroupVisibility, this, this.disposables);
|
||||
updateIndexGroupVisibility();
|
||||
|
||||
this.mergeGroup.hideWhenEmpty = true;
|
||||
this.indexGroup.hideWhenEmpty = true;
|
||||
|
||||
this.disposables.push(this.mergeGroup);
|
||||
this.disposables.push(this.indexGroup);
|
||||
@@ -615,19 +654,20 @@ export class Repository implements Disposable {
|
||||
end = match ? match.index : text.length;
|
||||
|
||||
const line = text.substring(start, end);
|
||||
const threshold = Math.max(config.get<number>('inputValidationLength') || 72, 0) || 72;
|
||||
|
||||
if (line.length <= Repository.InputValidationLength) {
|
||||
if (line.length <= threshold) {
|
||||
if (setting !== 'always') {
|
||||
return;
|
||||
}
|
||||
|
||||
return {
|
||||
message: localize('commitMessageCountdown', "{0} characters left in current line", Repository.InputValidationLength - line.length),
|
||||
message: localize('commitMessageCountdown', "{0} characters left in current line", threshold - line.length),
|
||||
type: SourceControlInputBoxValidationType.Information
|
||||
};
|
||||
} else {
|
||||
return {
|
||||
message: localize('commitMessageWarning', "{0} characters over {1} in current line", line.length - Repository.InputValidationLength, Repository.InputValidationLength),
|
||||
message: localize('commitMessageWarning', "{0} characters over {1} in current line", line.length - threshold, threshold),
|
||||
type: SourceControlInputBoxValidationType.Warning
|
||||
};
|
||||
}
|
||||
@@ -649,19 +689,67 @@ export class Repository implements Disposable {
|
||||
}
|
||||
}
|
||||
|
||||
getConfigs(): Promise<{ key: string; value: string; }[]> {
|
||||
return this.run(Operation.Config, () => this.repository.getConfigs('local'));
|
||||
}
|
||||
|
||||
getConfig(key: string): Promise<string> {
|
||||
return this.run(Operation.Config, () => this.repository.config('local', key));
|
||||
}
|
||||
|
||||
setConfig(key: string, value: string): Promise<string> {
|
||||
return this.run(Operation.Config, () => this.repository.config('local', key, value));
|
||||
}
|
||||
|
||||
@throttle
|
||||
async status(): Promise<void> {
|
||||
await this.run(Operation.Status);
|
||||
}
|
||||
|
||||
diff(path: string, options: DiffOptions = {}): Promise<string> {
|
||||
return this.run(Operation.Diff, () => this.repository.diff(path, options));
|
||||
diff(cached?: boolean): Promise<string> {
|
||||
return this.run(Operation.Diff, () => this.repository.diff(cached));
|
||||
}
|
||||
|
||||
diffWithHEAD(path: string): Promise<string> {
|
||||
return this.run(Operation.Diff, () => this.repository.diffWithHEAD(path));
|
||||
}
|
||||
|
||||
diffWith(ref: string, path: string): Promise<string> {
|
||||
return this.run(Operation.Diff, () => this.repository.diffWith(ref, path));
|
||||
}
|
||||
|
||||
diffIndexWithHEAD(path: string): Promise<string> {
|
||||
return this.run(Operation.Diff, () => this.repository.diffIndexWithHEAD(path));
|
||||
}
|
||||
|
||||
diffIndexWith(ref: string, path: string): Promise<string> {
|
||||
return this.run(Operation.Diff, () => this.repository.diffIndexWith(ref, path));
|
||||
}
|
||||
|
||||
diffBlobs(object1: string, object2: string): Promise<string> {
|
||||
return this.run(Operation.Diff, () => this.repository.diffBlobs(object1, object2));
|
||||
}
|
||||
|
||||
diffBetween(ref1: string, ref2: string, path: string): Promise<string> {
|
||||
return this.run(Operation.Diff, () => this.repository.diffBetween(ref1, ref2, path));
|
||||
}
|
||||
|
||||
getMergeBase(ref1: string, ref2: string): Promise<string> {
|
||||
return this.run(Operation.MergeBase, () => this.repository.getMergeBase(ref1, ref2));
|
||||
}
|
||||
|
||||
async hashObject(data: string): Promise<string> {
|
||||
return this.run(Operation.HashObject, () => this.repository.hashObject(data));
|
||||
}
|
||||
|
||||
async add(resources: Uri[]): Promise<void> {
|
||||
await this.run(Operation.Add, () => this.repository.add(resources.map(r => r.fsPath)));
|
||||
}
|
||||
|
||||
async rm(resources: Uri[]): Promise<void> {
|
||||
await this.run(Operation.Remove, () => this.repository.rm(resources.map(r => r.fsPath)));
|
||||
}
|
||||
|
||||
async stage(resource: Uri, contents: string): Promise<void> {
|
||||
const relativePath = path.relative(this.repository.root, resource.fsPath).replace(/\\/g, '/');
|
||||
await this.run(Operation.Stage, () => this.repository.stage(relativePath, contents));
|
||||
@@ -745,8 +833,8 @@ export class Repository implements Disposable {
|
||||
});
|
||||
}
|
||||
|
||||
async branch(name: string): Promise<void> {
|
||||
await this.run(Operation.Branch, () => this.repository.branch(name, true));
|
||||
async branch(name: string, _checkout: boolean, _ref?: string): Promise<void> {
|
||||
await this.run(Operation.Branch, () => this.repository.branch(name, _checkout, _ref));
|
||||
}
|
||||
|
||||
async deleteBranch(name: string, force?: boolean): Promise<void> {
|
||||
@@ -757,6 +845,14 @@ export class Repository implements Disposable {
|
||||
await this.run(Operation.RenameBranch, () => this.repository.renameBranch(name));
|
||||
}
|
||||
|
||||
async getBranch(name: string): Promise<Branch> {
|
||||
return await this.run(Operation.GetBranch, () => this.repository.getBranch(name));
|
||||
}
|
||||
|
||||
async setBranchUpstream(name: string, upstream: string): Promise<void> {
|
||||
await this.run(Operation.SetBranchUpstream, () => this.repository.setBranchUpstream(name, upstream));
|
||||
}
|
||||
|
||||
async merge(ref: string): Promise<void> {
|
||||
await this.run(Operation.Merge, () => this.repository.merge(ref));
|
||||
}
|
||||
@@ -769,6 +865,10 @@ export class Repository implements Disposable {
|
||||
await this.run(Operation.Checkout, () => this.repository.checkout(treeish, []));
|
||||
}
|
||||
|
||||
async checkoutTracking(treeish: string): Promise<void> {
|
||||
await this.run(Operation.CheckoutTracking, () => this.repository.checkout(treeish, [], { track: true }));
|
||||
}
|
||||
|
||||
async getCommit(ref: string): Promise<Commit> {
|
||||
return await this.repository.getCommit(ref);
|
||||
}
|
||||
@@ -781,11 +881,33 @@ export class Repository implements Disposable {
|
||||
await this.run(Operation.DeleteRef, () => this.repository.deleteRef(ref));
|
||||
}
|
||||
|
||||
async addRemote(name: string, url: string): Promise<void> {
|
||||
await this.run(Operation.Remote, () => this.repository.addRemote(name, url));
|
||||
}
|
||||
|
||||
async removeRemote(name: string): Promise<void> {
|
||||
await this.run(Operation.Remote, () => this.repository.removeRemote(name));
|
||||
}
|
||||
|
||||
@throttle
|
||||
async fetch(): Promise<void> {
|
||||
async fetchDefault(): Promise<void> {
|
||||
await this.run(Operation.Fetch, () => this.repository.fetch());
|
||||
}
|
||||
|
||||
@throttle
|
||||
async fetchPrune(): Promise<void> {
|
||||
await this.run(Operation.Fetch, () => this.repository.fetch({ prune: true }));
|
||||
}
|
||||
|
||||
@throttle
|
||||
async fetchAll(): Promise<void> {
|
||||
await this.run(Operation.Fetch, () => this.repository.fetch({ all: true }));
|
||||
}
|
||||
|
||||
async fetch(remote?: string, ref?: string): Promise<void> {
|
||||
await this.run(Operation.Fetch, () => this.repository.fetch({ remote, ref }));
|
||||
}
|
||||
|
||||
@throttle
|
||||
async pullWithRebase(head: Branch | undefined): Promise<void> {
|
||||
let remote: string | undefined;
|
||||
@@ -796,11 +918,18 @@ export class Repository implements Disposable {
|
||||
branch = `${head.upstream.name}`;
|
||||
}
|
||||
|
||||
await this.run(Operation.Pull, () => this.repository.pull(true, remote, branch));
|
||||
const config = workspace.getConfiguration('git', Uri.file(this.root));
|
||||
const fetchOnPull = config.get<boolean>('fetchOnPull');
|
||||
|
||||
if (fetchOnPull) {
|
||||
await this.run(Operation.Pull, () => this.repository.pull(true));
|
||||
} else {
|
||||
await this.run(Operation.Pull, () => this.repository.pull(true, remote, branch));
|
||||
}
|
||||
}
|
||||
|
||||
@throttle
|
||||
async pull(head: Branch | undefined): Promise<void> {
|
||||
async pull(head?: Branch): Promise<void> {
|
||||
let remote: string | undefined;
|
||||
let branch: string | undefined;
|
||||
|
||||
@@ -809,15 +938,29 @@ export class Repository implements Disposable {
|
||||
branch = `${head.upstream.name}`;
|
||||
}
|
||||
|
||||
await this.run(Operation.Pull, () => this.repository.pull(false, remote, branch));
|
||||
const config = workspace.getConfiguration('git', Uri.file(this.root));
|
||||
const fetchOnPull = config.get<boolean>('fetchOnPull');
|
||||
|
||||
if (fetchOnPull) {
|
||||
await this.run(Operation.Pull, () => this.repository.pull(false));
|
||||
} else {
|
||||
await this.run(Operation.Pull, () => this.repository.pull(false, remote, branch));
|
||||
}
|
||||
}
|
||||
|
||||
async pullFrom(rebase?: boolean, remote?: string, branch?: string): Promise<void> {
|
||||
await this.run(Operation.Pull, () => this.repository.pull(rebase, remote, branch));
|
||||
const config = workspace.getConfiguration('git', Uri.file(this.root));
|
||||
const fetchOnPull = config.get<boolean>('fetchOnPull');
|
||||
|
||||
if (fetchOnPull) {
|
||||
await this.run(Operation.Pull, () => this.repository.pull(rebase));
|
||||
} else {
|
||||
await this.run(Operation.Pull, () => this.repository.pull(rebase, remote, branch));
|
||||
}
|
||||
}
|
||||
|
||||
@throttle
|
||||
async push(head: Branch): Promise<void> {
|
||||
async push(head: Branch, forcePushMode?: ForcePushMode): Promise<void> {
|
||||
let remote: string | undefined;
|
||||
let branch: string | undefined;
|
||||
|
||||
@@ -826,15 +969,15 @@ export class Repository implements Disposable {
|
||||
branch = `${head.name}:${head.upstream.name}`;
|
||||
}
|
||||
|
||||
await this.run(Operation.Push, () => this.repository.push(remote, branch));
|
||||
await this.run(Operation.Push, () => this.repository.push(remote, branch, undefined, undefined, forcePushMode));
|
||||
}
|
||||
|
||||
async pushTo(remote?: string, name?: string, setUpstream: boolean = false): Promise<void> {
|
||||
await this.run(Operation.Push, () => this.repository.push(remote, name, setUpstream));
|
||||
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));
|
||||
}
|
||||
|
||||
async pushTags(remote?: string): Promise<void> {
|
||||
await this.run(Operation.Push, () => this.repository.push(remote, undefined, false, true));
|
||||
async pushTags(remote?: string, forcePushMode?: ForcePushMode): Promise<void> {
|
||||
await this.run(Operation.Push, () => this.repository.push(remote, undefined, false, true, forcePushMode));
|
||||
}
|
||||
|
||||
@throttle
|
||||
@@ -859,7 +1002,14 @@ export class Repository implements Disposable {
|
||||
}
|
||||
|
||||
await this.run(Operation.Sync, async () => {
|
||||
await this.repository.pull(rebase, remoteName, pullBranch);
|
||||
const config = workspace.getConfiguration('git', Uri.file(this.root));
|
||||
const fetchOnPull = config.get<boolean>('fetchOnPull');
|
||||
|
||||
if (fetchOnPull) {
|
||||
await this.repository.pull(rebase);
|
||||
} else {
|
||||
await this.repository.pull(rebase, remoteName, pullBranch);
|
||||
}
|
||||
|
||||
const remote = this.remotes.find(r => r.name === remoteName);
|
||||
|
||||
@@ -910,6 +1060,10 @@ export class Repository implements Disposable {
|
||||
return this.run(Operation.Show, () => this.repository.detectObjectType(object));
|
||||
}
|
||||
|
||||
async apply(patch: string, reverse?: boolean): Promise<void> {
|
||||
return await this.run(Operation.Apply, () => this.repository.apply(patch, reverse));
|
||||
}
|
||||
|
||||
async getStashes(): Promise<Stash[]> {
|
||||
return await this.repository.getStashes();
|
||||
}
|
||||
@@ -922,6 +1076,10 @@ export class Repository implements Disposable {
|
||||
return await this.run(Operation.Stash, () => this.repository.popStash(index));
|
||||
}
|
||||
|
||||
async applyStash(index?: number): Promise<void> {
|
||||
return await this.run(Operation.Stash, () => this.repository.applyStash(index));
|
||||
}
|
||||
|
||||
async getCommitTemplate(): Promise<string> {
|
||||
return await this.run(Operation.GetCommitTemplate, async () => this.repository.getCommitTemplate());
|
||||
}
|
||||
@@ -1009,7 +1167,7 @@ export class Repository implements Disposable {
|
||||
this._onRunOperation.fire(operation);
|
||||
|
||||
try {
|
||||
const result = await this.retryRun(runOperation);
|
||||
const result = await this.retryRun(operation, runOperation);
|
||||
|
||||
if (!isReadOnly(operation)) {
|
||||
await this.updateModelState();
|
||||
@@ -1030,7 +1188,7 @@ export class Repository implements Disposable {
|
||||
}
|
||||
}
|
||||
|
||||
private async retryRun<T>(runOperation: () => Promise<T> = () => Promise.resolve<any>(null)): Promise<T> {
|
||||
private async retryRun<T>(operation: Operation, runOperation: () => Promise<T> = () => Promise.resolve<any>(null)): Promise<T> {
|
||||
let attempt = 0;
|
||||
|
||||
while (true) {
|
||||
@@ -1038,7 +1196,12 @@ export class Repository implements Disposable {
|
||||
attempt++;
|
||||
return await runOperation();
|
||||
} catch (err) {
|
||||
if (err.gitErrorCode === GitErrorCodes.RepositoryIsLocked && attempt <= 10) {
|
||||
const shouldRetry = attempt <= 10 && (
|
||||
(err.gitErrorCode === GitErrorCodes.RepositoryIsLocked)
|
||||
|| ((operation === Operation.Pull || operation === Operation.Sync || operation === Operation.Fetch) && (err.gitErrorCode === GitErrorCodes.CantLockRef || err.gitErrorCode === GitErrorCodes.CantRebaseMultipleBranches))
|
||||
);
|
||||
|
||||
if (shouldRetry) {
|
||||
// quatratic backoff
|
||||
await timeout(Math.pow(attempt, 2) * 50);
|
||||
} else {
|
||||
@@ -1125,6 +1288,7 @@ export class Repository implements Disposable {
|
||||
case 'M': workingTree.push(new Resource(ResourceGroupType.WorkingTree, uri, Status.MODIFIED, useIcons, renameUri)); break;
|
||||
case 'D': workingTree.push(new Resource(ResourceGroupType.WorkingTree, uri, Status.DELETED, useIcons, renameUri)); break;
|
||||
}
|
||||
return undefined;
|
||||
});
|
||||
|
||||
// set resource groups
|
||||
@@ -1145,23 +1309,37 @@ export class Repository implements Disposable {
|
||||
|
||||
// Disable `Discard All Changes` for "fresh" repositories
|
||||
// https://github.com/Microsoft/vscode/issues/43066
|
||||
commands.executeCommand('setContext', 'gitFreshRepository', !this._HEAD || !this._HEAD.commit);
|
||||
const isFreshRepository = !this._HEAD || !this._HEAD.commit;
|
||||
|
||||
if (this.isFreshRepository !== isFreshRepository) {
|
||||
commands.executeCommand('setContext', 'gitFreshRepository', isFreshRepository);
|
||||
this.isFreshRepository = isFreshRepository;
|
||||
}
|
||||
|
||||
this._onDidChangeStatus.fire();
|
||||
}
|
||||
|
||||
private async getRebaseCommit(): Promise<Commit | undefined> {
|
||||
const rebaseHeadPath = path.join(this.repository.root, '.git', 'REBASE_HEAD');
|
||||
const rebaseApplyPath = path.join(this.repository.root, '.git', 'rebase-apply');
|
||||
const rebaseMergePath = path.join(this.repository.root, '.git', 'rebase-merge');
|
||||
|
||||
try {
|
||||
const rebaseHead = await new Promise<string>((c, e) => fs.readFile(rebaseHeadPath, 'utf8', (err, result) => err ? e(err) : c(result)));
|
||||
const [rebaseApplyExists, rebaseMergePathExists, rebaseHead] = await Promise.all([
|
||||
new Promise<boolean>(c => fs.exists(rebaseApplyPath, c)),
|
||||
new Promise<boolean>(c => fs.exists(rebaseMergePath, c)),
|
||||
new Promise<string>((c, e) => fs.readFile(rebaseHeadPath, 'utf8', (err, result) => err ? e(err) : c(result)))
|
||||
]);
|
||||
if (!rebaseApplyExists && !rebaseMergePathExists) {
|
||||
return undefined;
|
||||
}
|
||||
return await this.getCommit(rebaseHead.trim());
|
||||
} catch (err) {
|
||||
return undefined;
|
||||
}
|
||||
}
|
||||
|
||||
private onFSChange(uri: Uri): void {
|
||||
private onFSChange(_uri: Uri): void {
|
||||
const config = workspace.getConfiguration('git');
|
||||
const autorefresh = config.get<boolean>('autorefresh');
|
||||
|
||||
|
||||
@@ -3,8 +3,6 @@
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
'use strict';
|
||||
|
||||
import { TextDocument, Range, LineChange, Selection } from 'vscode';
|
||||
|
||||
export function applyLineChanges(original: TextDocument, modified: TextDocument, diffs: LineChange[]): string {
|
||||
@@ -21,9 +19,12 @@ export function applyLineChanges(original: TextDocument, modified: TextDocument,
|
||||
let fromLine = diff.modifiedStartLineNumber - 1;
|
||||
let fromCharacter = 0;
|
||||
|
||||
// if this is an insertion at the very end of the document,
|
||||
// then we must start the next range after the last character of the
|
||||
// previous line, in order to take the correct eol
|
||||
if (isInsertion && diff.originalStartLineNumber === original.lineCount) {
|
||||
fromLine = original.lineCount - 1;
|
||||
fromCharacter = original.lineAt(fromLine).range.end.character;
|
||||
fromLine -= 1;
|
||||
fromCharacter = modified.lineAt(fromLine).range.end.character;
|
||||
}
|
||||
|
||||
result.push(modified.getText(new Range(fromLine, fromCharacter, diff.modifiedEndLineNumber, 0)));
|
||||
@@ -76,6 +77,8 @@ export function getModifiedRange(textDocument: TextDocument, diff: LineChange):
|
||||
if (diff.modifiedEndLineNumber === 0) {
|
||||
if (diff.modifiedStartLineNumber === 0) {
|
||||
return new Range(textDocument.lineAt(diff.modifiedStartLineNumber).range.end, textDocument.lineAt(diff.modifiedStartLineNumber).range.start);
|
||||
} else if (textDocument.lineCount === diff.modifiedStartLineNumber) {
|
||||
return new Range(textDocument.lineAt(diff.modifiedStartLineNumber - 1).range.end, textDocument.lineAt(diff.modifiedStartLineNumber - 1).range.end);
|
||||
} else {
|
||||
return new Range(textDocument.lineAt(diff.modifiedStartLineNumber - 1).range.end, textDocument.lineAt(diff.modifiedStartLineNumber).range.start);
|
||||
}
|
||||
|
||||
@@ -3,13 +3,11 @@
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
'use strict';
|
||||
|
||||
import { Disposable, Command, EventEmitter, Event } from 'vscode';
|
||||
import { Branch } from './git';
|
||||
import { Disposable, Command, EventEmitter, Event, workspace, Uri } from 'vscode';
|
||||
import { Repository, Operation } from './repository';
|
||||
import { anyEvent, dispose } from './util';
|
||||
import * as nls from 'vscode-nls';
|
||||
import { Branch } from './api/git';
|
||||
|
||||
const localize = nls.loadMessageBundle();
|
||||
|
||||
@@ -103,7 +101,11 @@ class SyncStatusBar {
|
||||
if (HEAD.ahead || HEAD.behind) {
|
||||
text += this.repository.syncLabel;
|
||||
}
|
||||
command = 'git.sync';
|
||||
|
||||
const config = workspace.getConfiguration('git', Uri.file(this.repository.root));
|
||||
const rebaseWhenSync = config.get<string>('rebaseWhenSync');
|
||||
|
||||
command = rebaseWhenSync ? 'git.syncRebase' : 'git.sync';
|
||||
tooltip = localize('sync changes', "Synchronize Changes");
|
||||
} else {
|
||||
icon = '$(cloud-upload)';
|
||||
|
||||
@@ -3,8 +3,6 @@
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
'use strict';
|
||||
|
||||
import 'mocha';
|
||||
import { GitStatusParser, parseGitCommit, parseGitmodules, parseLsTree, parseLsFiles } from '../git';
|
||||
import * as assert from 'assert';
|
||||
|
||||
2
extensions/git/src/typings/jschardet.d.ts
vendored
2
extensions/git/src/typings/jschardet.d.ts
vendored
@@ -3,7 +3,7 @@ declare module 'jschardet' {
|
||||
encoding: string,
|
||||
confidence: number
|
||||
}
|
||||
export function detect(buffer: NodeBuffer): IDetectedMap;
|
||||
export function detect(buffer: Buffer): IDetectedMap;
|
||||
|
||||
export const Constants: {
|
||||
MINIMUM_THRESHOLD: number,
|
||||
|
||||
@@ -3,8 +3,6 @@
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
'use strict';
|
||||
|
||||
import { Uri } from 'vscode';
|
||||
|
||||
export interface GitUriParams {
|
||||
|
||||
@@ -3,8 +3,6 @@
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
'use strict';
|
||||
|
||||
import { Event } from 'vscode';
|
||||
import { dirname, sep } from 'path';
|
||||
import { Readable } from 'stream';
|
||||
@@ -35,7 +33,7 @@ export function combinedDisposable(disposables: IDisposable[]): IDisposable {
|
||||
export const EmptyDisposable = toDisposable(() => null);
|
||||
|
||||
export function fireEvent<T>(event: Event<T>): Event<T> {
|
||||
return (listener, thisArgs = null, disposables?) => event(_ => listener.call(thisArgs), null, disposables);
|
||||
return (listener, thisArgs = null, disposables?) => event(_ => (listener as any).call(thisArgs), null, disposables);
|
||||
}
|
||||
|
||||
export function mapEvent<I, O>(event: Event<I>, map: (i: I) => O): Event<O> {
|
||||
@@ -46,6 +44,18 @@ export function filterEvent<T>(event: Event<T>, filter: (e: T) => boolean): Even
|
||||
return (listener, thisArgs = null, disposables?) => event(e => filter(e) && listener.call(thisArgs, e), null, disposables);
|
||||
}
|
||||
|
||||
export function latchEvent<T>(event: Event<T>): Event<T> {
|
||||
let firstCall = true;
|
||||
let cache: T;
|
||||
|
||||
return filterEvent(event, value => {
|
||||
let shouldEmit = firstCall || value !== cache;
|
||||
firstCall = false;
|
||||
cache = value;
|
||||
return shouldEmit;
|
||||
});
|
||||
}
|
||||
|
||||
export function anyEvent<T>(...events: Event<T>[]): Event<T> {
|
||||
return (listener, thisArgs = null, disposables?) => {
|
||||
const result = combinedDisposable(events.map(event => event(i => listener.call(thisArgs, i))));
|
||||
@@ -268,7 +278,7 @@ export function readBytes(stream: Readable, bytes: number): Promise<Buffer> {
|
||||
});
|
||||
}
|
||||
|
||||
export enum Encoding {
|
||||
export const enum Encoding {
|
||||
UTF8 = 'utf8',
|
||||
UTF16be = 'utf16be',
|
||||
UTF16le = 'utf16le'
|
||||
|
||||
Reference in New Issue
Block a user