gitolemy 0.0.4

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.
@@ -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