@zjex/git-workflow 0.3.6 → 0.3.8
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/dist/index.js +121 -121
- package/package.json +1 -1
- package/src/commands/commit.ts +63 -61
- package/src/commands/log.ts +209 -182
- package/src/index.ts +2 -2
- package/src/utils.ts +2 -0
- package/tsup.config.ts +1 -1
package/dist/index.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
#!/usr/bin/env -S node --
|
|
1
|
+
#!/usr/bin/env -S node --disable-warning=ExperimentalWarning
|
|
2
2
|
var __defProp = Object.defineProperty;
|
|
3
3
|
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
4
4
|
var __esm = (fn, res) => function __init() {
|
|
@@ -67,6 +67,8 @@ var init_utils = __esm({
|
|
|
67
67
|
orange: (s) => `\x1B[38;5;208m${s}\x1B[0m`,
|
|
68
68
|
lightPurple: (s) => `\x1B[38;5;141m${s}\x1B[0m`,
|
|
69
69
|
white: (s) => `\x1B[37m${s}\x1B[0m`,
|
|
70
|
+
brightYellow: (s) => `\x1B[93m${s}\x1B[0m`,
|
|
71
|
+
// 亮黄色
|
|
70
72
|
reset: "\x1B[0m"
|
|
71
73
|
};
|
|
72
74
|
TODAY = (/* @__PURE__ */ new Date()).toISOString().slice(0, 10).replace(/-/g, "");
|
|
@@ -89,9 +91,9 @@ __export(update_notifier_exports, {
|
|
|
89
91
|
clearUpdateCache: () => clearUpdateCache
|
|
90
92
|
});
|
|
91
93
|
import { execSync as execSync6 } from "child_process";
|
|
92
|
-
import { readFileSync as readFileSync3, writeFileSync as
|
|
94
|
+
import { readFileSync as readFileSync3, writeFileSync as writeFileSync4, existsSync as existsSync3, unlinkSync as unlinkSync2 } from "fs";
|
|
93
95
|
import { homedir as homedir3 } from "os";
|
|
94
|
-
import { join as
|
|
96
|
+
import { join as join4 } from "path";
|
|
95
97
|
import boxen from "boxen";
|
|
96
98
|
import { select as select7 } from "@inquirer/prompts";
|
|
97
99
|
import ora5 from "ora";
|
|
@@ -281,16 +283,16 @@ async function performUpdate(packageName) {
|
|
|
281
283
|
}
|
|
282
284
|
function clearUpdateCache() {
|
|
283
285
|
try {
|
|
284
|
-
const cacheFile =
|
|
286
|
+
const cacheFile = join4(homedir3(), CACHE_FILE);
|
|
285
287
|
if (existsSync3(cacheFile)) {
|
|
286
|
-
|
|
288
|
+
unlinkSync2(cacheFile);
|
|
287
289
|
}
|
|
288
290
|
} catch {
|
|
289
291
|
}
|
|
290
292
|
}
|
|
291
293
|
function readCache() {
|
|
292
294
|
try {
|
|
293
|
-
const cacheFile =
|
|
295
|
+
const cacheFile = join4(homedir3(), CACHE_FILE);
|
|
294
296
|
if (!existsSync3(cacheFile)) {
|
|
295
297
|
return null;
|
|
296
298
|
}
|
|
@@ -302,8 +304,8 @@ function readCache() {
|
|
|
302
304
|
}
|
|
303
305
|
function writeCache(cache) {
|
|
304
306
|
try {
|
|
305
|
-
const cacheFile =
|
|
306
|
-
|
|
307
|
+
const cacheFile = join4(homedir3(), CACHE_FILE);
|
|
308
|
+
writeFileSync4(cacheFile, JSON.stringify(cache), "utf-8");
|
|
307
309
|
} catch {
|
|
308
310
|
}
|
|
309
311
|
}
|
|
@@ -1789,6 +1791,9 @@ async function dropStash(index) {
|
|
|
1789
1791
|
// src/commands/commit.ts
|
|
1790
1792
|
init_utils();
|
|
1791
1793
|
import { execSync as execSync5 } from "child_process";
|
|
1794
|
+
import { writeFileSync as writeFileSync3, unlinkSync } from "fs";
|
|
1795
|
+
import { tmpdir } from "os";
|
|
1796
|
+
import { join as join3 } from "path";
|
|
1792
1797
|
import { select as select6, input as input5, checkbox } from "@inquirer/prompts";
|
|
1793
1798
|
import ora4 from "ora";
|
|
1794
1799
|
|
|
@@ -2196,45 +2201,39 @@ function formatFileStatus(status) {
|
|
|
2196
2201
|
}
|
|
2197
2202
|
async function commit() {
|
|
2198
2203
|
const config2 = getConfig();
|
|
2204
|
+
const autoStage = config2.autoStage ?? true;
|
|
2205
|
+
if (autoStage) {
|
|
2206
|
+
execSync5("git add -A", { stdio: "pipe" });
|
|
2207
|
+
}
|
|
2199
2208
|
let { staged, unstaged } = parseGitStatus();
|
|
2200
|
-
if (unstaged.length > 0) {
|
|
2201
|
-
|
|
2202
|
-
|
|
2203
|
-
|
|
2204
|
-
|
|
2205
|
-
|
|
2206
|
-
|
|
2207
|
-
|
|
2208
|
-
|
|
2209
|
-
|
|
2210
|
-
|
|
2211
|
-
|
|
2212
|
-
|
|
2213
|
-
|
|
2214
|
-
|
|
2215
|
-
|
|
2216
|
-
|
|
2217
|
-
|
|
2218
|
-
|
|
2219
|
-
|
|
2220
|
-
|
|
2221
|
-
|
|
2222
|
-
|
|
2223
|
-
})),
|
|
2224
|
-
theme
|
|
2225
|
-
});
|
|
2226
|
-
if (filesToStage.length === 0) {
|
|
2227
|
-
console.log(colors.yellow("\u6CA1\u6709\u9009\u62E9\u4EFB\u4F55\u6587\u4EF6\uFF0C\u5DF2\u53D6\u6D88"));
|
|
2228
|
-
return;
|
|
2229
|
-
}
|
|
2230
|
-
for (const file of filesToStage) {
|
|
2231
|
-
execSync5(`git add "${file}"`, { stdio: "pipe" });
|
|
2232
|
-
}
|
|
2233
|
-
console.log(colors.green(`\u2714 \u5DF2\u6682\u5B58 ${filesToStage.length} \u4E2A\u6587\u4EF6`));
|
|
2234
|
-
divider();
|
|
2235
|
-
const newStatus = parseGitStatus();
|
|
2236
|
-
staged = newStatus.staged;
|
|
2209
|
+
if (staged.length === 0 && unstaged.length > 0 && !autoStage) {
|
|
2210
|
+
console.log(colors.yellow("\u6CA1\u6709\u6682\u5B58\u7684\u66F4\u6539"));
|
|
2211
|
+
divider();
|
|
2212
|
+
console.log("\u672A\u6682\u5B58\u7684\u6587\u4EF6:");
|
|
2213
|
+
for (const { status, file } of unstaged) {
|
|
2214
|
+
console.log(` ${formatFileStatus(status)} ${file}`);
|
|
2215
|
+
}
|
|
2216
|
+
divider();
|
|
2217
|
+
const filesToStage = await checkbox({
|
|
2218
|
+
message: "\u9009\u62E9\u8981\u6682\u5B58\u7684\u6587\u4EF6:",
|
|
2219
|
+
choices: unstaged.map(({ status, file }) => ({
|
|
2220
|
+
name: `${formatFileStatus(status)} ${file}`,
|
|
2221
|
+
value: file,
|
|
2222
|
+
checked: true
|
|
2223
|
+
})),
|
|
2224
|
+
theme
|
|
2225
|
+
});
|
|
2226
|
+
if (filesToStage.length === 0) {
|
|
2227
|
+
console.log(colors.yellow("\u6CA1\u6709\u9009\u62E9\u4EFB\u4F55\u6587\u4EF6\uFF0C\u5DF2\u53D6\u6D88"));
|
|
2228
|
+
return;
|
|
2229
|
+
}
|
|
2230
|
+
for (const file of filesToStage) {
|
|
2231
|
+
execSync5(`git add "${file}"`, { stdio: "pipe" });
|
|
2237
2232
|
}
|
|
2233
|
+
console.log(colors.green(`\u2714 \u5DF2\u6682\u5B58 ${filesToStage.length} \u4E2A\u6587\u4EF6`));
|
|
2234
|
+
divider();
|
|
2235
|
+
const newStatus = parseGitStatus();
|
|
2236
|
+
staged = newStatus.staged;
|
|
2238
2237
|
}
|
|
2239
2238
|
if (staged.length === 0) {
|
|
2240
2239
|
console.log(colors.yellow("\u5DE5\u4F5C\u533A\u5E72\u51C0\uFF0C\u6CA1\u6709\u9700\u8981\u63D0\u4EA4\u7684\u66F4\u6539"));
|
|
@@ -2320,14 +2319,10 @@ async function commit() {
|
|
|
2320
2319
|
}
|
|
2321
2320
|
const spinner = ora4("\u6B63\u5728\u63D0\u4EA4...").start();
|
|
2322
2321
|
try {
|
|
2323
|
-
|
|
2324
|
-
|
|
2325
|
-
const autoStage = config2.autoStage ?? true;
|
|
2326
|
-
if (autoStage) {
|
|
2327
|
-
execSync5("git add -A", { stdio: "pipe" });
|
|
2328
|
-
finalStatus = parseGitStatus();
|
|
2329
|
-
}
|
|
2322
|
+
if (autoStage) {
|
|
2323
|
+
execSync5("git add -A", { stdio: "pipe" });
|
|
2330
2324
|
}
|
|
2325
|
+
const finalStatus = parseGitStatus();
|
|
2331
2326
|
if (finalStatus.staged.length === 0) {
|
|
2332
2327
|
spinner.fail("\u6CA1\u6709\u6682\u5B58\u7684\u6587\u4EF6\u53EF\u4EE5\u63D0\u4EA4");
|
|
2333
2328
|
console.log("");
|
|
@@ -2338,9 +2333,18 @@ async function commit() {
|
|
|
2338
2333
|
console.log("");
|
|
2339
2334
|
return;
|
|
2340
2335
|
}
|
|
2341
|
-
|
|
2342
|
-
|
|
2343
|
-
|
|
2336
|
+
const tmpFile = join3(tmpdir(), `.gw-commit-msg-${Date.now()}`);
|
|
2337
|
+
try {
|
|
2338
|
+
writeFileSync3(tmpFile, message, "utf-8");
|
|
2339
|
+
execSync5(`git commit -F "${tmpFile}"`, {
|
|
2340
|
+
stdio: ["pipe", "pipe", "pipe"]
|
|
2341
|
+
});
|
|
2342
|
+
} finally {
|
|
2343
|
+
try {
|
|
2344
|
+
unlinkSync(tmpFile);
|
|
2345
|
+
} catch {
|
|
2346
|
+
}
|
|
2347
|
+
}
|
|
2344
2348
|
spinner.succeed("\u63D0\u4EA4\u6210\u529F");
|
|
2345
2349
|
const commitHash = execOutput("git rev-parse --short HEAD");
|
|
2346
2350
|
console.log(colors.dim(`commit: ${commitHash}`));
|
|
@@ -2447,15 +2451,15 @@ import { execSync as execSync7 } from "child_process";
|
|
|
2447
2451
|
import ora6 from "ora";
|
|
2448
2452
|
import boxen2 from "boxen";
|
|
2449
2453
|
import semver2 from "semver";
|
|
2450
|
-
import { existsSync as existsSync4, unlinkSync as
|
|
2454
|
+
import { existsSync as existsSync4, unlinkSync as unlinkSync3 } from "fs";
|
|
2451
2455
|
import { homedir as homedir4 } from "os";
|
|
2452
|
-
import { join as
|
|
2456
|
+
import { join as join5 } from "path";
|
|
2453
2457
|
var CACHE_FILE2 = ".gw-update-check";
|
|
2454
2458
|
function clearUpdateCache2() {
|
|
2455
2459
|
try {
|
|
2456
|
-
const cacheFile =
|
|
2460
|
+
const cacheFile = join5(homedir4(), CACHE_FILE2);
|
|
2457
2461
|
if (existsSync4(cacheFile)) {
|
|
2458
|
-
|
|
2462
|
+
unlinkSync3(cacheFile);
|
|
2459
2463
|
}
|
|
2460
2464
|
} catch {
|
|
2461
2465
|
}
|
|
@@ -2602,57 +2606,49 @@ function parseGitLog(output) {
|
|
|
2602
2606
|
}
|
|
2603
2607
|
return commits;
|
|
2604
2608
|
}
|
|
2605
|
-
function getCommitTypeIcon(subject) {
|
|
2606
|
-
const lowerSubject = subject.toLowerCase();
|
|
2607
|
-
if (lowerSubject.includes("feat") || lowerSubject.includes("feature")) return "\u2728";
|
|
2608
|
-
if (lowerSubject.includes("fix") || lowerSubject.includes("bug")) return "\u{1F41B}";
|
|
2609
|
-
if (lowerSubject.includes("docs") || lowerSubject.includes("doc")) return "\u{1F4DA}";
|
|
2610
|
-
if (lowerSubject.includes("style")) return "\u{1F484}";
|
|
2611
|
-
if (lowerSubject.includes("refactor")) return "\u267B\uFE0F";
|
|
2612
|
-
if (lowerSubject.includes("test")) return "\u{1F9EA}";
|
|
2613
|
-
if (lowerSubject.includes("chore")) return "\u{1F527}";
|
|
2614
|
-
if (lowerSubject.includes("perf")) return "\u26A1";
|
|
2615
|
-
if (lowerSubject.includes("ci")) return "\u{1F477}";
|
|
2616
|
-
if (lowerSubject.includes("build")) return "\u{1F4E6}";
|
|
2617
|
-
if (lowerSubject.includes("revert")) return "\u23EA";
|
|
2618
|
-
if (lowerSubject.includes("merge")) return "\u{1F500}";
|
|
2619
|
-
if (lowerSubject.includes("release") || lowerSubject.includes("version")) return "\u{1F516}";
|
|
2620
|
-
return "\u{1F4DD}";
|
|
2621
|
-
}
|
|
2622
2609
|
function groupCommitsByDate(commits) {
|
|
2623
2610
|
const groups = /* @__PURE__ */ new Map();
|
|
2611
|
+
const dateOrder = [];
|
|
2624
2612
|
for (const commit2 of commits) {
|
|
2625
2613
|
const date = commit2.date;
|
|
2626
2614
|
if (!groups.has(date)) {
|
|
2627
2615
|
groups.set(date, []);
|
|
2616
|
+
dateOrder.push(date);
|
|
2628
2617
|
}
|
|
2629
2618
|
groups.get(date).push(commit2);
|
|
2630
2619
|
}
|
|
2631
|
-
|
|
2620
|
+
const orderedGroups = /* @__PURE__ */ new Map();
|
|
2621
|
+
for (const date of dateOrder) {
|
|
2622
|
+
orderedGroups.set(date, groups.get(date));
|
|
2623
|
+
}
|
|
2624
|
+
return orderedGroups;
|
|
2632
2625
|
}
|
|
2633
2626
|
function formatRelativeTime(relativeDate) {
|
|
2634
2627
|
let result = relativeDate;
|
|
2635
2628
|
const timeMap = {
|
|
2636
|
-
|
|
2637
|
-
|
|
2638
|
-
|
|
2639
|
-
|
|
2640
|
-
|
|
2641
|
-
|
|
2642
|
-
|
|
2643
|
-
|
|
2644
|
-
|
|
2645
|
-
|
|
2646
|
-
|
|
2647
|
-
|
|
2648
|
-
|
|
2649
|
-
|
|
2650
|
-
|
|
2629
|
+
second: "\u79D2",
|
|
2630
|
+
seconds: "\u79D2",
|
|
2631
|
+
minute: "\u5206\u949F",
|
|
2632
|
+
minutes: "\u5206\u949F",
|
|
2633
|
+
hour: "\u5C0F\u65F6",
|
|
2634
|
+
hours: "\u5C0F\u65F6",
|
|
2635
|
+
day: "\u5929",
|
|
2636
|
+
days: "\u5929",
|
|
2637
|
+
week: "\u5468",
|
|
2638
|
+
weeks: "\u5468",
|
|
2639
|
+
month: "\u4E2A\u6708",
|
|
2640
|
+
months: "\u4E2A\u6708",
|
|
2641
|
+
year: "\u5E74",
|
|
2642
|
+
years: "\u5E74",
|
|
2643
|
+
ago: "\u524D"
|
|
2651
2644
|
};
|
|
2652
2645
|
for (const [en, zh] of Object.entries(timeMap)) {
|
|
2653
2646
|
result = result.replace(new RegExp(`\\b${en}\\b`, "g"), zh);
|
|
2654
2647
|
}
|
|
2655
|
-
result = result.replace(
|
|
2648
|
+
result = result.replace(
|
|
2649
|
+
/(\d+)\s+(秒|分钟|小时|天|周|个月|年)\s+前/g,
|
|
2650
|
+
"$1$2\u524D"
|
|
2651
|
+
);
|
|
2656
2652
|
const match = result.match(/(\d+)(分钟|小时|天|周|个月|年)前/);
|
|
2657
2653
|
if (match) {
|
|
2658
2654
|
const num = parseInt(match[1]);
|
|
@@ -2699,28 +2695,25 @@ function supportsColor() {
|
|
|
2699
2695
|
function formatTimelineStyle(commits) {
|
|
2700
2696
|
const groupedCommits = groupCommitsByDate(commits);
|
|
2701
2697
|
let output = "";
|
|
2702
|
-
const sortedDates = Array.from(groupedCommits.keys())
|
|
2703
|
-
(a, b) => new Date(b).getTime() - new Date(a).getTime()
|
|
2704
|
-
);
|
|
2698
|
+
const sortedDates = Array.from(groupedCommits.keys());
|
|
2705
2699
|
const useColors = supportsColor() || process.env.FORCE_COLOR;
|
|
2706
2700
|
for (let dateIndex = 0; dateIndex < sortedDates.length; dateIndex++) {
|
|
2707
2701
|
const date = sortedDates[dateIndex];
|
|
2708
2702
|
const dateCommits = groupedCommits.get(date);
|
|
2709
2703
|
const dateTitle = `\u{1F4C5} Commits on ${date}`;
|
|
2710
2704
|
if (useColors) {
|
|
2711
|
-
output +=
|
|
2705
|
+
output += colors.bold(colors.yellow(dateTitle)) + "\n";
|
|
2712
2706
|
} else {
|
|
2713
|
-
output +=
|
|
2707
|
+
output += dateTitle + "\n";
|
|
2714
2708
|
}
|
|
2715
2709
|
for (let commitIndex = 0; commitIndex < dateCommits.length; commitIndex++) {
|
|
2716
2710
|
const commit2 = dateCommits[commitIndex];
|
|
2717
|
-
const icon = getCommitTypeIcon(commit2.subject);
|
|
2718
2711
|
const { title, tasks } = parseCommitSubject(commit2.subject);
|
|
2719
2712
|
const commitContent = [];
|
|
2720
2713
|
if (useColors) {
|
|
2721
|
-
commitContent.push(
|
|
2714
|
+
commitContent.push(colors.bold(colors.white(title)));
|
|
2722
2715
|
} else {
|
|
2723
|
-
commitContent.push(
|
|
2716
|
+
commitContent.push(title);
|
|
2724
2717
|
}
|
|
2725
2718
|
if (tasks.length > 0) {
|
|
2726
2719
|
commitContent.push("");
|
|
@@ -2734,8 +2727,12 @@ function formatTimelineStyle(commits) {
|
|
|
2734
2727
|
}
|
|
2735
2728
|
commitContent.push("");
|
|
2736
2729
|
if (useColors) {
|
|
2737
|
-
commitContent.push(
|
|
2738
|
-
|
|
2730
|
+
commitContent.push(
|
|
2731
|
+
`${colors.dim("\u{1F464}")} ${colors.blue(commit2.author)} ${colors.dim(
|
|
2732
|
+
"committed"
|
|
2733
|
+
)} ${colors.green(formatRelativeTime(commit2.relativeDate))}`
|
|
2734
|
+
);
|
|
2735
|
+
commitContent.push(`${colors.dim("\u{1F517}")} ${colors.orange(commit2.hash)}`);
|
|
2739
2736
|
if (commit2.refs && commit2.refs.trim()) {
|
|
2740
2737
|
const refs = commit2.refs.trim();
|
|
2741
2738
|
const refParts = refs.split(", ");
|
|
@@ -2751,16 +2748,24 @@ function formatTimelineStyle(commits) {
|
|
|
2751
2748
|
}
|
|
2752
2749
|
});
|
|
2753
2750
|
if (branches.length > 0) {
|
|
2754
|
-
commitContent.push(
|
|
2751
|
+
commitContent.push(
|
|
2752
|
+
`${colors.dim("\u{1F33F}")} ${colors.lightPurple(branches.join(", "))}`
|
|
2753
|
+
);
|
|
2755
2754
|
}
|
|
2756
2755
|
if (tags.length > 0) {
|
|
2757
2756
|
const tagText = tags.map((tag) => `tag ${tag}`).join(", ");
|
|
2758
|
-
commitContent.push(
|
|
2757
|
+
commitContent.push(
|
|
2758
|
+
`${colors.dim("\u{1F516}")} ${colors.brightYellow(tagText)}`
|
|
2759
|
+
);
|
|
2759
2760
|
}
|
|
2760
2761
|
}
|
|
2761
2762
|
} else {
|
|
2762
|
-
commitContent.push(
|
|
2763
|
-
|
|
2763
|
+
commitContent.push(
|
|
2764
|
+
`\u{1F464} ${commit2.author} committed ${formatRelativeTime(
|
|
2765
|
+
commit2.relativeDate
|
|
2766
|
+
)}`
|
|
2767
|
+
);
|
|
2768
|
+
commitContent.push(`\u{1F517} ${commit2.hash}`);
|
|
2764
2769
|
if (commit2.refs && commit2.refs.trim()) {
|
|
2765
2770
|
const refs = commit2.refs.trim();
|
|
2766
2771
|
const refParts = refs.split(", ");
|
|
@@ -2786,7 +2791,7 @@ function formatTimelineStyle(commits) {
|
|
|
2786
2791
|
}
|
|
2787
2792
|
const commitBox = boxen3(commitContent.join("\n"), {
|
|
2788
2793
|
padding: { top: 0, bottom: 0, left: 1, right: 1 },
|
|
2789
|
-
margin: { top: 0, bottom:
|
|
2794
|
+
margin: { top: 0, bottom: 0, left: 0, right: 0 },
|
|
2790
2795
|
borderStyle: "round",
|
|
2791
2796
|
borderColor: "gray"
|
|
2792
2797
|
});
|
|
@@ -2802,11 +2807,17 @@ function startInteractivePager(content) {
|
|
|
2802
2807
|
stdio: ["pipe", "inherit", "inherit"],
|
|
2803
2808
|
env: { ...process.env, LESS: "-R -S -F -X -i" }
|
|
2804
2809
|
});
|
|
2810
|
+
pagerProcess.stdin.on("error", (err) => {
|
|
2811
|
+
if (err.code !== "EPIPE") {
|
|
2812
|
+
console.error(err);
|
|
2813
|
+
}
|
|
2814
|
+
});
|
|
2805
2815
|
pagerProcess.stdin.write(content);
|
|
2806
2816
|
pagerProcess.stdin.end();
|
|
2807
2817
|
pagerProcess.on("exit", () => {
|
|
2818
|
+
process.exit(0);
|
|
2808
2819
|
});
|
|
2809
|
-
pagerProcess.on("error", (
|
|
2820
|
+
pagerProcess.on("error", () => {
|
|
2810
2821
|
console.log(content);
|
|
2811
2822
|
});
|
|
2812
2823
|
} catch (error) {
|
|
@@ -2822,9 +2833,6 @@ function executeTimelineLog(options) {
|
|
|
2822
2833
|
if (options.until) cmd += ` --until="${options.until}"`;
|
|
2823
2834
|
if (options.grep) cmd += ` --grep="${options.grep}"`;
|
|
2824
2835
|
if (options.all) cmd += ` --all`;
|
|
2825
|
-
if (options.interactive && !options.limit) {
|
|
2826
|
-
cmd += ` -50`;
|
|
2827
|
-
}
|
|
2828
2836
|
const output = execSync8(cmd, {
|
|
2829
2837
|
encoding: "utf8",
|
|
2830
2838
|
stdio: "pipe",
|
|
@@ -2833,14 +2841,6 @@ function executeTimelineLog(options) {
|
|
|
2833
2841
|
if (output.trim()) {
|
|
2834
2842
|
const commits = parseGitLog(output);
|
|
2835
2843
|
let fullOutput = "";
|
|
2836
|
-
const title = `\u{1F4CA} \u5171\u663E\u793A ${commits.length} \u4E2A\u63D0\u4EA4`;
|
|
2837
|
-
fullOutput += "\n" + boxen3(title, {
|
|
2838
|
-
padding: { top: 0, bottom: 0, left: 2, right: 2 },
|
|
2839
|
-
margin: { top: 0, bottom: 1, left: 0, right: 0 },
|
|
2840
|
-
borderStyle: "double",
|
|
2841
|
-
borderColor: "green",
|
|
2842
|
-
textAlignment: "center"
|
|
2843
|
-
}) + "\n";
|
|
2844
2844
|
const timelineOutput = formatTimelineStyle(commits);
|
|
2845
2845
|
fullOutput += timelineOutput;
|
|
2846
2846
|
if (options.interactive) {
|
|
@@ -2916,7 +2916,7 @@ process.on("SIGTERM", () => {
|
|
|
2916
2916
|
console.log("");
|
|
2917
2917
|
process.exit(0);
|
|
2918
2918
|
});
|
|
2919
|
-
var version = true ? "0.3.
|
|
2919
|
+
var version = true ? "0.3.8" : "0.0.0-dev";
|
|
2920
2920
|
async function mainMenu() {
|
|
2921
2921
|
console.log(
|
|
2922
2922
|
colors.green(`
|
package/package.json
CHANGED
package/src/commands/commit.ts
CHANGED
|
@@ -1,4 +1,7 @@
|
|
|
1
1
|
import { execSync } from "child_process";
|
|
2
|
+
import { writeFileSync, unlinkSync } from "fs";
|
|
3
|
+
import { tmpdir } from "os";
|
|
4
|
+
import { join } from "path";
|
|
2
5
|
import { select, input, checkbox } from "@inquirer/prompts";
|
|
3
6
|
import ora from "ora";
|
|
4
7
|
import { colors, theme, execOutput, divider } from "../utils.js";
|
|
@@ -102,58 +105,52 @@ function formatFileStatus(status: string): string {
|
|
|
102
105
|
*/
|
|
103
106
|
export async function commit(): Promise<void> {
|
|
104
107
|
const config = getConfig();
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
// ========== 步骤 1: 处理未暂存的文件 ==========
|
|
108
|
-
if (unstaged.length > 0) {
|
|
109
|
-
const autoStage = config.autoStage ?? true;
|
|
108
|
+
const autoStage = config.autoStage ?? true;
|
|
110
109
|
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
divider();
|
|
116
|
-
// 重新获取状态
|
|
117
|
-
const newStatus = parseGitStatus();
|
|
118
|
-
staged = newStatus.staged;
|
|
119
|
-
unstaged = newStatus.unstaged;
|
|
120
|
-
} else if (staged.length === 0) {
|
|
121
|
-
// 没有暂存的文件,且不自动暂存,让用户选择
|
|
122
|
-
console.log(colors.yellow("没有暂存的更改"));
|
|
123
|
-
divider();
|
|
124
|
-
console.log("未暂存的文件:");
|
|
125
|
-
for (const { status, file } of unstaged) {
|
|
126
|
-
console.log(` ${formatFileStatus(status)} ${file}`);
|
|
127
|
-
}
|
|
128
|
-
divider();
|
|
110
|
+
// ========== 步骤 1: 自动暂存(如果启用)==========
|
|
111
|
+
if (autoStage) {
|
|
112
|
+
execSync("git add -A", { stdio: "pipe" });
|
|
113
|
+
}
|
|
129
114
|
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
message: "选择要暂存的文件:",
|
|
133
|
-
choices: unstaged.map(({ status, file }) => ({
|
|
134
|
-
name: `${formatFileStatus(status)} ${file}`,
|
|
135
|
-
value: file,
|
|
136
|
-
checked: true,
|
|
137
|
-
})),
|
|
138
|
-
theme,
|
|
139
|
-
});
|
|
115
|
+
// 获取当前状态
|
|
116
|
+
let { staged, unstaged } = parseGitStatus();
|
|
140
117
|
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
118
|
+
// ========== 步骤 2: 如果没有暂存文件,让用户选择 ==========
|
|
119
|
+
if (staged.length === 0 && unstaged.length > 0 && !autoStage) {
|
|
120
|
+
console.log(colors.yellow("没有暂存的更改"));
|
|
121
|
+
divider();
|
|
122
|
+
console.log("未暂存的文件:");
|
|
123
|
+
for (const { status, file } of unstaged) {
|
|
124
|
+
console.log(` ${formatFileStatus(status)} ${file}`);
|
|
125
|
+
}
|
|
126
|
+
divider();
|
|
127
|
+
|
|
128
|
+
// 让用户选择要暂存的文件
|
|
129
|
+
const filesToStage = await checkbox({
|
|
130
|
+
message: "选择要暂存的文件:",
|
|
131
|
+
choices: unstaged.map(({ status, file }) => ({
|
|
132
|
+
name: `${formatFileStatus(status)} ${file}`,
|
|
133
|
+
value: file,
|
|
134
|
+
checked: true,
|
|
135
|
+
})),
|
|
136
|
+
theme,
|
|
137
|
+
});
|
|
145
138
|
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
console.log(colors.green(`✔ 已暂存 ${filesToStage.length} 个文件`));
|
|
151
|
-
divider();
|
|
139
|
+
if (filesToStage.length === 0) {
|
|
140
|
+
console.log(colors.yellow("没有选择任何文件,已取消"));
|
|
141
|
+
return;
|
|
142
|
+
}
|
|
152
143
|
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
144
|
+
// 暂存选中的文件
|
|
145
|
+
for (const file of filesToStage) {
|
|
146
|
+
execSync(`git add "${file}"`, { stdio: "pipe" });
|
|
156
147
|
}
|
|
148
|
+
console.log(colors.green(`✔ 已暂存 ${filesToStage.length} 个文件`));
|
|
149
|
+
divider();
|
|
150
|
+
|
|
151
|
+
// 重新获取状态
|
|
152
|
+
const newStatus = parseGitStatus();
|
|
153
|
+
staged = newStatus.staged;
|
|
157
154
|
}
|
|
158
155
|
|
|
159
156
|
// ========== 步骤 2: 检查是否有文件可提交 ==========
|
|
@@ -264,18 +261,13 @@ export async function commit(): Promise<void> {
|
|
|
264
261
|
const spinner = ora("正在提交...").start();
|
|
265
262
|
|
|
266
263
|
try {
|
|
267
|
-
//
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
// 如果暂存区为空,但有未暂存的更改,且开启了自动暂存,则重新暂存
|
|
271
|
-
if (finalStatus.staged.length === 0 && finalStatus.unstaged.length > 0) {
|
|
272
|
-
const autoStage = config.autoStage ?? true;
|
|
273
|
-
if (autoStage) {
|
|
274
|
-
execSync("git add -A", { stdio: "pipe" });
|
|
275
|
-
finalStatus = parseGitStatus();
|
|
276
|
-
}
|
|
264
|
+
// 提交前再次暂存所有更改(确保不会遗漏)
|
|
265
|
+
if (autoStage) {
|
|
266
|
+
execSync("git add -A", { stdio: "pipe" });
|
|
277
267
|
}
|
|
278
268
|
|
|
269
|
+
// 检查是否有暂存的文件
|
|
270
|
+
const finalStatus = parseGitStatus();
|
|
279
271
|
if (finalStatus.staged.length === 0) {
|
|
280
272
|
spinner.fail("没有暂存的文件可以提交");
|
|
281
273
|
console.log("");
|
|
@@ -287,11 +279,21 @@ export async function commit(): Promise<void> {
|
|
|
287
279
|
return;
|
|
288
280
|
}
|
|
289
281
|
|
|
290
|
-
//
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
282
|
+
// 处理多行消息:使用临时文件传递 commit message
|
|
283
|
+
const tmpFile = join(tmpdir(), `.gw-commit-msg-${Date.now()}`);
|
|
284
|
+
try {
|
|
285
|
+
writeFileSync(tmpFile, message, "utf-8");
|
|
286
|
+
execSync(`git commit -F "${tmpFile}"`, {
|
|
287
|
+
stdio: ["pipe", "pipe", "pipe"],
|
|
288
|
+
});
|
|
289
|
+
} finally {
|
|
290
|
+
// 清理临时文件
|
|
291
|
+
try {
|
|
292
|
+
unlinkSync(tmpFile);
|
|
293
|
+
} catch {
|
|
294
|
+
// 忽略删除失败
|
|
295
|
+
}
|
|
296
|
+
}
|
|
295
297
|
spinner.succeed("提交成功");
|
|
296
298
|
|
|
297
299
|
// 显示提交信息
|
package/src/commands/log.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* @zjex/git-workflow - Log 命令
|
|
3
|
-
*
|
|
3
|
+
*
|
|
4
4
|
* 提供GitHub风格的时间线日志查看功能
|
|
5
5
|
*/
|
|
6
6
|
|
|
@@ -43,13 +43,13 @@ interface CommitInfo {
|
|
|
43
43
|
*/
|
|
44
44
|
function parseGitLog(output: string): CommitInfo[] {
|
|
45
45
|
const commits: CommitInfo[] = [];
|
|
46
|
-
const lines = output.trim().split(
|
|
47
|
-
|
|
46
|
+
const lines = output.trim().split("\n");
|
|
47
|
+
|
|
48
48
|
for (const line of lines) {
|
|
49
49
|
if (!line.trim()) continue;
|
|
50
|
-
|
|
50
|
+
|
|
51
51
|
// 使用分隔符解析
|
|
52
|
-
const parts = line.split(
|
|
52
|
+
const parts = line.split("|");
|
|
53
53
|
if (parts.length >= 6) {
|
|
54
54
|
commits.push({
|
|
55
55
|
hash: parts[0],
|
|
@@ -58,11 +58,11 @@ function parseGitLog(output: string): CommitInfo[] {
|
|
|
58
58
|
author: parts[3],
|
|
59
59
|
date: parts[4],
|
|
60
60
|
relativeDate: parts[5],
|
|
61
|
-
refs: parts[6] ||
|
|
61
|
+
refs: parts[6] || "",
|
|
62
62
|
});
|
|
63
63
|
}
|
|
64
64
|
}
|
|
65
|
-
|
|
65
|
+
|
|
66
66
|
return commits;
|
|
67
67
|
}
|
|
68
68
|
|
|
@@ -71,39 +71,50 @@ function parseGitLog(output: string): CommitInfo[] {
|
|
|
71
71
|
*/
|
|
72
72
|
function getCommitTypeIcon(subject: string): string {
|
|
73
73
|
const lowerSubject = subject.toLowerCase();
|
|
74
|
-
|
|
75
|
-
if (lowerSubject.includes(
|
|
76
|
-
|
|
77
|
-
if (lowerSubject.includes(
|
|
78
|
-
if (lowerSubject.includes(
|
|
79
|
-
|
|
80
|
-
if (lowerSubject.includes(
|
|
81
|
-
if (lowerSubject.includes(
|
|
82
|
-
if (lowerSubject.includes(
|
|
83
|
-
if (lowerSubject.includes(
|
|
84
|
-
if (lowerSubject.includes(
|
|
85
|
-
if (lowerSubject.includes(
|
|
86
|
-
if (lowerSubject.includes(
|
|
87
|
-
if (lowerSubject.includes(
|
|
88
|
-
|
|
89
|
-
|
|
74
|
+
|
|
75
|
+
if (lowerSubject.includes("feat") || lowerSubject.includes("feature"))
|
|
76
|
+
return "✨";
|
|
77
|
+
if (lowerSubject.includes("fix") || lowerSubject.includes("bug")) return "🐛";
|
|
78
|
+
if (lowerSubject.includes("docs") || lowerSubject.includes("doc"))
|
|
79
|
+
return "📚";
|
|
80
|
+
if (lowerSubject.includes("style")) return "💄";
|
|
81
|
+
if (lowerSubject.includes("refactor")) return "♻️";
|
|
82
|
+
if (lowerSubject.includes("test")) return "🧪";
|
|
83
|
+
if (lowerSubject.includes("chore")) return "🔧";
|
|
84
|
+
if (lowerSubject.includes("perf")) return "⚡";
|
|
85
|
+
if (lowerSubject.includes("ci")) return "👷";
|
|
86
|
+
if (lowerSubject.includes("build")) return "📦";
|
|
87
|
+
if (lowerSubject.includes("revert")) return "⏪";
|
|
88
|
+
if (lowerSubject.includes("merge")) return "🔀";
|
|
89
|
+
if (lowerSubject.includes("release") || lowerSubject.includes("version"))
|
|
90
|
+
return "🔖";
|
|
91
|
+
|
|
92
|
+
return "📝";
|
|
90
93
|
}
|
|
91
94
|
|
|
92
95
|
/**
|
|
93
|
-
*
|
|
96
|
+
* 按日期分组提交(保持原始提交顺序)
|
|
94
97
|
*/
|
|
95
98
|
function groupCommitsByDate(commits: CommitInfo[]): Map<string, CommitInfo[]> {
|
|
96
99
|
const groups = new Map<string, CommitInfo[]>();
|
|
97
|
-
|
|
100
|
+
const dateOrder: string[] = []; // 记录日期出现的顺序
|
|
101
|
+
|
|
98
102
|
for (const commit of commits) {
|
|
99
103
|
const date = commit.date;
|
|
100
104
|
if (!groups.has(date)) {
|
|
101
105
|
groups.set(date, []);
|
|
106
|
+
dateOrder.push(date); // 按出现顺序记录日期
|
|
102
107
|
}
|
|
103
108
|
groups.get(date)!.push(commit);
|
|
104
109
|
}
|
|
105
|
-
|
|
106
|
-
|
|
110
|
+
|
|
111
|
+
// 返回按出现顺序排列的 Map
|
|
112
|
+
const orderedGroups = new Map<string, CommitInfo[]>();
|
|
113
|
+
for (const date of dateOrder) {
|
|
114
|
+
orderedGroups.set(date, groups.get(date)!);
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
return orderedGroups;
|
|
107
118
|
}
|
|
108
119
|
|
|
109
120
|
/**
|
|
@@ -111,92 +122,101 @@ function groupCommitsByDate(commits: CommitInfo[]): Map<string, CommitInfo[]> {
|
|
|
111
122
|
*/
|
|
112
123
|
function formatRelativeTime(relativeDate: string): string {
|
|
113
124
|
let result = relativeDate;
|
|
114
|
-
|
|
125
|
+
|
|
115
126
|
// 先替换英文单词为中文
|
|
116
127
|
const timeMap: { [key: string]: string } = {
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
128
|
+
second: "秒",
|
|
129
|
+
seconds: "秒",
|
|
130
|
+
minute: "分钟",
|
|
131
|
+
minutes: "分钟",
|
|
132
|
+
hour: "小时",
|
|
133
|
+
hours: "小时",
|
|
134
|
+
day: "天",
|
|
135
|
+
days: "天",
|
|
136
|
+
week: "周",
|
|
137
|
+
weeks: "周",
|
|
138
|
+
month: "个月",
|
|
139
|
+
months: "个月",
|
|
140
|
+
year: "年",
|
|
141
|
+
years: "年",
|
|
142
|
+
ago: "前",
|
|
132
143
|
};
|
|
133
|
-
|
|
144
|
+
|
|
134
145
|
for (const [en, zh] of Object.entries(timeMap)) {
|
|
135
|
-
result = result.replace(new RegExp(`\\b${en}\\b`,
|
|
146
|
+
result = result.replace(new RegExp(`\\b${en}\\b`, "g"), zh);
|
|
136
147
|
}
|
|
137
|
-
|
|
148
|
+
|
|
138
149
|
// 去掉数字和单位之间的空格,以及单位和"前"之间的空格
|
|
139
150
|
// 例如:"22 分钟 前" -> "22分钟前"
|
|
140
|
-
result = result.replace(
|
|
141
|
-
|
|
151
|
+
result = result.replace(
|
|
152
|
+
/(\d+)\s+(秒|分钟|小时|天|周|个月|年)\s+前/g,
|
|
153
|
+
"$1$2前"
|
|
154
|
+
);
|
|
155
|
+
|
|
142
156
|
// 简化显示格式
|
|
143
157
|
const match = result.match(/(\d+)(分钟|小时|天|周|个月|年)前/);
|
|
144
158
|
if (match) {
|
|
145
159
|
const num = parseInt(match[1]);
|
|
146
160
|
const unit = match[2];
|
|
147
|
-
|
|
161
|
+
|
|
148
162
|
// 超过60分钟显示小时
|
|
149
|
-
if (unit ===
|
|
163
|
+
if (unit === "分钟" && num >= 60) {
|
|
150
164
|
const hours = Math.floor(num / 60);
|
|
151
165
|
return `${hours}小时前`;
|
|
152
166
|
}
|
|
153
|
-
|
|
167
|
+
|
|
154
168
|
// 超过24小时显示天数
|
|
155
|
-
if (unit ===
|
|
169
|
+
if (unit === "小时" && num >= 24) {
|
|
156
170
|
const days = Math.floor(num / 24);
|
|
157
171
|
return `${days}天前`;
|
|
158
172
|
}
|
|
159
|
-
|
|
173
|
+
|
|
160
174
|
// 超过7天显示周数
|
|
161
|
-
if (unit ===
|
|
175
|
+
if (unit === "天" && num >= 7 && num < 30) {
|
|
162
176
|
const weeks = Math.floor(num / 7);
|
|
163
177
|
return `${weeks}周前`;
|
|
164
178
|
}
|
|
165
|
-
|
|
179
|
+
|
|
166
180
|
// 超过30天显示月数
|
|
167
|
-
if (unit ===
|
|
181
|
+
if (unit === "天" && num >= 30) {
|
|
168
182
|
const months = Math.floor(num / 30);
|
|
169
183
|
return `${months}个月前`;
|
|
170
184
|
}
|
|
171
|
-
|
|
185
|
+
|
|
172
186
|
// 超过4周显示月数
|
|
173
|
-
if (unit ===
|
|
187
|
+
if (unit === "周" && num >= 4) {
|
|
174
188
|
const months = Math.floor(num / 4);
|
|
175
189
|
return `${months}个月前`;
|
|
176
190
|
}
|
|
177
|
-
|
|
191
|
+
|
|
178
192
|
// 超过12个月显示年数
|
|
179
|
-
if (unit ===
|
|
193
|
+
if (unit === "个月" && num >= 12) {
|
|
180
194
|
const years = Math.floor(num / 12);
|
|
181
195
|
return `${years}年前`;
|
|
182
196
|
}
|
|
183
197
|
}
|
|
184
|
-
|
|
198
|
+
|
|
185
199
|
return result;
|
|
186
200
|
}
|
|
187
201
|
|
|
188
202
|
/**
|
|
189
203
|
* 解析提交主题,分离标题和子任务
|
|
190
204
|
*/
|
|
191
|
-
function parseCommitSubject(subject: string): {
|
|
205
|
+
function parseCommitSubject(subject: string): {
|
|
206
|
+
title: string;
|
|
207
|
+
tasks: string[];
|
|
208
|
+
} {
|
|
192
209
|
// 检查是否包含 " - " 分隔的子任务
|
|
193
|
-
if (subject.includes(
|
|
194
|
-
const parts = subject.split(
|
|
210
|
+
if (subject.includes(" - ")) {
|
|
211
|
+
const parts = subject.split(" - ");
|
|
195
212
|
const title = parts[0].trim();
|
|
196
|
-
const tasks = parts
|
|
213
|
+
const tasks = parts
|
|
214
|
+
.slice(1)
|
|
215
|
+
.map((task) => task.trim())
|
|
216
|
+
.filter((task) => task.length > 0);
|
|
197
217
|
return { title, tasks };
|
|
198
218
|
}
|
|
199
|
-
|
|
219
|
+
|
|
200
220
|
return { title: subject, tasks: [] };
|
|
201
221
|
}
|
|
202
222
|
|
|
@@ -213,137 +233,146 @@ function supportsColor(): boolean {
|
|
|
213
233
|
*/
|
|
214
234
|
function formatTimelineStyle(commits: CommitInfo[]): string {
|
|
215
235
|
const groupedCommits = groupCommitsByDate(commits);
|
|
216
|
-
let output =
|
|
217
|
-
|
|
218
|
-
//
|
|
219
|
-
const sortedDates = Array.from(groupedCommits.keys())
|
|
220
|
-
|
|
221
|
-
);
|
|
222
|
-
|
|
236
|
+
let output = "";
|
|
237
|
+
|
|
238
|
+
// 保持原始提交顺序(已在 groupCommitsByDate 中按出现顺序排列)
|
|
239
|
+
const sortedDates = Array.from(groupedCommits.keys());
|
|
240
|
+
|
|
223
241
|
const useColors = supportsColor() || process.env.FORCE_COLOR;
|
|
224
|
-
|
|
242
|
+
|
|
225
243
|
for (let dateIndex = 0; dateIndex < sortedDates.length; dateIndex++) {
|
|
226
244
|
const date = sortedDates[dateIndex];
|
|
227
245
|
const dateCommits = groupedCommits.get(date)!;
|
|
228
|
-
|
|
246
|
+
|
|
229
247
|
// 日期标题 - 使用黄色突出显示
|
|
230
248
|
const dateTitle = `📅 Commits on ${date}`;
|
|
231
249
|
if (useColors) {
|
|
232
|
-
output +=
|
|
250
|
+
output += colors.bold(colors.yellow(dateTitle)) + "\n";
|
|
233
251
|
} else {
|
|
234
|
-
output +=
|
|
252
|
+
output += dateTitle + "\n";
|
|
235
253
|
}
|
|
236
|
-
|
|
254
|
+
|
|
237
255
|
// 该日期下的提交
|
|
238
256
|
for (let commitIndex = 0; commitIndex < dateCommits.length; commitIndex++) {
|
|
239
257
|
const commit = dateCommits[commitIndex];
|
|
240
|
-
const icon = getCommitTypeIcon(commit.subject);
|
|
241
258
|
const { title, tasks } = parseCommitSubject(commit.subject);
|
|
242
|
-
|
|
259
|
+
|
|
243
260
|
// 构建提交内容
|
|
244
261
|
const commitContent = [];
|
|
245
|
-
|
|
246
|
-
// 主标题 -
|
|
262
|
+
|
|
263
|
+
// 主标题 - 使用白色加粗,保持原始提交信息
|
|
247
264
|
if (useColors) {
|
|
248
|
-
commitContent.push(
|
|
265
|
+
commitContent.push(colors.bold(colors.white(title)));
|
|
249
266
|
} else {
|
|
250
|
-
commitContent.push(
|
|
267
|
+
commitContent.push(title);
|
|
251
268
|
}
|
|
252
|
-
|
|
269
|
+
|
|
253
270
|
// 如果有子任务,添加子任务列表
|
|
254
271
|
if (tasks.length > 0) {
|
|
255
|
-
commitContent.push(
|
|
256
|
-
tasks.forEach(task => {
|
|
272
|
+
commitContent.push(""); // 空行分隔
|
|
273
|
+
tasks.forEach((task) => {
|
|
257
274
|
if (useColors) {
|
|
258
|
-
commitContent.push(` ${colors.dim(
|
|
275
|
+
commitContent.push(` ${colors.dim("–")} ${colors.dim(task)}`);
|
|
259
276
|
} else {
|
|
260
277
|
commitContent.push(` – ${task}`);
|
|
261
278
|
}
|
|
262
279
|
});
|
|
263
280
|
}
|
|
264
|
-
|
|
281
|
+
|
|
265
282
|
// 空行分隔
|
|
266
|
-
commitContent.push(
|
|
267
|
-
|
|
283
|
+
commitContent.push("");
|
|
284
|
+
|
|
268
285
|
// 作者和时间信息
|
|
269
286
|
if (useColors) {
|
|
270
|
-
commitContent.push(
|
|
271
|
-
|
|
272
|
-
|
|
287
|
+
commitContent.push(
|
|
288
|
+
`${colors.dim("👤")} ${colors.blue(commit.author)} ${colors.dim(
|
|
289
|
+
"committed"
|
|
290
|
+
)} ${colors.green(formatRelativeTime(commit.relativeDate))}`
|
|
291
|
+
);
|
|
292
|
+
// Hash信息 - 使用橙色,显示完整 hash
|
|
293
|
+
commitContent.push(`${colors.dim("🔗")} ${colors.orange(commit.hash)}`);
|
|
273
294
|
// 如果有分支/标签信息 - 区分显示
|
|
274
295
|
if (commit.refs && commit.refs.trim()) {
|
|
275
296
|
const refs = commit.refs.trim();
|
|
276
297
|
// 解析并分别显示分支和标签
|
|
277
|
-
const refParts = refs.split(
|
|
298
|
+
const refParts = refs.split(", ");
|
|
278
299
|
const branches: string[] = [];
|
|
279
300
|
const tags: string[] = [];
|
|
280
|
-
|
|
281
|
-
refParts.forEach(ref => {
|
|
282
|
-
if (ref.startsWith(
|
|
283
|
-
tags.push(ref.replace(
|
|
284
|
-
} else if (ref.includes(
|
|
301
|
+
|
|
302
|
+
refParts.forEach((ref) => {
|
|
303
|
+
if (ref.startsWith("tag: ")) {
|
|
304
|
+
tags.push(ref.replace("tag: ", ""));
|
|
305
|
+
} else if (ref.includes("/") || ref === "HEAD") {
|
|
285
306
|
branches.push(ref);
|
|
286
307
|
} else {
|
|
287
308
|
branches.push(ref);
|
|
288
309
|
}
|
|
289
310
|
});
|
|
290
|
-
|
|
311
|
+
|
|
291
312
|
// 显示分支信息
|
|
292
313
|
if (branches.length > 0) {
|
|
293
|
-
commitContent.push(
|
|
314
|
+
commitContent.push(
|
|
315
|
+
`${colors.dim("🌿")} ${colors.lightPurple(branches.join(", "))}`
|
|
316
|
+
);
|
|
294
317
|
}
|
|
295
|
-
|
|
318
|
+
|
|
296
319
|
// 显示标签信息
|
|
297
320
|
if (tags.length > 0) {
|
|
298
|
-
const tagText = tags.map(tag => `tag ${tag}`).join(
|
|
299
|
-
commitContent.push(
|
|
321
|
+
const tagText = tags.map((tag) => `tag ${tag}`).join(", ");
|
|
322
|
+
commitContent.push(
|
|
323
|
+
`${colors.dim("🔖")} ${colors.brightYellow(tagText)}`
|
|
324
|
+
);
|
|
300
325
|
}
|
|
301
326
|
}
|
|
302
327
|
} else {
|
|
303
|
-
commitContent.push(
|
|
304
|
-
|
|
328
|
+
commitContent.push(
|
|
329
|
+
`👤 ${commit.author} committed ${formatRelativeTime(
|
|
330
|
+
commit.relativeDate
|
|
331
|
+
)}`
|
|
332
|
+
);
|
|
333
|
+
commitContent.push(`🔗 ${commit.hash}`);
|
|
305
334
|
if (commit.refs && commit.refs.trim()) {
|
|
306
335
|
const refs = commit.refs.trim();
|
|
307
336
|
// 解析并分别显示分支和标签
|
|
308
|
-
const refParts = refs.split(
|
|
337
|
+
const refParts = refs.split(", ");
|
|
309
338
|
const branches: string[] = [];
|
|
310
339
|
const tags: string[] = [];
|
|
311
|
-
|
|
312
|
-
refParts.forEach(ref => {
|
|
313
|
-
if (ref.startsWith(
|
|
314
|
-
tags.push(ref.replace(
|
|
315
|
-
} else if (ref.includes(
|
|
340
|
+
|
|
341
|
+
refParts.forEach((ref) => {
|
|
342
|
+
if (ref.startsWith("tag: ")) {
|
|
343
|
+
tags.push(ref.replace("tag: ", ""));
|
|
344
|
+
} else if (ref.includes("/") || ref === "HEAD") {
|
|
316
345
|
branches.push(ref);
|
|
317
346
|
} else {
|
|
318
347
|
branches.push(ref);
|
|
319
348
|
}
|
|
320
349
|
});
|
|
321
|
-
|
|
350
|
+
|
|
322
351
|
// 显示分支信息
|
|
323
352
|
if (branches.length > 0) {
|
|
324
|
-
commitContent.push(`🌿 ${branches.join(
|
|
353
|
+
commitContent.push(`🌿 ${branches.join(", ")}`);
|
|
325
354
|
}
|
|
326
|
-
|
|
355
|
+
|
|
327
356
|
// 显示标签信息
|
|
328
357
|
if (tags.length > 0) {
|
|
329
|
-
const tagText = tags.map(tag => `tag ${tag}`).join(
|
|
358
|
+
const tagText = tags.map((tag) => `tag ${tag}`).join(", ");
|
|
330
359
|
commitContent.push(`🔖 ${tagText}`);
|
|
331
360
|
}
|
|
332
361
|
}
|
|
333
362
|
}
|
|
334
|
-
|
|
363
|
+
|
|
335
364
|
// 使用boxen
|
|
336
|
-
const commitBox = boxen(commitContent.join(
|
|
365
|
+
const commitBox = boxen(commitContent.join("\n"), {
|
|
337
366
|
padding: { top: 0, bottom: 0, left: 1, right: 1 },
|
|
338
|
-
margin: { top: 0, bottom:
|
|
339
|
-
borderStyle:
|
|
340
|
-
borderColor:
|
|
367
|
+
margin: { top: 0, bottom: 0, left: 0, right: 0 },
|
|
368
|
+
borderStyle: "round",
|
|
369
|
+
borderColor: "gray",
|
|
341
370
|
});
|
|
342
|
-
|
|
343
|
-
output += commitBox +
|
|
371
|
+
|
|
372
|
+
output += commitBox + "\n";
|
|
344
373
|
}
|
|
345
374
|
}
|
|
346
|
-
|
|
375
|
+
|
|
347
376
|
return output;
|
|
348
377
|
}
|
|
349
378
|
|
|
@@ -352,34 +381,42 @@ function formatTimelineStyle(commits: CommitInfo[]): string {
|
|
|
352
381
|
*/
|
|
353
382
|
function startInteractivePager(content: string): void {
|
|
354
383
|
// 使用系统的 less 命令作为分页器,启用颜色支持
|
|
355
|
-
const pager = process.env.PAGER ||
|
|
356
|
-
|
|
384
|
+
const pager = process.env.PAGER || "less";
|
|
385
|
+
|
|
357
386
|
try {
|
|
358
387
|
// -R: 支持ANSI颜色代码
|
|
359
388
|
// -S: 不换行长行
|
|
360
389
|
// -F: 如果内容少于一屏则直接退出
|
|
361
390
|
// -X: 不清屏
|
|
362
391
|
// -i: 忽略大小写搜索
|
|
363
|
-
const pagerProcess = spawn(pager, [
|
|
364
|
-
stdio: [
|
|
365
|
-
env: { ...process.env, LESS:
|
|
392
|
+
const pagerProcess = spawn(pager, ["-R", "-S", "-F", "-X", "-i"], {
|
|
393
|
+
stdio: ["pipe", "inherit", "inherit"],
|
|
394
|
+
env: { ...process.env, LESS: "-R -S -F -X -i" },
|
|
366
395
|
});
|
|
367
|
-
|
|
396
|
+
|
|
397
|
+
// 处理 stdin 的 EPIPE 错误(当 less 提前退出时)
|
|
398
|
+
pagerProcess.stdin.on("error", (err: NodeJS.ErrnoException) => {
|
|
399
|
+
if (err.code !== "EPIPE") {
|
|
400
|
+
// 只忽略 EPIPE 错误,其他错误输出
|
|
401
|
+
console.error(err);
|
|
402
|
+
}
|
|
403
|
+
});
|
|
404
|
+
|
|
368
405
|
// 将内容写入分页器
|
|
369
406
|
pagerProcess.stdin.write(content);
|
|
370
407
|
pagerProcess.stdin.end();
|
|
371
|
-
|
|
408
|
+
|
|
372
409
|
// 处理分页器退出
|
|
373
|
-
pagerProcess.on(
|
|
374
|
-
//
|
|
410
|
+
pagerProcess.on("exit", () => {
|
|
411
|
+
// 分页器退出后正常退出程序
|
|
412
|
+
process.exit(0);
|
|
375
413
|
});
|
|
376
|
-
|
|
414
|
+
|
|
377
415
|
// 处理错误
|
|
378
|
-
pagerProcess.on(
|
|
416
|
+
pagerProcess.on("error", () => {
|
|
379
417
|
// 如果分页器启动失败,直接输出内容
|
|
380
418
|
console.log(content);
|
|
381
419
|
});
|
|
382
|
-
|
|
383
420
|
} catch (error) {
|
|
384
421
|
// 如果出错,直接输出内容
|
|
385
422
|
console.log(content);
|
|
@@ -393,7 +430,7 @@ function executeTimelineLog(options: LogOptions): void {
|
|
|
393
430
|
try {
|
|
394
431
|
// 构建Git命令
|
|
395
432
|
let cmd = 'git log --pretty=format:"%H|%h|%s|%an|%ad|%ar|%D" --date=short';
|
|
396
|
-
|
|
433
|
+
|
|
397
434
|
// 添加选项
|
|
398
435
|
if (options.limit && !options.interactive) cmd += ` -${options.limit}`;
|
|
399
436
|
if (options.author) cmd += ` --author="${options.author}"`;
|
|
@@ -401,53 +438,41 @@ function executeTimelineLog(options: LogOptions): void {
|
|
|
401
438
|
if (options.until) cmd += ` --until="${options.until}"`;
|
|
402
439
|
if (options.grep) cmd += ` --grep="${options.grep}"`;
|
|
403
440
|
if (options.all) cmd += ` --all`;
|
|
404
|
-
|
|
405
|
-
// 交互式模式默认显示更多提交
|
|
406
|
-
if (options.interactive && !options.limit) {
|
|
407
|
-
cmd += ` -50`; // 默认显示50个提交
|
|
408
|
-
}
|
|
409
441
|
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
442
|
+
// 交互式模式默认显示全部提交(不限制数量)
|
|
443
|
+
|
|
444
|
+
const output = execSync(cmd, {
|
|
445
|
+
encoding: "utf8",
|
|
446
|
+
stdio: "pipe",
|
|
447
|
+
maxBuffer: 1024 * 1024 * 10,
|
|
414
448
|
});
|
|
415
449
|
|
|
416
450
|
if (output.trim()) {
|
|
417
451
|
const commits = parseGitLog(output);
|
|
418
|
-
|
|
452
|
+
|
|
419
453
|
// 构建完整输出
|
|
420
|
-
let fullOutput =
|
|
421
|
-
|
|
422
|
-
// 显示标题
|
|
423
|
-
const title = `📊 共显示 ${commits.length} 个提交`;
|
|
424
|
-
fullOutput += '\n' + boxen(title, {
|
|
425
|
-
padding: { top: 0, bottom: 0, left: 2, right: 2 },
|
|
426
|
-
margin: { top: 0, bottom: 1, left: 0, right: 0 },
|
|
427
|
-
borderStyle: 'double',
|
|
428
|
-
borderColor: 'green',
|
|
429
|
-
textAlignment: 'center'
|
|
430
|
-
}) + '\n';
|
|
431
|
-
|
|
454
|
+
let fullOutput = "";
|
|
455
|
+
|
|
432
456
|
// 显示时间线
|
|
433
457
|
const timelineOutput = formatTimelineStyle(commits);
|
|
434
458
|
fullOutput += timelineOutput;
|
|
435
|
-
|
|
459
|
+
|
|
436
460
|
// 根据是否交互式模式选择输出方式
|
|
437
461
|
if (options.interactive) {
|
|
438
462
|
startInteractivePager(fullOutput);
|
|
439
463
|
} else {
|
|
440
464
|
console.log(fullOutput);
|
|
441
465
|
}
|
|
442
|
-
|
|
443
466
|
} else {
|
|
444
|
-
const noCommitsMsg =
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
467
|
+
const noCommitsMsg =
|
|
468
|
+
"\n" +
|
|
469
|
+
boxen("📭 没有找到匹配的提交记录", {
|
|
470
|
+
padding: { top: 0, bottom: 0, left: 2, right: 2 },
|
|
471
|
+
borderStyle: "round",
|
|
472
|
+
borderColor: "yellow",
|
|
473
|
+
textAlignment: "center",
|
|
474
|
+
});
|
|
475
|
+
|
|
451
476
|
if (options.interactive) {
|
|
452
477
|
startInteractivePager(noCommitsMsg);
|
|
453
478
|
} else {
|
|
@@ -455,20 +480,22 @@ function executeTimelineLog(options: LogOptions): void {
|
|
|
455
480
|
}
|
|
456
481
|
}
|
|
457
482
|
} catch (error: any) {
|
|
458
|
-
let errorMessage =
|
|
483
|
+
let errorMessage = "❌ 执行失败";
|
|
459
484
|
if (error.status === 128) {
|
|
460
|
-
errorMessage =
|
|
485
|
+
errorMessage = "❌ Git仓库错误或没有提交记录";
|
|
461
486
|
} else {
|
|
462
487
|
errorMessage = `❌ 执行失败: ${error.message}`;
|
|
463
488
|
}
|
|
464
|
-
|
|
465
|
-
const errorBox =
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
489
|
+
|
|
490
|
+
const errorBox =
|
|
491
|
+
"\n" +
|
|
492
|
+
boxen(errorMessage, {
|
|
493
|
+
padding: { top: 0, bottom: 0, left: 2, right: 2 },
|
|
494
|
+
borderStyle: "round",
|
|
495
|
+
borderColor: "red",
|
|
496
|
+
textAlignment: "center",
|
|
497
|
+
});
|
|
498
|
+
|
|
472
499
|
if (options.interactive) {
|
|
473
500
|
startInteractivePager(errorBox);
|
|
474
501
|
} else {
|
|
@@ -485,12 +512,12 @@ export async function log(options: LogOptions = {}): Promise<void> {
|
|
|
485
512
|
if (options.interactive === undefined) {
|
|
486
513
|
options.interactive = true;
|
|
487
514
|
}
|
|
488
|
-
|
|
515
|
+
|
|
489
516
|
// 交互式模式下不设置默认limit
|
|
490
517
|
if (!options.interactive && !options.limit) {
|
|
491
518
|
options.limit = 10;
|
|
492
519
|
}
|
|
493
|
-
|
|
520
|
+
|
|
494
521
|
executeTimelineLog(options);
|
|
495
522
|
}
|
|
496
523
|
|
|
@@ -500,4 +527,4 @@ export async function log(options: LogOptions = {}): Promise<void> {
|
|
|
500
527
|
export async function quickLog(limit: number = 10): Promise<void> {
|
|
501
528
|
const options: LogOptions = { limit };
|
|
502
529
|
executeTimelineLog(options);
|
|
503
|
-
}
|
|
530
|
+
}
|
package/src/index.ts
CHANGED
|
@@ -356,11 +356,11 @@ cli
|
|
|
356
356
|
.action(async (options: any) => {
|
|
357
357
|
await checkForUpdates(version, "@zjex/git-workflow");
|
|
358
358
|
checkGitRepo();
|
|
359
|
-
|
|
359
|
+
|
|
360
360
|
// 构建选项对象 - 默认交互式模式
|
|
361
361
|
const logOptions: any = { interactive: true };
|
|
362
362
|
if (options.limit) logOptions.limit = parseInt(options.limit);
|
|
363
|
-
|
|
363
|
+
|
|
364
364
|
return log(logOptions);
|
|
365
365
|
});
|
|
366
366
|
|
package/src/utils.ts
CHANGED
|
@@ -12,6 +12,7 @@ export interface Colors {
|
|
|
12
12
|
orange: (s: string) => string;
|
|
13
13
|
lightPurple: (s: string) => string;
|
|
14
14
|
white: (s: string) => string;
|
|
15
|
+
brightYellow: (s: string) => string;
|
|
15
16
|
reset: string;
|
|
16
17
|
}
|
|
17
18
|
|
|
@@ -27,6 +28,7 @@ export const colors: Colors = {
|
|
|
27
28
|
orange: (s) => `\x1b[38;5;208m${s}\x1b[0m`,
|
|
28
29
|
lightPurple: (s) => `\x1b[38;5;141m${s}\x1b[0m`,
|
|
29
30
|
white: (s) => `\x1b[37m${s}\x1b[0m`,
|
|
31
|
+
brightYellow: (s) => `\x1b[93m${s}\x1b[0m`, // 亮黄色
|
|
30
32
|
reset: "\x1b[0m",
|
|
31
33
|
};
|
|
32
34
|
|
package/tsup.config.ts
CHANGED
|
@@ -20,7 +20,7 @@ export default defineConfig({
|
|
|
20
20
|
"cac",
|
|
21
21
|
],
|
|
22
22
|
banner: {
|
|
23
|
-
js: "#!/usr/bin/env -S node --
|
|
23
|
+
js: "#!/usr/bin/env -S node --disable-warning=ExperimentalWarning",
|
|
24
24
|
},
|
|
25
25
|
define: {
|
|
26
26
|
__VERSION__: JSON.stringify(pkg.version),
|