git_ownership_insights 1.2.0 → 2.0.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/.github/CODEOWNERS +2 -0
- data/.rubocop.yml +14 -0
- data/.rubocop_todo.yml +58 -0
- data/Gemfile.lock +1 -1
- data/bin/git_ownership_insights +2 -2
- data/lib/git_ownership_insights/git_ownership_insight.rb +46 -33
- data/lib/git_ownership_insights/version.rb +2 -2
- data/lib/git_ownership_insights.rb +1 -1
- data/sig/git_ownership_insights.rbs +1 -1
- data/spec/fixtures/file.wrong +0 -0
- data/spec/fixtures/file1.swift +333 -0
- data/spec/fixtures/file2.kt +243 -0
- data/spec/fixtures/file3.swift +330 -0
- data/spec/fixtures/file4.swift +235 -0
- data/spec/fixtures/ignore.kt +0 -0
- data/spec/git_ownership_insights_spec.rb +86 -5
- metadata +16 -1
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: e323781f21d5b045b9fe23e6790f53739fe6bd99e917a9ad2b88c544a9aa3428
|
4
|
+
data.tar.gz: 0c23bd13ad86a4edc00f38a77e5219d18ebd81af84dc8e76764762611780fb1e
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 7302774074f86ca8f5ebd3288f47150a7cc9adf642b2dfe3bc60f0f0ff087a38f9645789b256e923fc578b5d445293be72dcc143d07906a86c6a678e8742a1c4
|
7
|
+
data.tar.gz: 97f58091878d741eac46b1c0a0eb8263cbe7cc7abdba5f8db2c70bf2d12fe4dc0f089496a8e8b7b87531c9bfa4bdb4577820b75afa50576eec9e30184bbdd723
|
data/.github/CODEOWNERS
ADDED
data/.rubocop.yml
ADDED
@@ -0,0 +1,14 @@
|
|
1
|
+
inherit_from: .rubocop_todo.yml
|
2
|
+
|
3
|
+
# The behavior of RuboCop can be controlled via the .rubocop.yml
|
4
|
+
# configuration file. It makes it possible to enable/disable
|
5
|
+
# certain cops (checks) and to alter their behavior if they accept
|
6
|
+
# any parameters. The file can be placed either in your home
|
7
|
+
# directory or in some project directory.
|
8
|
+
#
|
9
|
+
# RuboCop will start looking for the configuration file in the directory
|
10
|
+
# where the inspected file is and continue its way up to the root directory.
|
11
|
+
#
|
12
|
+
# See https://docs.rubocop.org/rubocop/configuration
|
13
|
+
AllCops:
|
14
|
+
TargetRubyVersion: 3.2.1
|
data/.rubocop_todo.yml
ADDED
@@ -0,0 +1,58 @@
|
|
1
|
+
# This configuration was generated by
|
2
|
+
# `rubocop --auto-gen-config`
|
3
|
+
# on 2024-01-30 07:43:06 UTC using RuboCop version 1.60.0.
|
4
|
+
# The point is for the user to remove these configuration records
|
5
|
+
# one by one as the offenses are removed from the code base.
|
6
|
+
# Note that changes in the inspected code, or installation of new
|
7
|
+
# versions of RuboCop, may require this file to be generated again.
|
8
|
+
|
9
|
+
# Offense count: 4
|
10
|
+
# Configuration parameters: AllowedMethods, AllowedPatterns, CountRepeatedAttributes.
|
11
|
+
Metrics/AbcSize:
|
12
|
+
Max: 187
|
13
|
+
|
14
|
+
# Offense count: 1
|
15
|
+
# Configuration parameters: CountComments, CountAsOne, AllowedMethods, AllowedPatterns.
|
16
|
+
# AllowedMethods: refine
|
17
|
+
Metrics/BlockLength:
|
18
|
+
Max: 69
|
19
|
+
|
20
|
+
# Offense count: 1
|
21
|
+
# Configuration parameters: AllowedMethods, AllowedPatterns.
|
22
|
+
Metrics/CyclomaticComplexity:
|
23
|
+
Max: 31
|
24
|
+
|
25
|
+
# Offense count: 4
|
26
|
+
# Configuration parameters: CountComments, CountAsOne, AllowedMethods, AllowedPatterns.
|
27
|
+
Metrics/MethodLength:
|
28
|
+
Max: 115
|
29
|
+
|
30
|
+
# Offense count: 1
|
31
|
+
# Configuration parameters: CountComments, CountAsOne.
|
32
|
+
Metrics/ModuleLength:
|
33
|
+
Max: 192
|
34
|
+
|
35
|
+
# Offense count: 1
|
36
|
+
# Configuration parameters: AllowedMethods, AllowedPatterns.
|
37
|
+
Metrics/PerceivedComplexity:
|
38
|
+
Max: 32
|
39
|
+
|
40
|
+
# Offense count: 1
|
41
|
+
# Configuration parameters: AllowedConstants.
|
42
|
+
Style/Documentation:
|
43
|
+
Exclude:
|
44
|
+
- 'spec/**/*'
|
45
|
+
- 'test/**/*'
|
46
|
+
- 'lib/git_ownership_insights/git_ownership_insight.rb'
|
47
|
+
|
48
|
+
# Offense count: 1
|
49
|
+
Style/MultilineBlockChain:
|
50
|
+
Exclude:
|
51
|
+
- 'lib/git_ownership_insights/git_ownership_insight.rb'
|
52
|
+
|
53
|
+
# Offense count: 7
|
54
|
+
# This cop supports safe autocorrection (--autocorrect).
|
55
|
+
# Configuration parameters: AllowHeredoc, AllowURI, URISchemes, IgnoreCopDirectives, AllowedPatterns.
|
56
|
+
# URISchemes: http, https
|
57
|
+
Layout/LineLength:
|
58
|
+
Max: 150
|
data/Gemfile.lock
CHANGED
data/bin/git_ownership_insights
CHANGED
@@ -134,5 +134,5 @@ end
|
|
134
134
|
system("git checkout #{DEFAULT_BRANCH}", [:out] => File::NULL)
|
135
135
|
system('git pull', %i[out err] => File::NULL)
|
136
136
|
|
137
|
-
GitOwnershipInsights.
|
138
|
-
|
137
|
+
GitOwnershipInsights.new(duration_in_days: options[:duration_in_days] || 30, directory_path: REPO_PATH,
|
138
|
+
begin_time: DateTime.now, steps: options[:steps].to_i, debug: options[:debug]).contribution_message
|
@@ -1,11 +1,22 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
|
4
|
-
|
3
|
+
require 'pry'
|
4
|
+
require 'date'
|
5
|
+
|
6
|
+
class GitOwnershipInsights
|
7
|
+
def initialize(directory_path:, duration_in_days:, begin_time:, debug: nil, steps: 1)
|
8
|
+
@directory_path = directory_path
|
9
|
+
@duration_in_days = duration_in_days
|
10
|
+
@begin_time = begin_time
|
11
|
+
@debug = debug
|
12
|
+
@steps = steps
|
13
|
+
end
|
14
|
+
|
15
|
+
def true?(obj)
|
5
16
|
obj.to_s.downcase == 'true'
|
6
17
|
end
|
7
18
|
|
8
|
-
def
|
19
|
+
def read_codeowners_file
|
9
20
|
raise "CODEOWNERS file does not exist under #{CODEOWNERS_PATH}" unless File.exist?(CODEOWNERS_PATH)
|
10
21
|
|
11
22
|
codeowners = {}
|
@@ -20,7 +31,7 @@ module GitOwnershipInsights
|
|
20
31
|
codeowners
|
21
32
|
end
|
22
33
|
|
23
|
-
def
|
34
|
+
def find_owners(file_path, codeowners)
|
24
35
|
matching_patterns = codeowners.keys.select do |pattern|
|
25
36
|
pattern_regex = Regexp.new("^#{Regexp.escape(pattern.sub(%r{^/+}, '').chomp('/')).gsub('\*', '.*').gsub('**',
|
26
37
|
'.*?')}")
|
@@ -42,7 +53,7 @@ module GitOwnershipInsights
|
|
42
53
|
codeowners[best_match].split(' ')
|
43
54
|
end
|
44
55
|
|
45
|
-
def
|
56
|
+
def count_big_files(directory_path, size: BIG_FILE_SIZE)
|
46
57
|
size = size.to_i
|
47
58
|
# Get a list of all files in the specified directory
|
48
59
|
files = Dir.glob(File.join(directory_path, '**', '*')).select { |file| File.file?(file) }
|
@@ -65,7 +76,7 @@ module GitOwnershipInsights
|
|
65
76
|
puts " *Current(*) total number of code files longer than #{size} lines:* #{count}"
|
66
77
|
end
|
67
78
|
|
68
|
-
def
|
79
|
+
def count_hotspot_lines(files)
|
69
80
|
code_files = files.select do |f|
|
70
81
|
extension = File.extname(f)
|
71
82
|
valid_extensions = CODE_EXTENSIONS
|
@@ -83,55 +94,57 @@ module GitOwnershipInsights
|
|
83
94
|
puts " *Total lines of hotspot code:* #{count}"
|
84
95
|
end
|
85
96
|
|
86
|
-
def
|
97
|
+
def filter_existing_code_files(files)
|
87
98
|
files.select do |f|
|
88
99
|
next unless File.exist?(f)
|
89
100
|
|
101
|
+
if EXCLUDED_FILES
|
102
|
+
excluded_patterns = EXCLUDED_FILES.split(',')
|
103
|
+
next if excluded_patterns.any? { |pattern| f.include?(pattern) }
|
104
|
+
end
|
105
|
+
|
90
106
|
extension = File.extname(f)
|
91
107
|
valid_extensions = CODE_EXTENSIONS
|
92
108
|
valid_extensions.include?(extension)
|
93
109
|
end
|
94
110
|
end
|
95
111
|
|
96
|
-
def
|
112
|
+
def git_files(directory_path:)
|
97
113
|
`git ls-tree -r --name-only $(git rev-list -1 HEAD) -- "#{directory_path}"`
|
98
114
|
end
|
99
115
|
|
100
|
-
def
|
116
|
+
def files_with_changes(directory_path:, start_date:, end_date:)
|
101
117
|
`git log --name-only --pretty=format:"" --since="#{start_date}" --until="#{end_date}" "#{directory_path}"`
|
102
118
|
end
|
103
119
|
|
104
|
-
def
|
120
|
+
def git_commit_count(file:, start_date:, end_date:)
|
105
121
|
`git log --since="#{start_date}" --until="#{end_date}" --follow -- "#{file}" | grep -c '^commit'`
|
106
122
|
end
|
107
123
|
|
108
|
-
def
|
109
|
-
|
124
|
+
def git_commit_info(file:, start_date:, end_date:)
|
125
|
+
`git log --pretty=format:"%s" --since="#{start_date}" --until="#{end_date}" --follow -- "#{file}"`
|
126
|
+
end
|
127
|
+
|
128
|
+
def contribution_message
|
129
|
+
duration_in_days = @duration_in_days.to_i
|
110
130
|
all_teams = []
|
111
131
|
cross_teams_count = 0
|
112
132
|
single_ownership_teams_count = 0
|
113
133
|
files_changed_by_many_teams = 0
|
114
134
|
total_changes = 0
|
115
|
-
start_date = begin_time.to_time.to_i - duration_in_days * 86_400 - 30 * 86_400
|
116
|
-
end_date = begin_time.to_time.to_i - 30 * 86_400
|
117
|
-
git_ls = git_files(directory_path:)
|
135
|
+
start_date = @begin_time.to_time.to_i - duration_in_days * 86_400 - 30 * 86_400
|
136
|
+
end_date = @begin_time.to_time.to_i - 30 * 86_400
|
137
|
+
git_ls = git_files(directory_path: @directory_path)
|
118
138
|
file_count = filter_existing_code_files(git_ls.split).count
|
119
|
-
all_files_with_changes = files_with_changes(directory_path
|
139
|
+
all_files_with_changes = files_with_changes(directory_path: @directory_path, start_date:, end_date:).split.sort
|
120
140
|
code_files_with_changes = filter_existing_code_files(all_files_with_changes)
|
121
141
|
uniq_code_files_with_changes = code_files_with_changes.uniq
|
122
142
|
|
123
|
-
if EXCLUDED_FILES
|
124
|
-
excluded_patterns = EXCLUDED_FILES.split(',')
|
125
|
-
uniq_code_files_with_changes = uniq_code_files_with_changes.reject do |file|
|
126
|
-
excluded_patterns.any? { |pattern| file.include?(pattern) }
|
127
|
-
end
|
128
|
-
end
|
129
|
-
|
130
143
|
file_team_map = {}
|
131
144
|
uniq_code_files_with_changes.each do |file|
|
132
145
|
filename = File.basename(file)
|
133
146
|
commit_count = git_commit_count(file:, start_date:, end_date:).to_i
|
134
|
-
git_log =
|
147
|
+
git_log = git_commit_info(file:, start_date:, end_date:).split("\n")
|
135
148
|
teams = git_log.map do |team|
|
136
149
|
team.match(/#{TEAM_REGEX}/)[0].upcase
|
137
150
|
end.reject { |e| EXCLUSIONS&.include?(e) }
|
@@ -148,7 +161,7 @@ module GitOwnershipInsights
|
|
148
161
|
single_ownership_teams_count += 1
|
149
162
|
end
|
150
163
|
|
151
|
-
puts "\n#{filename} [#{commit_count}]:#{teams}\n" if debug
|
164
|
+
puts "\n#{filename} [#{commit_count}]:#{teams}\n" if @debug
|
152
165
|
end
|
153
166
|
|
154
167
|
occurrences = all_teams.flatten.compact.tally
|
@@ -169,7 +182,7 @@ module GitOwnershipInsights
|
|
169
182
|
filtered_top_touched_files = filtered_files.sort_by { |element, count| [-count.last, element] }
|
170
183
|
|
171
184
|
puts ''
|
172
|
-
puts "*Timeframe:* #{(begin_time - duration_in_days).strftime('%Y-%m-%d')} to #{begin_time.strftime('%Y-%m-%d')}"
|
185
|
+
puts "*Timeframe:* #{(@begin_time - duration_in_days).strftime('%Y-%m-%d')} to #{@begin_time.strftime('%Y-%m-%d')}"
|
173
186
|
puts " *Code files with a single contributor:* #{(100 - ((files_changed_by_many_teams.to_f / file_count) * 100)).round(2)}%"
|
174
187
|
puts " *Existing files changed by many teams:* #{files_changed_by_many_teams}"
|
175
188
|
puts " *Current existing #{CODE_EXTENSIONS} files:* #{file_count}"
|
@@ -184,7 +197,7 @@ module GitOwnershipInsights
|
|
184
197
|
puts " *#{CODE_EXTENSIONS} files exceeding #{BIG_FILE_SIZE} lines with multiple contributors:* #{filtered_top_touched_files.count}"
|
185
198
|
puts " *Total amount of commits to #{CODE_EXTENSIONS} files:* #{total_changes}"
|
186
199
|
puts " *Total #{CODE_EXTENSIONS} files changed:* #{uniq_code_files_with_changes.count}"
|
187
|
-
count_big_files(directory_path)
|
200
|
+
count_big_files(@directory_path)
|
188
201
|
puts " *Current(*) total of #{CODE_EXTENSIONS} files in the folder:* #{file_count}"
|
189
202
|
puts " *Contributors:* #{contributors}"
|
190
203
|
puts "* means that it the current(instant) repository value, all the other metrics are done over #{duration_in_days} days period"
|
@@ -193,7 +206,7 @@ module GitOwnershipInsights
|
|
193
206
|
puts "\n"
|
194
207
|
puts ' Hotspot changes:'
|
195
208
|
filtered_top_touched_files.each do |line|
|
196
|
-
puts " #{line.first.gsub(directory_path, '')} Contributors: #{line.last.first} Commits: #{line.last.last}"
|
209
|
+
puts " #{line.first.gsub(@directory_path, '')} Contributors: #{line.last.first} Commits: #{line.last.last}"
|
197
210
|
end
|
198
211
|
end
|
199
212
|
|
@@ -240,13 +253,13 @@ module GitOwnershipInsights
|
|
240
253
|
end
|
241
254
|
end
|
242
255
|
end
|
243
|
-
steps -= 1
|
256
|
+
@steps -= 1
|
244
257
|
|
245
|
-
return unless steps.positive?
|
258
|
+
return unless @steps.positive?
|
246
259
|
|
247
|
-
system("git checkout `git rev-list -1 --before='#{(begin_time - duration_in_days).strftime('%B %d %Y')}' HEAD`",
|
260
|
+
system("git checkout `git rev-list -1 --before='#{(@begin_time - duration_in_days).strftime('%B %d %Y')}' HEAD`",
|
248
261
|
%i[out err] => File::NULL)
|
249
|
-
|
250
|
-
|
262
|
+
@begin_time -= duration_in_days
|
263
|
+
contribution_message
|
251
264
|
end
|
252
265
|
end
|
File without changes
|
@@ -0,0 +1,333 @@
|
|
1
|
+
line
|
2
|
+
line
|
3
|
+
line
|
4
|
+
line
|
5
|
+
line
|
6
|
+
line
|
7
|
+
line
|
8
|
+
line
|
9
|
+
line
|
10
|
+
line
|
11
|
+
line
|
12
|
+
line
|
13
|
+
line
|
14
|
+
line
|
15
|
+
line
|
16
|
+
line
|
17
|
+
line
|
18
|
+
line
|
19
|
+
line
|
20
|
+
line
|
21
|
+
line
|
22
|
+
line
|
23
|
+
line
|
24
|
+
line
|
25
|
+
line
|
26
|
+
line
|
27
|
+
line
|
28
|
+
line
|
29
|
+
line
|
30
|
+
line
|
31
|
+
line
|
32
|
+
line
|
33
|
+
line
|
34
|
+
line
|
35
|
+
line
|
36
|
+
line
|
37
|
+
line
|
38
|
+
line
|
39
|
+
line
|
40
|
+
line
|
41
|
+
line
|
42
|
+
line
|
43
|
+
line
|
44
|
+
line
|
45
|
+
line
|
46
|
+
line
|
47
|
+
line
|
48
|
+
line
|
49
|
+
line
|
50
|
+
line
|
51
|
+
line
|
52
|
+
line
|
53
|
+
line
|
54
|
+
line
|
55
|
+
line
|
56
|
+
line
|
57
|
+
line
|
58
|
+
line
|
59
|
+
line
|
60
|
+
line
|
61
|
+
line
|
62
|
+
line
|
63
|
+
line
|
64
|
+
line
|
65
|
+
line
|
66
|
+
line
|
67
|
+
line
|
68
|
+
line
|
69
|
+
line
|
70
|
+
line
|
71
|
+
line
|
72
|
+
line
|
73
|
+
line
|
74
|
+
line
|
75
|
+
line
|
76
|
+
line
|
77
|
+
line
|
78
|
+
line
|
79
|
+
line
|
80
|
+
line
|
81
|
+
line
|
82
|
+
line
|
83
|
+
line
|
84
|
+
line
|
85
|
+
line
|
86
|
+
line
|
87
|
+
line
|
88
|
+
line
|
89
|
+
line
|
90
|
+
line
|
91
|
+
line
|
92
|
+
line
|
93
|
+
line
|
94
|
+
line
|
95
|
+
line
|
96
|
+
line
|
97
|
+
line
|
98
|
+
line
|
99
|
+
line
|
100
|
+
line
|
101
|
+
line
|
102
|
+
line
|
103
|
+
line
|
104
|
+
line
|
105
|
+
line
|
106
|
+
line
|
107
|
+
line
|
108
|
+
line
|
109
|
+
line
|
110
|
+
line
|
111
|
+
line
|
112
|
+
line
|
113
|
+
line
|
114
|
+
line
|
115
|
+
line
|
116
|
+
line
|
117
|
+
line
|
118
|
+
line
|
119
|
+
line
|
120
|
+
line
|
121
|
+
line
|
122
|
+
line
|
123
|
+
line
|
124
|
+
line
|
125
|
+
line
|
126
|
+
line
|
127
|
+
line
|
128
|
+
line
|
129
|
+
line
|
130
|
+
line
|
131
|
+
line
|
132
|
+
line
|
133
|
+
line
|
134
|
+
line
|
135
|
+
line
|
136
|
+
line
|
137
|
+
line
|
138
|
+
line
|
139
|
+
line
|
140
|
+
line
|
141
|
+
line
|
142
|
+
line
|
143
|
+
line
|
144
|
+
line
|
145
|
+
line
|
146
|
+
line
|
147
|
+
line
|
148
|
+
line
|
149
|
+
line
|
150
|
+
line
|
151
|
+
line
|
152
|
+
line
|
153
|
+
line
|
154
|
+
line
|
155
|
+
line
|
156
|
+
line
|
157
|
+
line
|
158
|
+
line
|
159
|
+
line
|
160
|
+
line
|
161
|
+
line
|
162
|
+
line
|
163
|
+
line
|
164
|
+
line
|
165
|
+
line
|
166
|
+
line
|
167
|
+
line
|
168
|
+
line
|
169
|
+
line
|
170
|
+
line
|
171
|
+
line
|
172
|
+
line
|
173
|
+
line
|
174
|
+
line
|
175
|
+
line
|
176
|
+
line
|
177
|
+
line
|
178
|
+
line
|
179
|
+
line
|
180
|
+
line
|
181
|
+
line
|
182
|
+
line
|
183
|
+
line
|
184
|
+
line
|
185
|
+
line
|
186
|
+
line
|
187
|
+
line
|
188
|
+
line
|
189
|
+
line
|
190
|
+
line
|
191
|
+
line
|
192
|
+
line
|
193
|
+
line
|
194
|
+
line
|
195
|
+
line
|
196
|
+
line
|
197
|
+
line
|
198
|
+
line
|
199
|
+
line
|
200
|
+
line
|
201
|
+
line
|
202
|
+
line
|
203
|
+
line
|
204
|
+
line
|
205
|
+
line
|
206
|
+
line
|
207
|
+
line
|
208
|
+
line
|
209
|
+
line
|
210
|
+
line
|
211
|
+
line
|
212
|
+
line
|
213
|
+
line
|
214
|
+
line
|
215
|
+
line
|
216
|
+
line
|
217
|
+
line
|
218
|
+
line
|
219
|
+
line
|
220
|
+
line
|
221
|
+
line
|
222
|
+
line
|
223
|
+
line
|
224
|
+
line
|
225
|
+
line
|
226
|
+
line
|
227
|
+
line
|
228
|
+
line
|
229
|
+
line
|
230
|
+
line
|
231
|
+
line
|
232
|
+
line
|
233
|
+
line
|
234
|
+
line
|
235
|
+
line
|
236
|
+
line
|
237
|
+
line
|
238
|
+
line
|
239
|
+
line
|
240
|
+
line
|
241
|
+
line
|
242
|
+
line
|
243
|
+
line
|
244
|
+
line
|
245
|
+
line
|
246
|
+
line
|
247
|
+
line
|
248
|
+
line
|
249
|
+
line
|
250
|
+
line
|
251
|
+
line
|
252
|
+
line
|
253
|
+
line
|
254
|
+
line
|
255
|
+
line
|
256
|
+
line
|
257
|
+
line
|
258
|
+
line
|
259
|
+
line
|
260
|
+
line
|
261
|
+
line
|
262
|
+
line
|
263
|
+
line
|
264
|
+
line
|
265
|
+
line
|
266
|
+
line
|
267
|
+
line
|
268
|
+
line
|
269
|
+
line
|
270
|
+
line
|
271
|
+
line
|
272
|
+
line
|
273
|
+
line
|
274
|
+
line
|
275
|
+
line
|
276
|
+
line
|
277
|
+
line
|
278
|
+
line
|
279
|
+
line
|
280
|
+
line
|
281
|
+
line
|
282
|
+
line
|
283
|
+
line
|
284
|
+
line
|
285
|
+
line
|
286
|
+
line
|
287
|
+
line
|
288
|
+
line
|
289
|
+
line
|
290
|
+
line
|
291
|
+
line
|
292
|
+
line
|
293
|
+
line
|
294
|
+
line
|
295
|
+
line
|
296
|
+
line
|
297
|
+
line
|
298
|
+
line
|
299
|
+
line
|
300
|
+
line
|
301
|
+
line
|
302
|
+
line
|
303
|
+
line
|
304
|
+
line
|
305
|
+
line
|
306
|
+
line
|
307
|
+
line
|
308
|
+
line
|
309
|
+
line
|
310
|
+
line
|
311
|
+
line
|
312
|
+
line
|
313
|
+
line
|
314
|
+
line
|
315
|
+
line
|
316
|
+
line
|
317
|
+
line
|
318
|
+
line
|
319
|
+
line
|
320
|
+
line
|
321
|
+
line
|
322
|
+
// comment
|
323
|
+
|
324
|
+
|
325
|
+
line
|
326
|
+
line
|
327
|
+
line
|
328
|
+
line
|
329
|
+
line
|
330
|
+
line
|
331
|
+
line
|
332
|
+
line
|
333
|
+
line
|