mirror of
https://github.com/Microsoft/vscode
synced 2024-08-27 04:49:35 +00:00
Git - handle unsafe repositories (#167248)
This commit is contained in:
parent
227e4ca947
commit
3a52e79cea
|
@ -623,6 +623,16 @@
|
|||
"title": "%command.git.runGitMergeDiff3%",
|
||||
"category": "Git",
|
||||
"enablement": "isMergeEditor"
|
||||
},
|
||||
{
|
||||
"command": "git.addSafeDirectoryAndOpenRepository",
|
||||
"title": "%command.addSafeDirectoryAndOpenRepository%",
|
||||
"category": "Git"
|
||||
},
|
||||
{
|
||||
"command": "git.api.getUnsafeRepositories",
|
||||
"title": "%command.api.getUnsafeRepositories%",
|
||||
"category": "Git"
|
||||
}
|
||||
],
|
||||
"keybindings": [
|
||||
|
@ -1047,9 +1057,17 @@
|
|||
"command": "git.api.getRemoteSources",
|
||||
"when": "false"
|
||||
},
|
||||
{
|
||||
"command": "git.api.getUnsafeRepositories",
|
||||
"when": "false"
|
||||
},
|
||||
{
|
||||
"command": "git.openMergeEditor",
|
||||
"when": "false"
|
||||
},
|
||||
{
|
||||
"command": "git.addSafeDirectoryAndOpenRepository",
|
||||
"when": "config.git.enabled && !git.missing && git.unsafeRepositoryCount != 0"
|
||||
}
|
||||
],
|
||||
"scm/title": [
|
||||
|
@ -2697,30 +2715,48 @@
|
|||
{
|
||||
"view": "scm",
|
||||
"contents": "%view.workbench.scm.empty%",
|
||||
"when": "config.git.enabled && !git.missing && workbenchState == empty",
|
||||
"when": "config.git.enabled && !git.missing && workbenchState == empty && git.unsafeRepositoryCount == 0",
|
||||
"enablement": "git.state == initialized",
|
||||
"group": "2_open@1"
|
||||
},
|
||||
{
|
||||
"view": "scm",
|
||||
"contents": "%view.workbench.scm.folder%",
|
||||
"when": "config.git.enabled && !git.missing && workbenchState == folder && remoteName != 'codespaces'",
|
||||
"contents": "%view.workbench.scm.emptyWorkspace%",
|
||||
"when": "config.git.enabled && !git.missing && workbenchState == workspace && workspaceFolderCount == 0 && git.unsafeRepositoryCount == 0",
|
||||
"enablement": "git.state == initialized",
|
||||
"group": "2_open@1"
|
||||
},
|
||||
{
|
||||
"view": "scm",
|
||||
"contents": "%view.workbench.scm.scanFolderForRepositories%",
|
||||
"when": "config.git.enabled && !git.missing && workbenchState == folder && workspaceFolderCount != 0 && git.state != initialized"
|
||||
},
|
||||
{
|
||||
"view": "scm",
|
||||
"contents": "%view.workbench.scm.scanWorkspaceForRepositories%",
|
||||
"when": "config.git.enabled && !git.missing && workbenchState == workspace && workspaceFolderCount != 0 && git.state != initialized"
|
||||
},
|
||||
{
|
||||
"view": "scm",
|
||||
"contents": "%view.workbench.scm.folder%",
|
||||
"when": "config.git.enabled && !git.missing && git.state == initialized && workbenchState == folder && scmRepositoryCount == 0 && git.unsafeRepositoryCount == 0 && remoteName != 'codespaces'",
|
||||
"group": "5_scm@1"
|
||||
},
|
||||
{
|
||||
"view": "scm",
|
||||
"contents": "%view.workbench.scm.workspace%",
|
||||
"when": "config.git.enabled && !git.missing && workbenchState == workspace && workspaceFolderCount != 0 && remoteName != 'codespaces'",
|
||||
"enablement": "git.state == initialized",
|
||||
"when": "config.git.enabled && !git.missing && git.state == initialized && workbenchState == workspace && workspaceFolderCount != 0 && scmRepositoryCount == 0 && git.unsafeRepositoryCount == 0 && remoteName != 'codespaces'",
|
||||
"group": "5_scm@1"
|
||||
},
|
||||
{
|
||||
"view": "scm",
|
||||
"contents": "%view.workbench.scm.emptyWorkspace%",
|
||||
"when": "config.git.enabled && !git.missing && workbenchState == workspace && workspaceFolderCount == 0",
|
||||
"enablement": "git.state == initialized",
|
||||
"group": "2_open@1"
|
||||
"contents": "%view.workbench.scm.unsafeRepository%",
|
||||
"when": "config.git.enabled && !git.missing && git.state == initialized && git.unsafeRepositoryCount == 1"
|
||||
},
|
||||
{
|
||||
"view": "scm",
|
||||
"contents": "%view.workbench.scm.unsafeRepositories%",
|
||||
"when": "config.git.enabled && !git.missing && git.state == initialized && git.unsafeRepositoryCount > 1"
|
||||
},
|
||||
{
|
||||
"view": "explorer",
|
||||
|
|
|
@ -99,9 +99,11 @@
|
|||
"command.timelineCopyCommitMessage": "Copy Commit Message",
|
||||
"command.timelineSelectForCompare": "Select for Compare",
|
||||
"command.timelineCompareWithSelected": "Compare with Selected",
|
||||
"command.addSafeDirectoryAndOpenRepository": "Mark Repository as Safe and Open",
|
||||
"command.api.getRepositories": "Get Repositories",
|
||||
"command.api.getRepositoryState": "Get Repository State",
|
||||
"command.api.getRemoteSources": "Get Remote Sources",
|
||||
"command.api.getUnsafeRepositories": "Get Unsafe Repositories",
|
||||
"command.git.acceptMerge": "Complete Merge",
|
||||
"command.git.openMergeEditor": "Resolve in Merge Editor",
|
||||
"command.git.runGitMerge": "Compute Conflicts With Git",
|
||||
|
@ -322,6 +324,30 @@
|
|||
"Please make sure there is no space between the right bracket and left parenthesis: ]( this is an internal syntax for links"
|
||||
]
|
||||
},
|
||||
"view.workbench.scm.scanFolderForRepositories": {
|
||||
"message": "Scanning folder for git repositories..."
|
||||
},
|
||||
"view.workbench.scm.scanWorkspaceForRepositories": {
|
||||
"message": "Scanning workspace for git repositories..."
|
||||
},
|
||||
"view.workbench.scm.unsafeRepository": {
|
||||
"message": "The git repository in the following folder has been detected as potentially unsafe as the folder is owned by someone else other than the current user: ${command:git.api.getUnsafeRepositories}.\nDo you want to open the repository?\n[Open Repository](command:git.addSafeDirectoryAndOpenRepository)\n[Learn More](https://aka.ms/vscode-scm)",
|
||||
"comment": [
|
||||
"{Locked='](command:git.api.getUnsafeRepositories'}",
|
||||
"{Locked='](command:git.addSafeDirectoryAndOpenRepository'}",
|
||||
"Do not translate the 'command:*' part inside of the '(..)'. It is an internal command syntax for VS Code",
|
||||
"Please make sure there is no space between the right bracket and left parenthesis: ]( this is an internal syntax for links"
|
||||
]
|
||||
},
|
||||
"view.workbench.scm.unsafeRepositories": {
|
||||
"message": "The git repositories in the following folders have been detected as potentially unsafe as the folder is owned by someone else other than the current user: ${command:git.api.getUnsafeRepositories}.\nDo you want to open the repositories?\n[Open Repositories](command:git.addSafeDirectoryAndOpenRepository)\n[Learn More](https://aka.ms/vscode-scm)",
|
||||
"comment": [
|
||||
"{Locked='](command:git.api.getUnsafeRepositories'}",
|
||||
"{Locked='](command:git.addSafeDirectoryAndOpenRepository'}",
|
||||
"Do not translate the 'command:*' part inside of the '(..)'. It is an internal command syntax for VS Code",
|
||||
"Please make sure there is no space between the right bracket and left parenthesis: ]( this is an internal syntax for links"
|
||||
]
|
||||
},
|
||||
"view.workbench.cloneRepository": {
|
||||
"message": "You can clone a repository locally.\n[Clone Repository](command:git.clone 'Clone a repository once the git extension has activated')",
|
||||
"comment": [
|
||||
|
|
|
@ -3182,6 +3182,17 @@ export class CommandCenter {
|
|||
repository.closeDiffEditors(undefined, undefined, true);
|
||||
}
|
||||
|
||||
@command('git.api.getUnsafeRepositories')
|
||||
getUnsafeRepositories(): string {
|
||||
const repositories = Array.from(this.model.unsafeRepositories.values());
|
||||
return repositories.sort().map(m => `"${m}"`).join(', ');
|
||||
}
|
||||
|
||||
@command('git.addSafeDirectoryAndOpenRepository')
|
||||
async addSafeDirectoryAndOpenRepository(): Promise<void> {
|
||||
await this.model.addSafeDirectoryAndOpenRepository();
|
||||
}
|
||||
|
||||
private createCommand(id: string, key: string, method: Function, options: ScmCommandOptions): (...args: any[]) => any {
|
||||
const result = (...args: any[]) => {
|
||||
let result: Promise<any>;
|
||||
|
|
|
@ -684,6 +684,11 @@ export class Git {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
async addSafeDirectory(repositoryPath: string): Promise<void> {
|
||||
await this.exec(repositoryPath, ['config', '--global', '--add', 'safe.directory', repositoryPath]);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
export interface Commit {
|
||||
|
|
|
@ -3,7 +3,7 @@
|
|||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { workspace, WorkspaceFoldersChangeEvent, Uri, window, Event, EventEmitter, QuickPickItem, Disposable, SourceControl, SourceControlResourceGroup, TextEditor, Memento, commands, LogOutputChannel, l10n, ProgressLocation } from 'vscode';
|
||||
import { workspace, WorkspaceFoldersChangeEvent, Uri, window, Event, EventEmitter, QuickPickItem, Disposable, SourceControl, SourceControlResourceGroup, TextEditor, Memento, commands, LogOutputChannel, l10n, ProgressLocation, QuickPickItemKind } from 'vscode';
|
||||
import TelemetryReporter from '@vscode/extension-telemetry';
|
||||
import { Operation, Repository, RepositoryState } from './repository';
|
||||
import { memoize, sequentialize, debounce } from './decorators';
|
||||
|
@ -33,6 +33,31 @@ class RepositoryPick implements QuickPickItem {
|
|||
constructor(public readonly repository: Repository, public readonly index: number) { }
|
||||
}
|
||||
|
||||
class UnsafeRepositorySet extends Set<string> {
|
||||
constructor() {
|
||||
super();
|
||||
this.updateContextKey();
|
||||
}
|
||||
|
||||
override add(value: string): this {
|
||||
const result = super.add(value);
|
||||
this.updateContextKey();
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
override delete(value: string): boolean {
|
||||
const result = super.delete(value);
|
||||
this.updateContextKey();
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
private updateContextKey(): void {
|
||||
commands.executeCommand('setContext', 'git.unsafeRepositoryCount', this.size);
|
||||
}
|
||||
}
|
||||
|
||||
export interface ModelChangeEvent {
|
||||
repository: Repository;
|
||||
uri: Uri;
|
||||
|
@ -110,6 +135,11 @@ export class Model implements IRemoteSourcePublisherRegistry, IPostCommitCommand
|
|||
private showRepoOnHomeDriveRootWarning = true;
|
||||
private pushErrorHandlers = new Set<PushErrorHandler>();
|
||||
|
||||
private _unsafeRepositories = new UnsafeRepositorySet();
|
||||
get unsafeRepositories(): Set<string> {
|
||||
return this._unsafeRepositories;
|
||||
}
|
||||
|
||||
private disposables: Disposable[] = [];
|
||||
|
||||
constructor(readonly git: Git, private readonly askpass: Askpass, private globalState: Memento, private logger: LogOutputChannel, private telemetryReporter: TelemetryReporter) {
|
||||
|
@ -145,6 +175,11 @@ export class Model implements IRemoteSourcePublisherRegistry, IPostCommitCommand
|
|||
await initialScanFn();
|
||||
}
|
||||
|
||||
// Show unsafe repositories notification if we cannot use a welcome view
|
||||
if (this.repositories.length > 0 && this._unsafeRepositories.size > 0) {
|
||||
await this.showUnsafeRepositoryNotification();
|
||||
}
|
||||
|
||||
/* __GDPR__
|
||||
"git.repositoryInitialScan" : {
|
||||
"owner": "lszomoru",
|
||||
|
@ -394,6 +429,23 @@ export class Model implements IRemoteSourcePublisherRegistry, IPostCommitCommand
|
|||
this.open(repository);
|
||||
repository.status(); // do not await this, we want SCM to know about the repo asap
|
||||
} catch (ex) {
|
||||
// Handle unsafe repository
|
||||
const match = /^fatal: detected dubious ownership in repository at \'([^']+)\'$/m.exec(ex.stderr);
|
||||
if (match && match.length === 2) {
|
||||
const unsafeRepositoryPath = match[1];
|
||||
this.logger.trace(`Unsafe repository: ${unsafeRepositoryPath}`);
|
||||
|
||||
// If the unsafe repository is opened after the initial repository scan, and we cannot use the welcome view
|
||||
// as there is already at least one opened repository, we will be showing a notification for the repository.
|
||||
if (this._state === 'initialized' && this.openRepositories.length > 0 && !this._unsafeRepositories.has(unsafeRepositoryPath)) {
|
||||
this.showUnsafeRepositoryNotification(unsafeRepositoryPath);
|
||||
}
|
||||
|
||||
this._unsafeRepositories.add(unsafeRepositoryPath);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
// noop
|
||||
this.logger.trace(`Opening repository for path='${repoPath}' failed; ex=${ex}`);
|
||||
}
|
||||
|
@ -675,6 +727,75 @@ export class Model implements IRemoteSourcePublisherRegistry, IPostCommitCommand
|
|||
return [...this.pushErrorHandlers];
|
||||
}
|
||||
|
||||
async addSafeDirectoryAndOpenRepository() {
|
||||
const unsafeRepositories: string[] = [];
|
||||
|
||||
if (this._unsafeRepositories.size === 1) {
|
||||
// One unsafe repository
|
||||
unsafeRepositories.push(this._unsafeRepositories.values().next().value);
|
||||
} else {
|
||||
// Multiple unsafe repositories
|
||||
const allRepositoriesLabel = l10n.t('All Repositories');
|
||||
const allRepositoriesQuickPickItem: QuickPickItem = { label: allRepositoriesLabel };
|
||||
const repositoriesQuickPickItems: QuickPickItem[] = Array.from(this._unsafeRepositories.values()).sort().map(r => ({ label: `$(repo) ${r}` }));
|
||||
|
||||
const quickpick = window.createQuickPick();
|
||||
quickpick.title = l10n.t('Mark Repository as Safe and Open');
|
||||
quickpick.placeholder = l10n.t('Pick a repository to mark as safe and open');
|
||||
quickpick.items = [...repositoriesQuickPickItems, { label: '', kind: QuickPickItemKind.Separator }, allRepositoriesQuickPickItem];
|
||||
|
||||
quickpick.show();
|
||||
const repositoryItem = await new Promise<string | undefined>(
|
||||
resolve => {
|
||||
quickpick.onDidAccept(() => resolve(quickpick.activeItems[0].label));
|
||||
quickpick.onDidHide(() => resolve(undefined));
|
||||
});
|
||||
quickpick.hide();
|
||||
|
||||
if (!repositoryItem) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (repositoryItem === allRepositoriesLabel) {
|
||||
// All Repositories
|
||||
unsafeRepositories.push(...this._unsafeRepositories.values());
|
||||
} else {
|
||||
// One Repository
|
||||
unsafeRepositories.push(repositoryItem);
|
||||
}
|
||||
}
|
||||
|
||||
for (const unsafeRepository of unsafeRepositories) {
|
||||
// Mark as Safe
|
||||
await this.git.addSafeDirectory(unsafeRepository);
|
||||
|
||||
// Open Repository
|
||||
await this.openRepository(unsafeRepository);
|
||||
this._unsafeRepositories.delete(unsafeRepository);
|
||||
}
|
||||
}
|
||||
|
||||
private async showUnsafeRepositoryNotification(path?: string): Promise<void> {
|
||||
const unsafeRepositoryPaths: string[] = path ? [path] : Array.from(this._unsafeRepositories.values());
|
||||
const unsafeRepositoryPathLabels = unsafeRepositoryPaths.sort().map(m => `"${m}"`).join(', ');
|
||||
|
||||
const message = unsafeRepositoryPaths.length === 1 ?
|
||||
l10n.t('The git repository in the following folder has been detected as potentially unsafe as the folder is owned by someone else other than the current user: {0}. Do you want to open the repository?', unsafeRepositoryPathLabels) :
|
||||
l10n.t('The git repositories in the following folders have been detected as potentially unsafe as the folder is owned by someone else other than the current user: {0}. Do you want to open the repositories?', unsafeRepositoryPathLabels);
|
||||
|
||||
const openRepository = unsafeRepositoryPaths.length === 1 ? l10n.t('Open Repository') : l10n.t('Open Repositories');
|
||||
const learnMore = l10n.t('Learn More');
|
||||
|
||||
const choice = await window.showErrorMessage(message, openRepository, learnMore);
|
||||
if (choice === openRepository) {
|
||||
// Open Repository
|
||||
await this.addSafeDirectoryAndOpenRepository();
|
||||
} else if (choice === learnMore) {
|
||||
// Learn More
|
||||
commands.executeCommand('vscode.open', Uri.parse('https://aka.ms/vscode-scm'));
|
||||
}
|
||||
}
|
||||
|
||||
dispose(): void {
|
||||
const openRepositories = [...this.openRepositories];
|
||||
openRepositories.forEach(r => r.dispose());
|
||||
|
|
|
@ -118,12 +118,12 @@
|
|||
{
|
||||
"view": "scm",
|
||||
"contents": "%welcome.publishFolder%",
|
||||
"when": "config.git.enabled && git.state == initialized && workbenchState == folder"
|
||||
"when": "config.git.enabled && git.state == initialized && workbenchState == folder && git.unsafeRepositoryCount == 0"
|
||||
},
|
||||
{
|
||||
"view": "scm",
|
||||
"contents": "%welcome.publishWorkspaceFolder%",
|
||||
"when": "config.git.enabled && git.state == initialized && workbenchState == workspace && workspaceFolderCount != 0"
|
||||
"when": "config.git.enabled && git.state == initialized && workbenchState == workspace && workspaceFolderCount != 0 && git.unsafeRepositoryCount == 0"
|
||||
}
|
||||
],
|
||||
"markdown.previewStyles": [
|
||||
|
|
Loading…
Reference in a new issue