diffsage 1.2.1__tar.gz → 1.3.1__tar.gz
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.
- {diffsage-1.2.1/src/diffsage.egg-info → diffsage-1.3.1}/PKG-INFO +105 -10
- {diffsage-1.2.1 → diffsage-1.3.1}/README.md +104 -9
- {diffsage-1.2.1 → diffsage-1.3.1}/pyproject.toml +1 -1
- {diffsage-1.2.1 → diffsage-1.3.1}/src/diffsage/cli.py +24 -2
- {diffsage-1.2.1 → diffsage-1.3.1}/src/diffsage/commands/commit.py +23 -6
- diffsage-1.3.1/src/diffsage/commands/pr.py +220 -0
- {diffsage-1.2.1 → diffsage-1.3.1}/src/diffsage/exceptions/__init__.py +31 -10
- {diffsage-1.2.1 → diffsage-1.3.1}/src/diffsage/exceptions/base.py +12 -0
- diffsage-1.3.1/src/diffsage/exceptions/git.py +21 -0
- diffsage-1.3.1/src/diffsage/exceptions/github.py +13 -0
- {diffsage-1.2.1 → diffsage-1.3.1}/src/diffsage/git/client.py +102 -0
- diffsage-1.3.1/src/diffsage/github/client.py +65 -0
- {diffsage-1.2.1 → diffsage-1.3.1}/src/diffsage/models/commit_message.py +2 -0
- {diffsage-1.2.1 → diffsage-1.3.1}/src/diffsage/models/git.py +13 -1
- diffsage-1.3.1/src/diffsage/models/pull_request.py +27 -0
- diffsage-1.3.1/src/diffsage/models/pull_request_analysis.py +29 -0
- diffsage-1.3.1/src/diffsage/parsers/pull_request_parser.py +68 -0
- {diffsage-1.2.1 → diffsage-1.3.1}/src/diffsage/services/ai_service.py +11 -1
- {diffsage-1.2.1 → diffsage-1.3.1}/src/diffsage/services/commit_service.py +10 -2
- diffsage-1.3.1/src/diffsage/services/git_service.py +116 -0
- diffsage-1.3.1/src/diffsage/services/github_service.py +75 -0
- diffsage-1.3.1/src/diffsage/services/prompt_service.py +182 -0
- diffsage-1.3.1/src/diffsage/services/pull_request_analysis_service.py +281 -0
- diffsage-1.3.1/src/diffsage/services/pull_request_service.py +42 -0
- diffsage-1.3.1/src/diffsage/ui/__init__.py +0 -0
- {diffsage-1.2.1 → diffsage-1.3.1}/src/diffsage/ui/commit_view.py +2 -2
- diffsage-1.3.1/src/diffsage/ui/pull_request_view.py +122 -0
- {diffsage-1.2.1 → diffsage-1.3.1/src/diffsage.egg-info}/PKG-INFO +105 -10
- {diffsage-1.2.1 → diffsage-1.3.1}/src/diffsage.egg-info/SOURCES.txt +13 -1
- diffsage-1.3.1/tests/test_cli.py +21 -0
- diffsage-1.2.1/src/diffsage/exceptions/git.py +0 -9
- diffsage-1.2.1/src/diffsage/services/git_service.py +0 -20
- diffsage-1.2.1/src/diffsage/services/prompt_service.py +0 -60
- {diffsage-1.2.1 → diffsage-1.3.1}/LICENSE +0 -0
- {diffsage-1.2.1 → diffsage-1.3.1}/setup.cfg +0 -0
- {diffsage-1.2.1 → diffsage-1.3.1}/src/diffsage/__init__.py +0 -0
- {diffsage-1.2.1 → diffsage-1.3.1}/src/diffsage/__main__.py +0 -0
- {diffsage-1.2.1 → diffsage-1.3.1}/src/diffsage/commands/__init__.py +0 -0
- {diffsage-1.2.1 → diffsage-1.3.1}/src/diffsage/commands/ask.py +0 -0
- {diffsage-1.2.1 → diffsage-1.3.1}/src/diffsage/commands/auth.py +0 -0
- {diffsage-1.2.1 → diffsage-1.3.1}/src/diffsage/commands/config.py +0 -0
- {diffsage-1.2.1 → diffsage-1.3.1}/src/diffsage/commands/doctor.py +0 -0
- {diffsage-1.2.1 → diffsage-1.3.1}/src/diffsage/config/__init__.py +0 -0
- {diffsage-1.2.1 → diffsage-1.3.1}/src/diffsage/config/defaults.py +0 -0
- {diffsage-1.2.1 → diffsage-1.3.1}/src/diffsage/config/file_loader.py +0 -0
- {diffsage-1.2.1 → diffsage-1.3.1}/src/diffsage/config/loader.py +0 -0
- {diffsage-1.2.1 → diffsage-1.3.1}/src/diffsage/config/paths.py +0 -0
- {diffsage-1.2.1 → diffsage-1.3.1}/src/diffsage/config/resolver.py +0 -0
- {diffsage-1.2.1 → diffsage-1.3.1}/src/diffsage/config/schema.py +0 -0
- {diffsage-1.2.1 → diffsage-1.3.1}/src/diffsage/config/scope.py +0 -0
- {diffsage-1.2.1 → diffsage-1.3.1}/src/diffsage/config/settings.py +0 -0
- {diffsage-1.2.1 → diffsage-1.3.1}/src/diffsage/exceptions/credentials.py +0 -0
- {diffsage-1.2.1 → diffsage-1.3.1}/src/diffsage/exceptions/provider.py +0 -0
- {diffsage-1.2.1 → diffsage-1.3.1}/src/diffsage/git/__init__.py +0 -0
- {diffsage-1.2.1/src/diffsage/logging → diffsage-1.3.1/src/diffsage/github}/__init__.py +0 -0
- {diffsage-1.2.1/src/diffsage/models → diffsage-1.3.1/src/diffsage/logging}/__init__.py +0 -0
- {diffsage-1.2.1 → diffsage-1.3.1}/src/diffsage/logging/formatter.py +0 -0
- {diffsage-1.2.1 → diffsage-1.3.1}/src/diffsage/logging/logger.py +0 -0
- {diffsage-1.2.1/src/diffsage/prompts → diffsage-1.3.1/src/diffsage/models}/__init__.py +0 -0
- {diffsage-1.2.1 → diffsage-1.3.1}/src/diffsage/models/config.py +0 -0
- {diffsage-1.2.1 → diffsage-1.3.1}/src/diffsage/models/credentials.py +0 -0
- {diffsage-1.2.1 → diffsage-1.3.1}/src/diffsage/models/doctor.py +0 -0
- {diffsage-1.2.1 → diffsage-1.3.1}/src/diffsage/models/provider.py +0 -0
- {diffsage-1.2.1 → diffsage-1.3.1}/src/diffsage/parsers/commit_message_parser.py +0 -0
- {diffsage-1.2.1/src/diffsage/providers → diffsage-1.3.1/src/diffsage/prompts}/__init__.py +0 -0
- {diffsage-1.2.1/src/diffsage/services → diffsage-1.3.1/src/diffsage/providers}/__init__.py +0 -0
- {diffsage-1.2.1 → diffsage-1.3.1}/src/diffsage/providers/base.py +0 -0
- {diffsage-1.2.1 → diffsage-1.3.1}/src/diffsage/providers/factory.py +0 -0
- {diffsage-1.2.1 → diffsage-1.3.1}/src/diffsage/providers/gemini_provider.py +0 -0
- {diffsage-1.2.1/src/diffsage/storage → diffsage-1.3.1/src/diffsage/services}/__init__.py +0 -0
- {diffsage-1.2.1 → diffsage-1.3.1}/src/diffsage/services/config_service.py +0 -0
- {diffsage-1.2.1 → diffsage-1.3.1}/src/diffsage/services/credentials_service.py +0 -0
- {diffsage-1.2.1 → diffsage-1.3.1}/src/diffsage/services/doctor_service.py +0 -0
- {diffsage-1.2.1 → diffsage-1.3.1}/src/diffsage/services/editor_service.py +0 -0
- {diffsage-1.2.1/src/diffsage/ui → diffsage-1.3.1/src/diffsage/storage}/__init__.py +0 -0
- {diffsage-1.2.1 → diffsage-1.3.1}/src/diffsage/storage/base.py +0 -0
- {diffsage-1.2.1 → diffsage-1.3.1}/src/diffsage/storage/config_repository.py +0 -0
- {diffsage-1.2.1 → diffsage-1.3.1}/src/diffsage/storage/credentials_repository.py +0 -0
- {diffsage-1.2.1 → diffsage-1.3.1}/src/diffsage/ui/ask_view.py +0 -0
- {diffsage-1.2.1 → diffsage-1.3.1}/src/diffsage/ui/auth_view.py +0 -0
- {diffsage-1.2.1 → diffsage-1.3.1}/src/diffsage/ui/base.py +0 -0
- {diffsage-1.2.1 → diffsage-1.3.1}/src/diffsage/ui/config_view.py +0 -0
- {diffsage-1.2.1 → diffsage-1.3.1}/src/diffsage/ui/doctor_view.py +0 -0
- {diffsage-1.2.1 → diffsage-1.3.1}/src/diffsage.egg-info/dependency_links.txt +0 -0
- {diffsage-1.2.1 → diffsage-1.3.1}/src/diffsage.egg-info/entry_points.txt +0 -0
- {diffsage-1.2.1 → diffsage-1.3.1}/src/diffsage.egg-info/requires.txt +0 -0
- {diffsage-1.2.1 → diffsage-1.3.1}/src/diffsage.egg-info/top_level.txt +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: diffsage
|
|
3
|
-
Version: 1.
|
|
3
|
+
Version: 1.3.1
|
|
4
4
|
Summary: AI-powered Git workflow assistant
|
|
5
5
|
Author: Gunitha Rathnayake
|
|
6
6
|
License: MIT
|
|
@@ -116,6 +116,10 @@ Current capabilities include:
|
|
|
116
116
|
- AI provider authentication and error handling
|
|
117
117
|
- Layered architecture
|
|
118
118
|
- Comprehensive automated test suite
|
|
119
|
+
- Add AI-assisted PR generation
|
|
120
|
+
- Interactive PR draft review/edit/regeneration
|
|
121
|
+
- GitHub PR creation
|
|
122
|
+
- Branch synchronization validation
|
|
119
123
|
|
|
120
124
|
---
|
|
121
125
|
|
|
@@ -313,6 +317,35 @@ Display AI suggestion
|
|
|
313
317
|
[N] Cancel
|
|
314
318
|
```
|
|
315
319
|
|
|
320
|
+
```bash
|
|
321
|
+
diffsage pr
|
|
322
|
+
```
|
|
323
|
+
|
|
324
|
+
PR workflow
|
|
325
|
+
|
|
326
|
+
```text
|
|
327
|
+
Generate PR draft
|
|
328
|
+
│
|
|
329
|
+
▼
|
|
330
|
+
Show base/head branches
|
|
331
|
+
│
|
|
332
|
+
▼
|
|
333
|
+
Display PR draft
|
|
334
|
+
│
|
|
335
|
+
┌─────┼───────────┐
|
|
336
|
+
▼ ▼ ▼
|
|
337
|
+
Edit Regenerate Cancel
|
|
338
|
+
│
|
|
339
|
+
▼
|
|
340
|
+
Accept
|
|
341
|
+
│
|
|
342
|
+
▼
|
|
343
|
+
Create GitHub PR
|
|
344
|
+
│
|
|
345
|
+
▼
|
|
346
|
+
Display PR URL
|
|
347
|
+
```
|
|
348
|
+
|
|
316
349
|
Named credential profiles are also supported:
|
|
317
350
|
|
|
318
351
|
```bash
|
|
@@ -340,6 +373,8 @@ diffsage auth unset gemini
|
|
|
340
373
|
|----------|-------------|
|
|
341
374
|
| `diffsage doctor` | Validate the local DiffSage environment |
|
|
342
375
|
| `diffsage commit` | Generate and manage AI-assisted commit messages |
|
|
376
|
+
| `diffsage pr` | Generate, review, and create a GitHub pull request |
|
|
377
|
+
| `diffsage pr <base-branch>` | Generate and create a pull request against a specific base branch |
|
|
343
378
|
| `diffsage config list` | Display the current DiffSage configuration |
|
|
344
379
|
| `diffsage config get` | Display the value of a configuration setting |
|
|
345
380
|
| `diffsage config set` | Update a configuration setting |
|
|
@@ -350,7 +385,46 @@ diffsage auth unset gemini
|
|
|
350
385
|
| `diffsage auth unset` | Remove a provider credential |
|
|
351
386
|
| `diffsage ask` | Ask the configured AI provider a question |
|
|
352
387
|
|
|
353
|
-
|
|
388
|
+
---
|
|
389
|
+
|
|
390
|
+
## Pull Request Workflow
|
|
391
|
+
|
|
392
|
+
The `diffsage pr` command analyzes the current Git branch against a resolved base branch and generates a structured pull request draft.
|
|
393
|
+
|
|
394
|
+
The workflow is interactive:
|
|
395
|
+
|
|
396
|
+
```text
|
|
397
|
+
Analyze Git repository
|
|
398
|
+
│
|
|
399
|
+
▼
|
|
400
|
+
Resolve base branch
|
|
401
|
+
│
|
|
402
|
+
▼
|
|
403
|
+
Generate PR draft
|
|
404
|
+
│
|
|
405
|
+
▼
|
|
406
|
+
Display base/head branches
|
|
407
|
+
│
|
|
408
|
+
▼
|
|
409
|
+
Review PR draft
|
|
410
|
+
│
|
|
411
|
+
┌──────┼──────────────┐
|
|
412
|
+
▼ ▼ ▼
|
|
413
|
+
Edit Regenerate Cancel
|
|
414
|
+
│ │
|
|
415
|
+
└──────┴───────┐
|
|
416
|
+
▼
|
|
417
|
+
Accept
|
|
418
|
+
│
|
|
419
|
+
▼
|
|
420
|
+
Validate GitHub access
|
|
421
|
+
│
|
|
422
|
+
▼
|
|
423
|
+
Create GitHub pull request
|
|
424
|
+
│
|
|
425
|
+
▼
|
|
426
|
+
Display pull request URL
|
|
427
|
+
```
|
|
354
428
|
|
|
355
429
|
---
|
|
356
430
|
|
|
@@ -368,15 +442,22 @@ Commands
|
|
|
368
442
|
│
|
|
369
443
|
├── Authentication
|
|
370
444
|
│
|
|
371
|
-
|
|
372
|
-
Services
|
|
445
|
+
├── Commit Workflow
|
|
373
446
|
│
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
│
|
|
379
|
-
|
|
447
|
+
└── Pull Request Workflow
|
|
448
|
+
│
|
|
449
|
+
▼
|
|
450
|
+
Services
|
|
451
|
+
│
|
|
452
|
+
├── Git
|
|
453
|
+
├── GitHub
|
|
454
|
+
├── AI
|
|
455
|
+
├── Prompt Generation
|
|
456
|
+
├── Pull Request Analysis
|
|
457
|
+
├── Pull Request Parsing
|
|
458
|
+
└── Credentials
|
|
459
|
+
│
|
|
460
|
+
▼
|
|
380
461
|
Providers / Storage
|
|
381
462
|
```
|
|
382
463
|
|
|
@@ -402,6 +483,7 @@ src/
|
|
|
402
483
|
├── config/
|
|
403
484
|
├── exceptions/
|
|
404
485
|
├── git/
|
|
486
|
+
├── github/
|
|
405
487
|
├── logging/
|
|
406
488
|
├── models/
|
|
407
489
|
├── parsers/
|
|
@@ -412,6 +494,18 @@ src/
|
|
|
412
494
|
└── ui/
|
|
413
495
|
|
|
414
496
|
tests/
|
|
497
|
+
├── commands/
|
|
498
|
+
├── config/
|
|
499
|
+
├── git/
|
|
500
|
+
├── github/
|
|
501
|
+
├── integration/
|
|
502
|
+
├── models/
|
|
503
|
+
├── parsers/
|
|
504
|
+
├── prompts/
|
|
505
|
+
├── providers/
|
|
506
|
+
├── services/
|
|
507
|
+
├── storage/
|
|
508
|
+
└── ui/
|
|
415
509
|
|
|
416
510
|
docs/
|
|
417
511
|
```
|
|
@@ -460,6 +554,7 @@ Instead of requiring every automation tool or AI agent to implement Git workflow
|
|
|
460
554
|
|
|
461
555
|
- Repository validation
|
|
462
556
|
- Git operations
|
|
557
|
+
- GitHub integration
|
|
463
558
|
- AI integration
|
|
464
559
|
- Workflow execution
|
|
465
560
|
- Error handling
|
|
@@ -77,6 +77,10 @@ Current capabilities include:
|
|
|
77
77
|
- AI provider authentication and error handling
|
|
78
78
|
- Layered architecture
|
|
79
79
|
- Comprehensive automated test suite
|
|
80
|
+
- Add AI-assisted PR generation
|
|
81
|
+
- Interactive PR draft review/edit/regeneration
|
|
82
|
+
- GitHub PR creation
|
|
83
|
+
- Branch synchronization validation
|
|
80
84
|
|
|
81
85
|
---
|
|
82
86
|
|
|
@@ -274,6 +278,35 @@ Display AI suggestion
|
|
|
274
278
|
[N] Cancel
|
|
275
279
|
```
|
|
276
280
|
|
|
281
|
+
```bash
|
|
282
|
+
diffsage pr
|
|
283
|
+
```
|
|
284
|
+
|
|
285
|
+
PR workflow
|
|
286
|
+
|
|
287
|
+
```text
|
|
288
|
+
Generate PR draft
|
|
289
|
+
│
|
|
290
|
+
▼
|
|
291
|
+
Show base/head branches
|
|
292
|
+
│
|
|
293
|
+
▼
|
|
294
|
+
Display PR draft
|
|
295
|
+
│
|
|
296
|
+
┌─────┼───────────┐
|
|
297
|
+
▼ ▼ ▼
|
|
298
|
+
Edit Regenerate Cancel
|
|
299
|
+
│
|
|
300
|
+
▼
|
|
301
|
+
Accept
|
|
302
|
+
│
|
|
303
|
+
▼
|
|
304
|
+
Create GitHub PR
|
|
305
|
+
│
|
|
306
|
+
▼
|
|
307
|
+
Display PR URL
|
|
308
|
+
```
|
|
309
|
+
|
|
277
310
|
Named credential profiles are also supported:
|
|
278
311
|
|
|
279
312
|
```bash
|
|
@@ -301,6 +334,8 @@ diffsage auth unset gemini
|
|
|
301
334
|
|----------|-------------|
|
|
302
335
|
| `diffsage doctor` | Validate the local DiffSage environment |
|
|
303
336
|
| `diffsage commit` | Generate and manage AI-assisted commit messages |
|
|
337
|
+
| `diffsage pr` | Generate, review, and create a GitHub pull request |
|
|
338
|
+
| `diffsage pr <base-branch>` | Generate and create a pull request against a specific base branch |
|
|
304
339
|
| `diffsage config list` | Display the current DiffSage configuration |
|
|
305
340
|
| `diffsage config get` | Display the value of a configuration setting |
|
|
306
341
|
| `diffsage config set` | Update a configuration setting |
|
|
@@ -311,7 +346,46 @@ diffsage auth unset gemini
|
|
|
311
346
|
| `diffsage auth unset` | Remove a provider credential |
|
|
312
347
|
| `diffsage ask` | Ask the configured AI provider a question |
|
|
313
348
|
|
|
314
|
-
|
|
349
|
+
---
|
|
350
|
+
|
|
351
|
+
## Pull Request Workflow
|
|
352
|
+
|
|
353
|
+
The `diffsage pr` command analyzes the current Git branch against a resolved base branch and generates a structured pull request draft.
|
|
354
|
+
|
|
355
|
+
The workflow is interactive:
|
|
356
|
+
|
|
357
|
+
```text
|
|
358
|
+
Analyze Git repository
|
|
359
|
+
│
|
|
360
|
+
▼
|
|
361
|
+
Resolve base branch
|
|
362
|
+
│
|
|
363
|
+
▼
|
|
364
|
+
Generate PR draft
|
|
365
|
+
│
|
|
366
|
+
▼
|
|
367
|
+
Display base/head branches
|
|
368
|
+
│
|
|
369
|
+
▼
|
|
370
|
+
Review PR draft
|
|
371
|
+
│
|
|
372
|
+
┌──────┼──────────────┐
|
|
373
|
+
▼ ▼ ▼
|
|
374
|
+
Edit Regenerate Cancel
|
|
375
|
+
│ │
|
|
376
|
+
└──────┴───────┐
|
|
377
|
+
▼
|
|
378
|
+
Accept
|
|
379
|
+
│
|
|
380
|
+
▼
|
|
381
|
+
Validate GitHub access
|
|
382
|
+
│
|
|
383
|
+
▼
|
|
384
|
+
Create GitHub pull request
|
|
385
|
+
│
|
|
386
|
+
▼
|
|
387
|
+
Display pull request URL
|
|
388
|
+
```
|
|
315
389
|
|
|
316
390
|
---
|
|
317
391
|
|
|
@@ -329,15 +403,22 @@ Commands
|
|
|
329
403
|
│
|
|
330
404
|
├── Authentication
|
|
331
405
|
│
|
|
332
|
-
|
|
333
|
-
Services
|
|
406
|
+
├── Commit Workflow
|
|
334
407
|
│
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
│
|
|
340
|
-
|
|
408
|
+
└── Pull Request Workflow
|
|
409
|
+
│
|
|
410
|
+
▼
|
|
411
|
+
Services
|
|
412
|
+
│
|
|
413
|
+
├── Git
|
|
414
|
+
├── GitHub
|
|
415
|
+
├── AI
|
|
416
|
+
├── Prompt Generation
|
|
417
|
+
├── Pull Request Analysis
|
|
418
|
+
├── Pull Request Parsing
|
|
419
|
+
└── Credentials
|
|
420
|
+
│
|
|
421
|
+
▼
|
|
341
422
|
Providers / Storage
|
|
342
423
|
```
|
|
343
424
|
|
|
@@ -363,6 +444,7 @@ src/
|
|
|
363
444
|
├── config/
|
|
364
445
|
├── exceptions/
|
|
365
446
|
├── git/
|
|
447
|
+
├── github/
|
|
366
448
|
├── logging/
|
|
367
449
|
├── models/
|
|
368
450
|
├── parsers/
|
|
@@ -373,6 +455,18 @@ src/
|
|
|
373
455
|
└── ui/
|
|
374
456
|
|
|
375
457
|
tests/
|
|
458
|
+
├── commands/
|
|
459
|
+
├── config/
|
|
460
|
+
├── git/
|
|
461
|
+
├── github/
|
|
462
|
+
├── integration/
|
|
463
|
+
├── models/
|
|
464
|
+
├── parsers/
|
|
465
|
+
├── prompts/
|
|
466
|
+
├── providers/
|
|
467
|
+
├── services/
|
|
468
|
+
├── storage/
|
|
469
|
+
└── ui/
|
|
376
470
|
|
|
377
471
|
docs/
|
|
378
472
|
```
|
|
@@ -421,6 +515,7 @@ Instead of requiring every automation tool or AI agent to implement Git workflow
|
|
|
421
515
|
|
|
422
516
|
- Repository validation
|
|
423
517
|
- Git operations
|
|
518
|
+
- GitHub integration
|
|
424
519
|
- AI integration
|
|
425
520
|
- Workflow execution
|
|
426
521
|
- Error handling
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import logging
|
|
2
|
+
from importlib.metadata import version
|
|
2
3
|
|
|
3
4
|
import typer
|
|
4
5
|
|
|
@@ -7,14 +8,30 @@ from diffsage.commands.auth import app as auth_app
|
|
|
7
8
|
from diffsage.commands.commit import commit
|
|
8
9
|
from diffsage.commands.config import app as config_app
|
|
9
10
|
from diffsage.commands.doctor import doctor
|
|
11
|
+
from diffsage.commands.pr import pr
|
|
10
12
|
from diffsage.config.loader import load_settings
|
|
11
13
|
from diffsage.logging.logger import configure_logging
|
|
12
14
|
|
|
13
15
|
app = typer.Typer(help="DiffSage: AI-aware Git workflow toolkit")
|
|
14
16
|
|
|
15
17
|
|
|
18
|
+
def version_callback(value: bool) -> None:
|
|
19
|
+
if value:
|
|
20
|
+
typer.echo(version("diffsage"))
|
|
21
|
+
raise typer.Exit()
|
|
22
|
+
|
|
23
|
+
|
|
16
24
|
@app.callback()
|
|
17
|
-
def main(
|
|
25
|
+
def main(
|
|
26
|
+
version: bool = typer.Option(
|
|
27
|
+
False,
|
|
28
|
+
"--version",
|
|
29
|
+
"-v",
|
|
30
|
+
callback=version_callback,
|
|
31
|
+
is_eager=True,
|
|
32
|
+
help="Show the Diffsage version.",
|
|
33
|
+
),
|
|
34
|
+
) -> None:
|
|
18
35
|
"""
|
|
19
36
|
Initialize DiffSage.
|
|
20
37
|
"""
|
|
@@ -31,8 +48,13 @@ def main() -> None:
|
|
|
31
48
|
app.command()(doctor)
|
|
32
49
|
app.command()(ask)
|
|
33
50
|
app.command()(commit)
|
|
51
|
+
app.command()(pr)
|
|
52
|
+
|
|
34
53
|
app.add_typer(
|
|
35
54
|
config_app,
|
|
36
55
|
name="config",
|
|
37
56
|
)
|
|
38
|
-
app.add_typer(
|
|
57
|
+
app.add_typer(
|
|
58
|
+
auth_app,
|
|
59
|
+
name="auth",
|
|
60
|
+
)
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
from collections.abc import Callable
|
|
2
|
+
|
|
1
3
|
from diffsage.config.loader import load_settings
|
|
2
4
|
from diffsage.config.paths import get_credentials_path
|
|
3
5
|
from diffsage.exceptions import (
|
|
@@ -22,8 +24,13 @@ from diffsage.ui.commit_view import CommitView
|
|
|
22
24
|
logger = get_logger(__name__)
|
|
23
25
|
|
|
24
26
|
|
|
25
|
-
def generate_message(
|
|
26
|
-
|
|
27
|
+
def generate_message(
|
|
28
|
+
commit_service: CommitService,
|
|
29
|
+
on_attempt: Callable[[int, int], None] | None = None,
|
|
30
|
+
) -> str:
|
|
31
|
+
return commit_service.generate_commit_message(
|
|
32
|
+
on_attempt=on_attempt,
|
|
33
|
+
)
|
|
27
34
|
|
|
28
35
|
|
|
29
36
|
def commit() -> None:
|
|
@@ -54,8 +61,13 @@ def commit() -> None:
|
|
|
54
61
|
ai_service,
|
|
55
62
|
)
|
|
56
63
|
|
|
57
|
-
with view.generating():
|
|
58
|
-
raw_message = generate_message(
|
|
64
|
+
with view.generating() as status:
|
|
65
|
+
raw_message = generate_message(
|
|
66
|
+
commit_service,
|
|
67
|
+
on_attempt=lambda attempt, total: status.update(
|
|
68
|
+
f"Generating commit message... Attempt {attempt}/{total}"
|
|
69
|
+
),
|
|
70
|
+
)
|
|
59
71
|
commit_message = CommitMessageParser.parse(raw_message)
|
|
60
72
|
|
|
61
73
|
view.show_generated()
|
|
@@ -79,8 +91,13 @@ def commit() -> None:
|
|
|
79
91
|
|
|
80
92
|
if choice == "r":
|
|
81
93
|
logger.info("User selected regenerate.")
|
|
82
|
-
with view.generating():
|
|
83
|
-
raw_message = generate_message(
|
|
94
|
+
with view.generating() as status:
|
|
95
|
+
raw_message = generate_message(
|
|
96
|
+
commit_service,
|
|
97
|
+
on_attempt=lambda attempt, total: status.update(
|
|
98
|
+
f"Generating commit message... Attempt {attempt}/{total}"
|
|
99
|
+
),
|
|
100
|
+
)
|
|
84
101
|
commit_message = CommitMessageParser.parse(raw_message)
|
|
85
102
|
|
|
86
103
|
view.show_generated()
|
|
@@ -0,0 +1,220 @@
|
|
|
1
|
+
import json
|
|
2
|
+
|
|
3
|
+
import typer
|
|
4
|
+
|
|
5
|
+
from diffsage.config.loader import load_settings
|
|
6
|
+
from diffsage.config.paths import get_credentials_path
|
|
7
|
+
from diffsage.exceptions import (
|
|
8
|
+
BaseBranchNotFoundError,
|
|
9
|
+
ConfigError,
|
|
10
|
+
CredentialNotFoundError,
|
|
11
|
+
DetachedHeadError,
|
|
12
|
+
GitHubAuthenticationError,
|
|
13
|
+
GitHubCLIUnavailableError,
|
|
14
|
+
InvalidPullRequestDraftError,
|
|
15
|
+
NotGitRepositoryError,
|
|
16
|
+
ProviderError,
|
|
17
|
+
RemoteBranchNotFoundError,
|
|
18
|
+
SameBranchError,
|
|
19
|
+
UnpushedChangesError,
|
|
20
|
+
)
|
|
21
|
+
from diffsage.git.client import GitClient
|
|
22
|
+
from diffsage.github.client import GitHubClient
|
|
23
|
+
from diffsage.logging.logger import get_logger
|
|
24
|
+
from diffsage.parsers.pull_request_parser import PullRequestParser
|
|
25
|
+
from diffsage.services.ai_service import AIService
|
|
26
|
+
from diffsage.services.credentials_service import CredentialService
|
|
27
|
+
from diffsage.services.editor_service import EditorService
|
|
28
|
+
from diffsage.services.git_service import GitService
|
|
29
|
+
from diffsage.services.github_service import GitHubService
|
|
30
|
+
from diffsage.services.prompt_service import PromptService
|
|
31
|
+
from diffsage.services.pull_request_analysis_service import (
|
|
32
|
+
PullRequestAnalysisService,
|
|
33
|
+
)
|
|
34
|
+
from diffsage.services.pull_request_service import PullRequestService
|
|
35
|
+
from diffsage.storage.credentials_repository import CredentialsRepository
|
|
36
|
+
from diffsage.ui.pull_request_view import PullRequestView
|
|
37
|
+
|
|
38
|
+
logger = get_logger(__name__)
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
def pr(
|
|
42
|
+
base_branch: str | None = typer.Argument(
|
|
43
|
+
None,
|
|
44
|
+
help="Base branch to compare against. If omitted, DiffSage resolves it automatically.",
|
|
45
|
+
),
|
|
46
|
+
) -> None:
|
|
47
|
+
"""Generate a pull request draft"""
|
|
48
|
+
|
|
49
|
+
view = PullRequestView()
|
|
50
|
+
|
|
51
|
+
try:
|
|
52
|
+
git_client = GitClient()
|
|
53
|
+
github_client = GitHubClient()
|
|
54
|
+
settings = load_settings()
|
|
55
|
+
|
|
56
|
+
credential_path = get_credentials_path()
|
|
57
|
+
credential_repository = CredentialsRepository(credential_path)
|
|
58
|
+
credential_service = CredentialService(credential_repository)
|
|
59
|
+
|
|
60
|
+
git_service = GitService(git_client)
|
|
61
|
+
github_service = GitHubService(github_client)
|
|
62
|
+
analysis_service = PullRequestAnalysisService()
|
|
63
|
+
prompt_service = PromptService()
|
|
64
|
+
editor_service = EditorService()
|
|
65
|
+
|
|
66
|
+
ai_service = AIService(
|
|
67
|
+
settings,
|
|
68
|
+
credential_service,
|
|
69
|
+
)
|
|
70
|
+
|
|
71
|
+
parser = PullRequestParser()
|
|
72
|
+
|
|
73
|
+
pull_request_service = PullRequestService(
|
|
74
|
+
git_service=git_service,
|
|
75
|
+
analysis_service=analysis_service,
|
|
76
|
+
prompt_service=prompt_service,
|
|
77
|
+
ai_service=ai_service,
|
|
78
|
+
parser=parser,
|
|
79
|
+
)
|
|
80
|
+
|
|
81
|
+
resolved_base_branch = git_service.resolve_base_branch(base_branch)
|
|
82
|
+
head_branch = git_service.current_branch()
|
|
83
|
+
|
|
84
|
+
git_service.validate_remote_head(head_branch)
|
|
85
|
+
github_service.validate()
|
|
86
|
+
|
|
87
|
+
view.show_branches(
|
|
88
|
+
base_branch=resolved_base_branch,
|
|
89
|
+
head_branch=head_branch,
|
|
90
|
+
)
|
|
91
|
+
|
|
92
|
+
with view.generating() as status:
|
|
93
|
+
draft = pull_request_service.generate_draft(
|
|
94
|
+
resolved_base_branch,
|
|
95
|
+
on_attempt=lambda attempt, total: status.update(
|
|
96
|
+
f"Generating pull request draft... Attempt {attempt}/{total}"
|
|
97
|
+
),
|
|
98
|
+
)
|
|
99
|
+
|
|
100
|
+
view.show_generated(draft)
|
|
101
|
+
|
|
102
|
+
while True:
|
|
103
|
+
choice = view.prompt_action()
|
|
104
|
+
|
|
105
|
+
if choice in ("", "y"):
|
|
106
|
+
logger.info("User accepted pull request draft.")
|
|
107
|
+
|
|
108
|
+
with view.creating():
|
|
109
|
+
pull_request_url = github_service.create_pull_request(
|
|
110
|
+
draft=draft,
|
|
111
|
+
base_branch=resolved_base_branch,
|
|
112
|
+
head_branch=head_branch,
|
|
113
|
+
)
|
|
114
|
+
|
|
115
|
+
view.show_created(pull_request_url)
|
|
116
|
+
break
|
|
117
|
+
|
|
118
|
+
if choice == "e":
|
|
119
|
+
logger.info("User selected edit.")
|
|
120
|
+
|
|
121
|
+
edited_content = editor_service.edit(
|
|
122
|
+
json.dumps(
|
|
123
|
+
draft.to_dict(),
|
|
124
|
+
indent=2,
|
|
125
|
+
)
|
|
126
|
+
)
|
|
127
|
+
try:
|
|
128
|
+
draft = parser.parse(edited_content)
|
|
129
|
+
except InvalidPullRequestDraftError as e:
|
|
130
|
+
view.show_error(str(e))
|
|
131
|
+
continue
|
|
132
|
+
|
|
133
|
+
view.show_generated(draft)
|
|
134
|
+
continue
|
|
135
|
+
|
|
136
|
+
if choice == "r":
|
|
137
|
+
logger.info("User selected regenerate.")
|
|
138
|
+
|
|
139
|
+
with view.generating():
|
|
140
|
+
draft = pull_request_service.generate_draft(
|
|
141
|
+
resolved_base_branch,
|
|
142
|
+
on_attempt=lambda attempt, total: status.update(
|
|
143
|
+
f"Generating pull request draft... Attempt {attempt}/{total}"
|
|
144
|
+
),
|
|
145
|
+
)
|
|
146
|
+
|
|
147
|
+
view.show_generated(draft)
|
|
148
|
+
continue
|
|
149
|
+
|
|
150
|
+
if choice == "n":
|
|
151
|
+
logger.info("User cancelled pull request generation.")
|
|
152
|
+
view.show_cancelled()
|
|
153
|
+
break
|
|
154
|
+
|
|
155
|
+
view.show_invalid_option()
|
|
156
|
+
|
|
157
|
+
except NotGitRepositoryError:
|
|
158
|
+
logger.info("Command aborted: not a Git repository.")
|
|
159
|
+
view.show_not_git_repository()
|
|
160
|
+
raise SystemExit(1) from None
|
|
161
|
+
|
|
162
|
+
except DetachedHeadError:
|
|
163
|
+
logger.info("Command aborted: detached HEAD.")
|
|
164
|
+
view.show_detached_head()
|
|
165
|
+
raise SystemExit(1) from None
|
|
166
|
+
|
|
167
|
+
except BaseBranchNotFoundError:
|
|
168
|
+
logger.info("Command aborted: base branch could not be determined.")
|
|
169
|
+
view.show_base_branch_not_found()
|
|
170
|
+
raise SystemExit(1) from None
|
|
171
|
+
|
|
172
|
+
except SameBranchError:
|
|
173
|
+
logger.info("Current branch and base branch are the same.")
|
|
174
|
+
view.show_same_branch()
|
|
175
|
+
raise SystemExit(1) from None
|
|
176
|
+
|
|
177
|
+
except RemoteBranchNotFoundError:
|
|
178
|
+
logger.info("Command aborted: remote branch does not exist.")
|
|
179
|
+
view.show_error("Remote branch does not exist on origin.")
|
|
180
|
+
raise SystemExit(1) from None
|
|
181
|
+
|
|
182
|
+
except UnpushedChangesError:
|
|
183
|
+
logger.info("Command aborted: branch contains unpushed commits.")
|
|
184
|
+
view.show_error("Your branch contains commits that have not been pushed to origin.")
|
|
185
|
+
raise SystemExit(1) from None
|
|
186
|
+
|
|
187
|
+
except ProviderError as e:
|
|
188
|
+
logger.warning(str(e))
|
|
189
|
+
view.show_error(str(e))
|
|
190
|
+
raise SystemExit(1) from None
|
|
191
|
+
|
|
192
|
+
except CredentialNotFoundError as e:
|
|
193
|
+
logger.warning(str(e))
|
|
194
|
+
view.show_error(str(e))
|
|
195
|
+
raise SystemExit(1) from None
|
|
196
|
+
|
|
197
|
+
except ConfigError as e:
|
|
198
|
+
logger.warning(str(e))
|
|
199
|
+
view.show_error(str(e))
|
|
200
|
+
raise SystemExit(1) from None
|
|
201
|
+
|
|
202
|
+
except InvalidPullRequestDraftError as e:
|
|
203
|
+
logger.warning(str(e))
|
|
204
|
+
view.show_error(str(e))
|
|
205
|
+
raise SystemExit(1) from None
|
|
206
|
+
|
|
207
|
+
except GitHubCLIUnavailableError as e:
|
|
208
|
+
logger.warning(str(e))
|
|
209
|
+
view.show_error(str(e))
|
|
210
|
+
raise SystemExit(1) from None
|
|
211
|
+
|
|
212
|
+
except GitHubAuthenticationError as e:
|
|
213
|
+
logger.warning(str(e))
|
|
214
|
+
view.show_error(str(e))
|
|
215
|
+
raise SystemExit(1) from None
|
|
216
|
+
|
|
217
|
+
except Exception:
|
|
218
|
+
logger.exception("Unexpected error while executing PR command.")
|
|
219
|
+
view.show_error("An unexpected error occurred. Please check the log file for more details.")
|
|
220
|
+
raise SystemExit(1) from None
|