Merge remote-tracking branch 'origin/master' into joao/uri-handler

This commit is contained in:
Joao Moreno
2018-07-06 12:49:16 +02:00
625 changed files with 15064 additions and 7992 deletions

View File

@@ -137,6 +137,22 @@ const ImageMimetypes = [
'image/bmp'
];
async function categorizeResourceByResolution(resources: Resource[]): Promise<{ merge: Resource[], resolved: Resource[], unresolved: 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 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 unresolved = [
...merge.filter(s => !isBothAddedOrModified(s)),
...possibleUnresolved.filter((s, i) => unresolvedBothModified[i])
];
return { merge, resolved, unresolved };
}
export class CommandCenter {
private disposables: Disposable[];
@@ -629,20 +645,12 @@ export class CommandCenter {
}
const selection = resourceStates.filter(s => s instanceof Resource) as Resource[];
const merge = selection.filter(s => s.resourceGroupType === ResourceGroupType.Merge);
const bothModified = merge.filter(s => s.type === Status.BOTH_MODIFIED);
const promises = bothModified.map(s => grep(s.resourceUri.fsPath, /^<{7}|^={7}|^>{7}/));
const unresolvedBothModified = await Promise.all<boolean>(promises);
const resolvedConflicts = bothModified.filter((s, i) => !unresolvedBothModified[i]);
const unresolvedConflicts = [
...merge.filter(s => s.type !== Status.BOTH_MODIFIED),
...bothModified.filter((s, i) => unresolvedBothModified[i])
];
const { resolved, unresolved } = await categorizeResourceByResolution(selection);
if (unresolvedConflicts.length > 0) {
const message = unresolvedConflicts.length > 1
? localize('confirm stage files with merge conflicts', "Are you sure you want to stage {0} files with merge conflicts?", unresolvedConflicts.length)
: localize('confirm stage file with merge conflicts', "Are you sure you want to stage {0} with merge conflicts?", path.basename(unresolvedConflicts[0].resourceUri.fsPath));
if (unresolved.length > 0) {
const message = unresolved.length > 1
? localize('confirm stage files with merge conflicts', "Are you sure you want to stage {0} files with merge conflicts?", unresolved.length)
: localize('confirm stage file with merge conflicts', "Are you sure you want to stage {0} with merge conflicts?", path.basename(unresolved[0].resourceUri.fsPath));
const yes = localize('yes', "Yes");
const pick = await window.showWarningMessage(message, { modal: true }, yes);
@@ -653,7 +661,7 @@ export class CommandCenter {
}
const workingTree = selection.filter(s => s.resourceGroupType === ResourceGroupType.WorkingTree);
const scmResources = [...workingTree, ...resolvedConflicts, ...unresolvedConflicts];
const scmResources = [...workingTree, ...resolved, ...unresolved];
this.outputChannel.appendLine(`git.stage.scmResources ${scmResources.length}`);
if (!scmResources.length) {
@@ -667,12 +675,12 @@ 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 mergeConflicts = resources.filter(s => s.resourceGroupType === ResourceGroupType.Merge);
const { merge, unresolved } = await categorizeResourceByResolution(resources);
if (mergeConflicts.length > 0) {
const message = mergeConflicts.length > 1
? localize('confirm stage files with merge conflicts', "Are you sure you want to stage {0} files with merge conflicts?", mergeConflicts.length)
: localize('confirm stage file with merge conflicts', "Are you sure you want to stage {0} with merge conflicts?", path.basename(mergeConflicts[0].resourceUri.fsPath));
if (unresolved.length > 0) {
const message = unresolved.length > 1
? localize('confirm stage files with merge conflicts', "Are you sure you want to stage {0} files with merge conflicts?", merge.length)
: localize('confirm stage file with merge conflicts', "Are you sure you want to stage {0} with merge conflicts?", path.basename(merge[0].resourceUri.fsPath));
const yes = localize('yes', "Yes");
const pick = await window.showWarningMessage(message, { modal: true }, yes);
@@ -1273,16 +1281,7 @@ export class CommandCenter {
return;
}
try {
await choice.run(repository);
} catch (err) {
if (err.gitErrorCode !== GitErrorCodes.Conflict) {
throw err;
}
const message = localize('merge conflicts', "There are merge conflicts. Resolve them before committing.");
await window.showWarningMessage(message);
}
await choice.run(repository);
}
@command('git.createTag', { repository: true })
@@ -1655,10 +1654,11 @@ export class CommandCenter {
return result.catch(async err => {
const options: MessageOptions = {
modal: err.gitErrorCode === GitErrorCodes.DirtyWorkTree
modal: true
};
let message: string;
let type: 'error' | 'warning' = 'error';
switch (err.gitErrorCode) {
case GitErrorCodes.DirtyWorkTree:
@@ -1667,6 +1667,11 @@ export class CommandCenter {
case GitErrorCodes.PushRejected:
message = localize('cant push', "Can't push refs to remote. Try running 'Pull' first to integrate your changes.");
break;
case GitErrorCodes.Conflict:
message = localize('merge conflicts', "There are merge conflicts. Resolve them before committing.");
type = 'warning';
options.modal = false;
break;
default:
const hint = (err.stderr || err.message || String(err))
.replace(/^error: /mi, '')
@@ -1687,11 +1692,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, options, openOutputChannelChoice);
const choice = type === 'error'
? await window.showErrorMessage(message, options, openOutputChannelChoice)
: await window.showWarningMessage(message, options, openOutputChannelChoice);
if (choice === openOutputChannelChoice) {
outputChannel.show();

View File

@@ -336,7 +336,7 @@ export const GitErrorCodes = {
NoStashFound: 'NoStashFound',
LocalChangesOverwritten: 'LocalChangesOverwritten',
NoUpstreamBranch: 'NoUpstreamBranch',
IsInSubmodule: 'IsInSubmodule'
IsInSubmodule: 'IsInSubmodule',
};
function getGitErrorCode(stderr: string): string | undefined {
@@ -876,27 +876,41 @@ export class Repository {
try {
await this.run(args, { input: message || '' });
} catch (commitErr) {
if (/not possible because you have unmerged files/.test(commitErr.stderr || '')) {
commitErr.gitErrorCode = GitErrorCodes.UnmergedChanges;
throw commitErr;
}
await this.handleCommitError(commitErr);
}
}
try {
await this.run(['config', '--get-all', 'user.name']);
} catch (err) {
err.gitErrorCode = GitErrorCodes.NoUserNameConfigured;
throw err;
}
async rebaseContinue(): Promise<void> {
const args = ['rebase', '--continue'];
try {
await this.run(['config', '--get-all', 'user.email']);
} catch (err) {
err.gitErrorCode = GitErrorCodes.NoUserEmailConfigured;
throw err;
}
try {
await this.run(args);
} catch (commitErr) {
await this.handleCommitError(commitErr);
}
}
private async handleCommitError(commitErr: any): Promise<void> {
if (/not possible because you have unmerged files/.test(commitErr.stderr || '')) {
commitErr.gitErrorCode = GitErrorCodes.UnmergedChanges;
throw commitErr;
}
try {
await this.run(['config', '--get-all', 'user.name']);
} catch (err) {
err.gitErrorCode = GitErrorCodes.NoUserNameConfigured;
throw err;
}
try {
await this.run(['config', '--get-all', 'user.email']);
} catch (err) {
err.gitErrorCode = GitErrorCodes.NoUserEmailConfigured;
throw err;
}
throw commitErr;
}
async branch(name: string, checkout: boolean): Promise<void> {

View File

@@ -299,7 +299,8 @@ export enum Operation {
Stash = 'Stash',
CheckIgnore = 'CheckIgnore',
LSTree = 'LSTree',
SubmoduleUpdate = 'SubmoduleUpdate'
SubmoduleUpdate = 'SubmoduleUpdate',
RebaseContinue = 'RebaseContinue',
}
function isReadOnly(operation: Operation): boolean {
@@ -479,6 +480,22 @@ export class Repository implements Disposable {
return this._submodules;
}
private _rebaseCommit: Commit | undefined = undefined;
set rebaseCommit(rebaseCommit: Commit | undefined) {
if (this._rebaseCommit && !rebaseCommit) {
this.inputBox.value = '';
} else if (rebaseCommit && (!this._rebaseCommit || this._rebaseCommit.hash !== rebaseCommit.hash)) {
this.inputBox.value = rebaseCommit.message;
}
this._rebaseCommit = rebaseCommit;
}
get rebaseCommit(): Commit | undefined {
return this._rebaseCommit;
}
private _operations = new OperationsImpl();
get operations(): Operations { return this._operations; }
@@ -553,6 +570,15 @@ export class Repository implements Disposable {
}
validateInput(text: string, position: number): SourceControlInputBoxValidation | undefined {
if (this.rebaseCommit) {
if (this.rebaseCommit.message !== text) {
return {
message: localize('commit in rebase', "It's not possible to change the commit message in the middle of a rebase. Please complete the rebase operation and use interactive rebase instead."),
type: SourceControlInputBoxValidationType.Warning
};
}
}
const config = workspace.getConfiguration('git');
const setting = config.get<'always' | 'warn' | 'off'>('inputValidation');
@@ -636,13 +662,23 @@ export class Repository implements Disposable {
}
async commit(message: string, opts: CommitOptions = Object.create(null)): Promise<void> {
await this.run(Operation.Commit, async () => {
if (opts.all) {
await this.repository.add([]);
}
if (this.rebaseCommit) {
await this.run(Operation.RebaseContinue, async () => {
if (opts.all) {
await this.repository.add([]);
}
await this.repository.commit(message, opts);
});
await this.repository.rebaseContinue();
});
} else {
await this.run(Operation.Commit, async () => {
if (opts.all) {
await this.repository.add([]);
}
await this.repository.commit(message, opts);
});
}
}
async clean(resources: Uri[]): Promise<void> {
@@ -1025,12 +1061,13 @@ export class Repository implements Disposable {
// noop
}
const [refs, remotes, submodules] = await Promise.all([this.repository.getRefs(), this.repository.getRemotes(), this.repository.getSubmodules()]);
const [refs, remotes, submodules, rebaseCommit] = await Promise.all([this.repository.getRefs(), this.repository.getRemotes(), this.repository.getSubmodules(), this.getRebaseCommit()]);
this._HEAD = HEAD;
this._refs = refs;
this._remotes = remotes;
this._submodules = submodules;
this.rebaseCommit = rebaseCommit;
const index: Resource[] = [];
const workingTree: Resource[] = [];
@@ -1089,6 +1126,17 @@ export class Repository implements Disposable {
this._onDidChangeStatus.fire();
}
private async getRebaseCommit(): Promise<Commit | undefined> {
const rebaseHeadPath = path.join(this.repository.root, '.git', 'REBASE_HEAD');
try {
const rebaseHead = await new Promise<string>((c, e) => fs.readFile(rebaseHeadPath, 'utf8', (err, result) => err ? e(err) : c(result)));
return await this.getCommit(rebaseHead.trim());
} catch (err) {
return undefined;
}
}
private onFSChange(uri: Uri): void {
const config = workspace.getConfiguration('git');
const autorefresh = config.get<boolean>('autorefresh');

View File

@@ -24,7 +24,8 @@ class CheckoutStatusBar {
}
get command(): Command | undefined {
const title = `$(git-branch) ${this.repository.headLabel}`;
const rebasing = !!this.repository.rebaseCommit;
const title = `$(git-branch) ${this.repository.headLabel}${rebasing ? ` (${localize('rebasing', 'Rebasing')})` : ''}`;
return {
command: 'git.checkout',