Skip to content

Commit 4c79fc8

Browse files
committed
change git tag terminal-run cmds into normal cmds
1 parent b7ab46c commit 4c79fc8

File tree

7 files changed

+157
-30
lines changed

7 files changed

+157
-30
lines changed

src/commands/git/tag.ts

Lines changed: 17 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -8,9 +8,11 @@ import {
88
isTagReference,
99
} from '../../git/models/reference';
1010
import type { Repository } from '../../git/models/repository';
11+
import { showGenericErrorMessage } from '../../messages';
1112
import type { QuickPickItemOfT } from '../../quickpicks/items/common';
1213
import type { FlagsQuickPickItem } from '../../quickpicks/items/flags';
1314
import { createFlagsQuickPickItem } from '../../quickpicks/items/flags';
15+
import { Logger } from '../../system/logger';
1416
import { pluralize } from '../../system/string';
1517
import type { ViewsWithRepositoryFolders } from '../../views/viewBase';
1618
import type {
@@ -293,12 +295,12 @@ export class TagGitCommand extends QuickCommand<State> {
293295
}
294296

295297
endSteps(state);
296-
state.repo.tag(
297-
...state.flags,
298-
...(state.message.length !== 0 ? [`"${state.message}"`] : []),
299-
state.name,
300-
state.reference.ref,
301-
);
298+
try {
299+
await state.repo.git.createTag(state.name, state.reference.ref, state.message);
300+
} catch (ex) {
301+
Logger.error(ex, context.title);
302+
void showGenericErrorMessage(ex);
303+
}
302304
}
303305
}
304306

@@ -356,7 +358,7 @@ export class TagGitCommand extends QuickCommand<State> {
356358
return canPickStepContinue(step, state, selection) ? selection[0].item : StepResultBreak;
357359
}
358360

359-
private *deleteCommandSteps(state: DeleteStepState, context: Context): StepGenerator {
361+
private async *deleteCommandSteps(state: DeleteStepState, context: Context): StepGenerator {
360362
while (this.canStepsContinue(state)) {
361363
if (state.references != null && !Array.isArray(state.references)) {
362364
state.references = [state.references];
@@ -381,7 +383,14 @@ export class TagGitCommand extends QuickCommand<State> {
381383
if (result === StepResultBreak) continue;
382384

383385
endSteps(state);
384-
state.repo.tagDelete(state.references);
386+
for (const { ref } of state.references) {
387+
try {
388+
await state.repo.git.deleteTag(ref);
389+
} catch (ex) {
390+
Logger.error(ex, context.title);
391+
void showGenericErrorMessage(ex);
392+
}
393+
}
385394
}
386395
}
387396

src/env/node/git/git.ts

Lines changed: 27 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,8 @@ import {
2222
PushErrorReason,
2323
StashPushError,
2424
StashPushErrorReason,
25+
TagError,
26+
TagErrorReason,
2527
WorkspaceUntrustedError,
2628
} from '../../../git/errors';
2729
import type { GitDir } from '../../../git/gitProvider';
@@ -32,7 +34,6 @@ import type { GitUser } from '../../../git/models/user';
3234
import { parseGitBranchesDefaultFormat } from '../../../git/parsers/branchParser';
3335
import { parseGitLogAllFormat, parseGitLogDefaultFormat } from '../../../git/parsers/logParser';
3436
import { parseGitRemoteUrl } from '../../../git/parsers/remoteParser';
35-
import { parseGitTagsDefaultFormat } from '../../../git/parsers/tagParser';
3637
import { splitAt } from '../../../system/array';
3738
import { log } from '../../../system/decorators/log';
3839
import { join } from '../../../system/iterable';
@@ -99,6 +100,10 @@ export const GitErrors = {
99100
tagConflict: /! \[rejected\].*\(would clobber existing tag\)/m,
100101
unmergedFiles: /is not possible because you have unmerged files/i,
101102
unstagedChanges: /You have unstaged changes/i,
103+
tagAlreadyExists: /tag .* already exists/i,
104+
tagNotFound: /tag .* not found/i,
105+
invalidTagName: /invalid tag name/i,
106+
remoteRejected: /rejected because the remote contains work/i,
102107
};
103108

104109
const GitWarnings = {
@@ -159,6 +164,14 @@ function getStdinUniqueKey(): number {
159164
type ExitCodeOnlyGitCommandOptions = GitCommandOptions & { exitCodeOnly: true };
160165
export type PushForceOptions = { withLease: true; ifIncludes?: boolean } | { withLease: false; ifIncludes?: never };
161166

167+
const tagErrorAndReason = [
168+
[GitErrors.tagAlreadyExists, TagErrorReason.TagAlreadyExists],
169+
[GitErrors.tagNotFound, TagErrorReason.TagNotFound],
170+
[GitErrors.invalidTagName, TagErrorReason.InvalidTagName],
171+
[GitErrors.permissionDenied, TagErrorReason.PermissionDenied],
172+
[GitErrors.remoteRejected, TagErrorReason.RemoteRejected],
173+
];
174+
162175
export class Git {
163176
/** Map of running git commands -- avoids running duplicate overlaping commands */
164177
private readonly pendingCommands = new Map<string, Promise<string | Buffer>>();
@@ -2116,8 +2129,19 @@ export class Git {
21162129
return this.git<string>({ cwd: repoPath }, 'symbolic-ref', '--short', ref);
21172130
}
21182131

2119-
tag(repoPath: string) {
2120-
return this.git<string>({ cwd: repoPath }, 'tag', '-l', `--format=${parseGitTagsDefaultFormat}`);
2132+
async tag(repoPath: string, ...args: string[]) {
2133+
try {
2134+
const output = await this.git<string>({ cwd: repoPath }, 'tag', ...args);
2135+
return output;
2136+
} catch (ex) {
2137+
const msg: string = ex?.toString() ?? '';
2138+
for (const [error, reason] of tagErrorAndReason) {
2139+
if (error.test(msg) || error.test(ex.stderr ?? '')) {
2140+
throw new TagError(reason, ex);
2141+
}
2142+
}
2143+
throw new TagError(TagErrorReason.Other, ex);
2144+
}
21212145
}
21222146

21232147
worktree__add(

src/env/node/git/localGitProvider.ts

Lines changed: 29 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ import {
3333
StashApplyError,
3434
StashApplyErrorReason,
3535
StashPushError,
36+
TagError,
3637
WorktreeCreateError,
3738
WorktreeCreateErrorReason,
3839
WorktreeDeleteError,
@@ -158,7 +159,7 @@ import {
158159
import { parseGitRefLog, parseGitRefLogDefaultFormat } from '../../../git/parsers/reflogParser';
159160
import { parseGitRemotes } from '../../../git/parsers/remoteParser';
160161
import { parseGitStatus } from '../../../git/parsers/statusParser';
161-
import { parseGitTags } from '../../../git/parsers/tagParser';
162+
import { parseGitTags, parseGitTagsDefaultFormat } from '../../../git/parsers/tagParser';
162163
import { parseGitLsFiles, parseGitTree } from '../../../git/parsers/treeParser';
163164
import { parseGitWorktrees } from '../../../git/parsers/worktreeParser';
164165
import { getRemoteProviderMatcher, loadRemoteProviders } from '../../../git/remotes/remoteProviders';
@@ -1241,6 +1242,32 @@ export class LocalGitProvider implements GitProvider, Disposable {
12411242
await this.git.branch(repoPath, '-m', oldName, newName);
12421243
}
12431244

1245+
@log()
1246+
async createTag(repoPath: string, name: string, ref: string, message?: string): Promise<void> {
1247+
try {
1248+
await this.git.tag(repoPath, name, ref, ...(message?.length !== 0 ? ['-m', message] : []));
1249+
} catch (ex) {
1250+
if (ex instanceof TagError) {
1251+
throw ex.WithTag(name);
1252+
}
1253+
1254+
throw ex;
1255+
}
1256+
}
1257+
1258+
@log()
1259+
async deleteTag(repoPath: string, name: string): Promise<void> {
1260+
try {
1261+
await this.git.tag(repoPath, '-d', name);
1262+
} catch (ex) {
1263+
if (ex instanceof TagError) {
1264+
throw ex.WithTag(name);
1265+
}
1266+
1267+
throw ex;
1268+
}
1269+
}
1270+
12441271
@log()
12451272
async checkout(
12461273
repoPath: string,
@@ -5093,7 +5120,7 @@ export class LocalGitProvider implements GitProvider, Disposable {
50935120
if (resultsPromise == null) {
50945121
async function load(this: LocalGitProvider): Promise<PagedResult<GitTag>> {
50955122
try {
5096-
const data = await this.git.tag(repoPath!);
5123+
const data = await this.git.tag(repoPath!, '-l', `--format=${parseGitTagsDefaultFormat}`);
50975124
return { values: parseGitTags(data, repoPath!) };
50985125
} catch (_ex) {
50995126
this._tagsCache.delete(repoPath!);

src/git/errors.ts

Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -488,3 +488,68 @@ export class WorktreeDeleteError extends Error {
488488
Error.captureStackTrace?.(this, WorktreeDeleteError);
489489
}
490490
}
491+
492+
export const enum TagErrorReason {
493+
TagAlreadyExists,
494+
TagNotFound,
495+
InvalidTagName,
496+
PermissionDenied,
497+
RemoteRejected,
498+
Other,
499+
}
500+
501+
export class TagError extends Error {
502+
static is(ex: unknown, reason?: TagErrorReason): ex is TagError {
503+
return ex instanceof TagError && (reason == null || ex.reason === reason);
504+
}
505+
506+
readonly original?: Error;
507+
readonly reason: TagErrorReason | undefined;
508+
readonly tag?: string;
509+
510+
private static buildTagErrorMessage(reason?: TagErrorReason, tag?: string, remote?: string): string {
511+
const baseMessage = `Unable to perform action${
512+
tag ? ` with tag '${tag}'${remote ? ` on ${remote}` : ''}` : 'on tag'
513+
}`;
514+
switch (reason) {
515+
case TagErrorReason.TagAlreadyExists:
516+
return `${baseMessage} because it already exists`;
517+
case TagErrorReason.TagNotFound:
518+
return `${baseMessage} because it does not exist`;
519+
case TagErrorReason.InvalidTagName:
520+
return `${baseMessage} because the tag name is invalid`;
521+
case TagErrorReason.PermissionDenied:
522+
return `${baseMessage} because you don't have permission to push to this remote repository.`;
523+
case TagErrorReason.RemoteRejected:
524+
return `${baseMessage} because the remote repository rejected the push.`;
525+
default:
526+
return baseMessage;
527+
}
528+
}
529+
530+
constructor(reason?: TagErrorReason, original?: Error, tag?: string, remote?: string);
531+
constructor(message?: string, original?: Error);
532+
constructor(messageOrReason: string | TagErrorReason | undefined, original?: Error, tag?: string, remote?: string) {
533+
let reason: TagErrorReason | undefined;
534+
if (typeof messageOrReason !== 'string') {
535+
reason = messageOrReason as TagErrorReason;
536+
} else {
537+
super(messageOrReason);
538+
}
539+
const message =
540+
typeof messageOrReason === 'string'
541+
? messageOrReason
542+
: TagError.buildTagErrorMessage(messageOrReason as TagErrorReason, tag, remote);
543+
super(message);
544+
545+
this.original = original;
546+
this.reason = reason;
547+
this.tag = tag;
548+
Error.captureStackTrace?.(this, TagError);
549+
}
550+
551+
WithTag(tag: string) {
552+
this.message = TagError.buildTagErrorMessage(this.reason, tag);
553+
return this;
554+
}
555+
}

src/git/gitProvider.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -115,7 +115,8 @@ export interface RepositoryVisibilityInfo {
115115
export interface GitProviderRepository {
116116
createBranch?(repoPath: string, name: string, ref: string): Promise<void>;
117117
renameBranch?(repoPath: string, oldName: string, newName: string): Promise<void>;
118-
118+
createTag?(repoPath: string, name: string, ref: string, message?: string): Promise<void>;
119+
deleteTag?(repoPath: string, name: string): Promise<void>;
119120
addRemote?(repoPath: string, name: string, url: string, options?: { fetch?: boolean }): Promise<void>;
120121
pruneRemote?(repoPath: string, name: string): Promise<void>;
121122
removeRemote?(repoPath: string, name: string): Promise<void>;

src/git/gitProviderService.ts

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1381,6 +1381,22 @@ export class GitProviderService implements Disposable {
13811381
return provider.renameBranch(path, oldName, newName);
13821382
}
13831383

1384+
@log()
1385+
createTag(repoPath: string | Uri, name: string, ref: string, message?: string): Promise<void> {
1386+
const { provider, path } = this.getProvider(repoPath);
1387+
if (provider.createTag == null) throw new ProviderNotSupportedError(provider.descriptor.name);
1388+
1389+
return provider.createTag(path, name, ref, message);
1390+
}
1391+
1392+
@log()
1393+
deleteTag(repoPath: string | Uri, name: string): Promise<void> {
1394+
const { provider, path } = this.getProvider(repoPath);
1395+
if (provider.deleteTag == null) throw new ProviderNotSupportedError(provider.descriptor.name);
1396+
1397+
return provider.deleteTag(path, name);
1398+
}
1399+
13841400
@log()
13851401
checkout(
13861402
repoPath: string | Uri,

src/git/models/repository.ts

Lines changed: 1 addition & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ import type { GitProviderDescriptor, GitProviderRepository } from '../gitProvide
2727
import type { GitProviderService } from '../gitProviderService';
2828
import type { GitBranch } from './branch';
2929
import { getBranchNameWithoutRemote, getRemoteNameFromBranchName } from './branch';
30-
import type { GitBranchReference, GitReference, GitTagReference } from './reference';
30+
import type { GitBranchReference, GitReference } from './reference';
3131
import { getNameWithoutRemote, isBranchReference } from './reference';
3232
import type { GitRemote } from './remote';
3333
import type { GitWorktree } from './worktree';
@@ -992,21 +992,6 @@ export class Repository implements Disposable {
992992
this._suspended = true;
993993
}
994994

995-
@log()
996-
tag(...args: string[]) {
997-
void this.runTerminalCommand('tag', ...args);
998-
}
999-
1000-
@log()
1001-
tagDelete(tags: GitTagReference | GitTagReference[]) {
1002-
if (!Array.isArray(tags)) {
1003-
tags = [tags];
1004-
}
1005-
1006-
const args = ['--delete'];
1007-
void this.runTerminalCommand('tag', ...args, ...tags.map(t => t.ref));
1008-
}
1009-
1010995
private _fsWatcherDisposable: Disposable | undefined;
1011996
private _fsWatchers = new Map<string, number>();
1012997
private _fsChangeDelay: number = defaultFileSystemChangeDelay;

0 commit comments

Comments
 (0)