Merge VS Code 1.23.1 (#1520)

This commit is contained in:
Matt Irvine
2018-06-05 11:24:51 -07:00
committed by GitHub
parent e3baf5c443
commit 0c58f09e59
3651 changed files with 74249 additions and 48599 deletions

View File

@@ -6,37 +6,55 @@
'use strict';
import { Model } from './model';
import { Uri } from 'vscode';
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 interface API {
getRepositories(): Promise<Repository[]>;
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 function createApi(modelPromise: Promise<Model>) {
return {
async getRepositories(): Promise<Repository[]> {
const model = await modelPromise;
export interface API {
getRepositories(): Promise<Repository[]>;
getGitPath(): Promise<string>;
}
return model.repositories.map(repository => ({
rootUri: Uri.file(repository.root),
inputBox: {
set value(value: string) {
repository.inputBox.value = value;
},
get value(): string {
return repository.inputBox.value;
}
}
}));
}
};
export class APIImpl implements API {
constructor(private modelPromise: Promise<Model>) { }
async getGitPath(): Promise<string> {
const model = await this.modelPromise;
return model.git.path;
}
async getRepositories(): Promise<Repository[]> {
const model = await this.modelPromise;
return model.repositories.map(repository => new RepositoryImpl(repository));
}
}
export function createApi(modelPromise: Promise<Model>): API {
return new APIImpl(modelPromise);
}

View File

@@ -5,7 +5,7 @@
'use strict';
import { Uri, commands, Disposable, window, workspace, QuickPickItem, OutputChannel, Range, WorkspaceEdit, Position, LineChange, SourceControlResourceState, TextDocumentShowOptions, ViewColumn, ProgressLocation, TextEditor, CancellationTokenSource, StatusBarAlignment } from 'vscode';
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 { Model } from './model';
@@ -328,8 +328,6 @@ export class CommandCenter {
return '';
}
private static cloneId = 0;
@command('git.clone')
async clone(url?: string): Promise<void> {
if (!url) {
@@ -350,15 +348,18 @@ export class CommandCenter {
}
const config = workspace.getConfiguration('git');
let value = config.get<string>('defaultCloneDirectory') || os.homedir();
let defaultCloneDirectory = config.get<string>('defaultCloneDirectory') || os.homedir();
defaultCloneDirectory = defaultCloneDirectory.replace(/^~/, os.homedir());
const parentPath = await window.showInputBox({
prompt: localize('parent', "Parent Directory"),
value,
ignoreFocusOut: true
const uris = await window.showOpenDialog({
canSelectFiles: false,
canSelectFolders: true,
canSelectMany: false,
defaultUri: Uri.file(defaultCloneDirectory),
openLabel: localize('selectFolder', "Select Repository Location")
});
if (!parentPath) {
if (!uris || uris.length === 0) {
/* __GDPR__
"clone" : {
"outcome" : { "classification": "SystemMetaData", "purpose": "FeatureInsight" }
@@ -368,25 +369,33 @@ export class CommandCenter {
return;
}
const tokenSource = new CancellationTokenSource();
const cancelCommandId = `cancelClone${CommandCenter.cloneId++}`;
const commandDisposable = commands.registerCommand(cancelCommandId, () => tokenSource.cancel());
const statusBarItem = window.createStatusBarItem(StatusBarAlignment.Left);
statusBarItem.text = localize('cancel', "$(sync~spin) Cloning repository... Click to cancel");
statusBarItem.tooltip = localize('cancel tooltip', "Cancel clone");
statusBarItem.command = cancelCommandId;
statusBarItem.show();
const clonePromise = this.git.clone(url, parentPath.replace(/^~/, os.homedir()), tokenSource.token);
const uri = uris[0];
const parentPath = uri.fsPath;
try {
window.withProgress({ location: ProgressLocation.SourceControl, title: localize('cloning', "Cloning git repository...") }, () => clonePromise);
const opts = {
location: ProgressLocation.Notification,
title: localize('cloning', "Cloning git repository '{0}'...", url),
cancellable: true
};
const repositoryPath = await clonePromise;
const repositoryPath = await window.withProgress(
opts,
(_, token) => this.git.clone(url!, parentPath, token)
);
const choices = [];
let message = localize('proposeopen', "Would you like to open the cloned repository?");
const open = localize('openrepo', "Open Repository");
const result = await window.showInformationMessage(localize('proposeopen', "Would you like to open the cloned repository?"), open);
choices.push(open);
const addToWorkspace = localize('add', "Add to Workspace");
if (workspace.workspaceFolders) {
message = localize('proposeopen2', "Would you like to open the cloned repository, or add it to the current workspace?");
choices.push(addToWorkspace);
}
const result = await window.showInformationMessage(message, ...choices);
const openFolder = result === open;
/* __GDPR__
@@ -396,8 +405,13 @@ export class CommandCenter {
}
*/
this.telemetryReporter.sendTelemetryEvent('clone', { outcome: 'success' }, { openFolder: openFolder ? 1 : 0 });
const uri = Uri.file(repositoryPath);
if (openFolder) {
commands.executeCommand('vscode.openFolder', Uri.file(repositoryPath));
commands.executeCommand('vscode.openFolder', uri);
} else if (result === addToWorkspace) {
workspace.updateWorkspaceFolders(workspace.workspaceFolders!.length, 0, { uri });
}
} catch (err) {
if (/already exists and is not an empty directory/.test(err && err.stderr || '')) {
@@ -419,9 +433,6 @@ export class CommandCenter {
}
throw err;
} finally {
commandDisposable.dispose();
statusBarItem.dispose();
}
}
@@ -1205,7 +1216,7 @@ export class CommandCenter {
const message = localize('confirm force delete branch', "The branch '{0}' is not fully merged. Delete anyway?", name);
const yes = localize('delete branch', "Delete Branch");
const pick = await window.showWarningMessage(message, yes);
const pick = await window.showWarningMessage(message, { modal: true }, yes);
if (pick === yes) {
await run(true);
@@ -1333,7 +1344,7 @@ export class CommandCenter {
const remoteCharCnt = remotePick.label.length;
repository.pull(false, remotePick.label, branchPick.label.slice(remoteCharCnt + 1));
repository.pullFrom(false, remotePick.label, branchPick.label.slice(remoteCharCnt + 1));
}
@command('git.pull', { repository: true })
@@ -1345,7 +1356,7 @@ export class CommandCenter {
return;
}
await repository.pull();
await repository.pull(repository.HEAD);
}
@command('git.pullRebase', { repository: true })
@@ -1357,7 +1368,7 @@ export class CommandCenter {
return;
}
await repository.pullWithRebase();
await repository.pullWithRebase(repository.HEAD);
}
@command('git.push', { repository: true })
@@ -1375,7 +1386,7 @@ export class CommandCenter {
}
try {
await repository.push();
await repository.push(repository.HEAD);
} catch (err) {
if (err.gitErrorCode !== GitErrorCodes.NoUpstreamBranch) {
throw err;
@@ -1443,7 +1454,7 @@ export class CommandCenter {
const shouldPrompt = config.get<boolean>('confirmSync') === true;
if (shouldPrompt) {
const message = localize('sync is unpredictable', "This action will push and pull commits to and from '{0}'.", HEAD.upstream);
const message = localize('sync is unpredictable', "This action will push and pull commits to and from '{0}/{1}'.", HEAD.upstream.remote, HEAD.upstream.name);
const yes = localize('ok', "OK");
const neverAgain = localize('never again', "OK, Don't Show Again");
const pick = await window.showWarningMessage(message, { modal: true }, yes, neverAgain);
@@ -1456,9 +1467,9 @@ export class CommandCenter {
}
if (rebase) {
await repository.syncRebase();
await repository.syncRebase(HEAD);
} else {
await repository.sync();
await repository.sync(HEAD);
}
}
@@ -1476,7 +1487,7 @@ export class CommandCenter {
return;
}
await repository.sync();
await repository.sync(HEAD);
}));
}
@@ -1635,6 +1646,10 @@ export class CommandCenter {
this.telemetryReporter.sendTelemetryEvent('git.command', { command: id });
return result.catch(async err => {
const options: MessageOptions = {
modal: err.gitErrorCode === GitErrorCodes.DirtyWorkTree
};
let message: string;
switch (err.gitErrorCode) {
@@ -1664,9 +1679,11 @@ export class CommandCenter {
return;
}
options.modal = true;
const outputChannel = this.outputChannel as OutputChannel;
const openOutputChannelChoice = localize('open git log', "Open Git Log");
const choice = await window.showErrorMessage(message, openOutputChannelChoice);
const choice = await window.showErrorMessage(message, options, openOutputChannelChoice);
if (choice === openOutputChannelChoice) {
outputChannel.show();

View File

@@ -0,0 +1,81 @@
/*---------------------------------------------------------------------------------------------
* 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 * as jschardet from 'jschardet';
jschardet.Constants.MINIMUM_THRESHOLD = 0.2;
function detectEncodingByBOM(buffer: NodeBuffer): string | null {
if (!buffer || buffer.length < 2) {
return null;
}
const b0 = buffer.readUInt8(0);
const b1 = buffer.readUInt8(1);
// UTF-16 BE
if (b0 === 0xFE && b1 === 0xFF) {
return 'utf16be';
}
// UTF-16 LE
if (b0 === 0xFF && b1 === 0xFE) {
return 'utf16le';
}
if (buffer.length < 3) {
return null;
}
const b2 = buffer.readUInt8(2);
// UTF-8
if (b0 === 0xEF && b1 === 0xBB && b2 === 0xBF) {
return 'utf8';
}
return null;
}
const IGNORE_ENCODINGS = [
'ascii',
'utf-8',
'utf-16',
'utf-32'
];
const JSCHARDET_TO_ICONV_ENCODINGS: { [name: string]: string } = {
'ibm866': 'cp866',
'big5': 'cp950'
};
export function detectEncoding(buffer: Buffer): string | null {
let result = detectEncodingByBOM(buffer);
if (result) {
return result;
}
const detected = jschardet.detect(buffer);
if (!detected || !detected.encoding) {
return null;
}
const encoding = detected.encoding;
// Ignore encodings that cannot guess correctly
// (http://chardet.readthedocs.io/en/latest/supported-encodings.html)
if (0 <= IGNORE_ENCODINGS.indexOf(encoding.toLowerCase())) {
return null;
}
const normalizedEncodingName = encoding.replace(/[^a-zA-Z0-9]/g, '').toLowerCase();
const mapped = JSCHARDET_TO_ICONV_ENCODINGS[normalizedEncodingName];
return mapped || normalizedEncodingName;
}

View File

@@ -15,6 +15,7 @@ import iconv = require('iconv-lite');
import * as filetype from 'file-type';
import { assign, uniqBy, groupBy, denodeify, IDisposable, toDisposable, dispose, mkdirp, readBytes, detectUnicodeEncoding, Encoding, onceEvent } from './util';
import { CancellationToken } from 'vscode';
import { detectEncoding } from './encoding';
const readfile = denodeify<string, string | null, string>(fs.readFile);
@@ -53,8 +54,13 @@ export interface Ref {
remote?: string;
}
export interface UpstreamRef {
remote: string;
name: string;
}
export interface Branch extends Ref {
upstream?: string;
upstream?: UpstreamRef;
ahead?: number;
behind?: number;
}
@@ -361,14 +367,14 @@ function getGitErrorCode(stderr: string): string | undefined {
export class Git {
private gitPath: string;
readonly path: string;
private env: any;
private _onOutput = new EventEmitter();
get onOutput(): EventEmitter { return this._onOutput; }
constructor(options: IGitOptions) {
this.gitPath = options.gitPath;
this.path = options.gitPath;
this.env = options.env || {};
}
@@ -382,11 +388,29 @@ export class Git {
}
async clone(url: string, parentPath: string, cancellationToken?: CancellationToken): Promise<string> {
const folderName = decodeURI(url).replace(/^.*\//, '').replace(/\.git$/, '') || 'repository';
const folderPath = path.join(parentPath, folderName);
let baseFolderName = decodeURI(url).replace(/^.*\//, '').replace(/\.git$/, '') || 'repository';
let folderName = baseFolderName;
let folderPath = path.join(parentPath, folderName);
let count = 1;
while (count < 20 && await new Promise(c => fs.exists(folderPath, c))) {
folderName = `${baseFolderName}-${count++}`;
folderPath = path.join(parentPath, folderName);
}
await mkdirp(parentPath);
await this.exec(parentPath, ['clone', url, folderPath], { cancellationToken });
try {
await this.exec(parentPath, ['clone', url, folderPath], { cancellationToken });
} catch (err) {
if (err.stderr) {
err.stderr = err.stderr.replace(/^Cloning.+$/m, '').trim();
err.stderr = err.stderr.replace(/^ERROR:\s+/, '').trim();
}
throw err;
}
return folderPath;
}
@@ -442,7 +466,7 @@ export class Git {
}
spawn(args: string[], options: SpawnOptions = {}): cp.ChildProcess {
if (!this.gitPath) {
if (!this.path) {
throw new Error('git could not be found in the system.');
}
@@ -464,7 +488,7 @@ export class Git {
this.log(`> git ${args.join(' ')}\n`);
}
return cp.spawn(this.gitPath, args, options);
return cp.spawn(this.path, args, options);
}
private log(output: string): void {
@@ -654,9 +678,16 @@ export class Repository {
return result.stdout;
}
async bufferString(object: string, encoding: string = 'utf8'): Promise<string> {
async bufferString(object: string, encoding: string = 'utf8', autoGuessEncoding = false): Promise<string> {
const stdout = await this.buffer(object);
return iconv.decode(stdout, iconv.encodingExists(encoding) ? encoding : 'utf8');
if (autoGuessEncoding) {
encoding = detectEncoding(stdout) || encoding;
}
encoding = iconv.encodingExists(encoding) ? encoding : 'utf8';
return iconv.decode(stdout, encoding);
}
async buffer(object: string): Promise<Buffer> {
@@ -988,7 +1019,7 @@ export class Repository {
}
async pull(rebase?: boolean, remote?: string, branch?: string): Promise<void> {
const args = ['pull'];
const args = ['pull', '--tags'];
if (rebase) {
args.push('-r');
@@ -1008,7 +1039,8 @@ export class Repository {
err.gitErrorCode = GitErrorCodes.NoUserNameConfigured;
} else if (/Could not read from remote repository/.test(err.stderr || '')) {
err.gitErrorCode = GitErrorCodes.RemoteConnectionError;
} 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/.test(err.stderr)) {
} 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;
}
@@ -1218,10 +1250,16 @@ export class Repository {
const commit = result.stdout.trim();
try {
const res2 = await this.run(['rev-parse', '--symbolic-full-name', '--abbrev-ref', name + '@{u}']);
const upstream = res2.stdout.trim();
const res2 = await this.run(['rev-parse', '--symbolic-full-name', name + '@{u}']);
const fullUpstream = res2.stdout.trim();
const match = /^refs\/remotes\/([^/]+)\/(.+)$/.exec(fullUpstream);
const res3 = await this.run(['rev-list', '--left-right', name + '...' + upstream]);
if (!match) {
throw new Error(`Could not parse upstream branch: ${fullUpstream}`);
}
const upstream = { remote: match[1], name: match[2] };
const res3 = await this.run(['rev-list', '--left-right', name + '...' + fullUpstream]);
let ahead = 0, behind = 0;
let i = 0;

View File

@@ -17,6 +17,7 @@ import { Askpass } from './askpass';
import { toDisposable, filterEvent, eventToPromise } from './util';
import TelemetryReporter from 'vscode-extension-telemetry';
import { API, createApi } from './api';
import { GitProtocolHandler } from './protocolHandler';
let telemetryReporter: TelemetryReporter;
@@ -51,7 +52,8 @@ async function init(context: ExtensionContext, outputChannel: OutputChannel, dis
disposables.push(
new CommandCenter(git, model, outputChannel, telemetryReporter),
new GitContentProvider(model),
new GitDecorations(model)
new GitDecorations(model),
new GitProtocolHandler()
);
await checkGitVersion(info);

View File

@@ -66,7 +66,7 @@ export class Model {
private disposables: Disposable[] = [];
constructor(private git: Git, private globalState: Memento, private outputChannel: OutputChannel) {
constructor(readonly git: Git, private globalState: Memento, private outputChannel: OutputChannel) {
workspace.onDidChangeWorkspaceFolders(this.onDidChangeWorkspaceFolders, this, this.disposables);
this.onDidChangeWorkspaceFolders({ added: workspace.workspaceFolders || [], removed: [] });
@@ -227,14 +227,17 @@ export class Model {
const changeListener = repository.onDidChangeRepository(uri => this._onDidChangeRepository.fire({ repository, uri }));
const originalResourceChangeListener = repository.onDidChangeOriginalResource(uri => this._onDidChangeOriginalResource.fire({ repository, uri }));
const submodulesLimit = workspace
.getConfiguration('git', Uri.file(repository.root))
.get<number>('detectSubmodulesLimit') as number;
const checkForSubmodules = () => {
if (repository.submodules.length > 10) {
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();
return;
}
this.scanSubmodules(repository);
this.scanSubmodules(repository, submodulesLimit);
};
const statusListener = repository.onDidRunGitStatus(checkForSubmodules);
@@ -256,7 +259,7 @@ export class Model {
this._onDidOpenRepository.fire(repository);
}
private scanSubmodules(repository: Repository): void {
private scanSubmodules(repository: Repository, limit: number): void {
const shouldScanSubmodules = workspace
.getConfiguration('git', Uri.file(repository.root))
.get<boolean>('detectSubmodules') === true;
@@ -266,6 +269,7 @@ export class Model {
}
repository.submodules
.slice(0, limit)
.map(r => path.join(repository.root, r.path))
.forEach(p => this.eventuallyScanPossibleGitRepository(p));
}

View File

@@ -0,0 +1,39 @@
/*---------------------------------------------------------------------------------------------
* 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 { ProtocolHandler, Uri, window, Disposable, commands } from 'vscode';
import { dispose } from './util';
import * as querystring from 'querystring';
export class GitProtocolHandler implements ProtocolHandler {
private disposables: Disposable[] = [];
constructor() {
this.disposables.push(window.registerProtocolHandler(this));
}
handleUri(uri: Uri): void {
switch (uri.path) {
case '/clone': this.clone(uri);
}
}
private clone(uri: Uri): void {
const data = querystring.parse(uri.query);
if (!data.url) {
console.warn('Failed to open URI:', uri);
}
commands.executeCommand('git.clone', data.url);
}
dispose(): void {
this.disposables = dispose(this.disposables);
}
}

View File

@@ -728,24 +728,48 @@ export class Repository implements Disposable {
}
@throttle
async pullWithRebase(): Promise<void> {
await this.run(Operation.Pull, () => this.repository.pull(true));
async pullWithRebase(head: Branch | undefined): Promise<void> {
let remote: string | undefined;
let branch: string | undefined;
if (head && head.name && head.upstream) {
remote = head.upstream.remote;
branch = `${head.upstream.name}`;
}
await this.run(Operation.Pull, () => this.repository.pull(true, remote, branch));
}
@throttle
async pull(rebase?: boolean, remote?: string, name?: string): Promise<void> {
await this.run(Operation.Pull, () => this.repository.pull(rebase, remote, name));
}
async pull(head: Branch | undefined): Promise<void> {
let remote: string | undefined;
let branch: string | undefined;
@throttle
async push(): Promise<void> {
await this.run(Operation.Push, () => this.repository.push());
if (head && head.name && head.upstream) {
remote = head.upstream.remote;
branch = `${head.upstream.name}`;
}
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));
}
@throttle
async push(head: Branch): Promise<void> {
let remote: string | undefined;
let branch: string | undefined;
if (head && head.name && head.upstream) {
remote = head.upstream.remote;
branch = `${head.name}:${head.upstream.name}`;
}
await this.run(Operation.Push, () => this.repository.push(remote, branch));
}
async pushTo(remote?: string, name?: string, setUpstream: boolean = false): Promise<void> {
await this.run(Operation.Push, () => this.repository.push(remote, name, setUpstream));
}
@@ -754,47 +778,53 @@ export class Repository implements Disposable {
await this.run(Operation.Push, () => this.repository.push(remote, undefined, false, true));
}
private async _sync(rebase: boolean): Promise<void> {
@throttle
sync(head: Branch): Promise<void> {
return this._sync(head, false);
}
@throttle
async syncRebase(head: Branch): Promise<void> {
return this._sync(head, true);
}
private async _sync(head: Branch, rebase: boolean): Promise<void> {
let remote: string | undefined;
let pullBranch: string | undefined;
let pushBranch: string | undefined;
if (head.name && head.upstream) {
remote = head.upstream.remote;
pullBranch = `${head.upstream.name}`;
pushBranch = `${head.name}:${head.upstream.name}`;
}
await this.run(Operation.Sync, async () => {
await this.repository.pull(rebase);
await this.repository.pull(rebase, remote, pullBranch);
const shouldPush = this.HEAD && typeof this.HEAD.ahead === 'number' ? this.HEAD.ahead > 0 : true;
if (shouldPush) {
await this.repository.push();
await this.repository.push(remote, pushBranch);
}
});
}
@throttle
sync(): Promise<void> {
return this._sync(false);
}
@throttle
async syncRebase(): Promise<void> {
return this._sync(true);
}
async show(ref: string, filePath: string): Promise<string> {
return await this.run(Operation.Show, async () => {
return this.run(Operation.Show, () => {
const relativePath = path.relative(this.repository.root, filePath).replace(/\\/g, '/');
const configFiles = workspace.getConfiguration('files', Uri.file(filePath));
const encoding = configFiles.get<string>('encoding');
const defaultEncoding = configFiles.get<string>('encoding');
const autoGuessEncoding = configFiles.get<boolean>('autoGuessEncoding');
// TODO@joao: Resource config api
return await this.repository.bufferString(`${ref}:${relativePath}`, encoding);
return this.repository.bufferString(`${ref}:${relativePath}`, defaultEncoding, autoGuessEncoding);
});
}
async buffer(ref: string, filePath: string): Promise<Buffer> {
return await this.run(Operation.Show, async () => {
return this.run(Operation.Show, () => {
const relativePath = path.relative(this.repository.root, filePath).replace(/\\/g, '/');
// const configFiles = workspace.getConfiguration('files', Uri.file(filePath));
// const encoding = configFiles.get<string>('encoding');
// TODO@joao: REsource config api
return await this.repository.buffer(`${ref}:${relativePath}`);
return this.repository.buffer(`${ref}:${relativePath}`);
});
}

View File

@@ -0,0 +1,11 @@
declare module 'jschardet' {
export interface IDetectedMap {
encoding: string,
confidence: number
}
export function detect(buffer: NodeBuffer): IDetectedMap;
export const Constants: {
MINIMUM_THRESHOLD: number,
}
}

View File

@@ -303,6 +303,10 @@ export function detectUnicodeEncoding(buffer: Buffer): Encoding | null {
return null;
}
function isWindowsPath(path: string): boolean {
return /^[a-zA-Z]:\\/.test(path);
}
export function isDescendant(parent: string, descendant: string): boolean {
if (parent === descendant) {
return true;
@@ -312,5 +316,11 @@ export function isDescendant(parent: string, descendant: string): boolean {
parent += sep;
}
// Windows is case insensitive
if (isWindowsPath(parent)) {
parent = parent.toLowerCase();
descendant = descendant.toLowerCase();
}
return descendant.startsWith(parent);
}