@@ -34,12 +34,37 @@ module CodeOwnership
34
34
requires_ancestor { Kernel }
35
35
GlobsToOwningTeamMap = T . type_alias { T ::Hash [ String , CodeTeams ::Team ] }
36
36
37
+ # Returns the version of the code_ownership gem and the codeowners-rs gem.
37
38
sig { returns ( T ::Array [ String ] ) }
38
39
def version
39
40
[ "code_ownership version: #{ VERSION } " ,
40
41
"codeowners-rs version: #{ ::RustCodeOwners . version } " ]
41
42
end
42
43
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
+ #
43
68
sig { params ( file : String , from_codeowners : T ::Boolean , allow_raise : T ::Boolean ) . returns ( T . nilable ( CodeTeams ::Team ) ) }
44
69
def for_file ( file , from_codeowners : true , allow_raise : false )
45
70
if from_codeowners
@@ -49,11 +74,92 @@ def for_file(file, from_codeowners: true, allow_raise: false)
49
74
end
50
75
end
51
76
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
+ #
52
119
sig { params ( files : T ::Array [ String ] , allow_raise : T ::Boolean ) . returns ( T ::Hash [ String , T . nilable ( CodeTeams ::Team ) ] ) }
53
120
def teams_for_files_from_codeowners ( files , allow_raise : false )
54
121
Private ::TeamFinder . teams_for_files ( files , allow_raise : allow_raise )
55
122
end
56
123
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
+ #
57
163
sig { params ( file : String ) . returns ( T . nilable ( T ::Hash [ Symbol , String ] ) ) }
58
164
def for_file_verbose ( file )
59
165
::RustCodeOwners . for_file ( file )
@@ -65,6 +171,55 @@ def for_team(team)
65
171
::RustCodeOwners . for_team ( team . name )
66
172
end
67
173
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
+ #
68
223
sig do
69
224
params (
70
225
autocorrect : T ::Boolean ,
0 commit comments