code_ownership 1.38.2 → 1.39.0

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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: b40aef85c83e70ad2c2a8ba45030b3e894b184ec71e897443d47b859343a8636
4
- data.tar.gz: 27741ae85077282dca91f42f274325d66ee25b8ed01eaab733aab581f23e6e4e
3
+ metadata.gz: 4750c985a93873263fe3f86ee3acfa80b3bb9a234445b2cacfdb57dd657916e4
4
+ data.tar.gz: 4e672aeeeaa93addd346d0a01c0dc135dd576e4e196a91124e8f7e31f7045da1
5
5
  SHA512:
6
- metadata.gz: 8d8c2765433571d6407ec67608554fb04dc9b14d84948ddf9b227874aca628e3fd4e927c6154a3d41ede65bdd8628f522e77c089fab9727ec6860339a03673e3
7
- data.tar.gz: 0c8eeb737cbbcce93210c43f0f6c21d846761fa94daa97243796747db3343bf6fd40c033cd3337a628716dd91714354ed5d2af1a2eda1ea29ea716f9ef0687e7
6
+ metadata.gz: 39b311ddd4a120865344fea860189c452d7adb8b88d56ce3cd6add99a376bb8337cb27d4d741c1fc44fa473b4a15866a50c9e18d08f47f9ebb8bfb57106a9c7f
7
+ data.tar.gz: 1355b25ee4a54d09dd6722aca4f11ad410521414cca07fdf7ccafba299ccc7482cbfa97b58c0c198488eacdf12c479aa1777c1acfcee7305f5454d60441c44ad
data/README.md CHANGED
@@ -164,6 +164,8 @@ bin/codeownership for_team 'My Team' > tmp/ownership_report.md
164
164
 
165
165
  A `CODEOWNERS` file defines who owns specific files or paths in a repository. When you run `bin/codeownership validate`, a `.github/CODEOWNERS` file will automatically be generated and updated.
166
166
 
167
+ If `codeowners_path` is set in `code_ownership.yml` codeowners will use that path to generate the `CODEOWNERS` file. For example, `codeowners_path: docs` will generate `docs/CODEOWNERS`.
168
+
167
169
  ## Proper Configuration & Validation
168
170
 
169
171
  CodeOwnership comes with a validation function to ensure the following things are true:
@@ -12,6 +12,7 @@ module CodeOwnership
12
12
  const :skip_codeowners_validation, T::Boolean
13
13
  const :raw_hash, T::Hash[T.untyped, T.untyped]
14
14
  const :require_github_teams, T::Boolean
15
+ const :codeowners_path, String
15
16
 
16
17
  sig { returns(Configuration) }
17
18
  def self.fetch
@@ -29,7 +30,8 @@ module CodeOwnership
29
30
  js_package_paths: js_package_paths(config_hash),
30
31
  skip_codeowners_validation: config_hash.fetch('skip_codeowners_validation', false),
31
32
  raw_hash: config_hash,
32
- require_github_teams: config_hash.fetch('require_github_teams', false)
33
+ require_github_teams: config_hash.fetch('require_github_teams', false),
34
+ codeowners_path: config_hash.fetch('codeowners_path', '.github'),
33
35
  )
34
36
  end
35
37
 
@@ -111,7 +111,10 @@ module CodeOwnership
111
111
 
112
112
  sig { returns(Pathname) }
113
113
  def self.path
114
- Pathname.pwd.join('.github/CODEOWNERS')
114
+ Pathname.pwd.join(
115
+ CodeOwnership.configuration.codeowners_path,
116
+ 'CODEOWNERS'
117
+ )
115
118
  end
116
119
 
117
120
  sig { params(files: T::Array[String]).void }
@@ -12,7 +12,7 @@ module CodeOwnership
12
12
  # addresses the case where a directory name includes regex characters
13
13
  # such as `app/services/[test]/some_other_file.ts`
14
14
  mapping[glob] = owner if File.exist?(glob)
15
- Dir.glob(glob).each do |file|
15
+ Dir.glob(glob) do |file|
16
16
  mapping[file] ||= owner
17
17
  end
18
18
  end
@@ -18,7 +18,7 @@ module CodeOwnership
18
18
  extend T::Sig
19
19
  include Mapper
20
20
 
21
- TEAM_PATTERN = T.let(%r{\A(?:#|//) @team (?<team>.*)\Z}.freeze, Regexp)
21
+ TEAM_PATTERN = T.let(%r{\A(?:#|//|-#) @team (?<team>.*)\Z}.freeze, Regexp)
22
22
  DESCRIPTION = 'Annotations at the top of file'
23
23
 
24
24
  sig do
@@ -73,19 +73,20 @@ module CodeOwnership
73
73
 
74
74
  sig { params(filename: String).returns(T.nilable(CodeTeams::Team)) }
75
75
  def file_annotation_based_owner(filename)
76
- # If for a directory is named with an ownable extension, we need to skip
77
- # so File.foreach doesn't blow up below. This was needed because Cypress
78
- # screenshots are saved to a folder with the test suite filename.
79
- return if File.directory?(filename)
80
- return unless File.file?(filename)
81
-
82
76
  # The annotation should be on line 1 but as of this comment
83
77
  # there's no linter installed to enforce that. We therefore check the
84
78
  # first line (the Ruby VM makes a single `read(1)` call for 8KB),
85
79
  # and if the annotation isn't in the first two lines we assume it
86
80
  # doesn't exist.
87
81
 
88
- line1 = File.foreach(filename).first
82
+ begin
83
+ line1 = File.foreach(filename).first
84
+ rescue Errno::EISDIR, Errno::ENOENT
85
+ # Ignore files that fail to read to avoid intermittent bugs.
86
+ # Ignoring directories is needed because, e.g., Cypress screenshots
87
+ # are saved to a folder with the test suite filename.
88
+ return
89
+ end
89
90
 
90
91
  return if !line1
91
92
 
@@ -14,17 +14,16 @@ module CodeOwnership
14
14
  @@map_files_to_owners = {} # rubocop:disable Style/ClassVars
15
15
 
16
16
  sig do
17
- params(files: T::Array[String])
18
- .returns(T::Hash[String, ::CodeTeams::Team])
17
+ returns(T::Hash[String, ::CodeTeams::Team])
19
18
  end
20
- def map_files_to_owners(files)
19
+ def map_files_to_owners
21
20
  return @@map_files_to_owners if @@map_files_to_owners&.keys && @@map_files_to_owners.keys.count.positive?
22
21
 
23
22
  @@map_files_to_owners = CodeTeams.all.each_with_object({}) do |team, map| # rubocop:disable Style/ClassVars
24
- TeamPlugins::Ownership.for(team).owned_globs.each do |glob|
25
- Dir.glob(glob).each do |filename|
26
- map[filename] = team
27
- end
23
+ code_team = TeamPlugins::Ownership.for(team)
24
+
25
+ (Dir.glob(code_team.owned_globs) - Dir.glob(code_team.unowned_globs)).each do |filename|
26
+ map[filename] = team
28
27
  end
29
28
  end
30
29
  end
@@ -56,13 +55,20 @@ module CodeOwnership
56
55
  end
57
56
  def find_overlapping_globs
58
57
  mapped_files = T.let({}, T::Hash[String, T::Array[MappingContext]])
59
- CodeTeams.all.each_with_object({}) do |team, _map|
60
- TeamPlugins::Ownership.for(team).owned_globs.each do |glob|
61
- Dir.glob(glob).each do |filename|
58
+ CodeTeams.all.each do |team|
59
+ code_team = TeamPlugins::Ownership.for(team)
60
+
61
+ code_team.owned_globs.each do |glob|
62
+ Dir.glob(glob) do |filename|
62
63
  mapped_files[filename] ||= []
63
64
  T.must(mapped_files[filename]) << MappingContext.new(glob: glob, team: team)
64
65
  end
65
66
  end
67
+
68
+ # Remove anything that is unowned, globbing them all at once
69
+ Dir.glob(code_team.unowned_globs) do |filename|
70
+ mapped_files.reject! { |key, value| key == filename && value.any? { |context| context.team == team } }
71
+ end
66
72
  end
67
73
 
68
74
  overlaps = T.let([], T::Array[GlobOverlap])
@@ -81,10 +87,10 @@ module CodeOwnership
81
87
 
82
88
  sig do
83
89
  override.params(file: String)
84
- .returns(T.nilable(::CodeTeams::Team))
90
+ .returns(T.nilable(::CodeTeams::Team))
85
91
  end
86
92
  def map_file_to_owner(file)
87
- map_files_to_owners([file])[file]
93
+ map_files_to_owners[file]
88
94
  end
89
95
 
90
96
  sig do
@@ -96,7 +102,7 @@ module CodeOwnership
96
102
 
97
103
  sig do
98
104
  override.params(files: T::Array[String])
99
- .returns(T::Hash[String, ::CodeTeams::Team])
105
+ .returns(T::Hash[String, ::CodeTeams::Team])
100
106
  end
101
107
  def globs_to_owner(files)
102
108
  CodeTeams.all.each_with_object({}) do |team, map|
@@ -11,6 +11,11 @@ module CodeOwnership
11
11
  def owned_globs
12
12
  @team.raw_hash['owned_globs'] || []
13
13
  end
14
+
15
+ sig { returns(T::Array[String]) }
16
+ def unowned_globs
17
+ @team.raw_hash['unowned_globs'] || []
18
+ end
14
19
  end
15
20
  end
16
21
  end
metadata CHANGED
@@ -1,13 +1,13 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: code_ownership
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.38.2
4
+ version: 1.39.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Gusto Engineers
8
8
  bindir: bin
9
9
  cert_chain: []
10
- date: 2025-01-16 00:00:00.000000000 Z
10
+ date: 2025-03-01 00:00:00.000000000 Z
11
11
  dependencies:
12
12
  - !ruby/object:Gem::Dependency
13
13
  name: code_teams