Archived
1
0

chore(vscode): update to 1.53.2

These conflicts will be resolved in the following commits. We do it this way so
that PR review is possible.
This commit is contained in:
Joe Previte
2021-02-25 11:27:27 -07:00
1900 changed files with 83066 additions and 64589 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, LogOptions, APIState, CommitOptions, RefType, RemoteSourceProvider, CredentialsProvider, BranchQuery, PushErrorHandler } from './git';
import { InputBox, Git, API, Repository, Remote, RepositoryState, Branch, ForcePushMode, Ref, Submodule, Commit, Change, RepositoryUIState, Status, LogOptions, APIState, CommitOptions, RefType, RemoteSourceProvider, CredentialsProvider, BranchQuery, PushErrorHandler, PublishEvent } from './git';
import { Event, SourceControlInputBox, Uri, SourceControl, Disposable, commands } from 'vscode';
import { mapEvent } from '../util';
import { toGitUri } from '../uri';
@ -201,8 +201,8 @@ export class ApiRepository implements Repository {
return this._repository.pull(undefined, unshallow);
}
push(remoteName?: string, branchName?: string, setUpstream: boolean = false): Promise<void> {
return this._repository.pushTo(remoteName, branchName, setUpstream);
push(remoteName?: string, branchName?: string, setUpstream: boolean = false, force?: ForcePushMode): Promise<void> {
return this._repository.pushTo(remoteName, branchName, setUpstream, force);
}
blame(path: string): Promise<string> {
@ -237,6 +237,10 @@ export class ApiImpl implements API {
return this._model.onDidChangeState;
}
get onDidPublish(): Event<PublishEvent> {
return this._model.onDidPublish;
}
get onDidOpenRepository(): Event<Repository> {
return mapEvent(this._model.onDidOpenRepository, r => new ApiRepository(r));
}
@ -265,6 +269,11 @@ export class ApiImpl implements API {
return this.getRepository(root) || null;
}
async openRepository(root: Uri): Promise<Repository | null> {
await this._model.openRepository(root.fsPath);
return this.getRepository(root) || null;
}
registerRemoteSourceProvider(provider: RemoteSourceProvider): Disposable {
return this._model.registerRemoteSourceProvider(provider);
}

View File

@ -14,6 +14,11 @@ export interface InputBox {
value: string;
}
export const enum ForcePushMode {
Force,
ForceWithLease
}
export const enum RefType {
Head,
RemoteHead,
@ -131,6 +136,7 @@ export interface CommitOptions {
signCommit?: boolean;
empty?: boolean;
noVerify?: boolean;
requireUserConfig?: boolean;
}
export interface BranchQuery {
@ -193,7 +199,7 @@ export interface Repository {
fetch(remote?: string, ref?: string, depth?: number): Promise<void>;
pull(unshallow?: boolean): Promise<void>;
push(remoteName?: string, branchName?: string, setUpstream?: boolean): Promise<void>;
push(remoteName?: string, branchName?: string, setUpstream?: boolean, force?: ForcePushMode): Promise<void>;
blame(path: string): Promise<string>;
log(options?: LogOptions): Promise<Commit[]>;
@ -231,9 +237,15 @@ export interface PushErrorHandler {
export type APIState = 'uninitialized' | 'initialized';
export interface PublishEvent {
repository: Repository;
branch?: string;
}
export interface API {
readonly state: APIState;
readonly onDidChangeState: Event<APIState>;
readonly onDidPublish: Event<PublishEvent>;
readonly git: Git;
readonly repositories: Repository[];
readonly onDidOpenRepository: Event<Repository>;
@ -242,6 +254,7 @@ export interface API {
toGitUri(uri: Uri, ref: string): Uri;
getRepository(uri: Uri): Repository | null;
init(root: Uri): Promise<Repository | null>;
openRepository(root: Uri): Promise<Repository | null>
registerRemoteSourceProvider(provider: RemoteSourceProvider): Disposable;
registerCredentialsProvider(provider: CredentialsProvider): Disposable;

View File

@ -30,7 +30,7 @@ function main(argv: string[]): void {
const output = process.env['VSCODE_GIT_ASKPASS_PIPE'] as string;
const request = argv[2];
const host = argv[4].substring(1, argv[4].length - 2);
const host = argv[4].replace(/^["']+|["']+$/g, '');
const ipcClient = new IPCClient('askpass');
ipcClient.call({ request, host }).then(res => {

View File

@ -3,7 +3,7 @@
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { workspace, Disposable, EventEmitter, Memento, window, MessageItem, ConfigurationTarget, Uri } from 'vscode';
import { workspace, Disposable, EventEmitter, Memento, window, MessageItem, ConfigurationTarget, Uri, ConfigurationChangeEvent } from 'vscode';
import { Repository, Operation } from './repository';
import { eventToPromise, filterEvent, onceEvent } from './util';
import * as nls from 'vscode-nls';
@ -23,6 +23,7 @@ export class AutoFetcher {
private onDidChange = this._onDidChange.event;
private _enabled: boolean = false;
private _fetchAll: boolean = false;
get enabled(): boolean { return this._enabled; }
set enabled(enabled: boolean) { this._enabled = enabled; this._onDidChange.fire(enabled); }
@ -67,13 +68,26 @@ export class AutoFetcher {
this.globalState.update(AutoFetcher.DidInformUser, true);
}
private onConfiguration(): void {
const gitConfig = workspace.getConfiguration('git', Uri.file(this.repository.root));
private onConfiguration(e?: ConfigurationChangeEvent): void {
if (e !== undefined && !e.affectsConfiguration('git.autofetch')) {
return;
}
if (gitConfig.get<boolean>('autofetch') === false) {
this.disable();
} else {
this.enable();
const gitConfig = workspace.getConfiguration('git', Uri.file(this.repository.root));
switch (gitConfig.get<boolean | 'all'>('autofetch')) {
case true:
this._fetchAll = false;
this.enable();
break;
case 'all':
this._fetchAll = true;
this.enable();
break;
case false:
default:
this._fetchAll = false;
this.disable();
break;
}
}
@ -99,7 +113,11 @@ export class AutoFetcher {
}
try {
await this.repository.fetchDefault({ silent: true });
if (this._fetchAll) {
await this.repository.fetchAll();
} else {
await this.repository.fetchDefault({ silent: true });
}
} catch (err) {
if (err.gitErrorCode === GitErrorCodes.AuthenticationFailed) {
this.disable();

View File

@ -8,8 +8,8 @@ import * as path from 'path';
import { commands, Disposable, LineChange, MessageOptions, OutputChannel, Position, ProgressLocation, QuickPickItem, Range, SourceControlResourceState, TextDocumentShowOptions, TextEditor, Uri, ViewColumn, window, workspace, WorkspaceEdit, WorkspaceFolder, TimelineItem, env, Selection, TextDocumentContentProvider } from 'vscode';
import TelemetryReporter from 'vscode-extension-telemetry';
import * as nls from 'vscode-nls';
import { Branch, GitErrorCodes, Ref, RefType, Status, CommitOptions, RemoteSourceProvider } from './api/git';
import { ForcePushMode, Git, Stash } from './git';
import { Branch, ForcePushMode, GitErrorCodes, Ref, RefType, Status, CommitOptions, RemoteSourceProvider } from './api/git';
import { Git, Stash } from './git';
import { Model } from './model';
import { Repository, Resource, ResourceGroupType } from './repository';
import { applyLineChanges, getModifiedRange, intersectDiffWithRange, invertLineChange, toLineRanges } from './staging';
@ -277,6 +277,12 @@ interface PushOptions {
pushType: PushType;
forcePush?: boolean;
silent?: boolean;
pushTo?: {
remote?: string;
refspec?: string;
setUpstream?: boolean;
}
}
class CommandErrorOutputTextDocumentContentProvider implements TextDocumentContentProvider {
@ -366,6 +372,20 @@ export class CommandCenter {
await resource.open();
}
@command('git.openAllChanges', { repository: true })
async openChanges(repository: Repository): Promise<void> {
[
...repository.workingTreeGroup.resourceStates,
...repository.untrackedGroup.resourceStates,
].forEach(resource => {
commands.executeCommand(
'vscode.open',
resource.resourceUri,
{ preview: false, }
);
});
}
async cloneRepository(url?: string, parentPath?: string, options: { recursive?: boolean } = {}): Promise<void> {
if (!url || typeof url !== 'string') {
url = await pickRemoteSource(this.model, {
@ -500,12 +520,12 @@ export class CommandCenter {
@command('git.clone')
async clone(url?: string, parentPath?: string): Promise<void> {
this.cloneRepository(url, parentPath);
await this.cloneRepository(url, parentPath);
}
@command('git.cloneRecursive')
async cloneRecursive(url?: string, parentPath?: string): Promise<void> {
this.cloneRepository(url, parentPath, { recursive: true });
await this.cloneRepository(url, parentPath, { recursive: true });
}
@command('git.init')
@ -1323,8 +1343,8 @@ 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;
let noStagedChanges = repository.indexGroup.resourceStates.length === 0;
let noUnstagedChanges = repository.workingTreeGroup.resourceStates.length === 0;
if (promptToSaveFilesBeforeCommit !== 'never') {
let documents = workspace.textDocuments
@ -1346,6 +1366,9 @@ export class CommandCenter {
if (pick === saveAndCommit) {
await Promise.all(documents.map(d => d.save()));
await repository.add(documents.map(d => d.uri));
noStagedChanges = repository.indexGroup.resourceStates.length === 0;
noUnstagedChanges = repository.workingTreeGroup.resourceStates.length === 0;
} else if (pick !== commit) {
return false; // do not commit on cancel
}
@ -2112,23 +2135,27 @@ export class CommandCenter {
}
} else {
const branchName = repository.HEAD.name;
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 choice = await window.showQuickPick(picks, { placeHolder });
if (!pushOptions.pushTo?.remote) {
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 choice = await window.showQuickPick(picks, { placeHolder });
if (!choice) {
return;
}
if (!choice) {
return;
}
if (choice === addRemote) {
const newRemote = await this.addRemote(repository);
if (choice === addRemote) {
const newRemote = await this.addRemote(repository);
if (newRemote) {
await repository.pushTo(newRemote, branchName, undefined, forcePushMode);
if (newRemote) {
await repository.pushTo(newRemote, branchName, undefined, forcePushMode);
}
} else {
await repository.pushTo(choice.label, branchName, undefined, forcePushMode);
}
} else {
await repository.pushTo(choice.label, branchName, undefined, forcePushMode);
await repository.pushTo(pushOptions.pushTo.remote, pushOptions.pushTo.refspec || branchName, pushOptions.pushTo.setUpstream, forcePushMode);
}
}
}
@ -2169,13 +2196,13 @@ export class CommandCenter {
}
@command('git.pushTo', { repository: true })
async pushTo(repository: Repository): Promise<void> {
await this._push(repository, { pushType: PushType.PushTo });
async pushTo(repository: Repository, remote?: string, refspec?: string, setUpstream?: boolean): Promise<void> {
await this._push(repository, { pushType: PushType.PushTo, pushTo: { remote: remote, refspec: refspec, setUpstream: setUpstream } });
}
@command('git.pushToForce', { repository: true })
async pushToForce(repository: Repository): Promise<void> {
await this._push(repository, { pushType: PushType.PushTo, forcePush: true });
async pushToForce(repository: Repository, remote?: string, refspec?: string, setUpstream?: boolean): Promise<void> {
await this._push(repository, { pushType: PushType.PushTo, pushTo: { remote: remote, refspec: refspec, setUpstream: setUpstream }, forcePush: true });
}
@command('git.pushTags', { repository: true })
@ -2355,11 +2382,16 @@ export class CommandCenter {
}
await provider.publishRepository!(new ApiRepository(repository));
this.model.firePublishEvent(repository, branchName);
return;
}
if (remotes.length === 1) {
return await repository.pushTo(remotes[0].name, branchName, true);
await repository.pushTo(remotes[0].name, branchName, true);
this.model.firePublishEvent(repository, branchName);
return;
}
const addRemote = new AddRemoteItem(this);
@ -2376,9 +2408,13 @@ export class CommandCenter {
if (newRemote) {
await repository.pushTo(newRemote, branchName, true);
this.model.firePublishEvent(repository, branchName);
}
} else {
await repository.pushTo(choice.label, branchName, true);
this.model.firePublishEvent(repository, branchName);
}
}
@ -2648,7 +2684,7 @@ export class CommandCenter {
return Promise.resolve();
}
return Promise.resolve(method.apply(this, [repository, ...args]));
return Promise.resolve(method.apply(this, [repository, ...args.slice(1)]));
});
}

View File

@ -14,7 +14,7 @@ import * as filetype from 'file-type';
import { assign, groupBy, IDisposable, toDisposable, dispose, mkdirp, readBytes, detectUnicodeEncoding, Encoding, onceEvent, splitInChunks, Limiter } from './util';
import { CancellationToken, Progress, Uri } from 'vscode';
import { detectEncoding } from './encoding';
import { Ref, RefType, Branch, Remote, GitErrorCodes, LogOptions, Change, Status, CommitOptions, BranchQuery } from './api/git';
import { Ref, RefType, Branch, Remote, ForcePushMode, GitErrorCodes, LogOptions, Change, Status, CommitOptions, BranchQuery } from './api/git';
import * as byline from 'byline';
import { StringDecoder } from 'string_decoder';
@ -311,6 +311,7 @@ export class GitError {
export interface IGitOptions {
gitPath: string;
userAgent: string;
version: string;
env?: any;
}
@ -362,6 +363,8 @@ export interface ICloneOptions {
export class Git {
readonly path: string;
readonly userAgent: string;
readonly version: string;
private env: any;
private _onOutput = new EventEmitter();
@ -369,6 +372,8 @@ export class Git {
constructor(options: IGitOptions) {
this.path = options.gitPath;
this.version = options.version;
this.userAgent = options.userAgent;
this.env = options.env || {};
}
@ -427,7 +432,11 @@ export class Git {
if (options.recursive) {
command.push('--recursive');
}
await this.exec(options.parentPath, command, { cancellationToken, onSpawn });
await this.exec(options.parentPath, command, {
cancellationToken,
env: { 'GIT_HTTP_USER_AGENT': this.userAgent },
onSpawn,
});
} catch (err) {
if (err.stderr) {
err.stderr = err.stderr.replace(/^Cloning.+$/m, '').trim();
@ -458,7 +467,7 @@ export class Git {
try {
const networkPath = await new Promise<string | undefined>(resolve =>
realpath.native(`${letter}:`, { encoding: 'utf8' }, (err, resolvedPath) =>
realpath.native(`${letter}:\\`, { encoding: 'utf8' }, (err, resolvedPath) =>
resolve(err !== null ? undefined : resolvedPath),
),
);
@ -798,11 +807,6 @@ export interface PullOptions {
readonly cancellationToken?: CancellationToken;
}
export enum ForcePushMode {
Force,
ForceWithLease
}
export class Repository {
constructor(
@ -1365,8 +1369,10 @@ export class Repository {
args.push('--no-verify');
}
// Stops git from guessing at user/email
args.splice(0, 0, '-c', 'user.useConfigOnly=true');
if (opts.requireUserConfig ?? true) {
// Stops git from guessing at user/email
args.splice(0, 0, '-c', 'user.useConfigOnly=true');
}
try {
await this.run(args, !opts.amend || message ? { input: message || '' } : {});
@ -1563,6 +1569,7 @@ export class Repository {
const args = ['fetch'];
const spawnOptions: SpawnOptions = {
cancellationToken: options.cancellationToken,
env: { 'GIT_HTTP_USER_AGENT': this.git.userAgent }
};
if (options.remote) {
@ -1584,7 +1591,7 @@ export class Repository {
}
if (options.silent) {
spawnOptions.env = { 'VSCODE_GIT_FETCH_SILENT': 'true' };
spawnOptions.env!['VSCODE_GIT_FETCH_SILENT'] = 'true';
}
try {
@ -1621,7 +1628,10 @@ export class Repository {
}
try {
await this.run(args, options);
await this.run(args, {
cancellationToken: options.cancellationToken,
env: { 'GIT_HTTP_USER_AGENT': this.git.userAgent }
});
} catch (err) {
if (/^CONFLICT \([^)]+\): \b/m.test(err.stdout || '')) {
err.gitErrorCode = GitErrorCodes.Conflict;
@ -1690,7 +1700,7 @@ export class Repository {
}
try {
await this.run(args);
await this.run(args, { env: { 'GIT_HTTP_USER_AGENT': this.git.userAgent } });
} catch (err) {
if (/^error: failed to push some refs to\b/m.test(err.stderr || '')) {
err.gitErrorCode = GitErrorCodes.PushRejected;
@ -1913,7 +1923,7 @@ export class Repository {
return null;
};
return result.stdout.trim().split('\n')
return result.stdout.split('\n')
.filter(line => !!line)
.map(fn)
.filter(ref => !!ref) as Ref[];
@ -1968,50 +1978,59 @@ export class Repository {
return this.getHEAD();
}
let result = await this.run(['rev-parse', name]);
if (!result.stdout && /^@/.test(name)) {
const symbolicFullNameResult = await this.run(['rev-parse', '--symbolic-full-name', name]);
name = symbolicFullNameResult.stdout.trim();
result = await this.run(['rev-parse', name]);
const args = ['for-each-ref', '--format=%(refname)%00%(upstream:short)%00%(upstream:track)%00%(objectname)'];
if (/^refs\/(head|remotes)\//i.test(name)) {
args.push(name);
} else {
args.push(`refs/heads/${name}`, `refs/remotes/${name}`);
}
if (!result.stdout) {
return Promise.reject<Branch>(new Error('No such branch'));
}
const result = await this.run(args);
const branches: Branch[] = result.stdout.trim().split('\n').map<Branch | undefined>(line => {
let [branchName, upstream, status, ref] = line.trim().split('\0');
const commit = result.stdout.trim();
if (branchName.startsWith('refs/heads/')) {
branchName = branchName.substring(11);
const index = upstream.indexOf('/');
try {
const res2 = await this.run(['rev-parse', '--symbolic-full-name', name + '@{u}']);
const fullUpstream = res2.stdout.trim();
const match = /^refs\/remotes\/([^/]+)\/(.+)$/.exec(fullUpstream);
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;
while (i < res3.stdout.length) {
switch (res3.stdout.charAt(i)) {
case '<': ahead++; break;
case '>': behind++; break;
default: i++; break;
let ahead;
let behind;
const match = /\[(?:ahead ([0-9]+))?[,\s]*(?:behind ([0-9]+))?]|\[gone]/.exec(status);
if (match) {
[, ahead, behind] = match;
}
while (res3.stdout.charAt(i++) !== '\n') { /* no-op */ }
}
return {
type: RefType.Head,
name: branchName,
upstream: upstream ? {
name: upstream.substring(index + 1),
remote: upstream.substring(0, index)
} : undefined,
commit: ref || undefined,
ahead: Number(ahead) || 0,
behind: Number(behind) || 0,
};
} else if (branchName.startsWith('refs/remotes/')) {
branchName = branchName.substring(13);
const index = branchName.indexOf('/');
return { name, type: RefType.Head, commit, upstream, ahead, behind };
} catch (err) {
return { name, type: RefType.Head, commit };
return {
type: RefType.RemoteHead,
name: branchName.substring(index + 1),
remote: branchName.substring(0, index),
commit: ref,
};
} else {
return undefined;
}
}).filter((b?: Branch): b is Branch => !!b);
if (branches.length) {
return branches[0];
}
return Promise.reject<Branch>(new Error('No such branch'));
}
async getBranches(query: BranchQuery): Promise<Ref[]> {

View File

@ -6,7 +6,7 @@
import * as nls from 'vscode-nls';
const localize = nls.loadMessageBundle();
import { ExtensionContext, workspace, window, Disposable, commands, Uri, OutputChannel, WorkspaceFolder } from 'vscode';
import { env, ExtensionContext, workspace, window, Disposable, commands, Uri, OutputChannel, version as vscodeVersion, WorkspaceFolder } from 'vscode';
import { findGit, Git, IGit } from './git';
import { Model } from './model';
import { CommandCenter } from './commands';
@ -20,6 +20,7 @@ import { GitProtocolHandler } from './protocolHandler';
import { GitExtensionImpl } from './api/extension';
import * as path from 'path';
import * as fs from 'fs';
import * as os from 'os';
import { GitTimelineProvider } from './timelineProvider';
import { registerAPICommands } from './api/api1';
import { TerminalEnvironmentManager } from './terminal';
@ -39,11 +40,17 @@ async function createModel(context: ExtensionContext, outputChannel: OutputChann
const askpass = await Askpass.create(outputChannel, context.storagePath);
disposables.push(askpass);
const env = askpass.getEnv();
const terminalEnvironmentManager = new TerminalEnvironmentManager(context, env);
const environment = askpass.getEnv();
const terminalEnvironmentManager = new TerminalEnvironmentManager(context, environment);
disposables.push(terminalEnvironmentManager);
const git = new Git({ gitPath: info.path, version: info.version, env });
const git = new Git({
gitPath: info.path,
userAgent: `git/${info.version} (${(os as any).version?.() ?? os.type()} ${os.release()}; ${os.platform()} ${os.arch()}) vscode/${vscodeVersion} (${env.appName})`,
version: info.version,
env: environment,
});
const model = new Model(git, askpass, context.globalState, outputChannel);
disposables.push(model);

View File

@ -12,10 +12,11 @@ import * as path from 'path';
import * as fs from 'fs';
import * as nls from 'vscode-nls';
import { fromGitUri } from './uri';
import { APIState as State, RemoteSourceProvider, CredentialsProvider, PushErrorHandler } from './api/git';
import { APIState as State, RemoteSourceProvider, CredentialsProvider, PushErrorHandler, PublishEvent } from './api/git';
import { Askpass } from './askpass';
import { IRemoteSourceProviderRegistry } from './remoteProvider';
import { IPushErrorHandlerRegistry } from './pushError';
import { ApiRepository } from './api/api1';
const localize = nls.loadMessageBundle();
@ -69,6 +70,13 @@ export class Model implements IRemoteSourceProviderRegistry, IPushErrorHandlerRe
private _onDidChangeState = new EventEmitter<State>();
readonly onDidChangeState = this._onDidChangeState.event;
private _onDidPublish = new EventEmitter<PublishEvent>();
readonly onDidPublish = this._onDidPublish.event;
firePublishEvent(repository: Repository, branch?: string) {
this._onDidPublish.fire({ repository: new ApiRepository(repository), branch: branch });
}
private _state: State = 'uninitialized';
get state(): State { return this._state; }

View File

@ -7,10 +7,10 @@ import * as fs from 'fs';
import * as path from 'path';
import { CancellationToken, Command, Disposable, Event, EventEmitter, Memento, OutputChannel, ProgressLocation, ProgressOptions, scm, SourceControl, SourceControlInputBox, SourceControlInputBoxValidation, SourceControlInputBoxValidationType, SourceControlResourceDecorations, SourceControlResourceGroup, SourceControlResourceState, ThemeColor, Uri, window, workspace, WorkspaceEdit, FileDecoration, commands } from 'vscode';
import * as nls from 'vscode-nls';
import { Branch, Change, GitErrorCodes, LogOptions, Ref, RefType, Remote, Status, CommitOptions, BranchQuery } from './api/git';
import { Branch, Change, ForcePushMode, GitErrorCodes, LogOptions, Ref, RefType, Remote, Status, CommitOptions, BranchQuery } from './api/git';
import { AutoFetcher } from './autofetch';
import { debounce, memoize, throttle } from './decorators';
import { Commit, ForcePushMode, GitError, Repository as BaseRepository, Stash, Submodule, LogFileOptions } from './git';
import { Commit, GitError, Repository as BaseRepository, Stash, Submodule, LogFileOptions } from './git';
import { StatusBarCommands } from './statusbar';
import { toGitUri } from './uri';
import { anyEvent, combinedDisposable, debounceEvent, dispose, EmptyDisposable, eventToPromise, filterEvent, find, IDisposable, isDescendant, onceEvent } from './util';
@ -79,7 +79,7 @@ export class Resource implements SourceControlResourceState {
return this.resources[0];
}
get rightUri(): Uri {
get rightUri(): Uri | undefined {
return this.resources[1];
}
@ -88,7 +88,7 @@ export class Resource implements SourceControlResourceState {
}
@memoize
private get resources(): [Uri | undefined, Uri] {
private get resources(): [Uri | undefined, Uri | undefined] {
return this._commandResolver.getResources(this);
}
@ -613,7 +613,7 @@ class ResourceCommandResolver {
}
}
getResources(resource: Resource): [Uri | undefined, Uri] {
getResources(resource: Resource): [Uri | undefined, Uri | undefined] {
for (const submodule of this.repository.submodules) {
if (path.join(this.repository.root, submodule.path) === resource.resourceUri.fsPath) {
return [undefined, toGitUri(resource.resourceUri, resource.resourceGroupType === ResourceGroupType.Index ? 'index' : 'wt', { submoduleOf: this.repository.root })];
@ -641,7 +641,7 @@ class ResourceCommandResolver {
return undefined;
}
private getRightResource(resource: Resource): Uri {
private getRightResource(resource: Resource): Uri | undefined {
switch (resource.type) {
case Status.INDEX_MODIFIED:
case Status.INDEX_ADDED:
@ -677,7 +677,7 @@ class ResourceCommandResolver {
return resource.resourceUri;
}
throw new Error('Should never happen');
return undefined;
}
private getTitle(resource: Resource): string {
@ -1129,7 +1129,7 @@ export class Repository implements Disposable {
return this.run(Operation.HashObject, () => this.repository.hashObject(data));
}
async add(resources: Uri[], opts?: { update?: boolean }): Promise<void> {
async add(resources: Uri[], opts?: { update?: boolean; }): Promise<void> {
await this.run(Operation.Add, () => this.repository.add(resources.map(r => r.fsPath), opts));
}
@ -1165,6 +1165,12 @@ export class Repository implements Disposable {
}
delete opts.all;
if (opts.requireUserConfig === undefined || opts.requireUserConfig === null) {
const config = workspace.getConfiguration('git', Uri.file(this.root));
opts.requireUserConfig = config.get<boolean>('requireGitUserConfig');
}
await this.repository.commit(message, opts);
});
}
@ -1260,11 +1266,11 @@ export class Repository implements Disposable {
await this.run(Operation.DeleteTag, () => this.repository.deleteTag(name));
}
async checkout(treeish: string, opts?: { detached?: boolean }): Promise<void> {
async checkout(treeish: string, opts?: { detached?: boolean; }): Promise<void> {
await this.run(Operation.Checkout, () => this.repository.checkout(treeish, [], opts));
}
async checkoutTracking(treeish: string, opts: { detached?: boolean } = {}): Promise<void> {
async checkoutTracking(treeish: string, opts: { detached?: boolean; } = {}): Promise<void> {
await this.run(Operation.CheckoutTracking, () => this.repository.checkout(treeish, [], { ...opts, track: true }));
}
@ -1297,7 +1303,7 @@ export class Repository implements Disposable {
}
@throttle
async fetchDefault(options: { silent?: boolean } = {}): Promise<void> {
async fetchDefault(options: { silent?: boolean; } = {}): Promise<void> {
await this._fetch({ silent: options.silent });
}
@ -1315,7 +1321,7 @@ export class Repository implements Disposable {
await this._fetch({ remote, ref, depth });
}
private async _fetch(options: { remote?: string, ref?: string, all?: boolean, prune?: boolean, depth?: number, silent?: boolean } = {}): Promise<void> {
private async _fetch(options: { remote?: string, ref?: string, all?: boolean, prune?: boolean, depth?: number, silent?: boolean; } = {}): Promise<void> {
if (!options.prune) {
const config = workspace.getConfiguration('git', Uri.file(this.root));
const prune = config.get<boolean>('pruneOnFetch');
@ -1363,7 +1369,9 @@ export class Repository implements Disposable {
await this.repository.fetch({ all: true });
}
await this.repository.pull(rebase, remote, branch, { unshallow, tags });
if (await this.checkIfMaybeRebased(this.HEAD?.name)) {
await this.repository.pull(rebase, remote, branch, { unshallow, tags });
}
});
});
}
@ -1432,10 +1440,11 @@ export class Repository implements Disposable {
await this.repository.fetch({ all: true, cancellationToken });
}
await this.repository.pull(rebase, remoteName, pullBranch, { tags, cancellationToken });
if (await this.checkIfMaybeRebased(this.HEAD?.name)) {
await this.repository.pull(rebase, remoteName, pullBranch, { tags, cancellationToken });
}
};
if (supportCancellation) {
const opts: ProgressOptions = {
location: ProgressLocation.Notification,
@ -1463,6 +1472,54 @@ export class Repository implements Disposable {
});
}
private async checkIfMaybeRebased(currentBranch?: string) {
const config = workspace.getConfiguration('git');
const shouldIgnore = config.get<boolean>('ignoreRebaseWarning') === true;
if (shouldIgnore) {
return true;
}
const maybeRebased = await this.run(Operation.Log, async () => {
try {
const result = await this.repository.run(['log', '--oneline', '--cherry', `${currentBranch ?? ''}...${currentBranch ?? ''}@{upstream}`, '--']);
if (result.exitCode) {
return false;
}
return /^=/.test(result.stdout);
} catch {
return false;
}
});
if (!maybeRebased) {
return true;
}
const always = { title: localize('always pull', "Always Pull") };
const pull = { title: localize('pull', "Pull") };
const cancel = { title: localize('dont pull', "Don't Pull") };
const result = await window.showWarningMessage(
currentBranch
? localize('pull branch maybe rebased', "It looks like the current branch \'{0}\' might have been rebased. Are you sure you still want to pull into it?", currentBranch)
: localize('pull maybe rebased', "It looks like the current branch might have been rebased. Are you sure you still want to pull into it?"),
always, pull, cancel
);
if (result === pull) {
return true;
}
if (result === always) {
await config.update('ignoreRebaseWarning', true, true);
return true;
}
return false;
}
async show(ref: string, filePath: string): Promise<string> {
return await this.run(Operation.Show, async () => {
const relativePath = path.relative(this.repository.root, filePath).replace(/\\/g, '/');
@ -1490,11 +1547,11 @@ export class Repository implements Disposable {
});
}
getObjectDetails(ref: string, filePath: string): Promise<{ mode: string, object: string, size: number }> {
getObjectDetails(ref: string, filePath: string): Promise<{ mode: string, object: string, size: number; }> {
return this.run(Operation.GetObjectDetails, () => this.repository.getObjectDetails(ref, filePath));
}
detectObjectType(object: string): Promise<{ mimetype: string, encoding?: string }> {
detectObjectType(object: string): Promise<{ mimetype: string, encoding?: string; }> {
return this.run(Operation.Show, () => this.repository.detectObjectType(object));
}

View File

@ -4,7 +4,7 @@
*--------------------------------------------------------------------------------------------*/
const path = require('path');
const testRunner = require('vscode/lib/testrunner');
const testRunner = require('../../../../test/integration/electron/testrunner');
const options: any = {
ui: 'tdd',

View File

@ -124,4 +124,31 @@ suite('git smoke test', function () {
assert.equal(repository.state.workingTreeChanges.length, 0);
assert.equal(repository.state.indexChanges.length, 0);
});
test('rename/delete conflict', async function () {
cp.execSync('git branch test', { cwd });
cp.execSync('git checkout test', { cwd });
fs.unlinkSync(file('app.js'));
cp.execSync('git add .', { cwd });
await repository.commit('commit on test');
cp.execSync('git checkout master', { cwd });
fs.renameSync(file('app.js'), file('rename.js'));
cp.execSync('git add .', { cwd });
await repository.commit('commit on master');
try {
cp.execSync('git merge test', { cwd });
} catch (e) { }
setTimeout(() => {
commands.executeCommand('workbench.scm.focus');
}, 2e3);
await new Promise(resolve => {
setTimeout(resolve, 5e3);
});
});
});

View File

@ -58,8 +58,8 @@ export class GitTimelineItem extends TimelineItem {
}
export class GitTimelineProvider implements TimelineProvider {
private _onDidChange = new EventEmitter<TimelineChangeEvent>();
get onDidChange(): Event<TimelineChangeEvent> {
private _onDidChange = new EventEmitter<TimelineChangeEvent | undefined>();
get onDidChange(): Event<TimelineChangeEvent | undefined> {
return this._onDidChange.event;
}