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 +14 -0
- data/README +15 -8
- data/Rakefile +5 -1
- data/lib/rscm.rb +2 -1
- data/lib/rscm/base.rb +51 -76
- data/lib/rscm/command_line.rb +66 -0
- data/lib/rscm/historic_file.rb +2 -6
- data/lib/rscm/path_converter.rb +1 -15
- data/lib/rscm/platform.rb +25 -0
- data/lib/rscm/revision_file.rb +4 -4
- data/lib/rscm/revision_poller.rb +23 -20
- data/lib/rscm/scm/cvs.rb +108 -136
- data/lib/rscm/scm/cvs_log_parser.rb +2 -8
- data/lib/rscm/scm/monotone.rb +1 -1
- data/lib/rscm/scm/subversion.rb +75 -102
- data/lib/rscm/time_ext.rb +2 -1
- data/lib/rscm/version.rb +13 -0
- data/test/rscm/generic_scm_tests.rb +38 -49
- data/test/rscm/scm/cvs_test.rb +0 -25
- metadata +5 -3
- data/lib/rscm/better.rb +0 -12
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.
|
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,
|
12
|
-
with the SCM repository itself,
|
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
|
21
|
-
If you want the latest and greatest, you can get the 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
|
-
|
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.
|
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 =
|
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
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
|
7
|
-
#
|
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
|
11
|
-
#
|
12
|
-
# copy operations:
|
9
|
+
# Concrete subclasses of this class (concrete adapters) implement the integration
|
10
|
+
# with the respective SCMs.
|
13
11
|
#
|
14
|
-
#
|
15
|
-
#
|
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
|
-
#
|
27
|
-
#
|
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
|
-
#
|
30
|
-
#
|
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
|
-
|
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
|
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
|
140
|
-
|
141
|
-
|
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,
|
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
|
-
|
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
|
data/lib/rscm/historic_file.rb
CHANGED
@@ -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
|