code_owners 1.0.4 → 1.0.8

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
- SHA256:
3
- metadata.gz: f5e06c6fbdfd8cead1030d3d76b302885321095972e7fcab54d1ba887f1fceb0
4
- data.tar.gz: 54438d6312527c987d98f181f6242777badf72dad7b7561c39d3bc491dae5f2b
2
+ SHA1:
3
+ metadata.gz: 0c170835202aa29d1d863ac544a039dfa2aaca9b
4
+ data.tar.gz: 732f6a87815efa4092ea1404b55180d9bde78f39
5
5
  SHA512:
6
- metadata.gz: 05ba4fbc4d3c3613b458b30a7370c832622ffc1ffee7cb4f2cefb408277a167cde5c5204a2fa53f2584b3a62c01abddce662a1ada86d22c30c97612fd53359ab
7
- data.tar.gz: dfbd672e42dcd6944cfd3a9f246a8fd4e813cf77eeedf2b3bba7c1d38e58919335833c5ca49b1c8a45a61cec14da580b2fcabc4d1f2eeaf587da42f0a60bb692
6
+ metadata.gz: b205789714617196c0e067504e8beae29dc585a6601a79279475a3a17cc36b249d61e896c6972d8d2fc2b4780e5df406a86c92226ef6013fd6fb262087733ae6
7
+ data.tar.gz: 4e7766d5ee8b1adf676974e8dce8345ce93d2b4b9e868d4295578d35b5656897621c0dcf00e227377024946e2d7429c695e2bf86539fbd24a5992fdce2c9e4fd
data/Gemfile.lock CHANGED
@@ -1,27 +1,27 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- code_owners (1.0.4)
4
+ code_owners (1.0.8)
5
5
  rake
6
6
 
7
7
  GEM
8
8
  remote: http://rubygems.org/
9
9
  specs:
10
10
  diff-lcs (1.3)
11
- rake (12.3.1)
12
- rspec (3.7.0)
13
- rspec-core (~> 3.7.0)
14
- rspec-expectations (~> 3.7.0)
15
- rspec-mocks (~> 3.7.0)
16
- rspec-core (3.7.1)
17
- rspec-support (~> 3.7.0)
18
- rspec-expectations (3.7.0)
11
+ rake (13.0.6)
12
+ rspec (3.8.0)
13
+ rspec-core (~> 3.8.0)
14
+ rspec-expectations (~> 3.8.0)
15
+ rspec-mocks (~> 3.8.0)
16
+ rspec-core (3.8.2)
17
+ rspec-support (~> 3.8.0)
18
+ rspec-expectations (3.8.4)
19
19
  diff-lcs (>= 1.2.0, < 2.0)
20
- rspec-support (~> 3.7.0)
21
- rspec-mocks (3.7.0)
20
+ rspec-support (~> 3.8.0)
21
+ rspec-mocks (3.8.1)
22
22
  diff-lcs (>= 1.2.0, < 2.0)
23
- rspec-support (~> 3.7.0)
24
- rspec-support (3.7.1)
23
+ rspec-support (~> 3.8.0)
24
+ rspec-support (3.8.2)
25
25
 
26
26
  PLATFORMS
27
27
  ruby
@@ -31,4 +31,4 @@ DEPENDENCIES
31
31
  rspec
32
32
 
33
33
  BUNDLED WITH
34
- 1.16.1
34
+ 1.17.3
data/README.md CHANGED
@@ -5,6 +5,12 @@ Install
5
5
 
6
6
  gem install code_owners
7
7
 
8
+ Requirements
9
+ ============
10
+
11
+ * Ruby
12
+ * Git
13
+
8
14
  Usage
9
15
  =====
10
16
 
@@ -24,12 +30,12 @@ Development
24
30
 
25
31
  Maybe put it in a cleanliness test, like:
26
32
 
27
- ```
28
- it "does not introduce new unowned files" do
29
- unowned_files = CodeOwners.ownerships.select { |f| f[:owner] == "UNOWNED" }
30
- # this number should only decrease, never increase!
31
- assert_equal 12345, unowned_files.count, "Claim ownership of your new files in .github/CODEOWNERS to fix this test!"
32
- end
33
+ ```ruby
34
+ it "does not introduce new unowned files" do
35
+ unowned_files = CodeOwners.ownerships.select { |f| f[:owner] == CodeOwners::NO_OWNER }
36
+ # this number should only decrease, never increase!
37
+ assert_equal 12345, unowned_files.count, "Claim ownership of your new files in .github/CODEOWNERS to fix this test!"
38
+ end
33
39
  ```
34
40
 
35
41
  Author
data/bin/code_owners CHANGED
@@ -1,10 +1,45 @@
1
1
  #!/usr/bin/env ruby
2
2
 
3
+ unless system('git --version > /dev/null')
4
+ STDERR.puts 'Git does not appear to be installed.'
5
+ exit 2
6
+ end
7
+
8
+ unless system('git rev-parse --is-inside-work-tree > /dev/null')
9
+ STDERR.puts 'The current working directory must be a Git repo.'
10
+ exit 3
11
+ end
12
+
3
13
  $LOAD_PATH << File.join(File.dirname(__FILE__), '..', 'lib')
4
14
  require 'code_owners'
15
+ require 'code_owners/version'
16
+ require 'optparse'
17
+
18
+ options = {}
19
+ OptionParser.new do |opts|
20
+ opts.banner = "usage: code_owners [options]"
21
+ opts.on('-u', '--unowned', TrueClass, 'Display unowned files only') do |u|
22
+ options[:unowned] = u
23
+ end
24
+ opts.on('-e', '--error-unowned', TrueClass, 'Exit with error status if any files are unowned') do |e|
25
+ options[:error_unowned] = e
26
+ end
27
+ opts.on('-v', '--version', TrueClass, 'Display the version of the gem') do |_|
28
+ puts "Version: #{CodeOwners::VERSION}"
29
+ exit 0
30
+ end
31
+ end.parse!
5
32
 
33
+ unowned_error = false
6
34
  CodeOwners.ownerships.each do |ownership_status|
7
35
  owner_info = ownership_status[:owner].dup
36
+ if owner_info != CodeOwners::NO_OWNER
37
+ next if options[:unowned]
38
+ else
39
+ unowned_error ||= options[:error_unowned]
40
+ end
8
41
  owner_info += " per line #{ownership_status[:line]}, #{ownership_status[:pattern]}" if owner_info != "UNOWNED"
9
42
  puts "#{ownership_status[:file].ljust(100,' ')} #{owner_info}"
10
43
  end
44
+
45
+ exit(unowned_error && 1 || 0)
data/code_owners.gemspec CHANGED
@@ -8,10 +8,10 @@ Gem::Specification.new name, CodeOwners::VERSION do |s|
8
8
  s.description = "utility gem for .github/CODEOWNERS introspection"
9
9
  s.authors = "Jonathan Cheatham"
10
10
  s.email = "coaxis@gmail.com"
11
- s.homepage = "http://github.com/jcheatham/#{s.name}"
11
+ s.homepage = "https://github.com/jcheatham/#{s.name}"
12
12
  s.licenses = "MIT"
13
13
 
14
- s.files = `git ls-files`.split("\n")
14
+ s.files = `git -c "core.quotepath=off" ls-files`.split("\n")
15
15
  s.bindir = 'bin'
16
16
  s.test_files = `git ls-files -- test/*`.split("\n")
17
17
  s.require_paths = ["lib"]
@@ -1,3 +1,3 @@
1
1
  module CodeOwners
2
- VERSION = "1.0.4"
2
+ VERSION = "1.0.8"
3
3
  end
data/lib/code_owners.rb CHANGED
@@ -2,7 +2,9 @@ require "code_owners/version"
2
2
  require "tempfile"
3
3
 
4
4
  module CodeOwners
5
+ NO_OWNER = 'UNOWNED'
5
6
  class << self
7
+
6
8
  # github's CODEOWNERS rules (https://help.github.com/articles/about-codeowners/) are allegedly based on the gitignore format.
7
9
  # but you can't tell ls-files to ignore tracked files via an arbitrary pattern file
8
10
  # so we need to jump through some hacky git-fu hoops
@@ -13,24 +15,53 @@ module CodeOwners
13
15
  # -v -> verbose, outputs details about the matching pattern (if any) for each given pathname
14
16
  # -n -> non-matching, shows given paths which don't match any pattern
15
17
 
16
- def ownerships
17
- patterns, owners = pattern_owners.transpose
18
+ def log(message)
19
+ puts message
20
+ end
18
21
 
19
- git_owner_info(patterns).map do |line, pattern, file|
22
+ def ownerships
23
+ patterns = pattern_owners
24
+ git_owner_info(patterns.map { |p| p[0] }).map do |line, pattern, file|
20
25
  if line.empty?
21
- { file: file, owner: "UNOWNED" }
26
+ { file: file, owner: NO_OWNER, line: nil, pattern: nil }
22
27
  else
23
- { file: file, owner: owners[line.to_i - 1], line: line, pattern: pattern }
28
+ {
29
+ file: file,
30
+ owner: patterns.fetch(line.to_i-1)[1],
31
+ line: line,
32
+ pattern: pattern
33
+ }
24
34
  end
25
35
  end
26
36
  end
27
37
 
38
+ def search_codeowners_file
39
+ paths = ["CODEOWNERS", "docs/CODEOWNERS", ".github/CODEOWNERS"]
40
+ for path in paths
41
+ current_file_path = File.join(current_repo_path, path)
42
+ return current_file_path if File.exist?(current_file_path)
43
+ end
44
+ abort("[ERROR] CODEOWNERS file does not exist.")
45
+ end
46
+
28
47
  # read the github file and spit out a slightly formatted list of patterns and their owners
48
+ # Empty/invalid/commented lines are still included in order to preserve line numbering
29
49
  def pattern_owners
30
- codeowner_path = File.join(current_repo_path, ".github/CODEOWNERS")
31
- File.read(codeowner_path).split("\n").map do |line|
32
- line.gsub(/#.*/, '').gsub(/^$/, " @").split(/\s+@/, 2)
33
- end
50
+ codeowner_path = search_codeowners_file
51
+ patterns = []
52
+ File.read(codeowner_path).split("\n").each_with_index { |line, i|
53
+ path_owner = line.split(/\s+@/, 2)
54
+ if line.match(/^\s*(?:#.*)?$/)
55
+ patterns.push ['', ''] # Comment/empty line
56
+ elsif path_owner.length != 2 || (path_owner[0].empty? && !path_owner[1].empty?)
57
+ log "Parse error line #{(i+1).to_s}: \"#{line}\""
58
+ patterns.push ['', ''] # Invalid line
59
+ else
60
+ path_owner[1] = '@'+path_owner[1]
61
+ patterns.push path_owner
62
+ end
63
+ }
64
+ return patterns
34
65
  end
35
66
 
36
67
  def git_owner_info(patterns)
@@ -46,7 +77,7 @@ module CodeOwners
46
77
  Tempfile.open('codeowner_patterns') do |file|
47
78
  file.write(patterns.join("\n"))
48
79
  file.rewind
49
- `cd #{current_repo_path} && git ls-files | xargs -- git -c \"core.excludesfile=#{file.path}\" check-ignore --no-index -v -n`
80
+ `cd #{current_repo_path} && git -c \"core.quotepath=off\" ls-files | xargs -- git -c \"core.quotepath=off\" -c \"core.excludesfile=#{file.path}\" check-ignore --no-index -v -n`
50
81
  end
51
82
  end
52
83
 
@@ -1,38 +1,71 @@
1
1
  require 'code_owners'
2
+ require 'tmpdir'
2
3
 
3
- RSpec.describe CodeOwners do
4
+ RSpec.describe CodeOwners do |rspec|
4
5
  describe ".ownerships" do
5
6
  it "assigns owners to things" do
6
- expect(CodeOwners).to receive(:pattern_owners).and_return([["pat1", "own1"], ["pat2", "own2"], ["pat3", "own3"]])
7
+ expect(CodeOwners).to receive(:pattern_owners).and_return([["pat1", "own1"], ["pat2*", "own2"], ["pat3", "own3"]])
7
8
  expect(CodeOwners).to receive(:git_owner_info).and_return(
8
9
  [
9
- ["2", "whatever/pattern/thing", "this/is/a/file"],
10
+ ["2", "pat2*", "pat2file"],
10
11
  ["", "", "unowned/file"]
11
12
  ]
12
13
  )
13
14
  expect(CodeOwners.ownerships).to eq(
14
15
  [
15
- { file: "this/is/a/file", owner: "own2", line: "2", pattern: "whatever/pattern/thing" },
16
- { file: "unowned/file", owner: "UNOWNED"}
16
+ { file: "pat2file", owner: "own2", line: "2", pattern: "pat2*" },
17
+ { file: "unowned/file", owner: "UNOWNED", line: nil, pattern: nil }
17
18
  ]
18
19
  )
19
20
  end
20
21
  end
21
22
 
22
23
  describe ".pattern_owners" do
24
+ around(:each) do |example|
25
+ Dir.mktmpdir { |d|
26
+ @d = d
27
+ f = File.new(File.join(d, 'CODEOWNERS'), 'w+')
28
+ f.write <<-CODEOWNERS
29
+ lib/* @jcheatham
30
+ some/path/** @someoneelse
31
+ other/path/* @someoneelse @anotherperson
32
+ invalid/codeowners/line
33
+ @AnotherInvalidLine
34
+ #comment-line (empty line next)
35
+
36
+ # another comment line
37
+ CODEOWNERS
38
+ f.close
39
+ example.run
40
+ }
41
+ end
42
+
23
43
  it "returns a list of patterns and owners" do
24
- patterns, owners = CodeOwners.pattern_owners.transpose
25
- expect(owners).to include("jcheatham")
26
- expect(patterns).to include("lib/*")
44
+ expect(CodeOwners).to receive(:current_repo_path).and_return(@d)
45
+ expect(CodeOwners).to receive(:log).twice
46
+ pattern_owners = CodeOwners.pattern_owners
47
+ expect(pattern_owners).to include(["other/path/*", "@someoneelse @anotherperson"])
27
48
  end
28
49
 
29
50
  it "works when invoked in a repo's subdirectory" do
30
- Dir.chdir("spec") do
31
- patterns, owners = CodeOwners.pattern_owners.transpose
32
- expect(owners).to include("jcheatham")
33
- expect(patterns).to include("lib/*")
51
+ expect(CodeOwners).to receive(:current_repo_path).and_return(@d)
52
+ expect(CodeOwners).to receive(:log).twice
53
+ subdir = File.join(@d, 'spec')
54
+ Dir.mkdir(subdir)
55
+ Dir.chdir(subdir) do
56
+ pattern_owners = CodeOwners.pattern_owners
57
+ expect(pattern_owners).to include(["lib/*", "@jcheatham"])
34
58
  end
35
59
  end
60
+
61
+ it "prints validation errors and skips lines that aren't the expected format" do
62
+ expect(CodeOwners).to receive(:current_repo_path).and_return(@d)
63
+ expect(CodeOwners).to receive(:log).with("Parse error line 4: \"invalid/codeowners/line \"")
64
+ expect(CodeOwners).to receive(:log).with("Parse error line 5: \" @AnotherInvalidLine\"")
65
+ pattern_owners = CodeOwners.pattern_owners
66
+ expect(pattern_owners).not_to include(["", "@AnotherInvalidLine"])
67
+ expect(pattern_owners).to include(["", ""])
68
+ end
36
69
  end
37
70
 
38
71
  describe ".git_owner_info" do
@@ -54,4 +87,16 @@ RSpec.describe CodeOwners do
54
87
  expect(raw_ownership).to match(/^(?:.*:\d*:.*\t.*\n)+$/)
55
88
  end
56
89
  end
90
+
91
+ describe "code_owners" do
92
+ VERSION_REGEX = /Version: \d+\.\d+\.\d+(-[a-z0-9]+)?/i
93
+
94
+ it "prints a version number with the short option" do
95
+ expect(`bin#{File::SEPARATOR}code_owners -v`).to match VERSION_REGEX
96
+ end
97
+
98
+ it "prints a version number with the short option" do
99
+ expect(`bin#{File::SEPARATOR}code_owners --version`).to match VERSION_REGEX
100
+ end
101
+ end
57
102
  end
@@ -0,0 +1 @@
1
+ ☃☃☃☃☃☃☃☃☃
metadata CHANGED
@@ -1,11 +1,11 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: code_owners
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.0.4
4
+ version: 1.0.8
5
5
  platform: ruby
6
6
  authors:
7
7
  - Jonathan Cheatham
8
- autorequire:
8
+ autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
11
  date: 2018-03-29 00:00:00.000000000 Z
@@ -57,11 +57,12 @@ files:
57
57
  - lib/code_owners.rb
58
58
  - lib/code_owners/version.rb
59
59
  - spec/code_owners_spec.rb
60
- homepage: http://github.com/jcheatham/code_owners
60
+ - spec/files/☃.txt
61
+ homepage: https://github.com/jcheatham/code_owners
61
62
  licenses:
62
63
  - MIT
63
64
  metadata: {}
64
- post_install_message:
65
+ post_install_message:
65
66
  rdoc_options: []
66
67
  require_paths:
67
68
  - lib
@@ -76,9 +77,9 @@ required_rubygems_version: !ruby/object:Gem::Requirement
76
77
  - !ruby/object:Gem::Version
77
78
  version: '0'
78
79
  requirements: []
79
- rubyforge_project:
80
- rubygems_version: 2.7.3
81
- signing_key:
80
+ rubyforge_project:
81
+ rubygems_version: 2.5.2.3
82
+ signing_key:
82
83
  specification_version: 4
83
84
  summary: ".github/CODEOWNERS introspection utility gem"
84
85
  test_files: []