git-reviewer 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: 26093e3ace921d56e1cc429eab08775761614af03564508982f0a5f2b1490b8b
4
+ data.tar.gz: f7caa02138858499bc212b4ecf3e34d4d66a6a5edb6b11c9a844716c757dc85d
5
+ SHA512:
6
+ metadata.gz: d8dc85d6db503350493a26338cd55666486034a8de07eb630d566629b5267e0a10ef13190f460811a1b8c2ced1a221c6dbca95229aee26d5b324c917684b2edf
7
+ data.tar.gz: fdcefb88e454e9d49407475975454f25868f96fdd39ad041ef02fd3f9f5009cfdf82b8efcf5cdfe047988f2efe2dddf8ee2dcbddbb1016cece0eed948aa0b84b
data/.solargraph.yml ADDED
@@ -0,0 +1,33 @@
1
+ ---
2
+ include:
3
+ - "**/*.rb"
4
+ exclude:
5
+ - spec/**/*
6
+ - test/**/*
7
+ - vendor/**/*
8
+ - ".bundle/**/*"
9
+ require: []
10
+ domains: []
11
+ reporters:
12
+ - rubocop
13
+ - require_not_found
14
+ formatter:
15
+ rubocop:
16
+ cops: safe
17
+ except: []
18
+ only: []
19
+ extra_args: []
20
+ require_paths: []
21
+ plugins: []
22
+ max_files: 5000
23
+
24
+ interpreter:
25
+ name: "ruby"
26
+ version: "2.6.3"
27
+
28
+ require_paths:
29
+ - "lib"
30
+
31
+ gems:
32
+ - "claide"
33
+ - "rspec"
@@ -0,0 +1,14 @@
1
+ {
2
+ "version": "0.2.0",
3
+ "configurations": [
4
+ {
5
+ "type": "rdbg",
6
+ "name": "Debug",
7
+ "request": "launch",
8
+ "cwd": "${workspaceFolder}",
9
+ "command": "bundle exec ruby",
10
+ "script": "${workspaceFolder}/exe/git-reviewer",
11
+ "args": ["--target=main", "--verbose"]
12
+ }
13
+ ]
14
+ }
data/Gemfile ADDED
@@ -0,0 +1,13 @@
1
+ # frozen_string_literal: true
2
+
3
+ #source "https://rubygems.org"
4
+ source "https://gems.ruby-china.com/"
5
+
6
+ # Specify your gem's dependencies in git-reviewer.gemspec
7
+ gemspec
8
+
9
+ gem "rake", "~> 13.0"
10
+ gem "rspec", "~> 3.0"
11
+ gem 'debug'
12
+ gem "solargraph"
13
+ gem "terminal-table"
data/Gemfile.lock ADDED
@@ -0,0 +1,109 @@
1
+ PATH
2
+ remote: .
3
+ specs:
4
+ git-reviewer (0.1.0)
5
+ claide (~> 1.0.3)
6
+ terminal-table
7
+
8
+ GEM
9
+ remote: https://gems.ruby-china.com/
10
+ specs:
11
+ ast (2.4.2)
12
+ backport (1.2.0)
13
+ benchmark (0.3.0)
14
+ claide (1.0.3)
15
+ debug (1.8.0)
16
+ irb (>= 1.5.0)
17
+ reline (>= 0.3.1)
18
+ diff-lcs (1.5.1)
19
+ e2mmap (0.1.0)
20
+ io-console (0.7.2)
21
+ irb (1.6.3)
22
+ reline (>= 0.3.0)
23
+ jaro_winkler (1.5.6)
24
+ json (2.7.1)
25
+ kramdown (2.4.0)
26
+ rexml
27
+ kramdown-parser-gfm (1.1.0)
28
+ kramdown (~> 2.0)
29
+ nokogiri (1.13.10-arm64-darwin)
30
+ racc (~> 1.4)
31
+ nokogiri (1.13.10-x86_64-darwin)
32
+ racc (~> 1.4)
33
+ parallel (1.24.0)
34
+ parser (3.3.0.5)
35
+ ast (~> 2.4.1)
36
+ racc
37
+ racc (1.7.3)
38
+ rainbow (3.1.1)
39
+ rake (13.1.0)
40
+ rbs (2.8.4)
41
+ regexp_parser (2.9.0)
42
+ reline (0.5.0)
43
+ io-console (~> 0.5)
44
+ reverse_markdown (2.1.1)
45
+ nokogiri
46
+ rexml (3.2.6)
47
+ rspec (3.13.0)
48
+ rspec-core (~> 3.13.0)
49
+ rspec-expectations (~> 3.13.0)
50
+ rspec-mocks (~> 3.13.0)
51
+ rspec-core (3.13.0)
52
+ rspec-support (~> 3.13.0)
53
+ rspec-expectations (3.13.0)
54
+ diff-lcs (>= 1.2.0, < 2.0)
55
+ rspec-support (~> 3.13.0)
56
+ rspec-mocks (3.13.0)
57
+ diff-lcs (>= 1.2.0, < 2.0)
58
+ rspec-support (~> 3.13.0)
59
+ rspec-support (3.13.1)
60
+ rubocop (1.50.2)
61
+ json (~> 2.3)
62
+ parallel (~> 1.10)
63
+ parser (>= 3.2.0.0)
64
+ rainbow (>= 2.2.2, < 4.0)
65
+ regexp_parser (>= 1.8, < 3.0)
66
+ rexml (>= 3.2.5, < 4.0)
67
+ rubocop-ast (>= 1.28.0, < 2.0)
68
+ ruby-progressbar (~> 1.7)
69
+ unicode-display_width (>= 2.4.0, < 3.0)
70
+ rubocop-ast (1.30.0)
71
+ parser (>= 3.2.1.0)
72
+ ruby-progressbar (1.13.0)
73
+ solargraph (0.50.0)
74
+ backport (~> 1.2)
75
+ benchmark
76
+ bundler (~> 2.0)
77
+ diff-lcs (~> 1.4)
78
+ e2mmap
79
+ jaro_winkler (~> 1.5)
80
+ kramdown (~> 2.3)
81
+ kramdown-parser-gfm (~> 1.1)
82
+ parser (~> 3.0)
83
+ rbs (~> 2.0)
84
+ reverse_markdown (~> 2.0)
85
+ rubocop (~> 1.38)
86
+ thor (~> 1.0)
87
+ tilt (~> 2.0)
88
+ yard (~> 0.9, >= 0.9.24)
89
+ terminal-table (3.0.2)
90
+ unicode-display_width (>= 1.1.1, < 3)
91
+ thor (1.3.1)
92
+ tilt (2.3.0)
93
+ unicode-display_width (2.5.0)
94
+ yard (0.9.36)
95
+
96
+ PLATFORMS
97
+ arm64-darwin-22
98
+ x86_64-darwin-22
99
+
100
+ DEPENDENCIES
101
+ debug
102
+ git-reviewer!
103
+ rake (~> 13.0)
104
+ rspec (~> 3.0)
105
+ solargraph
106
+ terminal-table
107
+
108
+ BUNDLED WITH
109
+ 2.3.9
data/README.md ADDED
@@ -0,0 +1,37 @@
1
+ ![](https://chuquan-public-r-001.oss-cn-shanghai.aliyuncs.com/sketch-images/git-reviewer-02.png)
2
+
3
+ ![Platform](http://img.shields.io/badge/platform-macOS-blue.svg?style=flat)
4
+ ![Platform](http://img.shields.io/badge/platform-Linux-blue.svg?style=flat)
5
+ ![Platform](http://img.shields.io/badge/platform-Windows-blue.svg?style=flat)
6
+ ![Language](http://img.shields.io/badge/language-ruby-brightgreen.svg?style=flat)
7
+ ![Tool](http://img.shields.io/badge/tool-homebrew-orange.svg?style=flat)
8
+ ![License](http://img.shields.io/badge/license-MIT-red.svg?style=flat)
9
+ # Git::Reviewer
10
+
11
+ Welcome to your new gem! In this directory, you'll find the files you need to be able to package up your Ruby library into a gem. Put your Ruby code in the file `lib/git/reviewer`. To experiment with that code, run `bin/console` for an interactive prompt.
12
+
13
+ TODO: Delete this and the text above, and describe your gem
14
+
15
+ ## Installation
16
+
17
+ Install the gem and add to the application's Gemfile by executing:
18
+
19
+ $ bundle add git-reviewer
20
+
21
+ If bundler is not being used to manage dependencies, install the gem by executing:
22
+
23
+ $ gem install git-reviewer
24
+
25
+ ## Usage
26
+
27
+ TODO: Write usage instructions here
28
+
29
+ ## Development
30
+
31
+ After checking out the repo, run `bin/setup` to install dependencies. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
32
+
33
+ To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release`, which will create a git tag for the version, push git commits and the created tag, and push the `.gem` file to [rubygems.org](https://rubygems.org).
34
+
35
+ ## Contributing
36
+
37
+ Bug reports and pull requests are welcome on GitHub at https://github.com/[USERNAME]/git-reviewer.
data/Rakefile ADDED
@@ -0,0 +1,4 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "bundler/gem_tasks"
4
+ task default: %i[]
data/exe/git-reviewer ADDED
@@ -0,0 +1,5 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require_relative '../lib/gitreviewer'
4
+
5
+ GitReviewer::Command.run(ARGV)
@@ -0,0 +1,169 @@
1
+ require_relative '../analyze/blame_tree'
2
+ require_relative '../analyze/diff_tree'
3
+
4
+ module GitReviewer
5
+ class Myers
6
+ # Type: BlameFile
7
+ attr_accessor :source, :target
8
+
9
+ def initialize(source, target)
10
+ self.source = source
11
+ self.target = target
12
+ end
13
+
14
+ def resolve
15
+ # 字符串 a 和 b 的长度,分别为 n 和 m
16
+ m = source.blame_lines.count
17
+ n = target.blame_lines.count
18
+
19
+ # 用于存储每条 K 线上最佳位置的 Map
20
+ v = { 1 => 0 }
21
+ # 用于存储所有深度的所有最佳位置的 Map,用于回溯编辑路径
22
+ vs = { 0 => { 1 => 0 } }
23
+
24
+ loop do
25
+ # 外层循环,遍历深度
26
+ (0..m + n).each do |d|
27
+ tmp = {}
28
+ # 内层循环,宽度优先搜索,遍历 K 线
29
+ (-d..d).step(2) do |k|
30
+ down = ((k == -d) || ((k != d) && v[k + 1] > v[k - 1]))
31
+ k_prev = down ? k + 1 : k - 1
32
+ # 获取移动的起点位置
33
+ x_start = v[k_prev]
34
+ y_start = x_start - k_prev
35
+ # 获取移动一步的中间位置,向右或向下
36
+ x_mid = down ? x_start : x_start + 1
37
+ y_mid = x_mid - k
38
+ # 获取移动的终点位置,后续可能会向右下移动。
39
+ x_end = x_mid
40
+ y_end = y_mid
41
+
42
+ # 向右下移动,深度始终不变
43
+ while x_end < m && y_end < n && source.blame_lines[x_end] == target.blame_lines[y_end]
44
+ x_end += 1
45
+ y_end += 1
46
+ end
47
+
48
+ # 记录对应 K 线所能达到的最佳位置
49
+ v[k] = x_end
50
+
51
+ tmp[k] = x_end
52
+
53
+ # 如果两个字符串均到达末端,表示找到了终点,可以结束查找
54
+ if x_end == m && y_end == n
55
+ vs[d] = tmp
56
+ # 生成最短编辑路径
57
+ snakes = route(vs, m, n, d)
58
+ # 打印最短编辑路径
59
+ result = build_diff(snakes)
60
+ return result
61
+ end
62
+ end
63
+ # 记录深度为 D 的所有 K 线的最佳位置
64
+ vs[d] = tmp
65
+ end
66
+ end
67
+ end
68
+
69
+ def route(vs, m, n, d)
70
+ snakes = []
71
+ # 定义位置结构
72
+ pos = { x: m, y: n }
73
+
74
+ # 回溯最短编辑路径
75
+ while d > 0
76
+ v = vs[d]
77
+ v_prev = vs[d - 1]
78
+
79
+ k = pos[:x] - pos[:y]
80
+ # 判断之前位置到当前位置最开始移动的方式,向下或向右
81
+ down = ((k == -d) || ((k != d) && (v_prev[k + 1] > v_prev[k - 1])))
82
+ k_prev = down ? k + 1 : k - 1
83
+
84
+ # 当前位置
85
+ x_end = v[k]
86
+ y_end = x_end - k
87
+
88
+ # 之前位置
89
+ x_start = v_prev[k_prev]
90
+ y_start = x_start - k_prev
91
+
92
+ # 中间走斜线时的起始位置
93
+ x_mid = down ? x_start : x_start + 1
94
+ y_mid = x_mid - k
95
+
96
+ snakes.unshift([x_start, x_mid, x_end])
97
+
98
+ pos[:x] = x_start
99
+ pos[:y] = y_start
100
+
101
+ d -= 1
102
+ end
103
+ snakes
104
+ end
105
+
106
+ def build_diff(snakes)
107
+ diff_result = []
108
+ y_offset = 0
109
+
110
+ snakes.each_with_index do |snake, index|
111
+ s = snake[0]
112
+ m = snake[1]
113
+ e = snake[2]
114
+
115
+ # 如果是第一个差异,并且差异的开始点不是字符串头(即两字符串在开始部分有相同子字符串)
116
+ if index === 0 && s != 0
117
+ # 所有相同字符,直到s
118
+ (0..s - 1).each do |j|
119
+ diff_line = DiffLine.new(source.blame_lines[s], target.blame_lines[y_offset], DiffLine::UNCHANGE)
120
+ diff_result.append(diff_line)
121
+ y_offset += 1
122
+ end
123
+ end
124
+ if m - s == 1
125
+ # 用红色打印删除的字符
126
+ diff_line = DiffLine.new(source.blame_lines[s], nil, DiffLine::DELETE)
127
+ diff_result.append(diff_line)
128
+ else
129
+ # 用绿色打印插入的字符
130
+ diff_line = DiffLine.new(nil, target.blame_lines[y_offset], DiffLine::ADD)
131
+ diff_result.append(diff_line)
132
+ y_offset += 1
133
+ end
134
+ # 相同的字符
135
+ (0..e - m - 1).each do |i|
136
+ diff_line = DiffLine.new(source.blame_lines[s], target.blame_lines[y_offset], DiffLine::UNCHANGE)
137
+ diff_result.append(diff_line)
138
+ y_offset += 1
139
+ end
140
+ end
141
+
142
+ # 确定属性
143
+ operation = DiffFile::UNKNOWN
144
+ binary = false
145
+ if source.exist? && target.exist?
146
+ operation = DiffFile::MODIFY
147
+ binary = target.binary?
148
+ elsif source.exist? && !target.exist?
149
+ operation = DiffFile::DELETE
150
+ binary = source.binary?
151
+ elsif !source.exist? && target.exist?
152
+ operation = DiffFile::ADD
153
+ binary = target.binary?
154
+ else
155
+ operation = DiffFile::UNKNOWN
156
+ binary = false
157
+ end
158
+
159
+ # 确定结果
160
+ result = DiffFile.new(source.file_name, diff_result, operation, binary)
161
+
162
+ # # 打印内容
163
+ result.print_meta_info
164
+ Printer.verbose_put "#{result.format_line_diff}"
165
+
166
+ return result
167
+ end
168
+ end
169
+ end
@@ -0,0 +1,172 @@
1
+ require 'open3'
2
+ require_relative 'blame_tree'
3
+ require_relative 'builder'
4
+ require_relative '../utils/printer'
5
+ require_relative '../utils/checker'
6
+ require_relative '../config/configuration'
7
+ require_relative 'result_item'
8
+
9
+ module GitReviewer
10
+
11
+
12
+ class Analyzer
13
+ attr_accessor :source_branch # source branch
14
+ attr_accessor :target_branch # target branch
15
+ attr_accessor :builder # blame builder
16
+
17
+ attr_accessor :author_results #
18
+ attr_accessor :reviewer_results #
19
+ attr_accessor :configuration # 配置信息 Configuration
20
+
21
+ def initialize(source_branch, target_branch)
22
+ @source_branch = source_branch
23
+ @target_branch = target_branch
24
+ @author_results = Hash.new
25
+ @reviewer_results = Hash.new
26
+ end
27
+
28
+
29
+ def setup_builder
30
+ # 构建 BlameTree
31
+ @builder = Builder.new(@source_branch, @target_branch)
32
+ @builder.build
33
+
34
+ if @builder.source_blame.blame_files.count == 0
35
+ Printer.warning "Warning: no blame file for source blame branch<#{@source_branch}>"
36
+ exit 1
37
+ end
38
+
39
+ if @builder.target_blame.blame_files.count == 0
40
+ Printer.warning "Warning: no blame file for target blame branch<#{@target_branch}>"
41
+ exit 1
42
+ end
43
+
44
+ if @builder.source_blame.blame_files.count != @builder.target_blame.blame_files.count
45
+ Printer.red "Error: internal error. The number of files is not equal."
46
+ exit 1
47
+ end
48
+
49
+ if @builder.diff_files == nil
50
+ Printer.red "Error: internal error. The diff files of builder is nil."
51
+ exit 1
52
+ end
53
+ end
54
+
55
+ def setup_configuration
56
+ file_name = ".gitreviewer.yml"
57
+ file_exist = File.exist?(file_name)
58
+ # 检测配置文件
59
+ if !file_exist
60
+ Printer.red "Error: `.gitreviewer.yml` not exist in current directory. Please execute `git reviewer --init` first."
61
+ exit 1
62
+ end
63
+ # 解析配置文件
64
+ data = YAML.load_file(file_name)
65
+ @configuration = Configuration.new(data['project_owner'], data['folder_owner'], data['file_owner'], data['ignore_files'], data['ignore_folders'])
66
+ end
67
+
68
+ def execute
69
+ setup_builder
70
+ setup_configuration
71
+ analyze_author
72
+ analyze_reviewer
73
+ end
74
+
75
+ def analyze_author
76
+ @builder.diff_files.each do |fdiff|
77
+ fdiff.diff_lines.each_with_index do |ldiff, index|
78
+ record_author(fdiff, ldiff)
79
+ end
80
+ end
81
+ end
82
+
83
+ def analyze_reviewer
84
+ @builder.diff_files.each do |fdiff|
85
+ reviewer = nil
86
+ lines = 0
87
+
88
+ # 其他操作按行计算权重
89
+ fdiff.diff_lines.each_with_index do |ldiff, index|
90
+ if ldiff.operation == DiffLine::DELETE
91
+ # 删除行: 由原作者 review
92
+ reviewer = ldiff.source_line.user
93
+ record_reviewer(fdiff, reviewer, 1)
94
+ elsif ldiff.operation == DiffLine::ADD
95
+ # 新增行
96
+ if reviewer != nil
97
+ # 紧随删除,由删除行的原作者 review
98
+ lines += 1
99
+ else
100
+ # 非紧随删除,由 configuration 决定谁来 review
101
+ reviewer = @configuration.reviewer_of_file(fdiff.file_name)
102
+ record_reviewer(fdiff, reviewer, 1)
103
+ end
104
+ else
105
+ # 未变化
106
+ if reviewer != nil
107
+ # 处理编辑类型
108
+ record_reviewer(fdiff, reviewer, lines)
109
+ reviewer = nil
110
+ lines = 0
111
+ end
112
+ end
113
+
114
+ # 最后一行
115
+ if index == fdiff.diff_lines.count - 1 && reviewer != nil
116
+ record_reviewer(fdiff, reviewer, lines)
117
+ end
118
+ end
119
+ end
120
+ end
121
+
122
+ def record_author(fdiff, ldiff)
123
+ file_name = fdiff.file_name
124
+ if @configuration.is_ignore?(file_name)
125
+ return
126
+ end
127
+
128
+ author = ""
129
+ if ldiff.operation == DiffLine::DELETE
130
+ # 删除类型,记录为原始作者
131
+ author = ldiff.source_line.user
132
+ else
133
+ # 新增类型,记录为最新作者
134
+ author = ldiff.target_line.user
135
+ end
136
+
137
+ item = @author_results[author]
138
+ if item == nil
139
+ item = ResultItem.new(author)
140
+ end
141
+ item.add_file_name(file_name)
142
+ item.add_line_count(1)
143
+ @author_results[author] = item
144
+ end
145
+
146
+ def record_reviewer(fdiff, reviewer, lines)
147
+ if reviewer == nil
148
+ return
149
+ end
150
+ file_name = fdiff.file_name
151
+ if @configuration.is_ignore?(file_name)
152
+ return
153
+ end
154
+
155
+ item = @reviewer_results[reviewer]
156
+ if item == nil
157
+ item = ResultItem.new(reviewer)
158
+ end
159
+ item.add_file_name(file_name)
160
+ item.add_line_count(lines)
161
+ @reviewer_results[reviewer] = item
162
+ end
163
+
164
+ def print_author_result
165
+ print @author_results
166
+ end
167
+
168
+ def print_reviewer_result
169
+ print @reviewer_results
170
+ end
171
+ end
172
+ end
@@ -0,0 +1,68 @@
1
+ require 'digest'
2
+
3
+ module GitReviewer
4
+ # BlameBranch
5
+ class BlameBranch
6
+ attr_accessor :branch
7
+ attr_accessor :blame_files
8
+
9
+ def initialize(branch, blame_files)
10
+ @branch = branch
11
+ @blame_files = blame_files
12
+ end
13
+ end
14
+
15
+ # BlameFile
16
+ class BlameFile
17
+ attr_accessor :file_name
18
+ attr_accessor :blame_lines
19
+
20
+ attr_writer :exist
21
+ attr_writer :binary
22
+
23
+ # 文件是否存在
24
+ def exist?
25
+ @exist
26
+ end
27
+
28
+ # 文件是否是二进制
29
+ def binary?
30
+ @binary
31
+ end
32
+
33
+ def initialize(file_name, blame_lines, exist, binary)
34
+ @file_name = file_name
35
+ @blame_lines = blame_lines
36
+ @exist = exist
37
+ @binary = binary
38
+ end
39
+ end
40
+
41
+ # BlameLine
42
+ class BlameLine
43
+ attr_accessor :hash
44
+ attr_accessor :user
45
+ attr_accessor :date
46
+ attr_accessor :line
47
+ attr_accessor :code
48
+ attr_accessor :description
49
+
50
+ def initialize(hash, user, date, line, code)
51
+ @hash = hash
52
+ @user = user
53
+ @date = date
54
+ @line = line
55
+ @code = code
56
+ @description = code
57
+ end
58
+
59
+ # 用于 Myers 中进行判等操作
60
+ def ==(other)
61
+ other.is_a?(BlameLine) && other.code == @code
62
+ end
63
+
64
+ def format_line
65
+ format('%5d', line)
66
+ end
67
+ end
68
+ end