Merge from vscode c58aaab8a1cc22a7139b761166a0d4f37d41e998 (#7880)

* Merge from vscode c58aaab8a1cc22a7139b761166a0d4f37d41e998

* fix pipelines

* fix strict-null-checks

* add missing files
This commit is contained in:
Anthony Dresser
2019-10-21 22:12:22 -07:00
committed by GitHub
parent 7c9be74970
commit 1e22f47304
913 changed files with 18898 additions and 16536 deletions

View File

@@ -28,8 +28,8 @@ function main(argv: string[]): void {
return fatal('Missing pipe');
}
if (process.env['VSCODE_GIT_COMMAND'] === 'fetch') {
return fatal('Skip fetch commands');
if (process.env['VSCODE_GIT_COMMAND'] === 'fetch' && !!process.env['VSCODE_GIT_FETCH_SILENT']) {
return fatal('Skip silent fetch commands');
}
const output = process.env['VSCODE_GIT_ASKPASS_PIPE'] as string;

View File

@@ -100,7 +100,7 @@ export class AutoFetcher {
}
try {
await this.repository.fetchDefault();
await this.repository.fetchDefault({ silent: true });
} catch (err) {
if (err.gitErrorCode === GitErrorCodes.AuthenticationFailed) {
this.disable();

127
extensions/git/src/commands.ts Executable file → Normal file
View File

@@ -132,6 +132,20 @@ class HEADItem implements QuickPickItem {
get alwaysShow(): boolean { return true; }
}
class AddRemoteItem implements QuickPickItem {
constructor(private cc: CommandCenter) { }
get label(): string { return localize('add remote', '$(plus) Add a new remote...'); }
get description(): string { return ''; }
get alwaysShow(): boolean { return true; }
async run(repository: Repository): Promise<void> {
await this.cc.addRemote(repository);
}
}
interface CommandOptions {
repository?: boolean;
diff?: boolean;
@@ -493,7 +507,7 @@ export class CommandCenter {
const repositoryPath = await window.withProgress(
opts,
(_, token) => this.git.clone(url!, parentPath, token)
(progress, token) => this.git.clone(url!, parentPath, progress, token)
);
let message = localize('proposeopen', "Would you like to open the cloned repository?");
@@ -1249,11 +1263,13 @@ export class CommandCenter {
promptToSaveFilesBeforeCommit = 'never';
}
const enableSmartCommit = config.get<boolean>('enableSmartCommit') === true;
if (promptToSaveFilesBeforeCommit !== 'never') {
let documents = workspace.textDocuments
.filter(d => !d.isUntitled && d.isDirty && isDescendant(repository.root, d.uri.fsPath));
if (promptToSaveFilesBeforeCommit === 'staged') {
if (promptToSaveFilesBeforeCommit === 'staged' || repository.indexGroup.resourceStates.length > 0) {
documents = documents
.filter(d => repository.indexGroup.resourceStates.some(s => s.resourceUri.path === d.uri.fsPath));
}
@@ -1275,7 +1291,6 @@ export class CommandCenter {
}
}
const enableSmartCommit = config.get<boolean>('enableSmartCommit') === true;
const enableCommitSigning = config.get<boolean>('enableCommitSigning') === true;
const noStagedChanges = repository.indexGroup.resourceStates.length === 0;
const noUnstagedChanges = repository.workingTreeGroup.resourceStates.length === 0;
@@ -1360,28 +1375,38 @@ export class CommandCenter {
private async commitWithAnyInput(repository: Repository, opts?: CommitOptions): Promise<void> {
const message = repository.inputBox.value;
const getCommitMessage = async () => {
if (message) {
return message;
let _message: string | undefined = message;
if (!_message) {
let value: string | undefined = undefined;
if (opts && opts.amend && repository.HEAD && repository.HEAD.commit) {
value = (await repository.getCommit(repository.HEAD.commit)).message;
}
const branchName = repository.headShortName;
let placeHolder: string;
if (branchName) {
placeHolder = localize('commitMessageWithHeadLabel2', "Message (commit on '{0}')", branchName);
} else {
placeHolder = localize('commit message', "Commit message");
}
_message = await window.showInputBox({
value,
placeHolder,
prompt: localize('provide commit message', "Please provide a commit message"),
ignoreFocusOut: true
});
}
let value: string | undefined = undefined;
if (opts && opts.amend && repository.HEAD && repository.HEAD.commit) {
value = (await repository.getCommit(repository.HEAD.commit)).message;
}
return await window.showInputBox({
value,
placeHolder: localize('commit message', "Commit message"),
prompt: localize('provide commit message', "Please provide a commit message"),
ignoreFocusOut: true
});
return _message ? repository.cleanUpCommitEditMessage(_message) : _message;
};
const didCommit = await this.smartCommit(repository, getCommitMessage, opts);
if (message && didCommit) {
repository.inputBox.value = await repository.getCommitTemplate();
repository.inputBox.value = await repository.getInputTemplate();
}
}
@@ -1458,6 +1483,15 @@ export class CommandCenter {
const commit = await repository.getCommit('HEAD');
if (commit.parents.length > 1) {
const yes = localize('undo commit', "Undo merge commit");
const result = await window.showWarningMessage(localize('merge commit', "The last commit was a merge commit. Are you sure you want to undo it?"), yes);
if (result !== yes) {
return;
}
}
if (commit.parents.length > 0) {
await repository.reset('HEAD~');
} else {
@@ -1483,7 +1517,6 @@ export class CommandCenter {
const quickpick = window.createQuickPick();
quickpick.items = picks;
quickpick.placeholder = placeHolder;
quickpick.ignoreFocusOut = true;
quickpick.show();
const choice = await new Promise<QuickPickItem | undefined>(c => quickpick.onDidAccept(() => c(quickpick.activeItems[0])));
@@ -1722,7 +1755,7 @@ 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 branchPicks = remoteRefsFiltered.map(r => ({ label: r.name! }));
const branchPlaceHolder = localize('pick branch pull', "Pick a branch to pull from");
const branchPick = await window.showQuickPick(branchPicks, { placeHolder: branchPlaceHolder });
@@ -1829,15 +1862,24 @@ export class CommandCenter {
}
} else {
const branchName = repository.HEAD.name;
const picks = remotes.filter(r => r.pushUrl !== undefined).map(r => ({ label: r.name, description: r.pushUrl! }));
const addRemote = new AddRemoteItem(this);
const picks = [...remotes.filter(r => r.pushUrl !== undefined).map(r => ({ label: r.name, description: r.pushUrl })), addRemote];
const placeHolder = localize('pick remote', "Pick a remote to publish the branch '{0}' to:", branchName);
const pick = await window.showQuickPick(picks, { placeHolder });
const choice = await window.showQuickPick(picks, { placeHolder });
if (!pick) {
if (!choice) {
return;
}
await repository.pushTo(pick.label, branchName, undefined, forcePushMode);
if (choice === addRemote) {
const newRemote = await this.addRemote(repository);
if (newRemote) {
await repository.pushTo(newRemote, branchName, undefined, forcePushMode);
}
} else {
await repository.pushTo(choice.label, branchName, undefined, forcePushMode);
}
}
}
@@ -1872,7 +1914,7 @@ export class CommandCenter {
}
@command('git.addRemote', { repository: true })
async addRemote(repository: Repository): Promise<void> {
async addRemote(repository: Repository): Promise<string | undefined> {
const remotes = repository.remotes;
const sanitize = (name: string) => {
@@ -1914,6 +1956,8 @@ export class CommandCenter {
}
await repository.addRemote(name, url);
return name;
}
@command('git.removeRemote', { repository: true })
@@ -2029,19 +2073,25 @@ export class CommandCenter {
return;
}
const addRemote = new AddRemoteItem(this);
const picks = [...repository.remotes.map(r => ({ label: r.name, description: r.pushUrl })), addRemote];
const branchName = repository.HEAD && repository.HEAD.name || '';
const selectRemote = async () => {
const picks = repository.remotes.map(r => r.name);
const placeHolder = localize('pick remote', "Pick a remote to publish the branch '{0}' to:", branchName);
return await window.showQuickPick(picks, { placeHolder });
};
const choice = remotes.length === 1 ? remotes[0].name : await selectRemote();
const placeHolder = localize('pick remote', "Pick a remote to publish the branch '{0}' to:", branchName);
const choice = await window.showQuickPick(picks, { placeHolder });
if (!choice) {
return;
}
await repository.pushTo(choice, branchName, true);
if (choice === addRemote) {
const newRemote = await this.addRemote(repository);
if (newRemote) {
await repository.pushTo(newRemote, branchName, true);
}
} else {
await repository.pushTo(choice.label, branchName, true);
}
}
@command('git.ignore')
@@ -2069,6 +2119,19 @@ export class CommandCenter {
await this.runByRepository(resources, async (repository, resources) => repository.ignore(resources));
}
@command('git.revealInExplorer')
async revealInExplorer(resourceState: SourceControlResourceState): Promise<void> {
if (!resourceState) {
return;
}
if (!(resourceState.resourceUri instanceof Uri)) {
return;
}
await commands.executeCommand('revealInExplorer', resourceState.resourceUri);
}
private async _stash(repository: Repository, includeUntracked = false): Promise<void> {
const noUnstagedChanges = repository.workingTreeGroup.resourceStates.length === 0;
const noStagedChanges = repository.indexGroup.resourceStates.length === 0;

View File

@@ -12,10 +12,12 @@ 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, splitInChunks, Limiter } from './util';
import { CancellationToken } from 'vscode';
import { CancellationToken, Progress } from 'vscode';
import { URI } from 'vscode-uri';
import { detectEncoding } from './encoding';
import { Ref, RefType, Branch, Remote, GitErrorCodes, LogOptions, Change, Status } from './api/git';
import * as byline from 'byline';
import { StringDecoder } from 'string_decoder';
// https://github.com/microsoft/vscode/issues/65693
const MAX_CLI_LENGTH = 30000;
@@ -163,6 +165,7 @@ export interface SpawnOptions extends cp.SpawnOptions {
encoding?: string;
log?: boolean;
cancellationToken?: CancellationToken;
onSpawn?: (childProcess: cp.ChildProcess) => void;
}
async function exec(child: cp.ChildProcess, cancellationToken?: CancellationToken): Promise<IExecutionResult<Buffer>> {
@@ -341,7 +344,7 @@ export class Git {
return;
}
async clone(url: string, parentPath: string, cancellationToken?: CancellationToken): Promise<string> {
async clone(url: string, parentPath: string, progress: Progress<{ increment: number }>, cancellationToken?: CancellationToken): Promise<string> {
let baseFolderName = decodeURI(url).replace(/[\/]+$/, '').replace(/^.*[\/\\]/, '').replace(/\.git$/, '') || 'repository';
let folderName = baseFolderName;
let folderPath = path.join(parentPath, folderName);
@@ -354,8 +357,36 @@ export class Git {
await mkdirp(parentPath);
const onSpawn = (child: cp.ChildProcess) => {
const decoder = new StringDecoder('utf8');
const lineStream = new byline.LineStream({ encoding: 'utf8' });
child.stderr.on('data', (buffer: Buffer) => lineStream.write(decoder.write(buffer)));
let totalProgress = 0;
let previousProgress = 0;
lineStream.on('data', (line: string) => {
let match: RegExpMatchArray | null = null;
if (match = /Counting objects:\s*(\d+)%/i.exec(line)) {
totalProgress = Math.floor(parseInt(match[1]) * 0.1);
} else if (match = /Compressing objects:\s*(\d+)%/i.exec(line)) {
totalProgress = 10 + Math.floor(parseInt(match[1]) * 0.1);
} else if (match = /Receiving objects:\s*(\d+)%/i.exec(line)) {
totalProgress = 20 + Math.floor(parseInt(match[1]) * 0.4);
} else if (match = /Resolving deltas:\s*(\d+)%/i.exec(line)) {
totalProgress = 60 + Math.floor(parseInt(match[1]) * 0.4);
}
if (totalProgress !== previousProgress) {
progress.report({ increment: totalProgress - previousProgress });
previousProgress = totalProgress;
}
});
};
try {
await this.exec(parentPath, ['clone', url.includes(' ') ? encodeURI(url) : url, folderPath], { cancellationToken });
await this.exec(parentPath, ['clone', url.includes(' ') ? encodeURI(url) : url, folderPath, '--progress'], { cancellationToken, onSpawn });
} catch (err) {
if (err.stderr) {
err.stderr = err.stderr.replace(/^Cloning.+$/m, '').trim();
@@ -370,7 +401,8 @@ export class Git {
async getRepositoryRoot(repositoryPath: string): Promise<string> {
const result = await this.exec(repositoryPath, ['rev-parse', '--show-toplevel']);
return path.normalize(result.stdout.trim());
// Keep trailing spaces which are part of the directory name
return path.normalize(result.stdout.trimLeft().replace(/(\r\n|\r|\n)+$/, ''));
}
async getRepositoryDotGit(repositoryPath: string): Promise<string> {
@@ -401,6 +433,10 @@ export class Git {
private async _exec(args: string[], options: SpawnOptions = {}): Promise<IExecutionResult<string>> {
const child = this.spawn(args, options);
if (options.onSpawn) {
options.onSpawn(child);
}
if (options.input) {
child.stdin.end(options.input, 'utf8');
}
@@ -1366,8 +1402,9 @@ export class Repository {
await this.run(args);
}
async fetch(options: { remote?: string, ref?: string, all?: boolean, prune?: boolean, depth?: number } = {}): Promise<void> {
async fetch(options: { remote?: string, ref?: string, all?: boolean, prune?: boolean, depth?: number, silent?: boolean } = {}): Promise<void> {
const args = ['fetch'];
const spawnOptions: SpawnOptions = {};
if (options.remote) {
args.push(options.remote);
@@ -1387,8 +1424,12 @@ export class Repository {
args.push(`--depth=${options.depth}`);
}
if (options.silent) {
spawnOptions.env = { 'VSCODE_GIT_FETCH_SILENT': 'true' };
}
try {
await this.run(args);
await this.run(args, spawnOptions);
} catch (err) {
if (/No remote repository specified\./.test(err.stderr || '')) {
err.gitErrorCode = GitErrorCodes.NoRemoteRepositorySpecified;
@@ -1748,6 +1789,23 @@ export class Repository {
}
}
cleanupCommitEditMessage(message: string): string {
//TODO: Support core.commentChar
return message.replace(/^\s*#.*$\n?/gm, '').trim();
}
async getMergeMessage(): Promise<string | undefined> {
const mergeMsgPath = path.join(this.repositoryRoot, '.git', 'MERGE_MSG');
try {
const raw = await readfile(mergeMsgPath, 'utf8');
return raw.trim();
} catch {
return undefined;
}
}
async getCommitTemplate(): Promise<string> {
try {
const result = await this.run(['config', '--get', 'commit.template']);
@@ -1766,7 +1824,7 @@ export class Repository {
}
const raw = await readfile(templatePath, 'utf8');
return raw.replace(/^\s*#.*$\n?/gm, '');
return raw.trim();
} catch (err) {
return '';

View File

@@ -579,6 +579,27 @@ export class Repository implements Disposable {
return this._refs;
}
get headShortName(): string | undefined {
if (!this.HEAD) {
return;
}
const HEAD = this.HEAD;
if (HEAD.name) {
return HEAD.name;
}
const tag = this.refs.filter(iref => iref.type === RefType.Tag && iref.commit === HEAD.commit)[0];
const tagName = tag && tag.name;
if (tagName) {
return tagName;
}
return (HEAD.commit || '').substr(0, 8);
}
private _remotes: Remote[] = [];
get remotes(): Remote[] {
return this._remotes;
@@ -729,8 +750,6 @@ export class Repository implements Disposable {
const onDidChangeCountBadge = filterEvent(workspace.onDidChangeConfiguration, e => e.affectsConfiguration('git.countBadge', root));
onDidChangeCountBadge(this.setCountBadge, this, this.disposables);
this.setCountBadge();
this.updateCommitTemplate();
}
validateInput(text: string, position: number): SourceControlInputBoxValidation | undefined {
@@ -806,12 +825,14 @@ export class Repository implements Disposable {
return toGitUri(uri, '', { replaceFileExtension: true });
}
private async updateCommitTemplate(): Promise<void> {
try {
this._sourceControl.commitTemplate = await this.repository.getCommitTemplate();
} catch (e) {
// noop
async getInputTemplate(): Promise<string> {
const mergeMessage = await this.repository.getMergeMessage();
if (mergeMessage) {
return mergeMessage;
}
return await this.repository.getCommitTemplate();
}
getConfigs(): Promise<{ key: string; value: string; }[]> {
@@ -1033,8 +1054,8 @@ export class Repository implements Disposable {
}
@throttle
async fetchDefault(): Promise<void> {
await this.run(Operation.Fetch, () => this.repository.fetch());
async fetchDefault(options: { silent?: boolean } = {}): Promise<void> {
await this.run(Operation.Fetch, () => this.repository.fetch(options));
}
@throttle
@@ -1236,6 +1257,10 @@ export class Repository implements Disposable {
return await this.run(Operation.GetCommitTemplate, async () => this.repository.getCommitTemplate());
}
async cleanUpCommitEditMessage(editMessage: string): Promise<string> {
return this.repository.cleanupCommitEditMessage(editMessage);
}
async ignore(files: Uri[]): Promise<void> {
return await this.run(Operation.Ignore, async () => {
const ignoreFile = `${this.repository.root}${path.sep}.gitignore`;
@@ -1457,9 +1482,9 @@ export class Repository implements Disposable {
const [refs, remotes, submodules, rebaseCommit] = await Promise.all([this.repository.getRefs({ sort }), this.repository.getRemotes(), this.repository.getSubmodules(), this.getRebaseCommit()]);
this._HEAD = HEAD;
this._refs = refs;
this._remotes = remotes;
this._submodules = submodules;
this._refs = refs!;
this._remotes = remotes!;
this._submodules = submodules!;
this.rebaseCommit = rebaseCommit;
const index: Resource[] = [];
@@ -1507,6 +1532,8 @@ export class Repository implements Disposable {
this.setCountBadge();
this._onDidChangeStatus.fire();
this._sourceControl.commitTemplate = await this.getInputTemplate();
}
private setCountBadge(): void {
@@ -1643,15 +1670,11 @@ export class Repository implements Disposable {
}
private updateInputBoxPlaceholder(): void {
const HEAD = this.HEAD;
if (HEAD) {
const tag = this.refs.filter(iref => iref.type === RefType.Tag && iref.commit === HEAD.commit)[0];
const tagName = tag && tag.name;
const head = HEAD.name || tagName || (HEAD.commit || '').substr(0, 8);
const branchName = this.headShortName;
if (branchName) {
// '{0}' will be replaced by the corresponding key-command later in the process, which is why it needs to stay.
this._sourceControl.inputBox.placeholder = localize('commitMessageWithHeadLabel', "Message ({0} to commit on '{1}')", "{0}", head);
this._sourceControl.inputBox.placeholder = localize('commitMessageWithHeadLabel', "Message ({0} to commit on '{1}')", "{0}", branchName);
} else {
this._sourceControl.inputBox.placeholder = localize('commitMessage', "Message ({0} to commit)");
}

View File

@@ -160,7 +160,7 @@ export async function mkdirp(path: string, mode?: number): Promise<boolean> {
if (err.code === 'EEXIST') {
const stat = await nfcall<fs.Stats>(fs.stat, path);
if (stat.isDirectory) {
if (stat.isDirectory()) {
return;
}