getRepositoryFeatures gets repository features with caching to amortize API calls
(repo string, verbose bool)
| 189 | |
| 190 | // getRepositoryFeatures gets repository features with caching to amortize API calls |
| 191 | func getRepositoryFeatures(repo string, verbose bool) (*RepositoryFeatures, error) { |
| 192 | // Check cache first using sync.Map |
| 193 | if cached, exists := repositoryFeaturesCache.Load(repo); exists { |
| 194 | features, ok := cached.(*RepositoryFeatures) |
| 195 | if !ok { |
| 196 | repositoryFeaturesCache.Delete(repo) |
| 197 | return nil, fmt.Errorf("invalid repository feature cache entry for %s: expected *RepositoryFeatures, got %T", repo, cached) |
| 198 | } |
| 199 | repositoryFeaturesLog.Printf("Using cached repository features for: %s", repo) |
| 200 | return features, nil |
| 201 | } |
| 202 | |
| 203 | repositoryFeaturesLog.Printf("Fetching repository features from API for: %s", repo) |
| 204 | |
| 205 | // Fetch from API |
| 206 | features := &RepositoryFeatures{} |
| 207 | |
| 208 | // Check discussions |
| 209 | hasDiscussions, err := checkRepositoryHasDiscussionsUncached(repo) |
| 210 | if err != nil { |
| 211 | return nil, fmt.Errorf("failed to check discussions: %w", err) |
| 212 | } |
| 213 | features.HasDiscussions = hasDiscussions |
| 214 | |
| 215 | // Check issues |
| 216 | hasIssues, err := checkRepositoryHasIssuesUncached(repo) |
| 217 | if err != nil { |
| 218 | return nil, fmt.Errorf("failed to check issues: %w", err) |
| 219 | } |
| 220 | features.HasIssues = hasIssues |
| 221 | |
| 222 | // Cache the result using sync.Map's LoadOrStore for atomic caching |
| 223 | // This handles the race condition where multiple goroutines might fetch the same repo |
| 224 | actual, loaded := repositoryFeaturesCache.LoadOrStore(repo, features) |
| 225 | actualFeatures, ok := actual.(*RepositoryFeatures) |
| 226 | if !ok { |
| 227 | repositoryFeaturesCache.Delete(repo) |
| 228 | return nil, fmt.Errorf("invalid repository feature cache entry for %s: expected *RepositoryFeatures, got %T", repo, actual) |
| 229 | } |
| 230 | |
| 231 | repositoryFeaturesLog.Printf("Cached repository features for: %s (discussions: %v, issues: %v)", repo, actualFeatures.HasDiscussions, actualFeatures.HasIssues) |
| 232 | |
| 233 | // Only log the success messages if this is the first time we're caching these features |
| 234 | // and we haven't logged them before (checking loaded flag and logged cache) |
| 235 | if !loaded { |
| 236 | // Mark as logged atomically |
| 237 | // Log success messages only if we haven't logged them before |
| 238 | if _, alreadyLogged := repositoryFeaturesLoggedCache.LoadOrStore(repo, true); !alreadyLogged && verbose { |
| 239 | if actualFeatures.HasDiscussions { |
| 240 | fmt.Fprintln(os.Stderr, console.FormatInfoMessage( |
| 241 | fmt.Sprintf("✓ Repository %s has discussions enabled", repo))) |
| 242 | } |
| 243 | if actualFeatures.HasIssues { |
| 244 | fmt.Fprintln(os.Stderr, console.FormatInfoMessage( |
| 245 | fmt.Sprintf("✓ Repository %s has issues enabled", repo))) |
| 246 | } |
| 247 | } |
| 248 | } |
no test coverage detected