Merge VS Code 1.31.1 (#4283)

This commit is contained in:
Matt Irvine
2019-03-15 13:09:45 -07:00
committed by GitHub
parent 7d31575149
commit 86bac90001
1716 changed files with 53308 additions and 48375 deletions

View File

@@ -5,7 +5,7 @@
import { Model } from '../model';
import { Repository as BaseRepository, Resource } from '../repository';
import { InputBox, Git, API, Repository, Remote, RepositoryState, Branch, Ref, Submodule, Commit, Change, RepositoryUIState, Status } from './git';
import { InputBox, Git, API, Repository, Remote, RepositoryState, Branch, Ref, Submodule, Commit, Change, RepositoryUIState, Status, LogOptions } from './git';
import { Event, SourceControlInputBox, Uri, SourceControl } from 'vscode';
import { mapEvent } from '../util';
@@ -76,6 +76,10 @@ export class ApiRepository implements Repository {
return this._repository.setConfig(key, value);
}
getGlobalConfig(key: string): Promise<string> {
return this._repository.getGlobalConfig(key);
}
getObjectDetails(treeish: string, path: string): Promise<{ mode: string; object: string; size: number; }> {
return this._repository.getObjectDetails(treeish, path);
}
@@ -104,19 +108,27 @@ export class ApiRepository implements Repository {
return this._repository.diff(cached);
}
diffWithHEAD(path: string): Promise<string> {
diffWithHEAD(): Promise<Change[]>;
diffWithHEAD(path: string): Promise<string>;
diffWithHEAD(path?: string): Promise<string | Change[]> {
return this._repository.diffWithHEAD(path);
}
diffWith(ref: string, path: string): Promise<string> {
diffWith(ref: string): Promise<Change[]>;
diffWith(ref: string, path: string): Promise<string>;
diffWith(ref: string, path?: string): Promise<string | Change[]> {
return this._repository.diffWith(ref, path);
}
diffIndexWithHEAD(path: string): Promise<string> {
diffIndexWithHEAD(): Promise<Change[]>;
diffIndexWithHEAD(path: string): Promise<string>;
diffIndexWithHEAD(path?: string): Promise<string | Change[]> {
return this._repository.diffIndexWithHEAD(path);
}
diffIndexWith(ref: string, path: string): Promise<string> {
diffIndexWith(ref: string): Promise<Change[]>;
diffIndexWith(ref: string, path: string): Promise<string>;
diffIndexWith(ref: string, path?: string): Promise<string | Change[]> {
return this._repository.diffIndexWith(ref, path);
}
@@ -124,7 +136,9 @@ export class ApiRepository implements Repository {
return this._repository.diffBlobs(object1, object2);
}
diffBetween(ref1: string, ref2: string, path: string): Promise<string> {
diffBetween(ref1: string, ref2: string): Promise<Change[]>;
diffBetween(ref1: string, ref2: string, path: string): Promise<string>;
diffBetween(ref1: string, ref2: string, path?: string): Promise<string | Change[]> {
return this._repository.diffBetween(ref1, ref2, path);
}
@@ -168,17 +182,25 @@ export class ApiRepository implements Repository {
return this._repository.removeRemote(name);
}
fetch(remote?: string | undefined, ref?: string | undefined): Promise<void> {
return this._repository.fetch(remote, ref);
fetch(remote?: string | undefined, ref?: string | undefined, depth?: number | undefined): Promise<void> {
return this._repository.fetch(remote, ref, depth);
}
pull(): Promise<void> {
return this._repository.pull();
pull(unshallow?: boolean): Promise<void> {
return this._repository.pull(undefined, unshallow);
}
push(remoteName?: string, branchName?: string, setUpstream: boolean = false): Promise<void> {
return this._repository.pushTo(remoteName, branchName, setUpstream);
}
blame(path: string): Promise<string> {
return this._repository.blame(path);
}
log(options?: LogOptions): Promise<Commit[]> {
return this._repository.log(options);
}
}
export class ApiGit implements Git {

View File

@@ -41,6 +41,7 @@ export interface Commit {
readonly hash: string;
readonly message: string;
readonly parents: string[];
readonly authorEmail?: string | undefined;
}
export interface Submodule {
@@ -67,6 +68,7 @@ export const enum Status {
DELETED,
UNTRACKED,
IGNORED,
INTENT_TO_ADD,
ADDED_BY_US,
ADDED_BY_THEM,
@@ -109,6 +111,14 @@ export interface RepositoryUIState {
readonly onDidChange: Event<void>;
}
/**
* Log options.
*/
export interface LogOptions {
/** Max number of log entries to retrieve. If not specified, the default is 32. */
readonly maxEntries?: number;
}
export interface Repository {
readonly rootUri: Uri;
@@ -119,6 +129,7 @@ export interface Repository {
getConfigs(): Promise<{ key: string; value: string; }[]>;
getConfig(key: string): Promise<string>;
setConfig(key: string, value: string): Promise<string>;
getGlobalConfig(key: string): Promise<string>;
getObjectDetails(treeish: string, path: string): Promise<{ mode: string, object: string, size: number }>;
detectObjectType(object: string): Promise<{ mimetype: string, encoding?: string }>;
@@ -130,11 +141,16 @@ export interface Repository {
apply(patch: string, reverse?: boolean): Promise<void>;
diff(cached?: boolean): Promise<string>;
diffWithHEAD(): Promise<Change[]>;
diffWithHEAD(path: string): Promise<string>;
diffWith(ref: string): Promise<Change[]>;
diffWith(ref: string, path: string): Promise<string>;
diffIndexWithHEAD(): Promise<Change[]>;
diffIndexWithHEAD(path: string): Promise<string>;
diffIndexWith(ref: string): Promise<Change[]>;
diffIndexWith(ref: string, path: string): Promise<string>;
diffBlobs(object1: string, object2: string): Promise<string>;
diffBetween(ref1: string, ref2: string): Promise<Change[]>;
diffBetween(ref1: string, ref2: string, path: string): Promise<string>;
hashObject(data: string): Promise<string>;
@@ -152,9 +168,12 @@ export interface Repository {
addRemote(name: string, url: string): Promise<void>;
removeRemote(name: string): Promise<void>;
fetch(remote?: string, ref?: string): Promise<void>;
pull(): Promise<void>;
fetch(remote?: string, ref?: string, depth?: number): Promise<void>;
pull(unshallow?: boolean): Promise<void>;
push(remoteName?: string, branchName?: string, setUpstream?: boolean): Promise<void>;
blame(path: string): Promise<string>;
log(options?: LogOptions): Promise<Commit[]>;
}
export interface API {
@@ -214,4 +233,6 @@ export const enum GitErrorCodes {
WrongCase = 'WrongCase',
CantLockRef = 'CantLockRef',
CantRebaseMultipleBranches = 'CantRebaseMultipleBranches',
PatchDoesNotApply = 'PatchDoesNotApply',
NoPathFound = 'NoPathFound'
}

View File

@@ -3,7 +3,7 @@
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { workspace, Disposable, EventEmitter, Memento, window, MessageItem, ConfigurationTarget } from 'vscode';
import { workspace, Disposable, EventEmitter, Memento, window, MessageItem, ConfigurationTarget, Uri } from 'vscode';
import { Repository, Operation } from './repository';
import { eventToPromise, filterEvent, onceEvent } from './util';
import * as nls from 'vscode-nls';
@@ -17,7 +17,6 @@ function isRemoteOperation(operation: Operation): boolean {
export class AutoFetcher {
private static readonly Period = 3 * 60 * 1000 /* three minutes */;
private static DidInformUser = 'autofetch.didInformUser';
private _onDidChange = new EventEmitter<boolean>();
@@ -62,7 +61,7 @@ export class AutoFetcher {
}
if (result === yes) {
const gitConfig = workspace.getConfiguration('git');
const gitConfig = workspace.getConfiguration('git', Uri.file(this.repository.root));
gitConfig.update('autofetch', true, ConfigurationTarget.Global);
}
@@ -70,7 +69,7 @@ export class AutoFetcher {
}
private onConfiguration(): void {
const gitConfig = workspace.getConfiguration('git');
const gitConfig = workspace.getConfiguration('git', Uri.file(this.repository.root));
if (gitConfig.get<boolean>('autofetch') === false) {
this.disable();
@@ -112,8 +111,10 @@ export class AutoFetcher {
return;
}
const timeout = new Promise(c => setTimeout(c, AutoFetcher.Period));
const period = workspace.getConfiguration('git', Uri.file(this.repository.root)).get<number>('autofetchPeriod', 180) * 1000;
const timeout = new Promise(c => setTimeout(c, period));
const whenDisabled = eventToPromise(filterEvent(this.onDidChange, enabled => !enabled));
await Promise.race([timeout, whenDisabled]);
}
}

View File

@@ -103,6 +103,15 @@ class CreateBranchItem implements QuickPickItem {
}
}
class HEADItem implements QuickPickItem {
constructor(private repository: Repository) { }
get label(): string { return 'HEAD'; }
get description(): string { return (this.repository.HEAD && this.repository.HEAD.commit || '').substr(0, 8); }
get alwaysShow(): boolean { return true; }
}
interface CommandOptions {
repository?: boolean;
diff?: boolean;
@@ -154,6 +163,22 @@ async function categorizeResourceByResolution(resources: Resource[]): Promise<{
return { merge, resolved, unresolved, deletionConflicts };
}
function createCheckoutItems(repository: Repository): CheckoutItem[] {
const config = workspace.getConfiguration('git');
const checkoutType = config.get<string>('checkoutType') || 'all';
const includeTags = checkoutType === 'all' || checkoutType === 'tags';
const includeRemotes = checkoutType === 'all' || checkoutType === 'remote';
const heads = repository.refs.filter(ref => ref.type === RefType.Head)
.map(ref => new CheckoutItem(ref));
const tags = (includeTags ? repository.refs.filter(ref => ref.type === RefType.Tag) : [])
.map(ref => new CheckoutTagItem(ref));
const remoteHeads = (includeRemotes ? repository.refs.filter(ref => ref.type === RefType.RemoteHead) : [])
.map(ref => new CheckoutRemoteHeadItem(ref));
return [...heads, ...tags, ...remoteHeads];
}
enum PushType {
Push,
PushTo,
@@ -340,6 +365,7 @@ export class CommandCenter {
case Status.MODIFIED:
case Status.UNTRACKED:
case Status.IGNORED:
case Status.INTENT_TO_ADD:
const repository = this.model.getRepository(resource.resourceUri);
if (!repository) {
@@ -1393,56 +1419,52 @@ export class CommandCenter {
await repository.checkout(treeish);
return true;
}
const config = workspace.getConfiguration('git');
const checkoutType = config.get<string>('checkoutType') || 'all';
const includeTags = checkoutType === 'all' || checkoutType === 'tags';
const includeRemotes = checkoutType === 'all' || checkoutType === 'remote';
const createBranch = new CreateBranchItem(this);
const heads = repository.refs.filter(ref => ref.type === RefType.Head)
.map(ref => new CheckoutItem(ref));
const tags = (includeTags ? repository.refs.filter(ref => ref.type === RefType.Tag) : [])
.map(ref => new CheckoutTagItem(ref));
const remoteHeads = (includeRemotes ? repository.refs.filter(ref => ref.type === RefType.RemoteHead) : [])
.map(ref => new CheckoutRemoteHeadItem(ref));
const picks = [createBranch, ...heads, ...tags, ...remoteHeads];
const picks = [createBranch, ...createCheckoutItems(repository)];
const placeHolder = localize('select a ref to checkout', 'Select a ref to checkout');
const choice = await window.showQuickPick(picks, { placeHolder });
const quickpick = window.createQuickPick();
quickpick.items = picks;
quickpick.placeholder = placeHolder;
quickpick.show();
const choice = await new Promise<QuickPickItem | undefined>(c => quickpick.onDidAccept(() => c(quickpick.activeItems[0])));
quickpick.hide();
if (!choice) {
return false;
}
await choice.run(repository);
if (choice === createBranch) {
await this._branch(repository, quickpick.value);
} else {
await (choice as CheckoutItem).run(repository);
}
return true;
}
@command('git.branch', { repository: true })
async branch(repository: Repository): Promise<void> {
await this._branch(repository);
}
private async _branch(repository: Repository, defaultName?: string): 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();
const branchValidationRegex = config.get<string>('branchValidationRegex')!;
const sanitize = (name: string) => name ?
name.trim().replace(/^\.|\/\.|\.\.|~|\^|:|\/$|\.lock$|\.lock\/|\\|\*|\s|^\s*$|\.$|\[|\]$/g, branchWhitespaceChar)
: name;
if (!name) {
return name;
}
return name.replace(/^\.|\/\.|\.\.|~|\^|:|\/$|\.lock$|\.lock\/|\\|\*|\s|^\s*$|\.$|\[|\]$/g, branchWhitespaceChar);
};
const result = await window.showInputBox({
const rawBranchName = await window.showInputBox({
value: defaultName,
placeHolder: localize('branch name', "Branch name"),
prompt: localize('provide branch name', "Please provide a branch name"),
ignoreFocusOut: true,
validateInput: (name: string) => {
const validateName = new RegExp(branchValidationRegex);
if (validateName.test(sanitize(name))) {
return null;
}
@@ -1451,13 +1473,21 @@ export class CommandCenter {
}
});
const name = sanitize(result || '');
const branchName = sanitize(rawBranchName || '');
if (!name) {
if (!branchName) {
return;
}
await repository.branch(name, true);
const picks = [new HEADItem(repository), ...createCheckoutItems(repository)];
const placeHolder = localize('select a ref to create a new branch from', 'Select a ref to create a new branch from');
const target = await window.showQuickPick(picks, { placeHolder });
if (!target) {
return;
}
await repository.branch(branchName, true, target.label);
}
@command('git.deleteBranch', { repository: true })

View File

@@ -12,9 +12,9 @@ import { EventEmitter } from 'events';
import iconv = require('iconv-lite');
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 { CancellationToken, Uri } from 'vscode';
import { detectEncoding } from './encoding';
import { Ref, RefType, Branch, Remote, GitErrorCodes } from './api/git';
import { Ref, RefType, Branch, Remote, GitErrorCodes, LogOptions, Change, Status } from './api/git';
const readfile = denodeify<string, string | null, string>(fs.readFile);
@@ -114,17 +114,17 @@ function findGitWin32InPath(onLookup: (path: string) => void): Promise<IGit> {
function findGitWin32(onLookup: (path: string) => void): Promise<IGit> {
return findSystemGitWin32(process.env['ProgramW6432'] as string, onLookup)
.then(void 0, () => findSystemGitWin32(process.env['ProgramFiles(x86)'] as string, onLookup))
.then(void 0, () => findSystemGitWin32(process.env['ProgramFiles'] as string, onLookup))
.then(void 0, () => findSystemGitWin32(path.join(process.env['LocalAppData'] as string, 'Programs'), onLookup))
.then(void 0, () => findGitWin32InPath(onLookup));
.then(undefined, () => findSystemGitWin32(process.env['ProgramFiles(x86)'] as string, onLookup))
.then(undefined, () => findSystemGitWin32(process.env['ProgramFiles'] as string, onLookup))
.then(undefined, () => findSystemGitWin32(path.join(process.env['LocalAppData'] as string, 'Programs'), onLookup))
.then(undefined, () => findGitWin32InPath(onLookup));
}
export function findGit(hint: string | undefined, onLookup: (path: string) => void): Promise<IGit> {
const first = hint ? findSpecificGit(hint, onLookup) : Promise.reject<IGit>(null);
return first
.then(void 0, () => {
.then(undefined, () => {
switch (process.platform) {
case 'darwin': return findGitDarwin(onLookup);
case 'win32': return findGitWin32(onLookup);
@@ -248,7 +248,7 @@ export class GitError {
this.error = data.error;
this.message = data.error.message;
} else {
this.error = void 0;
this.error = undefined;
this.message = '';
}
@@ -308,9 +308,11 @@ function getGitErrorCode(stderr: string): string | undefined {
return GitErrorCodes.InvalidBranchName;
}
return void 0;
return undefined;
}
const COMMIT_FORMAT = '%H\n%ae\n%P\n%B';
export class Git {
readonly path: string;
@@ -450,6 +452,7 @@ export interface Commit {
hash: string;
message: string;
parents: string[];
authorEmail?: string | undefined;
}
export class GitStatusParser {
@@ -581,13 +584,13 @@ export function parseGitmodules(raw: string): Submodule[] {
}
export function parseGitCommit(raw: string): Commit | null {
const match = /^([0-9a-f]{40})\n(.*)\n([^]*)$/m.exec(raw.trim());
const match = /^([0-9a-f]{40})\n(.*)\n(.*)\n([^]*)$/m.exec(raw.trim());
if (!match) {
return null;
}
const parents = match[2] ? match[2].split(' ') : [];
return { hash: match[1], message: match[3], parents };
const parents = match[3] ? match[3].split(' ') : [];
return { hash: match[1], message: match[4], parents, authorEmail: match[2] };
}
interface LsTreeElement {
@@ -629,6 +632,10 @@ export interface CommitOptions {
empty?: boolean;
}
export interface PullOptions {
unshallow?: boolean;
}
export enum ForcePushMode {
Force,
ForceWithLease
@@ -697,6 +704,41 @@ export class Repository {
});
}
async log(options?: LogOptions): Promise<Commit[]> {
const maxEntries = options && typeof options.maxEntries === 'number' && options.maxEntries > 0 ? options.maxEntries : 32;
const args = ['log', '-' + maxEntries, `--pretty=format:${COMMIT_FORMAT}%x00%x00`];
const gitResult = await this.run(args);
if (gitResult.exitCode) {
// An empty repo.
return [];
}
const s = gitResult.stdout;
const result: Commit[] = [];
let index = 0;
while (index < s.length) {
let nextIndex = s.indexOf('\x00\x00', index);
if (nextIndex === -1) {
nextIndex = s.length;
}
let entry = s.substr(index, nextIndex - index);
if (entry.startsWith('\n')) {
entry = entry.substring(1);
}
const commit = parseGitCommit(entry);
if (!commit) {
break;
}
result.push(commit);
index = nextIndex + 2;
}
return result;
}
async bufferString(object: string, encoding: string = 'utf8', autoGuessEncoding = false): Promise<string> {
const stdout = await this.buffer(object);
@@ -829,7 +871,15 @@ export class Repository {
args.push('-R');
}
await this.run(args);
try {
await this.run(args);
} catch (err) {
if (/patch does not apply/.test(err.stderr)) {
err.gitErrorCode = GitErrorCodes.PatchDoesNotApply;
}
throw err;
}
}
async diff(cached = false): Promise<string> {
@@ -843,25 +893,53 @@ export class Repository {
return result.stdout;
}
async diffWithHEAD(path: string): Promise<string> {
diffWithHEAD(): Promise<Change[]>;
diffWithHEAD(path: string): Promise<string>;
diffWithHEAD(path?: string | undefined): Promise<string | Change[]>;
async diffWithHEAD(path?: string | undefined): Promise<string | Change[]> {
if (!path) {
return await this.diffFiles(false);
}
const args = ['diff', '--', path];
const result = await this.run(args);
return result.stdout;
}
async diffWith(ref: string, path: string): Promise<string> {
diffWith(ref: string): Promise<Change[]>;
diffWith(ref: string, path: string): Promise<string>;
diffWith(ref: string, path?: string | undefined): Promise<string | Change[]>;
async diffWith(ref: string, path?: string): Promise<string | Change[]> {
if (!path) {
return await this.diffFiles(false, ref);
}
const args = ['diff', ref, '--', path];
const result = await this.run(args);
return result.stdout;
}
async diffIndexWithHEAD(path: string): Promise<string> {
diffIndexWithHEAD(): Promise<Change[]>;
diffIndexWithHEAD(path: string): Promise<string>;
diffIndexWithHEAD(path?: string | undefined): Promise<string | Change[]>;
async diffIndexWithHEAD(path?: string): Promise<string | Change[]> {
if (!path) {
return await this.diffFiles(true);
}
const args = ['diff', '--cached', '--', path];
const result = await this.run(args);
return result.stdout;
}
async diffIndexWith(ref: string, path: string): Promise<string> {
diffIndexWith(ref: string): Promise<Change[]>;
diffIndexWith(ref: string, path: string): Promise<string>;
diffIndexWith(ref: string, path?: string | undefined): Promise<string | Change[]>;
async diffIndexWith(ref: string, path?: string): Promise<string | Change[]> {
if (!path) {
return await this.diffFiles(true, ref);
}
const args = ['diff', '--cached', ref, '--', path];
const result = await this.run(args);
return result.stdout;
@@ -873,13 +951,102 @@ export class Repository {
return result.stdout;
}
async diffBetween(ref1: string, ref2: string, path: string): Promise<string> {
const args = ['diff', `${ref1}...${ref2}`, '--', path];
diffBetween(ref1: string, ref2: string): Promise<Change[]>;
diffBetween(ref1: string, ref2: string, path: string): Promise<string>;
diffBetween(ref1: string, ref2: string, path?: string | undefined): Promise<string | Change[]>;
async diffBetween(ref1: string, ref2: string, path?: string): Promise<string | Change[]> {
const range = `${ref1}...${ref2}`;
if (!path) {
return await this.diffFiles(false, range);
}
const args = ['diff', range, '--', path];
const result = await this.run(args);
return result.stdout.trim();
}
private async diffFiles(cached: boolean, ref?: string): Promise<Change[]> {
const args = ['diff', '--name-status', '-z', '--diff-filter=ADMR'];
if (cached) {
args.push('--cached');
}
if (ref) {
args.push(ref);
}
const gitResult = await this.run(args);
if (gitResult.exitCode) {
return [];
}
const entries = gitResult.stdout.split('\x00');
let index = 0;
const result: Change[] = [];
entriesLoop:
while (index < entries.length - 1) {
const change = entries[index++];
const resourcePath = entries[index++];
if (!change || !resourcePath) {
break;
}
const originalUri = Uri.file(path.isAbsolute(resourcePath) ? resourcePath : path.join(this.repositoryRoot, resourcePath));
let status: Status = Status.UNTRACKED;
// Copy or Rename status comes with a number, e.g. 'R100'. We don't need the number, so we use only first character of the status.
switch (change[0]) {
case 'M':
status = Status.MODIFIED;
break;
case 'A':
status = Status.INDEX_ADDED;
break;
case 'D':
status = Status.DELETED;
break;
// Rename contains two paths, the second one is what the file is renamed/copied to.
case 'R':
if (index >= entries.length) {
break;
}
const newPath = entries[index++];
if (!newPath) {
break;
}
const uri = Uri.file(path.isAbsolute(newPath) ? newPath : path.join(this.repositoryRoot, newPath));
result.push({
uri,
renameUri: uri,
originalUri,
status: Status.INDEX_RENAMED
});
continue;
default:
// Unknown status
break entriesLoop;
}
result.push({
status,
originalUri,
uri: originalUri,
renameUri: originalUri,
});
}
return result;
}
async getMergeBase(ref1: string, ref2: string): Promise<string> {
const args = ['merge-base', ref1, ref2];
const result = await this.run(args);
@@ -1158,7 +1325,7 @@ export class Repository {
await this.run(args);
}
async fetch(options: { remote?: string, ref?: string, all?: boolean, prune?: boolean } = {}): Promise<void> {
async fetch(options: { remote?: string, ref?: string, all?: boolean, prune?: boolean, depth?: number } = {}): Promise<void> {
const args = ['fetch'];
if (options.remote) {
@@ -1175,6 +1342,9 @@ export class Repository {
args.push('--prune');
}
if (typeof options.depth === 'number') {
args.push(`--depth=${options.depth}`);
}
try {
await this.run(args);
@@ -1189,9 +1359,13 @@ export class Repository {
}
}
async pull(rebase?: boolean, remote?: string, branch?: string): Promise<void> {
async pull(rebase?: boolean, remote?: string, branch?: string, options: PullOptions = {}): Promise<void> {
const args = ['pull', '--tags'];
if (options.unshallow) {
args.push('--unshallow');
}
if (rebase) {
args.push('-r');
}
@@ -1263,6 +1437,23 @@ export class Repository {
}
}
async blame(path: string): Promise<string> {
try {
const args = ['blame'];
args.push(path);
let result = await this.run(args);
return result.stdout.trim();
} catch (err) {
if (/^fatal: no such path/.test(err.stderr || '')) {
err.gitErrorCode = GitErrorCodes.NoPathFound;
}
throw err;
}
}
async createStash(message?: string, includeUntracked?: boolean): Promise<void> {
try {
const args = ['stash', 'save'];
@@ -1368,7 +1559,7 @@ export class Repository {
throw new Error('Not in a branch');
}
return { name: result.stdout.trim(), commit: void 0, type: RefType.Head };
return { name: result.stdout.trim(), commit: undefined, type: RefType.Head };
} catch (err) {
const result = await this.run(['rev-parse', 'HEAD']);
@@ -1376,7 +1567,7 @@ export class Repository {
throw new Error('Error parsing HEAD');
}
return { name: void 0, commit: result.stdout.trim(), type: RefType.Head };
return { name: undefined, commit: result.stdout.trim(), type: RefType.Head };
}
}
@@ -1521,7 +1712,7 @@ export class Repository {
}
async getCommit(ref: string): Promise<Commit> {
const result = await this.run(['show', '-s', '--format=%H\n%P\n%B', ref]);
const result = await this.run(['show', '-s', `--format=${COMMIT_FORMAT}`, ref]);
return parseGitCommit(result.stdout) || Promise.reject<Commit>('bad commit format');
}

View File

@@ -13,7 +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';
import { Branch, Ref, Remote, RefType, GitErrorCodes, Status, LogOptions, Change } from './api/git';
const timeout = (millis: number) => new Promise(c => setTimeout(c, millis));
@@ -94,6 +94,7 @@ export class Resource implements SourceControlResourceState {
case Status.INDEX_COPIED: return Resource.Icons[theme].Copied;
case Status.UNTRACKED: return Resource.Icons[theme].Untracked;
case Status.IGNORED: return Resource.Icons[theme].Ignored;
case Status.INTENT_TO_ADD: return Resource.Icons[theme].Added;
case Status.BOTH_DELETED: return Resource.Icons[theme].Conflict;
case Status.ADDED_BY_US: return Resource.Icons[theme].Conflict;
case Status.DELETED_BY_THEM: return Resource.Icons[theme].Conflict;
@@ -116,6 +117,7 @@ export class Resource implements SourceControlResourceState {
case Status.INDEX_COPIED: return localize('index copied', "Index Copied");
case Status.UNTRACKED: return localize('untracked', "Untracked");
case Status.IGNORED: return localize('ignored', "Ignored");
case Status.INTENT_TO_ADD: return localize('intent to add', "Intent to Add");
case Status.BOTH_DELETED: return localize('both deleted', "Both Deleted");
case Status.ADDED_BY_US: return localize('added by us', "Added By Us");
case Status.DELETED_BY_THEM: return localize('deleted by them', "Deleted By Them");
@@ -166,6 +168,7 @@ export class Resource implements SourceControlResourceState {
case Status.MODIFIED:
return 'M';
case Status.INDEX_ADDED:
case Status.INTENT_TO_ADD:
return 'A';
case Status.INDEX_DELETED:
case Status.DELETED:
@@ -201,6 +204,7 @@ export class Resource implements SourceControlResourceState {
case Status.DELETED:
return new ThemeColor('gitDecoration.deletedResourceForeground');
case Status.INDEX_ADDED:
case Status.INTENT_TO_ADD:
return new ThemeColor('gitDecoration.addedResourceForeground');
case Status.INDEX_RENAMED: // todo@joh - special color?
case Status.UNTRACKED:
@@ -295,7 +299,9 @@ export const enum Operation {
GetObjectDetails = 'GetObjectDetails',
SubmoduleUpdate = 'SubmoduleUpdate',
RebaseContinue = 'RebaseContinue',
Apply = 'Apply'
Apply = 'Apply',
Blame = 'Blame',
Log = 'Log',
}
function isReadOnly(operation: Operation): boolean {
@@ -643,18 +649,43 @@ export class Repository implements Disposable {
};
}
let lineNumber = 0;
let start = 0, end;
let match: RegExpExecArray | null;
const regex = /\r?\n/g;
while ((match = regex.exec(text)) && position > match.index) {
start = match.index + match[0].length;
lineNumber++;
}
end = match ? match.index : text.length;
const line = text.substring(start, end);
const threshold = Math.max(config.get<number>('inputValidationLength') || 72, 0) || 72;
let threshold = config.get<number>('inputValidationLength', 50);
if (lineNumber === 0) {
const inputValidationSubjectLength = config.get<number | null>('inputValidationSubjectLength', null);
if (inputValidationSubjectLength !== null) {
threshold = inputValidationSubjectLength;
}
}
// const subjectThreshold =
// Math.max(config.get<number>('inputValidationLength') || 50, config.get<number>('subjectValidationLength') || 50, 0) || 50;
if (line.length <= threshold) {
if (setting !== 'always') {
@@ -697,10 +728,18 @@ export class Repository implements Disposable {
return this.run(Operation.Config, () => this.repository.config('local', key));
}
getGlobalConfig(key: string): Promise<string> {
return this.run(Operation.Config, () => this.repository.config('global', key));
}
setConfig(key: string, value: string): Promise<string> {
return this.run(Operation.Config, () => this.repository.config('local', key, value));
}
log(options?: LogOptions): Promise<Commit[]> {
return this.run(Operation.Log, () => this.repository.log(options));
}
@throttle
async status(): Promise<void> {
await this.run(Operation.Status);
@@ -710,19 +749,31 @@ export class Repository implements Disposable {
return this.run(Operation.Diff, () => this.repository.diff(cached));
}
diffWithHEAD(path: string): Promise<string> {
diffWithHEAD(): Promise<Change[]>;
diffWithHEAD(path: string): Promise<string>;
diffWithHEAD(path?: string | undefined): Promise<string | Change[]>;
diffWithHEAD(path?: string | undefined): Promise<string | Change[]> {
return this.run(Operation.Diff, () => this.repository.diffWithHEAD(path));
}
diffWith(ref: string, path: string): Promise<string> {
diffWith(ref: string): Promise<Change[]>;
diffWith(ref: string, path: string): Promise<string>;
diffWith(ref: string, path?: string | undefined): Promise<string | Change[]>;
diffWith(ref: string, path?: string): Promise<string | Change[]> {
return this.run(Operation.Diff, () => this.repository.diffWith(ref, path));
}
diffIndexWithHEAD(path: string): Promise<string> {
diffIndexWithHEAD(): Promise<Change[]>;
diffIndexWithHEAD(path: string): Promise<string>;
diffIndexWithHEAD(path?: string | undefined): Promise<string | Change[]>;
diffIndexWithHEAD(path?: string): Promise<string | Change[]> {
return this.run(Operation.Diff, () => this.repository.diffIndexWithHEAD(path));
}
diffIndexWith(ref: string, path: string): Promise<string> {
diffIndexWith(ref: string): Promise<Change[]>;
diffIndexWith(ref: string, path: string): Promise<string>;
diffIndexWith(ref: string, path?: string | undefined): Promise<string | Change[]>;
diffIndexWith(ref: string, path?: string): Promise<string | Change[]> {
return this.run(Operation.Diff, () => this.repository.diffIndexWith(ref, path));
}
@@ -730,7 +781,10 @@ export class Repository implements Disposable {
return this.run(Operation.Diff, () => this.repository.diffBlobs(object1, object2));
}
diffBetween(ref1: string, ref2: string, path: string): Promise<string> {
diffBetween(ref1: string, ref2: string): Promise<Change[]>;
diffBetween(ref1: string, ref2: string, path: string): Promise<string>;
diffBetween(ref1: string, ref2: string, path?: string | undefined): Promise<string | Change[]>;
diffBetween(ref1: string, ref2: string, path?: string): Promise<string | Change[]> {
return this.run(Operation.Diff, () => this.repository.diffBetween(ref1, ref2, path));
}
@@ -904,8 +958,8 @@ export class Repository implements Disposable {
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 }));
async fetch(remote?: string, ref?: string, depth?: number): Promise<void> {
await this.run(Operation.Fetch, () => this.repository.fetch({ remote, ref, depth }));
}
@throttle
@@ -918,18 +972,11 @@ export class Repository implements Disposable {
branch = `${head.upstream.name}`;
}
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));
}
return this.pullFrom(true, remote, branch);
}
@throttle
async pull(head?: Branch): Promise<void> {
async pull(head?: Branch, unshallow?: boolean): Promise<void> {
let remote: string | undefined;
let branch: string | undefined;
@@ -938,25 +985,22 @@ export class Repository implements Disposable {
branch = `${head.upstream.name}`;
}
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));
}
return this.pullFrom(false, remote, branch, unshallow);
}
async pullFrom(rebase?: boolean, remote?: string, branch?: string): Promise<void> {
const config = workspace.getConfiguration('git', Uri.file(this.root));
const fetchOnPull = config.get<boolean>('fetchOnPull');
async pullFrom(rebase?: boolean, remote?: string, branch?: string, unshallow?: boolean): Promise<void> {
await this.run(Operation.Pull, async () => {
await this.maybeAutoStash(async () => {
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));
}
if (fetchOnPull) {
await this.repository.pull(rebase, undefined, undefined, { unshallow });
} else {
await this.repository.pull(rebase, remote, branch, { unshallow });
}
});
});
}
@throttle
@@ -980,6 +1024,10 @@ export class Repository implements Disposable {
await this.run(Operation.Push, () => this.repository.push(remote, undefined, false, true, forcePushMode));
}
async blame(path: string): Promise<string> {
return await this.run(Operation.Blame, () => this.repository.blame(path));
}
@throttle
sync(head: Branch): Promise<void> {
return this._sync(head, false);
@@ -1002,26 +1050,28 @@ export class Repository implements Disposable {
}
await this.run(Operation.Sync, async () => {
const config = workspace.getConfiguration('git', Uri.file(this.root));
const fetchOnPull = config.get<boolean>('fetchOnPull');
await this.maybeAutoStash(async () => {
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);
}
if (fetchOnPull) {
await this.repository.pull(rebase);
} else {
await this.repository.pull(rebase, remoteName, pullBranch);
}
const remote = this.remotes.find(r => r.name === remoteName);
const remote = this.remotes.find(r => r.name === remoteName);
if (remote && remote.isReadOnly) {
return;
}
if (remote && remote.isReadOnly) {
return;
}
const shouldPush = this.HEAD && (typeof this.HEAD.ahead === 'number' ? this.HEAD.ahead > 0 : true);
const shouldPush = this.HEAD && (typeof this.HEAD.ahead === 'number' ? this.HEAD.ahead > 0 : true);
if (shouldPush) {
await this.repository.push(remoteName, pushBranch);
}
if (shouldPush) {
await this.repository.push(remoteName, pushBranch);
}
});
});
}
@@ -1102,7 +1152,8 @@ export class Repository implements Disposable {
const text = lastLine.isEmptyOrWhitespace ? `${textToAppend}\n` : `\n${textToAppend}\n`;
edit.insert(document.uri, lastLine.range.end, text);
workspace.applyEdit(edit);
await workspace.applyEdit(edit);
await document.save();
});
}
@@ -1211,6 +1262,24 @@ export class Repository implements Disposable {
}
}
private static KnownHugeFolderNames = ['node_modules'];
private async findKnownHugeFolderPathsToIgnore(): Promise<string[]> {
const folderPaths: string[] = [];
for (const folderName of Repository.KnownHugeFolderNames) {
const folderPath = path.join(this.repository.root, folderName);
if (await new Promise<boolean>(c => fs.exists(folderPath, c))) {
folderPaths.push(folderPath);
}
}
const ignored = await this.checkIgnore(folderPaths);
return folderPaths.filter(p => !ignored.has(p));
}
@throttle
private async updateModelState(): Promise<void> {
const { status, didHitLimit } = await this.repository.getStatus();
@@ -1221,15 +1290,34 @@ export class Repository implements Disposable {
this.isRepositoryHuge = didHitLimit;
if (didHitLimit && !shouldIgnore && !this.didWarnAboutLimit) {
const knownHugeFolderPaths = await this.findKnownHugeFolderPathsToIgnore();
const gitWarn = localize('huge', "The git repository at '{0}' has too many active changes, only a subset of Git features will be enabled.", this.repository.root);
const neverAgain = { title: localize('neveragain', "Don't Show Again") };
window.showWarningMessage(localize('huge', "The git repository at '{0}' has too many active changes, only a subset of Git features will be enabled.", this.repository.root), neverAgain).then(result => {
if (knownHugeFolderPaths.length > 0) {
const folderPath = knownHugeFolderPaths[0];
const folderName = path.basename(folderPath);
const addKnown = localize('add known', "Would you like to add '{0}' to .gitignore?", folderName);
const yes = { title: localize('yes', "Yes") };
const result = await window.showWarningMessage(`${gitWarn} ${addKnown}`, yes, neverAgain);
if (result === neverAgain) {
config.update('ignoreLimitWarning', true, false);
this.didWarnAboutLimit = true;
} else if (result === yes) {
this.ignore([Uri.file(folderPath)]);
}
} else {
const result = await window.showWarningMessage(gitWarn, neverAgain);
if (result === neverAgain) {
config.update('ignoreLimitWarning', true, false);
}
});
this.didWarnAboutLimit = true;
this.didWarnAboutLimit = true;
}
}
let HEAD: Branch | undefined;
@@ -1287,6 +1375,7 @@ export class Repository implements Disposable {
switch (raw.y) {
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;
case 'A': workingTree.push(new Resource(ResourceGroupType.WorkingTree, uri, Status.INTENT_TO_ADD, useIcons, renameUri)); break;
}
return undefined;
});
@@ -1339,6 +1428,22 @@ export class Repository implements Disposable {
}
}
private async maybeAutoStash<T>(runOperation: () => Promise<T>): Promise<T> {
const config = workspace.getConfiguration('git', Uri.file(this.root));
const shouldAutoStash = config.get<boolean>('autoStash')
&& this.workingTreeGroup.resourceStates.some(r => r.type !== Status.UNTRACKED && r.type !== Status.IGNORED);
if (!shouldAutoStash) {
return await runOperation();
}
await this.repository.createStash(undefined, true);
const result = await runOperation();
await this.repository.popStash();
return result;
}
private onFSChange(_uri: Uri): void {
const config = workspace.getConfiguration('git');
const autorefresh = config.get<boolean>('autorefresh');

View File

@@ -13,7 +13,18 @@ export function applyLineChanges(original: TextDocument, modified: TextDocument,
const isInsertion = diff.originalEndLineNumber === 0;
const isDeletion = diff.modifiedEndLineNumber === 0;
result.push(original.getText(new Range(currentLine, 0, isInsertion ? diff.originalStartLineNumber : diff.originalStartLineNumber - 1, 0)));
let endLine = isInsertion ? diff.originalStartLineNumber : diff.originalStartLineNumber - 1;
let endCharacter = 0;
// if this is a deletion at the very end of the document,then we need to account
// for a newline at the end of the last line which may have been deleted
// https://github.com/Microsoft/vscode/issues/59670
if (isDeletion && diff.originalStartLineNumber === original.lineCount) {
endLine -= 1;
endCharacter = original.lineAt(endLine).range.end.character;
}
result.push(original.getText(new Range(currentLine, 0, endLine, endCharacter)));
if (!isDeletion) {
let fromLine = diff.modifiedStartLineNumber - 1;
@@ -114,4 +125,4 @@ export function invertLineChange(diff: LineChange): LineChange {
originalStartLineNumber: diff.modifiedStartLineNumber,
originalEndLineNumber: diff.modifiedEndLineNumber
};
}
}

View File

@@ -177,37 +177,43 @@ suite('git', () => {
suite('parseGitCommit', () => {
test('single parent commit', function () {
const GIT_OUTPUT_SINGLE_PARENT = `52c293a05038d865604c2284aa8698bd087915a1
john.doe@mail.com
8e5a374372b8393906c7e380dbb09349c5385554
This is a commit message.`;
assert.deepEqual(parseGitCommit(GIT_OUTPUT_SINGLE_PARENT), {
hash: '52c293a05038d865604c2284aa8698bd087915a1',
message: 'This is a commit message.',
parents: ['8e5a374372b8393906c7e380dbb09349c5385554']
parents: ['8e5a374372b8393906c7e380dbb09349c5385554'],
authorEmail: 'john.doe@mail.com',
});
});
test('multiple parent commits', function () {
const GIT_OUTPUT_MULTIPLE_PARENTS = `52c293a05038d865604c2284aa8698bd087915a1
john.doe@mail.com
8e5a374372b8393906c7e380dbb09349c5385554 df27d8c75b129ab9b178b386077da2822101b217
This is a commit message.`;
assert.deepEqual(parseGitCommit(GIT_OUTPUT_MULTIPLE_PARENTS), {
hash: '52c293a05038d865604c2284aa8698bd087915a1',
message: 'This is a commit message.',
parents: ['8e5a374372b8393906c7e380dbb09349c5385554', 'df27d8c75b129ab9b178b386077da2822101b217']
parents: ['8e5a374372b8393906c7e380dbb09349c5385554', 'df27d8c75b129ab9b178b386077da2822101b217'],
authorEmail: 'john.doe@mail.com',
});
});
test('no parent commits', function () {
const GIT_OUTPUT_NO_PARENTS = `52c293a05038d865604c2284aa8698bd087915a1
john.doe@mail.com
This is a commit message.`;
assert.deepEqual(parseGitCommit(GIT_OUTPUT_NO_PARENTS), {
hash: '52c293a05038d865604c2284aa8698bd087915a1',
message: 'This is a commit message.',
parents: []
parents: [],
authorEmail: 'john.doe@mail.com',
});
});
});

View File

@@ -69,7 +69,7 @@ export function anyEvent<T>(...events: Event<T>[]): Event<T> {
}
export function done<T>(promise: Promise<T>): Promise<void> {
return promise.then<void>(() => void 0);
return promise.then<void>(() => undefined);
}
export function onceEvent<T>(event: Event<T>): Event<T> {