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:
@ -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);
|
||||
}
|
||||
|
15
lib/vscode/extensions/git/src/api/git.d.ts
vendored
15
lib/vscode/extensions/git/src/api/git.d.ts
vendored
@ -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;
|
||||
|
@ -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 => {
|
||||
|
@ -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();
|
||||
|
@ -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)]));
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -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[]> {
|
||||
|
@ -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);
|
||||
|
||||
|
@ -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; }
|
||||
|
||||
|
@ -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));
|
||||
}
|
||||
|
||||
|
@ -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',
|
||||
|
@ -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);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
@ -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;
|
||||
}
|
||||
|
||||
|
Reference in New Issue
Block a user