Module: CodeOwners
- Defined in:
- lib/code_owners.rb,
lib/code_owners/version.rb
Constant Summary collapse
- NO_OWNER =
'UNOWNED'
- CODEOWNER_PATTERN =
/(.*?)\s+((?:[^\s]*@[^\s]+\s*)+)/
- POTENTIAL_LOCATIONS =
["CODEOWNERS", "docs/CODEOWNERS", ".github/CODEOWNERS"]
- VERSION =
"2.0.1"
Class Method Summary collapse
- .build_ruby_patterns(patowns, opts = {}) ⇒ Object
-
.file_ownerships(opts = {}) ⇒ Object
helper function to create the lookup for when we have a file and want to find its owner.
- .git_owner_info(patterns) ⇒ Object
- .log(message, opts = {}) ⇒ Object
-
.ownerships(opts = {}) ⇒ Object
this maps the collection of ownership patterns and owners to actual files.
-
.ownerships_by_gitignore(patterns, opts = {}) ⇒ Object
gitignore approach.
-
.ownerships_by_ruby(patowns, files, opts = {}) ⇒ Object
ruby approach.
-
.pattern_owners(codeowner_data, opts = {}) ⇒ Object
read the github file and spit out a slightly formatted list of patterns and their owners Empty/invalid/commented lines are still included in order to preserve line numbering.
-
.raw_git_owner_info(patterns) ⇒ Object
IN: an array of gitignore* check-ignore compliant patterns OUT: a check-ignore formatted string for each file in the repo.
Class Method Details
.build_ruby_patterns(patowns, opts = {}) ⇒ Object
96 97 98 99 100 101 102 103 104 105 106 107 108 109 |
# File 'lib/code_owners.rb', line 96 def build_ruby_patterns(patowns, opts = {}) pattern_list = [] patowns.each_with_index do |(pattern, owner), i| next if pattern == "" pattern_list << { owner: owner, line: i+1, pattern: pattern, # gsub because spec approach needs a little help matching remainder of tree recursively pattern_regex: PathSpec::GitIgnoreSpec.new(pattern.gsub(/\/\*$/, "/**")) } end pattern_list end |
.file_ownerships(opts = {}) ⇒ Object
helper function to create the lookup for when we have a file and want to find its owner
14 15 16 |
# File 'lib/code_owners.rb', line 14 def file_ownerships(opts = {}) Hash[ ownerships(opts).map { |o| [o[:file], o] } ] end |
.git_owner_info(patterns) ⇒ Object
49 50 51 52 53 54 |
# File 'lib/code_owners.rb', line 49 def git_owner_info(patterns) make_utf8(raw_git_owner_info(patterns)).lines.map do |info| _, _exfile, line, pattern, file = info.strip.match(/^(.*):(\d*):(.*)\t(.*)$/).to_a [line, pattern, file] end end |
.log(message, opts = {}) ⇒ Object
141 142 143 |
# File 'lib/code_owners.rb', line 141 def log(, opts = {}) puts if opts[:log] end |
.ownerships(opts = {}) ⇒ Object
this maps the collection of ownership patterns and owners to actual files
19 20 21 22 23 24 25 26 27 28 |
# File 'lib/code_owners.rb', line 19 def ownerships(opts = {}) log("Calculating ownerships for #{opts.inspect}", opts) patowns = pattern_owners(codeowners_data(opts), opts) if opts[:no_git] files = files_to_own(opts) ownerships_by_ruby(patowns, files, opts) else ownerships_by_gitignore(patowns, opts) end end |
.ownerships_by_gitignore(patterns, opts = {}) ⇒ Object
gitignore approach
34 35 36 37 38 39 40 41 42 43 44 45 46 47 |
# File 'lib/code_owners.rb', line 34 def ownerships_by_gitignore(patterns, opts = {}) git_owner_info(patterns.map { |p| p[0] }).map do |line, pattern, file| if line.empty? { file: file, owner: NO_OWNER, line: nil, pattern: nil } else { file: file, owner: patterns.fetch(line.to_i-1)[1], line: line, pattern: pattern } end end end |
.ownerships_by_ruby(patowns, files, opts = {}) ⇒ Object
ruby approach
81 82 83 84 85 86 87 88 89 90 91 92 93 94 |
# File 'lib/code_owners.rb', line 81 def ownerships_by_ruby(patowns, files, opts = {}) pattern_list = build_ruby_patterns(patowns, opts) unowned = { owner: NO_OWNER, line: nil, pattern: nil } files.map do |file| last_match = nil # have a flag to go through in reverse order as potential optimization? # really depends on the data pattern_list.each do |p| last_match = p if p[:pattern_regex].match(file) end (last_match || unowned).dup.tap{|h| h[:file] = file } end end |
.pattern_owners(codeowner_data, opts = {}) ⇒ Object
read the github file and spit out a slightly formatted list of patterns and their owners Empty/invalid/commented lines are still included in order to preserve line numbering
117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 |
# File 'lib/code_owners.rb', line 117 def pattern_owners(codeowner_data, opts = {}) patterns = [] codeowner_data.split("\n").each_with_index do |line, i| stripped_line = line.strip if stripped_line == "" || stripped_line.start_with?("#") patterns << ['', ''] # Comment / empty line elsif stripped_line.start_with?("!") # unsupported per github spec log("Parse error line #{(i+1).to_s}: \"#{line}\"", opts) patterns << ['', ''] elsif stripped_line.match(CODEOWNER_PATTERN) patterns << [$1, $2] else log("Parse error line #{(i+1).to_s}: \"#{line}\"", opts) patterns << ['', ''] end end patterns end |
.raw_git_owner_info(patterns) ⇒ Object
IN: an array of gitignore* check-ignore compliant patterns OUT: a check-ignore formatted string for each file in the repo
sadly you can’t tell ls-files to ignore tracked files via an arbitrary pattern file so we jump through some hacky git-fu hoops
-c “core.quotepath=off” ls-files -z # prevent quoting the path and null-terminate each line to assist with matching stuff with spaces -c “core.excludesfiles=somefile” # tells git to use this as our gitignore pattern source check-ignore # debug gitignore / exclude files –no-index # don’t look in the index when checking, can be used to debug why a path became tracked -v # verbose, outputs details about the matching pattern (if any) for each given pathname -n # non-matching, shows given paths which don’t match any pattern
69 70 71 72 73 74 75 |
# File 'lib/code_owners.rb', line 69 def raw_git_owner_info(patterns) Tempfile.open('codeowner_patterns') do |file| file.write(patterns.join("\n")) file.rewind `cd #{current_repo_path} && git -c \"core.quotepath=off\" ls-files -z | xargs -0 -- git -c \"core.quotepath=off\" -c \"core.excludesfile=#{file.path}\" check-ignore --no-index -v -n` end end |