gitflow-analytics 1.0.3__py3-none-any.whl → 1.3.6__py3-none-any.whl
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.
- gitflow_analytics/_version.py +1 -1
- gitflow_analytics/classification/__init__.py +31 -0
- gitflow_analytics/classification/batch_classifier.py +752 -0
- gitflow_analytics/classification/classifier.py +464 -0
- gitflow_analytics/classification/feature_extractor.py +725 -0
- gitflow_analytics/classification/linguist_analyzer.py +574 -0
- gitflow_analytics/classification/model.py +455 -0
- gitflow_analytics/cli.py +4108 -350
- gitflow_analytics/cli_rich.py +198 -48
- gitflow_analytics/config/__init__.py +43 -0
- gitflow_analytics/config/errors.py +261 -0
- gitflow_analytics/config/loader.py +904 -0
- gitflow_analytics/config/profiles.py +264 -0
- gitflow_analytics/config/repository.py +124 -0
- gitflow_analytics/config/schema.py +441 -0
- gitflow_analytics/config/validator.py +154 -0
- gitflow_analytics/config.py +44 -508
- gitflow_analytics/core/analyzer.py +1209 -98
- gitflow_analytics/core/cache.py +1337 -29
- gitflow_analytics/core/data_fetcher.py +1193 -0
- gitflow_analytics/core/identity.py +363 -14
- gitflow_analytics/core/metrics_storage.py +526 -0
- gitflow_analytics/core/progress.py +372 -0
- gitflow_analytics/core/schema_version.py +269 -0
- gitflow_analytics/extractors/ml_tickets.py +1100 -0
- gitflow_analytics/extractors/story_points.py +8 -1
- gitflow_analytics/extractors/tickets.py +749 -11
- gitflow_analytics/identity_llm/__init__.py +6 -0
- gitflow_analytics/identity_llm/analysis_pass.py +231 -0
- gitflow_analytics/identity_llm/analyzer.py +464 -0
- gitflow_analytics/identity_llm/models.py +76 -0
- gitflow_analytics/integrations/github_integration.py +175 -11
- gitflow_analytics/integrations/jira_integration.py +461 -24
- gitflow_analytics/integrations/orchestrator.py +124 -1
- gitflow_analytics/metrics/activity_scoring.py +322 -0
- gitflow_analytics/metrics/branch_health.py +470 -0
- gitflow_analytics/metrics/dora.py +379 -20
- gitflow_analytics/models/database.py +843 -53
- gitflow_analytics/pm_framework/__init__.py +115 -0
- gitflow_analytics/pm_framework/adapters/__init__.py +50 -0
- gitflow_analytics/pm_framework/adapters/jira_adapter.py +1845 -0
- gitflow_analytics/pm_framework/base.py +406 -0
- gitflow_analytics/pm_framework/models.py +211 -0
- gitflow_analytics/pm_framework/orchestrator.py +652 -0
- gitflow_analytics/pm_framework/registry.py +333 -0
- gitflow_analytics/qualitative/__init__.py +9 -10
- gitflow_analytics/qualitative/chatgpt_analyzer.py +259 -0
- gitflow_analytics/qualitative/classifiers/__init__.py +3 -3
- gitflow_analytics/qualitative/classifiers/change_type.py +518 -244
- gitflow_analytics/qualitative/classifiers/domain_classifier.py +272 -165
- gitflow_analytics/qualitative/classifiers/intent_analyzer.py +321 -222
- gitflow_analytics/qualitative/classifiers/llm/__init__.py +35 -0
- gitflow_analytics/qualitative/classifiers/llm/base.py +193 -0
- gitflow_analytics/qualitative/classifiers/llm/batch_processor.py +383 -0
- gitflow_analytics/qualitative/classifiers/llm/cache.py +479 -0
- gitflow_analytics/qualitative/classifiers/llm/cost_tracker.py +435 -0
- gitflow_analytics/qualitative/classifiers/llm/openai_client.py +403 -0
- gitflow_analytics/qualitative/classifiers/llm/prompts.py +373 -0
- gitflow_analytics/qualitative/classifiers/llm/response_parser.py +287 -0
- gitflow_analytics/qualitative/classifiers/llm_commit_classifier.py +607 -0
- gitflow_analytics/qualitative/classifiers/risk_analyzer.py +215 -189
- gitflow_analytics/qualitative/core/__init__.py +4 -4
- gitflow_analytics/qualitative/core/llm_fallback.py +239 -235
- gitflow_analytics/qualitative/core/nlp_engine.py +157 -148
- gitflow_analytics/qualitative/core/pattern_cache.py +214 -192
- gitflow_analytics/qualitative/core/processor.py +381 -248
- gitflow_analytics/qualitative/enhanced_analyzer.py +2236 -0
- gitflow_analytics/qualitative/example_enhanced_usage.py +420 -0
- gitflow_analytics/qualitative/models/__init__.py +7 -7
- gitflow_analytics/qualitative/models/schemas.py +155 -121
- gitflow_analytics/qualitative/utils/__init__.py +4 -4
- gitflow_analytics/qualitative/utils/batch_processor.py +136 -123
- gitflow_analytics/qualitative/utils/cost_tracker.py +142 -140
- gitflow_analytics/qualitative/utils/metrics.py +172 -158
- gitflow_analytics/qualitative/utils/text_processing.py +146 -104
- gitflow_analytics/reports/__init__.py +100 -0
- gitflow_analytics/reports/analytics_writer.py +539 -14
- gitflow_analytics/reports/base.py +648 -0
- gitflow_analytics/reports/branch_health_writer.py +322 -0
- gitflow_analytics/reports/classification_writer.py +924 -0
- gitflow_analytics/reports/cli_integration.py +427 -0
- gitflow_analytics/reports/csv_writer.py +1676 -212
- gitflow_analytics/reports/data_models.py +504 -0
- gitflow_analytics/reports/database_report_generator.py +427 -0
- gitflow_analytics/reports/example_usage.py +344 -0
- gitflow_analytics/reports/factory.py +499 -0
- gitflow_analytics/reports/formatters.py +698 -0
- gitflow_analytics/reports/html_generator.py +1116 -0
- gitflow_analytics/reports/interfaces.py +489 -0
- gitflow_analytics/reports/json_exporter.py +2770 -0
- gitflow_analytics/reports/narrative_writer.py +2287 -158
- gitflow_analytics/reports/story_point_correlation.py +1144 -0
- gitflow_analytics/reports/weekly_trends_writer.py +389 -0
- gitflow_analytics/training/__init__.py +5 -0
- gitflow_analytics/training/model_loader.py +377 -0
- gitflow_analytics/training/pipeline.py +550 -0
- gitflow_analytics/tui/__init__.py +1 -1
- gitflow_analytics/tui/app.py +129 -126
- gitflow_analytics/tui/screens/__init__.py +3 -3
- gitflow_analytics/tui/screens/analysis_progress_screen.py +188 -179
- gitflow_analytics/tui/screens/configuration_screen.py +154 -178
- gitflow_analytics/tui/screens/loading_screen.py +100 -110
- gitflow_analytics/tui/screens/main_screen.py +89 -72
- gitflow_analytics/tui/screens/results_screen.py +305 -281
- gitflow_analytics/tui/widgets/__init__.py +2 -2
- gitflow_analytics/tui/widgets/data_table.py +67 -69
- gitflow_analytics/tui/widgets/export_modal.py +76 -76
- gitflow_analytics/tui/widgets/progress_widget.py +41 -46
- gitflow_analytics-1.3.6.dist-info/METADATA +1015 -0
- gitflow_analytics-1.3.6.dist-info/RECORD +122 -0
- gitflow_analytics-1.0.3.dist-info/METADATA +0 -490
- gitflow_analytics-1.0.3.dist-info/RECORD +0 -62
- {gitflow_analytics-1.0.3.dist-info → gitflow_analytics-1.3.6.dist-info}/WHEEL +0 -0
- {gitflow_analytics-1.0.3.dist-info → gitflow_analytics-1.3.6.dist-info}/entry_points.txt +0 -0
- {gitflow_analytics-1.0.3.dist-info → gitflow_analytics-1.3.6.dist-info}/licenses/LICENSE +0 -0
- {gitflow_analytics-1.0.3.dist-info → gitflow_analytics-1.3.6.dist-info}/top_level.txt +0 -0
|
@@ -0,0 +1,264 @@
|
|
|
1
|
+
"""Configuration profiles and presets for GitFlow Analytics."""
|
|
2
|
+
|
|
3
|
+
from typing import Any, Optional
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class ConfigurationProfile:
|
|
7
|
+
"""Base class for configuration profiles."""
|
|
8
|
+
|
|
9
|
+
name: str = ""
|
|
10
|
+
description: str = ""
|
|
11
|
+
|
|
12
|
+
@classmethod
|
|
13
|
+
def get_settings(cls) -> dict[str, Any]:
|
|
14
|
+
"""Get the profile settings.
|
|
15
|
+
|
|
16
|
+
Returns:
|
|
17
|
+
Dictionary of configuration settings for this profile
|
|
18
|
+
"""
|
|
19
|
+
raise NotImplementedError
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
class PerformanceProfile(ConfigurationProfile):
|
|
23
|
+
"""Performance-optimized configuration profile.
|
|
24
|
+
|
|
25
|
+
This profile prioritizes speed over accuracy and completeness.
|
|
26
|
+
Suitable for large codebases or quick analysis runs.
|
|
27
|
+
"""
|
|
28
|
+
|
|
29
|
+
name = "performance"
|
|
30
|
+
description = "Optimized for speed with large repositories"
|
|
31
|
+
|
|
32
|
+
@classmethod
|
|
33
|
+
def get_settings(cls) -> dict[str, Any]:
|
|
34
|
+
"""Get performance-optimized settings."""
|
|
35
|
+
return {
|
|
36
|
+
"analysis": {
|
|
37
|
+
"branch_analysis": {
|
|
38
|
+
"strategy": "main_only",
|
|
39
|
+
"max_branches_per_repo": 10,
|
|
40
|
+
"branch_commit_limit": 500,
|
|
41
|
+
},
|
|
42
|
+
"ml_categorization": {
|
|
43
|
+
"enabled": False, # Disable ML for speed
|
|
44
|
+
"batch_size": 500,
|
|
45
|
+
},
|
|
46
|
+
"commit_classification": {
|
|
47
|
+
"enabled": False, # Disable classification for speed
|
|
48
|
+
},
|
|
49
|
+
"llm_classification": {
|
|
50
|
+
"enabled": False, # Disable LLM for speed
|
|
51
|
+
},
|
|
52
|
+
},
|
|
53
|
+
"cache": {
|
|
54
|
+
"ttl_hours": 336, # 2 weeks cache
|
|
55
|
+
"max_size_mb": 1000,
|
|
56
|
+
},
|
|
57
|
+
"output": {
|
|
58
|
+
"formats": ["csv"], # CSV only for speed
|
|
59
|
+
},
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
class QualityProfile(ConfigurationProfile):
|
|
64
|
+
"""Quality-focused configuration profile.
|
|
65
|
+
|
|
66
|
+
This profile enables all analysis features for maximum insight.
|
|
67
|
+
Suitable for detailed analysis and reporting.
|
|
68
|
+
"""
|
|
69
|
+
|
|
70
|
+
name = "quality"
|
|
71
|
+
description = "Maximum analysis depth and accuracy"
|
|
72
|
+
|
|
73
|
+
@classmethod
|
|
74
|
+
def get_settings(cls) -> dict[str, Any]:
|
|
75
|
+
"""Get quality-focused settings."""
|
|
76
|
+
return {
|
|
77
|
+
"analysis": {
|
|
78
|
+
"branch_analysis": {
|
|
79
|
+
"strategy": "smart",
|
|
80
|
+
"max_branches_per_repo": 100,
|
|
81
|
+
"active_days_threshold": 180,
|
|
82
|
+
"branch_commit_limit": 2000,
|
|
83
|
+
},
|
|
84
|
+
"ml_categorization": {
|
|
85
|
+
"enabled": True,
|
|
86
|
+
"min_confidence": 0.7,
|
|
87
|
+
"semantic_weight": 0.8,
|
|
88
|
+
"batch_size": 50,
|
|
89
|
+
},
|
|
90
|
+
"commit_classification": {
|
|
91
|
+
"enabled": True,
|
|
92
|
+
"confidence_threshold": 0.6,
|
|
93
|
+
"auto_retrain": True,
|
|
94
|
+
},
|
|
95
|
+
"auto_identity_analysis": True,
|
|
96
|
+
},
|
|
97
|
+
"cache": {
|
|
98
|
+
"ttl_hours": 72, # Shorter cache for freshness
|
|
99
|
+
"max_size_mb": 500,
|
|
100
|
+
},
|
|
101
|
+
"output": {
|
|
102
|
+
"formats": ["csv", "markdown", "json"],
|
|
103
|
+
},
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
|
|
107
|
+
class BalancedProfile(ConfigurationProfile):
|
|
108
|
+
"""Balanced configuration profile.
|
|
109
|
+
|
|
110
|
+
This profile provides a good balance between performance and quality.
|
|
111
|
+
Suitable for most use cases.
|
|
112
|
+
"""
|
|
113
|
+
|
|
114
|
+
name = "balanced"
|
|
115
|
+
description = "Balanced performance and quality (default)"
|
|
116
|
+
|
|
117
|
+
@classmethod
|
|
118
|
+
def get_settings(cls) -> dict[str, Any]:
|
|
119
|
+
"""Get balanced settings."""
|
|
120
|
+
return {
|
|
121
|
+
"analysis": {
|
|
122
|
+
"branch_analysis": {
|
|
123
|
+
"strategy": "smart",
|
|
124
|
+
"max_branches_per_repo": 50,
|
|
125
|
+
"active_days_threshold": 90,
|
|
126
|
+
"branch_commit_limit": 1000,
|
|
127
|
+
},
|
|
128
|
+
"ml_categorization": {
|
|
129
|
+
"enabled": True,
|
|
130
|
+
"min_confidence": 0.6,
|
|
131
|
+
"semantic_weight": 0.7,
|
|
132
|
+
"batch_size": 100,
|
|
133
|
+
},
|
|
134
|
+
"commit_classification": {
|
|
135
|
+
"enabled": True,
|
|
136
|
+
"confidence_threshold": 0.5,
|
|
137
|
+
},
|
|
138
|
+
},
|
|
139
|
+
"cache": {
|
|
140
|
+
"ttl_hours": 168, # 1 week
|
|
141
|
+
"max_size_mb": 500,
|
|
142
|
+
},
|
|
143
|
+
"output": {
|
|
144
|
+
"formats": ["csv", "markdown"],
|
|
145
|
+
},
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
|
|
149
|
+
class MinimalProfile(ConfigurationProfile):
|
|
150
|
+
"""Minimal configuration profile.
|
|
151
|
+
|
|
152
|
+
This profile runs only essential analysis features.
|
|
153
|
+
Suitable for basic metrics and quick overview.
|
|
154
|
+
"""
|
|
155
|
+
|
|
156
|
+
name = "minimal"
|
|
157
|
+
description = "Essential features only"
|
|
158
|
+
|
|
159
|
+
@classmethod
|
|
160
|
+
def get_settings(cls) -> dict[str, Any]:
|
|
161
|
+
"""Get minimal settings."""
|
|
162
|
+
return {
|
|
163
|
+
"analysis": {
|
|
164
|
+
"branch_analysis": {
|
|
165
|
+
"strategy": "main_only",
|
|
166
|
+
},
|
|
167
|
+
"ml_categorization": {
|
|
168
|
+
"enabled": False,
|
|
169
|
+
},
|
|
170
|
+
"commit_classification": {
|
|
171
|
+
"enabled": False,
|
|
172
|
+
},
|
|
173
|
+
"llm_classification": {
|
|
174
|
+
"enabled": False,
|
|
175
|
+
},
|
|
176
|
+
"auto_identity_analysis": False,
|
|
177
|
+
},
|
|
178
|
+
"output": {
|
|
179
|
+
"formats": ["csv"],
|
|
180
|
+
},
|
|
181
|
+
"cache": {
|
|
182
|
+
"ttl_hours": 720, # 30 days
|
|
183
|
+
},
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
|
|
187
|
+
class ProfileManager:
|
|
188
|
+
"""Manages configuration profiles."""
|
|
189
|
+
|
|
190
|
+
# Registry of available profiles
|
|
191
|
+
_profiles: dict[str, type[ConfigurationProfile]] = {
|
|
192
|
+
"performance": PerformanceProfile,
|
|
193
|
+
"quality": QualityProfile,
|
|
194
|
+
"balanced": BalancedProfile,
|
|
195
|
+
"minimal": MinimalProfile,
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
@classmethod
|
|
199
|
+
def get_profile(cls, name: str) -> Optional[type[ConfigurationProfile]]:
|
|
200
|
+
"""Get a configuration profile by name.
|
|
201
|
+
|
|
202
|
+
Args:
|
|
203
|
+
name: Profile name
|
|
204
|
+
|
|
205
|
+
Returns:
|
|
206
|
+
Profile class or None if not found
|
|
207
|
+
"""
|
|
208
|
+
return cls._profiles.get(name.lower())
|
|
209
|
+
|
|
210
|
+
@classmethod
|
|
211
|
+
def list_profiles(cls) -> dict[str, str]:
|
|
212
|
+
"""List available profiles.
|
|
213
|
+
|
|
214
|
+
Returns:
|
|
215
|
+
Dictionary of profile names to descriptions
|
|
216
|
+
"""
|
|
217
|
+
return {name: profile.description for name, profile in cls._profiles.items()}
|
|
218
|
+
|
|
219
|
+
@classmethod
|
|
220
|
+
def apply_profile(cls, config_data: dict[str, Any], profile_name: str) -> dict[str, Any]:
|
|
221
|
+
"""Apply a profile to configuration data.
|
|
222
|
+
|
|
223
|
+
Args:
|
|
224
|
+
config_data: Base configuration data
|
|
225
|
+
profile_name: Name of profile to apply
|
|
226
|
+
|
|
227
|
+
Returns:
|
|
228
|
+
Updated configuration data with profile settings
|
|
229
|
+
|
|
230
|
+
Raises:
|
|
231
|
+
ValueError: If profile not found
|
|
232
|
+
"""
|
|
233
|
+
profile_class = cls.get_profile(profile_name)
|
|
234
|
+
if not profile_class:
|
|
235
|
+
available = ", ".join(cls._profiles.keys())
|
|
236
|
+
raise ValueError(
|
|
237
|
+
f"Unknown configuration profile: {profile_name}. "
|
|
238
|
+
f"Available profiles: {available}"
|
|
239
|
+
)
|
|
240
|
+
|
|
241
|
+
profile_settings = profile_class.get_settings()
|
|
242
|
+
# Profile settings are the base, config_data overrides them
|
|
243
|
+
return cls._deep_merge(profile_settings, config_data)
|
|
244
|
+
|
|
245
|
+
@staticmethod
|
|
246
|
+
def _deep_merge(base: dict[str, Any], override: dict[str, Any]) -> dict[str, Any]:
|
|
247
|
+
"""Deep merge two dictionaries.
|
|
248
|
+
|
|
249
|
+
Args:
|
|
250
|
+
base: Base dictionary
|
|
251
|
+
override: Dictionary with values to override
|
|
252
|
+
|
|
253
|
+
Returns:
|
|
254
|
+
Merged dictionary
|
|
255
|
+
"""
|
|
256
|
+
result = base.copy()
|
|
257
|
+
|
|
258
|
+
for key, value in override.items():
|
|
259
|
+
if key in result and isinstance(result[key], dict) and isinstance(value, dict):
|
|
260
|
+
result[key] = ProfileManager._deep_merge(result[key], value)
|
|
261
|
+
else:
|
|
262
|
+
result[key] = value
|
|
263
|
+
|
|
264
|
+
return result
|
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
"""Repository discovery and management for GitFlow Analytics."""
|
|
2
|
+
|
|
3
|
+
from pathlib import Path
|
|
4
|
+
from typing import Optional
|
|
5
|
+
|
|
6
|
+
from github import Github
|
|
7
|
+
|
|
8
|
+
from .schema import GitHubConfig, RepositoryConfig
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class RepositoryManager:
|
|
12
|
+
"""Manages repository discovery and configuration."""
|
|
13
|
+
|
|
14
|
+
def __init__(self, github_config: GitHubConfig):
|
|
15
|
+
"""Initialize repository manager.
|
|
16
|
+
|
|
17
|
+
Args:
|
|
18
|
+
github_config: GitHub configuration
|
|
19
|
+
"""
|
|
20
|
+
self.github_config = github_config
|
|
21
|
+
|
|
22
|
+
def discover_organization_repositories(
|
|
23
|
+
self, clone_base_path: Optional[Path] = None
|
|
24
|
+
) -> list[RepositoryConfig]:
|
|
25
|
+
"""Discover repositories from GitHub organization.
|
|
26
|
+
|
|
27
|
+
Args:
|
|
28
|
+
clone_base_path: Base directory where repos should be cloned/found.
|
|
29
|
+
|
|
30
|
+
Returns:
|
|
31
|
+
List of discovered repository configurations.
|
|
32
|
+
"""
|
|
33
|
+
if not self.github_config.organization or not self.github_config.token:
|
|
34
|
+
return []
|
|
35
|
+
|
|
36
|
+
github_client = Github(self.github_config.token, base_url=self.github_config.base_url)
|
|
37
|
+
|
|
38
|
+
try:
|
|
39
|
+
org = github_client.get_organization(self.github_config.organization)
|
|
40
|
+
discovered_repos = []
|
|
41
|
+
|
|
42
|
+
if clone_base_path is None:
|
|
43
|
+
raise ValueError("No base path available for repository cloning")
|
|
44
|
+
|
|
45
|
+
for repo in org.get_repos():
|
|
46
|
+
# Skip archived repositories
|
|
47
|
+
if repo.archived:
|
|
48
|
+
continue
|
|
49
|
+
|
|
50
|
+
# Create repository configuration
|
|
51
|
+
repo_path = clone_base_path / repo.name
|
|
52
|
+
repo_config = RepositoryConfig(
|
|
53
|
+
name=repo.name,
|
|
54
|
+
path=repo_path,
|
|
55
|
+
github_repo=repo.full_name,
|
|
56
|
+
project_key=repo.name.upper().replace("-", "_"),
|
|
57
|
+
branch=repo.default_branch,
|
|
58
|
+
)
|
|
59
|
+
discovered_repos.append(repo_config)
|
|
60
|
+
|
|
61
|
+
return discovered_repos
|
|
62
|
+
|
|
63
|
+
except Exception as e:
|
|
64
|
+
raise ValueError(
|
|
65
|
+
f"Failed to discover repositories from organization "
|
|
66
|
+
f"{self.github_config.organization}: {e}"
|
|
67
|
+
) from e
|
|
68
|
+
|
|
69
|
+
def process_repository_config(
|
|
70
|
+
self, repo_data: dict, index: int, config_path: Path
|
|
71
|
+
) -> RepositoryConfig:
|
|
72
|
+
"""Process a single repository configuration entry.
|
|
73
|
+
|
|
74
|
+
Args:
|
|
75
|
+
repo_data: Repository configuration data
|
|
76
|
+
index: Repository index in the list (for error messages)
|
|
77
|
+
config_path: Path to configuration file (for error messages)
|
|
78
|
+
|
|
79
|
+
Returns:
|
|
80
|
+
RepositoryConfig instance
|
|
81
|
+
|
|
82
|
+
Raises:
|
|
83
|
+
ValueError: If repository configuration is invalid
|
|
84
|
+
"""
|
|
85
|
+
from .errors import InvalidValueError, MissingFieldError
|
|
86
|
+
|
|
87
|
+
# Validate required repository fields
|
|
88
|
+
if not isinstance(repo_data, dict):
|
|
89
|
+
raise InvalidValueError(
|
|
90
|
+
f"repositories[{index}]",
|
|
91
|
+
type(repo_data).__name__,
|
|
92
|
+
"must be a YAML object with name and path",
|
|
93
|
+
config_path,
|
|
94
|
+
valid_values=["object with 'name' and 'path' fields"],
|
|
95
|
+
)
|
|
96
|
+
|
|
97
|
+
if "name" not in repo_data or repo_data["name"] is None:
|
|
98
|
+
raise MissingFieldError(
|
|
99
|
+
"name", f"repositories[{index}]", config_path, example='name: "your-repo-name"'
|
|
100
|
+
)
|
|
101
|
+
|
|
102
|
+
if "path" not in repo_data or repo_data["path"] is None:
|
|
103
|
+
raise MissingFieldError(
|
|
104
|
+
"path",
|
|
105
|
+
f"repositories[{index}] ('{repo_data.get('name', 'unknown')}')",
|
|
106
|
+
config_path,
|
|
107
|
+
example='path: "/path/to/repo"',
|
|
108
|
+
)
|
|
109
|
+
|
|
110
|
+
# Handle github_repo with owner/organization fallback
|
|
111
|
+
github_repo = repo_data.get("github_repo")
|
|
112
|
+
if github_repo and "/" not in github_repo:
|
|
113
|
+
if self.github_config.organization:
|
|
114
|
+
github_repo = f"{self.github_config.organization}/{github_repo}"
|
|
115
|
+
elif self.github_config.owner:
|
|
116
|
+
github_repo = f"{self.github_config.owner}/{github_repo}"
|
|
117
|
+
|
|
118
|
+
return RepositoryConfig(
|
|
119
|
+
name=repo_data["name"],
|
|
120
|
+
path=repo_data["path"],
|
|
121
|
+
github_repo=github_repo,
|
|
122
|
+
project_key=repo_data.get("project_key"),
|
|
123
|
+
branch=repo_data.get("branch"),
|
|
124
|
+
)
|