lib/fbe/unmask_repos.rb
# frozen_string_literal: true # SPDX-FileCopyrightText: Copyright (c) 2024-2025 Zerocracy # SPDX-License-Identifier: MIT require 'joined' require_relative '../fbe' require_relative 'octo' # Converts a repository mask pattern to a regular expression. # # @example Basic wildcard matching # Fbe.mask_to_regex('zerocracy/*') # # => /zerocracy\/.*/i # # @example Specific repository (no wildcard) # Fbe.mask_to_regex('zerocracy/fbe') # # => /zerocracy\/fbe/i # # @param [String] mask Repository mask in format 'org/repo' where repo can contain '*' # @return [Regexp] Case-insensitive regular expression for matching repositories # @raise [RuntimeError] If organization part contains asterisk def Fbe.mask_to_regex(mask) org, repo = mask.split('/') raise "Org '#{org}' can't have an asterisk" if org.include?('*') Regexp.compile("#{org}/#{repo.gsub('*', '.*')}", Regexp::IGNORECASE) end # Resolves repository masks to actual GitHub repository names. # # Takes a comma-separated list of repository masks from options and expands # wildcards by querying GitHub API. Supports inclusion and exclusion patterns. # Archived repositories are automatically filtered out. # # @example Basic usage with wildcards # # options.repositories = "zerocracy/fbe,zerocracy/ab*" # repos = Fbe.unmask_repos # # => ["zerocracy/fbe", "zerocracy/abc", "zerocracy/abcd"] # # @example Using exclusion patterns # # options.repositories = "zerocracy/*,-zerocracy/private*" # repos = Fbe.unmask_repos # # Returns all zerocracy repos except those starting with 'private' # # @example Empty result handling # # options.repositories = "nonexistent/*" # Fbe.unmask_repos # Raises error: "No repos found matching: nonexistent/*" # # @param [Judges::Options] options Options containing 'repositories' field with masks # @param [Hash] global Global cache for storing API responses # @param [Loog] loog Logger for debug output # @param [quota_aware] Boolean Should we stop if quota is off? # @return [Array<String>] Shuffled list of repository full names (e.g., 'org/repo') # @raise [RuntimeError] If no repositories match the provided masks # @note Exclusion patterns must start with '-' (e.g., '-org/pattern*') # @note Results are shuffled to distribute load when processing def Fbe.unmask_repos(options: $options, global: $global, loog: $loog, quota_aware: true) raise 'Repositories mask is not specified' unless options.repositories raise 'Repositories mask is empty' if options.repositories.empty? repos = [] octo = Fbe.octo(loog:, global:, options:) masks = (options.repositories || '').split(',') masks.reject { |m| m.start_with?('-') }.each do |mask| unless mask.include?('*') repos << mask next end re = Fbe.mask_to_regex(mask) octo.repositories(mask.split('/')[0]).each do |r| repos << r[:full_name] if re.match?(r[:full_name]) end end masks.select { |m| m.start_with?('-') }.each do |mask| re = Fbe.mask_to_regex(mask[1..]) repos.reject! { |r| re.match?(r) } end repos.reject! { |repo| octo.repository(repo)[:archived] } raise "No repos found matching: #{options.repositories.inspect}" if repos.empty? repos.shuffle! loog.debug("Scanning #{repos.size} repositories: #{repos.joined}...") repos.each { |repo| octo.repository(repo) } return repos unless block_given? repos.each do |repo| if quota_aware && octo.off_quota? $loog.info("No GitHub quota left, it is time to stop at #{repo}") break end yield repo end end