ソースを参照

Centralizes uris into the provider model

Reworks revision uris to use a packed authority for better stability
main
Eric Amodio 2年前
コミット
03d2b74fc9
28個のファイルの変更438行の追加332行の削除
  1. +1
    -1
      package.json
  2. +1
    -2
      src/commands/browseRepoAtRevision.ts
  3. +6
    -5
      src/commands/diffWith.ts
  4. +1
    -1
      src/commands/diffWithRevisionFrom.ts
  5. +4
    -2
      src/commands/gitCommands.actions.ts
  6. +4
    -2
      src/commands/openFileAtRevision.ts
  7. +2
    -2
      src/commands/openFileAtRevisionFrom.ts
  8. +7
    -3
      src/commands/openRevisionFile.ts
  9. +191
    -108
      src/env/node/git/localGitProvider.ts
  10. +4
    -4
      src/git/formatters/commitFormatter.ts
  11. +0
    -4
      src/git/fsProvider.ts
  12. +11
    -2
      src/git/gitProvider.ts
  13. +80
    -21
      src/git/gitProviderService.ts
  14. +45
    -121
      src/git/gitUri.ts
  15. +7
    -3
      src/git/models/commit.ts
  16. +2
    -1
      src/git/models/logCommit.ts
  17. +26
    -21
      src/git/models/repository.ts
  18. +8
    -8
      src/git/models/status.ts
  19. +9
    -1
      src/system/path.ts
  20. +1
    -0
      src/trackers/documentTracker.ts
  21. +3
    -3
      src/trackers/trackedDocument.ts
  22. +4
    -2
      src/views/nodes/lineHistoryTrackerNode.ts
  23. +1
    -1
      src/views/nodes/mergeConflictCurrentChangesNode.ts
  24. +2
    -2
      src/views/nodes/mergeConflictFileNode.ts
  25. +7
    -1
      src/views/nodes/mergeConflictIncomingChangesNode.ts
  26. +2
    -2
      src/views/nodes/statusFileNode.ts
  27. +3
    -3
      src/views/viewCommands.ts
  28. +6
    -6
      src/vsls/host.ts

+ 1
- 1
package.json ファイルの表示

@ -9965,7 +9965,7 @@
"scheme": "gitlens",
"authority": "*",
"formatting": {
"label": "${path} (${authority})",
"label": "${path} (${query.ref})",
"separator": "/",
"workspaceSuffix": "GitLens",
"stripPathStartingSeparator": true

+ 1
- 2
src/commands/browseRepoAtRevision.ts ファイルの表示

@ -2,7 +2,6 @@
import { commands, TextEditor, Uri } from 'vscode';
import { BuiltInCommands } from '../constants';
import type { Container } from '../container';
import { toGitLensFSUri } from '../git/fsProvider';
import { GitUri } from '../git/gitUri';
import { Logger } from '../logger';
import { Messages } from '../messages';
@ -68,7 +67,7 @@ export class BrowseRepoAtRevisionCommand extends ActiveEditorCommand {
const sha = args?.before
? await this.container.git.resolveReference(gitUri.repoPath!, `${gitUri.sha}^`)
: gitUri.sha;
uri = toGitLensFSUri(sha, gitUri.repoPath!);
uri = this.container.git.getRevisionUri(sha, gitUri.repoPath!, gitUri.repoPath!);
gitUri = GitUri.fromRevisionUri(uri);
openWorkspace(uri, {

+ 6
- 5
src/commands/diffWith.ts ファイルの表示

@ -2,7 +2,6 @@
import { commands, Range, TextDocumentShowOptions, Uri, ViewColumn } from 'vscode';
import { BuiltInCommands, GlyphChars } from '../constants';
import type { Container } from '../container';
import { GitUri } from '../git/gitUri';
import { GitCommit, GitRevision } from '../git/models';
import { Logger } from '../logger';
import { Messages } from '../messages';
@ -119,8 +118,8 @@ export class DiffWithCommand extends Command {
}
const [lhs, rhs] = await Promise.all([
this.container.git.getVersionedUri(args.repoPath, args.lhs.uri.fsPath, args.lhs.sha),
this.container.git.getVersionedUri(args.repoPath, args.rhs.uri.fsPath, args.rhs.sha),
this.container.git.getBestRevisionUri(args.repoPath, args.lhs.uri.fsPath, args.lhs.sha),
this.container.git.getBestRevisionUri(args.repoPath, args.rhs.uri.fsPath, args.rhs.sha),
]);
let rhsSuffix = GitRevision.shorten(rhsSha, { strings: { uncommitted: 'Working Tree' } });
@ -172,8 +171,10 @@ export class DiffWithCommand extends Command {
void (await commands.executeCommand(
BuiltInCommands.Diff,
lhs ?? GitUri.toRevisionUri(GitRevision.deletedOrMissing, args.lhs.uri.fsPath, args.repoPath),
rhs ?? GitUri.toRevisionUri(GitRevision.deletedOrMissing, args.rhs.uri.fsPath, args.repoPath),
lhs ??
this.container.git.getRevisionUri(GitRevision.deletedOrMissing, args.lhs.uri.fsPath, args.repoPath),
rhs ??
this.container.git.getRevisionUri(GitRevision.deletedOrMissing, args.rhs.uri.fsPath, args.repoPath),
title,
args.showOptions,
));

+ 1
- 1
src/commands/diffWithRevisionFrom.ts ファイルの表示

@ -86,7 +86,7 @@ export class DiffWithRevisionFromCommand extends ActiveEditorCommand {
const fileName = normalizePath(relative(gitUri.repoPath, gitUri.fsPath));
const rename = files.find(s => s.fileName === fileName);
if (rename?.originalFileName != null) {
renamedUri = GitUri.resolve(rename.originalFileName, gitUri.repoPath);
renamedUri = this.container.git.getAbsoluteUri(rename.originalFileName, gitUri.repoPath);
renamedTitle = `${basename(rename.originalFileName)} (${GitRevision.shorten(ref)})`;
}
}

+ 4
- 2
src/commands/gitCommands.actions.ts ファイルの表示

@ -531,7 +531,7 @@ export namespace GitActions {
file = fileOrRevisionUri;
}
uri = GitUri.toRevisionUri(
uri = Container.instance.git.getRevisionUri(
file.status === 'D' ? commit.previousFileSha : commit.sha,
file,
commit.repoPath,
@ -624,7 +624,9 @@ export namespace GitActions {
}
findOrOpenEditors(
files.map(file => GitUri.toRevisionUri(file.status === 'D' ? ref2! : ref1!, file, repoPath!)),
files.map(file =>
Container.instance.git.getRevisionUri(file.status === 'D' ? ref2! : ref1!, file, repoPath!),
),
);
}

+ 4
- 2
src/commands/openFileAtRevision.ts ファイルの表示

@ -67,13 +67,15 @@ export class OpenFileAtRevisionCommand extends ActiveEditorCommand {
gitUri.sha,
);
if (diffUris?.previous != null) {
args.revisionUri = GitUri.toRevisionUri(diffUris.previous);
args.revisionUri = this.container.git.getRevisionUri(diffUris.previous);
} else {
void Messages.showCommitHasNoPreviousCommitWarningMessage(blame.commit);
return undefined;
}
} else if (blame?.commit.previousSha != null) {
args.revisionUri = GitUri.toRevisionUri(GitUri.fromCommit(blame.commit, true));
args.revisionUri = this.container.git.getRevisionUri(
GitUri.fromCommit(blame.commit, true),
);
} else {
void Messages.showCommitHasNoPreviousCommitWarningMessage(blame.commit);
return undefined;

+ 2
- 2
src/commands/openFileAtRevisionFrom.ts ファイルの表示

@ -69,7 +69,7 @@ export class OpenFileAtRevisionFromCommand extends ActiveEditorCommand {
const [item] = quickpick.activeItems;
if (item != null) {
void (await GitActions.Commit.openFileAtRevision(
GitUri.toRevisionUri(item.ref, gitUri.fsPath, gitUri.repoPath!),
this.container.git.getRevisionUri(item.ref, gitUri.fsPath, gitUri.repoPath!),
{
annotationType: args!.annotationType,
line: args!.line,
@ -88,7 +88,7 @@ export class OpenFileAtRevisionFromCommand extends ActiveEditorCommand {
}
void (await GitActions.Commit.openFileAtRevision(
GitUri.toRevisionUri(args.reference.ref, gitUri.fsPath, gitUri.repoPath),
this.container.git.getRevisionUri(args.reference.ref, gitUri.fsPath, gitUri.repoPath),
{
annotationType: args.annotationType,
line: args.line,

+ 7
- 3
src/commands/openRevisionFile.ts ファイルの表示

@ -40,10 +40,14 @@ export class OpenRevisionFileCommand extends ActiveEditorCommand {
args.revisionUri =
commit != null && commit.status === 'D'
? GitUri.toRevisionUri(commit.previousSha!, commit.previousUri.fsPath, commit.repoPath)
: GitUri.toRevisionUri(gitUri);
? this.container.git.getRevisionUri(
commit.previousSha!,
commit.previousUri.fsPath,
commit.repoPath,
)
: this.container.git.getRevisionUri(gitUri);
} else {
args.revisionUri = GitUri.toRevisionUri(gitUri);
args.revisionUri = this.container.git.getRevisionUri(gitUri);
}
}

+ 191
- 108
src/env/node/git/localGitProvider.ts ファイルの表示

@ -1,7 +1,7 @@
'use strict';
import { readdir, realpath } from 'fs';
import { hostname, userInfo } from 'os';
import { dirname, relative, resolve as resolvePath } from 'path';
import { resolve as resolvePath } from 'path';
import {
Disposable,
env,
@ -35,10 +35,11 @@ import {
RepositoryCloseEvent,
RepositoryInitWatcher,
RepositoryOpenEvent,
RevisionUriData,
ScmRepository,
} from '../../../git/gitProvider';
import { GitProviderService } from '../../../git/gitProviderService';
import { GitUri } from '../../../git/gitUri';
import { GitProviderService, isUriRegex } from '../../../git/gitProviderService';
import { encodeGitLensRevisionUriAuthority, GitUri } from '../../../git/gitUri';
import {
BranchSortOptions,
GitAuthor,
@ -94,9 +95,10 @@ import { SearchPattern } from '../../../git/search';
import { LogCorrelationContext, Logger } from '../../../logger';
import { Messages } from '../../../messages';
import { Arrays, debug, Functions, gate, Iterables, log, Strings, Versions } from '../../../system';
import { isFolderGlob, normalizePath, splitPath } from '../../../system/path';
import { filterMap } from '../../../system/array';
import { dirname, isAbsolute, isFolderGlob, normalizePath, relative, splitPath } from '../../../system/path';
import { any, PromiseOrValue } from '../../../system/promise';
import { equalsIgnoreCase } from '../../../system/string';
import { CharCode, equalsIgnoreCase } from '../../../system/string';
import { PathTrie } from '../../../system/trie';
import {
CachedBlame,
@ -490,6 +492,157 @@ export class LocalGitProvider implements GitProvider, Disposable {
});
}
getAbsoluteUri(pathOrUri: string | Uri, base: string | Uri): Uri {
// Convert the base to a Uri if it isn't one
if (typeof base === 'string') {
// If it looks like a Uri parse it
if (isUriRegex.test(base)) {
base = Uri.parse(base);
} else {
if (!isAbsolute(base)) {
debugger;
throw new Error(`Base path '${base}' must be an absolute path`);
}
base = Uri.file(base);
}
}
// Short-circuit if the path is relative
if (typeof pathOrUri === 'string' && !isAbsolute(pathOrUri) && !isUriRegex.test(pathOrUri)) {
return Uri.joinPath(base, pathOrUri);
}
const relativePath = this.getRelativePath(pathOrUri, base);
const uri = Uri.joinPath(base, relativePath);
// TODO@eamodio We need to move live share support to a separate provider
if (this.container.vsls.isMaybeGuest) {
return uri.with({ scheme: DocumentSchemes.Vsls });
}
return uri;
}
@log()
async getBestRevisionUri(repoPath: string, path: string, ref: string | undefined): Promise<Uri | undefined> {
if (ref === GitRevision.deletedOrMissing) return undefined;
// TODO@eamodio Align this with isTrackedCore?
if (!ref || (GitRevision.isUncommitted(ref) && !GitRevision.isUncommittedStaged(ref))) {
// Make sure the file exists in the repo
let data = await Git.ls_files(repoPath, path);
if (data != null) return this.getAbsoluteUri(path, repoPath);
// Check if the file exists untracked
data = await Git.ls_files(repoPath, path, { untracked: true });
if (data != null) return this.getAbsoluteUri(path, repoPath);
return undefined;
}
if (GitRevision.isUncommittedStaged(ref)) return GitUri.git(path, repoPath);
return this.getRevisionUri(repoPath, path, ref);
}
getRelativePath(pathOrUri: string | Uri, base: string | Uri): string {
// Convert the base to a Uri if it isn't one
if (typeof base === 'string') {
// If it looks like a Uri parse it
if (isUriRegex.test(base)) {
base = Uri.parse(base);
} else {
if (!isAbsolute(base)) {
debugger;
throw new Error(`Base path '${base}' must be an absolute path`);
}
base = Uri.file(base);
}
}
// Convert the path to a Uri if it isn't one
if (typeof pathOrUri === 'string') {
if (isUriRegex.test(pathOrUri)) {
pathOrUri = Uri.parse(pathOrUri);
} else {
if (!isAbsolute(pathOrUri)) return normalizePath(pathOrUri);
pathOrUri = Uri.file(pathOrUri);
}
}
const relativePath = relative(base.fsPath, pathOrUri.fsPath);
return normalizePath(relativePath);
}
getRevisionUri(repoPath: string, path: string, ref: string): Uri {
if (GitRevision.isUncommitted(ref)) {
return GitRevision.isUncommittedStaged(ref)
? GitUri.git(path, repoPath)
: this.getAbsoluteUri(path, repoPath);
}
path = normalizePath(this.getAbsoluteUri(path, repoPath).fsPath);
if (path.charCodeAt(0) !== CharCode.Slash) {
path = `/${path}`;
}
const metadata: RevisionUriData = {
ref: ref,
repoPath: normalizePath(repoPath),
};
const uri = Uri.from({
scheme: DocumentSchemes.GitLens,
authority: encodeGitLensRevisionUriAuthority(metadata),
path: path,
query: ref ? JSON.stringify({ ref: GitRevision.shorten(ref) }) : undefined,
});
return uri;
}
@log()
async getWorkingUri(repoPath: string, uri: Uri) {
let fileName = GitUri.relativeTo(uri, repoPath);
let data;
let ref;
do {
data = await Git.ls_files(repoPath, fileName);
if (data != null) {
fileName = Strings.splitSingle(data, '\n')[0];
break;
}
// TODO: Add caching
// Get the most recent commit for this file name
ref = await Git.log__file_recent(repoPath, fileName, {
ordering: this.container.config.advanced.commitOrdering,
similarityThreshold: this.container.config.advanced.similarityThreshold,
});
if (ref == null) return undefined;
// Now check if that commit had any renames
data = await Git.log__file(repoPath, '.', ref, {
filters: ['R', 'C', 'D'],
format: 'simple',
limit: 1,
ordering: this.container.config.advanced.commitOrdering,
});
if (data == null || data.length === 0) break;
const [foundRef, foundFile, foundStatus] = GitLogParser.parseSimpleRenamed(data, fileName);
if (foundStatus === 'D' && foundFile != null) return undefined;
if (foundRef == null || foundFile == null) break;
fileName = foundFile;
} while (true);
uri = this.getAbsoluteUri(fileName, repoPath);
return (await fsExists(uri.fsPath)) ? uri : undefined;
}
@log()
async addRemote(repoPath: string, name: string, url: string): Promise<void> {
await Git.remote__add(repoPath, name, url);
@ -1298,10 +1451,7 @@ export class LocalGitProvider implements GitProvider, Disposable {
const data = await Git.branch__containsOrPointsAt(repoPath, ref, options);
if (!data) return [];
return data
.split('\n')
.map(b => b.trim())
.filter(<T>(i?: T): i is T => Boolean(i));
return filterMap(data.split('\n'), b => b.trim() || undefined);
}
@log()
@ -2580,12 +2730,12 @@ export class LocalGitProvider implements GitProvider, Disposable {
): Promise<{ current: GitUri; previous: GitUri | undefined } | undefined> {
if (ref === GitRevision.deletedOrMissing) return undefined;
const fileName = GitUri.relativeTo(uri, repoPath);
const path = this.getRelativePath(uri, repoPath);
// If we are at the working tree (i.e. no ref), we need to dig deeper to figure out where to go
if (ref == null || ref.length === 0) {
if (!ref) {
// First, check the file status to see if there is anything staged
const status = await this.getStatusForFile(repoPath, fileName);
const status = await this.getStatusForFile(repoPath, path);
if (status != null) {
// If the file is staged with working changes, diff working with staged (index)
// If the file is staged without working changes, diff staged with HEAD
@ -2598,20 +2748,20 @@ export class LocalGitProvider implements GitProvider, Disposable {
if (skip === 0) {
// Diff working with staged
return {
current: GitUri.fromFile(fileName, repoPath, undefined),
previous: GitUri.fromFile(fileName, repoPath, GitRevision.uncommittedStaged),
current: GitUri.fromFile(path, repoPath, undefined),
previous: GitUri.fromFile(path, repoPath, GitRevision.uncommittedStaged),
};
}
return {
// Diff staged with HEAD (or prior if more skips)
current: GitUri.fromFile(fileName, repoPath, GitRevision.uncommittedStaged),
current: GitUri.fromFile(path, repoPath, GitRevision.uncommittedStaged),
previous: await this.getPreviousUri(repoPath, uri, ref, skip - 1, undefined, firstParent),
};
} else if (status.workingTreeStatus != null) {
if (skip === 0) {
return {
current: GitUri.fromFile(fileName, repoPath, undefined),
current: GitUri.fromFile(path, repoPath, undefined),
previous: await this.getPreviousUri(repoPath, uri, undefined, skip, undefined, firstParent),
};
}
@ -2624,7 +2774,7 @@ export class LocalGitProvider implements GitProvider, Disposable {
else if (GitRevision.isUncommittedStaged(ref)) {
const current =
skip === 0
? GitUri.fromFile(fileName, repoPath, ref)
? GitUri.fromFile(path, repoPath, ref)
: (await this.getPreviousUri(repoPath, uri, undefined, skip - 1, undefined, firstParent))!;
if (current == null || current.sha === GitRevision.deletedOrMissing) return undefined;
@ -2637,7 +2787,7 @@ export class LocalGitProvider implements GitProvider, Disposable {
// If we are at a commit, diff commit with previous
const current =
skip === 0
? GitUri.fromFile(fileName, repoPath, ref)
? GitUri.fromFile(path, repoPath, ref)
: (await this.getPreviousUri(repoPath, uri, ref, skip - 1, undefined, firstParent))!;
if (current == null || current.sha === GitRevision.deletedOrMissing) return undefined;
@ -2657,12 +2807,12 @@ export class LocalGitProvider implements GitProvider, Disposable {
): Promise<{ current: GitUri; previous: GitUri | undefined; line: number } | undefined> {
if (ref === GitRevision.deletedOrMissing) return undefined;
let fileName = GitUri.relativeTo(uri, repoPath);
let path = this.getRelativePath(uri, repoPath);
let previous;
// If we are at the working tree (i.e. no ref), we need to dig deeper to figure out where to go
if (ref == null || ref.length === 0) {
if (!ref) {
// First, check the blame on the current line to see if there are any working/staged changes
const gitUri = new GitUri(uri, repoPath);
@ -2677,15 +2827,15 @@ export class LocalGitProvider implements GitProvider, Disposable {
// If the document is dirty (unsaved), use the status to determine where to go
if (document.isDirty) {
// Check the file status to see if there is anything staged
const status = await this.getStatusForFile(repoPath, fileName);
const status = await this.getStatusForFile(repoPath, path);
if (status != null) {
// If the file is staged, diff working with staged (index)
// If the file is not staged, diff working with HEAD
if (status.indexStatus != null) {
// Diff working with staged
return {
current: GitUri.fromFile(fileName, repoPath, undefined),
previous: GitUri.fromFile(fileName, repoPath, GitRevision.uncommittedStaged),
current: GitUri.fromFile(path, repoPath, undefined),
previous: GitUri.fromFile(path, repoPath, GitRevision.uncommittedStaged),
line: editorLine,
};
}
@ -2693,7 +2843,7 @@ export class LocalGitProvider implements GitProvider, Disposable {
// Diff working with HEAD (or prior if more skips)
return {
current: GitUri.fromFile(fileName, repoPath, undefined),
current: GitUri.fromFile(path, repoPath, undefined),
previous: await this.getPreviousUri(repoPath, uri, undefined, skip, editorLine),
line: editorLine,
};
@ -2715,19 +2865,19 @@ export class LocalGitProvider implements GitProvider, Disposable {
// If line is committed, diff with line ref with previous
else {
ref = blameLine.commit.sha;
fileName = blameLine.commit.fileName || (blameLine.commit.originalFileName ?? fileName);
uri = GitUri.resolve(fileName, repoPath);
path = blameLine.commit.fileName || (blameLine.commit.originalFileName ?? path);
uri = this.getAbsoluteUri(path, repoPath);
editorLine = blameLine.line.originalLine - 1;
if (skip === 0 && blameLine.commit.previousSha) {
previous = GitUri.fromFile(fileName, repoPath, blameLine.commit.previousSha);
previous = GitUri.fromFile(path, repoPath, blameLine.commit.previousSha);
}
}
} else {
if (GitRevision.isUncommittedStaged(ref)) {
const current =
skip === 0
? GitUri.fromFile(fileName, repoPath, ref)
? GitUri.fromFile(path, repoPath, ref)
: (await this.getPreviousUri(repoPath, uri, undefined, skip - 1, editorLine))!;
if (current.sha === GitRevision.deletedOrMissing) return undefined;
@ -2744,18 +2894,18 @@ export class LocalGitProvider implements GitProvider, Disposable {
// Diff with line ref with previous
ref = blameLine.commit.sha;
fileName = blameLine.commit.fileName || (blameLine.commit.originalFileName ?? fileName);
uri = GitUri.resolve(fileName, repoPath);
path = blameLine.commit.fileName || (blameLine.commit.originalFileName ?? path);
uri = this.getAbsoluteUri(path, repoPath);
editorLine = blameLine.line.originalLine - 1;
if (skip === 0 && blameLine.commit.previousSha) {
previous = GitUri.fromFile(fileName, repoPath, blameLine.commit.previousSha);
previous = GitUri.fromFile(path, repoPath, blameLine.commit.previousSha);
}
}
const current =
skip === 0
? GitUri.fromFile(fileName, repoPath, ref)
? GitUri.fromFile(path, repoPath, ref)
: (await this.getPreviousUri(repoPath, uri, ref, skip - 1, editorLine))!;
if (current.sha === GitRevision.deletedOrMissing) return undefined;
@ -2783,11 +2933,12 @@ export class LocalGitProvider implements GitProvider, Disposable {
ref = undefined;
}
const fileName = GitUri.relativeTo(uri, repoPath);
const path = this.getRelativePath(uri, repoPath);
// TODO: Add caching
let data;
try {
data = await Git.log__file(repoPath, fileName, ref, {
data = await Git.log__file(repoPath, path, ref, {
firstParent: firstParent,
format: 'simple',
limit: skip + 2,
@ -2799,16 +2950,16 @@ export class LocalGitProvider implements GitProvider, Disposable {
// If the line count is invalid just fallback to the most recent commit
if ((ref == null || GitRevision.isUncommittedStaged(ref)) && GitErrors.invalidLineCount.test(msg)) {
if (ref == null) {
const status = await this.getStatusForFile(repoPath, fileName);
const status = await this.getStatusForFile(repoPath, path);
if (status?.indexStatus != null) {
return GitUri.fromFile(fileName, repoPath, GitRevision.uncommittedStaged);
return GitUri.fromFile(path, repoPath, GitRevision.uncommittedStaged);
}
}
ref = await Git.log__file_recent(repoPath, fileName, {
ref = await Git.log__file_recent(repoPath, path, {
ordering: this.container.config.advanced.commitOrdering,
});
return GitUri.fromFile(fileName, repoPath, ref ?? GitRevision.deletedOrMissing);
return GitUri.fromFile(path, repoPath, ref ?? GitRevision.deletedOrMissing);
}
Logger.error(ex, cc);
@ -2820,7 +2971,7 @@ export class LocalGitProvider implements GitProvider, Disposable {
// If the previous ref matches the ref we asked for assume we are at the end of the history
if (ref != null && ref === previousRef) return undefined;
return GitUri.fromFile(file ?? fileName, repoPath, previousRef ?? GitRevision.deletedOrMissing);
return GitUri.fromFile(file ?? path, repoPath, previousRef ?? GitRevision.deletedOrMissing);
}
@log()
@ -3148,74 +3299,6 @@ export class LocalGitProvider implements GitProvider, Disposable {
return GitTreeParser.parse(data) ?? [];
}
@log()
async getVersionedUri(repoPath: string, fileName: string, ref: string | undefined): Promise<Uri | undefined> {
if (ref === GitRevision.deletedOrMissing) return undefined;
if (
ref == null ||
ref.length === 0 ||
(GitRevision.isUncommitted(ref) && !GitRevision.isUncommittedStaged(ref))
) {
// Make sure the file exists in the repo
let data = await Git.ls_files(repoPath, fileName);
if (data != null) return GitUri.file(fileName);
// Check if the file exists untracked
data = await Git.ls_files(repoPath, fileName, { untracked: true });
if (data != null) return GitUri.file(fileName);
return undefined;
}
if (GitRevision.isUncommittedStaged(ref)) {
return GitUri.git(fileName, repoPath);
}
return GitUri.toRevisionUri(ref, fileName, repoPath);
}
@log()
async getWorkingUri(repoPath: string, uri: Uri) {
let fileName = GitUri.relativeTo(uri, repoPath);
let data;
let ref;
do {
data = await Git.ls_files(repoPath, fileName);
if (data != null) {
fileName = Strings.splitSingle(data, '\n')[0];
break;
}
// TODO: Add caching
// Get the most recent commit for this file name
ref = await Git.log__file_recent(repoPath, fileName, {
ordering: this.container.config.advanced.commitOrdering,
similarityThreshold: this.container.config.advanced.similarityThreshold,
});
if (ref == null) return undefined;
// Now check if that commit had any renames
data = await Git.log__file(repoPath, '.', ref, {
filters: ['R', 'C', 'D'],
format: 'simple',
limit: 1,
ordering: this.container.config.advanced.commitOrdering,
});
if (data == null || data.length === 0) break;
const [foundRef, foundFile, foundStatus] = GitLogParser.parseSimpleRenamed(data, fileName);
if (foundStatus === 'D' && foundFile != null) return undefined;
if (foundRef == null || foundFile == null) break;
fileName = foundFile;
} while (true);
uri = GitUri.resolve(fileName, repoPath);
return (await fsExists(uri.fsPath)) ? uri : undefined;
}
@log({ args: { 1: false } })
async hasBranchOrTag(
repoPath: string | undefined,
@ -3467,7 +3550,7 @@ export class LocalGitProvider implements GitProvider, Disposable {
return (await Git.rev_parse__verify(repoPath, ref)) ?? ref;
}
const path = typeof pathOrUri === 'string' ? pathOrUri : normalizePath(relative(repoPath, pathOrUri.fsPath));
const path = normalizePath(this.getRelativePath(pathOrUri, repoPath));
const blob = await Git.rev_parse__verify(repoPath, ref, path);
if (blob == null) return GitRevision.deletedOrMissing;

+ 4
- 4
src/git/formatters/commitFormatter.ts ファイルの表示

@ -19,7 +19,7 @@ import { emojify } from '../../emojis';
import { Iterables, Strings } from '../../system';
import { PromiseCancelledError } from '../../system/promise';
import { ContactPresence } from '../../vsls/vsls';
import { GitUri } from '../gitUri';
import type { GitUri } from '../gitUri';
import { GitCommit, GitLogCommit, GitRemote, GitRevision, IssueOrPullRequest, PullRequest } from '../models';
import { RemoteProvider } from '../remotes/provider';
import { FormatOptions, Formatter } from './formatter';
@ -297,7 +297,7 @@ export class CommitFormatter extends Formatter {
})} "Open Changes with Previous Revision")`;
commands += ` &nbsp;&nbsp;[$(versions)](${OpenFileAtRevisionCommand.getMarkdownCommandArgs(
GitUri.toRevisionUri(diffUris.previous),
Container.instance.git.getRevisionUri(diffUris.previous),
FileAnnotationType.Blame,
this._options.editor?.line,
)} "Open Blame Prior to this Change")`;
@ -325,7 +325,7 @@ export class CommitFormatter extends Formatter {
)} "Open Changes with Previous Revision")`;
if (this._item.previousSha != null) {
const uri = GitUri.toRevisionUri(
const uri = Container.instance.git.getRevisionUri(
this._item.previousSha,
this._item.previousUri.fsPath,
this._item.repoPath,
@ -394,7 +394,7 @@ export class CommitFormatter extends Formatter {
}
commands += `${separator}[$(ellipsis)](${ShowQuickCommitFileCommand.getMarkdownCommandArgs({
revisionUri: GitUri.toRevisionUri(this._item.toGitUri()).toString(true),
revisionUri: Container.instance.git.getRevisionUri(this._item.toGitUri()).toString(true),
})} "Show More Actions")`;
return this._padOrTruncate(commands, this._options.tokenOptions.commands);

+ 0
- 4
src/git/fsProvider.ts ファイルの表示

@ -26,10 +26,6 @@ export function fromGitLensFSUri(uri: Uri): { path: string; ref: string; repoPat
return { path: gitUri.relativePath, ref: gitUri.sha!, repoPath: gitUri.repoPath! };
}
export function toGitLensFSUri(ref: string, repoPath: string): Uri {
return GitUri.toRevisionUri(ref, repoPath, repoPath);
}
export class GitFileSystemProvider implements FileSystemProvider, Disposable {
private readonly _disposable: Disposable;
private readonly _searchTreeMap = new Map<string, Promise<TernarySearchTree<string, GitTreeEntry>>>();

+ 11
- 2
src/git/gitProvider.ts ファイルの表示

@ -87,6 +87,12 @@ export interface GitProvider extends Disposable {
getOpenScmRepositories(): Promise<ScmRepository[]>;
getOrOpenScmRepository(repoPath: string): Promise<ScmRepository | undefined>;
getAbsoluteUri(pathOrUri: string | Uri, base?: string | Uri): Uri;
getBestRevisionUri(repoPath: string, path: string, ref: string | undefined): Promise<Uri | undefined>;
getRelativePath(pathOrUri: string | Uri, base: string | Uri): string;
getRevisionUri(repoPath: string, path: string, ref: string): Uri;
getWorkingUri(repoPath: string, uri: Uri): Promise<Uri | undefined>;
addRemote(repoPath: string, name: string, url: string): Promise<void>;
pruneRemote(repoPath: string, remoteName: string): Promise<void>;
applyChangesToWorkingFile(uri: GitUri, ref1?: string, ref2?: string): Promise<void>;
@ -336,8 +342,6 @@ export interface GitProvider extends Disposable {
): Promise<PagedResult<GitTag>>;
getTreeEntryForRevision(repoPath: string, path: string, ref: string): Promise<GitTreeEntry | undefined>;
getTreeForRevision(repoPath: string, ref: string): Promise<GitTreeEntry[]>;
getVersionedUri(repoPath: string, fileName: string, ref: string | undefined): Promise<Uri | undefined>;
getWorkingUri(repoPath: string, uri: Uri): Promise<Uri | undefined>;
hasBranchOrTag(
repoPath: string | undefined,
@ -395,3 +399,8 @@ export interface GitProvider extends Disposable {
options?: { includeUntracked?: boolean | undefined; keepIndex?: boolean | undefined },
): Promise<void>;
}
export interface RevisionUriData {
ref?: string;
repoPath: string;
}

+ 80
- 21
src/git/gitProviderService.ts ファイルの表示

@ -34,7 +34,7 @@ import { groupByFilterMap, groupByMap } from '../system/array';
import { gate } from '../system/decorators/gate';
import { debug, log } from '../system/decorators/log';
import { count, filter, first, flatMap, map } from '../system/iterable';
import { basename, dirname, normalizePath } from '../system/path';
import { basename, dirname, isAbsolute, normalizePath } from '../system/path';
import { cancellable, isPromise, PromiseCancelledError } from '../system/promise';
import { CharCode } from '../system/string';
import { VisitedPathsTrie } from '../system/trie';
@ -83,6 +83,8 @@ import { RemoteProviders } from './remotes/factory';
import { Authentication, RemoteProvider, RichRemoteProvider } from './remotes/provider';
import { SearchPattern } from './search';
export const isUriRegex = /^(\w[\w\d+.-]{1,}?):\/\//;
const maxDefaultBranchWeight = 100;
const weightedDefaultBranches = new Map<string, number>([
['master', maxDefaultBranchWeight],
@ -589,6 +591,80 @@ export class GitProviderService implements Disposable {
}
}
getAbsoluteUri(pathOrUri: string | Uri, base?: string | Uri): Uri {
if (base == null) {
if (typeof pathOrUri === 'string') {
if (isUriRegex.test(pathOrUri)) {
debugger;
return Uri.parse(pathOrUri, true);
}
// I think it is safe to assume this should be file://
return Uri.file(pathOrUri);
}
return pathOrUri;
}
// Short-circuit if the base is already a Uri and the path is relative
if (typeof base !== 'string' && typeof pathOrUri === 'string' && !isAbsolute(pathOrUri)) {
return Uri.joinPath(base, pathOrUri);
}
const { provider } = this.getProvider(base);
return provider.getAbsoluteUri(pathOrUri, base);
}
@log()
async getBestRevisionUri(
repoPath: string | Uri | undefined,
path: string,
ref: string | undefined,
): Promise<Uri | undefined> {
if (repoPath == null || ref === GitRevision.deletedOrMissing) return undefined;
const { provider, path: rp } = this.getProvider(repoPath);
return provider.getBestRevisionUri(rp, provider.getRelativePath(path, rp), ref);
}
getRelativePath(pathOrUri: string | Uri, base: string | Uri): string {
const { provider } = this.getProvider(pathOrUri instanceof Uri ? pathOrUri : base);
return provider.getRelativePath(pathOrUri, base);
}
getRevisionUri(uri: GitUri): Uri;
getRevisionUri(ref: string, path: string, repoPath: string): Uri;
getRevisionUri(ref: string, file: GitFile, repoPath: string): Uri;
@log()
getRevisionUri(refOrUri: string | GitUri, pathOrFile?: string | GitFile, repoPath?: string): Uri {
let path: string;
let ref: string | undefined;
if (typeof refOrUri === 'string') {
ref = refOrUri;
if (typeof pathOrFile === 'string') {
path = pathOrFile;
} else {
path = pathOrFile!.originalFileName ?? pathOrFile!.fileName;
}
} else {
ref = refOrUri.sha;
repoPath = refOrUri.repoPath!;
path = refOrUri.scheme === DocumentSchemes.File ? refOrUri.fsPath : refOrUri.path;
}
const { provider, path: rp } = this.getProvider(repoPath!);
return provider.getRevisionUri(rp, provider.getRelativePath(path, rp), ref!);
}
@log()
async getWorkingUri(repoPath: string | Uri, uri: Uri) {
const { provider, path } = this.getProvider(repoPath);
return provider.getWorkingUri(path, uri);
}
@log()
addRemote(repoPath: string | Uri, name: string, url: string): Promise<void> {
const { provider, path } = this.getProvider(repoPath);
@ -1521,7 +1597,7 @@ export class GitProviderService implements Disposable {
if (typeof pathOrUri === 'string') {
if (!pathOrUri) return undefined;
return this._repositories.getClosest(Uri.file(normalizePath(pathOrUri)));
return this._repositories.getClosest(this.getAbsoluteUri(pathOrUri));
}
return this._repositories.getClosest(pathOrUri);
@ -1590,7 +1666,7 @@ export class GitProviderService implements Disposable {
if (repoPath == null || !path) return undefined;
const { provider, path: rp } = this.getProvider(repoPath);
return provider.getTreeEntryForRevision(rp, path, ref);
return provider.getTreeEntryForRevision(rp, provider.getRelativePath(path, rp), ref);
}
@log()
@ -1601,30 +1677,13 @@ export class GitProviderService implements Disposable {
return provider.getTreeForRevision(path, ref);
}
@gate()
@log()
getRevisionContent(repoPath: string | Uri, path: string, ref: string): Promise<Uint8Array | undefined> {
const { provider, path: rp } = this.getProvider(repoPath);
return provider.getRevisionContent(rp, path, ref);
}
@log()
async getVersionedUri(
repoPath: string | Uri | undefined,
fileName: string,
ref: string | undefined,
): Promise<Uri | undefined> {
if (repoPath == null || ref === GitRevision.deletedOrMissing) return undefined;
const { provider, path } = this.getProvider(repoPath);
return provider.getVersionedUri(path, fileName, ref);
}
@log()
async getWorkingUri(repoPath: string | Uri, uri: Uri) {
const { provider, path } = this.getProvider(repoPath);
return provider.getWorkingUri(path, uri);
}
@log({ args: { 1: false } })
async hasBranchOrTag(
repoPath: string | Uri | undefined,

+ 45
- 121
src/git/gitUri.ts ファイルの表示

@ -1,12 +1,15 @@
'use strict';
import { Uri } from 'vscode';
import { decodeUtf8Hex, encodeUtf8Hex } from '@env/hex';
import { UriComparer } from '../comparers';
import { DocumentSchemes } from '../constants';
import { Container } from '../container';
import { Logger } from '../logger';
import { debug, memoize, Strings } from '../system';
import { basename, dirname, isAbsolute, joinPaths, normalizePath, relative } from '../system/path';
import { CharCode } from '../system/string';
import { debug } from '../system/decorators/log';
import { memoize } from '../system/decorators/memoize';
import { basename, dirname, isAbsolute, normalizePath, relative } from '../system/path';
import { CharCode, truncateLeft, truncateMiddle } from '../system/string';
import { RevisionUriData } from './gitProvider';
import { GitCommit, GitFile, GitRevision } from './models';
export interface GitCommitish {
@ -54,30 +57,18 @@ export class GitUri extends (Uri as any as UriEx) {
}
if (uri.scheme === DocumentSchemes.GitLens) {
const data = JSON.parse(uri.query) as UriRevisionData;
// Fixes issues with uri.query:
// When Uri's come from the FileSystemProvider, the uri.query only contains the root repo info (not the actual file path)
// When Uri's come from breadcrumbs (via the FileSystemProvider), the uri.query contains the wrong file path
if (data.path !== uri.path) {
if (data.path.startsWith('//') && !uri.path.startsWith('//')) {
data.path = `/${uri.path}`;
} else {
data.path = uri.path;
}
}
super({
scheme: uri.scheme,
authority: uri.authority,
path: data.path,
query: JSON.stringify(data),
path: uri.path,
query: uri.query,
fragment: uri.fragment,
});
this.repoPath = data.repoPath;
if (GitRevision.isUncommittedStaged(data.ref) || !GitRevision.isUncommitted(data.ref)) {
this.sha = data.ref;
const metadata = decodeGitLensRevisionUriAuthority<RevisionUriData>(uri.authority);
this.repoPath = metadata.repoPath;
if (GitRevision.isUncommittedStaged(metadata.ref) || !GitRevision.isUncommitted(metadata.ref)) {
this.sha = metadata.ref;
}
return;
@ -98,7 +89,10 @@ export class GitUri extends (Uri as any as UriEx) {
}
let authority = uri.authority;
let fsPath = GitUri.resolvePath(commitOrRepoPath.fileName ?? uri.fsPath, commitOrRepoPath.repoPath);
let fsPath = normalizePath(
Container.instance.git.getAbsoluteUri(commitOrRepoPath.fileName ?? uri.fsPath, commitOrRepoPath.repoPath)
.fsPath,
);
// Check for authority as used in UNC shares or use the path as given
if (fsPath.charCodeAt(0) === CharCode.Slash && fsPath.charCodeAt(1) === CharCode.Slash) {
@ -165,7 +159,7 @@ export class GitUri extends (Uri as any as UriEx) {
@memoize()
private get relativeFsPath() {
return this.repoPath == null || this.repoPath.length === 0 ? this.fsPath : relative(this.repoPath, this.fsPath);
return !this.repoPath ? this.fsPath : relative(this.repoPath, this.fsPath);
}
@memoize()
@ -180,6 +174,7 @@ export class GitUri extends (Uri as any as UriEx) {
@memoize()
documentUri() {
// TODO@eamodio which is correct?
return Uri.from({
scheme: this.scheme,
authority: this.authority,
@ -187,6 +182,7 @@ export class GitUri extends (Uri as any as UriEx) {
query: this.query,
fragment: this.fragment,
});
return Container.instance.git.getAbsoluteUri(this.fsPath, this.repoPath);
}
equals(uri: Uri | undefined) {
@ -205,7 +201,7 @@ export class GitUri extends (Uri as any as UriEx) {
@memoize()
toFileUri() {
return GitUri.file(this.fsPath);
return Container.instance.git.getAbsoluteUri(this.fsPath, this.repoPath);
}
static file(path: string, useVslsScheme?: boolean) {
@ -227,7 +223,7 @@ export class GitUri extends (Uri as any as UriEx) {
}
static fromFile(file: string | GitFile, repoPath: string, ref?: string, original: boolean = false): GitUri {
const uri = GitUri.resolve(
const uri = Container.instance.git.getAbsoluteUri(
typeof file === 'string' ? file : (original && file.originalFileName) || file.fileName,
repoPath,
);
@ -237,9 +233,9 @@ export class GitUri extends (Uri as any as UriEx) {
}
static fromRepoPath(repoPath: string, ref?: string) {
return ref == null || ref.length === 0
? new GitUri(GitUri.file(repoPath), repoPath)
: new GitUri(GitUri.file(repoPath), { repoPath: repoPath, sha: ref });
return !ref
? new GitUri(Container.instance.git.getAbsoluteUri(repoPath, repoPath), repoPath)
: new GitUri(Container.instance.git.getAbsoluteUri(repoPath, repoPath), { repoPath: repoPath, sha: ref });
}
static fromRevisionUri(uri: Uri): GitUri {
@ -362,12 +358,12 @@ export class GitUri extends (Uri as any as UriEx) {
let file = basename(fileName);
if (options?.truncateTo != null && file.length >= options.truncateTo) {
return Strings.truncateMiddle(file, options.truncateTo);
return truncateMiddle(file, options.truncateTo);
}
if (options?.suffix) {
if (options?.truncateTo != null && file.length + options.suffix.length >= options?.truncateTo) {
return `${Strings.truncateMiddle(file, options.truncateTo - options.suffix.length)}${options.suffix}`;
return `${truncateMiddle(file, options.truncateTo - options.suffix.length)}${options.suffix}`;
}
file += options.suffix;
@ -395,12 +391,12 @@ export class GitUri extends (Uri as any as UriEx) {
let file = basename(fileName);
if (truncateTo != null && file.length >= truncateTo) {
return Strings.truncateMiddle(file, truncateTo);
return truncateMiddle(file, truncateTo);
}
if (suffix) {
if (truncateTo != null && file.length + suffix.length >= truncateTo) {
return `${Strings.truncateMiddle(file, truncateTo - suffix.length)}${suffix}`;
return `${truncateMiddle(file, truncateTo - suffix.length)}${suffix}`;
}
file += suffix;
@ -412,7 +408,7 @@ export class GitUri extends (Uri as any as UriEx) {
file = `/${file}`;
if (truncateTo != null && file.length + directory.length >= truncateTo) {
return `${Strings.truncateLeft(directory, truncateTo - file.length)}${file}`;
return `${truncateLeft(directory, truncateTo - file.length)}${file}`;
}
return `${directory}${file}`;
@ -427,34 +423,17 @@ export class GitUri extends (Uri as any as UriEx) {
return normalizePath(relativePath);
}
static git(fileName: string, repoPath?: string) {
const path = GitUri.resolvePath(fileName, repoPath);
return Uri.parse(
// Change encoded / back to / otherwise uri parsing won't work properly
`${DocumentSchemes.Git}:/${encodeURIComponent(path).replace(/%2F/g, '/')}?${encodeURIComponent(
JSON.stringify({
// Ensure we use the fsPath here, otherwise the url won't open properly
path: Uri.file(path).fsPath,
ref: '~',
}),
)}`,
);
}
static resolvePath(fileName: string, repoPath?: string) {
const normalizedFileName = normalizePath(fileName);
if (repoPath === undefined) return normalizedFileName;
const normalizedRepoPath = normalizePath(repoPath);
if (normalizedFileName == null || normalizedFileName.length === 0) return normalizedRepoPath;
if (normalizedFileName.startsWith(normalizedRepoPath)) return normalizedFileName;
return normalizePath(joinPaths(normalizedRepoPath, normalizedFileName));
}
static resolve(fileName: string, repoPath?: string) {
return GitUri.file(this.resolvePath(fileName, repoPath));
static git(path: string, repoPath?: string): Uri {
const uri = Container.instance.git.getAbsoluteUri(path, repoPath);
return Uri.from({
scheme: DocumentSchemes.Git,
path: uri.path,
query: JSON.stringify({
// Ensure we use the fsPath here, otherwise the url won't open properly
path: uri.scheme === DocumentSchemes.File ? uri.fsPath : uri.path,
ref: '~',
}),
});
}
static toKey(fileName: string): string;
@ -467,67 +446,12 @@ export class GitUri extends (Uri as any as UriEx) {
// ? GitUri.file(fileNameOrUri).toString(true)
// : fileNameOrUri.toString(true);
}
}
static toRevisionUri(uri: GitUri): Uri;
static toRevisionUri(ref: string, fileName: string, repoPath: string): Uri;
static toRevisionUri(ref: string, file: GitFile, repoPath: string): Uri;
static toRevisionUri(uriOrRef: string | GitUri, fileNameOrFile?: string | GitFile, repoPath?: string): Uri {
let fileName: string;
let ref: string | undefined;
let shortSha: string | undefined;
if (typeof uriOrRef === 'string') {
if (typeof fileNameOrFile === 'string') {
fileName = fileNameOrFile;
} else {
//if (fileNameOrFile!.status === 'D') {
fileName = GitUri.resolvePath(fileNameOrFile!.originalFileName ?? fileNameOrFile!.fileName, repoPath);
// } else {
// fileName = GitUri.resolve(fileNameOrFile!.fileName, repoPath);
}
ref = uriOrRef;
shortSha = GitRevision.shorten(ref);
} else {
fileName = uriOrRef.fsPath;
ref = uriOrRef.sha;
shortSha = uriOrRef.shortSha;
repoPath = uriOrRef.repoPath!;
}
if (ref == null || ref.length === 0) {
return Uri.file(fileName);
}
if (GitRevision.isUncommitted(ref)) {
return GitRevision.isUncommittedStaged(ref) ? GitUri.git(fileName, repoPath) : Uri.file(fileName);
}
let filePath = normalizePath(fileName);
if (filePath.charCodeAt(0) !== CharCode.Slash) {
filePath = `/${filePath}`;
}
const data: UriRevisionData = {
path: filePath,
ref: ref,
repoPath: normalizePath(repoPath!),
};
const uri = Uri.parse(
// Replace / in the authority with a similar unicode characters otherwise parsing will be wrong
`${DocumentSchemes.GitLens}://${encodeURIComponent(shortSha.replace(/\//g, '\u200A\u2215\u200A'))}${
// Change encoded / back to / otherwise uri parsing won't work properly
filePath === '/' ? '' : encodeURIComponent(filePath).replace(/%2F/g, '/')
}?${encodeURIComponent(JSON.stringify(data))}`,
);
return uri;
}
export function decodeGitLensRevisionUriAuthority<T>(authority: string): T {
return JSON.parse(decodeUtf8Hex(authority)) as T;
}
interface UriRevisionData {
path: string;
ref?: string;
repoPath: string;
export function encodeGitLensRevisionUriAuthority<T>(metadata: T): string {
return encodeUtf8Hex(JSON.stringify(metadata));
}

+ 7
- 3
src/git/models/commit.ts ファイルの表示

@ -126,7 +126,9 @@ export abstract class GitCommit implements GitRevisionReference {
@memoize()
get originalUri(): Uri {
return this.originalFileName ? GitUri.resolve(this.originalFileName, this.repoPath) : this.uri;
return this.originalFileName
? Container.instance.git.getAbsoluteUri(this.originalFileName, this.repoPath)
: this.uri;
}
get previousFileSha(): string {
@ -138,12 +140,14 @@ export abstract class GitCommit implements GitRevisionReference {
}
get previousUri(): Uri {
return this.previousFileName ? GitUri.resolve(this.previousFileName, this.repoPath) : this.uri;
return this.previousFileName
? Container.instance.git.getAbsoluteUri(this.previousFileName, this.repoPath)
: this.uri;
}
@memoize()
get uri(): Uri {
return GitUri.resolve(this.fileName, this.repoPath);
return Container.instance.git.getAbsoluteUri(this.fileName, this.repoPath);
}
@memoize()

+ 2
- 1
src/git/models/logCommit.ts ファイルの表示

@ -1,5 +1,6 @@
'use strict';
import { Uri } from 'vscode';
import { Container } from '../../container';
import { memoize, Strings } from '../../system';
import { GitUri } from '../gitUri';
import { GitReference } from '../models';
@ -90,7 +91,7 @@ export class GitLogCommit extends GitCommit {
}
get nextUri(): Uri {
return this.nextFileName ? GitUri.resolve(this.nextFileName, this.repoPath) : this.uri;
return this.nextFileName ? Container.instance.git.getAbsoluteUri(this.nextFileName, this.repoPath) : this.uri;
}
override get previousFileSha(): string {

+ 26
- 21
src/git/models/repository.ts ファイルの表示

@ -19,8 +19,14 @@ import { BuiltInGitCommands, BuiltInGitConfiguration, Starred, WorkspaceState }
import { Container } from '../../container';
import { Logger } from '../../logger';
import { Messages } from '../../messages';
import { Arrays, Dates, debug, Functions, gate, Iterables, log, logName, memoize } from '../../system';
import { basename, joinPaths, relative } from '../../system/path';
import { filterMap, groupByMap } from '../../system/array';
import { getFormatter } from '../../system/date';
import { gate } from '../../system/decorators/gate';
import { debug, log, logName } from '../../system/decorators/log';
import { memoize } from '../../system/decorators/memoize';
import { debounce } from '../../system/function';
import { filter, join, some } from '../../system/iterable';
import { basename } from '../../system/path';
import { runGitCommandInTerminal } from '../../terminal';
import { GitProviderDescriptor } from '../gitProvider';
import { GitUri } from '../gitUri';
@ -85,8 +91,8 @@ export class RepositoryChangeEvent {
toString(changesOnly: boolean = false): string {
return changesOnly
? `changes=${Iterables.join(this._changes, ', ')}`
: `{ repository: ${this.repository?.name ?? ''}, changes: ${Iterables.join(this._changes, ', ')} }`;
? `changes=${join(this._changes, ', ')}`
: `{ repository: ${this.repository?.name ?? ''}, changes: ${join(this._changes, ', ')} }`;
}
changed(...args: [...RepositoryChange[], RepositoryChangeComparisonMode]) {
@ -94,7 +100,7 @@ export class RepositoryChangeEvent {
const mode = args[args.length - 1] as RepositoryChangeComparisonMode;
if (mode === RepositoryChangeComparisonMode.Any) {
return Iterables.some(this._changes, c => affected.includes(c));
return some(this._changes, c => affected.includes(c));
}
let changes = this._changes;
@ -116,7 +122,7 @@ export class RepositoryChangeEvent {
}
}
const intersection = [...Iterables.filter(changes, c => affected.includes(c))];
const intersection = [...filter(changes, c => affected.includes(c))];
return mode === RepositoryChangeComparisonMode.Exclusive
? intersection.length === changes.size
: intersection.length === affected.length;
@ -135,7 +141,7 @@ export interface RepositoryFileSystemChangeEvent {
@logName<Repository>((r, name) => `${name}(${r.id})`)
export class Repository implements Disposable {
static formatLastFetched(lastFetched: number, short: boolean = true): string {
const formatter = Dates.getFormatter(new Date(lastFetched));
const formatter = getFormatter(new Date(lastFetched));
if (Date.now() - lastFetched < millisecondsPerDay) {
return formatter.fromNow();
}
@ -204,7 +210,7 @@ export class Repository implements Disposable {
suspended: boolean,
closed: boolean = false,
) {
const relativePath = relative(folder.uri.fsPath, path);
const relativePath = container.git.getRelativePath(folder.uri, path);
if (root) {
// Check if the repository is not contained by a workspace folder
const repoFolder = workspace.getWorkspaceFolder(GitUri.fromRepoPath(path));
@ -261,7 +267,7 @@ export class Repository implements Disposable {
@memoize()
get uri(): Uri {
return Uri.file(this.path);
return this.container.git.getAbsoluteUri(this.path);
}
get etag(): number {
@ -402,9 +408,7 @@ export class Repository implements Disposable {
if (remote) {
const trackingBranches = localBranches.filter(b => b.upstream != null);
if (trackingBranches.length !== 0) {
const branchesByOrigin = Arrays.groupByMap(trackingBranches, b =>
GitBranch.getRemote(b.upstream!.name),
);
const branchesByOrigin = groupByMap(trackingBranches, b => GitBranch.getRemote(b.upstream!.name));
for (const [remote, branches] of branchesByOrigin.entries()) {
this.runTerminalCommand(
@ -420,7 +424,7 @@ export class Repository implements Disposable {
const remoteBranches = branches.filter(b => b.remote);
if (remoteBranches.length !== 0) {
const branchesByOrigin = Arrays.groupByMap(remoteBranches, b => GitBranch.getRemote(b.name));
const branchesByOrigin = groupByMap(remoteBranches, b => GitBranch.getRemote(b.name));
for (const [remote, branches] of branchesByOrigin.entries()) {
this.runTerminalCommand(
@ -440,7 +444,7 @@ export class Repository implements Disposable {
containsUri(uri: Uri) {
if (GitUri.is(uri)) {
uri = uri.repoPath != null ? GitUri.file(uri.repoPath) : uri.documentUri();
uri = uri.repoPath != null ? this.container.git.getAbsoluteUri(uri.repoPath) : uri.documentUri();
}
return this.folder === workspace.getWorkspaceFolder(uri);
@ -531,10 +535,11 @@ export class Repository implements Disposable {
}
try {
const stat = await workspace.fs.stat(Uri.file(joinPaths(this.path, '.git/FETCH_HEAD')));
// TODO@eamodio: Need to move this into an explicit provider call
const stats = await workspace.fs.stat(this.container.git.getAbsoluteUri('.git/FETCH_HEAD', this.path));
// If the file is empty, assume the fetch failed, and don't update the timestamp
if (stat.size > 0) {
this._lastFetched = stat.mtime;
if (stats.size > 0) {
this._lastFetched = stats.mtime;
}
} catch {
this._lastFetched = undefined;
@ -581,7 +586,7 @@ export class Repository implements Disposable {
this._remotesDisposable = undefined;
this._remotesDisposable = Disposable.from(
...Iterables.filterMap(await remotes, r => {
...filterMap(await remotes, r => {
if (!RichRemoteProvider.is(r.provider)) return undefined;
return r.provider.onDidChange(() => this.fireChange(RepositoryChange.RemoteProviders));
@ -879,7 +884,7 @@ export class Repository implements Disposable {
}
toAbsoluteUri(path: string, options?: { validate?: boolean }): Uri | undefined {
const uri = Uri.joinPath(GitUri.file(this.path), path);
const uri = this.container.git.getAbsoluteUri(path, this.path);
return !(options?.validate ?? true) || this.containsUri(uri) ? uri : undefined;
}
@ -964,7 +969,7 @@ export class Repository implements Disposable {
this._updatedAt = Date.now();
if (this._fireChangeDebounced == null) {
this._fireChangeDebounced = Functions.debounce(this.fireChangeCore.bind(this), 250);
this._fireChangeDebounced = debounce(this.fireChangeCore.bind(this), 250);
}
this._pendingRepoChange = this._pendingRepoChange?.with(changes) ?? new RepositoryChangeEvent(this, changes);
@ -997,7 +1002,7 @@ export class Repository implements Disposable {
this._updatedAt = Date.now();
if (this._fireFileSystemChangeDebounced == null) {
this._fireFileSystemChangeDebounced = Functions.debounce(this.fireFileSystemChangeCore.bind(this), 2500);
this._fireFileSystemChangeDebounced = debounce(this.fireFileSystemChangeCore.bind(this), 2500);
}
if (this._pendingFileSystemChange == null) {

+ 8
- 8
src/git/models/status.ts ファイルの表示

@ -2,8 +2,8 @@
import { Uri } from 'vscode';
import { GlyphChars } from '../../constants';
import { Container } from '../../container';
import { memoize, Strings } from '../../system';
import { GitUri } from '../gitUri';
import { memoize } from '../../system/decorators/memoize';
import { pluralize } from '../../system/string';
import { GitCommitType, GitLogCommit, GitRemote, GitRevision, GitUser } from '../models';
import { GitBranch, GitTrackingState } from './branch';
import { GitFile, GitFileConflictStatus, GitFileIndexStatus, GitFileStatus, GitFileWorkingTreeStatus } from './file';
@ -205,13 +205,13 @@ export class GitStatus {
if (expand) {
let status = '';
if (added) {
status += `${Strings.pluralize('file', added)} added`;
status += `${pluralize('file', added)} added`;
}
if (changed) {
status += `${status.length === 0 ? '' : separator}${Strings.pluralize('file', changed)} changed`;
status += `${status.length === 0 ? '' : separator}${pluralize('file', changed)} changed`;
}
if (deleted) {
status += `${status.length === 0 ? '' : separator}${Strings.pluralize('file', deleted)} deleted`;
status += `${status.length === 0 ? '' : separator}${pluralize('file', deleted)} deleted`;
}
return `${prefix}${status}${suffix}`;
}
@ -282,12 +282,12 @@ export class GitStatus {
status = 'missing';
} else {
if (state.behind) {
status += `${Strings.pluralize('commit', state.behind, {
status += `${pluralize('commit', state.behind, {
infix: icons ? '$(arrow-down) ' : undefined,
})} behind`;
}
if (state.ahead) {
status += `${status.length === 0 ? '' : separator}${Strings.pluralize('commit', state.ahead, {
status += `${status.length === 0 ? '' : separator}${pluralize('commit', state.ahead, {
infix: icons ? '$(arrow-up) ' : undefined,
})} ahead`;
if (suffix.startsWith(` ${upstream.name.split('/')[0]}`)) {
@ -403,7 +403,7 @@ export class GitStatusFile implements GitFile {
@memoize()
get uri(): Uri {
return GitUri.resolve(this.fileName, this.repoPath);
return Container.instance.git.getAbsoluteUri(this.fileName, this.repoPath);
}
getFormattedDirectory(includeOriginal: boolean = false): string {

+ 9
- 1
src/system/path.ts ファイルの表示

@ -5,7 +5,7 @@ import { isLinux, isWindows } from '@env/platform';
// TODO@eamodio don't import from string here since it will break the tests because of ESM dependencies
// import { CharCode } from './string';
export { basename, dirname, extname, isAbsolute, join as joinPaths, relative } from 'path';
export { basename, dirname, extname, isAbsolute, join as joinPaths } from 'path';
const driveLetterNormalizeRegex = /(?<=^\/?)([A-Z])(?=:\/)/;
const pathNormalizeRegex = /\\/g;
@ -120,6 +120,14 @@ export function normalizePath(path: string): string {
return path;
}
export function relative(from: string, to: string, ignoreCase?: boolean): string {
from = normalizePath(from);
to = normalizePath(to);
const index = commonBaseIndex(`${to}/`, `${from}/`, '/', ignoreCase);
return index > 0 ? to.substring(index + 1) : to;
}
export function splitPath(
path: string,
repoPath: string | undefined,

+ 1
- 0
src/trackers/documentTracker.ts ファイルの表示

@ -67,6 +67,7 @@ export class DocumentTracker implements Disposable {
private _dirtyIdleTriggerDelay: number;
private readonly _disposable: Disposable;
// TODO@eamodio: replace with a trie?
private readonly _documentMap = new Map<TextDocument | string, Promise<TrackedDocument<T>>>();
constructor(protected readonly container: Container) {

+ 3
- 3
src/trackers/trackedDocument.ts ファイルの表示

@ -5,7 +5,7 @@ import { Container } from '../container';
import { GitUri } from '../git/gitUri';
import { GitRevision } from '../git/models';
import { Logger } from '../logger';
import { Functions } from '../system';
import { debounce, Deferrable } from '../system/function';
export interface DocumentBlameStateChangeEvent<T> {
readonly editor: TextEditor;
@ -115,7 +115,7 @@ export class TrackedDocument implements Disposable {
}
private _updateDebounced:
| Functions.Deferrable<({ forceBlameChange }?: { forceBlameChange?: boolean | undefined }) => Promise<void>>
| Deferrable<({ forceBlameChange }?: { forceBlameChange?: boolean | undefined }) => Promise<void>>
| undefined;
reset(reason: 'config' | 'document' | 'repository') {
@ -130,7 +130,7 @@ export class TrackedDocument implements Disposable {
if (reason === 'repository' && isActiveDocument(this.document)) {
if (this._updateDebounced == null) {
this._updateDebounced = Functions.debounce(this.update.bind(this), 250);
this._updateDebounced = debounce(this.update.bind(this), 250);
}
void this._updateDebounced();

+ 4
- 2
src/views/nodes/lineHistoryTrackerNode.ts ファイルの表示

@ -6,7 +6,9 @@ import { GitCommitish, GitUri } from '../../git/gitUri';
import { GitReference, GitRevision } from '../../git/models';
import { Logger } from '../../logger';
import { ReferencePicker } from '../../quickpicks';
import { debug, Functions, gate, log } from '../../system';
import { gate } from '../../system/decorators/gate';
import { debug, log } from '../../system/decorators/log';
import { debounce } from '../../system/function';
import { LinesChangeEvent } from '../../trackers/gitLineTracker';
import { FileHistoryView } from '../fileHistoryView';
import { LineHistoryView } from '../lineHistoryView';
@ -220,7 +222,7 @@ export class LineHistoryTrackerNode extends SubscribeableViewNode
protected subscribe() {
if (this.view.container.lineTracker.subscribed(this)) return undefined;
const onActiveLinesChanged = Functions.debounce(this.onActiveLinesChanged.bind(this), 250);
const onActiveLinesChanged = debounce(this.onActiveLinesChanged.bind(this), 250);
return this.view.container.lineTracker.subscribe(
this,

+ 1
- 1
src/views/nodes/mergeConflictCurrentChangesNode.ts ファイルの表示

@ -71,7 +71,7 @@ export class MergeConflictCurrentChangesNode extends ViewNode
return {
title: 'Open Revision',
command: BuiltInCommands.Open,
arguments: [GitUri.toRevisionUri('HEAD', this.file.fileName, this.status.repoPath)],
arguments: [this.view.container.git.getRevisionUri('HEAD', this.file.fileName, this.status.repoPath)],
};
}

+ 2
- 2
src/views/nodes/mergeConflictFileNode.ts ファイルの表示

@ -53,7 +53,7 @@ export class MergeConflictFileNode extends ViewNode implements
this.file,
);
// Use the file icon and decorations
item.resourceUri = GitUri.resolve(this.file.fileName, this.repoPath);
item.resourceUri = this.view.container.git.getAbsoluteUri(this.file.fileName, this.repoPath);
item.iconPath = ThemeIcon.File;
item.command = this.getCommand();
@ -115,7 +115,7 @@ export class MergeConflictFileNode extends ViewNode implements
title: 'Open File',
command: BuiltInCommands.Open,
arguments: [
GitUri.resolve(this.file.fileName, this.repoPath),
this.view.container.git.getAbsoluteUri(this.file.fileName, this.repoPath),
{
preserveFocus: true,
preview: true,

+ 7
- 1
src/views/nodes/mergeConflictIncomingChangesNode.ts ファイルの表示

@ -83,7 +83,13 @@ export class MergeConflictIncomingChangesNode extends ViewNode
return {
title: 'Open Revision',
command: BuiltInCommands.Open,
arguments: [GitUri.toRevisionUri(this.status.HEAD.ref, this.file.fileName, this.status.repoPath)],
arguments: [
this.view.container.git.getRevisionUri(
this.status.HEAD.ref,
this.file.fileName,
this.status.repoPath,
),
],
};
}

+ 2
- 2
src/views/nodes/statusFileNode.ts ファイルの表示

@ -87,7 +87,7 @@ export class StatusFileNode extends ViewNode implements FileNo
}
// Use the file icon and decorations
item.resourceUri = GitUri.resolve(this.file.fileName, this.repoPath);
item.resourceUri = this.view.container.git.getAbsoluteUri(this.file.fileName, this.repoPath);
item.iconPath = ThemeIcon.File;
item.command = this.getCommand();
@ -104,7 +104,7 @@ export class StatusFileNode extends ViewNode implements FileNo
}
// Use the file icon and decorations
item.resourceUri = GitUri.resolve(this.file.fileName, this.repoPath);
item.resourceUri = this.view.container.git.getAbsoluteUri(this.file.fileName, this.repoPath);
item.iconPath = ThemeIcon.File;
} else {
item.contextValue = ContextValues.StatusFileCommits;

+ 3
- 3
src/views/viewCommands.ts ファイルの表示

@ -1090,16 +1090,16 @@ export class ViewCommands {
let uri = options.revisionUri;
if (uri == null) {
if (node instanceof ResultsFileNode || node instanceof MergeConflictFileNode) {
uri = GitUri.toRevisionUri(node.uri);
uri = Container.instance.git.getRevisionUri(node.uri);
} else {
uri =
node.commit.status === 'D'
? GitUri.toRevisionUri(
? Container.instance.git.getRevisionUri(
node.commit.previousSha!,
node.commit.previousUri.fsPath,
node.commit.repoPath,
)
: GitUri.toRevisionUri(node.uri);
: Container.instance.git.getRevisionUri(node.uri);
}
}

+ 6
- 6
src/vsls/host.ts ファイルの表示

@ -3,9 +3,9 @@ import { CancellationToken, Disposable, Uri, workspace, WorkspaceFoldersChangeEv
import { git } from '@env/git';
import type { LiveShare, SharedService } from '../@types/vsls';
import { Container } from '../container';
import { GitUri } from '../git/gitUri';
import { Logger } from '../logger';
import { debug, Iterables, log } from '../system';
import { debug, log } from '../system/decorators/log';
import { filterMap, join } from '../system/iterable';
import { normalizePath } from '../system/path';
import {
GitCommandRequest,
@ -120,11 +120,11 @@ export class VslsHostService implements Disposable {
this._sharedToLocalPaths.set(sharedPath, localPath);
}
let localPaths = Iterables.join(this._sharedToLocalPaths.values(), '|');
let localPaths = join(this._sharedToLocalPaths.values(), '|');
localPaths = localPaths.replace(/(\/|\\)/g, '[\\\\/|\\\\]');
this._localPathsRegex = new RegExp(`(${localPaths})`, 'gi');
let sharedPaths = Iterables.join(this._localToSharedPaths.values(), '|');
let sharedPaths = join(this._localToSharedPaths.values(), '|');
sharedPaths = sharedPaths.replace(/(\/|\\)/g, '[\\\\/|\\\\]');
this._sharedPathsRegex = new RegExp(`^(${sharedPaths})`, 'i');
}
@ -155,7 +155,7 @@ export class VslsHostService implements Disposable {
const localCwd = this._sharedToLocalPaths.get('/~0');
if (localCwd !== undefined) {
isRootWorkspace = true;
options.cwd = GitUri.resolvePath(options.cwd, localCwd);
options.cwd = normalizePath(this.container.git.getAbsoluteUri(options.cwd, localCwd).fsPath);
}
}
}
@ -216,7 +216,7 @@ export class VslsHostService implements Disposable {
const normalized = normalizePath(uri.fsPath).toLowerCase();
const repos = [
...Iterables.filterMap(this.container.git.repositories, r => {
...filterMap(this.container.git.repositories, r => {
if (!r.normalizedPath.startsWith(normalized)) return undefined;
const vslsUri = this.convertLocalUriToShared(r.folder.uri);

読み込み中…
キャンセル
保存