@zjex/git-workflow 0.4.3 → 0.4.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +15 -0
- package/dist/index.js +264 -174
- package/package.json +1 -1
- package/src/commands/branch.ts +47 -38
- package/src/commands/stash.ts +40 -33
- package/src/commands/tag.ts +113 -73
- package/src/commands/update.ts +77 -44
- package/src/utils.ts +59 -1
- package/tests/stash.test.ts +70 -75
- package/tests/update.test.ts +125 -112
package/package.json
CHANGED
package/src/commands/branch.ts
CHANGED
|
@@ -7,6 +7,7 @@ import {
|
|
|
7
7
|
theme,
|
|
8
8
|
exec,
|
|
9
9
|
execOutput,
|
|
10
|
+
execWithSpinner,
|
|
10
11
|
getMainBranch,
|
|
11
12
|
divider,
|
|
12
13
|
type BranchType,
|
|
@@ -35,8 +36,8 @@ export async function getBranchName(type: BranchType): Promise<string | null> {
|
|
|
35
36
|
// 描述是否必填,默认非必填
|
|
36
37
|
const requireDescription =
|
|
37
38
|
type === "feature"
|
|
38
|
-
? config.featureRequireDescription ?? false
|
|
39
|
-
: config.hotfixRequireDescription ?? false;
|
|
39
|
+
? (config.featureRequireDescription ?? false)
|
|
40
|
+
: (config.hotfixRequireDescription ?? false);
|
|
40
41
|
const descMessage = requireDescription
|
|
41
42
|
? "请输入描述:"
|
|
42
43
|
: "请输入描述 (可跳过):";
|
|
@@ -62,7 +63,7 @@ export async function getBranchName(type: BranchType): Promise<string | null> {
|
|
|
62
63
|
|
|
63
64
|
export async function createBranch(
|
|
64
65
|
type: BranchType,
|
|
65
|
-
baseBranchArg?: string | null
|
|
66
|
+
baseBranchArg?: string | null,
|
|
66
67
|
): Promise<void> {
|
|
67
68
|
const config = getConfig();
|
|
68
69
|
|
|
@@ -142,12 +143,16 @@ export async function createBranch(
|
|
|
142
143
|
|
|
143
144
|
if (shouldPush) {
|
|
144
145
|
const pushSpinner = ora("正在推送到远程...").start();
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
pushSpinner
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
146
|
+
const success = await execWithSpinner(
|
|
147
|
+
`git push -u origin "${branchName}"`,
|
|
148
|
+
pushSpinner,
|
|
149
|
+
`已推送到远程: origin/${branchName}`,
|
|
150
|
+
"远程推送失败",
|
|
151
|
+
);
|
|
152
|
+
|
|
153
|
+
if (!success) {
|
|
154
|
+
console.log(
|
|
155
|
+
colors.dim(` 可稍后手动执行: git push -u origin ${branchName}`),
|
|
151
156
|
);
|
|
152
157
|
}
|
|
153
158
|
}
|
|
@@ -175,20 +180,23 @@ export async function deleteBranch(branchArg?: string): Promise<void> {
|
|
|
175
180
|
if (!branch) {
|
|
176
181
|
// 获取本地分支
|
|
177
182
|
const localBranches = execOutput(
|
|
178
|
-
"git for-each-ref --sort=-committerdate refs/heads/ --format='%(refname:short)'"
|
|
183
|
+
"git for-each-ref --sort=-committerdate refs/heads/ --format='%(refname:short)'",
|
|
179
184
|
)
|
|
180
185
|
.split("\n")
|
|
181
186
|
.filter((b) => b && b !== currentBranch);
|
|
182
187
|
|
|
183
188
|
// 获取远程分支(排除 HEAD 和已有本地分支的)
|
|
184
189
|
const remoteBranches = execOutput(
|
|
185
|
-
"git for-each-ref --sort=-committerdate refs/remotes/origin/ --format='%(refname:short)'"
|
|
190
|
+
"git for-each-ref --sort=-committerdate refs/remotes/origin/ --format='%(refname:short)'",
|
|
186
191
|
)
|
|
187
192
|
.split("\n")
|
|
188
193
|
.map((b) => b.replace("origin/", ""))
|
|
189
194
|
.filter(
|
|
190
195
|
(b) =>
|
|
191
|
-
b &&
|
|
196
|
+
b &&
|
|
197
|
+
b !== "HEAD" &&
|
|
198
|
+
b !== currentBranch &&
|
|
199
|
+
!localBranches.includes(b),
|
|
192
200
|
);
|
|
193
201
|
|
|
194
202
|
if (localBranches.length === 0 && remoteBranches.length === 0) {
|
|
@@ -252,14 +260,12 @@ export async function deleteBranch(branchArg?: string): Promise<void> {
|
|
|
252
260
|
}
|
|
253
261
|
|
|
254
262
|
const spinner = ora(`正在删除远程分支: origin/${remoteBranch}`).start();
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
}
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
spinner.fail("远程分支删除失败");
|
|
262
|
-
}
|
|
263
|
+
await execWithSpinner(
|
|
264
|
+
`git push origin --delete "${remoteBranch}"`,
|
|
265
|
+
spinner,
|
|
266
|
+
`远程分支已删除: origin/${remoteBranch}`,
|
|
267
|
+
"远程分支删除失败",
|
|
268
|
+
);
|
|
263
269
|
return;
|
|
264
270
|
}
|
|
265
271
|
}
|
|
@@ -275,7 +281,7 @@ export async function deleteBranch(branchArg?: string): Promise<void> {
|
|
|
275
281
|
if (!localExists) {
|
|
276
282
|
if (hasRemote) {
|
|
277
283
|
console.log(
|
|
278
|
-
colors.yellow(`本地分支不存在,但远程分支存在: origin/${branch}`)
|
|
284
|
+
colors.yellow(`本地分支不存在,但远程分支存在: origin/${branch}`),
|
|
279
285
|
);
|
|
280
286
|
const deleteRemote = await select({
|
|
281
287
|
message: `确认删除远程分支 origin/${branch}?`,
|
|
@@ -288,12 +294,12 @@ export async function deleteBranch(branchArg?: string): Promise<void> {
|
|
|
288
294
|
|
|
289
295
|
if (deleteRemote) {
|
|
290
296
|
const spinner = ora(`正在删除远程分支: origin/${branch}`).start();
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
spinner
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
+
await execWithSpinner(
|
|
298
|
+
`git push origin --delete "${branch}"`,
|
|
299
|
+
spinner,
|
|
300
|
+
`远程分支已删除: origin/${branch}`,
|
|
301
|
+
"远程分支删除失败",
|
|
302
|
+
);
|
|
297
303
|
}
|
|
298
304
|
} else {
|
|
299
305
|
console.log(colors.red(`分支不存在: ${branch}`));
|
|
@@ -319,21 +325,24 @@ export async function deleteBranch(branchArg?: string): Promise<void> {
|
|
|
319
325
|
}
|
|
320
326
|
|
|
321
327
|
const localSpinner = ora(`正在删除本地分支: ${branch}`).start();
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
localSpinner
|
|
325
|
-
|
|
326
|
-
|
|
328
|
+
const localSuccess = await execWithSpinner(
|
|
329
|
+
`git branch -D "${branch}"`,
|
|
330
|
+
localSpinner,
|
|
331
|
+
`本地分支已删除: ${branch}`,
|
|
332
|
+
"本地分支删除失败",
|
|
333
|
+
);
|
|
334
|
+
|
|
335
|
+
if (!localSuccess) {
|
|
327
336
|
return;
|
|
328
337
|
}
|
|
329
338
|
|
|
330
339
|
if (hasRemote) {
|
|
331
340
|
const remoteSpinner = ora(`正在删除远程分支: origin/${branch}`).start();
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
remoteSpinner
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
341
|
+
await execWithSpinner(
|
|
342
|
+
`git push origin --delete "${branch}"`,
|
|
343
|
+
remoteSpinner,
|
|
344
|
+
`远程分支已删除: origin/${branch}`,
|
|
345
|
+
"远程分支删除失败",
|
|
346
|
+
);
|
|
338
347
|
}
|
|
339
348
|
}
|
package/src/commands/stash.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { spawn } from "child_process";
|
|
2
2
|
import { select, input } from "@inquirer/prompts";
|
|
3
3
|
import ora from "ora";
|
|
4
4
|
import boxen from "boxen";
|
|
@@ -7,6 +7,8 @@ import {
|
|
|
7
7
|
theme,
|
|
8
8
|
divider,
|
|
9
9
|
execOutput,
|
|
10
|
+
execAsync,
|
|
11
|
+
execWithSpinner,
|
|
10
12
|
type BranchType,
|
|
11
13
|
} from "../utils.js";
|
|
12
14
|
import { getBranchName } from "./branch.js";
|
|
@@ -40,7 +42,7 @@ function parseStashList(): StashEntry[] {
|
|
|
40
42
|
message = message || "(no message)";
|
|
41
43
|
|
|
42
44
|
const filesRaw = execOutput(
|
|
43
|
-
`git stash show stash@{${index}} --name-only 2>/dev/null
|
|
45
|
+
`git stash show stash@{${index}} --name-only 2>/dev/null`,
|
|
44
46
|
);
|
|
45
47
|
const files = filesRaw ? filesRaw.split("\n").filter(Boolean) : [];
|
|
46
48
|
|
|
@@ -145,10 +147,10 @@ async function showStashActions(entry: StashEntry): Promise<void> {
|
|
|
145
147
|
|
|
146
148
|
switch (action) {
|
|
147
149
|
case "apply":
|
|
148
|
-
applyStash(entry.index, false);
|
|
150
|
+
await applyStash(entry.index, false);
|
|
149
151
|
break;
|
|
150
152
|
case "pop":
|
|
151
|
-
applyStash(entry.index, true);
|
|
153
|
+
await applyStash(entry.index, true);
|
|
152
154
|
break;
|
|
153
155
|
case "branch":
|
|
154
156
|
await createBranchFromStash(entry.index);
|
|
@@ -193,27 +195,34 @@ async function createStash(): Promise<void> {
|
|
|
193
195
|
});
|
|
194
196
|
|
|
195
197
|
const spinner = ora("创建 stash...").start();
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
198
|
+
let cmd = "git stash push";
|
|
199
|
+
if (includeUntracked) cmd += " -u";
|
|
200
|
+
if (message) cmd += ` -m "${message.replace(/"/g, '\\"')}"`;
|
|
201
|
+
|
|
202
|
+
const success = await execWithSpinner(
|
|
203
|
+
cmd,
|
|
204
|
+
spinner,
|
|
205
|
+
"Stash 创建成功",
|
|
206
|
+
"Stash 创建失败",
|
|
207
|
+
);
|
|
208
|
+
|
|
209
|
+
if (success) {
|
|
202
210
|
await stash();
|
|
203
|
-
} catch {
|
|
204
|
-
spinner.fail("Stash 创建失败");
|
|
205
211
|
}
|
|
206
212
|
}
|
|
207
213
|
|
|
208
|
-
function applyStash(index: number, pop: boolean): void {
|
|
214
|
+
async function applyStash(index: number, pop: boolean): Promise<void> {
|
|
209
215
|
const action = pop ? "pop" : "apply";
|
|
210
216
|
const spinner = ora(`${pop ? "弹出" : "应用"} stash...`).start();
|
|
211
217
|
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
spinner
|
|
215
|
-
|
|
216
|
-
|
|
218
|
+
const success = await execWithSpinner(
|
|
219
|
+
`git stash ${action} stash@{${index}}`,
|
|
220
|
+
spinner,
|
|
221
|
+
`Stash ${pop ? "已弹出" : "已应用"}`,
|
|
222
|
+
"操作失败,可能存在冲突",
|
|
223
|
+
);
|
|
224
|
+
|
|
225
|
+
if (!success) {
|
|
217
226
|
const status = execOutput("git status --porcelain");
|
|
218
227
|
if (status.includes("UU") || status.includes("AA")) {
|
|
219
228
|
console.log(colors.yellow("\n存在冲突,请手动解决后提交"));
|
|
@@ -225,7 +234,7 @@ async function showDiff(index: number): Promise<void> {
|
|
|
225
234
|
try {
|
|
226
235
|
// 获取差异内容(不使用颜色,我们自己格式化)
|
|
227
236
|
const diffOutput = execOutput(
|
|
228
|
-
`git stash show -p --no-color stash@{${index}}
|
|
237
|
+
`git stash show -p --no-color stash@{${index}}`,
|
|
229
238
|
);
|
|
230
239
|
|
|
231
240
|
if (!diffOutput) {
|
|
@@ -424,14 +433,12 @@ async function createBranchFromStash(index: number): Promise<void> {
|
|
|
424
433
|
if (!branchName) return;
|
|
425
434
|
|
|
426
435
|
const spinner = ora(`创建分支 ${branchName}...`).start();
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
})
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
spinner.fail("创建分支失败");
|
|
434
|
-
}
|
|
436
|
+
await execWithSpinner(
|
|
437
|
+
`git stash branch "${branchName}" stash@{${index}}`,
|
|
438
|
+
spinner,
|
|
439
|
+
`分支已创建: ${branchName} (stash 已自动弹出)`,
|
|
440
|
+
"创建分支失败",
|
|
441
|
+
);
|
|
435
442
|
}
|
|
436
443
|
|
|
437
444
|
async function dropStash(index: number): Promise<void> {
|
|
@@ -450,10 +457,10 @@ async function dropStash(index: number): Promise<void> {
|
|
|
450
457
|
}
|
|
451
458
|
|
|
452
459
|
const spinner = ora("删除 stash...").start();
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
spinner
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
460
|
+
await execWithSpinner(
|
|
461
|
+
`git stash drop stash@{${index}}`,
|
|
462
|
+
spinner,
|
|
463
|
+
"Stash 已删除",
|
|
464
|
+
"删除失败",
|
|
465
|
+
);
|
|
459
466
|
}
|
package/src/commands/tag.ts
CHANGED
|
@@ -1,7 +1,14 @@
|
|
|
1
|
-
import { execSync } from "child_process";
|
|
2
1
|
import { select, input } from "@inquirer/prompts";
|
|
3
2
|
import ora from "ora";
|
|
4
|
-
import {
|
|
3
|
+
import {
|
|
4
|
+
colors,
|
|
5
|
+
theme,
|
|
6
|
+
exec,
|
|
7
|
+
execOutput,
|
|
8
|
+
execAsync,
|
|
9
|
+
execWithSpinner,
|
|
10
|
+
divider,
|
|
11
|
+
} from "../utils.js";
|
|
5
12
|
import { getConfig } from "../config.js";
|
|
6
13
|
|
|
7
14
|
/**
|
|
@@ -27,7 +34,7 @@ export async function listTags(prefix?: string): Promise<void> {
|
|
|
27
34
|
// 4. 如果没有 tags,提示并返回
|
|
28
35
|
if (tags.length === 0) {
|
|
29
36
|
console.log(
|
|
30
|
-
colors.yellow(prefix ? `没有 '${prefix}' 开头的 tag` : "没有 tag")
|
|
37
|
+
colors.yellow(prefix ? `没有 '${prefix}' 开头的 tag` : "没有 tag"),
|
|
31
38
|
);
|
|
32
39
|
return;
|
|
33
40
|
}
|
|
@@ -35,7 +42,7 @@ export async function listTags(prefix?: string): Promise<void> {
|
|
|
35
42
|
// 5. 如果指定了前缀,直接显示单列(最多 5 个)
|
|
36
43
|
if (prefix) {
|
|
37
44
|
console.log(
|
|
38
|
-
colors.green(`以 '${prefix}' 开头的 tags (共 ${tags.length} 个):`)
|
|
45
|
+
colors.green(`以 '${prefix}' 开头的 tags (共 ${tags.length} 个):`),
|
|
39
46
|
);
|
|
40
47
|
if (tags.length > 5) {
|
|
41
48
|
console.log(colors.dim(" ..."));
|
|
@@ -84,7 +91,7 @@ export async function listTags(prefix?: string): Promise<void> {
|
|
|
84
91
|
|
|
85
92
|
// 9. 计算每列的宽度(取所有 tag 中最长的,至少 20 字符)
|
|
86
93
|
const maxTagLength = Math.max(
|
|
87
|
-
...columns.flatMap((col) => col.tags.map((t) => t.length))
|
|
94
|
+
...columns.flatMap((col) => col.tags.map((t) => t.length)),
|
|
88
95
|
);
|
|
89
96
|
const columnWidth = Math.max(maxTagLength + 4, 20);
|
|
90
97
|
|
|
@@ -99,8 +106,9 @@ export async function listTags(prefix?: string): Promise<void> {
|
|
|
99
106
|
// 11. 打印分隔线
|
|
100
107
|
console.log(
|
|
101
108
|
colors.dim(
|
|
102
|
-
" " +
|
|
103
|
-
|
|
109
|
+
" " +
|
|
110
|
+
"─".repeat(columnWidth * columns.length + (columns.length - 1) * 2),
|
|
111
|
+
),
|
|
104
112
|
);
|
|
105
113
|
|
|
106
114
|
// 12. 打印省略号(如果某列有超过 5 个 tag)
|
|
@@ -248,7 +256,7 @@ export async function createTag(inputPrefix?: string): Promise<void> {
|
|
|
248
256
|
const choices: TagChoice[] = prefixWithDate.map(
|
|
249
257
|
({ prefix: p, latest }) => {
|
|
250
258
|
return { name: `${p} (最新: ${latest})`, value: p };
|
|
251
|
-
}
|
|
259
|
+
},
|
|
252
260
|
);
|
|
253
261
|
choices.push({ name: "输入新前缀...", value: "__new__" });
|
|
254
262
|
|
|
@@ -273,7 +281,7 @@ export async function createTag(inputPrefix?: string): Promise<void> {
|
|
|
273
281
|
if (!latestTag) {
|
|
274
282
|
const newTag = `${prefix}1.0.0`;
|
|
275
283
|
console.log(
|
|
276
|
-
colors.yellow(`未找到 '${prefix}' 开头的 tag,将创建 ${newTag}`)
|
|
284
|
+
colors.yellow(`未找到 '${prefix}' 开头的 tag,将创建 ${newTag}`),
|
|
277
285
|
);
|
|
278
286
|
const ok = await select({
|
|
279
287
|
message: `确认创建 ${newTag}?`,
|
|
@@ -297,7 +305,7 @@ export async function createTag(inputPrefix?: string): Promise<void> {
|
|
|
297
305
|
|
|
298
306
|
// 解析版本号,支持预发布版本如 1.0.0-beta.1
|
|
299
307
|
const preReleaseMatch = version.match(
|
|
300
|
-
/^(\d+)\.(\d+)\.(\d+)-([a-zA-Z]+)\.(\d+)
|
|
308
|
+
/^(\d+)\.(\d+)\.(\d+)-([a-zA-Z]+)\.(\d+)$/,
|
|
301
309
|
);
|
|
302
310
|
const match3 = version.match(/^(\d+)\.(\d+)\.(\d+)$/);
|
|
303
311
|
const match2 = version.match(/^(\d+)\.(\d+)$/);
|
|
@@ -404,33 +412,34 @@ export async function createTag(inputPrefix?: string): Promise<void> {
|
|
|
404
412
|
return;
|
|
405
413
|
}
|
|
406
414
|
|
|
407
|
-
doCreateTag(nextTag);
|
|
415
|
+
await doCreateTag(nextTag);
|
|
408
416
|
}
|
|
409
417
|
|
|
410
|
-
function doCreateTag(tagName: string): void {
|
|
418
|
+
async function doCreateTag(tagName: string): Promise<void> {
|
|
411
419
|
divider();
|
|
412
420
|
|
|
413
421
|
const spinner = ora(`正在创建 tag: ${tagName}`).start();
|
|
422
|
+
const success = await execWithSpinner(
|
|
423
|
+
`git tag -a "${tagName}" -m "Release ${tagName}"`,
|
|
424
|
+
spinner,
|
|
425
|
+
`Tag 创建成功: ${tagName}`,
|
|
426
|
+
"tag 创建失败",
|
|
427
|
+
);
|
|
414
428
|
|
|
415
|
-
|
|
416
|
-
execSync(`git tag -a "${tagName}" -m "Release ${tagName}"`, {
|
|
417
|
-
stdio: "pipe",
|
|
418
|
-
});
|
|
419
|
-
spinner.succeed(`Tag 创建成功: ${tagName}`);
|
|
420
|
-
} catch {
|
|
421
|
-
spinner.fail("tag 创建失败");
|
|
429
|
+
if (!success) {
|
|
422
430
|
return;
|
|
423
431
|
}
|
|
424
432
|
|
|
425
433
|
const pushSpinner = ora("正在推送到远程...").start();
|
|
434
|
+
const pushSuccess = await execWithSpinner(
|
|
435
|
+
`git push origin "${tagName}"`,
|
|
436
|
+
pushSpinner,
|
|
437
|
+
`Tag 已推送: ${tagName}`,
|
|
438
|
+
"远程推送失败",
|
|
439
|
+
);
|
|
426
440
|
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
pushSpinner.succeed(`Tag 已推送: ${tagName}`);
|
|
430
|
-
} catch {
|
|
431
|
-
pushSpinner.warn(
|
|
432
|
-
`远程推送失败,可稍后手动执行: git push origin ${tagName}`
|
|
433
|
-
);
|
|
441
|
+
if (!pushSuccess) {
|
|
442
|
+
console.log(colors.dim(` 可稍后手动执行: git push origin ${tagName}`));
|
|
434
443
|
}
|
|
435
444
|
}
|
|
436
445
|
|
|
@@ -484,12 +493,14 @@ export async function deleteTag(): Promise<void> {
|
|
|
484
493
|
divider();
|
|
485
494
|
|
|
486
495
|
const spinner = ora(`正在删除本地 tag: ${tagToDelete}`).start();
|
|
496
|
+
const localSuccess = await execWithSpinner(
|
|
497
|
+
`git tag -d "${tagToDelete}"`,
|
|
498
|
+
spinner,
|
|
499
|
+
`本地 tag 已删除: ${tagToDelete}`,
|
|
500
|
+
"本地 tag 删除失败",
|
|
501
|
+
);
|
|
487
502
|
|
|
488
|
-
|
|
489
|
-
execSync(`git tag -d "${tagToDelete}"`, { stdio: "pipe" });
|
|
490
|
-
spinner.succeed(`本地 tag 已删除: ${tagToDelete}`);
|
|
491
|
-
} catch {
|
|
492
|
-
spinner.fail("本地 tag 删除失败");
|
|
503
|
+
if (!localSuccess) {
|
|
493
504
|
return;
|
|
494
505
|
}
|
|
495
506
|
|
|
@@ -504,12 +515,15 @@ export async function deleteTag(): Promise<void> {
|
|
|
504
515
|
|
|
505
516
|
if (deleteRemote) {
|
|
506
517
|
const pushSpinner = ora("正在删除远程 tag...").start();
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
pushSpinner
|
|
510
|
-
|
|
518
|
+
const remoteSuccess = await execWithSpinner(
|
|
519
|
+
`git push origin --delete "${tagToDelete}"`,
|
|
520
|
+
pushSpinner,
|
|
521
|
+
`远程 tag 已删除: ${tagToDelete}`,
|
|
522
|
+
);
|
|
523
|
+
|
|
524
|
+
if (!remoteSuccess) {
|
|
511
525
|
pushSpinner.warn(
|
|
512
|
-
`远程删除失败,可稍后手动执行: git push origin --delete ${tagToDelete}
|
|
526
|
+
`远程删除失败,可稍后手动执行: git push origin --delete ${tagToDelete}`,
|
|
513
527
|
);
|
|
514
528
|
}
|
|
515
529
|
}
|
|
@@ -574,32 +588,40 @@ export async function updateTag(): Promise<void> {
|
|
|
574
588
|
|
|
575
589
|
const spinner = ora(`正在重命名 tag: ${oldTag} → ${newTag}`).start();
|
|
576
590
|
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
591
|
+
// 获取旧 tag 的 commit 和消息
|
|
592
|
+
const commit = execOutput(`git rev-list -n 1 "${oldTag}"`).trim();
|
|
593
|
+
const message = execOutput(
|
|
594
|
+
`git tag -l --format='%(contents)' "${oldTag}"`,
|
|
595
|
+
).trim();
|
|
596
|
+
|
|
597
|
+
// 创建新 tag(指向同一个 commit)
|
|
598
|
+
let createSuccess: boolean;
|
|
599
|
+
if (message) {
|
|
600
|
+
createSuccess = await execWithSpinner(
|
|
601
|
+
`git tag -a "${newTag}" "${commit}" -m "${message}"`,
|
|
602
|
+
spinner,
|
|
603
|
+
);
|
|
604
|
+
} else {
|
|
605
|
+
createSuccess = await execWithSpinner(
|
|
606
|
+
`git tag "${newTag}" "${commit}"`,
|
|
607
|
+
spinner,
|
|
608
|
+
);
|
|
609
|
+
}
|
|
595
610
|
|
|
596
|
-
|
|
597
|
-
} catch (error) {
|
|
611
|
+
if (!createSuccess) {
|
|
598
612
|
spinner.fail("tag 重命名失败");
|
|
599
|
-
console.log(colors.red(String(error)));
|
|
600
613
|
return;
|
|
601
614
|
}
|
|
602
615
|
|
|
616
|
+
// 删除旧 tag
|
|
617
|
+
const deleteSuccess = await execAsync(`git tag -d "${oldTag}"`, spinner);
|
|
618
|
+
if (!deleteSuccess) {
|
|
619
|
+
spinner.fail("删除旧 tag 失败");
|
|
620
|
+
return;
|
|
621
|
+
}
|
|
622
|
+
|
|
623
|
+
spinner.succeed(`Tag 已重命名: ${oldTag} → ${newTag}`);
|
|
624
|
+
|
|
603
625
|
const pushRemote = await select({
|
|
604
626
|
message: "是否同步到远程?",
|
|
605
627
|
choices: [
|
|
@@ -611,17 +633,32 @@ export async function updateTag(): Promise<void> {
|
|
|
611
633
|
|
|
612
634
|
if (pushRemote) {
|
|
613
635
|
const pushSpinner = ora("正在同步到远程...").start();
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
636
|
+
|
|
637
|
+
// 推送新 tag
|
|
638
|
+
const pushNewSuccess = await execAsync(
|
|
639
|
+
`git push origin "${newTag}"`,
|
|
640
|
+
pushSpinner,
|
|
641
|
+
);
|
|
642
|
+
if (!pushNewSuccess) {
|
|
621
643
|
pushSpinner.warn(
|
|
622
|
-
`远程同步失败,可稍后手动执行:\n git push origin ${newTag}\n git push origin --delete ${oldTag}
|
|
644
|
+
`远程同步失败,可稍后手动执行:\n git push origin ${newTag}\n git push origin --delete ${oldTag}`,
|
|
623
645
|
);
|
|
646
|
+
return;
|
|
624
647
|
}
|
|
648
|
+
|
|
649
|
+
// 删除远程旧 tag
|
|
650
|
+
const deleteOldSuccess = await execAsync(
|
|
651
|
+
`git push origin --delete "${oldTag}"`,
|
|
652
|
+
pushSpinner,
|
|
653
|
+
);
|
|
654
|
+
if (!deleteOldSuccess) {
|
|
655
|
+
pushSpinner.warn(
|
|
656
|
+
`远程旧 tag 删除失败,可稍后手动执行: git push origin --delete ${oldTag}`,
|
|
657
|
+
);
|
|
658
|
+
return;
|
|
659
|
+
}
|
|
660
|
+
|
|
661
|
+
pushSpinner.succeed(`远程 tag 已同步: ${oldTag} → ${newTag}`);
|
|
625
662
|
}
|
|
626
663
|
}
|
|
627
664
|
|
|
@@ -692,10 +729,10 @@ export async function cleanInvalidTags(): Promise<void> {
|
|
|
692
729
|
let localFailed = 0;
|
|
693
730
|
|
|
694
731
|
for (const tag of invalidTags) {
|
|
695
|
-
|
|
696
|
-
|
|
732
|
+
const success = await execAsync(`git tag -d "${tag}"`, localSpinner);
|
|
733
|
+
if (success) {
|
|
697
734
|
localSuccess++;
|
|
698
|
-
}
|
|
735
|
+
} else {
|
|
699
736
|
localFailed++;
|
|
700
737
|
}
|
|
701
738
|
}
|
|
@@ -704,7 +741,7 @@ export async function cleanInvalidTags(): Promise<void> {
|
|
|
704
741
|
localSpinner.succeed(`本地标签已删除: ${localSuccess} 个`);
|
|
705
742
|
} else {
|
|
706
743
|
localSpinner.warn(
|
|
707
|
-
`本地标签删除: 成功 ${localSuccess} 个,失败 ${localFailed}
|
|
744
|
+
`本地标签删除: 成功 ${localSuccess} 个,失败 ${localFailed} 个`,
|
|
708
745
|
);
|
|
709
746
|
}
|
|
710
747
|
|
|
@@ -724,10 +761,13 @@ export async function cleanInvalidTags(): Promise<void> {
|
|
|
724
761
|
let remoteFailed = 0;
|
|
725
762
|
|
|
726
763
|
for (const tag of invalidTags) {
|
|
727
|
-
|
|
728
|
-
|
|
764
|
+
const success = await execAsync(
|
|
765
|
+
`git push origin --delete "${tag}"`,
|
|
766
|
+
remoteSpinner,
|
|
767
|
+
);
|
|
768
|
+
if (success) {
|
|
729
769
|
remoteSuccess++;
|
|
730
|
-
}
|
|
770
|
+
} else {
|
|
731
771
|
remoteFailed++;
|
|
732
772
|
}
|
|
733
773
|
}
|
|
@@ -736,7 +776,7 @@ export async function cleanInvalidTags(): Promise<void> {
|
|
|
736
776
|
remoteSpinner.succeed(`远程标签已删除: ${remoteSuccess} 个`);
|
|
737
777
|
} else {
|
|
738
778
|
remoteSpinner.warn(
|
|
739
|
-
`远程标签删除: 成功 ${remoteSuccess} 个,失败 ${remoteFailed}
|
|
779
|
+
`远程标签删除: 成功 ${remoteSuccess} 个,失败 ${remoteFailed} 个`,
|
|
740
780
|
);
|
|
741
781
|
}
|
|
742
782
|
}
|