vcseif 1.2.0
Sign up to get free protection for your applications and to get access to all the features.
- data/lib/vcseif.rb +23 -0
- data/lib/vcseif/connection.rb +285 -0
- data/lib/vcseif/rally_vcs_connection.rb +925 -0
- data/lib/vcseif/utils/auxloader.rb +255 -0
- data/lib/vcseif/utils/exceptions.rb +101 -0
- data/lib/vcseif/utils/fuzzer.rb +71 -0
- data/lib/vcseif/utils/konfigger.rb +421 -0
- data/lib/vcseif/utils/lock_file.rb +90 -0
- data/lib/vcseif/utils/proctbl.rb +146 -0
- data/lib/vcseif/utils/rally_logger.rb +223 -0
- data/lib/vcseif/utils/time_file.rb +80 -0
- data/lib/vcseif/vcs_connector.rb +487 -0
- data/lib/vcseif/vcs_connector_driver.rb +227 -0
- data/lib/vcseif/vcs_connector_runner.rb +283 -0
- data/lib/version.rb +18 -0
- metadata +173 -0
@@ -0,0 +1,80 @@
|
|
1
|
+
# Copyright 2001-2013 Rally Software Development Corp. All Rights Reserved.
|
2
|
+
require "date"
|
3
|
+
require "time"
|
4
|
+
|
5
|
+
class TimeFile
|
6
|
+
|
7
|
+
# Store the last time the connector was run - just in case the connector stops and restarts
|
8
|
+
# Time (as UTC) is stored as a string in YYYY-MM-DD HH:MM:SS UTC" format
|
9
|
+
ISO8601_FORMAT = "%Y-%m-%dT%H:%M:%S.%L%z"
|
10
|
+
STD_TS_FORMAT = "%Y-%m-%d %H:%M:%S Z"
|
11
|
+
STD_STRPTIME_FMT = "%Y-%m-%d %H:%M:%S %z"
|
12
|
+
|
13
|
+
%{
|
14
|
+
An instance of this class is used to record a timestamp in a file.
|
15
|
+
The timestamp is an ASCII representation that is derived from the ISO-8601 format.
|
16
|
+
}
|
17
|
+
|
18
|
+
attr_reader :filename
|
19
|
+
attr_reader :log
|
20
|
+
|
21
|
+
def initialize(filename, logger)
|
22
|
+
@filename = filename
|
23
|
+
@log = logger
|
24
|
+
end
|
25
|
+
|
26
|
+
def exists?
|
27
|
+
return File.exists?(@filename)
|
28
|
+
end
|
29
|
+
|
30
|
+
def read()
|
31
|
+
%{
|
32
|
+
Returns the time stored in the file as an epoch seconds value
|
33
|
+
or a default value of the time 5 minutes ago (again in epoch seconds form).
|
34
|
+
}
|
35
|
+
# default to 5 minutes ago if file non-existent or empty
|
36
|
+
default = Time.now - (5 * 60)
|
37
|
+
last_run_timestamp = nil
|
38
|
+
|
39
|
+
if exists?
|
40
|
+
begin
|
41
|
+
File.open(@filename, "r") do |file|
|
42
|
+
line = file.gets
|
43
|
+
if !line.nil? and !line.empty?
|
44
|
+
line = line.strip
|
45
|
+
if line =~ /\d+.*T\d+:\d+:\d+.\d+/ # in old-style ISO8601 format
|
46
|
+
last_run_timestamp = Time.strptime(line, ISO8601_FORMAT)
|
47
|
+
else
|
48
|
+
begin
|
49
|
+
last_run_timestamp = Time.strptime(line, STD_STRPTIME_FMT)
|
50
|
+
rescue => ex
|
51
|
+
problem = "Invalid format for timefile entry: #{line}"
|
52
|
+
action = "reverting to default of #{last_run_timestamp}"
|
53
|
+
@log.error("%s, %s" % [problem, action])
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
58
|
+
rescue => ex
|
59
|
+
problem = "Could not read time entry from #{@filename}"
|
60
|
+
action = "rewriting time file with default value"
|
61
|
+
@log.error("%s, %s" % [problem, action])
|
62
|
+
write(last_run_timestamp)
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
last_run_timestamp = default if last_run_timestamp.nil?
|
67
|
+
return last_run_timestamp
|
68
|
+
end
|
69
|
+
|
70
|
+
def write(timehack=nil)
|
71
|
+
%{
|
72
|
+
Writes the time (expected as a Ruby Time object)
|
73
|
+
to the file in ISO 8601 format
|
74
|
+
}
|
75
|
+
timehack = Time.now if timehack.nil?
|
76
|
+
stamp = timehack.utc.strftime(STD_TS_FORMAT)
|
77
|
+
File.open(@filename, 'w') { |file| file.puts "%s\n" % stamp }
|
78
|
+
end
|
79
|
+
|
80
|
+
end
|
@@ -0,0 +1,487 @@
|
|
1
|
+
# Copyright 2001-2013 Rally Software Development Corp. All Rights Reserved.
|
2
|
+
|
3
|
+
# This is the large stone level operation of the VCSEIF infrastructure,
|
4
|
+
# wherein the two connections held by the connector get instantiated,
|
5
|
+
# connected to their respective systems and exercised.
|
6
|
+
|
7
|
+
require 'time'
|
8
|
+
require 'open3' # so that ocra will pick this up
|
9
|
+
|
10
|
+
# require_relative "../vcseif" #dsmith - this should not be needed - available when you require in vcseif
|
11
|
+
# which will make the following available:
|
12
|
+
# connection.VCSConnection
|
13
|
+
# utils/exceptions.UnrecoverableException
|
14
|
+
# utils/exceptions.ConfigurationError
|
15
|
+
# utils/exceptions.OperationalError
|
16
|
+
# utils/rally_logger.RallyLogger
|
17
|
+
# utils/auxloader.PluginLoader
|
18
|
+
# utils/auxloader.ClassLoader
|
19
|
+
|
20
|
+
##############################################################################################
|
21
|
+
|
22
|
+
class VCSConnector
|
23
|
+
|
24
|
+
VERSION = VCSEIF::Version
|
25
|
+
|
26
|
+
PLUGIN_SPEC_PATTERN = Regexp.compile('^(?<plugin_class>\w+)\s*\((?<plugin_config>[^\)]*)\)\s*$')
|
27
|
+
PLAIN_PLUGIN_SPEC_PATTERN = Regexp.compile('(?<plugin_class>\w+)\s*$')
|
28
|
+
|
29
|
+
RALLY_TIMESTAMP = "%Y-%m-%dT%H:%M:%S.%L %Z"
|
30
|
+
ISO8601_TIMESTAMP = "%Y-%m-%dT%H:%M:%S"
|
31
|
+
|
32
|
+
@@transformable_attributes = ['Author']
|
33
|
+
|
34
|
+
attr_reader :config, :log
|
35
|
+
attr_reader :rally_conn, :vcs_conn, :rally_conn_class, :vcs_conn_class
|
36
|
+
attr_reader :vcs_name
|
37
|
+
attr_reader :rally_conf, :vcs_conf, :svc_conf, :txfm_conf
|
38
|
+
attr_reader :transforms
|
39
|
+
|
40
|
+
def initialize(config, logger)
|
41
|
+
@config = config
|
42
|
+
@log = logger
|
43
|
+
|
44
|
+
conn_sections = []
|
45
|
+
begin
|
46
|
+
non_conn_sections = ['Services', 'Transforms']
|
47
|
+
conn_sections = config.topLevels.select {|section| !non_conn_sections.include?(section)}
|
48
|
+
if conn_sections.length != 2
|
49
|
+
problem = 'Config does not contain two connection sections'
|
50
|
+
confex = VCSEIF_Exceptions::ConfigurationError.new(problem)
|
51
|
+
raise confex, problem
|
52
|
+
end
|
53
|
+
rescue Exception => ex
|
54
|
+
problem = 'Config file lacks sufficient information for VCSConnector operation'
|
55
|
+
confex = VCSEIF_Exceptions::ConfigurationError.new(problem)
|
56
|
+
raise confex, problem
|
57
|
+
end
|
58
|
+
|
59
|
+
@rally_conn = nil
|
60
|
+
@vcs_conn = nil
|
61
|
+
|
62
|
+
@vcs_name = conn_sections.select {|section_name| section_name !~ /^Rally/}.first
|
63
|
+
@log.info("Rally VCS Connector for %s, version %s" % [@vcs_name, VERSION])
|
64
|
+
@log.info("Ruby platform: #{RUBY_PLATFORM}")
|
65
|
+
@log.info("Ruby version: #{RUBY_VERSION}")
|
66
|
+
|
67
|
+
internalizeConfig(config)
|
68
|
+
|
69
|
+
rally_conn_class_name = config.connectionClassName('Rally')
|
70
|
+
vcs_conn_class_name = config.connectionClassName(@vcs_name)
|
71
|
+
|
72
|
+
@log.debug("Loading #{rally_conn_class_name} class")
|
73
|
+
begin
|
74
|
+
@rally_conn_class = ClassLoader.loadConnectionClass('lib', rally_conn_class_name, 'rally_vcs_connection')
|
75
|
+
rescue Exception => ex
|
76
|
+
problem = 'Unable to load RallyVCSConnection class, %s' % ex.message
|
77
|
+
boomex = VCSEIF_Exceptions::UnrecoverableException.new(problem)
|
78
|
+
raise boomex, problem
|
79
|
+
end
|
80
|
+
|
81
|
+
@log.debug("Loading #{vcs_conn_class_name} class")
|
82
|
+
begin
|
83
|
+
@vcs_conn_class = ClassLoader.loadConnectionClass('lib', vcs_conn_class_name)
|
84
|
+
rescue Exception => ex
|
85
|
+
problem = 'Unable to load %sConnection class, %s' % [@vcs_name, ex.message]
|
86
|
+
boomex = VCSEIF_Exceptions::UnrecoverableException.new(problem)
|
87
|
+
raise boomex, problem
|
88
|
+
end
|
89
|
+
|
90
|
+
@log.debug('Obtaining Rally and VCS connections...')
|
91
|
+
establishConnections()
|
92
|
+
@log.debug('Rally and VCS connections established')
|
93
|
+
validate() # basically just calls validate on both connection instances
|
94
|
+
|
95
|
+
@log.debug("loading Transform class ...")
|
96
|
+
@transforms = loadTransforms(@txfm_conf)
|
97
|
+
@log.debug("Transform loaded")
|
98
|
+
|
99
|
+
@log.info("Initialization complete: Delegate connections operational, " +\
|
100
|
+
"aux facilities loaded, ready for scan/reflect ops")
|
101
|
+
end
|
102
|
+
|
103
|
+
|
104
|
+
def internalizeConfig(config)
|
105
|
+
@rally_conf = config.topLevel('Rally')
|
106
|
+
@vcs_conf = config.topLevel(@vcs_name)
|
107
|
+
@svc_conf = config.topLevel('Services')
|
108
|
+
@txfm_conf = config.topLevel('Transforms') || {}
|
109
|
+
# default the Author transform to Passthru if not specified in @txfm_conf
|
110
|
+
if not @txfm_conf.keys.include?('Author')
|
111
|
+
@txfm_conf['Author'] = 'Passthru'
|
112
|
+
end
|
113
|
+
##
|
114
|
+
## @log.debug("config.Services has |#{@svc_conf.inspect}|")
|
115
|
+
##
|
116
|
+
end
|
117
|
+
|
118
|
+
|
119
|
+
def establishConnections()
|
120
|
+
@rally_conn = @rally_conn_class.new(@rally_conf, @log)
|
121
|
+
@vcs_conn = @vcs_conn_class.new( @vcs_conf, @log)
|
122
|
+
@vcs_conn.connect() # we do this before rally_conn to be able to get the vcs backend version
|
123
|
+
vcs_backend_version = @vcs_conn.getBackendVersion()
|
124
|
+
|
125
|
+
if @rally_conn != nil and @vcs_conn != nil and @rally_conn.respond_to?('set_integration_header')
|
126
|
+
# so we can use it in our X-Rally-Integrations header items here
|
127
|
+
rally_headers = {'name' => 'Rally VCSConnector for %s' % @vcs_name,
|
128
|
+
'version' => VERSION,
|
129
|
+
'vendor' => 'Rally Software',
|
130
|
+
'other_version' => vcs_backend_version
|
131
|
+
}
|
132
|
+
@rally_conn.set_integration_header(rally_headers)
|
133
|
+
end
|
134
|
+
@rally_conn.setSourceIdentification(@vcs_conn.name, @vcs_conn.uri)
|
135
|
+
@rally_conn.connect()
|
136
|
+
end
|
137
|
+
|
138
|
+
|
139
|
+
private
|
140
|
+
def loadTransforms(txfms)
|
141
|
+
"""
|
142
|
+
Given a txfms parm (nil or Hash with keys for each transformable field)
|
143
|
+
that specifies the plugin class and plugin config (Rally User entity attributes
|
144
|
+
names that could be used in the transformation process), validate the
|
145
|
+
sytactical validity of the transformation specification, and load the
|
146
|
+
module containing the transformer class and obtain an instance of that class.
|
147
|
+
Populate a Hash keyed by attribute name with the value for each key
|
148
|
+
an instance of the transform class.
|
149
|
+
|
150
|
+
The field names are from the Rally Changeset entity. They must have their
|
151
|
+
first letter capitalized and any other words in the entity name must also
|
152
|
+
have their first letter capitalized and there can be no embedded spaces.
|
153
|
+
"""
|
154
|
+
attribute_transformer = {}
|
155
|
+
return attribute_transformer if txfms.nil?
|
156
|
+
|
157
|
+
txfms.each_pair do |attributeName, txfm_spec|
|
158
|
+
@log.debug("target attribute: %s txfm_spec: %s" % [attributeName, txfm_spec])
|
159
|
+
md = PLUGIN_SPEC_PATTERN.match(txfm_spec)
|
160
|
+
if not md.nil?
|
161
|
+
plugin_class_name, plugin_config = md[:plugin_class], md[:plugin_config]
|
162
|
+
else
|
163
|
+
md = PLAIN_PLUGIN_SPEC_PATTERN.match(txfm_spec)
|
164
|
+
if md.nil?
|
165
|
+
problem = "Bad Plugin Spec for %s : %s" % [attributeName, txfm_spec]
|
166
|
+
raise VCSEIF_Exceptions::ConfigurationError.new(problem)
|
167
|
+
end
|
168
|
+
plugin_class_name, plugin_config = md[:plugin_class], ""
|
169
|
+
end
|
170
|
+
|
171
|
+
begin
|
172
|
+
plugin_class = PluginLoader.getPlugin(plugin_class_name)
|
173
|
+
rescue => ex
|
174
|
+
raise
|
175
|
+
end
|
176
|
+
if plugin_class.nil?
|
177
|
+
problem = 'Unable to load plugin for %s' % plugin_class_name
|
178
|
+
raise VCSEIF_Exceptions::UnrecoverableException.new(problem)
|
179
|
+
end
|
180
|
+
|
181
|
+
@log.debug("Transform Class: %s" % plugin_class.name)
|
182
|
+
kwargs = { 'config' => plugin_config,
|
183
|
+
'vcs_ident' => @vcs_conn.name,
|
184
|
+
'rally' => @rally_conn,
|
185
|
+
'logger' => @rally_conn.log
|
186
|
+
}
|
187
|
+
begin
|
188
|
+
transformer = plugin_class.new(kwargs)
|
189
|
+
rescue Exception => ex
|
190
|
+
problem = "Caught exception on attempt to get instance of #{plugin_class_name} "
|
191
|
+
problem << "plugin, #{ex.message}"
|
192
|
+
raise StandardError, problem
|
193
|
+
end
|
194
|
+
attribute_transformer[attributeName] = (transformer)
|
195
|
+
end
|
196
|
+
|
197
|
+
return attribute_transformer
|
198
|
+
end
|
199
|
+
|
200
|
+
|
201
|
+
public
|
202
|
+
def validate()
|
203
|
+
"""
|
204
|
+
This calls the validate method on both the Rally and the VCS connections
|
205
|
+
"""
|
206
|
+
valid = true
|
207
|
+
|
208
|
+
@log.info("Connector validation starting")
|
209
|
+
|
210
|
+
if not @rally_conn.validate()
|
211
|
+
@log.debug("RallyConnection validation failed")
|
212
|
+
return false
|
213
|
+
end
|
214
|
+
@log.debug("RallyConnection validation succeeded")
|
215
|
+
|
216
|
+
if not @vcs_conn.validate()
|
217
|
+
@log.debug("%sConnection validation failed" % @vcs_name)
|
218
|
+
return false
|
219
|
+
end
|
220
|
+
@log.debug("%sConnection validation succeeded" % @vcs_name)
|
221
|
+
@log.info("Connector validation completed")
|
222
|
+
|
223
|
+
return valid
|
224
|
+
end
|
225
|
+
|
226
|
+
|
227
|
+
def run(last_commit_timestamp, extension)
|
228
|
+
"""
|
229
|
+
Where the rubber meets the road, or rather, controlling
|
230
|
+
the machinery that puts the rubber on the road...
|
231
|
+
"""
|
232
|
+
preBatch(extension)
|
233
|
+
status, repo_changesets = reflectChangesetsInRally(last_commit_timestamp)
|
234
|
+
postBatch(extension, status, repo_changesets)
|
235
|
+
@rally_conn.disconnect()
|
236
|
+
@vcs_conn.disconnect()
|
237
|
+
return [status, repo_changesets]
|
238
|
+
end
|
239
|
+
|
240
|
+
|
241
|
+
def preBatch(extension)
|
242
|
+
"""
|
243
|
+
"""
|
244
|
+
if extension != nil and extension =~ /PreBatchAction/
|
245
|
+
preba = extension['PreBatchAction']
|
246
|
+
preba.service()
|
247
|
+
end
|
248
|
+
end
|
249
|
+
|
250
|
+
|
251
|
+
def postBatch(extension, status, repo_changesets)
|
252
|
+
"""
|
253
|
+
"""
|
254
|
+
if extension != nil and extension =~ /PostBatchAction/
|
255
|
+
postba = extension['PostBatchAction']
|
256
|
+
postba.service(status, repo_changesets)
|
257
|
+
end
|
258
|
+
end
|
259
|
+
|
260
|
+
#last_commit_timestamp is an instance of Time
|
261
|
+
def reflectChangesetsInRally(last_commit_timestamp)
|
262
|
+
"""
|
263
|
+
The last commit timestamm is passed to Connection objects in UTC;
|
264
|
+
they are responsible for converting if necessary.
|
265
|
+
Time in log messages is always reported in UTC (aka Z or Zulu time).
|
266
|
+
"""
|
267
|
+
status = false
|
268
|
+
rally = @rally_conn
|
269
|
+
vcs = @vcs_conn
|
270
|
+
|
271
|
+
rally_repo = @rally_conf['RepositoryName']
|
272
|
+
preview_mode = false
|
273
|
+
preview_item = @svc_conf['Preview'].to_s.downcase
|
274
|
+
preview_mode = true if ['true', 'yes', '1', 'ok', 'on'].include?(preview_item)
|
275
|
+
pm_tag = ''
|
276
|
+
action = 'adding'
|
277
|
+
if preview_mode == true
|
278
|
+
pm_tag = "Preview: "
|
279
|
+
action = "would add"
|
280
|
+
@log.info('***** Preview Mode ***** (no Changesets will be created in Rally)')
|
281
|
+
end
|
282
|
+
|
283
|
+
begin
|
284
|
+
rally_ref_time, vcs_ref_time = getRefTimes(last_commit_timestamp)
|
285
|
+
time_info = " ref times --> rally_ref_time: |%s| vcs_ref_time: |%s|"
|
286
|
+
@log.debug(time_info % [rally_ref_time, vcs_ref_time])
|
287
|
+
recent_rally_changesets = rally.getRecentChangesets(rally_ref_time)
|
288
|
+
@log.debug("Obtained Rally recent changesets info")
|
289
|
+
recent_vcs_changesets = vcs.getRecentChangesets( vcs_ref_time)
|
290
|
+
@log.debug("Obtained VCS recent changesets info")
|
291
|
+
unrecorded_changesets = identifyUnrecordedChangesets(recent_rally_changesets,
|
292
|
+
recent_vcs_changesets,
|
293
|
+
vcs_ref_time)
|
294
|
+
@log.info("%d unrecorded Changesets" % unrecorded_changesets.length)
|
295
|
+
|
296
|
+
recorded_changesets = []
|
297
|
+
unrecorded_changesets.each do |changeset|
|
298
|
+
cts = changeset.commit_timestamp.sub('Z', ' Z').sub('T', ' ')
|
299
|
+
desc = '%sChangeset %16.16s | %s | %s not yet reflected in Rally'
|
300
|
+
@log.debug(desc % [pm_tag, changeset.ident, cts, changeset.author])
|
301
|
+
committer = changeset.author
|
302
|
+
transformEligibleAttributes(changeset)
|
303
|
+
|
304
|
+
adds = collect_files_and_links(changeset.ident, changeset.adds, 'add')
|
305
|
+
mods = collect_files_and_links(changeset.ident, changeset.mods, 'mod')
|
306
|
+
dels = collect_files_and_links(changeset.ident, changeset.dels, 'del')
|
307
|
+
|
308
|
+
info = {
|
309
|
+
'Revision' => changeset.ident,
|
310
|
+
'CommitTimestamp' => changeset.commit_timestamp,
|
311
|
+
'Committer' => committer,
|
312
|
+
'Author' => changeset.author,
|
313
|
+
'Message' => changeset.message,
|
314
|
+
'Additions' => adds,
|
315
|
+
'Modifications' => mods,
|
316
|
+
'Deletions' => dels,
|
317
|
+
'Uri' => @vcs_conn.get_rev_uri(changeset.ident)
|
318
|
+
}
|
319
|
+
|
320
|
+
desc = '%sChangeset %16.16s | %s | %s %s to Rally...'
|
321
|
+
@log.info(desc % [pm_tag, changeset.ident, cts, changeset.author, action])
|
322
|
+
@log.debug(" Rev URI: #{info['Uri']}")
|
323
|
+
|
324
|
+
if not preview_mode
|
325
|
+
begin
|
326
|
+
if not rally.changesetExists?(changeset.ident)
|
327
|
+
rally_changeset = rally.createChangeset(info)
|
328
|
+
recorded_changesets << rally_changeset
|
329
|
+
end
|
330
|
+
rescue Exception => ex
|
331
|
+
raise VCSEIF_Exceptions::OperationalError.new(ex.message)
|
332
|
+
end
|
333
|
+
end
|
334
|
+
end
|
335
|
+
status = true
|
336
|
+
rescue VCSEIF_Exceptions::OperationalError => ex
|
337
|
+
# already resulted in a log message
|
338
|
+
rescue VCSEIF_Exceptions::UnrecoverableException => ex
|
339
|
+
raise
|
340
|
+
rescue Exception => ex
|
341
|
+
raise
|
342
|
+
end
|
343
|
+
status = false if rally.operational_errors > 0
|
344
|
+
|
345
|
+
repo_changesets = {rally_repo => recorded_changesets}
|
346
|
+
return [status, repo_changesets]
|
347
|
+
end
|
348
|
+
|
349
|
+
def collect_files_and_links(revnum, file_list, operation)
|
350
|
+
return_list = []
|
351
|
+
file_list.each do |rev_file|
|
352
|
+
file_uri = @vcs_conn.get_file_rev_uri(revnum, rev_file, operation)
|
353
|
+
log.debug(" Change file URI: #{file_uri}")
|
354
|
+
return_list << {:file => rev_file, :link => file_uri}
|
355
|
+
end
|
356
|
+
return_list
|
357
|
+
end
|
358
|
+
|
359
|
+
def getRefTimes(last_commit_timestamp)
|
360
|
+
"""
|
361
|
+
last_commit_timestamp is provided as an epoch seconds value.
|
362
|
+
Return a two-tuple of the reference time to be used for obtaining the
|
363
|
+
recent changesets in Rally and the reference time to be used for
|
364
|
+
obtaining the recent changesets in the target VCS system.
|
365
|
+
"""
|
366
|
+
rally_lookback = get_config_lookback(@rally_conf)
|
367
|
+
vcs_lookback = get_config_lookback(@vcs_conf)
|
368
|
+
rally_ref_time = Time.at(last_commit_timestamp - rally_lookback).gmtime
|
369
|
+
vcs_ref_time = Time.at(last_commit_timestamp - vcs_lookback).gmtime
|
370
|
+
return [rally_ref_time, vcs_ref_time]
|
371
|
+
end
|
372
|
+
|
373
|
+
def get_config_lookback(config)
|
374
|
+
default_value = 3600 # 1 hour in seconds
|
375
|
+
lookback_val = config['Lookback']
|
376
|
+
return default_value if lookback_val.nil? || lookback_val.to_i <= 0
|
377
|
+
|
378
|
+
#default assumption is in minutes for lookback setting
|
379
|
+
lookback_val = lookback_val.to_s.downcase.strip
|
380
|
+
converted_val = lookback_val.to_i #rubyism: "5days".to_i = 5 "number".to_i = 0
|
381
|
+
return_val = (converted_val <= 0) ? default_value : converted_val * 60 #seconds per minute
|
382
|
+
|
383
|
+
if lookback_val.include?("d") #5 days, 5days, 5d, 5 d
|
384
|
+
return_val = (converted_val <= 0) ? default_value : converted_val * 86400 #seconds per day
|
385
|
+
elsif lookback_val.include?("h") #24 hours, 24 hours, 24h, 24 h
|
386
|
+
return_val = (converted_val <= 0) ? default_value : converted_val * 3600 #seconds per hour
|
387
|
+
end
|
388
|
+
return_val
|
389
|
+
end
|
390
|
+
|
391
|
+
def transformEligibleAttributes(changeset)
|
392
|
+
"""
|
393
|
+
Check for a transformer for each transformable attribute,
|
394
|
+
if no such transformer, leave the changeset.<attribute> value unchanged.
|
395
|
+
Otherwise, get the transformer for the transformable attribute,
|
396
|
+
call it's service method with:
|
397
|
+
getattr(changeset, transformable_attribute.lower())
|
398
|
+
and use the result as the value for transformable_attribute,
|
399
|
+
modifying the changeset item.
|
400
|
+
"""
|
401
|
+
@@transformable_attributes.each do |transformable_attribute|
|
402
|
+
@log.debug("tranformable attribute: |#{transformable_attribute}|")
|
403
|
+
if @transforms.include?(transformable_attribute)
|
404
|
+
@log.debug("tranforms has a transformer class for #{transformable_attribute}")
|
405
|
+
transformer = @transforms[transformable_attribute]
|
406
|
+
target_attr = transformable_attribute.downcase()
|
407
|
+
@log.debug("transformable target attribute: |#{target_attr}|")
|
408
|
+
target_value = changeset.send(target_attr)
|
409
|
+
@log.debug("transform target_value: |#{target_value}|")
|
410
|
+
transformed = transformer.service(target_value)
|
411
|
+
@log.debug("transformed to value: |#{transformed}|")
|
412
|
+
if not transformed.nil?
|
413
|
+
begin
|
414
|
+
##
|
415
|
+
## puts "#{changeset.class.name} attributes: #{changeset.instance_variables}"
|
416
|
+
##
|
417
|
+
changeset.send("#{target_attr}=", transformed)
|
418
|
+
rescue Exception => ex
|
419
|
+
puts "#{ex.message}"
|
420
|
+
for bt_line in ex.backtrace do
|
421
|
+
puts bt_line.sub(Dir.pwd, '${CWD}')
|
422
|
+
end
|
423
|
+
puts "#{ex.inspect}"
|
424
|
+
end
|
425
|
+
##
|
426
|
+
## puts "Changeset.#{target_attr} new value: |#{changeset.send(target_attr)}| should be set to |#{transformed}| ?"
|
427
|
+
##
|
428
|
+
else
|
429
|
+
# TODO: should we log this?
|
430
|
+
end
|
431
|
+
end
|
432
|
+
end
|
433
|
+
end
|
434
|
+
|
435
|
+
|
436
|
+
def identifyUnrecordedChangesets(rally_changesets, vcs_changesets, ref_time)
|
437
|
+
"""
|
438
|
+
Assume that for purposes of matching that the changeset identity in either system
|
439
|
+
can be determined by the rev_ident (as opposed to the rev_num).
|
440
|
+
If there are items in the rally_changesets for which there is a counterpart in
|
441
|
+
the vcs_changesets, the information has already been reflected in Rally. --> NOOP
|
442
|
+
If there are items in the rally_changesets for which there is no counterpart in
|
443
|
+
the vcs_changesets, information has been lost, dat be berry berry bad... --> ERROR
|
444
|
+
If there are items in the vcs_changesets for which there is no counterpart in
|
445
|
+
the rally_changesets, those items are candidates to be reflected in Rally --> REFLECT
|
446
|
+
"""
|
447
|
+
rally_tank = {}
|
448
|
+
# requirement: rc.ident <-- rc.Revision
|
449
|
+
rally_changesets.each {|rally_chgset| rally_tank[rally_chgset.ident] = rally_chgset}
|
450
|
+
|
451
|
+
vcs_tank = {}
|
452
|
+
# requirement: vc.ident <-- {node} or {hash} or {number}, etc .
|
453
|
+
##
|
454
|
+
## puts "vcs_changesets ..."
|
455
|
+
## vcs_changesets.each { |cset| { puts cset.details }
|
456
|
+
##
|
457
|
+
vcs_changesets.each {|vcs_chgset| vcs_tank[vcs_chgset.ident] = vcs_chgset}
|
458
|
+
|
459
|
+
reflected_changesets = rally_changesets.select {|rcs| vcs_tank.has_key?(rcs.ident) == true }
|
460
|
+
unpaired_changesets = rally_changesets.select {|rcs| vcs_tank.has_key?(rcs.ident) == false}
|
461
|
+
unrecorded_changesets = vcs_changesets.select {|vcs| rally_tank.has_key?(vcs.ident) == false}
|
462
|
+
|
463
|
+
if unpaired_changesets.length > 0
|
464
|
+
#lost_changesets = unpaired_changesets.select {|rcs| rcs.CommitTimestamp > ref_time}
|
465
|
+
lost_changesets = []
|
466
|
+
unpaired_changesets.each do |rcs|
|
467
|
+
rally_commit_time = Time.strptime(rcs.CommitTimestamp[0..18], ISO8601_TIMESTAMP)
|
468
|
+
lost_changesets << rcs if rally_commit_time > ref_time
|
469
|
+
end
|
470
|
+
# Hold off on blurting this information in the log. It may just lead to confusion.
|
471
|
+
# There _could_ be other systems throwing info into the Rally Change/Changeset pool
|
472
|
+
# (although we wouldn't expect that to happen very often).
|
473
|
+
#if not lost_changesets.empty?
|
474
|
+
# problem = "Changesets exist in Rally for which there is no "
|
475
|
+
# problem << "longer a counterpart changeset in the target VCS\n "
|
476
|
+
# cs_idents = lost_changesets.collect {|cs| cs.ident}
|
477
|
+
# @log.error(problem + cs_idents.join("\n "))
|
478
|
+
#end
|
479
|
+
end
|
480
|
+
|
481
|
+
return unrecorded_changesets
|
482
|
+
end
|
483
|
+
|
484
|
+
end # VCSConnector class
|
485
|
+
|
486
|
+
####################################################################################
|
487
|
+
|