Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[WIP] [POC] Add linter to CLI #22

Open
wants to merge 4 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 19 additions & 1 deletion lib/manageiq/style/cli.rb
Original file line number Diff line number Diff line change
Expand Up @@ -17,12 +17,16 @@ def parse_cli_options
synopsis "\nThe ManageIQ community's style configuration utility."
version "v#{ManageIQ::Style::VERSION}\n"

opt :install, "Install or update the style configurations", :default => false, :required => true
opt :format, "TODO: How to format the linter output", :default => "emacs"
opt :install, "Install or update the style configurations", :default => false
opt :lint, "Run linters for current changes", :default => false
opt :linters, "Linters to run for current changes", :default => ["rubocop"]
end
end

def run
install if @opts[:install]
lint if @opts[:lint]
end

def install
Expand All @@ -38,6 +42,20 @@ def install
update_gem_source
end

def lint
require 'more_core_extensions/all'

require 'manageiq/style/git_service/branch'
require 'manageiq/style/git_service/diff'

require 'manageiq/style/linter/base'
require 'manageiq/style/linter/haml'
require 'manageiq/style/linter/rubocop'
require 'manageiq/style/linter/yaml'

Linter::Rubocop.new(ARGV).run
end

private

def check_for_codeclimate_channel
Expand Down
158 changes: 158 additions & 0 deletions lib/manageiq/style/git_service/branch.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,158 @@
require 'rugged'

module ManageIQ
module Style
module GitService
class Branch
attr_reader :branch_options

def initialize(branch_options = {})
@branch_options = branch_options
end

def name
@branch_options[:name] || rugged_repo.head.name.sub(/^refs\/heads\//, '')
end

def pull_request?
!!@branch_options[:pull_request]
end

def merge_target
@branch_options[:merge_target]
end

def content_at(path, merged = false)
blob_at(path, merged).try(:content)
end

def permission_for(path, merged = false)
git_perm = blob_data_for(path, merged).to_h[:filemode]

# Since git stores file permissions in a different format:
#
# https://unix.stackexchange.com/a/450488
#
# Convert what we get from Rugged to something that will work with File
#
# git_perm = 0o100755
# git_perm.to_s(8)
# #=> "100755"
# _.[-3,3]
# #=> "755"
# _.to_i(8)
# #=> 493
# 0o755
# #=> 493
#
git_perm ? git_perm.to_s(8)[-3,3].to_i(8) : 0o644
end

def diff(merge_target = nil)
Diff.new(target_for_reference(merge_target_ref_name(merge_target)).diff(merge_tree))
end

def exists?
rugged_repo.branches.exists?(ref_name)
end

def mergeable?
merge_tree
true
rescue UnmergeableError
false
end

def merge_base(merge_target = nil)
rugged_repo.merge_base(target_for_reference(merge_target_ref_name(merge_target)), target_for_reference(ref_name))
end

def merge_index(merge_target = nil) # Rugged::Index for a merge of this branch
rugged_repo.merge_commits(target_for_reference(merge_target_ref_name(merge_target)), target_for_reference(ref_name))
end

def merge_tree # Rugged::Tree object for the merge of this branch
tree_ref = merge_index.write_tree(rugged_repo)
rugged_repo.lookup(tree_ref)
rescue Rugged::IndexError
raise UnmergeableError
ensure
# Rugged seems to allocate large C structures, but not many Ruby objects,
# and thus doesn't trigger a GC, so we will trigger one manually.
GC.start
end

def tip_commit
target_for_reference(ref_name)
end

def tip_tree
tip_commit.tree
end

def target_for_reference(reference) # Rugged::Commit for a given refname i.e. "refs/remotes/origin/master"
rugged_repo.references[reference].target
end

def tip_files
list_files_in_tree(tip_tree.oid)
end

def commit_ids_since(starting_point)
range_walker(starting_point).collect(&:oid).reverse
end

def commit(commit_oid)
Commit.new(rugged_repo, commit_oid)
end

private

def list_files_in_tree(rugged_tree_oid, current_path = Pathname.new(""))
rugged_repo.lookup(rugged_tree_oid).each_with_object([]) do |i, files|
full_path = current_path.join(i[:name])
case i[:type]
when :blob
files << full_path.to_s
when :tree
files.concat(list_files_in_tree(i[:oid], full_path))
end
end
end

def range_walker(walk_start, walk_end = ref_name)
Rugged::Walker.new(rugged_repo).tap do |walker|
walker.push_range("#{walk_start}..#{walk_end}")
end
end

def ref_name
return "refs/#{name}" if name.include?("prs/")
"refs/remotes/origin/#{name}"
end

def merge_target_ref_name(other_merge_target = nil)
ref = other_merge_target || merge_target
"refs/remotes/origin/#{ref}"
end

# Rugged::Blob object for a given file path on this branch
def blob_at(path, merged = false)
blob = Rugged::Blob.lookup(rugged_repo, blob_data_for(path, merged)[:oid])
blob.type == :blob ? blob : nil
rescue Rugged::TreeError
nil
end

def blob_data_for(path, merged = false)
source = merged ? merge_tree : tip_tree
source.path(path)
end

def rugged_repo
@rugged_repo ||= Rugged::Repository.new(branch_options[:repo_path])
end
end
end
end
end
57 changes: 57 additions & 0 deletions lib/manageiq/style/git_service/diff.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
module ManageIQ
module Style
module GitService
class Diff
attr_reader :raw_diff
def initialize(raw_diff)
@raw_diff = raw_diff
end

def new_files
raw_diff.deltas.collect { |delta| delta.try(:new_file).try(:[], :path) }.compact
end

def with_each_patch
raw_diff.patches.each { |patch| yield(patch) }
end

def with_each_hunk
with_each_patch do |patch|
patch.hunks.each { |hunk| yield(hunk, patch) }
end
end

def with_each_line
with_each_hunk do |hunk, parent_patch|
hunk.lines.each { |line| yield(line, hunk, parent_patch) }
end
end

def file_status
raw_diff.patches.each_with_object({}) do |patch, h|
new_file = patch.delta.new_file.try(:[], :path)
if new_file
additions = h.fetch_path(new_file, :additions) || 0
h.store_path(new_file, :additions, (additions + patch.additions))
end

old_file = patch.delta.old_file.try(:[], :path)
if old_file
deletions = h.fetch_path(old_file, :deletions) || 0
h.store_path(new_file, :deletions, (deletions + patch.deletions))
end
end
end

def status_summary
changed, added, deleted = raw_diff.stat
[
changed.positive? ? "#{changed} #{"file".pluralize(changed)} changed" : nil,
added.positive? ? "#{added} #{"insertion".pluralize(added)}(+)" : nil,
deleted.positive? ? "#{deleted} #{"deletion".pluralize(deleted)}(-)" : nil
].compact.join(", ")
end
end
end
end
end
7 changes: 7 additions & 0 deletions lib/manageiq/style/git_service/error.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
module ManageIQ
module Style
module GitService
Error = Class.new(StandardError)
end
end
end
7 changes: 7 additions & 0 deletions lib/manageiq/style/git_service/unmergeable_error.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
module ManageIQ
module Style
module GitService
UnmergeableError = Class.new(Error)
end
end
end
Loading