Skip to content

Commit 3b9f282

Browse files
lszomorujoaomoreno
andauthored
Git - better handle symbolic links (microsoft#186716)
Co-authored-by: João Moreno <[email protected]>
1 parent 32af6e7 commit 3b9f282

File tree

2 files changed

+25
-12
lines changed

2 files changed

+25
-12
lines changed

extensions/git/src/git.ts

Lines changed: 18 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ import * as which from 'which';
1212
import { EventEmitter } from 'events';
1313
import * as iconv from '@vscode/iconv-lite-umd';
1414
import * as filetype from 'file-type';
15-
import { assign, groupBy, IDisposable, toDisposable, dispose, mkdirp, readBytes, detectUnicodeEncoding, Encoding, onceEvent, splitInChunks, Limiter, Versions, isWindows, pathEquals, isMacintosh } from './util';
15+
import { assign, groupBy, IDisposable, toDisposable, dispose, mkdirp, readBytes, detectUnicodeEncoding, Encoding, onceEvent, splitInChunks, Limiter, Versions, isWindows, pathEquals, isMacintosh, isDescendant } from './util';
1616
import { CancellationError, CancellationToken, ConfigurationChangeEvent, LogOutputChannel, Progress, Uri, workspace } from 'vscode';
1717
import { detectEncoding } from './encoding';
1818
import { Ref, RefType, Branch, Remote, ForcePushMode, GitErrorCodes, LogOptions, Change, Status, CommitOptions, RefQuery, InitOptions } from './api/git';
@@ -477,18 +477,18 @@ export class Git {
477477
return folderPath;
478478
}
479479

480-
async getRepositoryRoot(repositoryPath: string): Promise<string> {
481-
const result = await this.exec(repositoryPath, ['rev-parse', '--show-toplevel']);
480+
async getRepositoryRoot(pathInsidePossibleRepository: string): Promise<string> {
481+
const result = await this.exec(pathInsidePossibleRepository, ['rev-parse', '--show-toplevel']);
482482

483483
// Keep trailing spaces which are part of the directory name
484-
const repoPath = path.normalize(result.stdout.trimLeft().replace(/[\r\n]+$/, ''));
484+
const repositoryRootPath = path.normalize(result.stdout.trimLeft().replace(/[\r\n]+$/, ''));
485485

486486
if (isWindows) {
487487
// On Git 2.25+ if you call `rev-parse --show-toplevel` on a mapped drive, instead of getting the mapped
488488
// drive path back, you get the UNC path for the mapped drive. So we will try to normalize it back to the
489489
// mapped drive path, if possible
490-
const repoUri = Uri.file(repoPath);
491-
const pathUri = Uri.file(repositoryPath);
490+
const repoUri = Uri.file(repositoryRootPath);
491+
const pathUri = Uri.file(pathInsidePossibleRepository);
492492
if (repoUri.authority.length !== 0 && pathUri.authority.length === 0) {
493493
// eslint-disable-next-line local/code-no-look-behind-regex
494494
const match = /(?<=^\/?)([a-zA-Z])(?=:\/)/.exec(pathUri.path);
@@ -520,7 +520,18 @@ export class Git {
520520
}
521521
}
522522

523-
return repoPath;
523+
// Handle symbolic links
524+
// Git 2.31 added the `--path-format` flag to rev-parse which
525+
// allows us to get the relative path of the repository root
526+
if (!pathEquals(pathInsidePossibleRepository, repositoryRootPath) &&
527+
!isDescendant(repositoryRootPath, pathInsidePossibleRepository) &&
528+
!isDescendant(pathInsidePossibleRepository, repositoryRootPath) &&
529+
this.compareGitVersionTo('2.31.0') !== -1) {
530+
const relativePathResult = await this.exec(pathInsidePossibleRepository, ['rev-parse', '--path-format=relative', '--show-toplevel',]);
531+
return path.resolve(pathInsidePossibleRepository, relativePathResult.stdout.trimLeft().replace(/[\r\n]+$/, ''));
532+
}
533+
534+
return repositoryRootPath;
524535
}
525536

526537
async getRepositoryDotGit(repositoryPath: string): Promise<{ path: string; commonPath?: string }> {

extensions/git/src/model.ts

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -931,12 +931,14 @@ export class Model implements IBranchProtectionProviderRegistry, IRemoteSourcePu
931931
return true;
932932
}
933933

934-
const result = await Promise.all(workspaceFolders.map(async folder => {
935-
const workspaceFolderRealPath = await this.getWorkspaceFolderRealPath(folder);
936-
return workspaceFolderRealPath ? pathEquals(workspaceFolderRealPath, repositoryPath) || isDescendant(workspaceFolderRealPath, repositoryPath) : undefined;
937-
}));
934+
// The repository path may be a canonical path or it may contain a symbolic link so we have
935+
// to match it against the workspace folders and the canonical paths of the workspace folders
936+
const workspaceFolderPaths = new Set<string | undefined>([
937+
...workspaceFolders.map(folder => folder.uri.fsPath),
938+
...await Promise.all(workspaceFolders.map(folder => this.getWorkspaceFolderRealPath(folder)))
939+
]);
938940

939-
return !result.some(r => r);
941+
return !Array.from(workspaceFolderPaths).some(folder => folder && (pathEquals(folder, repositoryPath) || isDescendant(folder, repositoryPath)));
940942
}
941943

942944
private async getWorkspaceFolderRealPath(workspaceFolder: WorkspaceFolder): Promise<string | undefined> {

0 commit comments

Comments
 (0)