kodevu 0.1.53 → 0.1.55
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/README.md +11 -7
- package/SKILL.md +27 -9
- package/package.json +1 -1
- package/src/config.js +15 -1
- package/src/git-client.js +103 -0
- package/src/review-runner.js +9 -2
- package/src/svn-client.js +52 -0
- package/src/utils.js +2 -1
- package/src/vcs-client.js +40 -0
package/README.md
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
# Kodevu
|
|
2
2
|
|
|
3
|
+
> The name **Kodevu** is a phonetic play on "code review".
|
|
4
|
+
|
|
3
5
|
A Node.js tool that fetches Git commits or SVN revisions, sends the diff to a supported AI reviewer CLI, and writes review results to report files.
|
|
4
6
|
|
|
5
7
|
## Pure & Zero Config
|
|
@@ -32,6 +34,7 @@ npx kodevu [target] [options]
|
|
|
32
34
|
- `--reviewer, -r`: `codex`, `gemini`, `copilot`, `openai`, or `auto` (default: `auto`).
|
|
33
35
|
- `--rev, -v`: A specific revision or commit hash to review.
|
|
34
36
|
- `--last, -n`: Number of latest revisions to review (default: 1). Use negative values (e.g., `-3`) to review only the 3rd commit from the top.
|
|
37
|
+
- `--uncommitted, -u`: Review current uncommitted changes in the target working tree.
|
|
35
38
|
- `--lang, -l`: Output language (e.g., `zh`, `en`, `auto`).
|
|
36
39
|
- `--prompt, -p`: Additional instructions for the reviewer. Use `@file.txt` to read from a file.
|
|
37
40
|
- `--output, -o`: Report output directory (default: `~/.kodevu`).
|
|
@@ -47,6 +50,9 @@ npx kodevu [target] [options]
|
|
|
47
50
|
> [!IMPORTANT]
|
|
48
51
|
> `--rev` and `--last` are mutually exclusive. Specifying both will result in an error.
|
|
49
52
|
|
|
53
|
+
> [!IMPORTANT]
|
|
54
|
+
> `--uncommitted` is mutually exclusive with `--rev` and `--last`.
|
|
55
|
+
|
|
50
56
|
### Environment Variables
|
|
51
57
|
|
|
52
58
|
You can set these in your shell to change default behavior without typing flags every time:
|
|
@@ -81,6 +87,11 @@ Review a **specific commit** hash:
|
|
|
81
87
|
npx kodevu . --rev abc1234
|
|
82
88
|
```
|
|
83
89
|
|
|
90
|
+
Review **current uncommitted changes** (Git/SVN working copy):
|
|
91
|
+
```bash
|
|
92
|
+
npx kodevu . --uncommitted
|
|
93
|
+
```
|
|
94
|
+
|
|
84
95
|
### Options & Formatting
|
|
85
96
|
|
|
86
97
|
Review using **custom instructions** from a file:
|
|
@@ -118,13 +129,6 @@ npx kodevu . \
|
|
|
118
129
|
--openai-model gpt-5-mini
|
|
119
130
|
```
|
|
120
131
|
|
|
121
|
-
## How it Works
|
|
122
|
-
|
|
123
|
-
- **Git Targets**: `target` must be a local repository or subdirectory.
|
|
124
|
-
- **SVN Targets**: `target` can be a working copy path or repository URL.
|
|
125
|
-
- **Reviewer "auto"**: Probes `codex`, `gemini`, and `copilot` in your `PATH` and selects one.
|
|
126
|
-
- **Reviewer "openai"**: Calls the OpenAI Chat Completions API directly. `auto` does not select `openai`, so API-based use stays explicit.
|
|
127
|
-
- **Contextual Review**: For local repositories, the reviewer can inspect related files beyond the diff to provide deeper insights.
|
|
128
132
|
|
|
129
133
|
## License
|
|
130
134
|
|
package/SKILL.md
CHANGED
|
@@ -1,15 +1,15 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: kodevu
|
|
3
|
-
description: A tool to fetch Git
|
|
3
|
+
description: A tool to fetch Git/SVN diffs, send them to an AI reviewer, and generate configurable code review reports.
|
|
4
4
|
---
|
|
5
5
|
|
|
6
6
|
# Kodevu Skill
|
|
7
7
|
|
|
8
|
-
Kodevu is a Node.js tool that fetches Git commits or SVN revisions, sends the diff to a supported AI reviewer CLI, and writes review results to report files.
|
|
8
|
+
Kodevu is a Node.js tool that fetches Git commits or SVN revisions, sends the diff to a supported AI reviewer CLI, and writes review results to report files. It is designed to be **stateless** and requires **no configuration files**.
|
|
9
9
|
|
|
10
10
|
## Usage
|
|
11
11
|
|
|
12
|
-
Use `npx kodevu` to review a codebase.
|
|
12
|
+
Use `npx kodevu` to review a codebase.
|
|
13
13
|
|
|
14
14
|
### Reviewing the latest commit
|
|
15
15
|
|
|
@@ -29,13 +29,19 @@ npx kodevu . --rev <commit-hash>
|
|
|
29
29
|
npx kodevu . --last 3
|
|
30
30
|
```
|
|
31
31
|
|
|
32
|
+
### Reviewing uncommitted changes
|
|
33
|
+
|
|
34
|
+
```bash
|
|
35
|
+
npx kodevu . --uncommitted
|
|
36
|
+
```
|
|
37
|
+
|
|
32
38
|
### Supported Reviewers
|
|
33
39
|
|
|
34
|
-
`kodevu` supports several AI reviewer
|
|
40
|
+
`kodevu` supports several AI reviewer backends: `auto`, `openai`, `gemini`, `codex`, `copilot`. The default is `auto`, which probes available CLI tools in your `PATH`.
|
|
35
41
|
|
|
36
42
|
Example using OpenAI:
|
|
37
43
|
```bash
|
|
38
|
-
npx kodevu . --reviewer openai --openai-api-key <YOUR_API_KEY> --openai-model gpt-
|
|
44
|
+
npx kodevu . --reviewer openai --openai-api-key <YOUR_API_KEY> --openai-model gpt-5-mini
|
|
39
45
|
```
|
|
40
46
|
|
|
41
47
|
### Generating JSON Reports
|
|
@@ -45,19 +51,31 @@ By default, review reports are generated as Markdown files in `~/.kodevu/`. You
|
|
|
45
51
|
npx kodevu . --format json --output ./reports
|
|
46
52
|
```
|
|
47
53
|
|
|
48
|
-
###
|
|
54
|
+
### Custom Prompts
|
|
49
55
|
|
|
50
|
-
You can provide
|
|
56
|
+
You can provide additional instructions to the reviewer using `--prompt`:
|
|
51
57
|
```bash
|
|
52
58
|
npx kodevu . --prompt "Focus on security issues and suggest optimizations."
|
|
53
59
|
```
|
|
54
60
|
Or from a file: `--prompt @my-rules.txt`
|
|
55
61
|
|
|
62
|
+
### Environment Variables
|
|
63
|
+
|
|
64
|
+
All options can also be set via environment variables to avoid repetitive flags:
|
|
65
|
+
|
|
66
|
+
- `KODEVU_REVIEWER` – Default reviewer.
|
|
67
|
+
- `KODEVU_LANG` – Default output language.
|
|
68
|
+
- `KODEVU_OUTPUT_DIR` – Default output directory.
|
|
69
|
+
- `KODEVU_PROMPT` – Default prompt instructions.
|
|
70
|
+
- `KODEVU_OPENAI_API_KEY` – API key for `openai`.
|
|
71
|
+
- `KODEVU_OPENAI_BASE_URL` – Base URL for `openai`.
|
|
72
|
+
- `KODEVU_OPENAI_MODEL` – Model for `openai`.
|
|
73
|
+
|
|
56
74
|
## Working with Target Repositories
|
|
57
75
|
|
|
58
|
-
- `target
|
|
76
|
+
- **Git**: `target` must be a local repository or subdirectory.
|
|
77
|
+
- **SVN**: `target` can be a working copy path or repository URL.
|
|
59
78
|
|
|
60
|
-
For example, to run on a specific path, you can use:
|
|
61
79
|
```bash
|
|
62
80
|
npx kodevu /path/to/project --last 1
|
|
63
81
|
```
|
package/package.json
CHANGED
package/src/config.js
CHANGED
|
@@ -23,6 +23,7 @@ const defaultConfig = {
|
|
|
23
23
|
outputFormats: ["markdown"],
|
|
24
24
|
rev: "",
|
|
25
25
|
last: 0,
|
|
26
|
+
uncommitted: false,
|
|
26
27
|
openaiApiKey: "",
|
|
27
28
|
openaiBaseUrl: "https://api.openai.com/v1",
|
|
28
29
|
openaiModel: "gpt-5-mini",
|
|
@@ -124,6 +125,7 @@ export function parseCliArgs(argv) {
|
|
|
124
125
|
prompt: "",
|
|
125
126
|
rev: "",
|
|
126
127
|
last: "",
|
|
128
|
+
uncommitted: false,
|
|
127
129
|
outputDir: "",
|
|
128
130
|
outputFormats: "",
|
|
129
131
|
openaiApiKey: "",
|
|
@@ -190,6 +192,11 @@ export function parseCliArgs(argv) {
|
|
|
190
192
|
continue;
|
|
191
193
|
}
|
|
192
194
|
|
|
195
|
+
if (value === "--uncommitted" || value === "-u") {
|
|
196
|
+
args.uncommitted = true;
|
|
197
|
+
continue;
|
|
198
|
+
}
|
|
199
|
+
|
|
193
200
|
if (value === "--output" || value === "-o") {
|
|
194
201
|
if (!hasNextValue) throw new Error(`Missing value for ${value}`);
|
|
195
202
|
args.outputDir = nextValue;
|
|
@@ -268,6 +275,7 @@ export async function resolveConfig(cliArgs = {}) {
|
|
|
268
275
|
"lang",
|
|
269
276
|
"rev",
|
|
270
277
|
"last",
|
|
278
|
+
"uncommitted",
|
|
271
279
|
"outputDir",
|
|
272
280
|
"outputFormats",
|
|
273
281
|
"openaiApiKey",
|
|
@@ -285,6 +293,10 @@ export async function resolveConfig(cliArgs = {}) {
|
|
|
285
293
|
throw new Error("Parameters --rev and --last are mutually exclusive. Please specify only one.");
|
|
286
294
|
}
|
|
287
295
|
|
|
296
|
+
if (cliArgs.uncommitted && (cliArgs.rev || cliArgs.last)) {
|
|
297
|
+
throw new Error("Parameter --uncommitted is mutually exclusive with --rev and --last.");
|
|
298
|
+
}
|
|
299
|
+
|
|
288
300
|
if (!config.target) {
|
|
289
301
|
config.target = process.cwd();
|
|
290
302
|
}
|
|
@@ -321,6 +333,7 @@ export async function resolveConfig(cliArgs = {}) {
|
|
|
321
333
|
config.maxRevisionsPerRun = Number(config.maxRevisionsPerRun);
|
|
322
334
|
config.commandTimeoutMs = Number(config.commandTimeoutMs);
|
|
323
335
|
config.last = Number(config.last);
|
|
336
|
+
config.uncommitted = Boolean(config.uncommitted);
|
|
324
337
|
config.outputFormats = normalizeOutputFormats(config.outputFormats);
|
|
325
338
|
config.openaiApiKey = String(config.openaiApiKey || "").trim();
|
|
326
339
|
config.openaiBaseUrl = String(config.openaiBaseUrl || defaultConfig.openaiBaseUrl).trim().replace(/\/+$/, "");
|
|
@@ -328,7 +341,7 @@ export async function resolveConfig(cliArgs = {}) {
|
|
|
328
341
|
config.openaiOrganization = String(config.openaiOrganization || "").trim();
|
|
329
342
|
config.openaiProject = String(config.openaiProject || "").trim();
|
|
330
343
|
|
|
331
|
-
if (!config.rev && (isNaN(config.last) || config.last === 0)) {
|
|
344
|
+
if (!config.uncommitted && !config.rev && (isNaN(config.last) || config.last === 0)) {
|
|
332
345
|
config.last = 1;
|
|
333
346
|
}
|
|
334
347
|
|
|
@@ -352,6 +365,7 @@ Options:
|
|
|
352
365
|
--lang, -l Output language (e.g. zh, en, auto)
|
|
353
366
|
--rev, -v Review specific revision(s), hashes, branches or ranges (comma-separated)
|
|
354
367
|
--last, -n Review the latest N revisions; use negative (-N) to review only the Nth-from-last revision (default: 1)
|
|
368
|
+
--uncommitted, -u Review current uncommitted changes (mutually exclusive with --rev and --last)
|
|
355
369
|
--output, -o Output directory (default: ~/.kodevu)
|
|
356
370
|
--format, -f Output formats (markdown, json, comma-separated)
|
|
357
371
|
--openai-api-key API key used when reviewer=openai
|
package/src/git-client.js
CHANGED
|
@@ -181,6 +181,49 @@ function parseNameStatus(stdout) {
|
|
|
181
181
|
return changedPaths;
|
|
182
182
|
}
|
|
183
183
|
|
|
184
|
+
function parseNameStatusLines(stdout) {
|
|
185
|
+
return stdout
|
|
186
|
+
.split(/\r?\n/)
|
|
187
|
+
.map((line) => line.trim())
|
|
188
|
+
.filter(Boolean)
|
|
189
|
+
.map((line) => line.split("\t"))
|
|
190
|
+
.map((parts) => {
|
|
191
|
+
const status = (parts[0] || "M").trim();
|
|
192
|
+
const action = status[0] || "M";
|
|
193
|
+
|
|
194
|
+
if ((action === "R" || action === "C") && parts.length >= 3) {
|
|
195
|
+
return {
|
|
196
|
+
action,
|
|
197
|
+
relativePath: parts[2],
|
|
198
|
+
previousPath: parts[1] || null
|
|
199
|
+
};
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
return {
|
|
203
|
+
action,
|
|
204
|
+
relativePath: parts[1] || "",
|
|
205
|
+
previousPath: null
|
|
206
|
+
};
|
|
207
|
+
})
|
|
208
|
+
.filter((item) => item.relativePath);
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
function mergeChangedPaths(...groups) {
|
|
212
|
+
const merged = [];
|
|
213
|
+
const seen = new Set();
|
|
214
|
+
|
|
215
|
+
for (const group of groups) {
|
|
216
|
+
for (const item of group) {
|
|
217
|
+
const key = `${item.action}|${item.relativePath}|${item.previousPath || ""}`;
|
|
218
|
+
if (seen.has(key)) continue;
|
|
219
|
+
seen.add(key);
|
|
220
|
+
merged.push(item);
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
return merged;
|
|
225
|
+
}
|
|
226
|
+
|
|
184
227
|
export async function getCommitDetails(config, targetInfo, commitHash) {
|
|
185
228
|
const metaResult = await runGit(
|
|
186
229
|
config,
|
|
@@ -214,3 +257,63 @@ export async function getCommitDetails(config, targetInfo, commitHash) {
|
|
|
214
257
|
changedPaths: parseNameStatus(changedFilesResult.stdout)
|
|
215
258
|
};
|
|
216
259
|
}
|
|
260
|
+
|
|
261
|
+
export async function getUncommittedDiff(config, targetInfo) {
|
|
262
|
+
const unstaged = await runGit(
|
|
263
|
+
config,
|
|
264
|
+
[
|
|
265
|
+
"diff",
|
|
266
|
+
"--find-renames",
|
|
267
|
+
"--find-copies",
|
|
268
|
+
"--no-ext-diff",
|
|
269
|
+
...buildPathArgs(targetInfo)
|
|
270
|
+
],
|
|
271
|
+
{ cwd: targetInfo.repoRootPath, trim: false }
|
|
272
|
+
);
|
|
273
|
+
const staged = await runGit(
|
|
274
|
+
config,
|
|
275
|
+
[
|
|
276
|
+
"diff",
|
|
277
|
+
"--cached",
|
|
278
|
+
"--find-renames",
|
|
279
|
+
"--find-copies",
|
|
280
|
+
"--no-ext-diff",
|
|
281
|
+
...buildPathArgs(targetInfo)
|
|
282
|
+
],
|
|
283
|
+
{ cwd: targetInfo.repoRootPath, trim: false }
|
|
284
|
+
);
|
|
285
|
+
|
|
286
|
+
const sections = [];
|
|
287
|
+
if (unstaged.stdout.trim()) {
|
|
288
|
+
sections.push("# Unstaged changes", unstaged.stdout.trimEnd());
|
|
289
|
+
}
|
|
290
|
+
if (staged.stdout.trim()) {
|
|
291
|
+
sections.push("# Staged changes", staged.stdout.trimEnd());
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
return sections.join("\n\n");
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
export async function getUncommittedDetails(config, targetInfo) {
|
|
298
|
+
const unstagedFiles = await runGit(
|
|
299
|
+
config,
|
|
300
|
+
["diff", "--name-status", "-M", "-C", ...buildPathArgs(targetInfo)],
|
|
301
|
+
{ cwd: targetInfo.repoRootPath, trim: false }
|
|
302
|
+
);
|
|
303
|
+
const stagedFiles = await runGit(
|
|
304
|
+
config,
|
|
305
|
+
["diff", "--cached", "--name-status", "-M", "-C", ...buildPathArgs(targetInfo)],
|
|
306
|
+
{ cwd: targetInfo.repoRootPath, trim: false }
|
|
307
|
+
);
|
|
308
|
+
|
|
309
|
+
const unstagedChanged = parseNameStatusLines(unstagedFiles.stdout);
|
|
310
|
+
const stagedChanged = parseNameStatusLines(stagedFiles.stdout);
|
|
311
|
+
|
|
312
|
+
return {
|
|
313
|
+
commitHash: "UNCOMMITTED",
|
|
314
|
+
author: "working-tree",
|
|
315
|
+
date: new Date().toISOString(),
|
|
316
|
+
message: "Uncommitted changes (staged + unstaged).",
|
|
317
|
+
changedPaths: mergeChangedPaths(unstagedChanged, stagedChanged)
|
|
318
|
+
};
|
|
319
|
+
}
|
package/src/review-runner.js
CHANGED
|
@@ -155,7 +155,9 @@ export async function runReviewCycle(config) {
|
|
|
155
155
|
|
|
156
156
|
let changeIdsToReview = [];
|
|
157
157
|
|
|
158
|
-
if (config.
|
|
158
|
+
if (config.uncommitted) {
|
|
159
|
+
changeIdsToReview = ["UNCOMMITTED"];
|
|
160
|
+
} else if (config.rev) {
|
|
159
161
|
changeIdsToReview = await backend.resolveChangeIds(config, targetInfo, config.rev);
|
|
160
162
|
} else if (config.last < 0) {
|
|
161
163
|
const candidates = await backend.getLatestChangeIds(config, targetInfo, Math.abs(config.last));
|
|
@@ -169,7 +171,12 @@ export async function runReviewCycle(config) {
|
|
|
169
171
|
return;
|
|
170
172
|
}
|
|
171
173
|
|
|
172
|
-
|
|
174
|
+
const isUncommittedBatch =
|
|
175
|
+
changeIdsToReview.length === 1 && changeIdsToReview[0] === "UNCOMMITTED";
|
|
176
|
+
const batchSummary = isUncommittedBatch
|
|
177
|
+
? `Reviewing ${backend.displayName} uncommitted changes`
|
|
178
|
+
: `Reviewing ${backend.displayName} ${backend.changeName}s ${formatChangeList(backend, changeIdsToReview)}`;
|
|
179
|
+
logger.info(batchSummary);
|
|
173
180
|
const progress = createProgressReporter(`${backend.displayName} ${backend.changeName} batch`);
|
|
174
181
|
progress.update(0, `0/${changeIdsToReview.length} completed`);
|
|
175
182
|
|
package/src/svn-client.js
CHANGED
|
@@ -128,6 +128,24 @@ function toRelativePath(targetRepoPath, repoPath) {
|
|
|
128
128
|
return repoPath.slice(targetRepoPath.length).replace(/^\/+/, "");
|
|
129
129
|
}
|
|
130
130
|
|
|
131
|
+
function parseSvnStatus(statusText) {
|
|
132
|
+
return statusText
|
|
133
|
+
.split(/\r?\n/)
|
|
134
|
+
.map((line) => line.replace(/\r$/, ""))
|
|
135
|
+
.filter(Boolean)
|
|
136
|
+
.map((line) => {
|
|
137
|
+
const action = (line[0] || " ").trim();
|
|
138
|
+
const relativePath = line.length > 8 ? line.slice(8).trim() : "";
|
|
139
|
+
return {
|
|
140
|
+
action,
|
|
141
|
+
relativePath,
|
|
142
|
+
previousPath: null
|
|
143
|
+
};
|
|
144
|
+
})
|
|
145
|
+
.filter((item) => item.relativePath)
|
|
146
|
+
.filter((item) => ["A", "D", "M", "R"].includes(item.action));
|
|
147
|
+
}
|
|
148
|
+
|
|
131
149
|
export async function getRevisionDetails(config, targetInfo, revision) {
|
|
132
150
|
const result = await runCommand(
|
|
133
151
|
SVN_COMMAND,
|
|
@@ -165,3 +183,37 @@ export async function getRevisionDetails(config, targetInfo, revision) {
|
|
|
165
183
|
changedPaths
|
|
166
184
|
};
|
|
167
185
|
}
|
|
186
|
+
|
|
187
|
+
export async function getUncommittedDiff(config, targetInfo) {
|
|
188
|
+
if (!targetInfo.workingCopyPath) {
|
|
189
|
+
throw new Error("SVN --uncommitted requires a working copy path target.");
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
const result = await runCommand(
|
|
193
|
+
SVN_COMMAND,
|
|
194
|
+
["diff", "--git", "--internal-diff", "--ignore-properties", config.target],
|
|
195
|
+
{ encoding: COMMAND_ENCODING, trim: false, debug: config.debug }
|
|
196
|
+
);
|
|
197
|
+
|
|
198
|
+
return result.stdout;
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
export async function getUncommittedDetails(config, targetInfo) {
|
|
202
|
+
if (!targetInfo.workingCopyPath) {
|
|
203
|
+
throw new Error("SVN --uncommitted requires a working copy path target.");
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
const statusResult = await runCommand(
|
|
207
|
+
SVN_COMMAND,
|
|
208
|
+
["status", config.target],
|
|
209
|
+
{ encoding: COMMAND_ENCODING, trim: false, debug: config.debug }
|
|
210
|
+
);
|
|
211
|
+
|
|
212
|
+
return {
|
|
213
|
+
revision: "UNCOMMITTED",
|
|
214
|
+
author: "working-copy",
|
|
215
|
+
date: new Date().toISOString(),
|
|
216
|
+
message: "Uncommitted changes in working copy.",
|
|
217
|
+
changedPaths: parseSvnStatus(statusResult.stdout)
|
|
218
|
+
};
|
|
219
|
+
}
|
package/src/utils.js
CHANGED
|
@@ -50,9 +50,10 @@ export function formatDate(dateInput) {
|
|
|
50
50
|
const hours = pad(d.getHours());
|
|
51
51
|
const minutes = pad(d.getMinutes());
|
|
52
52
|
const seconds = pad(d.getSeconds());
|
|
53
|
+
const milliseconds = String(d.getMilliseconds()).padStart(3, "0");
|
|
53
54
|
const offset = `${sign}${pad(offsetHours)}:${pad(offsetMins)}`;
|
|
54
55
|
|
|
55
|
-
return `${year}-${month}-${day} ${hours}:${minutes}:${seconds} ${offset}`;
|
|
56
|
+
return `${year}-${month}-${day} ${hours}:${minutes}:${seconds}.${milliseconds} ${offset}`;
|
|
56
57
|
}
|
|
57
58
|
export function getTimestampPrefix() {
|
|
58
59
|
const now = new Date();
|
package/src/vcs-client.js
CHANGED
|
@@ -13,9 +13,13 @@ function createSvnBackend() {
|
|
|
13
13
|
displayName: "SVN",
|
|
14
14
|
changeName: "revision",
|
|
15
15
|
formatChangeId(revision) {
|
|
16
|
+
if (revision === "UNCOMMITTED") return "uncommitted";
|
|
16
17
|
return `r${revision}`;
|
|
17
18
|
},
|
|
18
19
|
getReportFileName(revision) {
|
|
20
|
+
if (revision === "UNCOMMITTED") {
|
|
21
|
+
return `${getTimestampPrefix()}-svn-uncommitted.md`;
|
|
22
|
+
}
|
|
19
23
|
return `${getTimestampPrefix()}-svn-r${revision}.md`;
|
|
20
24
|
},
|
|
21
25
|
|
|
@@ -33,9 +37,25 @@ function createSvnBackend() {
|
|
|
33
37
|
return await svnClient.getLatestRevisionIds(config, targetInfo, limit);
|
|
34
38
|
},
|
|
35
39
|
async getChangeDiff(config, targetInfo, revision) {
|
|
40
|
+
if (revision === "UNCOMMITTED") {
|
|
41
|
+
return await svnClient.getUncommittedDiff(config, targetInfo);
|
|
42
|
+
}
|
|
36
43
|
return await svnClient.getRevisionDiff(config, revision);
|
|
37
44
|
},
|
|
38
45
|
async getChangeDetails(config, targetInfo, revision) {
|
|
46
|
+
if (revision === "UNCOMMITTED") {
|
|
47
|
+
const details = await svnClient.getUncommittedDetails(config, targetInfo);
|
|
48
|
+
|
|
49
|
+
return {
|
|
50
|
+
id: "UNCOMMITTED",
|
|
51
|
+
displayId: "uncommitted",
|
|
52
|
+
author: details.author,
|
|
53
|
+
date: details.date,
|
|
54
|
+
message: details.message,
|
|
55
|
+
changedPaths: details.changedPaths
|
|
56
|
+
};
|
|
57
|
+
}
|
|
58
|
+
|
|
39
59
|
const details = await svnClient.getRevisionDetails(config, targetInfo, revision);
|
|
40
60
|
|
|
41
61
|
return {
|
|
@@ -56,9 +76,13 @@ function createGitBackend() {
|
|
|
56
76
|
displayName: "Git",
|
|
57
77
|
changeName: "commit",
|
|
58
78
|
formatChangeId(commitHash) {
|
|
79
|
+
if (commitHash === "UNCOMMITTED") return "uncommitted";
|
|
59
80
|
return commitHash.slice(0, 12);
|
|
60
81
|
},
|
|
61
82
|
getReportFileName(commitHash) {
|
|
83
|
+
if (commitHash === "UNCOMMITTED") {
|
|
84
|
+
return `${getTimestampPrefix()}-git-uncommitted.md`;
|
|
85
|
+
}
|
|
62
86
|
return `${getTimestampPrefix()}-git-${commitHash.slice(0, 12)}.md`;
|
|
63
87
|
},
|
|
64
88
|
|
|
@@ -82,9 +106,25 @@ function createGitBackend() {
|
|
|
82
106
|
return await gitClient.getLatestCommitIds(config, targetInfo, limit);
|
|
83
107
|
},
|
|
84
108
|
async getChangeDiff(config, targetInfo, commitHash) {
|
|
109
|
+
if (commitHash === "UNCOMMITTED") {
|
|
110
|
+
return await gitClient.getUncommittedDiff(config, targetInfo);
|
|
111
|
+
}
|
|
85
112
|
return await gitClient.getCommitDiff(config, targetInfo, commitHash);
|
|
86
113
|
},
|
|
87
114
|
async getChangeDetails(config, targetInfo, commitHash) {
|
|
115
|
+
if (commitHash === "UNCOMMITTED") {
|
|
116
|
+
const details = await gitClient.getUncommittedDetails(config, targetInfo);
|
|
117
|
+
|
|
118
|
+
return {
|
|
119
|
+
id: "UNCOMMITTED",
|
|
120
|
+
displayId: "uncommitted",
|
|
121
|
+
author: details.author,
|
|
122
|
+
date: details.date,
|
|
123
|
+
message: details.message,
|
|
124
|
+
changedPaths: details.changedPaths
|
|
125
|
+
};
|
|
126
|
+
}
|
|
127
|
+
|
|
88
128
|
const details = await gitClient.getCommitDetails(config, targetInfo, commitHash);
|
|
89
129
|
|
|
90
130
|
return {
|