@aaronshaf/ger 2.0.2 → 2.0.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/docs/adr/0023-show-reviewer-list.md +42 -0
- package/docs/adr/README.md +1 -0
- package/docs/prd/commands.md +3 -1
- package/docs/prd/data-model.md +19 -8
- package/package.json +1 -1
- package/src/cli/commands/abandon.ts +28 -2
- package/src/cli/commands/add-reviewer.ts +37 -5
- package/src/cli/commands/comment.ts +93 -6
- package/src/cli/commands/comments.ts +34 -2
- package/src/cli/commands/diff.ts +13 -1
- package/src/cli/commands/groups-members.ts +28 -3
- package/src/cli/commands/groups-show.ts +38 -2
- package/src/cli/commands/groups.ts +32 -3
- package/src/cli/commands/incoming.ts +21 -1
- package/src/cli/commands/install-hook.ts +27 -6
- package/src/cli/commands/mine.ts +18 -1
- package/src/cli/commands/projects.ts +22 -2
- package/src/cli/commands/rebase.ts +19 -2
- package/src/cli/commands/remove-reviewer.ts +32 -4
- package/src/cli/commands/restore.ts +15 -1
- package/src/cli/commands/search.ts +22 -1
- package/src/cli/commands/show.ts +114 -1
- package/src/cli/commands/status.ts +11 -1
- package/src/cli/commands/submit.ts +30 -2
- package/src/cli/commands/topic.ts +33 -3
- package/src/cli/commands/vote.ts +15 -1
- package/src/cli/commands/workspace.ts +23 -4
- package/src/cli/register-commands.ts +40 -22
- package/src/cli/register-group-commands.ts +17 -2
- package/src/cli/register-reviewer-commands.ts +16 -2
- package/src/schemas/gerrit.ts +29 -14
- package/src/services/commit-hook.ts +6 -8
- package/tests/show.test.ts +126 -0
- package/tests/submit.test.ts +28 -0
|
@@ -13,6 +13,7 @@ const execAsync = promisify(exec)
|
|
|
13
13
|
|
|
14
14
|
interface IncomingOptions {
|
|
15
15
|
xml?: boolean
|
|
16
|
+
json?: boolean
|
|
16
17
|
interactive?: boolean
|
|
17
18
|
}
|
|
18
19
|
|
|
@@ -161,7 +162,26 @@ export const incomingCommand = (
|
|
|
161
162
|
return
|
|
162
163
|
}
|
|
163
164
|
|
|
164
|
-
if (options.
|
|
165
|
+
if (options.json) {
|
|
166
|
+
// JSON output
|
|
167
|
+
const groupedChanges = groupChangesByProject(changes)
|
|
168
|
+
const jsonOutput = {
|
|
169
|
+
status: 'success',
|
|
170
|
+
count: changes.length,
|
|
171
|
+
changes: groupedChanges.flatMap(({ project, changes: projectChanges }) =>
|
|
172
|
+
projectChanges.map((change) => ({
|
|
173
|
+
number: change._number,
|
|
174
|
+
subject: change.subject,
|
|
175
|
+
status: change.status,
|
|
176
|
+
project,
|
|
177
|
+
owner: change.owner?.name ?? 'Unknown',
|
|
178
|
+
...(change.owner?.email ? { owner_email: change.owner.email } : {}),
|
|
179
|
+
...(change.updated ? { updated: change.updated } : {}),
|
|
180
|
+
})),
|
|
181
|
+
),
|
|
182
|
+
}
|
|
183
|
+
console.log(JSON.stringify(jsonOutput, null, 2))
|
|
184
|
+
} else if (options.xml) {
|
|
165
185
|
// XML output
|
|
166
186
|
const xmlOutput = [
|
|
167
187
|
'<?xml version="1.0" encoding="UTF-8"?>',
|
|
@@ -11,6 +11,7 @@ import { type ConfigError, type ConfigServiceImpl } from '@/services/config'
|
|
|
11
11
|
export interface InstallHookOptions {
|
|
12
12
|
force?: boolean
|
|
13
13
|
xml?: boolean
|
|
14
|
+
json?: boolean
|
|
14
15
|
}
|
|
15
16
|
|
|
16
17
|
export type InstallHookErrors = ConfigError | HookInstallError | NotGitRepoError
|
|
@@ -25,7 +26,19 @@ export const installHookCommand = (
|
|
|
25
26
|
const hookExists = yield* commitHookService.hasHook()
|
|
26
27
|
|
|
27
28
|
if (hookExists && !options.force) {
|
|
28
|
-
if (options.
|
|
29
|
+
if (options.json) {
|
|
30
|
+
yield* Console.log(
|
|
31
|
+
JSON.stringify(
|
|
32
|
+
{
|
|
33
|
+
status: 'skipped',
|
|
34
|
+
message: 'commit-msg hook already installed',
|
|
35
|
+
hint: 'Use --force to overwrite',
|
|
36
|
+
},
|
|
37
|
+
null,
|
|
38
|
+
2,
|
|
39
|
+
),
|
|
40
|
+
)
|
|
41
|
+
} else if (options.xml) {
|
|
29
42
|
yield* Console.log('<?xml version="1.0" encoding="UTF-8"?>')
|
|
30
43
|
yield* Console.log('<install_hook_result>')
|
|
31
44
|
yield* Console.log(' <status>skipped</status>')
|
|
@@ -40,16 +53,24 @@ export const installHookCommand = (
|
|
|
40
53
|
}
|
|
41
54
|
|
|
42
55
|
if (hookExists && options.force) {
|
|
43
|
-
if (!options.xml) {
|
|
56
|
+
if (!options.xml && !options.json) {
|
|
44
57
|
yield* Console.log(chalk.yellow('Overwriting existing commit-msg hook...'))
|
|
45
58
|
}
|
|
46
59
|
}
|
|
47
60
|
|
|
48
|
-
|
|
49
|
-
yield* commitHookService.installHook()
|
|
61
|
+
const quiet = options.xml === true || options.json === true
|
|
62
|
+
yield* commitHookService.installHook(quiet)
|
|
50
63
|
|
|
51
|
-
// Only output XML here - service already logs success message for
|
|
52
|
-
if (options.
|
|
64
|
+
// Only output JSON/XML here - service already logs success message for plain mode
|
|
65
|
+
if (options.json) {
|
|
66
|
+
yield* Console.log(
|
|
67
|
+
JSON.stringify(
|
|
68
|
+
{ status: 'success', message: 'commit-msg hook installed successfully' },
|
|
69
|
+
null,
|
|
70
|
+
2,
|
|
71
|
+
),
|
|
72
|
+
)
|
|
73
|
+
} else if (options.xml) {
|
|
53
74
|
yield* Console.log('<?xml version="1.0" encoding="UTF-8"?>')
|
|
54
75
|
yield* Console.log('<install_hook_result>')
|
|
55
76
|
yield* Console.log(' <status>success</status>')
|
package/src/cli/commands/mine.ts
CHANGED
|
@@ -5,6 +5,7 @@ import { colors } from '@/utils/formatters'
|
|
|
5
5
|
|
|
6
6
|
interface MineOptions {
|
|
7
7
|
xml?: boolean
|
|
8
|
+
json?: boolean
|
|
8
9
|
}
|
|
9
10
|
|
|
10
11
|
// ANSI color codes
|
|
@@ -17,7 +18,23 @@ export const mineCommand = (
|
|
|
17
18
|
|
|
18
19
|
const changes = yield* gerritApi.listChanges('owner:self status:open')
|
|
19
20
|
|
|
20
|
-
if (options.
|
|
21
|
+
if (options.json) {
|
|
22
|
+
const jsonOutput = {
|
|
23
|
+
status: 'success',
|
|
24
|
+
count: changes.length,
|
|
25
|
+
changes: changes.map((change) => ({
|
|
26
|
+
number: change._number,
|
|
27
|
+
subject: change.subject,
|
|
28
|
+
project: change.project,
|
|
29
|
+
branch: change.branch,
|
|
30
|
+
status: change.status,
|
|
31
|
+
change_id: change.change_id,
|
|
32
|
+
...(change.updated ? { updated: change.updated } : {}),
|
|
33
|
+
...(change.owner?.name ? { owner: change.owner.name } : {}),
|
|
34
|
+
})),
|
|
35
|
+
}
|
|
36
|
+
console.log(JSON.stringify(jsonOutput, null, 2))
|
|
37
|
+
} else if (options.xml) {
|
|
21
38
|
console.log(`<?xml version="1.0" encoding="UTF-8"?>`)
|
|
22
39
|
console.log(`<changes count="${changes.length}">`)
|
|
23
40
|
|
|
@@ -4,6 +4,7 @@ import { type ApiError, GerritApiService } from '@/api/gerrit'
|
|
|
4
4
|
interface ProjectsOptions {
|
|
5
5
|
pattern?: string
|
|
6
6
|
xml?: boolean
|
|
7
|
+
json?: boolean
|
|
7
8
|
}
|
|
8
9
|
|
|
9
10
|
/**
|
|
@@ -27,7 +28,9 @@ export const projectsCommand = (
|
|
|
27
28
|
|
|
28
29
|
// Handle empty results
|
|
29
30
|
if (projects.length === 0) {
|
|
30
|
-
if (options.
|
|
31
|
+
if (options.json) {
|
|
32
|
+
console.log(JSON.stringify({ status: 'success', projects: [] }, null, 2))
|
|
33
|
+
} else if (options.xml) {
|
|
31
34
|
console.log(`<?xml version="1.0" encoding="UTF-8"?>`)
|
|
32
35
|
console.log(`<projects_result>`)
|
|
33
36
|
console.log(` <status>success</status>`)
|
|
@@ -40,7 +43,24 @@ export const projectsCommand = (
|
|
|
40
43
|
}
|
|
41
44
|
|
|
42
45
|
// Output results
|
|
43
|
-
if (options.
|
|
46
|
+
if (options.json) {
|
|
47
|
+
console.log(
|
|
48
|
+
JSON.stringify(
|
|
49
|
+
{
|
|
50
|
+
status: 'success',
|
|
51
|
+
count: projects.length,
|
|
52
|
+
projects: projects.map((project) => ({
|
|
53
|
+
id: project.id,
|
|
54
|
+
name: project.name,
|
|
55
|
+
...(project.parent ? { parent: project.parent } : {}),
|
|
56
|
+
...(project.state ? { state: project.state } : {}),
|
|
57
|
+
})),
|
|
58
|
+
},
|
|
59
|
+
null,
|
|
60
|
+
2,
|
|
61
|
+
),
|
|
62
|
+
)
|
|
63
|
+
} else if (options.xml) {
|
|
44
64
|
console.log(`<?xml version="1.0" encoding="UTF-8"?>`)
|
|
45
65
|
console.log(`<projects_result>`)
|
|
46
66
|
console.log(` <status>success</status>`)
|
|
@@ -6,6 +6,7 @@ import { escapeXML, sanitizeCDATA } from '@/utils/shell-safety'
|
|
|
6
6
|
interface RebaseOptions {
|
|
7
7
|
base?: string
|
|
8
8
|
xml?: boolean
|
|
9
|
+
json?: boolean
|
|
9
10
|
}
|
|
10
11
|
|
|
11
12
|
/**
|
|
@@ -30,7 +31,21 @@ export const rebaseCommand = (
|
|
|
30
31
|
// Perform the rebase - this returns the rebased change info
|
|
31
32
|
const change = yield* gerritApi.rebaseChange(resolvedChangeId, { base: options.base })
|
|
32
33
|
|
|
33
|
-
if (options.
|
|
34
|
+
if (options.json) {
|
|
35
|
+
console.log(
|
|
36
|
+
JSON.stringify(
|
|
37
|
+
{
|
|
38
|
+
status: 'success',
|
|
39
|
+
change_number: change._number,
|
|
40
|
+
subject: change.subject,
|
|
41
|
+
branch: change.branch,
|
|
42
|
+
...(options.base ? { base: options.base } : {}),
|
|
43
|
+
},
|
|
44
|
+
null,
|
|
45
|
+
2,
|
|
46
|
+
),
|
|
47
|
+
)
|
|
48
|
+
} else if (options.xml) {
|
|
34
49
|
console.log(`<?xml version="1.0" encoding="UTF-8"?>`)
|
|
35
50
|
console.log(`<rebase_result>`)
|
|
36
51
|
console.log(` <status>success</status>`)
|
|
@@ -57,7 +72,9 @@ export const rebaseCommand = (
|
|
|
57
72
|
? error.message
|
|
58
73
|
: String(error)
|
|
59
74
|
|
|
60
|
-
if (options.
|
|
75
|
+
if (options.json) {
|
|
76
|
+
console.log(JSON.stringify({ status: 'error', error: errorMessage }, null, 2))
|
|
77
|
+
} else if (options.xml) {
|
|
61
78
|
console.log(`<?xml version="1.0" encoding="UTF-8"?>`)
|
|
62
79
|
console.log(`<rebase_result>`)
|
|
63
80
|
console.log(` <status>error</status>`)
|
|
@@ -6,6 +6,7 @@ interface RemoveReviewerOptions {
|
|
|
6
6
|
change?: string
|
|
7
7
|
notify?: string
|
|
8
8
|
xml?: boolean
|
|
9
|
+
json?: boolean
|
|
9
10
|
}
|
|
10
11
|
|
|
11
12
|
type NotifyLevel = 'NONE' | 'OWNER' | 'OWNER_REVIEWERS' | 'ALL'
|
|
@@ -23,6 +24,10 @@ const outputXmlError = (message: string): void => {
|
|
|
23
24
|
console.log(`</remove_reviewer_result>`)
|
|
24
25
|
}
|
|
25
26
|
|
|
27
|
+
const outputJsonError = (message: string): void => {
|
|
28
|
+
console.log(JSON.stringify({ status: 'error', error: message }, null, 2))
|
|
29
|
+
}
|
|
30
|
+
|
|
26
31
|
class ValidationError extends Error {
|
|
27
32
|
readonly _tag = 'ValidationError'
|
|
28
33
|
}
|
|
@@ -39,7 +44,9 @@ export const removeReviewerCommand = (
|
|
|
39
44
|
if (!changeId) {
|
|
40
45
|
const message =
|
|
41
46
|
'Change ID is required. Use -c <change-id> or run from a branch with an active change.'
|
|
42
|
-
if (options.
|
|
47
|
+
if (options.json) {
|
|
48
|
+
outputJsonError(message)
|
|
49
|
+
} else if (options.xml) {
|
|
43
50
|
outputXmlError(message)
|
|
44
51
|
} else {
|
|
45
52
|
console.error(`✗ ${message}`)
|
|
@@ -49,7 +56,9 @@ export const removeReviewerCommand = (
|
|
|
49
56
|
|
|
50
57
|
if (reviewers.length === 0) {
|
|
51
58
|
const message = 'At least one reviewer is required.'
|
|
52
|
-
if (options.
|
|
59
|
+
if (options.json) {
|
|
60
|
+
outputJsonError(message)
|
|
61
|
+
} else if (options.xml) {
|
|
53
62
|
outputXmlError(message)
|
|
54
63
|
} else {
|
|
55
64
|
console.error(`✗ ${message}`)
|
|
@@ -62,7 +71,9 @@ export const removeReviewerCommand = (
|
|
|
62
71
|
const upperNotify = options.notify.toUpperCase()
|
|
63
72
|
if (!isValidNotifyLevel(upperNotify)) {
|
|
64
73
|
const message = `Invalid notify level: ${options.notify}. Valid values: none, owner, owner_reviewers, all`
|
|
65
|
-
if (options.
|
|
74
|
+
if (options.json) {
|
|
75
|
+
outputJsonError(message)
|
|
76
|
+
} else if (options.xml) {
|
|
66
77
|
outputXmlError(message)
|
|
67
78
|
} else {
|
|
68
79
|
console.error(`✗ ${message}`)
|
|
@@ -90,7 +101,24 @@ export const removeReviewerCommand = (
|
|
|
90
101
|
results.push({ reviewer, success: true })
|
|
91
102
|
}
|
|
92
103
|
|
|
93
|
-
if (options.
|
|
104
|
+
if (options.json) {
|
|
105
|
+
const allSuccess = results.every((r) => r.success)
|
|
106
|
+
console.log(
|
|
107
|
+
JSON.stringify(
|
|
108
|
+
{
|
|
109
|
+
status: allSuccess ? 'success' : 'partial_failure',
|
|
110
|
+
change_id: changeId,
|
|
111
|
+
reviewers: results.map((r) =>
|
|
112
|
+
r.success
|
|
113
|
+
? { input: r.reviewer, status: 'removed' }
|
|
114
|
+
: { input: r.reviewer, error: r.error, status: 'failed' },
|
|
115
|
+
),
|
|
116
|
+
},
|
|
117
|
+
null,
|
|
118
|
+
2,
|
|
119
|
+
),
|
|
120
|
+
)
|
|
121
|
+
} else if (options.xml) {
|
|
94
122
|
console.log(`<?xml version="1.0" encoding="UTF-8"?>`)
|
|
95
123
|
console.log(`<remove_reviewer_result>`)
|
|
96
124
|
console.log(` <change_id>${escapeXML(changeId)}</change_id>`)
|
|
@@ -4,6 +4,7 @@ import { type ApiError, GerritApiService } from '@/api/gerrit'
|
|
|
4
4
|
interface RestoreOptions {
|
|
5
5
|
message?: string
|
|
6
6
|
xml?: boolean
|
|
7
|
+
json?: boolean
|
|
7
8
|
}
|
|
8
9
|
|
|
9
10
|
/**
|
|
@@ -31,7 +32,20 @@ export const restoreCommand = (
|
|
|
31
32
|
// Perform the restore - this returns the restored change info
|
|
32
33
|
const change = yield* gerritApi.restoreChange(changeId, options.message)
|
|
33
34
|
|
|
34
|
-
if (options.
|
|
35
|
+
if (options.json) {
|
|
36
|
+
console.log(
|
|
37
|
+
JSON.stringify(
|
|
38
|
+
{
|
|
39
|
+
status: 'success',
|
|
40
|
+
change_number: change._number,
|
|
41
|
+
subject: change.subject,
|
|
42
|
+
...(options.message ? { message: options.message } : {}),
|
|
43
|
+
},
|
|
44
|
+
null,
|
|
45
|
+
2,
|
|
46
|
+
),
|
|
47
|
+
)
|
|
48
|
+
} else if (options.xml) {
|
|
35
49
|
console.log(`<?xml version="1.0" encoding="UTF-8"?>`)
|
|
36
50
|
console.log(`<restore_result>`)
|
|
37
51
|
console.log(` <status>success</status>`)
|
|
@@ -46,6 +46,7 @@ Full query syntax: https://gerrit-review.googlesource.com/Documentation/user-sea
|
|
|
46
46
|
|
|
47
47
|
interface SearchOptions {
|
|
48
48
|
xml?: boolean
|
|
49
|
+
json?: boolean
|
|
49
50
|
limit?: string
|
|
50
51
|
}
|
|
51
52
|
|
|
@@ -93,7 +94,27 @@ export const searchCommand = (
|
|
|
93
94
|
// Group changes by project (used by both output formats)
|
|
94
95
|
const groupedChanges = changes.length > 0 ? groupChangesByProject(changes) : []
|
|
95
96
|
|
|
96
|
-
if (options.
|
|
97
|
+
if (options.json) {
|
|
98
|
+
// JSON output
|
|
99
|
+
const jsonOutput = {
|
|
100
|
+
status: 'success',
|
|
101
|
+
query: finalQuery,
|
|
102
|
+
count: changes.length,
|
|
103
|
+
changes: groupedChanges.flatMap(({ project, changes: projectChanges }) =>
|
|
104
|
+
projectChanges.map((change) => ({
|
|
105
|
+
number: change._number,
|
|
106
|
+
subject: change.subject,
|
|
107
|
+
status: change.status,
|
|
108
|
+
project,
|
|
109
|
+
branch: change.branch,
|
|
110
|
+
owner: change.owner?.name ?? 'Unknown',
|
|
111
|
+
...(change.owner?.email ? { owner_email: change.owner.email } : {}),
|
|
112
|
+
...(change.updated ? { updated: change.updated } : {}),
|
|
113
|
+
})),
|
|
114
|
+
),
|
|
115
|
+
}
|
|
116
|
+
console.log(JSON.stringify(jsonOutput, null, 2))
|
|
117
|
+
} else if (options.xml) {
|
|
97
118
|
// XML output
|
|
98
119
|
const xmlOutput = [
|
|
99
120
|
'<?xml version="1.0" encoding="UTF-8"?>',
|
package/src/cli/commands/show.ts
CHANGED
|
@@ -7,7 +7,6 @@ import { formatDiffPretty } from '@/utils/diff-formatters'
|
|
|
7
7
|
import { sanitizeCDATA, escapeXML } from '@/utils/shell-safety'
|
|
8
8
|
import { formatDate } from '@/utils/formatters'
|
|
9
9
|
import { getChangeIdFromHead, GitError, NoChangeIdError } from '@/utils/git-commit'
|
|
10
|
-
import { writeFileSync } from 'node:fs'
|
|
11
10
|
|
|
12
11
|
export const SHOW_HELP_TEXT = `
|
|
13
12
|
Examples:
|
|
@@ -34,6 +33,13 @@ interface ShowOptions {
|
|
|
34
33
|
json?: boolean
|
|
35
34
|
}
|
|
36
35
|
|
|
36
|
+
interface ReviewerIdentity {
|
|
37
|
+
accountId?: number
|
|
38
|
+
name?: string
|
|
39
|
+
email?: string
|
|
40
|
+
username?: string
|
|
41
|
+
}
|
|
42
|
+
|
|
37
43
|
interface ChangeDetails {
|
|
38
44
|
id: string
|
|
39
45
|
number: number
|
|
@@ -49,6 +55,24 @@ interface ChangeDetails {
|
|
|
49
55
|
updated?: string
|
|
50
56
|
commitMessage: string
|
|
51
57
|
topic?: string
|
|
58
|
+
reviewers: ReviewerIdentity[]
|
|
59
|
+
ccs: ReviewerIdentity[]
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
const formatReviewerLabel = (reviewer: ReviewerIdentity): string => {
|
|
63
|
+
const preferredIdentity = reviewer.name || reviewer.email || reviewer.username
|
|
64
|
+
if (!preferredIdentity) {
|
|
65
|
+
if (reviewer.accountId !== undefined) {
|
|
66
|
+
return `Account ${reviewer.accountId}`
|
|
67
|
+
}
|
|
68
|
+
return 'Unknown Reviewer'
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
if (reviewer.email && reviewer.name && reviewer.name !== reviewer.email) {
|
|
72
|
+
return `${reviewer.name} <${reviewer.email}>`
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
return preferredIdentity
|
|
52
76
|
}
|
|
53
77
|
|
|
54
78
|
const getChangeDetails = (
|
|
@@ -58,6 +82,21 @@ const getChangeDetails = (
|
|
|
58
82
|
const gerritApi = yield* GerritApiService
|
|
59
83
|
const change = yield* gerritApi.getChange(changeId)
|
|
60
84
|
|
|
85
|
+
let reviewerMap = change.reviewers
|
|
86
|
+
const shouldFetchReviewerFallback =
|
|
87
|
+
reviewerMap === undefined ||
|
|
88
|
+
(reviewerMap.REVIEWER === undefined && reviewerMap.CC === undefined)
|
|
89
|
+
|
|
90
|
+
if (shouldFetchReviewerFallback) {
|
|
91
|
+
const detailedChanges = yield* gerritApi
|
|
92
|
+
.listChanges(`change:${change._number}`)
|
|
93
|
+
.pipe(Effect.catchAll(() => Effect.succeed([])))
|
|
94
|
+
const detailedChange =
|
|
95
|
+
detailedChanges.find((candidate) => candidate._number === change._number) ||
|
|
96
|
+
detailedChanges[0]
|
|
97
|
+
reviewerMap = detailedChange?.reviewers
|
|
98
|
+
}
|
|
99
|
+
|
|
61
100
|
return {
|
|
62
101
|
id: change.change_id,
|
|
63
102
|
number: change._number,
|
|
@@ -73,6 +112,18 @@ const getChangeDetails = (
|
|
|
73
112
|
updated: change.updated,
|
|
74
113
|
commitMessage: change.subject, // For now, using subject as commit message
|
|
75
114
|
topic: change.topic,
|
|
115
|
+
reviewers: (reviewerMap?.REVIEWER ?? []).map((reviewer) => ({
|
|
116
|
+
accountId: reviewer._account_id,
|
|
117
|
+
name: reviewer.name,
|
|
118
|
+
email: reviewer.email,
|
|
119
|
+
username: reviewer.username,
|
|
120
|
+
})),
|
|
121
|
+
ccs: (reviewerMap?.CC ?? []).map((cc) => ({
|
|
122
|
+
accountId: cc._account_id,
|
|
123
|
+
name: cc.name,
|
|
124
|
+
email: cc.email,
|
|
125
|
+
username: cc.username,
|
|
126
|
+
})),
|
|
76
127
|
}
|
|
77
128
|
})
|
|
78
129
|
|
|
@@ -154,6 +205,14 @@ const formatShowPretty = (
|
|
|
154
205
|
console.log(
|
|
155
206
|
` Updated: ${changeDetails.updated ? formatDate(changeDetails.updated) : 'Unknown'}`,
|
|
156
207
|
)
|
|
208
|
+
if (changeDetails.reviewers.length > 0) {
|
|
209
|
+
console.log(
|
|
210
|
+
` Reviewers: ${changeDetails.reviewers.map((reviewer) => formatReviewerLabel(reviewer)).join(', ')}`,
|
|
211
|
+
)
|
|
212
|
+
}
|
|
213
|
+
if (changeDetails.ccs.length > 0) {
|
|
214
|
+
console.log(` CCs: ${changeDetails.ccs.map((cc) => formatReviewerLabel(cc)).join(', ')}`)
|
|
215
|
+
}
|
|
157
216
|
console.log(` Change-Id: ${changeDetails.id}`)
|
|
158
217
|
console.log()
|
|
159
218
|
|
|
@@ -227,6 +286,22 @@ const formatShowJson = async (
|
|
|
227
286
|
branch: changeDetails.branch,
|
|
228
287
|
topic: changeDetails.topic,
|
|
229
288
|
owner: removeUndefined(changeDetails.owner),
|
|
289
|
+
reviewers: changeDetails.reviewers.map((reviewer) =>
|
|
290
|
+
removeUndefined({
|
|
291
|
+
account_id: reviewer.accountId,
|
|
292
|
+
name: reviewer.name,
|
|
293
|
+
email: reviewer.email,
|
|
294
|
+
username: reviewer.username,
|
|
295
|
+
}),
|
|
296
|
+
),
|
|
297
|
+
ccs: changeDetails.ccs.map((cc) =>
|
|
298
|
+
removeUndefined({
|
|
299
|
+
account_id: cc.accountId,
|
|
300
|
+
name: cc.name,
|
|
301
|
+
email: cc.email,
|
|
302
|
+
username: cc.username,
|
|
303
|
+
}),
|
|
304
|
+
),
|
|
230
305
|
created: changeDetails.created,
|
|
231
306
|
updated: changeDetails.updated,
|
|
232
307
|
}),
|
|
@@ -318,6 +393,44 @@ const formatShowXml = async (
|
|
|
318
393
|
xmlParts.push(` <email>${escapeXML(changeDetails.owner.email)}</email>`)
|
|
319
394
|
}
|
|
320
395
|
xmlParts.push(` </owner>`)
|
|
396
|
+
xmlParts.push(` <reviewers>`)
|
|
397
|
+
xmlParts.push(` <count>${changeDetails.reviewers.length}</count>`)
|
|
398
|
+
for (const reviewer of changeDetails.reviewers) {
|
|
399
|
+
xmlParts.push(` <reviewer>`)
|
|
400
|
+
if (reviewer.accountId !== undefined) {
|
|
401
|
+
xmlParts.push(` <account_id>${reviewer.accountId}</account_id>`)
|
|
402
|
+
}
|
|
403
|
+
if (reviewer.name) {
|
|
404
|
+
xmlParts.push(` <name><![CDATA[${sanitizeCDATA(reviewer.name)}]]></name>`)
|
|
405
|
+
}
|
|
406
|
+
if (reviewer.email) {
|
|
407
|
+
xmlParts.push(` <email>${escapeXML(reviewer.email)}</email>`)
|
|
408
|
+
}
|
|
409
|
+
if (reviewer.username) {
|
|
410
|
+
xmlParts.push(` <username>${escapeXML(reviewer.username)}</username>`)
|
|
411
|
+
}
|
|
412
|
+
xmlParts.push(` </reviewer>`)
|
|
413
|
+
}
|
|
414
|
+
xmlParts.push(` </reviewers>`)
|
|
415
|
+
xmlParts.push(` <ccs>`)
|
|
416
|
+
xmlParts.push(` <count>${changeDetails.ccs.length}</count>`)
|
|
417
|
+
for (const cc of changeDetails.ccs) {
|
|
418
|
+
xmlParts.push(` <cc>`)
|
|
419
|
+
if (cc.accountId !== undefined) {
|
|
420
|
+
xmlParts.push(` <account_id>${cc.accountId}</account_id>`)
|
|
421
|
+
}
|
|
422
|
+
if (cc.name) {
|
|
423
|
+
xmlParts.push(` <name><![CDATA[${sanitizeCDATA(cc.name)}]]></name>`)
|
|
424
|
+
}
|
|
425
|
+
if (cc.email) {
|
|
426
|
+
xmlParts.push(` <email>${escapeXML(cc.email)}</email>`)
|
|
427
|
+
}
|
|
428
|
+
if (cc.username) {
|
|
429
|
+
xmlParts.push(` <username>${escapeXML(cc.username)}</username>`)
|
|
430
|
+
}
|
|
431
|
+
xmlParts.push(` </cc>`)
|
|
432
|
+
}
|
|
433
|
+
xmlParts.push(` </ccs>`)
|
|
321
434
|
xmlParts.push(` <created>${escapeXML(changeDetails.created || '')}</created>`)
|
|
322
435
|
xmlParts.push(` <updated>${escapeXML(changeDetails.updated || '')}</updated>`)
|
|
323
436
|
xmlParts.push(` </change>`)
|
|
@@ -3,6 +3,7 @@ import { GerritApiService } from '@/api/gerrit'
|
|
|
3
3
|
|
|
4
4
|
interface StatusOptions {
|
|
5
5
|
xml?: boolean
|
|
6
|
+
json?: boolean
|
|
6
7
|
}
|
|
7
8
|
|
|
8
9
|
export const statusCommand = (
|
|
@@ -13,7 +14,16 @@ export const statusCommand = (
|
|
|
13
14
|
|
|
14
15
|
const isConnected = yield* apiService.testConnection
|
|
15
16
|
|
|
16
|
-
if (options.
|
|
17
|
+
if (options.json) {
|
|
18
|
+
// JSON output
|
|
19
|
+
console.log(
|
|
20
|
+
JSON.stringify(
|
|
21
|
+
{ status: isConnected ? 'success' : 'error', connected: isConnected },
|
|
22
|
+
null,
|
|
23
|
+
2,
|
|
24
|
+
),
|
|
25
|
+
)
|
|
26
|
+
} else if (options.xml) {
|
|
17
27
|
// XML output for LLM consumption
|
|
18
28
|
console.log(`<?xml version="1.0" encoding="UTF-8"?>`)
|
|
19
29
|
console.log(`<status_result>`)
|
|
@@ -3,6 +3,7 @@ import { type ApiError, GerritApiService } from '@/api/gerrit'
|
|
|
3
3
|
|
|
4
4
|
interface SubmitOptions {
|
|
5
5
|
xml?: boolean
|
|
6
|
+
json?: boolean
|
|
6
7
|
}
|
|
7
8
|
|
|
8
9
|
/**
|
|
@@ -65,7 +66,21 @@ export const submitCommand = (
|
|
|
65
66
|
reasons.push('Change does not meet submit requirements')
|
|
66
67
|
}
|
|
67
68
|
|
|
68
|
-
if (options.
|
|
69
|
+
if (options.json) {
|
|
70
|
+
console.log(
|
|
71
|
+
JSON.stringify(
|
|
72
|
+
{
|
|
73
|
+
status: 'error',
|
|
74
|
+
change_number: change._number,
|
|
75
|
+
subject: change.subject,
|
|
76
|
+
submittable: false,
|
|
77
|
+
reasons,
|
|
78
|
+
},
|
|
79
|
+
null,
|
|
80
|
+
2,
|
|
81
|
+
),
|
|
82
|
+
)
|
|
83
|
+
} else if (options.xml) {
|
|
69
84
|
console.log(`<?xml version="1.0" encoding="UTF-8"?>`)
|
|
70
85
|
console.log(`<submit_result>`)
|
|
71
86
|
console.log(` <status>error</status>`)
|
|
@@ -93,7 +108,20 @@ export const submitCommand = (
|
|
|
93
108
|
// Change is submittable, proceed with submission
|
|
94
109
|
const result = yield* gerritApi.submitChange(changeId)
|
|
95
110
|
|
|
96
|
-
if (options.
|
|
111
|
+
if (options.json) {
|
|
112
|
+
console.log(
|
|
113
|
+
JSON.stringify(
|
|
114
|
+
{
|
|
115
|
+
status: 'success',
|
|
116
|
+
change_number: change._number,
|
|
117
|
+
subject: change.subject,
|
|
118
|
+
submit_status: result.status,
|
|
119
|
+
},
|
|
120
|
+
null,
|
|
121
|
+
2,
|
|
122
|
+
),
|
|
123
|
+
)
|
|
124
|
+
} else if (options.xml) {
|
|
97
125
|
console.log(`<?xml version="1.0" encoding="UTF-8"?>`)
|
|
98
126
|
console.log(`<submit_result>`)
|
|
99
127
|
console.log(` <status>success</status>`)
|
|
@@ -22,6 +22,7 @@ Note: When no change-id is provided, it will be auto-detected from the HEAD comm
|
|
|
22
22
|
|
|
23
23
|
interface TopicOptions {
|
|
24
24
|
xml?: boolean
|
|
25
|
+
json?: boolean
|
|
25
26
|
delete?: boolean
|
|
26
27
|
}
|
|
27
28
|
|
|
@@ -52,7 +53,15 @@ export const topicCommand = (
|
|
|
52
53
|
if (options.delete) {
|
|
53
54
|
yield* gerritApi.deleteTopic(resolvedChangeId)
|
|
54
55
|
|
|
55
|
-
if (options.
|
|
56
|
+
if (options.json) {
|
|
57
|
+
console.log(
|
|
58
|
+
JSON.stringify(
|
|
59
|
+
{ status: 'success', action: 'deleted', change_id: resolvedChangeId },
|
|
60
|
+
null,
|
|
61
|
+
2,
|
|
62
|
+
),
|
|
63
|
+
)
|
|
64
|
+
} else if (options.xml) {
|
|
56
65
|
console.log(`<?xml version="1.0" encoding="UTF-8"?>`)
|
|
57
66
|
console.log(`<topic_result>`)
|
|
58
67
|
console.log(` <status>success</status>`)
|
|
@@ -69,7 +78,15 @@ export const topicCommand = (
|
|
|
69
78
|
if (topic !== undefined && topic.trim() !== '') {
|
|
70
79
|
const newTopic = yield* gerritApi.setTopic(resolvedChangeId, topic)
|
|
71
80
|
|
|
72
|
-
if (options.
|
|
81
|
+
if (options.json) {
|
|
82
|
+
console.log(
|
|
83
|
+
JSON.stringify(
|
|
84
|
+
{ status: 'success', action: 'set', change_id: resolvedChangeId, topic: newTopic },
|
|
85
|
+
null,
|
|
86
|
+
2,
|
|
87
|
+
),
|
|
88
|
+
)
|
|
89
|
+
} else if (options.xml) {
|
|
73
90
|
console.log(`<?xml version="1.0" encoding="UTF-8"?>`)
|
|
74
91
|
console.log(`<topic_result>`)
|
|
75
92
|
console.log(` <status>success</status>`)
|
|
@@ -86,7 +103,20 @@ export const topicCommand = (
|
|
|
86
103
|
// Handle get operation (default)
|
|
87
104
|
const currentTopic = yield* gerritApi.getTopic(resolvedChangeId)
|
|
88
105
|
|
|
89
|
-
if (options.
|
|
106
|
+
if (options.json) {
|
|
107
|
+
console.log(
|
|
108
|
+
JSON.stringify(
|
|
109
|
+
{
|
|
110
|
+
status: 'success',
|
|
111
|
+
action: 'get',
|
|
112
|
+
change_id: resolvedChangeId,
|
|
113
|
+
topic: currentTopic || null,
|
|
114
|
+
},
|
|
115
|
+
null,
|
|
116
|
+
2,
|
|
117
|
+
),
|
|
118
|
+
)
|
|
119
|
+
} else if (options.xml) {
|
|
90
120
|
console.log(`<?xml version="1.0" encoding="UTF-8"?>`)
|
|
91
121
|
console.log(`<topic_result>`)
|
|
92
122
|
console.log(` <status>success</status>`)
|