vcseif 1.2.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.
- 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
|
+
|