pmdtester 1.0.1 → 1.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 +4 -4
- data/.ci/build.sh +91 -0
- data/.ci/files/env.gpg +1 -0
- data/.github/workflows/build.yml +39 -0
- data/.gitignore +9 -0
- data/.hoerc +1 -1
- data/.rubocop.yml +6 -2
- data/.ruby-version +1 -0
- data/History.md +40 -0
- data/Manifest.txt +24 -9
- data/README.rdoc +45 -30
- data/Rakefile +5 -3
- data/config/all-java.xml +1 -1
- data/config/design.xml +1 -1
- data/config/projectlist_1_0_0.xsd +2 -1
- data/config/projectlist_1_1_0.xsd +31 -0
- data/lib/pmdtester.rb +8 -7
- data/lib/pmdtester/builders/liquid_renderer.rb +73 -0
- data/lib/pmdtester/builders/pmd_report_builder.rb +102 -78
- data/lib/pmdtester/builders/project_builder.rb +100 -0
- data/lib/pmdtester/builders/project_hasher.rb +126 -0
- data/lib/pmdtester/builders/rule_set_builder.rb +92 -47
- data/lib/pmdtester/builders/simple_progress_logger.rb +4 -4
- data/lib/pmdtester/builders/summary_report_builder.rb +62 -131
- data/lib/pmdtester/collection_by_file.rb +55 -0
- data/lib/pmdtester/parsers/options.rb +19 -0
- data/lib/pmdtester/parsers/pmd_report_document.rb +74 -29
- data/lib/pmdtester/parsers/projects_parser.rb +2 -4
- data/lib/pmdtester/pmd_branch_detail.rb +29 -19
- data/lib/pmdtester/pmd_configerror.rb +23 -24
- data/lib/pmdtester/pmd_error.rb +34 -34
- data/lib/pmdtester/pmd_report_detail.rb +9 -12
- data/lib/pmdtester/pmd_tester_utils.rb +55 -0
- data/lib/pmdtester/pmd_violation.rb +66 -28
- data/lib/pmdtester/project.rb +21 -48
- data/lib/pmdtester/report_diff.rb +179 -111
- data/lib/pmdtester/resource_locator.rb +4 -0
- data/lib/pmdtester/runner.rb +66 -64
- data/pmdtester.gemspec +27 -36
- data/resources/_includes/diff_pill_row.html +6 -0
- data/resources/css/bootstrap.min.css +7 -0
- data/resources/css/datatables.min.css +36 -0
- data/resources/css/pmd-tester.css +131 -0
- data/resources/js/bootstrap.min.js +7 -0
- data/resources/js/code-snippets.js +66 -0
- data/resources/js/datatables.min.js +726 -0
- data/resources/js/jquery-3.2.1.slim.min.js +4 -0
- data/resources/js/jquery.min.js +2 -0
- data/resources/js/popper.min.js +5 -0
- data/resources/js/project-report.js +136 -0
- data/resources/project_diff_report.html +205 -0
- data/resources/project_index.html +102 -0
- metadata +64 -20
- data/.travis.yml +0 -40
- data/lib/pmdtester/builders/diff_builder.rb +0 -31
- data/lib/pmdtester/builders/diff_report/configerrors.rb +0 -50
- data/lib/pmdtester/builders/diff_report/errors.rb +0 -71
- data/lib/pmdtester/builders/diff_report/violations.rb +0 -77
- data/lib/pmdtester/builders/diff_report_builder.rb +0 -99
- data/lib/pmdtester/builders/html_report_builder.rb +0 -56
- data/resources/css/maven-base.css +0 -155
- data/resources/css/maven-theme.css +0 -171
@@ -0,0 +1,55 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module PmdTester
|
4
|
+
# A collection of things, grouped by file.
|
5
|
+
#
|
6
|
+
# (Note: this replaces PmdErrors and PmdViolations)
|
7
|
+
class CollectionByFile
|
8
|
+
def initialize
|
9
|
+
# a hash of filename -> [list of items]
|
10
|
+
@hash = Hash.new([])
|
11
|
+
@total = 0
|
12
|
+
end
|
13
|
+
|
14
|
+
def add_all(filename, values)
|
15
|
+
return if values.empty?
|
16
|
+
|
17
|
+
if @hash.key?(filename)
|
18
|
+
@hash[filename].concat(values)
|
19
|
+
else
|
20
|
+
@hash[filename] = values
|
21
|
+
end
|
22
|
+
@total += values.size
|
23
|
+
end
|
24
|
+
|
25
|
+
def total_size
|
26
|
+
@total
|
27
|
+
end
|
28
|
+
|
29
|
+
def all_files
|
30
|
+
@hash.keys
|
31
|
+
end
|
32
|
+
|
33
|
+
def num_files
|
34
|
+
@hash.size
|
35
|
+
end
|
36
|
+
|
37
|
+
def all_values
|
38
|
+
@hash.values.flatten
|
39
|
+
end
|
40
|
+
|
41
|
+
def each_value(&block)
|
42
|
+
@hash.each_value do |vs|
|
43
|
+
vs.each(&block)
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
def [](fname)
|
48
|
+
@hash[fname]
|
49
|
+
end
|
50
|
+
|
51
|
+
def to_h
|
52
|
+
@hash
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
@@ -16,6 +16,7 @@ module PmdTester
|
|
16
16
|
SINGLE = 'single'
|
17
17
|
DEFAULT_CONFIG_PATH = ResourceLocator.locate('config/all-java.xml')
|
18
18
|
DEFAULT_LIST_PATH = ResourceLocator.locate('config/project-list.xml')
|
19
|
+
DEFAULT_BASELINE_URL_PREFIX = 'https://sourceforge.net/projects/pmd/files/pmd-regression-tester/'
|
19
20
|
|
20
21
|
attr_reader :local_git_repo
|
21
22
|
attr_reader :base_branch
|
@@ -30,6 +31,9 @@ module PmdTester
|
|
30
31
|
attr_reader :auto_config_flag
|
31
32
|
attr_reader :debug_flag
|
32
33
|
attr_accessor :filter_set
|
34
|
+
attr_reader :keep_reports
|
35
|
+
attr_reader :error_recovery
|
36
|
+
attr_reader :baseline_download_url_prefix
|
33
37
|
|
34
38
|
def initialize(argv)
|
35
39
|
options = parse(argv)
|
@@ -46,6 +50,14 @@ module PmdTester
|
|
46
50
|
@auto_config_flag = options[:a]
|
47
51
|
@debug_flag = options[:d]
|
48
52
|
@filter_set = nil
|
53
|
+
@keep_reports = options.keep_reports?
|
54
|
+
@error_recovery = options.error_recovery?
|
55
|
+
url = options[:baseline_download_url]
|
56
|
+
@baseline_download_url_prefix = if url[-1] == '/'
|
57
|
+
url
|
58
|
+
else
|
59
|
+
"#{url}/"
|
60
|
+
end
|
49
61
|
|
50
62
|
# if the 'config' option is selected then `config` overrides `base_config` and `patch_config`
|
51
63
|
@base_config = @config if !@config.nil? && @mode == 'local'
|
@@ -88,8 +100,15 @@ module PmdTester
|
|
88
100
|
o.bool '-a', '--auto-gen-config',
|
89
101
|
'whether to generate configurations automatically based on branch differences,' \
|
90
102
|
'this option only works in online and local mode'
|
103
|
+
o.bool '--keep-reports',
|
104
|
+
'whether to keep old reports and skip running PMD again if possible'
|
91
105
|
o.bool '-d', '--debug',
|
92
106
|
'whether change log level to DEBUG to see more information'
|
107
|
+
o.bool '--error-recovery',
|
108
|
+
'enable error recovery mode when executing PMD. Might help to analyze errors.'
|
109
|
+
o.string '--baseline-download-url',
|
110
|
+
'download url prefix from where to download the baseline in online mode',
|
111
|
+
default: DEFAULT_BASELINE_URL_PREFIX
|
93
112
|
o.on '-v', '--version' do
|
94
113
|
puts VERSION
|
95
114
|
exit
|
@@ -8,80 +8,125 @@ module PmdTester
|
|
8
8
|
attr_reader :violations
|
9
9
|
attr_reader :errors
|
10
10
|
attr_reader :configerrors
|
11
|
+
attr_reader :infos_by_rules
|
12
|
+
|
11
13
|
def initialize(branch_name, working_dir, filter_set = nil)
|
12
|
-
@violations =
|
13
|
-
@errors =
|
14
|
-
@configerrors =
|
14
|
+
@violations = CollectionByFile.new
|
15
|
+
@errors = CollectionByFile.new
|
16
|
+
@configerrors = Hash.new { |hash, key| hash[key] = [] }
|
17
|
+
|
18
|
+
@infos_by_rules = {}
|
15
19
|
@current_violations = []
|
16
20
|
@current_violation = nil
|
17
21
|
@current_error = nil
|
18
22
|
@current_configerror = nil
|
19
|
-
@current_element = ''
|
20
|
-
@filename = ''
|
21
23
|
@filter_set = filter_set
|
22
24
|
@working_dir = working_dir
|
23
25
|
@branch_name = branch_name
|
26
|
+
|
27
|
+
@cur_text = String.new(capacity: 200)
|
24
28
|
end
|
25
29
|
|
26
30
|
def start_element(name, attrs = [])
|
27
31
|
attrs = attrs.to_h
|
28
|
-
@current_element = name
|
29
32
|
|
30
33
|
case name
|
31
34
|
when 'file'
|
32
|
-
|
33
|
-
@current_filename = remove_work_dir!(attrs['name'])
|
35
|
+
handle_start_file attrs
|
34
36
|
when 'violation'
|
35
|
-
|
37
|
+
handle_start_violation attrs
|
36
38
|
when 'error'
|
37
|
-
|
38
|
-
remove_work_dir!(attrs['msg'])
|
39
|
-
@current_error = PmdError.new(attrs, @branch_name)
|
39
|
+
handle_start_error attrs
|
40
40
|
when 'configerror'
|
41
|
-
|
41
|
+
handle_start_configerror attrs
|
42
42
|
end
|
43
43
|
end
|
44
44
|
|
45
|
-
def
|
46
|
-
|
45
|
+
def characters(string)
|
46
|
+
@cur_text << remove_work_dir!(string)
|
47
47
|
end
|
48
48
|
|
49
|
-
def
|
50
|
-
@
|
49
|
+
def cdata_block(string)
|
50
|
+
@cur_text << remove_work_dir!(string)
|
51
51
|
end
|
52
52
|
|
53
53
|
def end_element(name)
|
54
54
|
case name
|
55
55
|
when 'file'
|
56
|
-
@violations.
|
56
|
+
@violations.add_all(@current_filename, @current_violations)
|
57
57
|
@current_filename = nil
|
58
58
|
when 'violation'
|
59
|
-
@current_violation
|
60
|
-
|
59
|
+
if match_filter_set?(@current_violation)
|
60
|
+
@current_violation.message = finish_text!
|
61
|
+
@current_violations.push(@current_violation)
|
62
|
+
end
|
61
63
|
@current_violation = nil
|
62
64
|
when 'error'
|
63
|
-
@
|
65
|
+
@current_error.stack_trace = finish_text!
|
66
|
+
@errors.add_all(@current_filename, [@current_error])
|
64
67
|
@current_filename = nil
|
65
68
|
@current_error = nil
|
66
69
|
when 'configerror'
|
67
|
-
@configerrors.
|
70
|
+
@configerrors[@current_configerror.rulename].push(@current_configerror)
|
68
71
|
@current_configerror = nil
|
69
72
|
end
|
73
|
+
@cur_text.clear
|
70
74
|
end
|
71
75
|
|
72
|
-
|
73
|
-
|
74
|
-
|
76
|
+
private
|
77
|
+
|
78
|
+
# Modifies the string in place and returns it
|
79
|
+
# (this is what sub! does, except it returns nil if no replacement occurred)
|
80
|
+
def remove_work_dir!(str)
|
81
|
+
str.sub!(/#{@working_dir}/, '')
|
82
|
+
str
|
83
|
+
end
|
84
|
+
|
85
|
+
def finish_text!
|
86
|
+
res = @cur_text.strip!.dup.freeze
|
87
|
+
@cur_text.clear
|
88
|
+
res
|
75
89
|
end
|
76
90
|
|
77
91
|
def match_filter_set?(violation)
|
78
92
|
return true if @filter_set.nil?
|
79
93
|
|
80
|
-
|
81
|
-
|
82
|
-
|
94
|
+
ruleset_attr = violation.ruleset_name.delete(' ').downcase! << '.xml'
|
95
|
+
return true if @filter_set.include?(ruleset_attr)
|
96
|
+
|
97
|
+
rule_ref = "#{ruleset_attr}/#{violation.rule_name}"
|
98
|
+
|
99
|
+
@filter_set.include?(rule_ref)
|
100
|
+
end
|
101
|
+
|
102
|
+
def handle_start_file(attrs)
|
103
|
+
@current_filename = remove_work_dir!(attrs['name'])
|
104
|
+
@current_violations = []
|
105
|
+
end
|
106
|
+
|
107
|
+
def handle_start_violation(attrs)
|
108
|
+
@current_violation = PmdViolation.new(
|
109
|
+
branch: @branch_name,
|
110
|
+
fname: @current_filename,
|
111
|
+
info_url: attrs['externalInfoUrl'],
|
112
|
+
bline: attrs['beginline'].to_i,
|
113
|
+
rule_name: attrs['rule'],
|
114
|
+
ruleset_name: attrs['ruleset'].freeze
|
115
|
+
)
|
116
|
+
end
|
117
|
+
|
118
|
+
def handle_start_error(attrs)
|
119
|
+
@current_filename = remove_work_dir!(attrs['filename'])
|
120
|
+
|
121
|
+
@current_error = PmdError.new(
|
122
|
+
branch: @branch_name,
|
123
|
+
filename: @current_filename,
|
124
|
+
short_message: remove_work_dir!(attrs['msg'])
|
125
|
+
)
|
126
|
+
end
|
83
127
|
|
84
|
-
|
128
|
+
def handle_start_configerror(attrs)
|
129
|
+
@current_configerror = PmdConfigError.new(attrs, @branch_name)
|
85
130
|
end
|
86
131
|
end
|
87
132
|
end
|
@@ -11,9 +11,7 @@ module PmdTester
|
|
11
11
|
document = Nokogiri::XML(File.read(list_file))
|
12
12
|
|
13
13
|
errors = schema.validate(document)
|
14
|
-
unless errors.empty?
|
15
|
-
raise ProjectsParserException.new(errors), "Schema validate failed: In #{list_file}"
|
16
|
-
end
|
14
|
+
raise ProjectsParserException.new(errors), "Schema validate failed: In #{list_file}" unless errors.empty?
|
17
15
|
|
18
16
|
projects = []
|
19
17
|
document.xpath('//project').each do |project|
|
@@ -23,7 +21,7 @@ module PmdTester
|
|
23
21
|
end
|
24
22
|
|
25
23
|
def schema_file_path
|
26
|
-
ResourceLocator.locate('config/
|
24
|
+
ResourceLocator.locate('config/projectlist_1_1_0.xsd')
|
27
25
|
end
|
28
26
|
end
|
29
27
|
|
@@ -12,8 +12,9 @@ module PmdTester
|
|
12
12
|
attr_accessor :branch_name
|
13
13
|
# The branch's execution time on all standard projects
|
14
14
|
attr_accessor :execution_time
|
15
|
-
|
16
|
-
|
15
|
+
attr_accessor :jdk_version
|
16
|
+
attr_accessor :language
|
17
|
+
attr_accessor :pull_request
|
17
18
|
|
18
19
|
def self.branch_filename(branch_name)
|
19
20
|
branch_name&.tr('/', '_')
|
@@ -28,24 +29,29 @@ module PmdTester
|
|
28
29
|
@execution_time = 0
|
29
30
|
# the result of command 'java -version' is going to stderr
|
30
31
|
@jdk_version = Cmd.stderr_of('java -version')
|
31
|
-
@language = ENV['LANG']
|
32
|
+
@language = ENV['LANG'] # the locale
|
33
|
+
|
34
|
+
prnum = ENV[PR_NUM_ENV_VAR]
|
35
|
+
@pull_request = prnum == 'false' ? nil : prnum
|
32
36
|
end
|
33
37
|
|
34
|
-
def load
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
38
|
+
def self.load(branch_name, logger)
|
39
|
+
details = PmdBranchDetail.new(branch_name)
|
40
|
+
if File.exist?(details.path_to_save_file)
|
41
|
+
hash = JSON.parse(File.read(details.path_to_save_file))
|
42
|
+
details.branch_last_sha = hash['branch_last_sha']
|
43
|
+
details.branch_last_message = hash['branch_last_message']
|
44
|
+
details.branch_name = hash['branch_name']
|
45
|
+
details.execution_time = hash['execution_time']
|
46
|
+
details.jdk_version = hash['jdk_version']
|
47
|
+
details.language = hash['language']
|
48
|
+
details.pull_request = hash['pull_request']
|
43
49
|
else
|
44
|
-
|
45
|
-
|
46
|
-
logger
|
50
|
+
details.jdk_version = ''
|
51
|
+
details.language = ''
|
52
|
+
logger&.warn "#{details.path_to_save_file} doesn't exist!"
|
47
53
|
end
|
48
|
-
|
54
|
+
details
|
49
55
|
end
|
50
56
|
|
51
57
|
def save
|
@@ -54,14 +60,18 @@ module PmdTester
|
|
54
60
|
branch_name: @branch_name,
|
55
61
|
execution_time: @execution_time,
|
56
62
|
jdk_version: @jdk_version,
|
57
|
-
language: @language
|
58
|
-
|
63
|
+
language: @language,
|
64
|
+
pull_request: @pull_request }
|
65
|
+
|
66
|
+
FileUtils.mkdir_p(@base_branch_dir) unless File.directory?(@base_branch_dir)
|
67
|
+
|
68
|
+
file = File.new(path_to_save_file, 'w')
|
59
69
|
file.puts JSON.generate(hash)
|
60
70
|
file.close
|
61
71
|
self
|
62
72
|
end
|
63
73
|
|
64
|
-
def
|
74
|
+
def path_to_save_file
|
65
75
|
"#{@base_branch_dir}/branch_info.json"
|
66
76
|
end
|
67
77
|
|
@@ -1,28 +1,6 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
module PmdTester
|
4
|
-
# This class is used to store pmd config errors and its size.
|
5
|
-
class PmdConfigErrors
|
6
|
-
attr_reader :errors
|
7
|
-
attr_reader :size
|
8
|
-
|
9
|
-
def initialize
|
10
|
-
# key:rulename as String => value:PmdConfigError Array
|
11
|
-
@errors = {}
|
12
|
-
@size = 0
|
13
|
-
end
|
14
|
-
|
15
|
-
def add_error(error)
|
16
|
-
rulename = error.rulename
|
17
|
-
if @errors.key?(rulename)
|
18
|
-
@errors[rulename].push(error)
|
19
|
-
else
|
20
|
-
@errors.store(rulename, [error])
|
21
|
-
end
|
22
|
-
@size += 1
|
23
|
-
end
|
24
|
-
end
|
25
|
-
|
26
4
|
# This class represents a 'configerror' element of Pmd xml report
|
27
5
|
# and which Pmd branch the 'configerror' is from
|
28
6
|
class PmdConfigError
|
@@ -35,13 +13,13 @@ module PmdTester
|
|
35
13
|
# <xs:attribute name="msg" type="xs:string" use="required" />
|
36
14
|
# </xs:complexType>
|
37
15
|
attr_reader :attrs
|
38
|
-
attr_accessor :
|
16
|
+
attr_accessor :old_error
|
39
17
|
|
40
18
|
def initialize(attrs, branch)
|
41
19
|
@attrs = attrs
|
42
20
|
|
21
|
+
@changed = false
|
43
22
|
@branch = branch
|
44
|
-
@text = ''
|
45
23
|
end
|
46
24
|
|
47
25
|
def rulename
|
@@ -52,10 +30,31 @@ module PmdTester
|
|
52
30
|
@attrs['msg']
|
53
31
|
end
|
54
32
|
|
33
|
+
def sort_key
|
34
|
+
rulename
|
35
|
+
end
|
36
|
+
|
37
|
+
def changed?
|
38
|
+
@changed
|
39
|
+
end
|
40
|
+
|
55
41
|
def eql?(other)
|
56
42
|
rulename.eql?(other.rulename) && msg.eql?(other.msg)
|
57
43
|
end
|
58
44
|
|
45
|
+
def try_merge?(other)
|
46
|
+
if branch != BASE &&
|
47
|
+
branch != other.branch &&
|
48
|
+
rulename == other.rulename &&
|
49
|
+
!changed? # not already changed
|
50
|
+
@changed = true
|
51
|
+
@old_error = other
|
52
|
+
true
|
53
|
+
end
|
54
|
+
|
55
|
+
false
|
56
|
+
end
|
57
|
+
|
59
58
|
def hash
|
60
59
|
[rulename, msg].hash
|
61
60
|
end
|
data/lib/pmdtester/pmd_error.rb
CHANGED
@@ -1,30 +1,10 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
module PmdTester
|
4
|
-
# This class is used to store pmd errors and its size.
|
5
|
-
class PmdErrors
|
6
|
-
attr_reader :errors
|
7
|
-
attr_reader :errors_size
|
8
|
-
|
9
|
-
def initialize
|
10
|
-
# key:filename as String => value:PmdError Array
|
11
|
-
@errors = {}
|
12
|
-
@errors_size = 0
|
13
|
-
end
|
14
|
-
|
15
|
-
def add_error_by_filename(filename, error)
|
16
|
-
if @errors.key?(filename)
|
17
|
-
@errors[filename].push(error)
|
18
|
-
else
|
19
|
-
@errors.store(filename, [error])
|
20
|
-
end
|
21
|
-
@errors_size += 1
|
22
|
-
end
|
23
|
-
end
|
24
|
-
|
25
4
|
# This class represents a 'error' element of Pmd xml report
|
26
5
|
# and which Pmd branch the 'error' is from
|
27
6
|
class PmdError
|
7
|
+
include PmdTester
|
28
8
|
# The pmd branch type, 'base' or 'patch'
|
29
9
|
attr_reader :branch
|
30
10
|
|
@@ -37,31 +17,51 @@ module PmdTester
|
|
37
17
|
# </xs:extension>
|
38
18
|
# </xs:simpleContent>
|
39
19
|
# </xs:complexType>
|
40
|
-
|
41
|
-
attr_accessor :
|
42
|
-
|
43
|
-
def initialize(attrs, branch)
|
44
|
-
@attrs = attrs
|
20
|
+
attr_accessor :stack_trace
|
21
|
+
attr_accessor :old_error
|
22
|
+
attr_reader :filename, :short_message
|
45
23
|
|
24
|
+
def initialize(branch:, filename:, short_message:)
|
46
25
|
@branch = branch
|
47
|
-
@
|
26
|
+
@stack_trace = ''
|
27
|
+
@changed = false
|
28
|
+
@short_message = short_message
|
29
|
+
@filename = filename
|
48
30
|
end
|
49
31
|
|
50
|
-
def
|
51
|
-
|
32
|
+
def short_filename
|
33
|
+
filename.gsub(%r{([^/]*+/)+}, '')
|
52
34
|
end
|
53
35
|
|
54
|
-
def
|
55
|
-
@
|
36
|
+
def changed?
|
37
|
+
@changed
|
56
38
|
end
|
57
39
|
|
58
40
|
def eql?(other)
|
59
|
-
filename.eql?(other.filename) &&
|
60
|
-
|
41
|
+
filename.eql?(other.filename) &&
|
42
|
+
short_message.eql?(other.short_message) &&
|
43
|
+
stack_trace.eql?(other.stack_trace)
|
61
44
|
end
|
62
45
|
|
63
46
|
def hash
|
64
|
-
[filename,
|
47
|
+
[filename, stack_trace].hash
|
48
|
+
end
|
49
|
+
|
50
|
+
def sort_key
|
51
|
+
filename
|
52
|
+
end
|
53
|
+
|
54
|
+
def try_merge?(other)
|
55
|
+
if branch != BASE &&
|
56
|
+
branch != other.branch &&
|
57
|
+
filename == other.filename &&
|
58
|
+
!changed? # not already changed
|
59
|
+
@changed = true
|
60
|
+
@old_error = other
|
61
|
+
true
|
62
|
+
else
|
63
|
+
false
|
64
|
+
end
|
65
65
|
end
|
66
66
|
end
|
67
67
|
end
|