Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: add fetch prune & delete command #205031

Open
wants to merge 14 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions extensions/git/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -455,6 +455,12 @@
"category": "Git",
"enablement": "!operationInProgress"
},
{
"command": "git.fetchPruneAndDelete",
"title": "%command.fetchPruneAndDelete%",
"category": "Git",
"enablement": "!operationInProgress"
},
{
"command": "git.renameBranch",
"title": "%command.renameBranch%",
Expand Down
1 change: 1 addition & 0 deletions extensions/git/package.nls.json
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,7 @@
"command.branch": "Create Branch...",
"command.branchFrom": "Create Branch From...",
"command.deleteBranch": "Delete Branch...",
"command.fetchPruneAndDelete": "Fetch (Prune) & Delete...",
"command.renameBranch": "Rename Branch...",
"command.cherryPick": "Cherry Pick...",
"command.merge": "Merge...",
Expand Down
9 changes: 9 additions & 0 deletions extensions/git/src/commands.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2790,6 +2790,15 @@ export class CommandCenter {
}
}

@command('git.fetchPruneAndDelete', { repository: true })
async fetchPruneAndDelete(repository: Repository): Promise<void> {
try {
repository.fetchPruneAndDelete();
} catch (error) {
throw error;
}
}

@command('git.renameBranch', { repository: true })
async renameBranch(repository: Repository): Promise<void> {
const currentBranchName = repository.HEAD && repository.HEAD.name;
Expand Down
60 changes: 59 additions & 1 deletion extensions/git/src/git.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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, 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';
Expand Down Expand Up @@ -1730,6 +1730,64 @@ export class Repository {
await this.exec(args);
}

async fetchPruneAndDelete(): Promise<void> {
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.',
});
}

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/', '') ?? '')
Comment on lines +1763 to +1764
Copy link
Author

@devjiwonchoi devjiwonchoi Mar 13, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
// TODO: origin is hardcoded
.map((el) => el.match(branchRegex)?.[1]?.replace('origin/', '') ?? '')
.map((el) => el.match(branchRegex)?.[1]?.replace('origin/', '') ?? '')

I don't think there will be edge cases for remote origin, or?

// The origin branch name to run `git branch -d <name>`
// [
// '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);
}
}
}
}

async renameBranch(name: string): Promise<void> {
const args = ['branch', '-m', name];
await this.exec(args);
Expand Down
5 changes: 4 additions & 1 deletion extensions/git/src/operation.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ export const enum OperationKind {
Commit = 'Commit',
Config = 'Config',
DeleteBranch = 'DeleteBranch',
FetchPruneAndDelete = 'FetchPruneAndDelete',
DeleteRef = 'DeleteRef',
DeleteRemoteTag = 'DeleteRemoteTag',
DeleteTag = 'DeleteTag',
Expand Down Expand Up @@ -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 | FetchPruneAndDeleteOperation |
DeleteRefOperation | DeleteRemoteTagOperation | DeleteTagOperation | DiffOperation | FetchOperation | FindTrackingBranchesOperation |
GetBranchOperation | GetBranchesOperation | GetCommitTemplateOperation | GetObjectDetailsOperation | GetObjectFilesOperation | GetRefsOperation |
GetRemoteRefsOperation | HashObjectOperation | IgnoreOperation | LogOperation | LogFileOperation | MergeOperation | MergeAbortOperation |
Expand All @@ -86,6 +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 DeleteRefOperation = BaseOperation & { kind: OperationKind.DeleteRef };
export type DeleteRemoteTagOperation = BaseOperation & { kind: OperationKind.DeleteRemoteTag };
export type DeleteTagOperation = BaseOperation & { kind: OperationKind.DeleteTag };
Expand Down Expand Up @@ -143,6 +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,
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,
Expand Down
4 changes: 4 additions & 0 deletions extensions/git/src/repository.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1385,6 +1385,10 @@ export class Repository implements Disposable {
await this.run(Operation.DeleteBranch, () => this.repository.deleteBranch(name, force));
}

async fetchPruneAndDelete(): Promise<void> {
await this.run(Operation.FetchPruneAndDelete, () => this.repository.fetchPruneAndDelete());
}

async renameBranch(name: string): Promise<void> {
await this.run(Operation.RenameBranch, () => this.repository.renameBranch(name));
}
Expand Down