mirror of
https://github.com/Microsoft/vscode
synced 2024-08-27 04:49:35 +00:00
Git - refactor ref pickers (#201411)
* Refactored quick picks * Another set of refactoring * Moved over the remaining classes * git.checkoutType setting should only be used by the Checkout quick pick * Introduce RefProcessor and cleanup code * Introduced multiple processor types * Another round of refactoring
This commit is contained in:
parent
58041888f4
commit
860d67064a
|
@ -64,12 +64,12 @@
|
|||
"command.deleteBranch": "Delete Branch...",
|
||||
"command.renameBranch": "Rename Branch...",
|
||||
"command.cherryPick": "Cherry Pick...",
|
||||
"command.merge": "Merge Branch...",
|
||||
"command.merge": "Merge...",
|
||||
"command.mergeAbort": "Abort Merge",
|
||||
"command.rebase": "Rebase Branch...",
|
||||
"command.createTag": "Create Tag",
|
||||
"command.deleteTag": "Delete Tag",
|
||||
"command.deleteRemoteTag": "Delete Remote Tag",
|
||||
"command.deleteTag": "Delete Tag...",
|
||||
"command.deleteRemoteTag": "Delete Remote Tag...",
|
||||
"command.fetch": "Fetch",
|
||||
"command.fetchPrune": "Fetch (Prune)",
|
||||
"command.fetchAll": "Fetch From All Remotes",
|
||||
|
|
|
@ -8,7 +8,7 @@ import * as path from 'path';
|
|||
import { Command, commands, Disposable, LineChange, MessageOptions, Position, ProgressLocation, QuickPickItem, Range, SourceControlResourceState, TextDocumentShowOptions, TextEditor, Uri, ViewColumn, window, workspace, WorkspaceEdit, WorkspaceFolder, TimelineItem, env, Selection, TextDocumentContentProvider, InputBoxValidationSeverity, TabInputText, TabInputTextMerge, QuickPickItemKind, TextDocument, LogOutputChannel, l10n, Memento, UIKind, QuickInputButton, ThemeIcon } from 'vscode';
|
||||
import TelemetryReporter from '@vscode/extension-telemetry';
|
||||
import { uniqueNamesGenerator, adjectives, animals, colors, NumberDictionary } from '@joaomoreno/unique-names-generator';
|
||||
import { Branch, ForcePushMode, GitErrorCodes, Ref, RefType, Status, CommitOptions, RemoteSourcePublisher, Remote } from './api/git';
|
||||
import { ForcePushMode, GitErrorCodes, Ref, RefType, Status, CommitOptions, RemoteSourcePublisher, Remote } from './api/git';
|
||||
import { Git, Stash } from './git';
|
||||
import { Model } from './model';
|
||||
import { Repository, Resource, ResourceGroupType } from './repository';
|
||||
|
@ -20,120 +20,179 @@ import { ApiRepository } from './api/api1';
|
|||
import { getRemoteSourceActions, pickRemoteSource } from './remoteSource';
|
||||
import { RemoteSourceAction } from './api/git-base';
|
||||
|
||||
class CheckoutItem implements QuickPickItem {
|
||||
abstract class CheckoutCommandItem implements QuickPickItem {
|
||||
abstract get label(): string;
|
||||
get description(): string { return ''; }
|
||||
get alwaysShow(): boolean { return true; }
|
||||
}
|
||||
|
||||
class CreateBranchItem extends CheckoutCommandItem {
|
||||
get label(): string { return l10n.t('{0} Create new branch...', '$(plus)'); }
|
||||
}
|
||||
|
||||
class CreateBranchFromItem extends CheckoutCommandItem {
|
||||
get label(): string { return l10n.t('{0} Create new branch from...', '$(plus)'); }
|
||||
}
|
||||
|
||||
class CheckoutDetachedItem extends CheckoutCommandItem {
|
||||
get label(): string { return l10n.t('{0} Checkout detached...', '$(debug-disconnect)'); }
|
||||
}
|
||||
|
||||
class RefItemSeparator implements QuickPickItem {
|
||||
get kind(): QuickPickItemKind { return QuickPickItemKind.Separator; }
|
||||
|
||||
get label(): string {
|
||||
switch (this.refType) {
|
||||
case RefType.Head:
|
||||
return l10n.t('branches');
|
||||
case RefType.RemoteHead:
|
||||
return l10n.t('remote branches');
|
||||
case RefType.Tag:
|
||||
return l10n.t('tags');
|
||||
default:
|
||||
return '';
|
||||
}
|
||||
}
|
||||
|
||||
constructor(private readonly refType: RefType) { }
|
||||
}
|
||||
|
||||
class RefItem implements QuickPickItem {
|
||||
|
||||
get label(): string {
|
||||
switch (this.ref.type) {
|
||||
case RefType.Head:
|
||||
return `$(git-branch) ${this.ref.name ?? this.shortCommit}`;
|
||||
case RefType.RemoteHead:
|
||||
return `$(cloud) ${this.ref.name ?? this.shortCommit}`;
|
||||
case RefType.Tag:
|
||||
return `$(tag) ${this.ref.name ?? this.shortCommit}`;
|
||||
default:
|
||||
return '';
|
||||
}
|
||||
}
|
||||
|
||||
get description(): string {
|
||||
switch (this.ref.type) {
|
||||
case RefType.Head:
|
||||
return this.shortCommit;
|
||||
case RefType.RemoteHead:
|
||||
return l10n.t('Remote branch at {0}', this.shortCommit);
|
||||
case RefType.Tag:
|
||||
return l10n.t('Tag at {0}', this.shortCommit);
|
||||
default:
|
||||
return '';
|
||||
}
|
||||
}
|
||||
|
||||
protected get shortCommit(): string { return (this.ref.commit || '').substr(0, 8); }
|
||||
get label(): string { return `${this.repository.isBranchProtected(this.ref) ? '$(lock)' : '$(git-branch)'} ${this.ref.name || this.shortCommit}`; }
|
||||
get description(): string { return this.shortCommit; }
|
||||
get refName(): string | undefined { return this.ref.name; }
|
||||
get refRemote(): string | undefined { return this.ref.remote; }
|
||||
get shortCommit(): string { return (this.ref.commit || '').substr(0, 8); }
|
||||
|
||||
private _buttons?: QuickInputButton[];
|
||||
get buttons(): QuickInputButton[] | undefined { return this._buttons; }
|
||||
set buttons(newButtons: QuickInputButton[] | undefined) { this._buttons = newButtons; }
|
||||
|
||||
constructor(protected repository: Repository, protected ref: Ref, protected _buttons?: QuickInputButton[]) { }
|
||||
constructor(protected readonly ref: Ref) { }
|
||||
}
|
||||
|
||||
async run(opts?: { detached?: boolean }): Promise<void> {
|
||||
class CheckoutItem extends RefItem {
|
||||
|
||||
async run(repository: Repository, opts?: { detached?: boolean }): Promise<void> {
|
||||
if (!this.ref.name) {
|
||||
return;
|
||||
}
|
||||
|
||||
const config = workspace.getConfiguration('git', Uri.file(this.repository.root));
|
||||
const config = workspace.getConfiguration('git', Uri.file(repository.root));
|
||||
const pullBeforeCheckout = config.get<boolean>('pullBeforeCheckout', false) === true;
|
||||
|
||||
const treeish = opts?.detached ? this.ref.commit ?? this.ref.name : this.ref.name;
|
||||
await this.repository.checkout(treeish, { ...opts, pullBeforeCheckout });
|
||||
await repository.checkout(treeish, { ...opts, pullBeforeCheckout });
|
||||
}
|
||||
}
|
||||
|
||||
class CheckoutTagItem extends CheckoutItem {
|
||||
class CheckoutProtectedItem extends CheckoutItem {
|
||||
|
||||
override get label(): string { return `$(tag) ${this.ref.name || this.shortCommit}`; }
|
||||
override get description(): string {
|
||||
return l10n.t('Tag at {0}', this.shortCommit);
|
||||
override get label(): string {
|
||||
return `$(lock) ${this.ref.name ?? this.shortCommit}`;
|
||||
}
|
||||
|
||||
override async run(opts?: { detached?: boolean }): Promise<void> {
|
||||
if (!this.ref.name) {
|
||||
return;
|
||||
}
|
||||
|
||||
await this.repository.checkout(this.ref.name, opts);
|
||||
}
|
||||
}
|
||||
class CheckoutRemoteHeadItem extends RefItem {
|
||||
|
||||
class CheckoutRemoteHeadItem extends CheckoutItem {
|
||||
|
||||
override get label(): string { return `$(cloud) ${this.ref.name || this.shortCommit}`; }
|
||||
override get description(): string {
|
||||
return l10n.t('Remote branch at {0}', this.shortCommit);
|
||||
}
|
||||
|
||||
override async run(opts?: { detached?: boolean }): Promise<void> {
|
||||
async run(repository: Repository, opts?: { detached?: boolean }): Promise<void> {
|
||||
if (!this.ref.name) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (opts?.detached) {
|
||||
await this.repository.checkout(this.ref.commit ?? this.ref.name, opts);
|
||||
await repository.checkout(this.ref.commit ?? this.ref.name, opts);
|
||||
return;
|
||||
}
|
||||
|
||||
const branches = await this.repository.findTrackingBranches(this.ref.name);
|
||||
const branches = await repository.findTrackingBranches(this.ref.name);
|
||||
|
||||
if (branches.length > 0) {
|
||||
await this.repository.checkout(branches[0].name!, opts);
|
||||
await repository.checkout(branches[0].name!, opts);
|
||||
} else {
|
||||
await this.repository.checkoutTracking(this.ref.name, opts);
|
||||
await repository.checkoutTracking(this.ref.name, opts);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class BranchDeleteItem implements QuickPickItem {
|
||||
class CheckoutTagItem extends RefItem {
|
||||
|
||||
private get shortCommit(): string { return (this.ref.commit || '').substr(0, 8); }
|
||||
get branchName(): string | undefined { return this.ref.name; }
|
||||
get label(): string { return this.branchName || ''; }
|
||||
get description(): string { return this.shortCommit; }
|
||||
|
||||
constructor(private ref: Ref) { }
|
||||
|
||||
async run(repository: Repository, force?: boolean): Promise<void> {
|
||||
if (!this.branchName) {
|
||||
async run(repository: Repository, opts?: { detached?: boolean }): Promise<void> {
|
||||
if (!this.ref.name) {
|
||||
return;
|
||||
}
|
||||
await repository.deleteBranch(this.branchName, force);
|
||||
|
||||
await repository.checkout(this.ref.name, opts);
|
||||
}
|
||||
}
|
||||
|
||||
class MergeItem implements QuickPickItem {
|
||||
class BranchDeleteItem extends RefItem {
|
||||
|
||||
private shortCommit: string;
|
||||
|
||||
get label(): string {
|
||||
return this.ref.type === RefType.RemoteHead ?
|
||||
`$(cloud) ${this.ref.name ?? this.shortCommit}` :
|
||||
`${this.repository.isBranchProtected(this.ref) ? '$(lock)' : '$(git-branch)'} ${this.ref.name ?? this.shortCommit}`;
|
||||
async run(repository: Repository, force?: boolean): Promise<void> {
|
||||
if (this.ref.name) {
|
||||
await repository.deleteBranch(this.ref.name, force);
|
||||
}
|
||||
|
||||
get description(): string {
|
||||
return this.ref.type === RefType.RemoteHead ? l10n.t('Remote branch at {0}', this.shortCommit) : this.shortCommit;
|
||||
}
|
||||
|
||||
constructor(private readonly repository: Repository, private readonly ref: Ref) {
|
||||
this.shortCommit = (this.ref.commit ?? '').substring(0, 8);
|
||||
}
|
||||
|
||||
async run(): Promise<void> {
|
||||
await this.repository.merge(this.ref.name ?? this.ref.commit!);
|
||||
}
|
||||
}
|
||||
|
||||
class RebaseItem implements QuickPickItem {
|
||||
class TagDeleteItem extends RefItem {
|
||||
|
||||
get label(): string { return this.ref.name || ''; }
|
||||
description: string = '';
|
||||
async run(repository: Repository): Promise<void> {
|
||||
if (this.ref.name) {
|
||||
await repository.deleteTag(this.ref.name);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
constructor(readonly ref: Ref) { }
|
||||
class RemoteTagDeleteItem extends RefItem {
|
||||
|
||||
override get description(): string {
|
||||
return l10n.t('Remote tag at {0}', this.shortCommit);
|
||||
}
|
||||
|
||||
async run(repository: Repository, remote: string): Promise<void> {
|
||||
if (this.ref.name) {
|
||||
await repository.deleteRemoteTag(remote, this.ref.name);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class MergeItem extends RefItem {
|
||||
|
||||
async run(repository: Repository): Promise<void> {
|
||||
if (this.ref.name || this.ref.commit) {
|
||||
await repository.merge(this.ref.name ?? this.ref.commit!);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class RebaseItem extends RefItem {
|
||||
|
||||
async run(repository: Repository): Promise<void> {
|
||||
if (this.ref?.name) {
|
||||
|
@ -142,22 +201,11 @@ class RebaseItem implements QuickPickItem {
|
|||
}
|
||||
}
|
||||
|
||||
class CreateBranchItem implements QuickPickItem {
|
||||
get label(): string { return '$(plus) ' + l10n.t('Create new branch...'); }
|
||||
get description(): string { return ''; }
|
||||
get alwaysShow(): boolean { return true; }
|
||||
}
|
||||
class RebaseUpstreamItem extends RebaseItem {
|
||||
|
||||
class CreateBranchFromItem implements QuickPickItem {
|
||||
get label(): string { return '$(plus) ' + l10n.t('Create new branch from...'); }
|
||||
get description(): string { return ''; }
|
||||
get alwaysShow(): boolean { return true; }
|
||||
override get description(): string {
|
||||
return '(upstream)';
|
||||
}
|
||||
|
||||
class CheckoutDetachedItem implements QuickPickItem {
|
||||
get label(): string { return '$(debug-disconnect) ' + l10n.t('Checkout detached...'); }
|
||||
get description(): string { return ''; }
|
||||
get alwaysShow(): boolean { return true; }
|
||||
}
|
||||
|
||||
class HEADItem implements QuickPickItem {
|
||||
|
@ -265,7 +313,7 @@ async function categorizeResourceByResolution(resources: Resource[]): Promise<{
|
|||
return { merge, resolved, unresolved, deletionConflicts };
|
||||
}
|
||||
|
||||
async function createCheckoutItems(repository: Repository, detached = false): Promise<CheckoutItem[]> {
|
||||
async function createCheckoutItems(repository: Repository, detached = false): Promise<QuickPickItem[]> {
|
||||
const config = workspace.getConfiguration('git');
|
||||
const checkoutTypeConfig = config.get<string | string[]>('checkoutType');
|
||||
let checkoutTypes: string[];
|
||||
|
@ -284,39 +332,13 @@ async function createCheckoutItems(repository: Repository, detached = false): Pr
|
|||
}
|
||||
|
||||
const refs = await repository.getRefs();
|
||||
const processors = checkoutTypes.map(type => getCheckoutProcessor(repository, type))
|
||||
.filter(p => !!p) as CheckoutProcessor[];
|
||||
|
||||
for (const ref of refs) {
|
||||
if (!detached && ref.name === 'origin/HEAD') {
|
||||
continue;
|
||||
}
|
||||
|
||||
for (const processor of processors) {
|
||||
processor.onRef(ref);
|
||||
}
|
||||
}
|
||||
const refProcessors = checkoutTypes.map(type => getCheckoutRefProcessor(repository, type))
|
||||
.filter(p => !!p) as RefProcessor[];
|
||||
|
||||
const buttons = await getRemoteRefItemButtons(repository);
|
||||
let fallbackRemoteButtons: RemoteSourceActionButton[] | undefined = [];
|
||||
const remote = repository.remotes.find(r => r.pushUrl === repository.HEAD?.remote || r.fetchUrl === repository.HEAD?.remote) ?? repository.remotes[0];
|
||||
const remoteUrl = remote?.pushUrl ?? remote?.fetchUrl;
|
||||
if (remoteUrl) {
|
||||
fallbackRemoteButtons = buttons.get(remoteUrl);
|
||||
}
|
||||
const itemsProcessor = new CheckoutItemsProcessor(refProcessors, repository, buttons, detached);
|
||||
|
||||
return processors.reduce<CheckoutItem[]>((r, p) => r.concat(...p.items.map((item) => {
|
||||
if (item.refRemote) {
|
||||
const matchingRemote = repository.remotes.find((remote) => remote.name === item.refRemote);
|
||||
const remoteUrl = matchingRemote?.pushUrl ?? matchingRemote?.fetchUrl;
|
||||
if (remoteUrl) {
|
||||
item.buttons = buttons.get(item.refRemote);
|
||||
}
|
||||
}
|
||||
|
||||
item.buttons = fallbackRemoteButtons;
|
||||
return item;
|
||||
})), []);
|
||||
return itemsProcessor.processRefs(refs);
|
||||
}
|
||||
|
||||
type RemoteSourceActionButton = {
|
||||
|
@ -347,31 +369,182 @@ async function getRemoteRefItemButtons(repository: Repository) {
|
|||
return remoteUrlsToActions;
|
||||
}
|
||||
|
||||
class CheckoutProcessor {
|
||||
class RefProcessor {
|
||||
protected readonly refs: Ref[] = [];
|
||||
|
||||
private refs: Ref[] = [];
|
||||
get items(): CheckoutItem[] { return this.refs.map(r => new this.ctor(this.repository, r)); }
|
||||
constructor(private repository: Repository, private type: RefType, private ctor: { new(repository: Repository, ref: Ref): CheckoutItem }) { }
|
||||
get items(): QuickPickItem[] {
|
||||
const items = this.refs.map(r => new this.ctor(r));
|
||||
return items.length === 0 ? items : [new RefItemSeparator(this.type), ...items];
|
||||
}
|
||||
|
||||
constructor(protected readonly type: RefType, protected readonly ctor: { new(ref: Ref): QuickPickItem } = RefItem) { }
|
||||
|
||||
processRef(ref: Ref): boolean {
|
||||
if (!ref.name && !ref.commit) {
|
||||
return false;
|
||||
}
|
||||
if (ref.type !== this.type) {
|
||||
return false;
|
||||
}
|
||||
|
||||
onRef(ref: Ref): void {
|
||||
if (ref.type === this.type) {
|
||||
this.refs.push(ref);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
class RefItemsProcessor {
|
||||
|
||||
constructor(protected readonly processors: RefProcessor[]) { }
|
||||
|
||||
processRefs(refs: Ref[]): QuickPickItem[] {
|
||||
for (const ref of refs) {
|
||||
for (const processor of this.processors) {
|
||||
if (processor.processRef(ref)) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function getCheckoutProcessor(repository: Repository, type: string): CheckoutProcessor | undefined {
|
||||
const result: QuickPickItem[] = [];
|
||||
for (const processor of this.processors) {
|
||||
result.push(...processor.items);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
class RebaseItemsProcessors extends RefItemsProcessor {
|
||||
|
||||
private upstreamName: string | undefined;
|
||||
|
||||
constructor(private readonly repository: Repository) {
|
||||
super([
|
||||
new RefProcessor(RefType.Head, RebaseItem),
|
||||
new RefProcessor(RefType.RemoteHead, RebaseItem)
|
||||
]);
|
||||
|
||||
if (this.repository.HEAD?.upstream) {
|
||||
this.upstreamName = `${this.repository.HEAD?.upstream.remote}/${this.repository.HEAD?.upstream.name}`;
|
||||
}
|
||||
}
|
||||
|
||||
override processRefs(refs: Ref[]): QuickPickItem[] {
|
||||
const result: QuickPickItem[] = [];
|
||||
|
||||
for (const ref of refs) {
|
||||
if (ref.name === this.repository.HEAD?.name) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (ref.name === this.upstreamName) {
|
||||
result.push(new RebaseUpstreamItem(ref));
|
||||
continue;
|
||||
}
|
||||
|
||||
for (const processor of this.processors) {
|
||||
if (processor.processRef(ref)) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (const processor of this.processors) {
|
||||
result.push(...processor.items);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
class CheckoutRefProcessor extends RefProcessor {
|
||||
|
||||
override get items(): QuickPickItem[] {
|
||||
const items = this.refs.map(ref => {
|
||||
return this.repository.isBranchProtected(ref) ?
|
||||
new CheckoutProtectedItem(ref) :
|
||||
new CheckoutItem(ref);
|
||||
});
|
||||
|
||||
return items.length === 0 ? items : [new RefItemSeparator(this.type), ...items];
|
||||
}
|
||||
|
||||
constructor(private readonly repository: Repository) {
|
||||
super(RefType.Head);
|
||||
}
|
||||
}
|
||||
|
||||
class CheckoutItemsProcessor extends RefItemsProcessor {
|
||||
|
||||
private defaultButtons: RemoteSourceActionButton[] | undefined;
|
||||
|
||||
constructor(
|
||||
processors: RefProcessor[],
|
||||
private readonly repository: Repository,
|
||||
private readonly buttons: Map<string, RemoteSourceActionButton[]>,
|
||||
private readonly detached = false) {
|
||||
super(processors);
|
||||
|
||||
// Default button(s)
|
||||
const remote = repository.remotes.find(r => r.pushUrl === repository.HEAD?.remote || r.fetchUrl === repository.HEAD?.remote) ?? repository.remotes[0];
|
||||
const remoteUrl = remote?.pushUrl ?? remote?.fetchUrl;
|
||||
if (remoteUrl) {
|
||||
this.defaultButtons = buttons.get(remoteUrl);
|
||||
}
|
||||
}
|
||||
|
||||
override processRefs(refs: Ref[]): QuickPickItem[] {
|
||||
for (const ref of refs) {
|
||||
if (!this.detached && ref.name === 'origin/HEAD') {
|
||||
continue;
|
||||
}
|
||||
|
||||
for (const processor of this.processors) {
|
||||
if (processor.processRef(ref)) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const result: QuickPickItem[] = [];
|
||||
for (const processor of this.processors) {
|
||||
for (const item of processor.items) {
|
||||
if (!(item instanceof RefItem)) {
|
||||
result.push(item);
|
||||
continue;
|
||||
}
|
||||
|
||||
// Button(s)
|
||||
if (item.refRemote) {
|
||||
const matchingRemote = this.repository.remotes.find((remote) => remote.name === item.refRemote);
|
||||
const remoteUrl = matchingRemote?.pushUrl ?? matchingRemote?.fetchUrl;
|
||||
if (remoteUrl) {
|
||||
item.buttons = this.buttons.get(item.refRemote);
|
||||
}
|
||||
} else {
|
||||
item.buttons = this.defaultButtons;
|
||||
}
|
||||
|
||||
result.push(item);
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
function getCheckoutRefProcessor(repository: Repository, type: string): RefProcessor | undefined {
|
||||
switch (type) {
|
||||
case 'local':
|
||||
return new CheckoutProcessor(repository, RefType.Head, CheckoutItem);
|
||||
return new CheckoutRefProcessor(repository);
|
||||
case 'remote':
|
||||
return new CheckoutProcessor(repository, RefType.RemoteHead, CheckoutRemoteHeadItem);
|
||||
return new RefProcessor(RefType.RemoteHead, CheckoutRemoteHeadItem);
|
||||
case 'tags':
|
||||
return new CheckoutProcessor(repository, RefType.Tag, CheckoutTagItem);
|
||||
}
|
||||
|
||||
return new RefProcessor(RefType.Tag, CheckoutTagItem);
|
||||
default:
|
||||
return undefined;
|
||||
}
|
||||
}
|
||||
|
||||
function getRepositoryLabel(repositoryRoot: string): string {
|
||||
const workspaceFolder = workspace.getWorkspaceFolder(Uri.file(repositoryRoot));
|
||||
|
@ -391,12 +564,6 @@ function sanitizeRemoteName(name: string) {
|
|||
return name && name.replace(/^\.|\/\.|\.\.|~|\^|:|\/$|\.lock$|\.lock\/|\\|\*|\s|^\s*$|\.$|\[|\]$/g, '-');
|
||||
}
|
||||
|
||||
class TagItem implements QuickPickItem {
|
||||
get label(): string { return `$(tag) ${this.ref.name ?? ''}`; }
|
||||
get description(): string { return this.ref.commit?.substr(0, 8) ?? ''; }
|
||||
constructor(readonly ref: Ref) { }
|
||||
}
|
||||
|
||||
enum PushType {
|
||||
Push,
|
||||
PushTo,
|
||||
|
@ -2230,7 +2397,7 @@ export class CommandCenter {
|
|||
const picks: QuickPickItem[] = [];
|
||||
|
||||
if (!opts?.detached) {
|
||||
picks.push(createBranch, createBranchFrom, checkoutDetached, { label: '', kind: QuickPickItemKind.Separator });
|
||||
picks.push(createBranch, createBranchFrom, checkoutDetached);
|
||||
}
|
||||
|
||||
const quickpick = window.createQuickPick();
|
||||
|
@ -2272,7 +2439,7 @@ export class CommandCenter {
|
|||
const item = choice as CheckoutItem;
|
||||
|
||||
try {
|
||||
await item.run(opts);
|
||||
await item.run(repository, opts);
|
||||
} catch (err) {
|
||||
if (err.gitErrorCode !== GitErrorCodes.DirtyWorkTree) {
|
||||
throw err;
|
||||
|
@ -2285,10 +2452,10 @@ export class CommandCenter {
|
|||
|
||||
if (choice === force) {
|
||||
await this.cleanAll(repository);
|
||||
await item.run(opts);
|
||||
await item.run(repository, opts);
|
||||
} else if (choice === stash || choice === migrate) {
|
||||
if (await this._stash(repository)) {
|
||||
await item.run(opts);
|
||||
await item.run(repository, opts);
|
||||
|
||||
if (choice === migrate) {
|
||||
await this.stashPopLatest(repository);
|
||||
|
@ -2408,7 +2575,14 @@ export class CommandCenter {
|
|||
|
||||
if (from) {
|
||||
const getRefPicks = async () => {
|
||||
return [new HEADItem(repository), ...await createCheckoutItems(repository)];
|
||||
const refs = await repository.getRefs();
|
||||
const refProcessors = new RefItemsProcessor([
|
||||
new RefProcessor(RefType.Head),
|
||||
new RefProcessor(RefType.RemoteHead),
|
||||
new RefProcessor(RefType.Tag)
|
||||
]);
|
||||
|
||||
return [new HEADItem(repository), ...refProcessors.processRefs(refs)];
|
||||
};
|
||||
|
||||
const placeHolder = l10n.t('Select a ref to create the branch from');
|
||||
|
@ -2418,7 +2592,7 @@ export class CommandCenter {
|
|||
return;
|
||||
}
|
||||
|
||||
if (choice.refName) {
|
||||
if (choice instanceof RefItem && choice.refName) {
|
||||
target = choice.refName;
|
||||
}
|
||||
}
|
||||
|
@ -2448,10 +2622,10 @@ export class CommandCenter {
|
|||
const placeHolder = l10n.t('Select a branch to delete');
|
||||
const choice = await window.showQuickPick<BranchDeleteItem>(getBranchPicks(), { placeHolder });
|
||||
|
||||
if (!choice || !choice.branchName) {
|
||||
if (!choice || !choice.refName) {
|
||||
return;
|
||||
}
|
||||
name = choice.branchName;
|
||||
name = choice.refName;
|
||||
run = force => choice.run(repository, force);
|
||||
}
|
||||
|
||||
|
@ -2499,32 +2673,23 @@ export class CommandCenter {
|
|||
|
||||
@command('git.merge', { repository: true })
|
||||
async merge(repository: Repository): Promise<void> {
|
||||
const config = workspace.getConfiguration('git');
|
||||
const checkoutType = config.get<string | string[]>('checkoutType');
|
||||
const includeRemotes = checkoutType === 'all' || checkoutType === 'remote' || checkoutType?.includes('remote');
|
||||
|
||||
const getBranchPicks = async (): Promise<MergeItem[]> => {
|
||||
const getQuickPickItems = async (): Promise<QuickPickItem[]> => {
|
||||
const refs = await repository.getRefs();
|
||||
const itemsProcessor = new RefItemsProcessor([
|
||||
new RefProcessor(RefType.Head, MergeItem),
|
||||
new RefProcessor(RefType.RemoteHead, MergeItem),
|
||||
new RefProcessor(RefType.Tag, MergeItem)
|
||||
]);
|
||||
|
||||
const heads = refs.filter(ref => ref.type === RefType.Head)
|
||||
.filter(ref => ref.name || ref.commit)
|
||||
.map(ref => new MergeItem(repository, ref as Branch));
|
||||
|
||||
const remoteHeads = (includeRemotes ? refs.filter(ref => ref.type === RefType.RemoteHead) : [])
|
||||
.filter(ref => ref.name || ref.commit)
|
||||
.map(ref => new MergeItem(repository, ref as Branch));
|
||||
|
||||
return [...heads, ...remoteHeads];
|
||||
return itemsProcessor.processRefs(refs);
|
||||
};
|
||||
|
||||
const placeHolder = l10n.t('Select a branch to merge from');
|
||||
const choice = await window.showQuickPick<MergeItem>(getBranchPicks(), { placeHolder });
|
||||
const placeHolder = l10n.t('Select a branch or tag to merge from');
|
||||
const choice = await window.showQuickPick(getQuickPickItems(), { placeHolder });
|
||||
|
||||
if (!choice) {
|
||||
return;
|
||||
if (choice instanceof MergeItem) {
|
||||
await choice.run(repository);
|
||||
}
|
||||
|
||||
await choice.run();
|
||||
}
|
||||
|
||||
@command('git.mergeAbort', { repository: true })
|
||||
|
@ -2534,46 +2699,20 @@ export class CommandCenter {
|
|||
|
||||
@command('git.rebase', { repository: true })
|
||||
async rebase(repository: Repository): Promise<void> {
|
||||
const config = workspace.getConfiguration('git');
|
||||
const checkoutType = config.get<string | string[]>('checkoutType');
|
||||
const includeRemotes = checkoutType === 'all' || checkoutType === 'remote' || checkoutType?.includes('remote');
|
||||
|
||||
const getBranchPicks = async () => {
|
||||
const getQuickPickItems = async (): Promise<QuickPickItem[]> => {
|
||||
const refs = await repository.getRefs();
|
||||
const itemsProcessor = new RebaseItemsProcessors(repository);
|
||||
|
||||
const heads = refs.filter(ref => ref.type === RefType.Head)
|
||||
.filter(ref => ref.name !== repository.HEAD?.name)
|
||||
.filter(ref => ref.name || ref.commit);
|
||||
|
||||
const remoteHeads = (includeRemotes ? refs.filter(ref => ref.type === RefType.RemoteHead) : [])
|
||||
.filter(ref => ref.name || ref.commit);
|
||||
|
||||
const picks = [...heads, ...remoteHeads].map(ref => new RebaseItem(ref));
|
||||
|
||||
// set upstream branch as first
|
||||
if (repository.HEAD?.upstream) {
|
||||
const upstreamName = `${repository.HEAD?.upstream.remote}/${repository.HEAD?.upstream.name}`;
|
||||
const index = picks.findIndex(e => e.ref.name === upstreamName);
|
||||
|
||||
if (index > -1) {
|
||||
const [ref] = picks.splice(index, 1);
|
||||
ref.description = '(upstream)';
|
||||
picks.unshift(ref);
|
||||
}
|
||||
}
|
||||
|
||||
return picks;
|
||||
return itemsProcessor.processRefs(refs);
|
||||
};
|
||||
|
||||
const placeHolder = l10n.t('Select a branch to rebase onto');
|
||||
const choice = await window.showQuickPick<RebaseItem>(getBranchPicks(), { placeHolder });
|
||||
|
||||
if (!choice) {
|
||||
return;
|
||||
}
|
||||
const choice = await window.showQuickPick(getQuickPickItems(), { placeHolder });
|
||||
|
||||
if (choice instanceof RebaseItem) {
|
||||
await choice.run(repository);
|
||||
}
|
||||
}
|
||||
|
||||
@command('git.createTag', { repository: true })
|
||||
async createTag(repository: Repository): Promise<void> {
|
||||
|
@ -2599,16 +2738,16 @@ export class CommandCenter {
|
|||
|
||||
@command('git.deleteTag', { repository: true })
|
||||
async deleteTag(repository: Repository): Promise<void> {
|
||||
const tagPicks = async (): Promise<TagItem[] | QuickPickItem[]> => {
|
||||
const tagPicks = async (): Promise<TagDeleteItem[] | QuickPickItem[]> => {
|
||||
const remoteTags = await repository.getRefs({ pattern: 'refs/tags' });
|
||||
return remoteTags.length === 0 ? [{ label: l10n.t('$(info) This repository has no tags.') }] : remoteTags.map(ref => new TagItem(ref));
|
||||
return remoteTags.length === 0 ? [{ label: l10n.t('$(info) This repository has no tags.') }] : remoteTags.map(ref => new TagDeleteItem(ref));
|
||||
};
|
||||
|
||||
const placeHolder = l10n.t('Select a tag to delete');
|
||||
const choice = await window.showQuickPick<TagItem | QuickPickItem>(tagPicks(), { placeHolder });
|
||||
const choice = await window.showQuickPick<TagDeleteItem | QuickPickItem>(tagPicks(), { placeHolder });
|
||||
|
||||
if (choice && choice instanceof TagItem && choice.ref.name) {
|
||||
await repository.deleteTag(choice.ref.name);
|
||||
if (choice instanceof TagDeleteItem) {
|
||||
await choice.run(repository);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -2635,7 +2774,7 @@ export class CommandCenter {
|
|||
remoteName = remotePick.remoteName;
|
||||
}
|
||||
|
||||
const remoteTagPicks = async (): Promise<TagItem[] | QuickPickItem[]> => {
|
||||
const remoteTagPicks = async (): Promise<RemoteTagDeleteItem[] | QuickPickItem[]> => {
|
||||
const remoteTagsRaw = await repository.getRemoteRefs(remoteName, { tags: true });
|
||||
|
||||
// Deduplicate annotated and lightweight tags
|
||||
|
@ -2650,14 +2789,14 @@ export class CommandCenter {
|
|||
}
|
||||
}
|
||||
|
||||
return remoteTags.length === 0 ? [{ label: l10n.t('$(info) Remote "{0}" has no tags.', remoteName) }] : remoteTags.map(ref => new TagItem(ref));
|
||||
return remoteTags.length === 0 ? [{ label: l10n.t('$(info) Remote "{0}" has no tags.', remoteName) }] : remoteTags.map(ref => new RemoteTagDeleteItem(ref));
|
||||
};
|
||||
|
||||
const tagPickPlaceholder = l10n.t('Select a tag to delete');
|
||||
const remoteTagPick = await window.showQuickPick<TagItem | QuickPickItem>(remoteTagPicks(), { placeHolder: tagPickPlaceholder });
|
||||
const tagPickPlaceholder = l10n.t('Select a remote tag to delete');
|
||||
const remoteTagPick = await window.showQuickPick<RemoteTagDeleteItem | QuickPickItem>(remoteTagPicks(), { placeHolder: tagPickPlaceholder });
|
||||
|
||||
if (remoteTagPick && remoteTagPick instanceof TagItem && remoteTagPick.ref.name) {
|
||||
await repository.deleteRemoteTag(remoteName, remoteTagPick.ref.name);
|
||||
if (remoteTagPick instanceof RemoteTagDeleteItem) {
|
||||
await remoteTagPick.run(repository, remoteName);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -2747,21 +2886,20 @@ export class CommandCenter {
|
|||
remoteName = remotePick.label;
|
||||
}
|
||||
|
||||
const getBranchPicks = async (): Promise<QuickPickItem[]> => {
|
||||
const remoteRefs = await repository.getRefs();
|
||||
const remoteRefsFiltered = remoteRefs.filter(r => (r.remote === remoteName));
|
||||
return remoteRefsFiltered.map(r => ({ label: r.name! }));
|
||||
const getBranchPicks = async (): Promise<RefItem[]> => {
|
||||
const remoteRefs = await repository.getRefs({ pattern: `refs/remotes/${remoteName}/` });
|
||||
return remoteRefs.map(r => new RefItem(r));
|
||||
};
|
||||
|
||||
const branchPlaceHolder = l10n.t('Pick a branch to pull from');
|
||||
const branchPick = await window.showQuickPick(getBranchPicks(), { placeHolder: branchPlaceHolder });
|
||||
|
||||
if (!branchPick) {
|
||||
if (!branchPick || !branchPick.refName) {
|
||||
return;
|
||||
}
|
||||
|
||||
const remoteCharCnt = remoteName.length;
|
||||
await repository.pullFrom(false, remoteName, branchPick.label.slice(remoteCharCnt + 1));
|
||||
await repository.pullFrom(false, remoteName, branchPick.refName.slice(remoteCharCnt + 1));
|
||||
}
|
||||
|
||||
@command('git.pull', { repository: true })
|
||||
|
|
Loading…
Reference in a new issue