Ver código fonte

Improves workspace creation experience

- Always creates a new worktree from the "main" repo
 - Automatically shows the worktree view after creation
 - Adds `worktrees.openAfterCreate` setting
main
Eric Amodio 1 ano atrás
pai
commit
94c4963e2f
8 arquivos alterados com 157 adições e 17 exclusões
  1. +4
    -0
      CHANGELOG.md
  2. +21
    -0
      package.json
  3. +69
    -5
      src/commands/git/worktree.ts
  4. +30
    -4
      src/commands/gitCommands.actions.ts
  5. +1
    -0
      src/config.ts
  6. +6
    -2
      src/env/node/git/localGitProvider.ts
  7. +8
    -3
      src/git/gitProviderService.ts
  8. +18
    -3
      src/git/models/repository.ts

+ 4
- 0
CHANGELOG.md Ver arquivo

@ -18,6 +18,10 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/) and this p
- Improves tooltips in the graph:
- Author and avatar tooltips now also show the contributor's email address, if available
- Date tooltips now always show both the absolute date and relative date
- Improves the Worktree creation experience
- Adds a `worktrees.openAfterCreate` setting to specify how and when to open a worktree after it is created
- Creates new worktrees from the "main" repo, if already in a worktree
- Shows the _Worktrees_ view after creating a new worktree
### Changed

+ 21
- 0
package.json Ver arquivo

@ -1665,6 +1665,27 @@
"scope": "resource",
"order": 11
},
"gitlens.worktrees.openAfterCreate": {
"type": "string",
"default": "prompt",
"enum": [
"always",
"alwaysNewWindow",
"onlyWhenEmpty",
"never",
"prompt"
],
"enumDescriptions": [
"Always open the new worktree in the current window",
"Always open the new worktree in a new window",
"Only open the new worktree in the current window when no folder is opened",
"Never open the new worktree",
"Always prompt to open the new worktree"
],
"markdownDescription": "Specifies how and when to open a worktree after it is created",
"scope": "resource",
"order": 12
},
"gitlens.views.worktrees.showBranchComparison": {
"type": [
"boolean",

+ 69
- 5
src/commands/git/worktree.ts Ver arquivo

@ -1,5 +1,6 @@
import type { MessageItem } from 'vscode';
import { QuickInputButtons, Uri, window } from 'vscode';
import type { Config } from '../../configuration';
import { configuration } from '../../configuration';
import type { Container } from '../../container';
import { PlusFeatures } from '../../features';
@ -206,6 +207,9 @@ export class WorktreeGitCommand extends QuickCommand {
}
}
// Ensure we use the "main" repository if we are in a worktree already
state.repo = await state.repo.getMainRepository();
const result = yield* ensureAccessStep(state as any, context, PlusFeatures.Worktrees);
if (result === StepResult.Break) break;
@ -365,12 +369,62 @@ export class WorktreeGitCommand extends QuickCommand {
);
try {
await state.repo.createWorktree(uri, {
const worktree = await state.repo.createWorktree(uri, {
commitish: state.reference?.name,
createBranch: state.flags.includes('-b') ? state.createBranch : undefined,
detach: state.flags.includes('--detach'),
force: state.flags.includes('--force'),
});
void GitActions.Worktree.reveal(worktree, {
select: true,
focus: true,
});
if (worktree == null) return;
type OpenAction = Config['worktrees']['openAfterCreate'] | 'addToWorkspace';
let action: OpenAction = configuration.get('worktrees.openAfterCreate');
if (action === 'never') return;
queueMicrotask(async () => {
if (action === 'prompt') {
type ActionMessageItem = MessageItem & { action: OpenAction };
const open: ActionMessageItem = { title: 'Open', action: 'always' };
const openNewWindow: ActionMessageItem = {
title: 'Open in New Window',
action: 'alwaysNewWindow',
};
const addToWorkspace: ActionMessageItem = {
title: 'Add to Workspace',
action: 'addToWorkspace',
};
const cancel: ActionMessageItem = { title: 'Cancel', isCloseAffordance: true, action: 'never' };
const result = await window.showInformationMessage(
`Would you like to open the new worktree, or add it to the current workspace?`,
{ modal: true },
open,
openNewWindow,
addToWorkspace,
cancel,
);
action = result?.action ?? 'never';
}
switch (action) {
case 'always':
GitActions.Worktree.open(worktree, { location: OpenWorkspaceLocation.CurrentWindow });
break;
case 'alwaysNewWindow':
GitActions.Worktree.open(worktree, { location: OpenWorkspaceLocation.NewWindow });
break;
case 'addToWorkspace':
GitActions.Worktree.open(worktree, { location: OpenWorkspaceLocation.AddToWorkspace });
break;
}
});
} catch (ex) {
if (
WorktreeCreateError.is(ex, WorktreeCreateErrorReason.AlreadyCheckedOut) &&
@ -505,6 +559,8 @@ export class WorktreeGitCommand extends QuickCommand {
60,
);
const isRemoteBranch = state.reference?.refType === 'branch' && state.reference?.remote;
const step: QuickPickStep<FlagsQuickPickItem<CreateFlags, Uri>> = QuickCommand.createConfirmStep(
appendReposToTitle(`Confirm ${context.title}`, state, context),
[
@ -512,7 +568,7 @@ export class WorktreeGitCommand extends QuickCommand {
state.flags,
[],
{
label: context.title,
label: isRemoteBranch ?n> 'Create Local Branch and Worktree' : context.title,
description: ` for ${GitReference.toString(state.reference)}`,
detail: `Will create worktree in $(folder) ${recommendedFriendlyPath}`,
},
@ -522,7 +578,9 @@ export class WorktreeGitCommand extends QuickCommand {
state.flags,
['-b'],
{
label: 'Create New Branch and Worktree',
label: isRemoteBranch
? 'Create New Local Branch and Worktree'
: 'Create New Branch and Worktree',
description: ` from ${GitReference.toString(state.reference)}`,
detail: `Will create worktree in $(folder) ${recommendedNewBranchFriendlyPath}`,
},
@ -535,7 +593,9 @@ export class WorktreeGitCommand extends QuickCommand {
state.flags,
['--direct'],
{
label: `${context.title} (directly in folder)`,
label: `${
isRemoteBranch ? 'Create Local Branch and Worktree' : context.title
} (directly in folder)`,
description: ` for ${GitReference.toString(state.reference)}`,
detail: `Will create worktree directly in $(folder) ${pickedFriendlyPath}`,
},
@ -545,7 +605,11 @@ export class WorktreeGitCommand extends QuickCommand {
state.flags,
['-b', '--direct'],
{
label: 'Create New Branch and Worktree (directly in folder)',
label: `${
isRemoteBranch
? 'Create New Local Branch and Worktree'
: 'Create New Branch and Worktree'
} (directly in folder)`,
description: ` from ${GitReference.toString(state.reference)}`,
detail: `Will create worktree directly in $(folder) ${pickedFriendlyPath}`,
},

+ 30
- 4
src/commands/gitCommands.actions.ts Ver arquivo

@ -169,6 +169,10 @@ export namespace GitActions {
const node = view.canReveal
? await view.revealBranch(branch, options)
: await Container.instance.repositoriesView.revealBranch(branch, options);
if (node == null) {
void view.show({ preserveFocus: !options?.focus });
}
return node;
}
}
@ -745,6 +749,7 @@ export namespace GitActions {
if (node != null) return node;
}
void views[0].show({ preserveFocus: !options?.focus });
return undefined;
}
@ -809,6 +814,9 @@ export namespace GitActions {
const node = view.canReveal
? await view.revealContributor(contributor, options)
: await Container.instance.repositoriesView.revealContributor(contributor, options);
if (node == null) {
void view.show({ preserveFocus: !options?.focus });
}
return node;
}
}
@ -875,6 +883,9 @@ export namespace GitActions {
const node = view.canReveal
? await view.revealRemote(remote, options)
: await Container.instance.repositoriesView.revealRemote(remote, options);
if (node == null) {
void view.show({ preserveFocus: !options?.focus });
}
return node;
}
}
@ -892,6 +903,9 @@ export namespace GitActions {
const node = view?.canReveal
? await view.revealRepository(repoPath, options)
: await Container.instance.repositoriesView.revealRepository(repoPath, options);
if (node == null) {
void (view ?? Container.instance.repositoriesView).show({ preserveFocus: !options?.focus });
}
return node;
}
}
@ -943,6 +957,9 @@ export namespace GitActions {
const node = view.canReveal
? await view.revealStash(stash, options)
: await Container.instance.repositoriesView.revealStash(stash, options);
if (node == null) {
void view.show({ preserveFocus: !options?.focus });
}
return node;
}
@ -990,6 +1007,9 @@ export namespace GitActions {
const node = view.canReveal
? await view.revealTag(tag, options)
: await Container.instance.repositoriesView.revealTag(tag, options);
if (node == null) {
void view.show({ preserveFocus: !options?.focus });
}
return node;
}
}
@ -1014,13 +1034,19 @@ export namespace GitActions {
}
export async function reveal(
worktree: GitWorktree,
worktree: GitWorktree | undefined,
options?: { select?: boolean; focus?: boolean; expand?: boolean | number },
) {
const view = Container.instance.worktreesView;
const node = view.canReveal
? await view.revealWorktree(worktree, options)
: await Container.instance.repositoriesView.revealWorktree(worktree, options);
const node =
worktree != null
? view.canReveal
? await view.revealWorktree(worktree, options)
: await Container.instance.repositoriesView.revealWorktree(worktree, options)
: undefined;
if (node == null) {
void view.show({ preserveFocus: !options?.focus });
}
return node;
}

+ 1
- 0
src/config.ts Ver arquivo

@ -173,6 +173,7 @@ export interface Config {
};
worktrees: {
defaultLocation: string | null;
openAfterCreate: 'always' | 'alwaysNewWindow' | 'onlyWhenEmpty' | 'never' | 'prompt';
promptForLocation: boolean;
};
advanced: AdvancedConfig;

+ 6
- 2
src/env/node/git/localGitProvider.ts Ver arquivo

@ -4084,7 +4084,9 @@ export class LocalGitProvider implements GitProvider, Disposable {
// If we didn't find it, check it as close to the file as possible (will find nested repos)
tracked = Boolean(await this.git.ls_files(newRepoPath, newRelativePath));
if (tracked) {
repository = await this.container.git.getOrOpenRepository(Uri.file(path), true);
repository = await this.container.git.getOrOpenRepository(Uri.file(path), {
detectNested: true,
});
if (repository != null) {
return splitPath(path, repository.path);
}
@ -4109,7 +4111,9 @@ export class LocalGitProvider implements GitProvider, Disposable {
const index = relativePath.indexOf('/');
if (index < 0 || index === relativePath.length - 1) return undefined;
const nested = await this.container.git.getOrOpenRepository(Uri.file(path), true);
const nested = await this.container.git.getOrOpenRepository(Uri.file(path), {
detectNested: true,
});
if (nested != null && nested !== repository) {
[relativePath, repoPath] = splitPath(path, repository.path);
repository = undefined;

+ 8
- 3
src/git/gitProviderService.ts Ver arquivo

@ -2003,7 +2003,10 @@ export class GitProviderService implements Disposable {
}
@log<GitProviderService['getOrOpenRepository']>({ exit: r => `returned ${r?.path}` })
async getOrOpenRepository(uri: Uri, detectNested?: boolean): Promise<Repository | undefined> {
async getOrOpenRepository(
uri: Uri,
options?: { closeOnOpen?: boolean; detectNested?: boolean },
): Promise<Repository | undefined> {
const scope = getLogScope();
const path = getBestPath(uri);
@ -2012,7 +2015,7 @@ export class GitProviderService implements Disposable {
let isDirectory: boolean | undefined;
detectNested = detectNested ?? configuration.get('detectNestedRepositories', uri);
const detectNested = options?.detectNested ?? configuration.get('detectNestedRepositories', uri);
if (!detectNested) {
if (repository != null) return repository;
} else if (this._visitedPaths.has(path)) {
@ -2053,7 +2056,9 @@ export class GitProviderService implements Disposable {
CoreGitConfiguration.AutoRepositoryDetection,
) ?? true;
const closed = autoRepositoryDetection !== true && autoRepositoryDetection !== 'openEditors';
const closed =
options?.closeOnOpen ??
(autoRepositoryDetection !== true && autoRepositoryDetection !== 'openEditors');
Logger.log(scope, `Repository found in '${repoUri.toString(true)}'`);
const repositories = provider.openRepository(root?.folder, repoUri, false, undefined, closed);

+ 18
- 3
src/git/models/repository.ts Ver arquivo

@ -624,6 +624,15 @@ export class Repository implements Disposable {
return this._lastFetched ?? 0;
}
@gate()
async getMainRepository(): Promise<Repository | undefined> {
const gitDir = await this.getGitDir();
if (gitDir?.commonUri == null) return this;
// If the repository isn't already opened, then open it as a "closed" repo (won't show up in the UI)
return this.container.git.getOrOpenRepository(gitDir.commonUri, { closeOnOpen: true });
}
getMergeStatus(): Promise<GitMergeStatus | undefined> {
return this.container.git.getMergeStatus(this.path);
}
@ -689,11 +698,13 @@ export class Repository implements Disposable {
return this.container.git.getTags(this.path, options);
}
createWorktree(
async createWorktree(
uri: Uri,
options?: { commitish?: string; createBranch?: string; detach?: boolean; force?: boolean },
): Promise<void> {
return this.container.git.createWorktree(this.path, uri.fsPath, options);
): Promise<GitWorktree | undefined> {
await this.container.git.createWorktree(this.path, uri.fsPath, options);
const url = uri.toString();
return this.container.git.getWorktree(this.path, w => w.uri.toString() === url);
}
getWorktrees(): Promise<GitWorktree[]> {
@ -723,6 +734,10 @@ export class Repository implements Disposable {
return branch?.upstream != null;
}
async isWorktree(): Promise<boolean> {
return (await this.getGitDir())?.commonUri != null;
}
@log()
merge(...args: string[]) {
this.runTerminalCommand('merge', ...args);

Carregando…
Cancelar
Salvar