From eb5caafdb6409480a5a2db7b9a15f552125c6c32 Mon Sep 17 00:00:00 2001 From: dev-jiwonchoi Date: Wed, 31 May 2023 23:28:32 +0900 Subject: [PATCH 1/4] feat: add delete pruned branches command Co-authored-by: Ladislau Szomoru --- extensions/git/package.json | 6 ++++++ extensions/git/package.nls.json | 1 + extensions/git/src/commands.ts | 19 +++++++++++++++++++ extensions/git/src/git.ts | 6 ++++++ extensions/git/src/operation.ts | 5 ++++- extensions/git/src/repository.ts | 4 ++++ 6 files changed, 40 insertions(+), 1 deletion(-) diff --git a/extensions/git/package.json b/extensions/git/package.json index 5e00d21546347..823c109621fe5 100644 --- a/extensions/git/package.json +++ b/extensions/git/package.json @@ -393,6 +393,12 @@ "category": "Git", "enablement": "!operationInProgress" }, + { + "command": "git.deletePrunedBranches", + "title": "%command.deletePrunedBranches%", + "category": "Git", + "enablement": "!operationInProgress" + }, { "command": "git.renameBranch", "title": "%command.renameBranch%", diff --git a/extensions/git/package.nls.json b/extensions/git/package.nls.json index 2471b5d92869d..2bd31cd35ed77 100644 --- a/extensions/git/package.nls.json +++ b/extensions/git/package.nls.json @@ -56,6 +56,7 @@ "command.branch": "Create Branch...", "command.branchFrom": "Create Branch From...", "command.deleteBranch": "Delete Branch...", + "command.deletePrunedBranches": "Delete Pruned Branches...", "command.renameBranch": "Rename Branch...", "command.cherryPick": "Cherry Pick...", "command.merge": "Merge Branch...", diff --git a/extensions/git/src/commands.ts b/extensions/git/src/commands.ts index cdf81dc6e6120..d3df36e05e23e 100644 --- a/extensions/git/src/commands.ts +++ b/extensions/git/src/commands.ts @@ -2362,6 +2362,25 @@ export class CommandCenter { } } + @command('git.deletePrunedBranches', { repository: true }) + async deletePrunedBranches(repository: Repository, force?: boolean): Promise { + try { + await repository.deletePrunedBranches(force); + } catch (err) { + if (err.gitErrorCode !== GitErrorCodes.BranchNotFullyMerged) { + throw err; + } + + const message = l10n.t('The branch is not fully merged. Delete anyway?'); + const yes = l10n.t('Delete Branch'); + const pick = await window.showWarningMessage(message, { modal: true }, yes); + + if (pick === yes) { + await repository.deletePrunedBranches(true); + } + } + } + @command('git.renameBranch', { repository: true }) async renameBranch(repository: Repository): Promise { const currentBranchName = repository.HEAD && repository.HEAD.name; diff --git a/extensions/git/src/git.ts b/extensions/git/src/git.ts index 8290560e3cedf..50908aada6700 100644 --- a/extensions/git/src/git.ts +++ b/extensions/git/src/git.ts @@ -1588,6 +1588,12 @@ export class Repository { await this.exec(args); } + async deletePrunedBranches(force?: boolean): Promise { + force ? + await cp.exec('git branch -D `git branch -vv | grep ": gone]" | awk "{print $1}"`') : + await cp.exec('git branch -d `git branch -vv | grep ": gone]" | awk "{print $1}"`'); + } + async renameBranch(name: string): Promise { const args = ['branch', '-m', name]; await this.exec(args); diff --git a/extensions/git/src/operation.ts b/extensions/git/src/operation.ts index b385acb11c51d..7c8e03682558c 100644 --- a/extensions/git/src/operation.ts +++ b/extensions/git/src/operation.ts @@ -20,6 +20,7 @@ export const enum OperationKind { Commit = 'Commit', Config = 'Config', DeleteBranch = 'DeleteBranch', + deletePrunedBranches = 'deletePrunedBranches', DeleteRef = 'DeleteRef', DeleteRemoteTag = 'DeleteRemoteTag', DeleteTag = 'DeleteTag', @@ -64,7 +65,7 @@ export const enum OperationKind { } export type Operation = AddOperation | ApplyOperation | BlameOperation | BranchOperation | CheckIgnoreOperation | CherryPickOperation | - CheckoutOperation | CheckoutTrackingOperation | CleanOperation | CommitOperation | ConfigOperation | DeleteBranchOperation | + CheckoutOperation | CheckoutTrackingOperation | CleanOperation | CommitOperation | ConfigOperation | DeleteBranchOperation | deletePrunedBranchesOperation | DeleteRefOperation | DeleteRemoteTagOperation | DeleteTagOperation | DiffOperation | FetchOperation | FindTrackingBranchesOperation | GetBranchOperation | GetBranchesOperation | GetCommitTemplateOperation | GetObjectDetailsOperation | GetRefsOperation | GetRemoteRefsOperation | HashObjectOperation | IgnoreOperation | LogOperation | LogFileOperation | MergeOperation | MergeAbortOperation | MergeBaseOperation | @@ -85,6 +86,7 @@ export type CleanOperation = BaseOperation & { kind: OperationKind.Clean }; export type CommitOperation = BaseOperation & { kind: OperationKind.Commit }; export type ConfigOperation = BaseOperation & { kind: OperationKind.Config }; export type DeleteBranchOperation = BaseOperation & { kind: OperationKind.DeleteBranch }; +export type deletePrunedBranchesOperation = BaseOperation & { kind: OperationKind.deletePrunedBranches }; export type DeleteRefOperation = BaseOperation & { kind: OperationKind.DeleteRef }; export type DeleteRemoteTagOperation = BaseOperation & { kind: OperationKind.DeleteRemoteTag }; export type DeleteTagOperation = BaseOperation & { kind: OperationKind.DeleteTag }; @@ -138,6 +140,7 @@ export const Operation = { Commit: { kind: OperationKind.Commit, blocking: true, readOnly: false, remote: false, retry: false, showProgress: true } as CommitOperation, Config: { kind: OperationKind.Config, blocking: false, readOnly: false, remote: false, retry: false, showProgress: true } as ConfigOperation, DeleteBranch: { kind: OperationKind.DeleteBranch, blocking: false, readOnly: false, remote: false, retry: false, showProgress: true } as DeleteBranchOperation, + deletePrunedBranches: { kind: OperationKind.deletePrunedBranches, blocking: false, readOnly: false, remote: false, retry: false, showProgress: true } as deletePrunedBranchesOperation, DeleteRef: { kind: OperationKind.DeleteRef, blocking: false, readOnly: false, remote: false, retry: false, showProgress: true } as DeleteRefOperation, DeleteRemoteTag: { kind: OperationKind.DeleteRemoteTag, blocking: false, readOnly: false, remote: true, retry: false, showProgress: true } as DeleteRemoteTagOperation, DeleteTag: { kind: OperationKind.DeleteTag, blocking: false, readOnly: false, remote: false, retry: false, showProgress: true } as DeleteTagOperation, diff --git a/extensions/git/src/repository.ts b/extensions/git/src/repository.ts index ead77390bdedb..6bd85214be151 100644 --- a/extensions/git/src/repository.ts +++ b/extensions/git/src/repository.ts @@ -1363,6 +1363,10 @@ export class Repository implements Disposable { await this.run(Operation.DeleteBranch, () => this.repository.deleteBranch(name, force)); } + async deletePrunedBranches(force?: boolean): Promise { + await this.run(Operation.deletePrunedBranches, () => this.repository.deletePrunedBranches(force)); + } + async renameBranch(name: string): Promise { await this.run(Operation.RenameBranch, () => this.repository.renameBranch(name)); } From 9faae82d1f22fd147df3e87cc75adc5d2485619d Mon Sep 17 00:00:00 2001 From: devjiwonchoi Date: Fri, 9 Jun 2023 03:07:01 +0900 Subject: [PATCH 2/4] feat: add fetch --prune & delete Co-authored-by: Ladislau Szomoru --- extensions/git/package.json | 4 ++-- extensions/git/package.nls.json | 2 +- extensions/git/src/commands.ts | 20 +++----------------- extensions/git/src/git.ts | 31 ++++++++++++++++++++++++++----- extensions/git/src/operation.ts | 8 ++++---- extensions/git/src/repository.ts | 5 +++-- 6 files changed, 39 insertions(+), 31 deletions(-) diff --git a/extensions/git/package.json b/extensions/git/package.json index 823c109621fe5..e0d68cc6c3734 100644 --- a/extensions/git/package.json +++ b/extensions/git/package.json @@ -394,8 +394,8 @@ "enablement": "!operationInProgress" }, { - "command": "git.deletePrunedBranches", - "title": "%command.deletePrunedBranches%", + "command": "git.fetchAndPruneDelete", + "title": "%command.fetchAndPruneDelete%", "category": "Git", "enablement": "!operationInProgress" }, diff --git a/extensions/git/package.nls.json b/extensions/git/package.nls.json index 2bd31cd35ed77..007fecc8eeb07 100644 --- a/extensions/git/package.nls.json +++ b/extensions/git/package.nls.json @@ -56,7 +56,7 @@ "command.branch": "Create Branch...", "command.branchFrom": "Create Branch From...", "command.deleteBranch": "Delete Branch...", - "command.deletePrunedBranches": "Delete Pruned Branches...", + "command.fetchAndPruneDelete": "Fetch (Prune) & Delete...", "command.renameBranch": "Rename Branch...", "command.cherryPick": "Cherry Pick...", "command.merge": "Merge Branch...", diff --git a/extensions/git/src/commands.ts b/extensions/git/src/commands.ts index d3df36e05e23e..28f6dd89ec5d1 100644 --- a/extensions/git/src/commands.ts +++ b/extensions/git/src/commands.ts @@ -2362,23 +2362,9 @@ export class CommandCenter { } } - @command('git.deletePrunedBranches', { repository: true }) - async deletePrunedBranches(repository: Repository, force?: boolean): Promise { - try { - await repository.deletePrunedBranches(force); - } catch (err) { - if (err.gitErrorCode !== GitErrorCodes.BranchNotFullyMerged) { - throw err; - } - - const message = l10n.t('The branch is not fully merged. Delete anyway?'); - const yes = l10n.t('Delete Branch'); - const pick = await window.showWarningMessage(message, { modal: true }, yes); - - if (pick === yes) { - await repository.deletePrunedBranches(true); - } - } + @command('git.fetchAndPruneDelete', { repository: true }) + async fetchAndPruneDelete(repository: Repository): Promise { + repository.fetchAndPruneDelete(); } @command('git.renameBranch', { repository: true }) diff --git a/extensions/git/src/git.ts b/extensions/git/src/git.ts index 50908aada6700..9044f118310c3 100644 --- a/extensions/git/src/git.ts +++ b/extensions/git/src/git.ts @@ -13,7 +13,7 @@ import { EventEmitter } from 'events'; import * as iconv from '@vscode/iconv-lite-umd'; import * as filetype from 'file-type'; import { assign, groupBy, IDisposable, toDisposable, dispose, mkdirp, readBytes, detectUnicodeEncoding, Encoding, onceEvent, splitInChunks, Limiter, Versions, isWindows, pathEquals } from './util'; -import { CancellationError, CancellationToken, ConfigurationChangeEvent, LogOutputChannel, Progress, Uri, workspace } from 'vscode'; +import { CancellationError, CancellationToken, ConfigurationChangeEvent, LogOutputChannel, l10n, Progress, Uri, window, workspace } from 'vscode'; import { detectEncoding } from './encoding'; import { Ref, RefType, Branch, Remote, ForcePushMode, GitErrorCodes, LogOptions, Change, Status, CommitOptions, RefQuery, InitOptions } from './api/git'; import * as byline from 'byline'; @@ -1588,10 +1588,31 @@ export class Repository { await this.exec(args); } - async deletePrunedBranches(force?: boolean): Promise { - force ? - await cp.exec('git branch -D `git branch -vv | grep ": gone]" | awk "{print $1}"`') : - await cp.exec('git branch -d `git branch -vv | grep ": gone]" | awk "{print $1}"`'); + async fetchAndPruneDelete(): Promise { + try { + const fetchPrune = await this.exec(['fetch', '--prune']); + const result = Object.values(fetchPrune).toString().split('\n'); + result.shift(); + result.pop(); + result?.forEach(async (element) => { + const branch = element.split('origin/')[1]; + try { + await this.exec(['branch', '-d', branch]); + } catch (error) { + if (error.gitErrorCode !== GitErrorCodes.BranchNotFullyMerged) { + throw error; + } + const message = l10n.t(`The branch "${branch}" is not fully merged. Delete anyway?`); + const yes = l10n.t('Delete Branch'); + const pick = await window.showWarningMessage(message, { modal: true }, yes); + if (pick === yes) { + await this.exec(['branch', '-D', branch]); + } + } + }); + } catch (error) { + throw error; + } } async renameBranch(name: string): Promise { diff --git a/extensions/git/src/operation.ts b/extensions/git/src/operation.ts index 7c8e03682558c..f3426cff0bfb9 100644 --- a/extensions/git/src/operation.ts +++ b/extensions/git/src/operation.ts @@ -20,7 +20,7 @@ export const enum OperationKind { Commit = 'Commit', Config = 'Config', DeleteBranch = 'DeleteBranch', - deletePrunedBranches = 'deletePrunedBranches', + fetchAndPruneDelete = 'fetchAndPruneDelete', DeleteRef = 'DeleteRef', DeleteRemoteTag = 'DeleteRemoteTag', DeleteTag = 'DeleteTag', @@ -65,7 +65,7 @@ export const enum OperationKind { } export type Operation = AddOperation | ApplyOperation | BlameOperation | BranchOperation | CheckIgnoreOperation | CherryPickOperation | - CheckoutOperation | CheckoutTrackingOperation | CleanOperation | CommitOperation | ConfigOperation | DeleteBranchOperation | deletePrunedBranchesOperation | + CheckoutOperation | CheckoutTrackingOperation | CleanOperation | CommitOperation | ConfigOperation | DeleteBranchOperation | fetchAndPruneDeleteOperation | DeleteRefOperation | DeleteRemoteTagOperation | DeleteTagOperation | DiffOperation | FetchOperation | FindTrackingBranchesOperation | GetBranchOperation | GetBranchesOperation | GetCommitTemplateOperation | GetObjectDetailsOperation | GetRefsOperation | GetRemoteRefsOperation | HashObjectOperation | IgnoreOperation | LogOperation | LogFileOperation | MergeOperation | MergeAbortOperation | MergeBaseOperation | @@ -86,7 +86,7 @@ export type CleanOperation = BaseOperation & { kind: OperationKind.Clean }; export type CommitOperation = BaseOperation & { kind: OperationKind.Commit }; export type ConfigOperation = BaseOperation & { kind: OperationKind.Config }; export type DeleteBranchOperation = BaseOperation & { kind: OperationKind.DeleteBranch }; -export type deletePrunedBranchesOperation = BaseOperation & { kind: OperationKind.deletePrunedBranches }; +export type fetchAndPruneDeleteOperation = BaseOperation & { kind: OperationKind.fetchAndPruneDelete }; export type DeleteRefOperation = BaseOperation & { kind: OperationKind.DeleteRef }; export type DeleteRemoteTagOperation = BaseOperation & { kind: OperationKind.DeleteRemoteTag }; export type DeleteTagOperation = BaseOperation & { kind: OperationKind.DeleteTag }; @@ -140,7 +140,7 @@ export const Operation = { Commit: { kind: OperationKind.Commit, blocking: true, readOnly: false, remote: false, retry: false, showProgress: true } as CommitOperation, Config: { kind: OperationKind.Config, blocking: false, readOnly: false, remote: false, retry: false, showProgress: true } as ConfigOperation, DeleteBranch: { kind: OperationKind.DeleteBranch, blocking: false, readOnly: false, remote: false, retry: false, showProgress: true } as DeleteBranchOperation, - deletePrunedBranches: { kind: OperationKind.deletePrunedBranches, blocking: false, readOnly: false, remote: false, retry: false, showProgress: true } as deletePrunedBranchesOperation, + fetchAndPruneDelete: { kind: OperationKind.fetchAndPruneDelete, blocking: false, readOnly: false, remote: false, retry: false, showProgress: true } as fetchAndPruneDeleteOperation, DeleteRef: { kind: OperationKind.DeleteRef, blocking: false, readOnly: false, remote: false, retry: false, showProgress: true } as DeleteRefOperation, DeleteRemoteTag: { kind: OperationKind.DeleteRemoteTag, blocking: false, readOnly: false, remote: true, retry: false, showProgress: true } as DeleteRemoteTagOperation, DeleteTag: { kind: OperationKind.DeleteTag, blocking: false, readOnly: false, remote: false, retry: false, showProgress: true } as DeleteTagOperation, diff --git a/extensions/git/src/repository.ts b/extensions/git/src/repository.ts index 6bd85214be151..5dc9e95f2ab3d 100644 --- a/extensions/git/src/repository.ts +++ b/extensions/git/src/repository.ts @@ -1363,8 +1363,9 @@ export class Repository implements Disposable { await this.run(Operation.DeleteBranch, () => this.repository.deleteBranch(name, force)); } - async deletePrunedBranches(force?: boolean): Promise { - await this.run(Operation.deletePrunedBranches, () => this.repository.deletePrunedBranches(force)); + async fetchAndPruneDelete(): Promise { + // await this.run(Operation.fetchAndPruneDelete, () => this.repository.fetchAndPruneDelete(force)); + await this.run(Operation.fetchAndPruneDelete, () => this.repository.fetchAndPruneDelete()); } async renameBranch(name: string): Promise { From d12209d188e987fe8096cc9a2fa1f13b3e255d82 Mon Sep 17 00:00:00 2001 From: devjiwonchoi Date: Fri, 9 Jun 2023 03:11:33 +0900 Subject: [PATCH 3/4] chore: rename function as fetchPruneAndDelete Co-authored-by: Ladislau Szomoru --- extensions/git/package.json | 4 ++-- extensions/git/package.nls.json | 2 +- extensions/git/src/commands.ts | 6 +++--- extensions/git/src/git.ts | 2 +- extensions/git/src/operation.ts | 8 ++++---- extensions/git/src/repository.ts | 6 +++--- 6 files changed, 14 insertions(+), 14 deletions(-) diff --git a/extensions/git/package.json b/extensions/git/package.json index e0d68cc6c3734..71790da3c63b3 100644 --- a/extensions/git/package.json +++ b/extensions/git/package.json @@ -394,8 +394,8 @@ "enablement": "!operationInProgress" }, { - "command": "git.fetchAndPruneDelete", - "title": "%command.fetchAndPruneDelete%", + "command": "git.fetchPruneAndDelete", + "title": "%command.fetchPruneAndDelete%", "category": "Git", "enablement": "!operationInProgress" }, diff --git a/extensions/git/package.nls.json b/extensions/git/package.nls.json index 007fecc8eeb07..a2a9a077be345 100644 --- a/extensions/git/package.nls.json +++ b/extensions/git/package.nls.json @@ -56,7 +56,7 @@ "command.branch": "Create Branch...", "command.branchFrom": "Create Branch From...", "command.deleteBranch": "Delete Branch...", - "command.fetchAndPruneDelete": "Fetch (Prune) & Delete...", + "command.fetchPruneAndDelete": "Fetch (Prune) & Delete...", "command.renameBranch": "Rename Branch...", "command.cherryPick": "Cherry Pick...", "command.merge": "Merge Branch...", diff --git a/extensions/git/src/commands.ts b/extensions/git/src/commands.ts index 28f6dd89ec5d1..80d17f0743457 100644 --- a/extensions/git/src/commands.ts +++ b/extensions/git/src/commands.ts @@ -2362,9 +2362,9 @@ export class CommandCenter { } } - @command('git.fetchAndPruneDelete', { repository: true }) - async fetchAndPruneDelete(repository: Repository): Promise { - repository.fetchAndPruneDelete(); + @command('git.fetchPruneAndDelete', { repository: true }) + async fetchPruneAndDelete(repository: Repository): Promise { + repository.fetchPruneAndDelete(); } @command('git.renameBranch', { repository: true }) diff --git a/extensions/git/src/git.ts b/extensions/git/src/git.ts index 9044f118310c3..f3b074f091870 100644 --- a/extensions/git/src/git.ts +++ b/extensions/git/src/git.ts @@ -1588,7 +1588,7 @@ export class Repository { await this.exec(args); } - async fetchAndPruneDelete(): Promise { + async fetchPruneAndDelete(): Promise { try { const fetchPrune = await this.exec(['fetch', '--prune']); const result = Object.values(fetchPrune).toString().split('\n'); diff --git a/extensions/git/src/operation.ts b/extensions/git/src/operation.ts index f3426cff0bfb9..06f29a54b3e08 100644 --- a/extensions/git/src/operation.ts +++ b/extensions/git/src/operation.ts @@ -20,7 +20,7 @@ export const enum OperationKind { Commit = 'Commit', Config = 'Config', DeleteBranch = 'DeleteBranch', - fetchAndPruneDelete = 'fetchAndPruneDelete', + fetchPruneAndDelete = 'fetchPruneAndDelete', DeleteRef = 'DeleteRef', DeleteRemoteTag = 'DeleteRemoteTag', DeleteTag = 'DeleteTag', @@ -65,7 +65,7 @@ export const enum OperationKind { } export type Operation = AddOperation | ApplyOperation | BlameOperation | BranchOperation | CheckIgnoreOperation | CherryPickOperation | - CheckoutOperation | CheckoutTrackingOperation | CleanOperation | CommitOperation | ConfigOperation | DeleteBranchOperation | fetchAndPruneDeleteOperation | + CheckoutOperation | CheckoutTrackingOperation | CleanOperation | CommitOperation | ConfigOperation | DeleteBranchOperation | fetchPruneAndDeleteOperation | DeleteRefOperation | DeleteRemoteTagOperation | DeleteTagOperation | DiffOperation | FetchOperation | FindTrackingBranchesOperation | GetBranchOperation | GetBranchesOperation | GetCommitTemplateOperation | GetObjectDetailsOperation | GetRefsOperation | GetRemoteRefsOperation | HashObjectOperation | IgnoreOperation | LogOperation | LogFileOperation | MergeOperation | MergeAbortOperation | MergeBaseOperation | @@ -86,7 +86,7 @@ export type CleanOperation = BaseOperation & { kind: OperationKind.Clean }; export type CommitOperation = BaseOperation & { kind: OperationKind.Commit }; export type ConfigOperation = BaseOperation & { kind: OperationKind.Config }; export type DeleteBranchOperation = BaseOperation & { kind: OperationKind.DeleteBranch }; -export type fetchAndPruneDeleteOperation = BaseOperation & { kind: OperationKind.fetchAndPruneDelete }; +export type fetchPruneAndDeleteOperation = BaseOperation & { kind: OperationKind.fetchPruneAndDelete }; export type DeleteRefOperation = BaseOperation & { kind: OperationKind.DeleteRef }; export type DeleteRemoteTagOperation = BaseOperation & { kind: OperationKind.DeleteRemoteTag }; export type DeleteTagOperation = BaseOperation & { kind: OperationKind.DeleteTag }; @@ -140,7 +140,7 @@ export const Operation = { Commit: { kind: OperationKind.Commit, blocking: true, readOnly: false, remote: false, retry: false, showProgress: true } as CommitOperation, Config: { kind: OperationKind.Config, blocking: false, readOnly: false, remote: false, retry: false, showProgress: true } as ConfigOperation, DeleteBranch: { kind: OperationKind.DeleteBranch, blocking: false, readOnly: false, remote: false, retry: false, showProgress: true } as DeleteBranchOperation, - fetchAndPruneDelete: { kind: OperationKind.fetchAndPruneDelete, blocking: false, readOnly: false, remote: false, retry: false, showProgress: true } as fetchAndPruneDeleteOperation, + fetchPruneAndDelete: { kind: OperationKind.fetchPruneAndDelete, blocking: false, readOnly: false, remote: false, retry: false, showProgress: true } as fetchPruneAndDeleteOperation, DeleteRef: { kind: OperationKind.DeleteRef, blocking: false, readOnly: false, remote: false, retry: false, showProgress: true } as DeleteRefOperation, DeleteRemoteTag: { kind: OperationKind.DeleteRemoteTag, blocking: false, readOnly: false, remote: true, retry: false, showProgress: true } as DeleteRemoteTagOperation, DeleteTag: { kind: OperationKind.DeleteTag, blocking: false, readOnly: false, remote: false, retry: false, showProgress: true } as DeleteTagOperation, diff --git a/extensions/git/src/repository.ts b/extensions/git/src/repository.ts index 5dc9e95f2ab3d..cd7d8f9ea93ac 100644 --- a/extensions/git/src/repository.ts +++ b/extensions/git/src/repository.ts @@ -1363,9 +1363,9 @@ export class Repository implements Disposable { await this.run(Operation.DeleteBranch, () => this.repository.deleteBranch(name, force)); } - async fetchAndPruneDelete(): Promise { - // await this.run(Operation.fetchAndPruneDelete, () => this.repository.fetchAndPruneDelete(force)); - await this.run(Operation.fetchAndPruneDelete, () => this.repository.fetchAndPruneDelete()); + async fetchPruneAndDelete(): Promise { + // await this.run(Operation.fetchPruneAndDelete, () => this.repository.fetchPruneAndDelete(force)); + await this.run(Operation.fetchPruneAndDelete, () => this.repository.fetchPruneAndDelete()); } async renameBranch(name: string): Promise { From 44ed3fba55ce8c304743c85f66072e2696fe8408 Mon Sep 17 00:00:00 2001 From: devjiwonchoi Date: Wed, 13 Mar 2024 20:42:06 +0900 Subject: [PATCH 4/4] refactor: error handling and operations --- extensions/git/src/commands.ts | 6 ++- extensions/git/src/git.ts | 77 ++++++++++++++++++++++---------- extensions/git/src/operation.ts | 8 ++-- extensions/git/src/repository.ts | 3 +- 4 files changed, 64 insertions(+), 30 deletions(-) diff --git a/extensions/git/src/commands.ts b/extensions/git/src/commands.ts index ffb065f5d98a2..8a6d224993963 100644 --- a/extensions/git/src/commands.ts +++ b/extensions/git/src/commands.ts @@ -2792,7 +2792,11 @@ export class CommandCenter { @command('git.fetchPruneAndDelete', { repository: true }) async fetchPruneAndDelete(repository: Repository): Promise { - repository.fetchPruneAndDelete(); + try { + repository.fetchPruneAndDelete(); + } catch (error) { + throw error; + } } @command('git.renameBranch', { repository: true }) diff --git a/extensions/git/src/git.ts b/extensions/git/src/git.ts index d8db69406b010..f221f1e3b5980 100644 --- a/extensions/git/src/git.ts +++ b/extensions/git/src/git.ts @@ -13,7 +13,7 @@ import { EventEmitter } from 'events'; import * as iconv from '@vscode/iconv-lite-umd'; import * as filetype from 'file-type'; import { assign, groupBy, IDisposable, toDisposable, dispose, mkdirp, readBytes, detectUnicodeEncoding, Encoding, onceEvent, splitInChunks, Limiter, Versions, isWindows, pathEquals, isMacintosh, isDescendant } from './util'; -import { CancellationError, CancellationToken, ConfigurationChangeEvent, LogOutputChannel, l10n, Progress, Uri, workspace } from 'vscode'; +import { CancellationError, CancellationToken, ConfigurationChangeEvent, LogOutputChannel, l10n, Progress, Uri, workspace, window } from 'vscode'; import { detectEncoding } from './encoding'; import { Ref, RefType, Branch, Remote, ForcePushMode, GitErrorCodes, LogOptions, Change, Status, CommitOptions, RefQuery, InitOptions } from './api/git'; import * as byline from 'byline'; @@ -1731,29 +1731,60 @@ export class Repository { } async fetchPruneAndDelete(): Promise { - try { - const fetchPrune = await this.exec(['fetch', '--prune']); - const result = Object.values(fetchPrune).toString().split('\n'); - result.shift(); - result.pop(); - result?.forEach(async (element) => { - const branch = element.split('origin/')[1]; - try { - await this.exec(['branch', '-d', branch]); - } catch (error) { - if (error.gitErrorCode !== GitErrorCodes.BranchNotFullyMerged) { - throw error; - } - const message = l10n.t(`The branch "${branch}" is not fully merged. Delete anyway?`); - const yes = l10n.t('Delete Branch'); - const pick = await window.showWarningMessage(message, { modal: true }, yes); - if (pick === yes) { - await this.exec(['branch', '-D', branch]); - } - } + const fetchPruneOutput = await this.exec(['fetch', '--prune']); + const { exitCode, stderr } = fetchPruneOutput; + + if (exitCode) { + throw new GitError({ + message: 'Could not fetch.', + exitCode + }); + } + + // The outputs of the fetch prune is at stderr + if (!stderr) { + throw new GitError({ + message: 'Could not find pruned branches to delete.', }); - } catch (error) { - throw error; + } + + const branchRegex = /->\s*(\S+)/; + // [ + // 'From github.com:username/some-repo', + // ' - [deleted] (none) -> origin/foo', + // ' - [deleted] (none) -> origin/bar', + // '' + // ] + const branches: string[] = stderr.split('\n') + // [ + // 'origin/foo', + // 'origin/bar', + // ] + // TODO: origin is hardcoded + .map((el) => el.match(branchRegex)?.[1]?.replace('origin/', '') ?? '') + // The origin branch name to run `git branch -d ` + // [ + // 'foo', + // 'bar', + // ] + .filter(Boolean); + + for (const branch of branches) { + try { + await this.deleteBranch(branch); + } catch (error) { + if (error.gitErrorCode !== GitErrorCodes.BranchNotFullyMerged) { + throw error; + } + + const message = l10n.t(`The branch "${branch}" is not fully merged. Delete anyway?`); + const yes = l10n.t('Delete Branch'); + + const pick = await window.showWarningMessage(message, { modal: true }, yes); + if (pick === yes) { + await this.deleteBranch(branch, true); + } + } } } diff --git a/extensions/git/src/operation.ts b/extensions/git/src/operation.ts index f5c8b2a59f890..ba401558fad0b 100644 --- a/extensions/git/src/operation.ts +++ b/extensions/git/src/operation.ts @@ -18,7 +18,7 @@ export const enum OperationKind { Commit = 'Commit', Config = 'Config', DeleteBranch = 'DeleteBranch', - fetchPruneAndDelete = 'fetchPruneAndDelete', + FetchPruneAndDelete = 'FetchPruneAndDelete', DeleteRef = 'DeleteRef', DeleteRemoteTag = 'DeleteRemoteTag', DeleteTag = 'DeleteTag', @@ -65,7 +65,7 @@ export const enum OperationKind { } export type Operation = AddOperation | ApplyOperation | BlameOperation | BranchOperation | CheckIgnoreOperation | CherryPickOperation | - CheckoutOperation | CheckoutTrackingOperation | CleanOperation | CommitOperation | ConfigOperation | DeleteBranchOperation | fetchPruneAndDeleteOperation | + CheckoutOperation | CheckoutTrackingOperation | CleanOperation | CommitOperation | ConfigOperation | DeleteBranchOperation | FetchPruneAndDeleteOperation | DeleteRefOperation | DeleteRemoteTagOperation | DeleteTagOperation | DiffOperation | FetchOperation | FindTrackingBranchesOperation | GetBranchOperation | GetBranchesOperation | GetCommitTemplateOperation | GetObjectDetailsOperation | GetObjectFilesOperation | GetRefsOperation | GetRemoteRefsOperation | HashObjectOperation | IgnoreOperation | LogOperation | LogFileOperation | MergeOperation | MergeAbortOperation | @@ -87,7 +87,7 @@ export type CleanOperation = BaseOperation & { kind: OperationKind.Clean }; export type CommitOperation = BaseOperation & { kind: OperationKind.Commit }; export type ConfigOperation = BaseOperation & { kind: OperationKind.Config }; export type DeleteBranchOperation = BaseOperation & { kind: OperationKind.DeleteBranch }; -export type fetchPruneAndDeleteOperation = BaseOperation & { kind: OperationKind.fetchPruneAndDelete }; +export type FetchPruneAndDeleteOperation = BaseOperation & { kind: OperationKind.FetchPruneAndDelete }; export type DeleteRefOperation = BaseOperation & { kind: OperationKind.DeleteRef }; export type DeleteRemoteTagOperation = BaseOperation & { kind: OperationKind.DeleteRemoteTag }; export type DeleteTagOperation = BaseOperation & { kind: OperationKind.DeleteTag }; @@ -145,7 +145,7 @@ export const Operation = { Commit: { kind: OperationKind.Commit, blocking: true, readOnly: false, remote: false, retry: false, showProgress: true } as CommitOperation, Config: (readOnly: boolean) => ({ kind: OperationKind.Config, blocking: false, readOnly, remote: false, retry: false, showProgress: true } as ConfigOperation), DeleteBranch: { kind: OperationKind.DeleteBranch, blocking: false, readOnly: false, remote: false, retry: false, showProgress: true } as DeleteBranchOperation, - fetchPruneAndDelete: { kind: OperationKind.fetchPruneAndDelete, blocking: false, readOnly: false, remote: false, retry: false, showProgress: true } as fetchPruneAndDeleteOperation, + FetchPruneAndDelete: { kind: OperationKind.FetchPruneAndDelete, blocking: false, readOnly: false, remote: false, retry: false, showProgress: true } as FetchPruneAndDeleteOperation, DeleteRef: { kind: OperationKind.DeleteRef, blocking: false, readOnly: false, remote: false, retry: false, showProgress: true } as DeleteRefOperation, DeleteRemoteTag: { kind: OperationKind.DeleteRemoteTag, blocking: false, readOnly: false, remote: true, retry: false, showProgress: true } as DeleteRemoteTagOperation, DeleteTag: { kind: OperationKind.DeleteTag, blocking: false, readOnly: false, remote: false, retry: false, showProgress: true } as DeleteTagOperation, diff --git a/extensions/git/src/repository.ts b/extensions/git/src/repository.ts index 4bd26b785398f..ea1c72a2fd01c 100644 --- a/extensions/git/src/repository.ts +++ b/extensions/git/src/repository.ts @@ -1386,8 +1386,7 @@ export class Repository implements Disposable { } async fetchPruneAndDelete(): Promise { - // await this.run(Operation.fetchPruneAndDelete, () => this.repository.fetchPruneAndDelete(force)); - await this.run(Operation.fetchPruneAndDelete, () => this.repository.fetchPruneAndDelete()); + await this.run(Operation.FetchPruneAndDelete, () => this.repository.fetchPruneAndDelete()); } async renameBranch(name: string): Promise {