Browse Source

Improves startup perf

Avoid view flashing on startup
main
Eric Amodio 3 years ago
parent
commit
0799cc1fc2
8 changed files with 261 additions and 246 deletions
  1. +2
    -0
      src/constants.ts
  2. +7
    -4
      src/container.ts
  3. +22
    -37
      src/extension.ts
  4. +17
    -41
      src/git/git.ts
  5. +0
    -8
      src/git/gitProvider.ts
  6. +164
    -117
      src/git/gitProviderService.ts
  7. +47
    -36
      src/git/providers/localGitProvider.ts
  8. +2
    -3
      src/vsls/guest.ts

+ 2
- 0
src/constants.ts View File

@ -255,6 +255,8 @@ export interface Usage {
}
export enum WorkspaceState {
AssumeRepositoriesOnStartup = 'gitlens:assumeRepositoriesOnStartup',
BranchComparisons = 'gitlens:branch:comparisons',
ConnectedPrefix = 'gitlens:connected:',
DefaultRemote = 'gitlens:remote:default',

+ 7
- 4
src/container.ts View File

@ -24,8 +24,7 @@ import {
FileAnnotationType,
} from './configuration';
import { GitFileSystemProvider } from './git/fsProvider';
import { GitProviderId } from './git/gitProvider';
import { GitProviderService } from './git/gitProviderService';
import { GitProviderId, GitProviderService } from './git/gitProviderService';
import { LocalGitProvider } from './git/providers/localGitProvider';
import { LineHoverController } from './hovers/lineHoverController';
import { Keyboard } from './keyboard';
@ -147,14 +146,18 @@ export class Container {
if (this._ready) throw new Error('Container is already ready');
this._ready = true;
this.registerGitProviders();
this._onReady.fire();
queueMicrotask(() => {
this.registerGitProviders();
this._onReady.fire();
});
}
private registerGitProviders() {
if (env.uiKind !== UIKind.Web) {
this._context.subscriptions.push(this._git.register(GitProviderId.Git, new LocalGitProvider(this)));
}
this._git.registrationComplete();
}
private onConfigurationChanging(e: ConfigurationWillChangeEvent) {

+ 22
- 37
src/extension.ts View File

@ -31,9 +31,6 @@ export function activate(context: ExtensionContext): Promise
}
}
// Pretend we are enabled (until we know otherwise) and set the view contexts to reduce flashing on load
void setContext(ContextKeys.Enabled, true);
if (!workspace.isTrusted) {
void setContext(ContextKeys.Readonly, true);
context.subscriptions.push(
@ -68,7 +65,7 @@ export function activate(context: ExtensionContext): Promise
context.globalState.get<string>(GlobalState.Version) ??
context.globalState.get<string>(GlobalState.Deprecated_Version);
let previousVersion;
let previousVersion: string | undefined;
if (localVersion == null || syncedVersion == null) {
previousVersion = syncedVersion ?? localVersion;
} else if (Versions.compare(syncedVersion, localVersion) === 1) {
@ -95,46 +92,38 @@ export function activate(context: ExtensionContext): Promise
);
}
const enabled = workspace.getConfiguration('git', null).get<boolean>('enabled', true);
if (!enabled) {
Logger.log(`GitLens (v${gitlensVersion}) was NOT activated -- "git.enabled": false`);
void setEnabled(false);
void Messages.showGitDisabledErrorMessage();
return undefined;
}
Configuration.configure(context);
const cfg = configuration.get();
// await migrateSettings(context, previousVersion);
const container = Container.create(context, cfg);
// Signal that the container is now ready
container.ready();
container.onReady(() => {
registerCommands(context);
registerBuiltInActionRunners(container);
registerPartnerActionRunners(context);
registerCommands(context);
registerBuiltInActionRunners(container);
registerPartnerActionRunners(context);
void showWelcomeOrWhatsNew(container, gitlensVersion, previousVersion);
void showWelcomeOrWhatsNew(container, gitlensVersion, previousVersion);
void context.globalState.update(GlobalState.Version, gitlensVersion);
void context.globalState.update(GlobalState.Version, gitlensVersion);
// Only update our synced version if the new version is greater
if (syncedVersion == null || Versions.compare(gitlensVersion, syncedVersion) === 1) {
void context.globalState.update(SyncedState.Version, gitlensVersion);
}
// Only update our synced version if the new version is greater
if (syncedVersion == null || Versions.compare(gitlensVersion, syncedVersion) === 1) {
void context.globalState.update(SyncedState.Version, gitlensVersion);
}
if (cfg.outputLevel === TraceLevel.Debug) {
setTimeout(async () => {
if (cfg.outputLevel !== TraceLevel.Debug) return;
if (cfg.outputLevel === TraceLevel.Debug) {
setTimeout(async () => {
if (cfg.outputLevel !== TraceLevel.Debug) return;
if (await Messages.showDebugLoggingWarningMessage()) {
void commands.executeCommand(Commands.DisableDebugLogging);
}
}, 60000);
}
});
if (await Messages.showDebugLoggingWarningMessage()) {
void commands.executeCommand(Commands.DisableDebugLogging);
}
}, 60000);
}
// Signal that the container is now ready
container.ready();
Logger.log(
`GitLens (v${gitlensVersion}${cfg.mode.active ? `, mode: ${cfg.mode.active}` : ''}) activated ${
@ -163,10 +152,6 @@ export function deactivate() {
// }
// }
export async function setEnabled(enabled: boolean): Promise<void> {
await Promise.all([setContext(ContextKeys.Enabled, enabled), setContext(ContextKeys.Disabled, !enabled)]);
}
function setKeysForSync(context: ExtensionContext, ...keys: (SyncedState | string)[]) {
return context.globalState?.setKeysForSync([...keys, SyncedState.Version, SyncedState.WelcomeViewVisible]);
}

+ 17
- 41
src/git/git.ts View File

@ -1,14 +1,12 @@
/* eslint-disable @typescript-eslint/naming-convention */
'use strict';
import * as paths from 'path';
import * as iconv from 'iconv-lite';
import { Uri, window, workspace } from 'vscode';
import { GlyphChars } from '../constants';
import { Container } from '../container';
import { Logger } from '../logger';
import { Messages } from '../messages';
import { Paths, Strings, Versions } from '../system';
import { findGitPath, GitLocation } from './locator';
import { Paths, Strings } from '../system';
import { GitLocation } from './locator';
import { GitRevision } from './models/models';
import { GitBranchParser, GitLogParser, GitReflogParser, GitStashParser, GitTagParser } from './parsers/parsers';
import { fsExists, run, RunError, RunOptions } from './shell';
@ -130,7 +128,7 @@ export async function git(options: GitCommandOptio
args.splice(0, 0, '-c', 'core.longpaths=true');
}
promise = run<TOut>(Git.getGitPath(), args, encoding ?? 'utf8', runOpts);
promise = run<TOut>(await Git.path(), args, encoding ?? 'utf8', runOpts);
pendingCommands.set(command, promise);
} else {
@ -208,43 +206,21 @@ function defaultExceptionHandler(ex: Error, cwd: string | undefined, start?: [nu
}
export namespace Git {
let gitInfo: GitLocation | undefined;
export function getEncoding(encoding: string | undefined) {
return encoding !== undefined && iconv.encodingExists(encoding) ? encoding : 'utf8';
}
export function getGitPath(): string {
return gitInfo?.path ?? '';
}
export function getGitVersion(): string {
return gitInfo?.version ?? '';
let gitLocator!: () => Promise<GitLocation>;
export function setLocator(locator: () => Promise<GitLocation>): void {
gitLocator = locator;
}
export function hasGitPath(): boolean {
return Boolean(gitInfo?.path);
export async function path(): Promise<string> {
return (await gitLocator()).path;
}
export async function setOrFindGitPath(gitPath?: string | string[]): Promise<void> {
const start = process.hrtime();
gitInfo = await findGitPath(gitPath);
Logger.log(
`Git found: ${gitInfo.version} @ ${gitInfo.path === 'git' ? 'PATH' : gitInfo.path} ${
GlyphChars.Dot
} ${Strings.getDurationMilliseconds(start)} ms`,
);
// Warn if git is less than v2.7.2
if (Versions.compare(Versions.fromString(gitInfo.version), Versions.fromString('2.7.2')) === -1) {
void Messages.showGitVersionUnsupportedErrorMessage(gitInfo.version, '2.7.2');
}
export async function version(): Promise<string> {
return (await gitLocator()).version;
}
export function validateVersion(major: number, minor: number): boolean {
const [gitMajor, gitMinor] = getGitVersion().split('.');
export async function validateVersion(major: number, minor: number): Promise<boolean> {
const [gitMajor, gitMinor] = (await version()).split('.');
return parseInt(gitMajor, 10) >= major && parseInt(gitMinor, 10) >= minor;
}
@ -286,7 +262,7 @@ export namespace Git {
const index = params.indexOf('--ignore-revs-file');
if (index !== -1) {
// Ensure the version of Git supports the --ignore-revs-file flag, otherwise the blame will fail
let supported = Git.validateVersion(2, 23);
let supported = await Git.validateVersion(2, 23);
if (supported) {
let ignoreRevsFile = params[index + 1];
if (!paths.isAbsolute(ignoreRevsFile)) {
@ -1493,7 +1469,7 @@ export namespace Git {
void (await git<string>({ cwd: repoPath }, ...params));
}
export function status(
export async function status(
repoPath: string,
porcelainVersion: number = 1,
{ similarityThreshold }: { similarityThreshold?: number | null } = {},
@ -1504,7 +1480,7 @@ export namespace Git {
'--branch',
'-u',
];
if (Git.validateVersion(2, 18)) {
if (await Git.validateVersion(2, 18)) {
params.push(`--find-renames${similarityThreshold == null ? '' : `=${similarityThreshold}%`}`);
}
@ -1515,7 +1491,7 @@ export namespace Git {
);
}
export function status__file(
export async function status__file(
repoPath: string,
fileName: string,
porcelainVersion: number = 1,
@ -1524,7 +1500,7 @@ export namespace Git {
const [file, root] = Paths.splitPath(fileName, repoPath);
const params = ['status', porcelainVersion >= 2 ? `--porcelain=v${porcelainVersion}` : '--porcelain'];
if (Git.validateVersion(2, 18)) {
if (await Git.validateVersion(2, 18)) {
params.push(`--find-renames${similarityThreshold == null ? '' : `=${similarityThreshold}%`}`);
}

+ 0
- 8
src/git/gitProvider.ts View File

@ -396,15 +396,7 @@ export interface GitProvider {
hasTrackingBranch(repoPath: string | undefined): Promise<boolean>;
isActiveRepoPath(repoPath: string | undefined, editor?: TextEditor): Promise<boolean>;
isTrackable(scheme: string): boolean;
isTrackable(uri: Uri): boolean;
isTrackable(schemeOruri: string | Uri): boolean;
isTracked(
fileName: string,
repoPath?: string,
options?: { ref?: string | undefined; skipCacheUpdate?: boolean | undefined },
): Promise<boolean>;
isTracked(uri: GitUri): Promise<boolean>;
isTracked(
fileNameOrUri: string | GitUri,
repoPath?: string,

+ 164
- 117
src/git/gitProviderService.ts View File

@ -1,4 +1,5 @@
'use strict';
import { encodingExists } from 'iconv-lite';
import {
ConfigurationChangeEvent,
Disposable,
@ -16,9 +17,15 @@ import {
} from 'vscode';
import { resetAvatarCache } from '../avatars';
import { configuration } from '../configuration';
import { BuiltInGitConfiguration, ContextKeys, DocumentSchemes, GlyphChars, setContext } from '../constants';
import {
BuiltInGitConfiguration,
ContextKeys,
DocumentSchemes,
GlyphChars,
setContext,
WorkspaceState,
} from '../constants';
import { Container } from '../container';
import { setEnabled } from '../extension';
import { Logger } from '../logger';
import { Arrays, debug, gate, Iterables, log, Paths, Promises, Strings } from '../system';
import { vslsUriPrefixRegex } from '../vsls/vsls';
@ -28,7 +35,6 @@ import {
BranchDateFormatting,
BranchSortOptions,
CommitDateFormatting,
Git,
GitBlame,
GitBlameLine,
GitBlameLines,
@ -68,7 +74,7 @@ import { GitProvider, GitProviderDescriptor, GitProviderId, PagedResult, ScmRepo
import { GitUri } from './gitUri';
import { RemoteProvider, RemoteProviders, RichRemoteProvider } from './remotes/factory';
export type { GitProviderDescriptor, GitProviderId };
export { type GitProviderDescriptor, GitProviderId };
const slash = '/';
@ -122,7 +128,7 @@ export class GitProviderService implements Disposable {
}
this.resetCaches('providers');
void this.updateContext(this._repositories);
void this.updateContext();
}),
);
@ -130,7 +136,7 @@ export class GitProviderService implements Disposable {
CommitDateFormatting.reset();
PullRequestDateFormatting.reset();
void this.updateContext(this._repositories);
void this.updateContext();
}
dispose() {
@ -192,7 +198,7 @@ export class GitProviderService implements Disposable {
}
if (removed.length) {
void this.updateContext(this._repositories);
void this.updateContext();
// Defer the event trigger enough to let everything unwind
queueMicrotask(() => {
@ -203,70 +209,6 @@ export class GitProviderService implements Disposable {
}
}
private async updateContext(repositories: Map<string, Repository>) {
const hasRepositories = this.openRepositoryCount !== 0;
await setEnabled(hasRepositories);
// Don't block for the remote context updates (because it can block other downstream requests during initialization)
async function updateRemoteContext() {
let hasRemotes = false;
let hasRichRemotes = false;
let hasConnectedRemotes = false;
if (hasRepositories) {
for (const repo of repositories.values()) {
if (!hasConnectedRemotes) {
hasConnectedRemotes = await repo.hasRichRemote(true);
if (hasConnectedRemotes) {
hasRichRemotes = true;
hasRemotes = true;
}
}
if (!hasRichRemotes) {
hasRichRemotes = await repo.hasRichRemote();
}
if (!hasRemotes) {
hasRemotes = await repo.hasRemotes();
}
if (hasRemotes && hasRichRemotes && hasConnectedRemotes) break;
}
}
await Promise.all([
setContext(ContextKeys.HasRemotes, hasRemotes),
setContext(ContextKeys.HasRichRemotes, hasRichRemotes),
setContext(ContextKeys.HasConnectedRemotes, hasConnectedRemotes),
]);
}
void updateRemoteContext();
// If we have no repositories setup a watcher in case one is initialized
if (!hasRepositories) {
for (const provider of this._providers.values()) {
const watcher = provider.createRepositoryInitWatcher?.();
if (watcher != null) {
const disposable = Disposable.from(
watcher,
watcher.onDidCreate(uri => {
const f = workspace.getWorkspaceFolder(uri);
if (f == null) return;
void this.discoverRepositories([f], { force: true }).then(() => {
if (Iterables.some(this.repositories, r => r.folder === f)) {
disposable.dispose();
}
});
}),
);
}
}
}
}
get hasProviders(): boolean {
return this._providers.size !== 0;
}
@ -330,20 +272,42 @@ export class GitProviderService implements Disposable {
if (this._providers.has(id)) throw new Error(`Provider '${id}' has already been registered`);
this._providers.set(id, provider);
const disposable = provider.onDidChangeRepository(e => {
if (e.changed(RepositoryChange.Closed, RepositoryChangeComparisonMode.Any)) {
void this.updateContext(this._repositories);
// Send a notification that the repositories changed
queueMicrotask(() => this._onDidChangeRepositories.fire({ added: [], removed: [e.repository] }));
}
const disposables = [];
const watcher = provider.createRepositoryInitWatcher?.();
if (watcher != null) {
disposables.push(
watcher,
watcher.onDidCreate(uri => {
const f = workspace.getWorkspaceFolder(uri);
if (f == null) return;
void this.discoverRepositories([f], { force: true });
}),
);
}
this._onDidChangeRepository.fire(e);
});
const disposable = Disposable.from(
...disposables,
provider.onDidChangeRepository(e => {
if (e.changed(RepositoryChange.Closed, RepositoryChangeComparisonMode.Any)) {
void this.updateContext();
// Send a notification that the repositories changed
queueMicrotask(() => this._onDidChangeRepositories.fire({ added: [], removed: [e.repository] }));
}
this._onDidChangeRepository.fire(e);
}),
);
this._onDidChangeProviders.fire({ added: [provider], removed: [] });
void this.onWorkspaceFoldersChanged({ added: workspace.workspaceFolders ?? [], removed: [] });
// Don't kick off the discovery if we're still initializing (we'll do it at the end for all "known" providers)
if (!this._initializing) {
this.onWorkspaceFoldersChanged({ added: workspace.workspaceFolders ?? [], removed: [] });
}
return {
dispose: () => {
@ -359,7 +323,7 @@ export class GitProviderService implements Disposable {
}
}
void this.updateContext(this._repositories);
void this.updateContext();
if (removed.length) {
// Defer the event trigger enough to let everything unwind
@ -374,6 +338,27 @@ export class GitProviderService implements Disposable {
};
}
private _initializing: boolean = true;
registrationComplete() {
this._initializing = false;
const { workspaceFolders } = workspace;
if (workspaceFolders?.length) {
const autoRepositoryDetection =
configuration.getAny<boolean | 'subFolders' | 'openEditors'>(
BuiltInGitConfiguration.AutoRepositoryDetection,
) ?? true;
if (autoRepositoryDetection !== false) {
void this.discoverRepositories(workspaceFolders);
return;
}
}
void this.updateContext();
}
private _discoveredWorkspaceFolders = new Map<WorkspaceFolder, Promise<Repository[]>>();
async discoverRepositories(folders: readonly WorkspaceFolder[], options?: { force?: boolean }): Promise<void> {
@ -408,9 +393,10 @@ export class GitProviderService implements Disposable {
this._repositories.set(repository.path, repository);
}
void this.updateContext();
if (added.length === 0) return;
void this.updateContext(this._repositories);
// Defer the event trigger enough to let everything unwind
queueMicrotask(() => this._onDidChangeRepositories.fire({ added: added, removed: [] }));
}
@ -434,16 +420,85 @@ export class GitProviderService implements Disposable {
}
}
static getProviderId(repoPath: string | Uri): GitProviderId {
if (typeof repoPath !== 'string' && repoPath.scheme === DocumentSchemes.VirtualFS) {
if (repoPath.authority.startsWith('github')) {
return GitProviderId.GitHub;
private _context: { enabled: boolean; disabled: boolean } = { enabled: false, disabled: false };
async setEnabledContext(enabled: boolean): Promise<void> {
let disabled = !enabled;
// If we think we should be disabled during startup, check if we have a saved value from the last time this repo was loaded
if (!enabled && this._initializing) {
disabled = !(
this.container.context.workspaceState.get<boolean>(WorkspaceState.AssumeRepositoriesOnStartup) ?? true
);
}
if (this._context.enabled === enabled && this._context.disabled === disabled) return;
const promises = [];
if (this._context.enabled !== enabled) {
this._context.enabled = enabled;
promises.push(setContext(ContextKeys.Enabled, enabled));
}
if (this._context.disabled !== disabled) {
this._context.disabled = disabled;
promises.push(setContext(ContextKeys.Disabled, disabled));
}
await Promise.all(promises);
if (!this._initializing) {
void this.container.context.workspaceState.update(WorkspaceState.AssumeRepositoriesOnStartup, enabled);
}
}
private async updateContext() {
const hasRepositories = this.openRepositoryCount !== 0;
await this.setEnabledContext(hasRepositories);
// Don't bother trying to set the values if we're still starting up
if (!hasRepositories && this._initializing) return;
// Don't block for the remote context updates (because it can block other downstream requests during initialization)
async function updateRemoteContext(this: GitProviderService) {
let hasRemotes = false;
let hasRichRemotes = false;
let hasConnectedRemotes = false;
if (hasRepositories) {
for (const repo of this._repositories.values()) {
if (!hasConnectedRemotes) {
hasConnectedRemotes = await repo.hasRichRemote(true);
if (hasConnectedRemotes) {
hasRichRemotes = true;
hasRemotes = true;
}
}
if (!hasRichRemotes) {
hasRichRemotes = await repo.hasRichRemote();
if (hasRichRemotes) {
hasRemotes = true;
}
}
if (!hasRemotes) {
hasRemotes = await repo.hasRemotes();
}
if (hasRemotes && hasRichRemotes && hasConnectedRemotes) break;
}
}
throw new Error(`Unsupported scheme: ${repoPath.scheme}`);
await Promise.all([
setContext(ContextKeys.HasRemotes, hasRemotes),
setContext(ContextKeys.HasRichRemotes, hasRichRemotes),
setContext(ContextKeys.HasConnectedRemotes, hasConnectedRemotes),
]);
}
return GitProviderId.Git;
void updateRemoteContext.call(this);
}
private getProvider(repoPath: string | Uri): { provider: GitProvider; path: string } {
@ -467,6 +522,18 @@ export class GitProviderService implements Disposable {
}
}
static getProviderId(repoPath: string | Uri): GitProviderId {
if (typeof repoPath !== 'string' && repoPath.scheme === DocumentSchemes.VirtualFS) {
if (repoPath.authority.startsWith('github')) {
return GitProviderId.GitHub;
}
throw new Error(`Unsupported scheme: ${repoPath.scheme}`);
}
return GitProviderId.Git;
}
@log()
addRemote(repoPath: string | Uri, name: string, url: string): Promise<void> {
const { provider, path } = this.getProvider(repoPath);
@ -1396,7 +1463,7 @@ export class GitProviderService implements Disposable {
repo = provider.createRepository(folder, rp, false);
this._repositories.set(rp, repo);
void this.updateContext(this._repositories);
void this.updateContext();
// Send a notification that the repositories changed
queueMicrotask(() => this._onDidChangeRepositories.fire({ added: [repo!], removed: [] }));
@ -1638,41 +1705,20 @@ export class GitProviderService implements Disposable {
return repoPath === doc?.uri.repoPath;
}
isTrackable(scheme: string): boolean;
isTrackable(uri: Uri): boolean;
isTrackable(schemeOruri: string | Uri): boolean {
const scheme = typeof schemeOruri === 'string' ? schemeOruri : schemeOruri.scheme;
return (
scheme === DocumentSchemes.File ||
scheme === DocumentSchemes.Git ||
scheme === DocumentSchemes.GitLens ||
scheme === DocumentSchemes.PRs ||
scheme === DocumentSchemes.Vsls ||
scheme === DocumentSchemes.VirtualFS
);
isTrackable(uri: Uri): boolean {
const { provider } = this.getProvider(uri);
return provider.isTrackable(uri);
}
async isTracked(uri: GitUri): Promise<boolean>;
async isTracked(
private async isTracked(
fileName: string,
repoPath: string | Uri,
options?: { ref?: string; skipCacheUpdate?: boolean },
): Promise<boolean>;
@log<GitProviderService['isTracked']>({
exit: tracked => `returned ${tracked}`,
singleLine: true,
})
async isTracked(
fileNameOrUri: string | GitUri,
repoPath?: string | Uri,
options?: { ref?: string; skipCacheUpdate?: boolean },
): Promise<boolean> {
if (options?.ref === GitRevision.deletedOrMissing) return false;
const { provider, path } = this.getProvider(
repoPath ?? (typeof fileNameOrUri === 'string' ? undefined! : fileNameOrUri),
);
return provider.isTracked(fileNameOrUri, path, options);
const { provider, path } = this.getProvider(repoPath);
return provider.isTracked(fileName, path, options);
}
@log()
@ -1814,7 +1860,8 @@ export class GitProviderService implements Disposable {
static getEncoding(uri: Uri): string;
static getEncoding(repoPathOrUri: string | Uri, fileName?: string): string {
const uri = typeof repoPathOrUri === 'string' ? GitUri.resolveToUri(fileName!, repoPathOrUri) : repoPathOrUri;
return Git.getEncoding(configuration.getAny<string>('files.encoding', uri));
const encoding = configuration.getAny<string>('files.encoding', uri);
return encoding != null && encodingExists(encoding) ? encoding : 'utf8';
}
}

+ 47
- 36
src/git/providers/localGitProvider.ts View File

@ -17,7 +17,7 @@ import {
} from 'vscode';
import type { API as BuiltInGitApi, Repository as BuiltInGitRepository, GitExtension } from '../../@types/vscode.git';
import { configuration } from '../../configuration';
import { BuiltInGitConfiguration, DocumentSchemes } from '../../constants';
import { BuiltInGitConfiguration, DocumentSchemes, GlyphChars } from '../../constants';
import { Container } from '../../container';
import { LogCorrelationContext, Logger } from '../../logger';
import { Messages } from '../../messages';
@ -84,7 +84,7 @@ import {
import { GitProvider, GitProviderId, PagedResult, RepositoryInitWatcher, ScmRepository } from '../gitProvider';
import { GitProviderService } from '../gitProviderService';
import { GitUri } from '../gitUri';
import { InvalidGitConfigError, UnableToFindGitError } from '../locator';
import { findGitPath, GitLocation, InvalidGitConfigError, UnableToFindGitError } from '../locator';
import { GitReflogParser, GitShortLogParser } from '../parsers/parsers';
import { RemoteProvider, RemoteProviderFactory, RemoteProviders, RichRemoteProvider } from '../remotes/factory';
import { fsExists, isWindows } from '../shell';
@ -121,7 +121,9 @@ export class LocalGitProvider implements GitProvider, Disposable {
private readonly _trackedCache = new Map<string, boolean | Promise<boolean>>();
private readonly _userMapCache = new Map<string, GitUser | null>();
constructor(private readonly container: Container) {}
constructor(private readonly container: Container) {
Git.setLocator(this.ensureGit.bind(this));
}
dispose() {}
@ -167,16 +169,24 @@ export class LocalGitProvider implements GitProvider, Disposable {
this._onDidChangeRepository.fire(e);
}
private _initialized: Promise<void> | undefined;
private async ensureInitialized(): Promise<void> {
if (this._initialized == null) {
this._initialized = this.initializeCore();
private _gitLocator: Promise<GitLocation> | undefined;
private async ensureGit(): Promise<GitLocation> {
if (this._gitLocator == null) {
this._gitLocator = this.findGit();
}
return this._initialized;
return this._gitLocator;
}
private async initializeCore() {
@log()
private async findGit(): Promise<GitLocation> {
if (!configuration.getAny<boolean>('git.enabled', null, true)) {
Logger.log('Built-in Git is disabled ("git.enabled": false)');
void Messages.showGitDisabledErrorMessage();
throw new UnableToFindGitError();
}
// Try to use the same git as the built-in vscode git extension
const gitApi = await this.getScmGitApi();
if (gitApi != null) {
@ -196,14 +206,23 @@ export class LocalGitProvider implements GitProvider, Disposable {
);
}
if (Git.hasGitPath()) return;
const gitPath = gitApi?.git.path ?? configuration.getAny<string | string[]>('git.path');
await Git.setOrFindGitPath(gitApi?.git.path ?? configuration.getAny<string | string[]>('git.path'));
const start = process.hrtime();
const location = await findGitPath(gitPath);
Logger.log(
`Git found: ${location.version} @ ${location.path === 'git' ? 'PATH' : location.path} ${
GlyphChars.Dot
} ${Strings.getDurationMilliseconds(start)} ms`,
);
// Warn if git is less than v2.7.2
if (this.compareGitVersion('2.7.2') === -1) {
void Messages.showGitVersionUnsupportedErrorMessage(Git.getGitVersion(), '2.7.2');
if (Versions.compare(Versions.fromString(location.version), Versions.fromString('2.7.2')) === -1) {
Logger.log(`Git version (${location.version}) is outdated`);
void Messages.showGitVersionUnsupportedErrorMessage(location.version, '2.7.2');
}
return location;
}
async discoverRepositories(uri: Uri): Promise<Repository[]> {
@ -216,7 +235,7 @@ export class LocalGitProvider implements GitProvider, Disposable {
if (autoRepositoryDetection === false) return [];
try {
await this.ensureInitialized();
void (await this.ensureGit());
const repositories = await this.repositorySearch(workspace.getWorkspaceFolder(uri)!);
if (autoRepositoryDetection === true || autoRepositoryDetection === 'subFolders') {
@ -3143,7 +3162,7 @@ export class LocalGitProvider implements GitProvider, Disposable {
@log()
async getStatusForFile(repoPath: string, fileName: string): Promise<GitStatusFile | undefined> {
const porcelainVersion = Git.validateVersion(2, 11) ? 2 : 1;
const porcelainVersion = (await Git.validateVersion(2, 11)) ? 2 : 1;
const data = await Git.status__file(repoPath, fileName, porcelainVersion, {
similarityThreshold: this.container.config.advanced.similarityThreshold,
@ -3156,7 +3175,7 @@ export class LocalGitProvider implements GitProvider, Disposable {
@log()
async getStatusForFiles(repoPath: string, path: string): Promise<GitStatusFile[] | undefined> {
const porcelainVersion = Git.validateVersion(2, 11) ? 2 : 1;
const porcelainVersion = (await Git.validateVersion(2, 11)) ? 2 : 1;
const data = await Git.status__file(repoPath, path, porcelainVersion, {
similarityThreshold: this.container.config.advanced.similarityThreshold,
@ -3171,7 +3190,7 @@ export class LocalGitProvider implements GitProvider, Disposable {
async getStatusForRepo(repoPath: string | undefined): Promise<GitStatus | undefined> {
if (repoPath == null) return undefined;
const porcelainVersion = Git.validateVersion(2, 11) ? 2 : 1;
const porcelainVersion = (await Git.validateVersion(2, 11)) ? 2 : 1;
const data = await Git.status(repoPath, porcelainVersion, {
similarityThreshold: this.container.config.advanced.similarityThreshold,
@ -3396,10 +3415,8 @@ export class LocalGitProvider implements GitProvider, Disposable {
return repoPath === doc?.uri.repoPath;
}
isTrackable(scheme: string): boolean;
isTrackable(uri: Uri): boolean;
isTrackable(schemeOruri: string | Uri): boolean {
const scheme = typeof schemeOruri === 'string' ? schemeOruri : schemeOruri.scheme;
isTrackable(uri: Uri): boolean {
const { scheme } = uri;
return (
scheme === DocumentSchemes.File ||
scheme === DocumentSchemes.Git ||
@ -3409,12 +3426,6 @@ export class LocalGitProvider implements GitProvider, Disposable {
);
}
async isTracked(
fileName: string,
repoPath?: string,
options?: { ref?: string; skipCacheUpdate?: boolean },
): Promise<boolean>;
async isTracked(uri: GitUri): Promise<boolean>;
@log<LocalGitProvider['isTracked']>({
exit: tracked => `returned ${tracked}`,
singleLine: true,
@ -3687,7 +3698,7 @@ export class LocalGitProvider implements GitProvider, Disposable {
}
@log()
stashSave(
async stashSave(
repoPath: string,
message?: string,
uris?: Uri[],
@ -3695,7 +3706,7 @@ export class LocalGitProvider implements GitProvider, Disposable {
): Promise<void> {
if (uris == null) return Git.stash__push(repoPath, message, options);
this.ensureGitVersion(
await this.ensureGitVersion(
'2.13.2',
'Stashing individual files',
' Please retry by stashing everything or install a more recent version of Git.',
@ -3704,10 +3715,10 @@ export class LocalGitProvider implements GitProvider, Disposable {
const pathspecs = uris.map(u => `./${Paths.splitPath(u.fsPath, repoPath)[0]}`);
const stdinVersion = '2.30.0';
const stdin = this.compareGitVersion(stdinVersion) !== -1;
const stdin = (await this.compareGitVersion(stdinVersion)) !== -1;
// If we don't support stdin, then error out if we are over the maximum allowed git cli length
if (!stdin && Arrays.countStringLength(pathspecs) > maxGitCliLength) {
this.ensureGitVersion(
await this.ensureGitVersion(
stdinVersion,
`Stashing so many files (${pathspecs.length}) at once`,
' Please retry by stashing fewer files or install a more recent version of Git.',
@ -3774,14 +3785,14 @@ export class LocalGitProvider implements GitProvider, Disposable {
}
}
private compareGitVersion(version: string) {
return Versions.compare(Versions.fromString(Git.getGitVersion()), Versions.fromString(version));
private async compareGitVersion(version: string) {
return Versions.compare(Versions.fromString(await Git.version()), Versions.fromString(version));
}
private ensureGitVersion(version: string, prefix: string, suffix: string): void {
if (this.compareGitVersion(version) === -1) {
private async ensureGitVersion(version: string, prefix: string, suffix: string): Promise<void> {
if ((await this.compareGitVersion(version)) === -1) {
throw new Error(
`${prefix} requires a newer version of Git (>= ${version}) than is currently installed (${Git.getGitVersion()}).${suffix}`,
`${prefix} requires a newer version of Git (>= ${version}) than is currently installed (${await Git.version()}).${suffix}`,
);
}
}

+ 2
- 3
src/vsls/guest.ts View File

@ -2,7 +2,6 @@
import { CancellationToken, Disposable, window, WorkspaceFolder } from 'vscode';
import type { LiveShare, SharedServiceProxy } from '../@types/vsls';
import { Container } from '../container';
import { setEnabled } from '../extension';
import { GitCommandOptions, Repository, RepositoryChangeEvent } from '../git/git';
import { Logger } from '../logger';
import { debug, log } from '../system';
@ -43,12 +42,12 @@ export class VslsGuestService implements Disposable {
@log()
private onAvailabilityChanged(available: boolean) {
if (available) {
void setEnabled(true);
void this.container.git.setEnabledContext(true);
return;
}
void setEnabled(false);
void this.container.git.setEnabledContext(false);
void window.showWarningMessage(
'GitLens features will be unavailable. Unable to connect to the host GitLens service. The host may have disabled GitLens guest access or may not have GitLens installed.',
);

Loading…
Cancel
Save