@aaronshaf/ger 2.0.10 → 3.0.2
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 +0 -26
- package/docs/prd/architecture.md +6 -48
- package/docs/prd/commands.md +85 -21
- package/index.ts +0 -19
- package/llms.txt +0 -14
- package/package.json +1 -5
- package/src/api/gerrit.ts +37 -40
- package/src/cli/commands/files.ts +86 -0
- package/src/cli/commands/reviewers.ts +95 -0
- package/src/cli/commands/set-ready.ts +76 -0
- package/src/cli/commands/set-wip.ts +76 -0
- package/src/cli/commands/setup.ts +0 -1
- package/src/cli/index.ts +1 -0
- package/src/cli/register-commands.ts +43 -102
- package/src/cli/register-state-commands.ts +106 -0
- package/src/schemas/gerrit.ts +2 -0
- package/src/schemas/reviewer.ts +16 -0
- package/src/utils/diff-formatters.ts +32 -0
- package/tests/files.test.ts +223 -0
- package/tests/reviewers.test.ts +259 -0
- package/src/cli/commands/review.ts +0 -486
- package/src/prompts/default-review.md +0 -86
- package/src/prompts/system-inline-review.md +0 -135
- package/src/prompts/system-overall-review.md +0 -206
- package/src/services/review-strategy.ts +0 -292
package/README.md
CHANGED
|
@@ -93,9 +93,6 @@ ger rebase 12345
|
|
|
93
93
|
ger submit 12345
|
|
94
94
|
ger restore 12345
|
|
95
95
|
|
|
96
|
-
# AI-powered code review (requires claude, llm, or opencode CLI)
|
|
97
|
-
ger review 12345
|
|
98
|
-
ger review 12345 --dry-run # Preview without posting
|
|
99
96
|
```
|
|
100
97
|
|
|
101
98
|
## Commands
|
|
@@ -711,29 +708,6 @@ ger groups-members project-reviewers --xml
|
|
|
711
708
|
- Group IDs can be names, numeric IDs, or UUIDs
|
|
712
709
|
- Use groups with `ger add-reviewer --group` to add entire teams as reviewers
|
|
713
710
|
|
|
714
|
-
### AI-Powered Review
|
|
715
|
-
|
|
716
|
-
The `ger review` command provides automated code review using AI tools (claude, llm, or opencode CLI).
|
|
717
|
-
|
|
718
|
-
```bash
|
|
719
|
-
# Full AI review with inline and overall comments
|
|
720
|
-
ger review 12345
|
|
721
|
-
|
|
722
|
-
# Preview what would be posted without actually posting
|
|
723
|
-
ger review 12345 --dry-run
|
|
724
|
-
|
|
725
|
-
# Show debug output including AI responses
|
|
726
|
-
ger review 12345 --debug
|
|
727
|
-
```
|
|
728
|
-
|
|
729
|
-
The review command performs a two-stage review process:
|
|
730
|
-
1. **Inline comments**: Specific code issues with line-by-line feedback
|
|
731
|
-
2. **Overall review**: High-level assessment and recommendations
|
|
732
|
-
|
|
733
|
-
Requirements:
|
|
734
|
-
- One of these AI tools must be installed: `claude`, `llm`, or `opencode`
|
|
735
|
-
- Gerrit credentials must be configured (`ger setup`)
|
|
736
|
-
|
|
737
711
|
## Claude Code Skill
|
|
738
712
|
|
|
739
713
|
This repository includes a Claude Code Agent Skill that teaches Claude how to work effectively with Gerrit using the ger CLI. The skill provides Claude with expertise in Gerrit workflows, command usage, and best practices.
|
package/docs/prd/architecture.md
CHANGED
|
@@ -6,16 +6,16 @@
|
|
|
6
6
|
┌─────────────────────────────────────────────────────────────┐
|
|
7
7
|
│ CLI Layer │
|
|
8
8
|
│ ┌─────────┐ ┌─────────┐ ┌─────────┐ ┌─────────┐ │
|
|
9
|
-
│ │ show │ │ comment │ │ push │ │
|
|
9
|
+
│ │ show │ │ comment │ │ push │ │ vote │ ... │
|
|
10
10
|
│ └────┬────┘ └────┬────┘ └────┬────┘ └────┬────┘ │
|
|
11
11
|
└───────┼───────────┼───────────┼───────────┼─────────────────┘
|
|
12
12
|
│ │ │ │
|
|
13
13
|
┌───────┴───────────┴───────────┴───────────┴─────────────────┐
|
|
14
14
|
│ Service Layer │
|
|
15
|
-
│ ┌──────────────┐ ┌──────────────┐
|
|
16
|
-
│ │ GerritApi │ │ ConfigService│
|
|
17
|
-
│ │ Service │ │ │
|
|
18
|
-
│ └──────────────┘ └──────────────┘
|
|
15
|
+
│ ┌──────────────┐ ┌──────────────┐ │
|
|
16
|
+
│ │ GerritApi │ │ ConfigService│ │
|
|
17
|
+
│ │ Service │ │ │ │
|
|
18
|
+
│ └──────────────┘ └──────────────┘ │
|
|
19
19
|
└───────┬───────────────────────────────────────────────────────┘
|
|
20
20
|
│
|
|
21
21
|
┌───────┴─────────────────────────────────────────────────────┐
|
|
@@ -46,7 +46,6 @@ src/
|
|
|
46
46
|
│
|
|
47
47
|
├── services/ # Business logic services
|
|
48
48
|
│ ├── config.ts # Configuration management
|
|
49
|
-
│ ├── review-strategy.ts # AI tool strategies
|
|
50
49
|
│ ├── git-worktree.ts # Git worktree operations
|
|
51
50
|
│ └── commit-hook.ts # Gerrit hook installation
|
|
52
51
|
│
|
|
@@ -59,12 +58,7 @@ src/
|
|
|
59
58
|
│ ├── git-commit.ts # Git operations
|
|
60
59
|
│ ├── formatters.ts # Output formatting
|
|
61
60
|
│ ├── shell-safety.ts # XML/CDATA handling
|
|
62
|
-
│ └── ... (diff, comment
|
|
63
|
-
│
|
|
64
|
-
├── prompts/ # AI review prompts
|
|
65
|
-
│ ├── default-review.md
|
|
66
|
-
│ ├── system-inline-review.md
|
|
67
|
-
│ └── system-overall-review.md
|
|
61
|
+
│ └── ... (diff, comment utils)
|
|
68
62
|
│
|
|
69
63
|
└── i18n/ # Internationalization (planned)
|
|
70
64
|
|
|
@@ -207,42 +201,6 @@ User: echo '...' | ger comment 12345
|
|
|
207
201
|
Success/Error
|
|
208
202
|
```
|
|
209
203
|
|
|
210
|
-
## AI Integration Flow
|
|
211
|
-
|
|
212
|
-
```
|
|
213
|
-
User: ger review 12345
|
|
214
|
-
│
|
|
215
|
-
▼
|
|
216
|
-
┌─────────────────────┐
|
|
217
|
-
│ Fetch change diff │
|
|
218
|
-
│ (API: GET /patch) │
|
|
219
|
-
└─────────┬───────────┘
|
|
220
|
-
│
|
|
221
|
-
▼
|
|
222
|
-
┌─────────────────────┐
|
|
223
|
-
│ Detect AI tool │
|
|
224
|
-
│ (claude/llm/etc) │
|
|
225
|
-
└─────────┬───────────┘
|
|
226
|
-
│
|
|
227
|
-
▼
|
|
228
|
-
┌─────────────────────┐ ┌─────────────────────┐
|
|
229
|
-
│ Stage 1: Inline │────►│ AI: Generate inline │
|
|
230
|
-
│ comments │ │ comments JSON │
|
|
231
|
-
└─────────┬───────────┘ └─────────────────────┘
|
|
232
|
-
│
|
|
233
|
-
▼
|
|
234
|
-
┌─────────────────────┐ ┌─────────────────────┐
|
|
235
|
-
│ Stage 2: Overall │────►│ AI: Generate │
|
|
236
|
-
│ review │ │ summary │
|
|
237
|
-
└─────────┬───────────┘ └─────────────────────┘
|
|
238
|
-
│
|
|
239
|
-
▼
|
|
240
|
-
┌─────────────────────┐
|
|
241
|
-
│ API: POST /review │
|
|
242
|
-
│ (all comments) │
|
|
243
|
-
└─────────────────────┘
|
|
244
|
-
```
|
|
245
|
-
|
|
246
204
|
## Configuration Architecture
|
|
247
205
|
|
|
248
206
|
```
|
package/docs/prd/commands.md
CHANGED
|
@@ -28,7 +28,65 @@ ger show # Auto-detect from HEAD
|
|
|
28
28
|
- Full diff
|
|
29
29
|
- All comments with context
|
|
30
30
|
|
|
31
|
-
|
|
31
|
+
### files
|
|
32
|
+
|
|
33
|
+
List files changed in a change.
|
|
34
|
+
|
|
35
|
+
```bash
|
|
36
|
+
ger files [change-id]
|
|
37
|
+
ger files 12345
|
|
38
|
+
ger files # Auto-detect from HEAD
|
|
39
|
+
ger files 12345 --json
|
|
40
|
+
ger files 12345 --xml
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
| Option | Description |
|
|
44
|
+
|--------|-------------|
|
|
45
|
+
| `--json` | Output as JSON |
|
|
46
|
+
| `--xml` | Output as XML for LLM consumption |
|
|
47
|
+
|
|
48
|
+
**Output:** One file per line with status prefix (`M` modified, `A` added, `D` deleted, `R` renamed). Magic files (`/COMMIT_MSG`, `/MERGE_LIST`, `/PATCHSET_LEVEL`) are filtered out.
|
|
49
|
+
|
|
50
|
+
**JSON output:**
|
|
51
|
+
```json
|
|
52
|
+
{
|
|
53
|
+
"status": "success",
|
|
54
|
+
"change_id": "12345",
|
|
55
|
+
"files": [
|
|
56
|
+
{ "path": "src/foo.ts", "status": "M", "lines_inserted": 10, "lines_deleted": 2 }
|
|
57
|
+
]
|
|
58
|
+
}
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
### reviewers
|
|
62
|
+
|
|
63
|
+
List reviewers on a change.
|
|
64
|
+
|
|
65
|
+
```bash
|
|
66
|
+
ger reviewers [change-id]
|
|
67
|
+
ger reviewers 12345
|
|
68
|
+
ger reviewers # Auto-detect from HEAD
|
|
69
|
+
ger reviewers 12345 --json
|
|
70
|
+
ger reviewers 12345 --xml
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
| Option | Description |
|
|
74
|
+
|--------|-------------|
|
|
75
|
+
| `--json` | Output as JSON |
|
|
76
|
+
| `--xml` | Output as XML for LLM consumption |
|
|
77
|
+
|
|
78
|
+
**Output:** One reviewer per line in `Name <email>` format (or email alone for email-only accounts).
|
|
79
|
+
|
|
80
|
+
**JSON output:**
|
|
81
|
+
```json
|
|
82
|
+
{
|
|
83
|
+
"status": "success",
|
|
84
|
+
"change_id": "12345",
|
|
85
|
+
"reviewers": [
|
|
86
|
+
{ "account_id": 1001, "name": "Alice Smith", "email": "alice@example.com", "username": "alice" }
|
|
87
|
+
]
|
|
88
|
+
}
|
|
89
|
+
```
|
|
32
90
|
|
|
33
91
|
### diff
|
|
34
92
|
|
|
@@ -121,6 +179,32 @@ ger restore <change-id>
|
|
|
121
179
|
ger restore <change-id> -m "Needed after all"
|
|
122
180
|
```
|
|
123
181
|
|
|
182
|
+
### set-ready
|
|
183
|
+
|
|
184
|
+
Mark a WIP change as ready for review via the Gerrit REST API. Does not require a git push.
|
|
185
|
+
|
|
186
|
+
```bash
|
|
187
|
+
ger set-ready <change-id>
|
|
188
|
+
ger set-ready <change-id> -m "Ready for another look"
|
|
189
|
+
```
|
|
190
|
+
|
|
191
|
+
| Option | Description |
|
|
192
|
+
|--------|-------------|
|
|
193
|
+
| `-m <message>` | Optional message to include with the status change |
|
|
194
|
+
|
|
195
|
+
### set-wip
|
|
196
|
+
|
|
197
|
+
Mark a change as work-in-progress via the Gerrit REST API. Does not require a git push.
|
|
198
|
+
|
|
199
|
+
```bash
|
|
200
|
+
ger set-wip <change-id>
|
|
201
|
+
ger set-wip <change-id> -m "Still in progress"
|
|
202
|
+
```
|
|
203
|
+
|
|
204
|
+
| Option | Description |
|
|
205
|
+
|--------|-------------|
|
|
206
|
+
| `-m <message>` | Optional message to include with the status change |
|
|
207
|
+
|
|
124
208
|
### workspace
|
|
125
209
|
|
|
126
210
|
View local git branch tracking information.
|
|
@@ -236,26 +320,6 @@ ger vote <change-id> --label "Custom-Label" +1
|
|
|
236
320
|
| `--label <name> <score>` | Custom label vote |
|
|
237
321
|
| `-m <message>` | Optional message with vote |
|
|
238
322
|
|
|
239
|
-
### review
|
|
240
|
-
|
|
241
|
-
AI-powered code review (multi-stage).
|
|
242
|
-
|
|
243
|
-
```bash
|
|
244
|
-
ger review <change-id>
|
|
245
|
-
ger review <change-id> --tool claude # Specific AI tool
|
|
246
|
-
ger review # Auto-detect change from HEAD
|
|
247
|
-
```
|
|
248
|
-
|
|
249
|
-
| Option | Description |
|
|
250
|
-
|--------|-------------|
|
|
251
|
-
| `--tool <name>` | AI tool (claude, llm, opencode, gemini) |
|
|
252
|
-
| `--inline-only` | Only post inline comments |
|
|
253
|
-
| `--overall-only` | Only post overall review |
|
|
254
|
-
|
|
255
|
-
**Stages:**
|
|
256
|
-
1. **Inline**: Generate line-specific comments
|
|
257
|
-
2. **Overall**: Generate high-level assessment
|
|
258
|
-
|
|
259
323
|
### add-reviewer
|
|
260
324
|
|
|
261
325
|
Add reviewers or groups to a change.
|
package/index.ts
CHANGED
|
@@ -61,25 +61,6 @@ export {
|
|
|
61
61
|
type ConfigErrorFields,
|
|
62
62
|
} from './src/services/config'
|
|
63
63
|
|
|
64
|
-
// ============================================================================
|
|
65
|
-
// Review Strategy Service
|
|
66
|
-
// ============================================================================
|
|
67
|
-
|
|
68
|
-
export {
|
|
69
|
-
// Strategy types
|
|
70
|
-
type ReviewStrategy,
|
|
71
|
-
// Built-in strategies
|
|
72
|
-
claudeCliStrategy,
|
|
73
|
-
geminiCliStrategy,
|
|
74
|
-
openCodeCliStrategy,
|
|
75
|
-
// Service
|
|
76
|
-
ReviewStrategyService,
|
|
77
|
-
ReviewStrategyServiceLive,
|
|
78
|
-
type ReviewStrategyServiceImpl,
|
|
79
|
-
// Errors
|
|
80
|
-
ReviewStrategyError,
|
|
81
|
-
type ReviewStrategyErrorFields,
|
|
82
|
-
} from './src/services/review-strategy'
|
|
83
64
|
|
|
84
65
|
// ============================================================================
|
|
85
66
|
// Git Worktree Service
|
package/llms.txt
CHANGED
|
@@ -114,14 +114,6 @@ ger extract-url "build-summary-report" | tail -1 # Latest Jenkins URL
|
|
|
114
114
|
ger extract-url "jenkins" --json | jq -r '.urls[-1]'
|
|
115
115
|
```
|
|
116
116
|
|
|
117
|
-
### AI Review
|
|
118
|
-
|
|
119
|
-
```bash
|
|
120
|
-
ger review <change-id> # AI-powered review (requires claude/gemini/opencode CLI)
|
|
121
|
-
ger review <change-id> --tool claude
|
|
122
|
-
ger review <change-id> --comment --yes # Post review comments
|
|
123
|
-
```
|
|
124
|
-
|
|
125
117
|
### Groups
|
|
126
118
|
|
|
127
119
|
```bash
|
|
@@ -179,12 +171,6 @@ ger comment 12345 -m "LGTM" # Add comment
|
|
|
179
171
|
ger vote 12345 --code-review +2 # Approve
|
|
180
172
|
```
|
|
181
173
|
|
|
182
|
-
### Post AI review
|
|
183
|
-
|
|
184
|
-
```bash
|
|
185
|
-
ger review 12345 --comment --yes
|
|
186
|
-
```
|
|
187
|
-
|
|
188
174
|
### Check build and get failures
|
|
189
175
|
|
|
190
176
|
```bash
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@aaronshaf/ger",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "3.0.2",
|
|
4
4
|
"description": "Gerrit CLI and SDK - A modern CLI tool and TypeScript SDK for Gerrit Code Review, built with Effect-TS",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"gerrit",
|
|
@@ -30,10 +30,6 @@
|
|
|
30
30
|
"import": "./src/services/config.ts",
|
|
31
31
|
"types": "./src/services/config.ts"
|
|
32
32
|
},
|
|
33
|
-
"./services/review-strategy": {
|
|
34
|
-
"import": "./src/services/review-strategy.ts",
|
|
35
|
-
"types": "./src/services/review-strategy.ts"
|
|
36
|
-
},
|
|
37
33
|
"./services/git-worktree": {
|
|
38
34
|
"import": "./src/services/git-worktree.ts",
|
|
39
35
|
"types": "./src/services/git-worktree.ts"
|
package/src/api/gerrit.ts
CHANGED
|
@@ -18,7 +18,9 @@ import {
|
|
|
18
18
|
GroupDetailInfo,
|
|
19
19
|
AccountInfo,
|
|
20
20
|
} from '@/schemas/gerrit'
|
|
21
|
+
import { ReviewerListItem } from '@/schemas/reviewer'
|
|
21
22
|
import { filterMeaningfulMessages } from '@/utils/message-filters'
|
|
23
|
+
import { convertToUnifiedDiff } from '@/utils/diff-formatters'
|
|
22
24
|
import { ConfigService } from '@/services/config'
|
|
23
25
|
import { normalizeChangeIdentifier } from '@/utils/change-id'
|
|
24
26
|
|
|
@@ -85,6 +87,7 @@ export interface GerritApiServiceImpl {
|
|
|
85
87
|
readonly getGroup: (groupId: string) => Effect.Effect<GroupInfo, ApiError>
|
|
86
88
|
readonly getGroupDetail: (groupId: string) => Effect.Effect<GroupDetailInfo, ApiError>
|
|
87
89
|
readonly getGroupMembers: (groupId: string) => Effect.Effect<readonly AccountInfo[], ApiError>
|
|
90
|
+
readonly getReviewers: (changeId: string) => Effect.Effect<readonly ReviewerListItem[], ApiError>
|
|
88
91
|
readonly removeReviewer: (
|
|
89
92
|
changeId: string,
|
|
90
93
|
accountId: string,
|
|
@@ -93,6 +96,8 @@ export interface GerritApiServiceImpl {
|
|
|
93
96
|
readonly getTopic: (changeId: string) => Effect.Effect<string | null, ApiError>
|
|
94
97
|
readonly setTopic: (changeId: string, topic: string) => Effect.Effect<string, ApiError>
|
|
95
98
|
readonly deleteTopic: (changeId: string) => Effect.Effect<void, ApiError>
|
|
99
|
+
readonly setReady: (changeId: string, message?: string) => Effect.Effect<void, ApiError>
|
|
100
|
+
readonly setWip: (changeId: string, message?: string) => Effect.Effect<void, ApiError>
|
|
96
101
|
}
|
|
97
102
|
|
|
98
103
|
export const GerritApiService: Context.Tag<GerritApiServiceImpl, GerritApiServiceImpl> =
|
|
@@ -224,13 +229,8 @@ export const GerritApiServiceLive: Layer.Layer<GerritApiService, never, ConfigSe
|
|
|
224
229
|
const { credentials, authHeader } = yield* getCredentialsAndAuth
|
|
225
230
|
let url = `${credentials.host}/a/projects/`
|
|
226
231
|
if (options?.pattern) url += `?p=${encodeURIComponent(options.pattern)}`
|
|
227
|
-
const
|
|
228
|
-
|
|
229
|
-
authHeader,
|
|
230
|
-
'GET',
|
|
231
|
-
undefined,
|
|
232
|
-
Schema.Record({ key: Schema.String, value: ProjectInfo }),
|
|
233
|
-
)
|
|
232
|
+
const schema = Schema.Record({ key: Schema.String, value: ProjectInfo })
|
|
233
|
+
const projectsRecord = yield* makeRequest(url, authHeader, 'GET', undefined, schema)
|
|
234
234
|
return Object.values(projectsRecord).sort((a, b) => a.name.localeCompare(b.name))
|
|
235
235
|
})
|
|
236
236
|
|
|
@@ -455,39 +455,6 @@ export const GerritApiServiceLive: Layer.Layer<GerritApiService, never, ConfigSe
|
|
|
455
455
|
return yield* getPatch(changeId, revisionId)
|
|
456
456
|
})
|
|
457
457
|
|
|
458
|
-
const convertToUnifiedDiff = (diff: FileDiffContent, filePath: string): string => {
|
|
459
|
-
const lines: string[] = []
|
|
460
|
-
|
|
461
|
-
if (diff.diff_header) {
|
|
462
|
-
lines.push(...diff.diff_header)
|
|
463
|
-
} else {
|
|
464
|
-
lines.push(`--- a/${filePath}`)
|
|
465
|
-
lines.push(`+++ b/${filePath}`)
|
|
466
|
-
}
|
|
467
|
-
|
|
468
|
-
for (const section of diff.content) {
|
|
469
|
-
if (section.ab) {
|
|
470
|
-
for (const line of section.ab) {
|
|
471
|
-
lines.push(` ${line}`)
|
|
472
|
-
}
|
|
473
|
-
}
|
|
474
|
-
|
|
475
|
-
if (section.a) {
|
|
476
|
-
for (const line of section.a) {
|
|
477
|
-
lines.push(`-${line}`)
|
|
478
|
-
}
|
|
479
|
-
}
|
|
480
|
-
|
|
481
|
-
if (section.b) {
|
|
482
|
-
for (const line of section.b) {
|
|
483
|
-
lines.push(`+${line}`)
|
|
484
|
-
}
|
|
485
|
-
}
|
|
486
|
-
}
|
|
487
|
-
|
|
488
|
-
return lines.join('\n')
|
|
489
|
-
}
|
|
490
|
-
|
|
491
458
|
const getComments = (changeId: string, revisionId = 'current') =>
|
|
492
459
|
Effect.gen(function* () {
|
|
493
460
|
const { credentials, authHeader } = yield* getCredentialsAndAuth
|
|
@@ -611,6 +578,15 @@ export const GerritApiServiceLive: Layer.Layer<GerritApiService, never, ConfigSe
|
|
|
611
578
|
return yield* makeRequest(url, authHeader, 'GET', undefined, Schema.Array(AccountInfo))
|
|
612
579
|
})
|
|
613
580
|
|
|
581
|
+
const getReviewers = (changeId: string) =>
|
|
582
|
+
Effect.gen(function* () {
|
|
583
|
+
const { credentials, authHeader } = yield* getCredentialsAndAuth
|
|
584
|
+
const normalized = yield* normalizeAndValidate(changeId)
|
|
585
|
+
const url = `${credentials.host}/a/changes/${encodeURIComponent(normalized)}/reviewers`
|
|
586
|
+
const schema = Schema.Array(ReviewerListItem)
|
|
587
|
+
return yield* makeRequest(url, authHeader, 'GET', undefined, schema)
|
|
588
|
+
})
|
|
589
|
+
|
|
614
590
|
const removeReviewer = (
|
|
615
591
|
changeId: string,
|
|
616
592
|
accountId: string,
|
|
@@ -667,6 +643,24 @@ export const GerritApiServiceLive: Layer.Layer<GerritApiService, never, ConfigSe
|
|
|
667
643
|
yield* makeRequest(getTopicUrl(credentials.host, normalized), authHeader, 'DELETE')
|
|
668
644
|
})
|
|
669
645
|
|
|
646
|
+
const setReady = (changeId: string, message?: string) =>
|
|
647
|
+
Effect.gen(function* () {
|
|
648
|
+
const { credentials, authHeader } = yield* getCredentialsAndAuth
|
|
649
|
+
const normalized = yield* normalizeAndValidate(changeId)
|
|
650
|
+
const url = `${credentials.host}/a/changes/${encodeURIComponent(normalized)}/ready`
|
|
651
|
+
const body = message ? { message } : {}
|
|
652
|
+
yield* makeRequest(url, authHeader, 'POST', body)
|
|
653
|
+
})
|
|
654
|
+
|
|
655
|
+
const setWip = (changeId: string, message?: string) =>
|
|
656
|
+
Effect.gen(function* () {
|
|
657
|
+
const { credentials, authHeader } = yield* getCredentialsAndAuth
|
|
658
|
+
const normalized = yield* normalizeAndValidate(changeId)
|
|
659
|
+
const url = `${credentials.host}/a/changes/${encodeURIComponent(normalized)}/wip`
|
|
660
|
+
const body = message ? { message } : {}
|
|
661
|
+
yield* makeRequest(url, authHeader, 'POST', body)
|
|
662
|
+
})
|
|
663
|
+
|
|
670
664
|
return {
|
|
671
665
|
getChange,
|
|
672
666
|
listChanges,
|
|
@@ -686,6 +680,7 @@ export const GerritApiServiceLive: Layer.Layer<GerritApiService, never, ConfigSe
|
|
|
686
680
|
getComments,
|
|
687
681
|
getMessages,
|
|
688
682
|
addReviewer,
|
|
683
|
+
getReviewers,
|
|
689
684
|
listGroups,
|
|
690
685
|
getGroup,
|
|
691
686
|
getGroupDetail,
|
|
@@ -694,6 +689,8 @@ export const GerritApiServiceLive: Layer.Layer<GerritApiService, never, ConfigSe
|
|
|
694
689
|
getTopic,
|
|
695
690
|
setTopic,
|
|
696
691
|
deleteTopic,
|
|
692
|
+
setReady,
|
|
693
|
+
setWip,
|
|
697
694
|
}
|
|
698
695
|
}),
|
|
699
696
|
)
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
import { Effect } from 'effect'
|
|
2
|
+
import { type ApiError, GerritApiService } from '@/api/gerrit'
|
|
3
|
+
import { GitError, NoChangeIdError, getChangeIdFromHead } from '@/utils/git-commit'
|
|
4
|
+
import { escapeXML, sanitizeCDATA } from '@/utils/shell-safety'
|
|
5
|
+
|
|
6
|
+
const MAGIC_FILES = new Set(['/COMMIT_MSG', '/MERGE_LIST', '/PATCHSET_LEVEL'])
|
|
7
|
+
|
|
8
|
+
interface FilesOptions {
|
|
9
|
+
xml?: boolean
|
|
10
|
+
json?: boolean
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export const filesCommand = (
|
|
14
|
+
changeId?: string,
|
|
15
|
+
options: FilesOptions = {},
|
|
16
|
+
): Effect.Effect<void, never, GerritApiService> =>
|
|
17
|
+
Effect.gen(function* () {
|
|
18
|
+
const gerritApi = yield* GerritApiService
|
|
19
|
+
const resolvedChangeId = changeId || (yield* getChangeIdFromHead())
|
|
20
|
+
|
|
21
|
+
const filesRecord = yield* gerritApi.getFiles(resolvedChangeId)
|
|
22
|
+
const files = Object.entries(filesRecord)
|
|
23
|
+
.filter(([path]) => !MAGIC_FILES.has(path))
|
|
24
|
+
.map(([path, info]) => ({
|
|
25
|
+
path,
|
|
26
|
+
status: info.status ?? 'M',
|
|
27
|
+
lines_inserted: info.lines_inserted ?? 0,
|
|
28
|
+
lines_deleted: info.lines_deleted ?? 0,
|
|
29
|
+
}))
|
|
30
|
+
|
|
31
|
+
if (options.json) {
|
|
32
|
+
console.log(
|
|
33
|
+
JSON.stringify(
|
|
34
|
+
{
|
|
35
|
+
status: 'success',
|
|
36
|
+
change_id: resolvedChangeId,
|
|
37
|
+
files,
|
|
38
|
+
},
|
|
39
|
+
null,
|
|
40
|
+
2,
|
|
41
|
+
),
|
|
42
|
+
)
|
|
43
|
+
} else if (options.xml) {
|
|
44
|
+
console.log(`<?xml version="1.0" encoding="UTF-8"?>`)
|
|
45
|
+
console.log(`<files_result>`)
|
|
46
|
+
console.log(` <status>success</status>`)
|
|
47
|
+
console.log(` <change_id><![CDATA[${sanitizeCDATA(resolvedChangeId)}]]></change_id>`)
|
|
48
|
+
console.log(` <files>`)
|
|
49
|
+
for (const file of files) {
|
|
50
|
+
console.log(` <file>`)
|
|
51
|
+
console.log(` <path><![CDATA[${sanitizeCDATA(file.path)}]]></path>`)
|
|
52
|
+
console.log(` <status>${escapeXML(file.status)}</status>`)
|
|
53
|
+
console.log(` <lines_inserted>${file.lines_inserted}</lines_inserted>`)
|
|
54
|
+
console.log(` <lines_deleted>${file.lines_deleted}</lines_deleted>`)
|
|
55
|
+
console.log(` </file>`)
|
|
56
|
+
}
|
|
57
|
+
console.log(` </files>`)
|
|
58
|
+
console.log(`</files_result>`)
|
|
59
|
+
} else {
|
|
60
|
+
for (const file of files) {
|
|
61
|
+
console.log(`${file.status} ${file.path}`)
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
}).pipe(
|
|
65
|
+
Effect.catchAll((error: ApiError | GitError | NoChangeIdError) =>
|
|
66
|
+
Effect.sync(() => {
|
|
67
|
+
const errorMessage =
|
|
68
|
+
error instanceof GitError || error instanceof NoChangeIdError || error instanceof Error
|
|
69
|
+
? error.message
|
|
70
|
+
: String(error)
|
|
71
|
+
|
|
72
|
+
if (options.json) {
|
|
73
|
+
console.log(JSON.stringify({ status: 'error', error: errorMessage }, null, 2))
|
|
74
|
+
} else if (options.xml) {
|
|
75
|
+
console.log(`<?xml version="1.0" encoding="UTF-8"?>`)
|
|
76
|
+
console.log(`<files_result>`)
|
|
77
|
+
console.log(` <status>error</status>`)
|
|
78
|
+
console.log(` <error><![CDATA[${sanitizeCDATA(errorMessage)}]]></error>`)
|
|
79
|
+
console.log(`</files_result>`)
|
|
80
|
+
} else {
|
|
81
|
+
console.error(`✗ Error: ${errorMessage}`)
|
|
82
|
+
}
|
|
83
|
+
process.exit(1)
|
|
84
|
+
}),
|
|
85
|
+
),
|
|
86
|
+
)
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
import { Effect } from 'effect'
|
|
2
|
+
import { type ApiError, GerritApiService } from '@/api/gerrit'
|
|
3
|
+
import { GitError, NoChangeIdError, getChangeIdFromHead } from '@/utils/git-commit'
|
|
4
|
+
import { sanitizeCDATA } from '@/utils/shell-safety'
|
|
5
|
+
import type { ReviewerListItem } from '@/schemas/reviewer'
|
|
6
|
+
|
|
7
|
+
interface ReviewersOptions {
|
|
8
|
+
xml?: boolean
|
|
9
|
+
json?: boolean
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
function formatReviewer(r: ReviewerListItem): string {
|
|
13
|
+
const name =
|
|
14
|
+
r.name ?? r.username ?? (r._account_id !== undefined ? `#${r._account_id}` : undefined)
|
|
15
|
+
if (name !== undefined) return r.email ? `${name} <${r.email}>` : name
|
|
16
|
+
return r.email ?? 'unknown'
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export const reviewersCommand = (
|
|
20
|
+
changeId?: string,
|
|
21
|
+
options: ReviewersOptions = {},
|
|
22
|
+
): Effect.Effect<void, never, GerritApiService> =>
|
|
23
|
+
Effect.gen(function* () {
|
|
24
|
+
const gerritApi = yield* GerritApiService
|
|
25
|
+
const resolvedChangeId = changeId || (yield* getChangeIdFromHead())
|
|
26
|
+
|
|
27
|
+
const reviewers = yield* gerritApi.getReviewers(resolvedChangeId)
|
|
28
|
+
|
|
29
|
+
if (options.json) {
|
|
30
|
+
console.log(
|
|
31
|
+
JSON.stringify(
|
|
32
|
+
{
|
|
33
|
+
status: 'success',
|
|
34
|
+
change_id: resolvedChangeId,
|
|
35
|
+
reviewers: reviewers.map((r) => ({
|
|
36
|
+
...(r._account_id !== undefined ? { account_id: r._account_id } : {}),
|
|
37
|
+
name: r.name,
|
|
38
|
+
email: r.email,
|
|
39
|
+
username: r.username,
|
|
40
|
+
})),
|
|
41
|
+
},
|
|
42
|
+
null,
|
|
43
|
+
2,
|
|
44
|
+
),
|
|
45
|
+
)
|
|
46
|
+
} else if (options.xml) {
|
|
47
|
+
console.log(`<?xml version="1.0" encoding="UTF-8"?>`)
|
|
48
|
+
console.log(`<reviewers_result>`)
|
|
49
|
+
console.log(` <status>success</status>`)
|
|
50
|
+
console.log(` <change_id><![CDATA[${sanitizeCDATA(resolvedChangeId)}]]></change_id>`)
|
|
51
|
+
console.log(` <reviewers>`)
|
|
52
|
+
for (const r of reviewers) {
|
|
53
|
+
console.log(` <reviewer>`)
|
|
54
|
+
if (r._account_id !== undefined)
|
|
55
|
+
console.log(` <account_id>${r._account_id}</account_id>`)
|
|
56
|
+
if (r.name) console.log(` <name><![CDATA[${sanitizeCDATA(r.name)}]]></name>`)
|
|
57
|
+
if (r.email) console.log(` <email><![CDATA[${sanitizeCDATA(r.email)}]]></email>`)
|
|
58
|
+
if (r.username)
|
|
59
|
+
console.log(` <username><![CDATA[${sanitizeCDATA(r.username)}]]></username>`)
|
|
60
|
+
console.log(` </reviewer>`)
|
|
61
|
+
}
|
|
62
|
+
console.log(` </reviewers>`)
|
|
63
|
+
console.log(`</reviewers_result>`)
|
|
64
|
+
} else {
|
|
65
|
+
if (reviewers.length === 0) {
|
|
66
|
+
console.log('No reviewers')
|
|
67
|
+
} else {
|
|
68
|
+
for (const r of reviewers) {
|
|
69
|
+
console.log(formatReviewer(r))
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
}).pipe(
|
|
74
|
+
Effect.catchAll((error: ApiError | GitError | NoChangeIdError) =>
|
|
75
|
+
Effect.sync(() => {
|
|
76
|
+
const errorMessage =
|
|
77
|
+
error instanceof GitError || error instanceof NoChangeIdError || error instanceof Error
|
|
78
|
+
? error.message
|
|
79
|
+
: String(error)
|
|
80
|
+
|
|
81
|
+
if (options.json) {
|
|
82
|
+
console.log(JSON.stringify({ status: 'error', error: errorMessage }, null, 2))
|
|
83
|
+
} else if (options.xml) {
|
|
84
|
+
console.log(`<?xml version="1.0" encoding="UTF-8"?>`)
|
|
85
|
+
console.log(`<reviewers_result>`)
|
|
86
|
+
console.log(` <status>error</status>`)
|
|
87
|
+
console.log(` <error><![CDATA[${sanitizeCDATA(errorMessage)}]]></error>`)
|
|
88
|
+
console.log(`</reviewers_result>`)
|
|
89
|
+
} else {
|
|
90
|
+
console.error(`✗ Error: ${errorMessage}`)
|
|
91
|
+
}
|
|
92
|
+
process.exit(1)
|
|
93
|
+
}),
|
|
94
|
+
),
|
|
95
|
+
)
|