rscm-accurev 0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (52) hide show
  1. data/LICENSE +25 -0
  2. data/README +9 -0
  3. data/Rakefile +137 -0
  4. data/STATUS +63 -0
  5. data/TODO +43 -0
  6. data/apitest.rb +21 -0
  7. data/bumprelease.sh +13 -0
  8. data/lib/rscm/accurev.rb +18 -0
  9. data/lib/rscm/scm/accurev/api.rb +411 -0
  10. data/lib/rscm/scm/accurev/api.rb.mine +382 -0
  11. data/lib/rscm/scm/accurev/api.rb.r263 +364 -0
  12. data/lib/rscm/scm/accurev/api.rb.r265 +393 -0
  13. data/lib/rscm/scm/accurev/command.rb +151 -0
  14. data/lib/rscm/scm/accurev/exception.rb +38 -0
  15. data/lib/rscm/scm/accurev/filterio.rb +57 -0
  16. data/lib/rscm/scm/accurev/xml.rb +224 -0
  17. data/lib/test/unit/ui/xml/testrunner.rb +165 -0
  18. data/lib/test/unit/ui/xml/xmltestrunner.xslt +79 -0
  19. data/test/acreplay.rb +22 -0
  20. data/test/coverage/analyzer.rb +127 -0
  21. data/test/coverage/c_loader.rb +34 -0
  22. data/test/coverage/cover.rb +91 -0
  23. data/test/coverage/coverage_loader.rb +21 -0
  24. data/test/coverage/coveragetask.rb +38 -0
  25. data/test/coverage/index_tmpl.html +42 -0
  26. data/test/coverage/template.html +36 -0
  27. data/test/eg/ac-files.xml +172 -0
  28. data/test/eg/ac-pop.txt +195 -0
  29. data/test/eg/files-various-states.xml +188 -0
  30. data/test/eg/hist-oneweek-all.xml +1483 -0
  31. data/test/eg/hist-oneweek-external.xml +246 -0
  32. data/test/eg/hist-oneweek-promotes.xml +1092 -0
  33. data/test/eg/info.txt +14 -0
  34. data/test/eg/stat-a-various.xml +1789 -0
  35. data/test/eg/stat-m.xml +13 -0
  36. data/test/eg/stat-overlap.xml +13 -0
  37. data/test/eg/stat-x.xml +20 -0
  38. data/test/eg/update-i-mods-and-updates-and-overlap.xml +73 -0
  39. data/test/eg/update-i-nochanges.xml +8 -0
  40. data/test/eg/update-i-stale.xml +0 -0
  41. data/test/eg/update-i-updates.xml +125 -0
  42. data/test/eg/update-newwksp.xml +183 -0
  43. data/test/eg/update-nochanges.xml +7 -0
  44. data/test/eg/update-stale.xml +12 -0
  45. data/test/eg/update-updates.xml +147 -0
  46. data/test/t_api.rb +163 -0
  47. data/test/t_command.rb +85 -0
  48. data/test/t_filterio.rb +60 -0
  49. data/test/t_load.rb +11 -0
  50. data/test/t_scrubio.rb +117 -0
  51. data/test/t_xmlmapper.rb +75 -0
  52. metadata +106 -0
@@ -0,0 +1,393 @@
1
+ # -*- ruby -*-
2
+ #
3
+ # = api.rb
4
+ #
5
+ # API for interacting with accurev workspaces.
6
+ #
7
+ # Includes:
8
+ #
9
+ # * API
10
+ #
11
+ require 'stringio'
12
+ require 'rscm/base'
13
+ require 'rscm/path_converter'
14
+ require 'rexml/document'
15
+ require 'rscm/scm/accurev/xml'
16
+ require 'rscm/scm/accurev/filterio'
17
+ require 'rscm/scm/accurev/command'
18
+ require 'rscm/scm/accurev/exception'
19
+
20
+ # need to define module within module before you can use module X::Y
21
+ module RSCM; module Accurev; end; end
22
+
23
+ module RSCM::Accurev
24
+
25
+ #
26
+ # RSCM implementation for Accurev (http://www.accurev.com).
27
+ #
28
+ # Requires an accurev cli executable on the path (see also
29
+ # RSCM::Accurev::Command), and the correct environment
30
+ # configuration for ac server/principal/password.
31
+ #
32
+ # ---
33
+ #
34
+ # === Example
35
+ #
36
+ # api = RSCM::Accurev::API.new( "./ws/proj-1", "proj", "proj-1" )
37
+ # api.checkout() each do |file|
38
+ # puts "Updated #{file}..."
39
+ # end
40
+ #
41
+ class API < RSCM::Base
42
+ register self
43
+
44
+ ann :description => "Accurev Depot"
45
+ attr_accessor :depot
46
+
47
+ ann :description => "Backing Stream (autodetected)"
48
+ attr_accessor :backing_stream
49
+
50
+ # If workspace stream is nil (the default), checkout/update will be
51
+ # done using `accurev pop`. This is faster and more appropriate for
52
+ # read-only use; however a workspace is required for commits.
53
+ ann :description => "Workspace Stream"
54
+ attr_accessor :workspace_stream
55
+
56
+ # [defined in Base]
57
+ # ann :description => "Filesystem Path to Workspace"
58
+ # attr_accessor :checkout_dir
59
+
60
+ ann :description => "Overwrite Mode: if true, co overwrites existing"
61
+ attr_accessor :overwrite
62
+
63
+ def initialize(checkout_dir=nil, backing_stream=nil, workspace_stream=nil)
64
+ @depot = nil # will be pulled from files cmd output
65
+ @workspace_stream = workspace_stream
66
+ @backing_stream = backing_stream
67
+ @checkout_dir = checkout_dir
68
+ @overwrite = false
69
+ end
70
+
71
+ def name()
72
+ return "Accurev"
73
+ end
74
+
75
+ # Accurev operations are atomic: returns true.
76
+ def transactional?
77
+ return true
78
+ end
79
+
80
+ # "Adds +relative_filename+ to the working copy."
81
+ def add( relative_filename )
82
+ raise "XXX implement me"
83
+ end
84
+
85
+ def edit( relative_filename )
86
+ # NOP - not required for ac
87
+ end
88
+
89
+ # "Returns a Revisions object for the period specified ... (inclusive)."
90
+ #
91
+ # list txns (promotes to bs) from hist within the period
92
+ #
93
+ def revisions( from_identifier, to_identifier=Time.infinity )
94
+ raise "XXX implement me"
95
+ end
96
+
97
+ # default impl depends on a correct impl of revisions()
98
+ # def uptodate?( identifier=nil )
99
+ #
100
+ # end
101
+
102
+ # def checked_out? - base
103
+
104
+ # accurev actually does have triggers, but I haven't implemented that yet
105
+ def supports_trigger?
106
+ false
107
+ end
108
+
109
+ # def diff() XXX
110
+
111
+
112
+ # --- ac specific
113
+
114
+ def ac_files( relative_path )
115
+ cmd = Command.instance
116
+ ret = []
117
+ with_working_dir( @checkout_dir ) do
118
+ acresponse = cmd.accurev( "files", relative_path )
119
+ acresponse['element'].each do |fd|
120
+ yield fd if block_given?
121
+ ret << fd
122
+ end
123
+ end
124
+ return ret
125
+ end
126
+
127
+ # yeilds the TransactionData objects for the given time period
128
+ # (promotes only)
129
+ def ac_hist( from, to=Time.infinite )
130
+ cmd = Command.instance
131
+ lower = from.to_accurev
132
+ upper = "now"
133
+ unless to == Time.infinite
134
+ upper = to.to_accurev
135
+ end
136
+ with_working_dir( self.co_filepath ) do
137
+ acresponse = cmd.accurev( "hist",
138
+ "-t", "'#{upper}-#{lower}'",
139
+ "-k", "promote"
140
+ )
141
+ ret = []
142
+ acresponse['transaction'].each do |txn|
143
+ ret << txn
144
+ yield txn if block_given?
145
+ end
146
+ return ret
147
+ end
148
+ end
149
+
150
+ def ac_keep( files=[], message="" )
151
+ raise "XXX implement me"
152
+ end
153
+
154
+ def ac_promote( files=[], message="" )
155
+ raise "XXX implement me"
156
+ end
157
+
158
+ def ac_update( relative_path="." )
159
+ d = accurev( "update", relative_path )
160
+ if xxx_error_stale
161
+ raise StaleWorkspaceError.new( "#{relative_path} is stale -- keep/anchor all changes and re-update" )
162
+ end
163
+ end
164
+
165
+ def ac_purge( files=[] )
166
+ raise "XXX implement me"
167
+ end
168
+
169
+ def ac_revert( files=[] )
170
+ raise "XXX implement me"
171
+ end
172
+
173
+ def ac_move( current_file, new_file )
174
+ raise "XXX implement me"
175
+ end
176
+
177
+ #
178
+ # Performs an "accurev info" command in the workspace.
179
+ # Returns a map of
180
+ #
181
+ # ac info: shows eg, backing stream
182
+ # but doesn't support -fx!
183
+ #
184
+ def ac_info()
185
+ co = RSCM::PathConverter.nativepath_to_filepath( @checkout_dir )
186
+ unless File.exists?( co )
187
+ raise AccurevException.new( "Checkout dir #{co} does not exist" )
188
+ end
189
+ info = {}
190
+ with_working_dir( co ) do
191
+ cmd = Command.instance
192
+ io = StringIO.new( cmd.accurev_nofx( "info" ) )
193
+ io.each_line do |line|
194
+ next unless line =~ /\S/
195
+ if line =~ /^(.*?):\s+(.*)$/
196
+ info[$1] = $2
197
+ end
198
+ end
199
+ end
200
+ return info
201
+ end
202
+
203
+ # ac mkws
204
+ # -b orbitz-host-gt3-0-impl
205
+ # -w orbitz-host-gt3-0-impl-test2_gfast
206
+ # -l `pwd`
207
+ # - does not support fx (argh!@)
208
+ # - does not pop
209
+ #
210
+
211
+ # diff: /usr/local/bin/acdiff (!) --fx
212
+ # (after an ac cat on the backed file)
213
+ # (eg, no in-process diffing ala cvs diff)
214
+ # lists modified lines in xml
215
+
216
+ #
217
+ # "Checks out or updates[!] contents from a central SCM
218
+ # to +checkout_dir+ - a local working copy."
219
+ #
220
+ # "The +to_identifier+ parameter may be optionally specified to
221
+ # obtain files up to a particular time or label."
222
+ #
223
+ # For accurev, this:
224
+ # * checks to see if +@checkout_dir+ exists and appears checked out.
225
+ # If it's already checked out, this calls +update()+. If
226
+ # +@checkout_dir+ exists and +to_identifier+ is given, an
227
+ # exception is raised.
228
+ # * otherwise, this creates a new workspace stream and populates it
229
+ # at +@checkout_dir+.
230
+ #
231
+ # This both returns and yields a list of updated files.
232
+ # Only updated files are returned, not directories.
233
+ #
234
+ # Current, ac_update returns both files and directories, including
235
+ # deleted files.
236
+ #
237
+ def checkout( to_identifier=Time.infinite )
238
+ co = RSCM::PathConverter.nativepath_to_filepath( @checkout_dir )
239
+ if @backing_stream.nil?
240
+ self.attempt_init_from_info()
241
+ end
242
+ if @workspace_stream.nil?
243
+ self.checkout_pop()
244
+ else
245
+ unless File.exists?( co )
246
+ # create new workspace
247
+ self.checkout_workspace()
248
+ end
249
+ # update workspace
250
+ self.update( to_identifier )
251
+ end
252
+ end
253
+
254
+ def checkout_pop()
255
+ co = RSCM::PathConverter.nativepath_to_filepath( @checkout_dir )
256
+ raise "A backing stream is required" if @backing_stream.nil?
257
+ raise "A working stream may not be given" unless @working_stream.nil?
258
+ # for `accurev pop`: remove and re-pop the checkout copy
259
+ if File.exists?( co )
260
+ unless @overwrite
261
+ raise "Checkout dir #{co} already exists (@overwrite=#@overwrite)"
262
+ end
263
+ rm_rf( co )
264
+ end
265
+ mkdir(co)
266
+ with_working_dir( co ) do
267
+ cmd = Command.instance
268
+ pop_out = cmd.accurev_nofx("pop",
269
+ "-R",
270
+ "-v", @backing_stream,
271
+ "-L", ".", ".")
272
+ elems = []
273
+ popio = StringIO.new( pop_out )
274
+ popio.each_line do |line|
275
+ if line =~ /^Populating element \/.\/(.*)$/
276
+ loc = $1
277
+ elems << loc
278
+ yield loc if block_given?
279
+ end
280
+ end
281
+ return elems
282
+ end
283
+ end
284
+
285
+ def checkout_workspace()
286
+ co = RSCM::PathConverter.nativepath_to_filepath( @checkout_dir )
287
+ raise "A backing stream is required" if @backing_stream.nil?
288
+ raise "A workspace is required" if @working_stream.nil?
289
+ if File.exist?( co ) and !@overwrite
290
+ raise "Checkout dir #{co} already exists (@overwrite=#@overwrite)"
291
+ end
292
+ cmd = Command.instance
293
+ mkws_out = cmd.accurev_nofx( "mkws",
294
+ "-b", @backing_stream,
295
+ "-w", @workspace_stream,
296
+ "-l", co )
297
+ # kinda sucks:
298
+ if ( mkws_out =~ /already exists/ )
299
+ raise AccurevException.new( "Failed to checkout", mkws_out )
300
+ end
301
+ end
302
+
303
+ def update( to_identifier=Time.infinite )
304
+ co = RSCM::PathConverter.nativepath_to_filepath( @checkout_dir )
305
+ unless File.exists?( co )
306
+ raise AccurevException.new( "Workspace does not exist!" )
307
+ end
308
+ updated = []
309
+ with_working_dir( co ) do
310
+ cmd = Command.instance
311
+ acresponse = cmd.accurev( "update" )
312
+ if acresponse.error
313
+ if acresponse.error =~ /workspace have been modified/
314
+ raise StaleWorkspaceException.new( "Workspace stale",
315
+ acresponse.error )
316
+ else
317
+ # some other update problem
318
+ raise AccurevException.new( "Error on update", acresponse.error )
319
+ end
320
+ end
321
+ if acresponse['element']
322
+ acresponse['element'].each do |up|
323
+ # "ac update" on a new workspace yields element locations
324
+ # with leading "/"s, which should be removed to get a proper
325
+ # relative path:
326
+ loc = up.location.sub( /^\//, "" )
327
+ yield loc if block_given?
328
+ updated << loc
329
+ end
330
+ end
331
+ end
332
+ return updated
333
+ end
334
+
335
+ # --- internals
336
+
337
+ # status flag mappings for "stat", "file" commands
338
+ STATUSES = {
339
+ '(backed)' => :backed,
340
+ '(external)' => :external,
341
+ '(modified)' => :modified,
342
+ '(kept)' => :kept,
343
+ '(defunct)' => :defunct,
344
+ '(missing)' => :missing,
345
+ '(stranded)' => :stranded,
346
+ '(overlap)' => :overlap
347
+ }
348
+
349
+ # private
350
+
351
+ # psuedo-accessor (cached)
352
+ def co_filepath
353
+ if @co_filepath.nil?
354
+ @co_filepath = RSCM::PathConverter.nativepath_to_filepath( @checkout_dir )
355
+ end
356
+ return @co_filepath
357
+ end
358
+
359
+ #
360
+ # Runs an `ac info` command in +@checkout_dir+, and tries to
361
+ # set +@backing_stream+ and +@workspace_stream+ from the output.
362
+ #
363
+ def attempt_init_from_info()
364
+ if File.exists?( self.co_filepath )
365
+ info = self.ac_info
366
+ if info.has_key?( "Basis" )
367
+ @backing_stream = info["Basis"]
368
+ end
369
+ if info.has_key?( "ws/ref" )
370
+ @workspace_stream = info["ws/ref"]
371
+ end
372
+ end
373
+ end
374
+
375
+ # Takes a status flags line (eg, "(modified)(kept)")
376
+ # and returns a list of status flags.
377
+ def map_status( status_line )
378
+ l = status_line.split( " " ).map {|x| x.trim}
379
+ end
380
+
381
+ end
382
+
383
+ end
384
+
385
+ class Time
386
+ def to_accurev
387
+ self.strftime( "%Y/%m/%d %H:%M:%S" )
388
+ end
389
+ end
390
+
391
+ #
392
+ # Copyright (c) 2005 Gregory D. Fast <gdf@speakeasy.net>
393
+ #
@@ -0,0 +1,151 @@
1
+ # -*- ruby -*-
2
+
3
+ module RSCM
4
+ module Accurev
5
+
6
+ #
7
+ # Executes accurev commands and deals with the output.
8
+ # This class is a thread local singleton: each thread
9
+ # calling +Command.instance+ gets a different instance.
10
+ #
11
+ class Command
12
+
13
+ attr_accessor :debug, :debug_to, :accurev_bin
14
+
15
+ # If you need to swap out the mapper class
16
+ attr_accessor :xmlmapper
17
+
18
+ def initialize()
19
+ @xmlmapper = XMLMapper
20
+ @debug = false
21
+ @debug_to = STDOUT
22
+ @accurev_bin = "accurev"
23
+ end
24
+
25
+ #
26
+ # Returns a command line string for executing the given
27
+ # accurev subcomment +cmd+ with arguments +opts+.
28
+ #
29
+ def accurev_cmdline( cmd, *opts )
30
+ return "#{@accurev_bin} #{cmd} #{opts.join(' ')}";
31
+ end
32
+
33
+ #
34
+ # Executes the given accurev subcommand +cmd+ with the
35
+ # given arguments +opts+. The command will be executed with
36
+ # standard (non-xml) output format. This method returns
37
+ # the stdout from the command execution.
38
+ #
39
+ # (Not all accurev subcommands (eg, `accurev info`) support
40
+ # `-fx` for xml output.)
41
+ #
42
+ def accurev_nofx( cmd, *opts )
43
+ ## # nativepath_to_filepath is actually generic to native
44
+ ## dir = PathConverter.nativepath_to_filepath( @working_dir )
45
+ ## dir = File.expand_path( dir )
46
+ ## with_working_dir( dir ) do
47
+ cmdline = self.accurev_cmdline( cmd, opts )
48
+ if @debug
49
+ @debug_to.puts("ac> #{cmdline}")
50
+ end
51
+ Better.popen( cmdline ) do |stdout|
52
+ return stdout.read()
53
+ end
54
+ ## end
55
+ end
56
+
57
+ #
58
+ # Executes the given accurev subcommand +cmd+ with the
59
+ # given arguments +opts+ in XML mode. The output of the command
60
+ # will be converted to an REXML document and returned.
61
+ # The command's options list will have `-fx` appended,
62
+ # to specify xml output format.
63
+ #
64
+ # Certain quirks in <AcResponse>-type documents will be
65
+ # corrected (see Accurev::AcXMLScrubIO). Note that not all
66
+ # accurev subcommands support the "-fx" option.
67
+ #
68
+ def accurev_xml( cmd, *opts )
69
+ opts << "-fx"
70
+ ## # nativepath_to_filepath is actually generic to native
71
+ ## dir = PathConverter.nativepath_to_filepath( @working_dir )
72
+ ## dir = File.expand_path( dir )
73
+ ## with_working_dir( dir ) do
74
+ cmdline = self.accurev_cmdline( cmd, opts )
75
+ if @debug
76
+ @debug_to.puts("ac> #{cmdline}")
77
+ end
78
+ Better.popen( cmdline ) do |stdout|
79
+ output = stdout.read()
80
+ if @debug
81
+ @debug_to.puts( "raw>" )
82
+ @debug_to.puts( output )
83
+ end
84
+ begin
85
+ return REXML::Document.new( AcXMLScrubIO.new( output ) )
86
+ rescue Exception => e
87
+ raise "Unexpected output from #{cmdline}: #{e}"
88
+ end
89
+ end
90
+ ## end
91
+ end
92
+
93
+ #
94
+ # Execute the given accurev subcommand using +accurev_xml+,
95
+ # and return an object tree mirroring the structure of the
96
+ # XML output.
97
+ #
98
+ # This uses +XMLMapper+ to build the object tree.
99
+ #
100
+ def accurev( cmd, *opts )
101
+ doc = self.accurev_xml( cmd, opts )
102
+ if @debug
103
+ @debug_to.puts( "doc>" )
104
+ @debug_to.puts( doc )
105
+ end
106
+ if doc.elements.size==0
107
+ raise "Unexpected output from #{cmd}: #{doc}"
108
+ end
109
+ mapper = @xmlmapper.new()
110
+ return mapper.map( doc.root )
111
+ end
112
+
113
+ # static methods
114
+
115
+ # Retrieve this thread's +Command+ instance.
116
+ #
117
+ # eg:
118
+ # cmd = Command.instance
119
+ # eg:
120
+ # Command.instance do |cmd|
121
+ # cmd.workspace_dir = "/var/tmp"
122
+ # end
123
+ #
124
+ def Command.instance()
125
+ if Thread.current[ :RSCM_ACCUREV_COMMAND ].nil?
126
+ Thread.current[ :RSCM_ACCUREV_COMMAND ] = Command.new
127
+ end
128
+ yield Thread.current[ :RSCM_ACCUREV_COMMAND ] if block_given?
129
+ return Thread.current[ :RSCM_ACCUREV_COMMAND ]
130
+ end
131
+
132
+ # Discard the current thread's Command object.
133
+ # The next call to +Command.instance+ in this thread will
134
+ # return a new instance configured with the defaults.
135
+ def Command.discard()
136
+ Thread.current[ :RSCM_ACCUREV_COMMAND ] = nil
137
+ end
138
+
139
+ private
140
+
141
+ # new() is marked private, use Command.instance().
142
+ def new( *args )
143
+ super(args)
144
+ end
145
+
146
+ end # class Command
147
+
148
+ end
149
+ end
150
+
151
+