gitolemy 0.0.4

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,65 @@
1
+ require "active_support/core_ext/string"
2
+
3
+ class Diff
4
+
5
+ attr_accessor :insertions
6
+ attr_accessor :deletions
7
+ attr_accessor :delete_start
8
+ attr_accessor :delete_count
9
+ attr_accessor :insert_start
10
+ attr_accessor :insert_count
11
+
12
+ def initialize(attrs={})
13
+ attrs.each do |key, val|
14
+ instance_variable_set("@#{key}".to_sym, val)
15
+ end
16
+ end
17
+
18
+ private
19
+
20
+ class << self
21
+ def from_git(diff_lines)
22
+ Diff.new(parse_diff(diff_lines))
23
+ end
24
+
25
+ def parse_diff(diff_lines)
26
+ stats = diff_lines
27
+ .first
28
+ .split("@@")[1]
29
+ .split(" ")
30
+
31
+ delete = stats.first.split(",")
32
+ add = stats.last.split(",")
33
+
34
+ delete_start = delete.first[1..-1].to_i
35
+ delete_count = delete.length > 1 ? delete.last.to_i : 1
36
+ add_start = add.first[1..-1].to_i
37
+ add_count = add.length > 1 ? add.last.to_i : 1
38
+
39
+ diffs = diff_lines[1..-1]
40
+ patch = {
41
+ insertions: [],
42
+ deletions: [],
43
+ delete_start: delete_start,
44
+ delete_count: delete_count,
45
+ insert_start: add_start,
46
+ insert_count: add_count
47
+ }
48
+
49
+ diffs.reduce(patch, &method(:fold_lines_standard))
50
+ end
51
+
52
+ def fold_lines_standard(acc, diff_line)
53
+ diff = diff_line
54
+ line = diff_line[1..-1]
55
+
56
+ if diff.first == "+"
57
+ acc[:insertions] << line
58
+ elsif diff.first == "-"
59
+ acc[:deletions] << line
60
+ end
61
+
62
+ acc
63
+ end
64
+ end
65
+ end
@@ -0,0 +1,98 @@
1
+ require_relative "util"
2
+ require_relative "diff"
3
+
4
+ class FileDiff
5
+ DIFF_REGEX = /^@@ -\d+(,\d+)? \+\d+(,\d+)? @@/
6
+ FILE_INDEX_REGEX = /index [0-9a-f]{7,40}/
7
+ BINARY_REGEX = /^Binary files \/dev\/null and/
8
+ DELETION_REGEX = /\[-.*?-\]/
9
+ INSERTION_REGEX = /\{\+.*?\+\}/
10
+ DIFF_STAT_REGEX = /^\d+\t\d+\t/
11
+ DELETE_REGEX = /$\[-.*?-\]$/
12
+ DELETE_OP_REGEX = /^deleted file/
13
+
14
+ attr_accessor :operation
15
+ attr_accessor :a_file_name
16
+ attr_accessor :b_file_name
17
+ attr_accessor :a_file_id
18
+ attr_accessor :b_file_id
19
+ attr_accessor :diffs
20
+ attr_accessor :insertions_total
21
+ attr_accessor :deletions_total
22
+ attr_accessor :changes_total
23
+
24
+ def initialize(attrs={})
25
+ attrs.each do |key, val|
26
+ instance_variable_set("@#{key}".to_sym, val)
27
+ end
28
+ end
29
+
30
+ class << self
31
+ def from_git(file_diff_lines)
32
+ FileDiff.new(parse_file_diff(file_diff_lines))
33
+ end
34
+
35
+ # TODO: Handle different file modes like new, move, and delete.
36
+ def parse_file_diff(file_diff_lines)
37
+ file_names = file_diff_lines
38
+ .first
39
+ .sub("diff --git a/", "")
40
+ .split(" b/")
41
+
42
+ a_file_name = file_names.first.strip()
43
+ b_file_name = file_names.last.strip()
44
+
45
+ file_index = file_diff_lines
46
+ .each_index
47
+ .detect { |i| file_diff_lines[i].match(FILE_INDEX_REGEX) }
48
+
49
+ if file_index.nil?
50
+ a_file_id = nil
51
+ b_file_id = nil
52
+ else
53
+ a_file_id, b_file_id = file_diff_lines[file_index]
54
+ .sub("index ", "")
55
+ .split(" ")
56
+ .first
57
+ .split("..")
58
+ end
59
+
60
+ if file_diff_lines.last.match(BINARY_REGEX)
61
+ operation = :binary
62
+ elsif (a_file_name != b_file_name)
63
+ operation = :move
64
+ elsif (file_diff_lines[1].match(DELETE_OP_REGEX))
65
+ operation = :delete
66
+ else
67
+ operation = :change
68
+ end
69
+
70
+ diff_index = file_diff_lines
71
+ .each_index
72
+ .detect { |i| file_diff_lines[i].match(DIFF_REGEX) }
73
+
74
+ if diff_index.nil?
75
+ diffs = []
76
+ else
77
+ diffs = file_diff_lines[diff_index..-1]
78
+ .reduce([], &fold_reducer(DIFF_REGEX))
79
+ .flat_map { |diff_lines| Diff.from_git(diff_lines) }
80
+ end
81
+
82
+ insertions = diffs.map { |diff| diff.insert_count }.reduce(0, :+)
83
+ deletions = diffs.map { |diff| diff.delete_count }.reduce(0, :+)
84
+
85
+ {
86
+ operation: operation,
87
+ a_file_name: a_file_name,
88
+ b_file_name: b_file_name,
89
+ a_file_id: a_file_id,
90
+ b_file_id: b_file_id,
91
+ diffs: diffs,
92
+ insertions_total: insertions,
93
+ deletions_total: deletions,
94
+ changes_total: insertions + deletions
95
+ }
96
+ end
97
+ end
98
+ end
@@ -0,0 +1,58 @@
1
+ require "byebug"
2
+
3
+ require_relative "loggr"
4
+
5
+ module FileHelper
6
+ def fdiff_stat(diff)
7
+ "@@ -#{diff.delete_start},#{diff.delete_count} +#{diff.insert_start},#{diff.insert_count} @@"
8
+ end
9
+
10
+ def print_context(lines, from, to=nil)
11
+ if to.nil?
12
+ to = from + 3
13
+ from -= 4
14
+ end
15
+ (from...to).each do |i|
16
+ line = lines[i]
17
+ line = "nil" if line.nil?
18
+ puts "#{i + 1}: #{line}"
19
+ end
20
+ nil
21
+ end
22
+
23
+ def compare(commit, file_name, lines, context=nil, diff=nil)
24
+ if diff.nil?
25
+ Loggr.instance.info("COMPARE COMMIT: #{commit.id}")
26
+ else
27
+ Loggr.instance.info("COMPARE fdiff: #{fdiff_stat(diff)}")
28
+ end
29
+
30
+ real_file = `git show #{commit.id}:#{file_name}`
31
+ .encode("UTF-8", invalid: :replace)
32
+ .lines
33
+ .map(&:chomp)
34
+
35
+ return if real_file.length == 0
36
+ if real_file.length != lines.length && diff.nil?
37
+ puts "FILE SIZE MISMATCH:"
38
+ puts "REAL: #{real_file.length}"
39
+ puts "RECONSTRUCT: #{lines.length}"
40
+ byebug
41
+ 1
42
+ end
43
+
44
+ lines
45
+ .each_with_index
46
+ .map do |line, line_number|
47
+ if real_file[line_number] != line.gsub(/\r$/, "")
48
+ puts "MISMATCH: #{commit.id}:#{file_name}"
49
+ puts "REAL:"
50
+ print_context(real_file, line_number)
51
+ puts "RECONSTRUCT"
52
+ print_context(lines, line_number)
53
+ byebug
54
+ 1
55
+ end
56
+ end
57
+ end
58
+ end
@@ -0,0 +1,116 @@
1
+ require "active_support/core_ext/hash"
2
+
3
+ require_relative "virtual_file_system"
4
+ require_relative "file_helper"
5
+ require_relative "loggr"
6
+ require_relative "store"
7
+
8
+ class FileManager
9
+ include FileHelper
10
+
11
+ attr_reader :vfs
12
+
13
+ def initialize(branch)
14
+ @branch = branch
15
+ @vfs = VirtualFileSystem.new()
16
+ end
17
+
18
+ def apply!(commits, meta)
19
+ vfs.load!(commits, meta)
20
+ .each do |commit|
21
+ # TODO: Won't work for first commit...
22
+ coverage = meta[:coverage].get_report(commit.id, @vfs.file_paths)
23
+ errors = meta[:errors]&.get_errors!(commit) || []
24
+ apply_commit!(commit, errors, coverage)
25
+ Cache.index_commit(@branch, commit.id)
26
+ end
27
+ end
28
+
29
+ private
30
+
31
+ def apply_commit!(commit, errors, coverage)
32
+ Loggr.instance.info("APPLY COMMIT: #{commit.id}")
33
+
34
+ movements = extract_changes(commit.movements)
35
+ changes = extract_changes(commit.changes)
36
+
37
+ commit.trees.each { |diff_tree| @vfs.touch_tree(diff_tree, commit) }
38
+
39
+ files = commit
40
+ .file_diffs
41
+ .map { |file_name, file_diff| apply_file_diff!(commit, file_diff) }
42
+
43
+ changes.each(&method(:merge_change!))
44
+ movements.each(&method(:merge_movement!))
45
+
46
+ merge_coverage!(coverage) if not coverage.nil?
47
+ errors.each { |error| apply_error!(error) }
48
+
49
+ @vfs.save(commit)
50
+ files.each(&:save)
51
+ end
52
+
53
+ def apply_file_diff!(commit, file_diff)
54
+ file = @vfs.file_for_diff(commit, file_diff)
55
+ file.apply_file_diff!(commit, file_diff)
56
+ compare(commit, file.path, file.plain_text(), nil, nil) if compare?(file)
57
+ file
58
+ end
59
+
60
+ def apply_error!(error)
61
+ error[:stack_trace].each_with_index do |trace, i|
62
+ file = trace_file(trace[:file])
63
+ if file.nil?
64
+ Loggr.instance.error("Trace Not Found: #{trace[:file]}:#{trace[:line]} - #{trace[:message]}")
65
+ else
66
+ file.merge_error!(error, trace, i) if not file.nil?
67
+ end
68
+ end
69
+ rescue
70
+ Loggr.instance.error("CANNOT APPLLY ERROR: #{error[:error_id]}")
71
+ end
72
+
73
+ def extract_changes(changes)
74
+ changes.reduce({}) do |acc, (dest_file, changes)|
75
+ changes.each do |dest_line, change|
76
+ acc[dest_file] ||= {}
77
+ acc[dest_file][dest_line] = @vfs[change[:from]].lines[change[:line]]
78
+ end
79
+ acc
80
+ end
81
+ end
82
+
83
+ def merge_change!(file, file_line_changes)
84
+ file_line_changes.each do |(line_number, line_change)|
85
+ @vfs[file].change_line!(line_change, line_number)
86
+ end
87
+ end
88
+
89
+ def merge_movement!(file, file_line_movements)
90
+ file_line_movements.each do |(line_number, line_movement)|
91
+ @vfs[file].move_line!(line_movement, line_number)
92
+ end
93
+ end
94
+
95
+ # TODO: Technically, file lines aren't covered. Lines at commits are.
96
+ # 1: To avoid ambiguity, maybe only view trees by commit.
97
+ def merge_coverage!(coverage)
98
+ coverage.each do |file, file_coverage|
99
+ begin
100
+ @vfs[file].merge_coverage!(file_coverage)
101
+ @vfs[file].save()
102
+ rescue => ex
103
+ Loggr.instance.error("Coverage File Not Found: #{file}")
104
+ end
105
+ end
106
+ end
107
+
108
+ def trace_file(trace_file)
109
+ match = @vfs.file_paths.detect { |app_file| trace_file.include?(app_file) }
110
+ match.nil? ? nil : @vfs[match]
111
+ end
112
+
113
+ def compare?(file)
114
+ ENV["GITOLEMY_COMPARE"] == "true" && !file.binary?
115
+ end
116
+ end
@@ -0,0 +1,111 @@
1
+ class CSyntaxTracer
2
+ def trace(code)
3
+ code = strip_comments(code)
4
+ find_funcs(code)
5
+ .map { |func| define_func(func, code) }
6
+ rescue
7
+ []
8
+ end
9
+
10
+ private
11
+
12
+ def find_funcs(code)
13
+ funcs = code.scan(/[a-zA-Z \t]*function\s+[A-Za-z_][A-Za-z0-9_]*/)
14
+ funcs += code.scan(/[a-zA-Z0-9_. \t]*\s*=\s*function/)
15
+
16
+ funcs = funcs
17
+ .each_with_index
18
+ .map do |func, index|
19
+ if func.match(/[a-zA-Z0-9_]*\s+=/)
20
+ name = func
21
+ .split("=")
22
+ .last(2)
23
+ .first
24
+ .split(/[ \t.]/)
25
+ .last
26
+ else
27
+ name = func.split(" ").last
28
+ end
29
+
30
+ [name] + code
31
+ .lines
32
+ .each_with_index
33
+ .select { |line, line_num| line.match(func) }
34
+ .first
35
+ end
36
+
37
+ funcs
38
+ .each_with_index
39
+ .map do |func, index|
40
+ if funcs.count > index + 2
41
+ func << funcs[index + 1].last
42
+ else
43
+ func << code.lines.count
44
+ end
45
+ func
46
+ end
47
+ end
48
+
49
+ def define_func(func, code)
50
+ function_code = code.lines[func[2]...func[3]]
51
+ indent = function_code
52
+ .first
53
+ .match(/^\s*/)[0]
54
+
55
+ close = function_code
56
+ .each_with_index
57
+ .select { |line, line_num| line.match(Regexp.compile("^#{indent}}")) }
58
+ .first
59
+
60
+ close ||= function_code
61
+ .each_with_index
62
+ .map { |line, line_num| [line.match(/}/), line_num] }
63
+ .select { |line| line.first }
64
+ .reverse
65
+ .first
66
+
67
+ end_line = close.nil? ? func[3] : func[2] + close.last + 1
68
+
69
+ {
70
+ name: func.first,
71
+ start: func[2] + 1,
72
+ end: end_line
73
+ }
74
+ end
75
+
76
+ def strip_comments(code)
77
+ block_captures.each do |block_capture|
78
+ matches = code.scan(block_capture[:regex])
79
+ matches.each do |match|
80
+ code = code.gsub(match, "#{block_capture[:start]} #{block_capture[:type]}" + "\n" * (match.lines.count - 1) + block_capture[:end])
81
+ end
82
+ end
83
+
84
+ line_captures.each do |line_capture|
85
+ code = code.gsub(line_capture[:regex], "#{line_capture[:start]} #{line_capture[:type]}")
86
+ end
87
+
88
+ code
89
+ end
90
+
91
+ def line_captures()
92
+ [
93
+ {
94
+ regex: /\/\/.*/,
95
+ type: "LINE COMMENT",
96
+ start: "//"
97
+ }
98
+ ]
99
+ end
100
+
101
+ def block_captures()
102
+ [
103
+ {
104
+ regex: /\/\*.*?\*\//m,
105
+ type: "BLOCK QUOTE",
106
+ start: "/*",
107
+ end: "*/"
108
+ }
109
+ ]
110
+ end
111
+ end