code_ownership 1.33.1 → 1.34.1

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: db417ae94561fc36906b23a65105d40bb48c6a084ae79fa33f49a84a903344f1
4
- data.tar.gz: b0ba72d4cfd3f5c34e90352f1c009fd3e9093cea73f6a7b5044e6ae4632040c7
3
+ metadata.gz: 5b59593095e1b124a7e6bf7b929e7f2bc9ab61f65f5f0867fff88b6cf2b5e097
4
+ data.tar.gz: 1dcef45327a13811dacbe44566dbddae71998145de1b9ca63df5814e848ca899
5
5
  SHA512:
6
- metadata.gz: 24b6290597a69fd80b62a70a0268dc559a8704f12f67800aa5640ed71d3af6c57b551f7fc757ea57a92503cc5cca89556872b13159bf13dfaef06b868dd51e09
7
- data.tar.gz: 28ad95acd3dcba5b2b94577a6b8355bd4d78b044cd96c7c608a9ece82a89bd4c0792143503b29d4a3ca81a0b0c71fb57427c3c1ca684a0ba31cbc0a5cb4f541b
6
+ metadata.gz: 05546e3fb45286c44cc7227675b01d747ada9ee9b5e8cb02ab692fe3b9e30558fd1d8cfbc6716fadbece2c4048d164fe9a00b940a80f7fdf4366dbf0043fcbdf
7
+ data.tar.gz: '06584d3cb45cfb7ab7cd8e0e55a1a4b1c5db09e0dc022300f75f63714a2121f42301fe9bdbe11c1f5715909a7853d68060244be51925d0edb1fab0985b0112e5'
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
@@ -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.1
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-08 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: code_teams
@@ -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