rscm 0.3.16 → 0.4.0

Sign up to get free protection for your applications and to get access to all the features.
data/CHANGES CHANGED
@@ -1,5 +1,19 @@
1
1
  = RSCM Changelog
2
2
 
3
+ == Version 0.4.0
4
+
5
+ This release of RSCM modifies mosts API methods to take an options Hash (or alternatively, setting
6
+ the default_options attribute), allowing better capturing of the underlying SCM's IO (stdout/stderr)
7
+
8
+ * Introduced named parameters for several API methods.
9
+ * Added RSCM::VERSION
10
+ * Fixed incorrect arguments to poll_new_revisions in revision_poller.rb
11
+ * Added Base.to_identifier(raw_identifier) for type conversion to the native revision type.
12
+ * Removed support for directory listings (No use case for it anymore, keep things simpler)
13
+ * checkout no longer yields new files, only returns an array (primarily used for testing)
14
+ * Better.popen removed in favour of RSCM::CommandLine.
15
+ * Stdout and stderr logs are now written to disk.
16
+
3
17
  == Version 0.3.16
4
18
 
5
19
  Bugfix release
data/README CHANGED
@@ -1,4 +1,4 @@
1
- = RSCM - Ruby Source Control Management (0.3.16)
1
+ = RSCM - Ruby Source Control Management (0.4.0)
2
2
 
3
3
  RSCM is to SCM what DBI/JDBC/ODBC are to databases - an SCM-independent API for accessing different SCMs. The high level features are roughly:
4
4
 
@@ -8,8 +8,9 @@ RSCM is to SCM what DBI/JDBC/ODBC are to databases - an SCM-independent API for
8
8
  * Add and commit files
9
9
  * Manipluate triggers
10
10
 
11
- Although RSCM's main focus is operations on a working copy of an SCM repository, the API also allows some level of interaction
12
- with the SCM repository itself, like creating new repositories.
11
+ Although RSCM's main focus is operations on a working copy of an SCM repository,
12
+ the API also allows some level of interaction with the SCM repository itself,
13
+ like creating new repositories.
13
14
 
14
15
  == Download
15
16
 
@@ -17,10 +18,14 @@ RSCM is available as a RubyGem, and can be installed like this:
17
18
 
18
19
  gem install rscm
19
20
 
20
- (You may need administer access to do this on a POSIX system).
21
- If you want the latest and greatest, you can get the sources, which live alongside DamageControl's sources:
21
+ (You may need administrator access to do this on a POSIX system).
22
+ If you want the latest and greatest, you can get the sources:
22
23
 
23
- * See the DamageControl Developer Guide at http://hieraki.lavalamp.ca/
24
+ Anonymous:
25
+ svn co svn://svn.damagecontrol.codehaus.org/damagecontrol/scm/trunk/rscm
26
+
27
+ Developers:
28
+ svn co svn+ssh://developer@beaver.codehaus.org/home/projects/damagecontrol/scm/trunk/rscm
24
29
 
25
30
  == Contributors
26
31
 
@@ -33,10 +38,10 @@ If you want the latest and greatest, you can get the sources, which live alongsi
33
38
 
34
39
  * CVS - http://www.nongnu.org/cvs (stable)
35
40
  * Subversion - http://subversion.tigris.org (stable)
36
- * ClearCase - http://www-306.ibm.com/software/awdtools/clearcase (not thoroughly tested)
37
41
 
38
42
  In progress:
39
43
 
44
+ * ClearCase - http://www-306.ibm.com/software/awdtools/clearcase (not thoroughly tested)
40
45
  * Darcs - http://www.abridgegame.org/darcs (very incomplete)
41
46
  * Monotone - http://www.venge.net/monotone (half complete)
42
47
  * Perforce - http://www.perforce.com (nearly complete - a little out of date with recent API)
@@ -48,7 +53,8 @@ Loads! All of them! How to add support for a new one is described further down i
48
53
 
49
54
  == Related projects
50
55
 
51
- * DamageControl - http://damagecontrol.codehaus.org. DamageControl adds a web interface to RSCM and tons of other features for continuous integration.
56
+ * DamageControl - http://damagecontrol.buildpatterns.com. DamageControl is a Continuous Integration system
57
+ built around RSCM and Ruby on Rails.
52
58
 
53
59
  == Sample usage
54
60
 
@@ -57,6 +63,7 @@ Here is an example of how to use RSCM to get a list of revisions (aka changesets
57
63
  require 'rscm'
58
64
 
59
65
  scm = RSCM::Subversion.new("svn://some.server/some/path/trunk")
66
+ scm.default_options = {:stdout => "stdout.log", :stderr => "stderr.log"}
60
67
  # What follows would look the same for any supported SCM
61
68
  revisions = scm.revisions(Time.utc(2004, 11, 10, 12, 34, 22)) # For Subversion, you can also pass a revision number (int)
62
69
  revisions.each do |revision|
data/Rakefile CHANGED
@@ -7,10 +7,11 @@ require 'rake/gempackagetask'
7
7
  require 'rake/contrib/sshpublisher'
8
8
  require 'rake/contrib/rubyforgepublisher'
9
9
  require 'meta_project'
10
+ require 'lib/rscm/version'
10
11
 
11
12
  PKG_BUILD = ENV['PKG_BUILD'] ? '.' + ENV['PKG_BUILD'] : ''
12
13
  PKG_NAME = 'rscm'
13
- PKG_VERSION = '0.3.16' + PKG_BUILD
14
+ PKG_VERSION = RSCM::VERSION::STRING + PKG_BUILD
14
15
  PKG_FILE_NAME = "#{PKG_NAME}-#{PKG_VERSION}"
15
16
 
16
17
  desc "Default Task"
@@ -32,6 +33,9 @@ Rake::TestTask.new { |t|
32
33
  t.libs << "test"
33
34
  t.test_files = fl
34
35
  t.verbose = true
36
+
37
+ # turn on code coverage
38
+ #t.ruby_opts << "-rcoverage/coverage"
35
39
  }
36
40
 
37
41
  rd = Rake::RDocTask.new { |rdoc|
data/lib/rscm.rb CHANGED
@@ -1,6 +1,7 @@
1
1
  require 'rscm/path_converter'
2
2
  require 'rscm/difftool'
3
- require 'rscm/better'
3
+ require 'rscm/platform'
4
+ require 'rscm/command_line'
4
5
  require 'rscm/base'
5
6
  require 'rscm/revision'
6
7
  require 'rscm/revision_poller'
data/lib/rscm/base.rb CHANGED
@@ -3,48 +3,20 @@ require 'rscm/revision'
3
3
  require 'rscm/path_converter'
4
4
 
5
5
  module RSCM
6
- # This class defines the RSCM API. The documentation of the various methods
7
- # uses CVS and Subversion's terminology. (For example, checkout means 'get working copy',
8
- # not 'lock for private edit' as in ClearCase or VSS terminology).
6
+ # This class defines the RSCM API, which offers access to an SCM working copy
7
+ # as well as a 'central' repository.
9
8
  #
10
- # Concrete subclasses of this class provide an API to manage a local working copy
11
- # as well as an associated 'central' repository. The main responsibility is working
12
- # copy operations:
9
+ # Concrete subclasses of this class (concrete adapters) implement the integration
10
+ # with the respective SCMs.
13
11
  #
14
- # * add
15
- # * checkout
16
- # * checked_out?
17
- # * diff
18
- # * edit
19
- # * ls
20
- # * move
21
- # * revisions
22
- # * uptodate?
23
- # * file
24
- # * destroy_working_copy
12
+ # Most of the methods take an optional +options+ Hash (named parameters), allowing
13
+ # the following options:
25
14
  #
26
- # In addition to operations related to working copies, the same instance should provide
27
- # methods to administer the working copy's associated 'central' repository. These are:
15
+ # * <tt>:stdout</tt>: Path to file name where stdout of SCM operations are written.
16
+ # * <tt>:stdout</tt>: Path to file name where stderr of SCM operations are written.
28
17
  #
29
- # * central_exists?
30
- # * create_central
31
- # * can_create_central?
32
- # * import_central
33
- # * install_trigger
34
- # * supports_trigger? / can_install_trigger?
35
- # * trigger_installed?
36
- # * trigger_mechanism
37
- # * uninstall_trigger
38
- #
39
- # Some methods are a bit fuzzy with respect to their relevance to the working copy or
40
- # the associated central repository, as it depends on the nature of the individual underlying
41
- # SCMs. These methods are:
42
- #
43
- # * checkout_command_line
44
- # * label
45
- # * name
46
- # * transactional? / atomic?
47
- # * update_command_line
18
+ # In stead of specifying the +options+ parameters for every API method, it's possible
19
+ # to assign default options via the +default_options+ attribute.
48
20
  #
49
21
  # Some of the methods in this API use +from_identifier+ and +to_identifier+.
50
22
  # These identifiers can be either a UTC Time (according to the SCM's clock)
@@ -54,29 +26,30 @@ module RSCM
54
26
  # If +from_identifier+ or +to_identifier+ are +nil+ they should respectively default to
55
27
  # Time.epoch or Time.infinite.
56
28
  #
57
- # TODO: rename this superclass to 'Base'
58
- #
59
29
  class Base
60
30
 
61
- # TODO: Make revisions yield revisions as they are determined, to avoid
62
- # having to load them all into memory before the method exits. Careful not to
63
- # use yielded revisions to do another scm hit - like get diffs. Some SCMs
64
- # might dead lock on this. Implement a guard for that.
65
- # TODO: Add some visitor support here too?
66
-
67
- public
31
+ attr_accessor :default_options
68
32
 
33
+ # Transforms +raw_identifier+ into the native rype used for revisions.
34
+ def to_identifier(raw_identifier)
35
+ raw_identifier.to_s
36
+ end
37
+
38
+ # Sets the checkout dir (working copy). Should be set prior to most other method
39
+ # invocations (depending on the implementation).
69
40
  def checkout_dir=(dir)
70
41
  @checkout_dir = PathConverter.filepath_to_nativepath(dir, false)
71
42
  end
72
43
 
44
+ # Gets the working copy directory.
73
45
  def checkout_dir
74
46
  @checkout_dir
75
47
  end
76
48
 
77
- def to_yaml_properties
49
+ def to_yaml_properties #:nodoc:
78
50
  props = instance_variables
79
51
  props.delete("@checkout_dir")
52
+ props.delete("@default_options")
80
53
  props.sort!
81
54
  end
82
55
 
@@ -85,8 +58,7 @@ module RSCM
85
58
  FileUtils.rm_rf(checkout_dir) unless checkout_dir.nil?
86
59
  end
87
60
 
88
- # Whether the physical SCM represented by this instance exists.
89
- #
61
+ # Whether or not the SCM represented by this instance exists.
90
62
  def central_exists?
91
63
  # The default implementation assumes yes - override if it can be
92
64
  # determined programmatically.
@@ -94,7 +66,6 @@ module RSCM
94
66
  end
95
67
 
96
68
  # Whether or not this SCM is transactional (atomic).
97
- #
98
69
  def transactional?
99
70
  false
100
71
  end
@@ -103,12 +74,12 @@ module RSCM
103
74
  # Creates a new 'central' repository. This is intended only for creation of 'central'
104
75
  # repositories (not for working copies). You shouldn't have to call this method if a central repository
105
76
  # already exists. This method is used primarily for testing of RSCM, but can also
106
- # be used if you *really* want to create a central repository.
77
+ # be used if you *really* want to use RSCM to create a central repository.
107
78
  #
108
79
  # This method should throw an exception if the repository cannot be created (for
109
80
  # example if the repository is 'remote' or if it already exists).
110
81
  #
111
- def create_central
82
+ def create_central(options={})
112
83
  raise NotImplementedError
113
84
  end
114
85
 
@@ -125,28 +96,29 @@ module RSCM
125
96
  end
126
97
 
127
98
  # Adds +relative_filename+ to the working copy.
128
- def add(relative_filename)
99
+ def add(relative_filename, options={})
129
100
  raise NotImplementedError
130
101
  end
131
102
 
132
103
  # Schedules a move of +relative_src+ to +relative_dest+
133
104
  # Should not take effect in the central repository until
134
105
  # +commit+ is invoked.
135
- def move(relative_src, relative_dest)
106
+ def move(relative_src, relative_dest, options={})
136
107
  raise NotImplementedError
137
108
  end
138
109
 
139
- # Recursively imports files from a +dir+ into the central scm
140
- def import_central(dir, message)
141
- raise "Not implemented"
110
+ # Recursively imports files from <tt>:dir</tt> into the central scm,
111
+ # using commit message <tt>:message</tt>
112
+ def import_central(options)
113
+ raise NotImplementedError
142
114
  end
143
115
 
144
116
  # Open a file for edit - required by scms that check out files in read-only mode e.g. perforce
145
- def edit(file)
117
+ def edit(file, options={})
146
118
  end
147
119
 
148
120
  # Commit (check in) modified files.
149
- def commit(message)
121
+ def commit(message, options={})
150
122
  raise NotImplementedError
151
123
  end
152
124
 
@@ -166,12 +138,14 @@ module RSCM
166
138
  # For some SCMs this is not possible, or at least very hard. In that case, just override
167
139
  # the checkout_silent method instead of this method (should be protected).
168
140
  #
169
- def checkout(to_identifier=Time.infinity) # :yield: file
141
+ def checkout(to_identifier=Time.infinity, options={}) # :yield: file
142
+ to_identifier=Time.infinity if to_identifier.nil?
143
+
170
144
  # the OS doesn't store file timestamps with fractions.
171
145
  before_checkout_time = Time.now.utc - 1
172
146
 
173
147
  # We expect subclasses to implement this as a protected method (unless this whole method is overridden).
174
- checkout_silent(to_identifier)
148
+ checkout_silent(to_identifier, options)
175
149
  files = Dir["#{@checkout_dir}/**/*"]
176
150
  added = []
177
151
  files.each do |file|
@@ -184,9 +158,6 @@ module RSCM
184
158
  File.file?(path)
185
159
  end
186
160
  relative_added_file_paths = to_relative(checkout_dir, added_file_paths)
187
- relative_added_file_paths.each do |path|
188
- yield path if block_given?
189
- end
190
161
  relative_added_file_paths
191
162
  end
192
163
 
@@ -194,7 +165,7 @@ module RSCM
194
165
  # and +to_identifier+ (inclusive). If +relative_path+ is specified, the result will only contain
195
166
  # revisions pertaining to that path.
196
167
  #
197
- def revisions(from_identifier, to_identifier=Time.infinity, relative_path=nil)
168
+ def revisions(from_identifier, options={})
198
169
  raise NotImplementedError
199
170
  end
200
171
 
@@ -208,11 +179,6 @@ module RSCM
208
179
  HistoricFile.new(relative_path, dir, self)
209
180
  end
210
181
 
211
- # Returns an Array of the children under +relative_path+
212
- def ls(relative_path)
213
- raise NotImplementedError
214
- end
215
-
216
182
  # Opens a revision_file
217
183
  def open(revision_file, &block) #:yield: io
218
184
  raise NotImplementedError
@@ -222,12 +188,8 @@ module RSCM
222
188
  # repository's revision/time identified by +identifier+.
223
189
  # If +identifier+ is nil, 'HEAD' of repository should be assumed.
224
190
  #
225
- # TODO: rename to in_synch?
226
191
  def uptodate?(identifier)
227
- # Suboptimal algorithm that works for all SCMs.
228
- # Subclasses can override this to improve efficiency.
229
-
230
- revisions(identifier).empty?
192
+ raise NotImplementedError
231
193
  end
232
194
 
233
195
  # Whether the project is checked out from the central repository or not.
@@ -300,6 +262,19 @@ module RSCM
300
262
  end
301
263
 
302
264
  protected
265
+
266
+ # Wrapper for CommandLine.execute that provides default values for
267
+ # dir plus any options set in default_options (typically stdout and stderr).
268
+ def execute(cmd, options={}, &proc)
269
+ default_dir = @checkout_dir.nil? ? Dir.pwd : @checkout_dir
270
+ options = {:dir => default_dir}.merge(default_options).merge(options)
271
+ begin
272
+ CommandLine.execute(cmd, options, &proc)
273
+ rescue CommandLine::OptionError => e
274
+ e.message += "\nEither specify default_options on the scm object, or pass the required options to the method"
275
+ raise e
276
+ end
277
+ end
303
278
 
304
279
  # Takes an array of +absolute_paths+ and turns it into an array
305
280
  # of paths relative to +dir+
@@ -0,0 +1,66 @@
1
+ module RSCM
2
+ # Utility for running a +cmd+ in a +dir+ with a specified +env+.
3
+ # If a block is passed, the standard out stream is passed to that block (and returns)
4
+ # the result from the block. Otherwise, if a block is not passed, standard output
5
+ # is redirected to +stdout_file+. The standard error stream is always redirected
6
+ # to +stderr_file+. Note that both +stdout_file+ and +stderr_file+ must always
7
+ # be specified with non-nil values, as both of them will always have the command lines
8
+ # written to them.
9
+ module CommandLine
10
+ class OptionError < StandardError; end
11
+ class ExecutionError < StandardError
12
+ attr_reader :cmd, :dir, :exitstatus, :stderr
13
+ def initialize(cmd, dir, exitstatus, stderr); @cmd, @dir, @exitstatus, @stderr = cmd, dir, exitstatus, stderr; end
14
+ def to_s
15
+ "\ndir : #{@dir}\n" +
16
+ "command : #{@cmd}\n" +
17
+ "exitstatus: #{@exitstatus}\n" +
18
+ "stderr : #{@stderr}\n"
19
+ end
20
+ end
21
+
22
+ def execute(cmd, options={}, &proc)
23
+ options = {
24
+ :dir => Dir.pwd,
25
+ :env => {},
26
+ :exitstatus => 0
27
+ }.merge(options)
28
+
29
+ raise OptionError.new(":stdout can't be nil") if options[:stdout].nil?
30
+ raise OptionError.new(":stderr can't be nil") if options[:stderr].nil?
31
+ options[:stdout] = File.expand_path(options[:stdout])
32
+ options[:stderr] = File.expand_path(options[:stderr])
33
+
34
+ commands = cmd.split("&&").collect{|c| c.strip}
35
+ Dir.chdir(options[:dir]) do
36
+ redirected_cmd = commands.collect do |c|
37
+ redirection = block_given? ? "#{c} 2>> #{options[:stderr]}" : "#{c} >> #{options[:stdout]} 2>> #{options[:stderr]}"
38
+
39
+ "echo #{RSCM::Platform.prompt} #{c} >> #{options[:stdout]} && " +
40
+ "echo #{RSCM::Platform.prompt} #{c} >> #{options[:stderr]} && " +
41
+ redirection
42
+ end.join(" && ")
43
+
44
+ options[:env].each{|k,v| ENV[k]=v}
45
+ begin
46
+ IO.popen(redirected_cmd) do |io|
47
+ if(block_given?)
48
+ return(proc.call(io))
49
+ else
50
+ io.read
51
+ end
52
+ end
53
+ rescue Errno::ENOENT => e
54
+ File.open(options[:stderr], "a") {|io| io.write(e.message)}
55
+ ensure
56
+ if($?.exitstatus != options[:exitstatus])
57
+ error_message = File.exist?(options[:stderr]) ? File.read(options[:stderr]) : "#{options[:stderr]} doesn't exist"
58
+ raise ExecutionError.new(cmd, options[:dir], $?.exitstatus, error_message)
59
+ end
60
+ end
61
+ end
62
+ $?.exitstatus
63
+ end
64
+ module_function :execute
65
+ end
66
+ end
@@ -13,8 +13,8 @@ module RSCM
13
13
  end
14
14
 
15
15
  # Returns an Array of RevisionFile - from Time.epoch until Time.infinity (now)
16
- def revision_files
17
- @scm.revisions(Time.epoch, Time.infinity, @relative_path).collect do |revision|
16
+ def revision_files(options={})
17
+ @scm.revisions(Time.epoch, options.dup.merge({:to_identifier => Time.infinity, :relative_path => @relative_path})).collect do |revision|
18
18
  if revision.files.length != 1
19
19
  files_s = revision.files.collect{|f| f.to_s}.join("\n")
20
20
  raise "The file-specific revision didn't have exactly one file, but #{revision.files.length}:\n#{files_s}"
@@ -26,9 +26,5 @@ module RSCM
26
26
  end
27
27
  end
28
28
 
29
- def children
30
- raise "Not a directory" unless directory?
31
- @scm.ls(@relative_path)
32
- end
33
29
  end
34
30
  end