@zjex/git-workflow 0.4.2 → 0.4.3
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 +11 -0
- package/README.md +1 -1
- package/dist/index.js +323 -11
- package/docs/.vitepress/config.ts +2 -0
- package/docs/commands/amend-date.md +425 -0
- package/docs/commands/amend.md +380 -0
- package/docs/commands/index.md +14 -10
- package/package.json +1 -1
- package/src/commands/amend-date.ts +228 -0
- package/src/commands/amend.ts +189 -0
- package/src/index.ts +39 -4
- package/tests/amend-date.test.ts +364 -0
- package/tests/amend.test.ts +441 -0
|
@@ -0,0 +1,441 @@
|
|
|
1
|
+
import { describe, it, expect, beforeEach, afterEach } from "vitest";
|
|
2
|
+
import { execSync } from "child_process";
|
|
3
|
+
import { mkdtempSync, rmSync } from "fs";
|
|
4
|
+
import { join } from "path";
|
|
5
|
+
import { tmpdir } from "os";
|
|
6
|
+
|
|
7
|
+
describe("amend command", () => {
|
|
8
|
+
let testDir: string;
|
|
9
|
+
let originalCwd: string;
|
|
10
|
+
|
|
11
|
+
beforeEach(() => {
|
|
12
|
+
// 保存当前工作目录
|
|
13
|
+
originalCwd = process.cwd();
|
|
14
|
+
|
|
15
|
+
// 创建临时测试目录
|
|
16
|
+
testDir = mkdtempSync(join(tmpdir(), "gw-test-"));
|
|
17
|
+
process.chdir(testDir);
|
|
18
|
+
|
|
19
|
+
// 初始化 git 仓库
|
|
20
|
+
execSync("git init", { stdio: "pipe" });
|
|
21
|
+
execSync('git config user.email "test@example.com"', { stdio: "pipe" });
|
|
22
|
+
execSync('git config user.name "Test User"', { stdio: "pipe" });
|
|
23
|
+
|
|
24
|
+
// 创建初始提交
|
|
25
|
+
execSync("echo 'test' > test.txt", { stdio: "pipe" });
|
|
26
|
+
execSync("git add .", { stdio: "pipe" });
|
|
27
|
+
execSync('git commit -m "Initial commit"', { stdio: "pipe" });
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
afterEach(() => {
|
|
31
|
+
// 恢复工作目录
|
|
32
|
+
process.chdir(originalCwd);
|
|
33
|
+
|
|
34
|
+
// 清理测试目录
|
|
35
|
+
try {
|
|
36
|
+
rmSync(testDir, { recursive: true, force: true });
|
|
37
|
+
} catch {
|
|
38
|
+
// 忽略清理错误
|
|
39
|
+
}
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
describe("基础功能", () => {
|
|
43
|
+
it("should have commits in test repo", () => {
|
|
44
|
+
const log = execSync("git log --oneline", { encoding: "utf-8" });
|
|
45
|
+
expect(log).toContain("Initial commit");
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
it("should be able to get commit message", () => {
|
|
49
|
+
const message = execSync('git log -1 --format="%s"', {
|
|
50
|
+
encoding: "utf-8",
|
|
51
|
+
});
|
|
52
|
+
expect(message.trim()).toBe("Initial commit");
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
it("should get commit with full format", () => {
|
|
56
|
+
const output = execSync('git log -1 --format="%H|%s|%ai"', {
|
|
57
|
+
encoding: "utf-8",
|
|
58
|
+
}).trim();
|
|
59
|
+
|
|
60
|
+
const [hash, message, date] = output.split("|");
|
|
61
|
+
|
|
62
|
+
expect(hash).toBeTruthy();
|
|
63
|
+
expect(hash).toHaveLength(40); // 完整 hash 长度
|
|
64
|
+
expect(message).toBe("Initial commit");
|
|
65
|
+
expect(date).toMatch(/^\d{4}-\d{2}-\d{2}/);
|
|
66
|
+
});
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
describe("修改 commit message", () => {
|
|
70
|
+
it("should be able to amend commit message", () => {
|
|
71
|
+
const newMessage = "Updated commit message";
|
|
72
|
+
|
|
73
|
+
execSync(`git commit --amend -m "${newMessage}"`, { stdio: "pipe" });
|
|
74
|
+
|
|
75
|
+
const message = execSync('git log -1 --format="%s"', {
|
|
76
|
+
encoding: "utf-8",
|
|
77
|
+
}).trim();
|
|
78
|
+
|
|
79
|
+
expect(message).toBe(newMessage);
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
it("should change commit hash when amending message", () => {
|
|
83
|
+
const beforeHash = execSync("git rev-parse HEAD", {
|
|
84
|
+
encoding: "utf-8",
|
|
85
|
+
}).trim();
|
|
86
|
+
|
|
87
|
+
execSync('git commit --amend -m "New message"', { stdio: "pipe" });
|
|
88
|
+
|
|
89
|
+
const afterHash = execSync("git rev-parse HEAD", {
|
|
90
|
+
encoding: "utf-8",
|
|
91
|
+
}).trim();
|
|
92
|
+
|
|
93
|
+
expect(beforeHash).not.toBe(afterHash);
|
|
94
|
+
});
|
|
95
|
+
|
|
96
|
+
it("should preserve commit date when amending message", () => {
|
|
97
|
+
const beforeDate = execSync('git log -1 --format="%ai"', {
|
|
98
|
+
encoding: "utf-8",
|
|
99
|
+
}).trim();
|
|
100
|
+
|
|
101
|
+
execSync('git commit --amend -m "New message"', { stdio: "pipe" });
|
|
102
|
+
|
|
103
|
+
const afterDate = execSync('git log -1 --format="%ai"', {
|
|
104
|
+
encoding: "utf-8",
|
|
105
|
+
}).trim();
|
|
106
|
+
|
|
107
|
+
// Author date 应该保持不变
|
|
108
|
+
expect(beforeDate).toBe(afterDate);
|
|
109
|
+
});
|
|
110
|
+
|
|
111
|
+
it("should handle multiline commit messages", () => {
|
|
112
|
+
const newMessage = "feat: add feature\\n\\nDetailed description";
|
|
113
|
+
|
|
114
|
+
execSync(`git commit --amend -m "${newMessage}"`, { stdio: "pipe" });
|
|
115
|
+
|
|
116
|
+
const message = execSync('git log -1 --format="%s"', {
|
|
117
|
+
encoding: "utf-8",
|
|
118
|
+
}).trim();
|
|
119
|
+
|
|
120
|
+
expect(message).toContain("feat: add feature");
|
|
121
|
+
});
|
|
122
|
+
});
|
|
123
|
+
|
|
124
|
+
describe("特殊字符处理", () => {
|
|
125
|
+
it("should escape special characters in commit message", () => {
|
|
126
|
+
const newMessage = 'feat: add "login" feature';
|
|
127
|
+
|
|
128
|
+
execSync(`git commit --amend -m "${newMessage.replace(/"/g, '\\"')}"`, {
|
|
129
|
+
stdio: "pipe",
|
|
130
|
+
});
|
|
131
|
+
|
|
132
|
+
const message = execSync('git log -1 --format="%s"', {
|
|
133
|
+
encoding: "utf-8",
|
|
134
|
+
}).trim();
|
|
135
|
+
|
|
136
|
+
expect(message).toBe(newMessage);
|
|
137
|
+
});
|
|
138
|
+
|
|
139
|
+
it("should handle single quotes", () => {
|
|
140
|
+
const newMessage = "feat: add 'login' feature";
|
|
141
|
+
|
|
142
|
+
execSync(`git commit --amend -m "${newMessage}"`, { stdio: "pipe" });
|
|
143
|
+
|
|
144
|
+
const message = execSync('git log -1 --format="%s"', {
|
|
145
|
+
encoding: "utf-8",
|
|
146
|
+
}).trim();
|
|
147
|
+
|
|
148
|
+
expect(message).toBe(newMessage);
|
|
149
|
+
});
|
|
150
|
+
|
|
151
|
+
it("should handle emoji in commit message", () => {
|
|
152
|
+
const newMessage = "✨ feat: add new feature";
|
|
153
|
+
|
|
154
|
+
execSync(`git commit --amend -m "${newMessage}"`, { stdio: "pipe" });
|
|
155
|
+
|
|
156
|
+
const message = execSync('git log -1 --format="%s"', {
|
|
157
|
+
encoding: "utf-8",
|
|
158
|
+
}).trim();
|
|
159
|
+
|
|
160
|
+
expect(message).toBe(newMessage);
|
|
161
|
+
});
|
|
162
|
+
|
|
163
|
+
it("should handle Chinese characters", () => {
|
|
164
|
+
const newMessage = "feat: 添加登录功能";
|
|
165
|
+
|
|
166
|
+
execSync(`git commit --amend -m "${newMessage}"`, { stdio: "pipe" });
|
|
167
|
+
|
|
168
|
+
const message = execSync('git log -1 --format="%s"', {
|
|
169
|
+
encoding: "utf-8",
|
|
170
|
+
}).trim();
|
|
171
|
+
|
|
172
|
+
expect(message).toBe(newMessage);
|
|
173
|
+
});
|
|
174
|
+
|
|
175
|
+
it("should handle special symbols", () => {
|
|
176
|
+
const newMessage = "feat: add feature (v1.0.0) [WIP] #123";
|
|
177
|
+
|
|
178
|
+
execSync(`git commit --amend -m "${newMessage}"`, { stdio: "pipe" });
|
|
179
|
+
|
|
180
|
+
const message = execSync('git log -1 --format="%s"', {
|
|
181
|
+
encoding: "utf-8",
|
|
182
|
+
}).trim();
|
|
183
|
+
|
|
184
|
+
expect(message).toBe(newMessage);
|
|
185
|
+
});
|
|
186
|
+
});
|
|
187
|
+
|
|
188
|
+
describe("获取 commit 信息", () => {
|
|
189
|
+
it("should get commit by hash", () => {
|
|
190
|
+
const hash = execSync("git rev-parse HEAD", {
|
|
191
|
+
encoding: "utf-8",
|
|
192
|
+
}).trim();
|
|
193
|
+
|
|
194
|
+
const message = execSync(`git log -1 ${hash} --format="%s"`, {
|
|
195
|
+
encoding: "utf-8",
|
|
196
|
+
}).trim();
|
|
197
|
+
|
|
198
|
+
expect(message).toBe("Initial commit");
|
|
199
|
+
});
|
|
200
|
+
|
|
201
|
+
it("should get commit by short hash", () => {
|
|
202
|
+
const shortHash = execSync("git rev-parse --short HEAD", {
|
|
203
|
+
encoding: "utf-8",
|
|
204
|
+
}).trim();
|
|
205
|
+
|
|
206
|
+
const message = execSync(`git log -1 ${shortHash} --format="%s"`, {
|
|
207
|
+
encoding: "utf-8",
|
|
208
|
+
}).trim();
|
|
209
|
+
|
|
210
|
+
expect(message).toBe("Initial commit");
|
|
211
|
+
});
|
|
212
|
+
|
|
213
|
+
it("should get commit by HEAD reference", () => {
|
|
214
|
+
const message = execSync('git log -1 HEAD --format="%s"', {
|
|
215
|
+
encoding: "utf-8",
|
|
216
|
+
}).trim();
|
|
217
|
+
|
|
218
|
+
expect(message).toBe("Initial commit");
|
|
219
|
+
});
|
|
220
|
+
|
|
221
|
+
it("should get commit by HEAD~N reference", () => {
|
|
222
|
+
// 创建第二个 commit
|
|
223
|
+
execSync("echo 'test2' > test2.txt", { stdio: "pipe" });
|
|
224
|
+
execSync("git add .", { stdio: "pipe" });
|
|
225
|
+
execSync('git commit -m "Second commit"', { stdio: "pipe" });
|
|
226
|
+
|
|
227
|
+
const message = execSync('git log -1 HEAD~1 --format="%s"', {
|
|
228
|
+
encoding: "utf-8",
|
|
229
|
+
}).trim();
|
|
230
|
+
|
|
231
|
+
expect(message).toBe("Initial commit");
|
|
232
|
+
});
|
|
233
|
+
|
|
234
|
+
it("should handle invalid hash gracefully", () => {
|
|
235
|
+
try {
|
|
236
|
+
execSync('git log -1 invalid-hash --format="%s"', { stdio: "pipe" });
|
|
237
|
+
expect.fail("Should throw error for invalid hash");
|
|
238
|
+
} catch (error) {
|
|
239
|
+
expect(error).toBeTruthy();
|
|
240
|
+
}
|
|
241
|
+
});
|
|
242
|
+
});
|
|
243
|
+
|
|
244
|
+
describe("Conventional Commits 格式", () => {
|
|
245
|
+
it("should support feat type", () => {
|
|
246
|
+
const message = "feat: add new feature";
|
|
247
|
+
execSync(`git commit --amend -m "${message}"`, { stdio: "pipe" });
|
|
248
|
+
|
|
249
|
+
const result = execSync('git log -1 --format="%s"', {
|
|
250
|
+
encoding: "utf-8",
|
|
251
|
+
}).trim();
|
|
252
|
+
|
|
253
|
+
expect(result).toBe(message);
|
|
254
|
+
});
|
|
255
|
+
|
|
256
|
+
it("should support fix type", () => {
|
|
257
|
+
const message = "fix: resolve bug";
|
|
258
|
+
execSync(`git commit --amend -m "${message}"`, { stdio: "pipe" });
|
|
259
|
+
|
|
260
|
+
const result = execSync('git log -1 --format="%s"', {
|
|
261
|
+
encoding: "utf-8",
|
|
262
|
+
}).trim();
|
|
263
|
+
|
|
264
|
+
expect(result).toBe(message);
|
|
265
|
+
});
|
|
266
|
+
|
|
267
|
+
it("should support scope", () => {
|
|
268
|
+
const message = "feat(auth): add login";
|
|
269
|
+
execSync(`git commit --amend -m "${message}"`, { stdio: "pipe" });
|
|
270
|
+
|
|
271
|
+
const result = execSync('git log -1 --format="%s"', {
|
|
272
|
+
encoding: "utf-8",
|
|
273
|
+
}).trim();
|
|
274
|
+
|
|
275
|
+
expect(result).toBe(message);
|
|
276
|
+
});
|
|
277
|
+
|
|
278
|
+
it("should support breaking change", () => {
|
|
279
|
+
const message = "feat!: breaking change";
|
|
280
|
+
execSync(`git commit --amend -m "${message}"`, { stdio: "pipe" });
|
|
281
|
+
|
|
282
|
+
const result = execSync('git log -1 --format="%s"', {
|
|
283
|
+
encoding: "utf-8",
|
|
284
|
+
}).trim();
|
|
285
|
+
|
|
286
|
+
expect(result).toBe(message);
|
|
287
|
+
});
|
|
288
|
+
|
|
289
|
+
it("should support gitmoji", () => {
|
|
290
|
+
const message = "✨ feat: add feature";
|
|
291
|
+
execSync(`git commit --amend -m "${message}"`, { stdio: "pipe" });
|
|
292
|
+
|
|
293
|
+
const result = execSync('git log -1 --format="%s"', {
|
|
294
|
+
encoding: "utf-8",
|
|
295
|
+
}).trim();
|
|
296
|
+
|
|
297
|
+
expect(result).toBe(message);
|
|
298
|
+
});
|
|
299
|
+
});
|
|
300
|
+
|
|
301
|
+
describe("多个 commits", () => {
|
|
302
|
+
beforeEach(() => {
|
|
303
|
+
// 创建多个 commits
|
|
304
|
+
execSync("echo 'test2' > test2.txt", { stdio: "pipe" });
|
|
305
|
+
execSync("git add .", { stdio: "pipe" });
|
|
306
|
+
execSync('git commit -m "Second commit"', { stdio: "pipe" });
|
|
307
|
+
|
|
308
|
+
execSync("echo 'test3' > test3.txt", { stdio: "pipe" });
|
|
309
|
+
execSync("git add .", { stdio: "pipe" });
|
|
310
|
+
execSync('git commit -m "Third commit"', { stdio: "pipe" });
|
|
311
|
+
});
|
|
312
|
+
|
|
313
|
+
it("should have multiple commits", () => {
|
|
314
|
+
const count = execSync("git log --oneline | wc -l", {
|
|
315
|
+
encoding: "utf-8",
|
|
316
|
+
}).trim();
|
|
317
|
+
|
|
318
|
+
expect(parseInt(count)).toBe(3);
|
|
319
|
+
});
|
|
320
|
+
|
|
321
|
+
it("should get recent commits", () => {
|
|
322
|
+
const log = execSync('git log -3 --format="%s"', {
|
|
323
|
+
encoding: "utf-8",
|
|
324
|
+
});
|
|
325
|
+
|
|
326
|
+
expect(log).toContain("Third commit");
|
|
327
|
+
expect(log).toContain("Second commit");
|
|
328
|
+
expect(log).toContain("Initial commit");
|
|
329
|
+
});
|
|
330
|
+
|
|
331
|
+
it("should modify only latest commit", () => {
|
|
332
|
+
execSync('git commit --amend -m "Modified third commit"', {
|
|
333
|
+
stdio: "pipe",
|
|
334
|
+
});
|
|
335
|
+
|
|
336
|
+
const latest = execSync('git log -1 --format="%s"', {
|
|
337
|
+
encoding: "utf-8",
|
|
338
|
+
}).trim();
|
|
339
|
+
|
|
340
|
+
const second = execSync('git log -1 HEAD~1 --format="%s"', {
|
|
341
|
+
encoding: "utf-8",
|
|
342
|
+
}).trim();
|
|
343
|
+
|
|
344
|
+
expect(latest).toBe("Modified third commit");
|
|
345
|
+
expect(second).toBe("Second commit");
|
|
346
|
+
});
|
|
347
|
+
|
|
348
|
+
it("should get commits in reverse order", () => {
|
|
349
|
+
const firstHash = execSync('git log --reverse --format="%H" | head -1', {
|
|
350
|
+
encoding: "utf-8",
|
|
351
|
+
}).trim();
|
|
352
|
+
|
|
353
|
+
const message = execSync(`git log -1 ${firstHash} --format="%s"`, {
|
|
354
|
+
encoding: "utf-8",
|
|
355
|
+
}).trim();
|
|
356
|
+
|
|
357
|
+
expect(message).toBe("Initial commit");
|
|
358
|
+
});
|
|
359
|
+
});
|
|
360
|
+
|
|
361
|
+
describe("边界情况", () => {
|
|
362
|
+
it("should handle very long commit messages", () => {
|
|
363
|
+
const longMessage = "feat: " + "a".repeat(500);
|
|
364
|
+
|
|
365
|
+
execSync(`git commit --amend -m "${longMessage}"`, { stdio: "pipe" });
|
|
366
|
+
|
|
367
|
+
const message = execSync('git log -1 --format="%s"', {
|
|
368
|
+
encoding: "utf-8",
|
|
369
|
+
}).trim();
|
|
370
|
+
|
|
371
|
+
expect(message).toBe(longMessage);
|
|
372
|
+
expect(message.length).toBeGreaterThan(500);
|
|
373
|
+
});
|
|
374
|
+
|
|
375
|
+
it("should handle empty repository edge case", () => {
|
|
376
|
+
// 这个测试在有 commit 的仓库中,验证基本功能
|
|
377
|
+
const message = execSync('git log -1 --format="%s"', {
|
|
378
|
+
encoding: "utf-8",
|
|
379
|
+
}).trim();
|
|
380
|
+
|
|
381
|
+
expect(message).toBeTruthy();
|
|
382
|
+
});
|
|
383
|
+
|
|
384
|
+
it("should handle commit with no changes", () => {
|
|
385
|
+
// 修改 message 但不修改文件
|
|
386
|
+
execSync('git commit --amend -m "No file changes"', { stdio: "pipe" });
|
|
387
|
+
|
|
388
|
+
const message = execSync('git log -1 --format="%s"', {
|
|
389
|
+
encoding: "utf-8",
|
|
390
|
+
}).trim();
|
|
391
|
+
|
|
392
|
+
expect(message).toBe("No file changes");
|
|
393
|
+
});
|
|
394
|
+
});
|
|
395
|
+
|
|
396
|
+
describe("commit 格式验证", () => {
|
|
397
|
+
it("should accept standard format", () => {
|
|
398
|
+
const message = "feat: add feature";
|
|
399
|
+
execSync(`git commit --amend -m "${message}"`, { stdio: "pipe" });
|
|
400
|
+
|
|
401
|
+
const result = execSync('git log -1 --format="%s"', {
|
|
402
|
+
encoding: "utf-8",
|
|
403
|
+
}).trim();
|
|
404
|
+
|
|
405
|
+
expect(result).toBe(message);
|
|
406
|
+
});
|
|
407
|
+
|
|
408
|
+
it("should accept format with scope", () => {
|
|
409
|
+
const message = "feat(api): add endpoint";
|
|
410
|
+
execSync(`git commit --amend -m "${message}"`, { stdio: "pipe" });
|
|
411
|
+
|
|
412
|
+
const result = execSync('git log -1 --format="%s"', {
|
|
413
|
+
encoding: "utf-8",
|
|
414
|
+
}).trim();
|
|
415
|
+
|
|
416
|
+
expect(result).toBe(message);
|
|
417
|
+
});
|
|
418
|
+
|
|
419
|
+
it("should accept format with emoji prefix", () => {
|
|
420
|
+
const message = "✨ feat(api): add endpoint";
|
|
421
|
+
execSync(`git commit --amend -m "${message}"`, { stdio: "pipe" });
|
|
422
|
+
|
|
423
|
+
const result = execSync('git log -1 --format="%s"', {
|
|
424
|
+
encoding: "utf-8",
|
|
425
|
+
}).trim();
|
|
426
|
+
|
|
427
|
+
expect(result).toBe(message);
|
|
428
|
+
});
|
|
429
|
+
|
|
430
|
+
it("should accept any custom format", () => {
|
|
431
|
+
const message = "Custom format message";
|
|
432
|
+
execSync(`git commit --amend -m "${message}"`, { stdio: "pipe" });
|
|
433
|
+
|
|
434
|
+
const result = execSync('git log -1 --format="%s"', {
|
|
435
|
+
encoding: "utf-8",
|
|
436
|
+
}).trim();
|
|
437
|
+
|
|
438
|
+
expect(result).toBe(message);
|
|
439
|
+
});
|
|
440
|
+
});
|
|
441
|
+
});
|