@@ -34,12 +34,37 @@ module CodeOwnership
3434 requires_ancestor { Kernel }
3535 GlobsToOwningTeamMap = T . type_alias { T ::Hash [ String , CodeTeams ::Team ] }
3636
37+ # Returns the version of the code_ownership gem and the codeowners-rs gem.
3738 sig { returns ( T ::Array [ String ] ) }
3839 def version
3940 [ "code_ownership version: #{ VERSION } " ,
4041 "codeowners-rs version: #{ ::RustCodeOwners . version } " ]
4142 end
4243
44+ # Returns the owning team for a given file path.
45+ #
46+ # @param file [String] The path to the file to find ownership for. Can be relative or absolute.
47+ # @param from_codeowners [Boolean] (default: true) When true, uses CODEOWNERS file to determine ownership.
48+ # When false, uses alternative team finding strategies (e.g., package ownership).
49+ # from_codeowners true is faster because it simply matches the provided file to the generate CODEOWNERS file. This is a safe option when you can trust the CODEOWNERS file to be up to date.
50+ # @param allow_raise [Boolean] (default: false) When true, raises an exception if ownership cannot be determined.
51+ # When false, returns nil for files without ownership.
52+ #
53+ # @return [CodeTeams::Team, nil] The team that owns the file, or nil if no owner is found
54+ # (unless allow_raise is true, in which case an exception is raised).
55+ #
56+ # @example Find owner for a file using CODEOWNERS
57+ # team = CodeOwnership.for_file('app/models/user.rb')
58+ # # => #<CodeTeams::Team:0x... @name="platform">
59+ #
60+ # @example Find owner without using CODEOWNERS
61+ # team = CodeOwnership.for_file('app/models/user.rb', from_codeowners: false)
62+ # # => #<CodeTeams::Team:0x... @name="platform">
63+ #
64+ # @example Raise if no owner is found
65+ # team = CodeOwnership.for_file('unknown_file.rb', allow_raise: true)
66+ # # => raises exception if no owner found
67+ #
4368 sig { params ( file : String , from_codeowners : T ::Boolean , allow_raise : T ::Boolean ) . returns ( T . nilable ( CodeTeams ::Team ) ) }
4469 def for_file ( file , from_codeowners : true , allow_raise : false )
4570 if from_codeowners
@@ -49,11 +74,92 @@ def for_file(file, from_codeowners: true, allow_raise: false)
4974 end
5075 end
5176
77+ # Returns the owning teams for multiple file paths using the CODEOWNERS file.
78+ #
79+ # This method efficiently determines ownership for multiple files in a single operation
80+ # by leveraging the generated CODEOWNERS file. It's more performant than calling
81+ # `for_file` multiple times when you need to check ownership for many files.
82+ #
83+ # @param files [Array<String>] An array of file paths to find ownership for.
84+ # Paths can be relative to the project root or absolute.
85+ # @param allow_raise [Boolean] (default: false) When true, raises an exception if a team
86+ # name in CODEOWNERS cannot be resolved to an actual team.
87+ # When false, returns nil for files with unresolvable teams.
88+ #
89+ # @return [T::Hash[String, T.nilable(CodeTeams::Team)]] A hash mapping each file path to its
90+ # owning team. Files without ownership
91+ # or with unresolvable teams will map to nil.
92+ #
93+ # @example Get owners for multiple files
94+ # files = ['app/models/user.rb', 'app/controllers/users_controller.rb', 'config/routes.rb']
95+ # owners = CodeOwnership.teams_for_files_from_codeowners(files)
96+ # # => {
97+ # # 'app/models/user.rb' => #<CodeTeams::Team:0x... @name="platform">,
98+ # # 'app/controllers/users_controller.rb' => #<CodeTeams::Team:0x... @name="platform">,
99+ # # 'config/routes.rb' => #<CodeTeams::Team:0x... @name="infrastructure">
100+ # # }
101+ #
102+ # @example Handle files without owners
103+ # files = ['owned_file.rb', 'unowned_file.txt']
104+ # owners = CodeOwnership.teams_for_files_from_codeowners(files)
105+ # # => {
106+ # # 'owned_file.rb' => #<CodeTeams::Team:0x... @name="backend">,
107+ # # 'unowned_file.txt' => nil
108+ # # }
109+ #
110+ # @note This method uses caching internally for performance. The cache is populated
111+ # as files are processed and reused for subsequent lookups.
112+ #
113+ # @note This method relies on the CODEOWNERS file being up-to-date. Run
114+ # `CodeOwnership.validate!` to ensure the CODEOWNERS file is current.
115+ #
116+ # @see #for_file for single file ownership lookup
117+ # @see #validate! for ensuring CODEOWNERS file is up-to-date
118+ #
52119 sig { params ( files : T ::Array [ String ] , allow_raise : T ::Boolean ) . returns ( T ::Hash [ String , T . nilable ( CodeTeams ::Team ) ] ) }
53120 def teams_for_files_from_codeowners ( files , allow_raise : false )
54121 Private ::TeamFinder . teams_for_files ( files , allow_raise : allow_raise )
55122 end
56123
124+ # Returns detailed ownership information for a given file path.
125+ #
126+ # This method provides verbose ownership details including the team name,
127+ # team configuration file path, and the reasons/sources for ownership assignment.
128+ # It's particularly useful for debugging ownership assignments and understanding
129+ # why a file is owned by a specific team.
130+ #
131+ # @param file [String] The path to the file to find ownership for. Can be relative or absolute.
132+ #
133+ # @return [T::Hash[Symbol, String], nil] A hash containing detailed ownership information,
134+ # or nil if no owner is found.
135+ #
136+ # The returned hash contains the following keys when an owner is found:
137+ # - :team_name [String] - The name of the owning team
138+ # - :team_config_yml [String] - Path to the team's configuration YAML file
139+ # - :reasons [Array<String>] - List of reasons/sources explaining why this team owns the file
140+ # (e.g., "CODEOWNERS pattern: /app/models/**", "Package ownership")
141+ #
142+ # @example Get verbose ownership details
143+ # details = CodeOwnership.for_file_verbose('app/models/user.rb')
144+ # # => {
145+ # # team_name: "platform",
146+ # # team_config_yml: "config/teams/platform.yml",
147+ # # reasons: ["Matched pattern '/app/models/**' in CODEOWNERS"]
148+ # # }
149+ #
150+ # @example Handle unowned files
151+ # details = CodeOwnership.for_file_verbose('unowned_file.txt')
152+ # # => nil
153+ #
154+ # @note This method is primarily used by the CLI tool when the --verbose flag is provided,
155+ # allowing users to understand the ownership assignment logic.
156+ #
157+ # @note Unlike `for_file`, this method always uses the CODEOWNERS file and other ownership
158+ # sources to determine ownership, providing complete context about the ownership decision.
159+ #
160+ # @see #for_file for a simpler ownership lookup that returns just the team
161+ # @see CLI#for_file for the command-line interface that uses this method
162+ #
57163 sig { params ( file : String ) . returns ( T . nilable ( T ::Hash [ Symbol , String ] ) ) }
58164 def for_file_verbose ( file )
59165 ::RustCodeOwners . for_file ( file )
@@ -65,6 +171,55 @@ def for_team(team)
65171 ::RustCodeOwners . for_team ( team . name )
66172 end
67173
174+ # Validates code ownership configuration and optionally corrects issues.
175+ #
176+ # This method performs comprehensive validation of the code ownership setup, ensuring:
177+ # 1. Only one ownership mechanism is defined per file (no conflicts between annotations, packages, or globs)
178+ # 2. All referenced teams are valid (exist in CodeTeams configuration)
179+ # 3. All files have ownership (unless explicitly listed in unowned_globs)
180+ # 4. The .github/CODEOWNERS file is up-to-date and properly formatted
181+ #
182+ # When autocorrect is enabled, the method will automatically:
183+ # - Generate or update the CODEOWNERS file based on current ownership rules
184+ # - Fix any formatting issues in the CODEOWNERS file
185+ # - Stage the corrected CODEOWNERS file (unless stage_changes is false)
186+ #
187+ # @param autocorrect [Boolean] Whether to automatically fix correctable issues (default: true)
188+ # When true, regenerates and updates the CODEOWNERS file
189+ # When false, only validates without making changes
190+ #
191+ # @param stage_changes [Boolean] Whether to stage the CODEOWNERS file after autocorrection (default: true)
192+ # Only applies when autocorrect is true
193+ # When false, changes are written but not staged with git
194+ #
195+ # @param files [Array<String>, nil] Ignored. This is a legacy parameter that is no longer used.
196+ #
197+ # @return [void]
198+ #
199+ # @raise [RuntimeError] Raises an error if validation fails with details about:
200+ # - Files with conflicting ownership definitions
201+ # - References to non-existent teams
202+ # - Files without ownership (not in unowned_globs)
203+ # - CODEOWNERS file inconsistencies
204+ #
205+ # @example Basic validation with autocorrection
206+ # CodeOwnership.validate!
207+ # # Validates all files and auto-corrects/stages CODEOWNERS if needed
208+ #
209+ # @example Validation without making changes
210+ # CodeOwnership.validate!(autocorrect: false)
211+ # # Only checks for issues without updating CODEOWNERS
212+ #
213+ # @example Validate and fix but don't stage changes
214+ # CodeOwnership.validate!(autocorrect: true, stage_changes: false)
215+ # # Fixes CODEOWNERS but doesn't stage it with git
216+ #
217+ # @note This method is called by the CLI command: bin/codeownership validate
218+ # @note The validation can be disabled for CODEOWNERS by setting skip_codeowners_validation: true in config/code_ownership.yml
219+ #
220+ # @see CLI.validate! for the command-line interface
221+ # @see https://docs.github.com/en/repositories/managing-your-repositorys-settings-and-features/customizing-your-repository/about-code-owners for CODEOWNERS format
222+ #
68223 sig do
69224 params (
70225 autocorrect : T ::Boolean ,
0 commit comments