git-reviewer 0.1.0

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