From 4e85f47fbeb09b598664e994e3d4ef39d328095e Mon Sep 17 00:00:00 2001 From: Eric Amodio Date: Tue, 15 Sep 2020 00:57:44 -0400 Subject: [PATCH] Adds branch delete option for tracking branches --- src/commands/git/branch.ts | 63 +++++++++++++++++++++++++++++++------------- src/commands/git/status.ts | 1 + src/git/models/branch.ts | 4 +++ src/git/models/models.ts | 7 ++--- src/git/models/repository.ts | 37 ++++++++++++++++++++------ 5 files changed, 83 insertions(+), 29 deletions(-) diff --git a/src/commands/git/branch.ts b/src/commands/git/branch.ts index 8556b90..a2b6f7c 100644 --- a/src/commands/git/branch.ts +++ b/src/commands/git/branch.ts @@ -37,7 +37,7 @@ interface CreateState { flags: CreateFlags[]; } -type DeleteFlags = '--force'; +type DeleteFlags = '--force' | '--remotes'; interface DeleteState { subcommand: 'delete'; @@ -363,7 +363,10 @@ export class BranchGitCommand extends QuickCommand { state.flags = result; QuickCommand.endSteps(state); - void state.repo.branchDelete(state.references, { force: state.flags.includes('--force') }); + void state.repo.branchDelete(state.references, { + force: state.flags.includes('--force'), + remote: state.flags.includes('--remotes'), + }); } } @@ -371,24 +374,48 @@ export class BranchGitCommand extends QuickCommand { state: DeleteStepState>, context: Context, ): StepResultGenerator { + const confirmations: FlagsQuickPickItem[] = [ + FlagsQuickPickItem.create(state.flags, [], { + label: context.title, + detail: `Will delete ${GitReference.toString(state.references)}`, + }), + ]; + if (!state.references.every(b => b.remote)) { + confirmations.push( + FlagsQuickPickItem.create(state.flags, ['--force'], { + label: `Force ${context.title}`, + description: '--force', + detail: `Will forcably delete ${GitReference.toString(state.references)}`, + }), + ); + + if (state.references.some(b => b.tracking != null)) { + confirmations.push( + FlagsQuickPickItem.create(state.flags, ['--remotes'], { + label: `${context.title} & Remote${ + state.references.filter(b => !b.remote).length > 1 ? 's' : '' + }`, + description: '--remotes', + detail: `Will delete ${GitReference.toString( + state.references, + )} and any remote tracking branches`, + }), + FlagsQuickPickItem.create(state.flags, ['--force', '--remotes'], { + label: `Force ${context.title} & Remote${ + state.references.filter(b => !b.remote).length > 1 ? 's' : '' + }`, + description: '--force --remotes', + detail: `Will forcably delete ${GitReference.toString( + state.references, + )} and any remote tracking branches`, + }), + ); + } + } + const step: QuickPickStep> = QuickCommand.createConfirmStep( appendReposToTitle(`Confirm ${context.title}`, state, context), - [ - FlagsQuickPickItem.create(state.flags, [], { - label: context.title, - detail: `Will delete ${GitReference.toString(state.references)}`, - }), - // Don't allow force if there are remote branches - ...(!state.references.some(r => r.remote) - ? [ - FlagsQuickPickItem.create(state.flags, ['--force'], { - label: `Force ${context.title}`, - description: '--force', - detail: `Will forcably delete ${GitReference.toString(state.references)}`, - }), - ] - : []), - ], + confirmations, context, ); const selection: StepSelection = yield step; diff --git a/src/commands/git/status.ts b/src/commands/git/status.ts index d1fc7a5..0ab6d6d 100644 --- a/src/commands/git/status.ts +++ b/src/commands/git/status.ts @@ -89,6 +89,7 @@ export class StatusGitCommand extends QuickCommand { refType: 'branch', name: context.status.branch, remote: false, + tracking: context.status.upstream, }), { icon: false }, )}`; diff --git a/src/git/models/branch.ts b/src/git/models/branch.ts index 5f94a8d..ee45824 100644 --- a/src/git/models/branch.ts +++ b/src/git/models/branch.ts @@ -219,6 +219,10 @@ export class GitBranch implements GitBranchReference { return `(${GitRevision.shorten(sha)}...)`; } + static getNameWithoutRemote(name: string): string { + return name.substring(name.indexOf('/') + 1); + } + static getRemote(name: string): string { return name.substring(0, name.indexOf('/')); } diff --git a/src/git/models/models.ts b/src/git/models/models.ts index ed4d7a3..36e3682 100644 --- a/src/git/models/models.ts +++ b/src/git/models/models.ts @@ -1,5 +1,5 @@ 'use strict'; - +import { GitBranch } from './branch'; import { Container } from '../../container'; import { GlyphChars } from '../../constants'; @@ -97,6 +97,7 @@ export interface GitBranchReference { name: string; ref: string; readonly remote: boolean; + readonly tracking?: string; repoPath: string; } @@ -133,7 +134,7 @@ export namespace GitReference { export function create( ref: string, repoPath: string, - options: { refType: 'branch'; name: string; remote: boolean }, + options: { refType: 'branch'; name: string; remote: boolean; tracking?: string }, ): GitBranchReference; export function create( ref: string, @@ -194,7 +195,7 @@ export namespace GitReference { export function getNameWithoutRemote(ref: GitReference) { if (ref.refType === 'branch') { - return ref.remote ? ref.name.substring(ref.name.indexOf('/') + 1) : ref.name; + return ref.remote ? GitBranch.getNameWithoutRemote(ref.name) : ref.name; } return ref.name; } diff --git a/src/git/models/repository.ts b/src/git/models/repository.ts index a22563d..8533da5 100644 --- a/src/git/models/repository.ts +++ b/src/git/models/repository.ts @@ -16,15 +16,15 @@ import { import { configuration } from '../../configuration'; import { StarredRepositories, WorkspaceState } from '../../constants'; import { Container } from '../../container'; -import { Functions, gate, Iterables, log, logName } from '../../system'; import { GitBranch, GitContributor, GitDiffShortStat, GitRemote, GitStash, GitStatus, GitTag } from '../git'; import { GitService } from '../gitService'; import { GitUri } from '../gitUri'; -import { RemoteProviderFactory, RemoteProviders, RemoteProviderWithApi } from '../remotes/factory'; -import { Messages } from '../../messages'; import { Logger } from '../../logger'; -import { runGitCommandInTerminal } from '../../terminal'; +import { Messages } from '../../messages'; import { GitBranchReference, GitReference, GitTagReference } from './models'; +import { RemoteProviderFactory, RemoteProviders, RemoteProviderWithApi } from '../remotes/factory'; +import { Arrays, Functions, gate, Iterables, log, logName } from '../../system'; +import { runGitCommandInTerminal } from '../../terminal'; const ignoreGitRegex = /\.git(?:\/|\\|$)/; const refsRegex = /\.git\/refs\/(heads|remotes|tags)/; @@ -275,7 +275,10 @@ export class Repository implements Disposable { @gate() @log() - branchDelete(branches: GitBranchReference | GitBranchReference[], { force }: { force?: boolean } = {}) { + branchDelete( + branches: GitBranchReference | GitBranchReference[], + { force, remote }: { force?: boolean; remote?: boolean } = {}, + ) { if (!Array.isArray(branches)) { branches = [branches]; } @@ -287,16 +290,34 @@ export class Repository implements Disposable { args.push('--force'); } this.runTerminalCommand('branch', ...args, ...branches.map(b => b.ref)); + + if (remote) { + const trackingBranches = localBranches.filter(b => b.tracking != null); + if (trackingBranches.length !== 0) { + const branchesByOrigin = Arrays.groupByMap(trackingBranches, b => GitBranch.getRemote(b.tracking!)); + + for (const [remote, branches] of branchesByOrigin.entries()) { + this.runTerminalCommand( + 'push', + '-d', + remote, + ...branches.map(b => GitBranch.getNameWithoutRemote(b.tracking!)), + ); + } + } + } } const remoteBranches = branches.filter(b => b.remote); if (remoteBranches.length !== 0) { - for (const branch of remoteBranches) { + const branchesByOrigin = Arrays.groupByMap(remoteBranches, b => GitBranch.getRemote(b.name)); + + for (const [remote, branches] of branchesByOrigin.entries()) { this.runTerminalCommand( 'push', '-d', - GitBranch.getRemote(branch.name), - GitReference.getNameWithoutRemote(branch), + remote, + ...branches.map(b => GitReference.getNameWithoutRemote(b)), ); } }