code_ownership 1.33.1 → 1.34.2

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: db417ae94561fc36906b23a65105d40bb48c6a084ae79fa33f49a84a903344f1
4
- data.tar.gz: b0ba72d4cfd3f5c34e90352f1c009fd3e9093cea73f6a7b5044e6ae4632040c7
3
+ metadata.gz: 1a67c504ef03f9010003b0c3fad3eb09aab223ed033d58a090c3a3efc3509a89
4
+ data.tar.gz: 30aef026b8487bc1c20aa94890a4eca64fdaa26dddd44f17b77f705317c9b038
5
5
  SHA512:
6
- metadata.gz: 24b6290597a69fd80b62a70a0268dc559a8704f12f67800aa5640ed71d3af6c57b551f7fc757ea57a92503cc5cca89556872b13159bf13dfaef06b868dd51e09
7
- data.tar.gz: 28ad95acd3dcba5b2b94577a6b8355bd4d78b044cd96c7c608a9ece82a89bd4c0792143503b29d4a3ca81a0b0c71fb57427c3c1ca684a0ba31cbc0a5cb4f541b
6
+ metadata.gz: '09d72749e78b49b14284c3360cba9f9f3dda4e20818305b3fe0a819bbaa2720be5107bb85d8612bedfbd5590a039010d42700510ac027b34bc1a6bdaadc7531f'
7
+ data.tar.gz: 4be38dfd55b95eadf9ee661cb160715cc0ee7f3936f9b9b19d8b7ce65d0ed19b62805f882b8b684863acbc2caa602cb676e58837a060da04fd142f1b89b1e744
data/README.md CHANGED
@@ -12,6 +12,18 @@ There is also a [companion VSCode Extension]([url](https://github.com/rubyatscal
12
12
 
13
13
  There are three ways to declare code ownership using this gem.
14
14
 
15
+ ### Directory-Based Ownership
16
+ Directory based ownership allows for all files in that directory and all its sub-directories to be owned by one team. To define this, add a `.codeowner` file inside that directory with the name of the team as the contents of that file.
17
+ ```
18
+ Team
19
+ ```
20
+
21
+ ### File-Annotation Based Ownership
22
+ File annotations are a last resort if there is no clear home for your code. File annotations go at the top of your file, and look like this:
23
+ ```ruby
24
+ # @team MyTeam
25
+ ```
26
+
15
27
  ### Package-Based Ownership
16
28
  Package based ownership integrates [`packwerk`](https://github.com/Shopify/packwerk) and has ownership defined per package. To define that all files within a package are owned by one team, configure your `package.yml` like this:
17
29
  ```yml
@@ -38,11 +50,6 @@ owned_globs:
38
50
  - app/services/stuff_belonging_to_my_team/**/**
39
51
  - app/controllers/other_stuff_belonging_to_my_team/**/**
40
52
  ```
41
- ### File-Annotation Based Ownership
42
- File annotations are a last resort if there is no clear home for your code. File annotations go at the top of your file, and look like this:
43
- ```ruby
44
- # @team MyTeam
45
- ```
46
53
 
47
54
  ### Javascript Package Ownership
48
55
  Javascript package based ownership allows you to specify an ownership key in a `package.json`. To use this, configure your `package.json` like this:
@@ -0,0 +1,111 @@
1
+ # frozen_string_literal: true
2
+
3
+ # typed: true
4
+
5
+ module CodeOwnership
6
+ module Private
7
+ module OwnershipMappers
8
+ class DirectoryOwnership
9
+ extend T::Sig
10
+ include Mapper
11
+
12
+ CODEOWNERS_DIRECTORY_FILE_NAME = '.codeowner'
13
+
14
+ @@directory_cache = T.let({}, T::Hash[String, T.nilable(CodeTeams::Team)]) # rubocop:disable Style/ClassVars
15
+
16
+ sig do
17
+ override.params(file: String).
18
+ returns(T.nilable(::CodeTeams::Team))
19
+ end
20
+ def map_file_to_owner(file)
21
+ map_file_to_relevant_owner(file)
22
+ end
23
+
24
+ sig do
25
+ override.params(cache: GlobsToOwningTeamMap, files: T::Array[String]).returns(GlobsToOwningTeamMap)
26
+ end
27
+ def update_cache(cache, files)
28
+ globs_to_owner(files)
29
+ end
30
+
31
+ #
32
+ # Directory ownership ignores the passed in files when generating code owners lines.
33
+ # This is because Directory ownership knows that the fastest way to find code owners for directory based ownership
34
+ # is to simply iterate over the directories and grab the owner, rather than iterating over each file just to get what directory it is in
35
+ # In theory this means that we may generate code owners lines that cover files that are not in the passed in argument,
36
+ # but in practice this is not of consequence because in reality we never really want to generate code owners for only a
37
+ # subset of files, but rather we want code ownership for all files.
38
+ #
39
+ sig do
40
+ override.params(files: T::Array[String]).
41
+ returns(T::Hash[String, ::CodeTeams::Team])
42
+ end
43
+ def globs_to_owner(files)
44
+ # The T.unsafe is because the upstream RBI is wrong for Pathname.glob
45
+ T
46
+ .unsafe(Pathname)
47
+ .glob(File.join('**/', CODEOWNERS_DIRECTORY_FILE_NAME))
48
+ .map(&:cleanpath)
49
+ .each_with_object({}) do |pathname, res|
50
+ owner = owner_for_codeowners_file(pathname)
51
+ res[pathname.dirname.cleanpath.join('**/**').to_s] = owner
52
+ end
53
+ end
54
+
55
+ sig { override.returns(String) }
56
+ def description
57
+ 'Owner in .codeowner'
58
+ end
59
+
60
+ sig { override.void }
61
+ def bust_caches!
62
+ @@directory_cache = {} # rubocop:disable Style/ClassVars
63
+ end
64
+
65
+ private
66
+
67
+ sig { params(codeowners_file: Pathname).returns(CodeTeams::Team) }
68
+ def owner_for_codeowners_file(codeowners_file)
69
+ raw_owner_value = File.foreach(codeowners_file).first.strip
70
+
71
+ Private.find_team!(
72
+ raw_owner_value,
73
+ codeowners_file.to_s
74
+ )
75
+ end
76
+
77
+ # takes a file and finds the relevant `.codeowner` file by walking up the directory
78
+ # structure. Example, given `a/b/c.rb`, this looks for `a/b/.codeowner`, `a/.codeowner`,
79
+ # and `.codeowner` in that order, stopping at the first file to actually exist.
80
+ # We do additional caching so that we don't have to check for file existence every time
81
+ sig { params(file: String).returns(T.nilable(CodeTeams::Team)) }
82
+ def map_file_to_relevant_owner(file)
83
+ file_path = Pathname.new(file)
84
+ path_components = file_path.each_filename.to_a.map { |path| Pathname.new(path) }
85
+
86
+ (path_components.length - 1).downto(0).each do |i|
87
+ potential_relative_path_name = T.must(path_components[0...i]).reduce(Pathname.new('')) { |built_path, path| built_path.join(path) }
88
+ potential_codeowners_file = potential_relative_path_name.join(CODEOWNERS_DIRECTORY_FILE_NAME)
89
+
90
+ potential_codeowners_file_name = potential_codeowners_file.to_s
91
+
92
+ team = nil
93
+ if @@directory_cache.key?(potential_codeowners_file_name)
94
+ team = @@directory_cache[potential_codeowners_file_name]
95
+ elsif potential_codeowners_file.exist?
96
+ team = owner_for_codeowners_file(potential_codeowners_file)
97
+
98
+ @@directory_cache[potential_codeowners_file_name] = team
99
+ else
100
+ @@directory_cache[potential_codeowners_file_name] = nil
101
+ end
102
+
103
+ return team unless team.nil?
104
+ end
105
+
106
+ nil
107
+ end
108
+ end
109
+ end
110
+ end
111
+ end
@@ -13,6 +13,7 @@ require 'code_ownership/private/validations/github_codeowners_up_to_date'
13
13
  require 'code_ownership/private/validations/files_have_unique_owners'
14
14
  require 'code_ownership/private/ownership_mappers/file_annotations'
15
15
  require 'code_ownership/private/ownership_mappers/team_globs'
16
+ require 'code_ownership/private/ownership_mappers/directory_ownership'
16
17
  require 'code_ownership/private/ownership_mappers/package_ownership'
17
18
  require 'code_ownership/private/ownership_mappers/js_package_ownership'
18
19
  require 'code_ownership/private/ownership_mappers/team_yml_ownership'
@@ -62,7 +63,7 @@ module CodeOwnership
62
63
 
63
64
  # Returns a string version of the relative path to a Rails constant,
64
65
  # or nil if it can't find something
65
- sig { params(klass: T.nilable(T.any(Class, Module))).returns(T.nilable(String)) }
66
+ sig { params(klass: T.nilable(T.any(T::Class[T.anything], Module))).returns(T.nilable(String)) }
66
67
  def self.path_from_klass(klass)
67
68
  if klass
68
69
  path = Object.const_source_location(klass.to_s)&.first
@@ -6,7 +6,7 @@ require 'set'
6
6
  require 'code_teams'
7
7
  require 'sorbet-runtime'
8
8
  require 'json'
9
- require 'packs'
9
+ require 'packs-specification'
10
10
  require 'code_ownership/mapper'
11
11
  require 'code_ownership/validator'
12
12
  require 'code_ownership/private'
@@ -156,7 +156,7 @@ module CodeOwnership
156
156
  end
157
157
  private_class_method(:backtrace_with_ownership)
158
158
 
159
- sig { params(klass: T.nilable(T.any(Class, Module))).returns(T.nilable(::CodeTeams::Team)) }
159
+ sig { params(klass: T.nilable(T.any(T::Class[T.anything], Module))).returns(T.nilable(::CodeTeams::Team)) }
160
160
  def for_class(klass)
161
161
  @memoized_values ||= T.let(@memoized_values, T.nilable(T::Hash[String, T.nilable(::CodeTeams::Team)]))
162
162
  @memoized_values ||= {}
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: code_ownership
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.33.1
4
+ version: 1.34.2
5
5
  platform: ruby
6
6
  authors:
7
7
  - Gusto Engineers
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2023-06-30 00:00:00.000000000 Z
11
+ date: 2023-08-09 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: code_teams
@@ -25,7 +25,7 @@ dependencies:
25
25
  - !ruby/object:Gem::Version
26
26
  version: '1.0'
27
27
  - !ruby/object:Gem::Dependency
28
- name: packs
28
+ name: packs-specification
29
29
  requirement: !ruby/object:Gem::Requirement
30
30
  requirements:
31
31
  - - ">="
@@ -168,6 +168,7 @@ files:
168
168
  - lib/code_ownership/private/codeowners_file.rb
169
169
  - lib/code_ownership/private/extension_loader.rb
170
170
  - lib/code_ownership/private/glob_cache.rb
171
+ - lib/code_ownership/private/ownership_mappers/directory_ownership.rb
171
172
  - lib/code_ownership/private/ownership_mappers/file_annotations.rb
172
173
  - lib/code_ownership/private/ownership_mappers/js_package_ownership.rb
173
174
  - lib/code_ownership/private/ownership_mappers/package_ownership.rb