rscm 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (43) hide show
  1. data/README +198 -0
  2. data/Rakefile +118 -0
  3. data/ext/rscm.jar +0 -0
  4. data/lib/rscm.rb +10 -0
  5. data/lib/rscm/abstract_log_parser.rb +49 -0
  6. data/lib/rscm/abstract_scm.rb +229 -0
  7. data/lib/rscm/changes.rb +271 -0
  8. data/lib/rscm/cvs/cvs.rb +363 -0
  9. data/lib/rscm/cvs/cvs_log_parser.rb +161 -0
  10. data/lib/rscm/darcs/darcs.rb +69 -0
  11. data/lib/rscm/line_editor.rb +46 -0
  12. data/lib/rscm/logging.rb +5 -0
  13. data/lib/rscm/monotone/monotone.rb +107 -0
  14. data/lib/rscm/mooky/mooky.rb +13 -0
  15. data/lib/rscm/parser.rb +39 -0
  16. data/lib/rscm/path_converter.rb +92 -0
  17. data/lib/rscm/perforce/perforce.rb +415 -0
  18. data/lib/rscm/starteam/starteam.rb +99 -0
  19. data/lib/rscm/svn/svn.rb +337 -0
  20. data/lib/rscm/svn/svn_log_parser.rb +134 -0
  21. data/lib/rscm/time_ext.rb +125 -0
  22. data/test/rscm/apply_label_scm_tests.rb +26 -0
  23. data/test/rscm/changes_fixture.rb +20 -0
  24. data/test/rscm/changes_test.rb +129 -0
  25. data/test/rscm/cvs/cvs_log_parser_test.rb +575 -0
  26. data/test/rscm/cvs/cvs_test.rb +22 -0
  27. data/test/rscm/darcs/darcs_test.rb +14 -0
  28. data/test/rscm/difftool_test.rb +40 -0
  29. data/test/rscm/file_ext.rb +12 -0
  30. data/test/rscm/generic_scm_tests.rb +282 -0
  31. data/test/rscm/line_editor_test.rb +76 -0
  32. data/test/rscm/mockit.rb +130 -0
  33. data/test/rscm/mockit_test.rb +117 -0
  34. data/test/rscm/monotone/monotone_test.rb +19 -0
  35. data/test/rscm/mooky/mooky_test.rb +14 -0
  36. data/test/rscm/parser_test.rb +47 -0
  37. data/test/rscm/path_converter_test.rb +52 -0
  38. data/test/rscm/perforce/perforce_test.rb +14 -0
  39. data/test/rscm/starteam/starteam_test.rb +36 -0
  40. data/test/rscm/svn/svn_log_parser_test.rb +111 -0
  41. data/test/rscm/svn/svn_test.rb +28 -0
  42. data/test/rscm/tempdir.rb +12 -0
  43. metadata +81 -0
data/README ADDED
@@ -0,0 +1,198 @@
1
+ = RSCM - Ruby Source Control Management
2
+
3
+ RSCM is to SCM what ODBC/JDBC is to databases - a common API to access a wide variety of SCMs. The features are roughly:
4
+
5
+ * Check out a working copy (with possibility to specify branch/date/label)
6
+ * Get changesets (changesets are emulated for non-transactional SCMs like CVS and StarTeam)
7
+ * Get diffs
8
+ * Add and commit files
9
+ * Manipluate triggers
10
+
11
+ == Download
12
+
13
+ RSCM is available as a RubyGem, and can be installed like this:
14
+
15
+ gem install rscm
16
+
17
+ If you want the latest and greatest, you can get the sources, which live alongside DamageControl's sources:
18
+
19
+ http://damagecontrol.codehaus.org/Installing
20
+
21
+ == Supported SCMs
22
+
23
+ * CVS - http://www.cvshome.org (stable)
24
+ * Perforce - http://www.perforce.com (not thoroughly tested)
25
+ * StarTeam - http://www.borland.com/starteam (not thoroughly tested)
26
+ * Subversion - http://subversion.tigris.org (stable)
27
+
28
+ In progress:
29
+
30
+ * Darcs - http://www.abridgegame.org/darcs (very incomplete)
31
+ * Monotone - http://www.venge.net/monotone (half way there)
32
+
33
+ Planned:
34
+
35
+ Loads! All of them! How to add support for a new one is described further down in this file.
36
+
37
+ == Related projects
38
+
39
+ * DamageControl - http://damagecontrol.codehaus.org. DamageControl adds a web interface to RSCM and tons of other features for continuous integration.
40
+
41
+ == Sample usage
42
+
43
+ Here is an example of how to use RSCM to get a list of changesets from a CVS repository:
44
+
45
+ require 'rscm'
46
+
47
+ scm = RSCM::SVN.new("svn://some.server/some/path/trunk", "trunk")
48
+
49
+ scm.checkout("mycheckout")
50
+ changesets = scm.changesets("mycheckout", Time.utc(2004, 11, 10, 12, 34, 22))
51
+ changesets.each do |changeset|
52
+ puts changeset
53
+ end
54
+
55
+ === Using visitors
56
+
57
+ Although ChangeSets and ChangeSet support external iteration (as in the example above), the preferred way
58
+ to operate on them is to let a ChangeSets object +accept+ a visitor. A visitor
59
+ must respond to the following methods:
60
+
61
+ def visit_changesets(changesets); end
62
+ def visit_changeset(changeset); end
63
+ def visit_change(change); end
64
+
65
+ === Getting diffs
66
+
67
+ RSCM allows you to get diffs for changesets. This is done in the following way:
68
+
69
+ See the DamageControl sources for more detailed examples (DamageControl persists diffs to disk
70
+ and presents them as colourised diffs in its user interface).
71
+
72
+ == Future plans
73
+
74
+ === Cross-SCM synchronisation
75
+
76
+ RSCM could be used as a tool to migrate files from one SCM to another (of a different type)
77
+ while keeping the history. -Similar to cvs2svn.
78
+
79
+ RSCM could also be used as a continuous synchronisation service between SCMs. This can be very useful
80
+ when you have to work with an SCM and you'd rather use a different one. RSCM could synchronise between
81
+ the central SCM and one that you set up on your local machine.
82
+
83
+ = Implementing support for a new SCM
84
+
85
+ We'd be happy to receive contributions for more SCMs. You can always
86
+ file a JIRA issue and hope for someone to implement it for you, or
87
+ you can do it yourself. The rest of this file should get you started.
88
+
89
+ N.B. IF YOU START IMPLEMENTING A NEW RSCM PLUGIN, PLEASE SUBMIT YOUR CODE
90
+ TO JIRA AT AN EARLY STAGE (BEFORE IT'S COMPLETE). THIS WAY IT'S EASIER
91
+ FOR EXISTING DEVELOPERS TO PROVIDE TIPS AND HELP UNDERWAY.
92
+
93
+ Let's write an RSCM implementation for the imaginary SCM called Mooky.
94
+ You should be able to use the same recipe for most SCMs.
95
+
96
+ == Writing the API
97
+
98
+ Start by writing a test:
99
+
100
+ test/rscm/mooky/mooky_test.rb
101
+
102
+ By including GenericSCMTests your test will automatically include the
103
+ acceptance test suite for RSCM. By doing this you'll actually follow a TDD
104
+ approach for your new Mooky class - except that the tests are already written
105
+ for you!
106
+
107
+ IMPORTANT NOTE: If your SCM doesn't provide an easy way to create new local repositories
108
+ - such as with StarTeam - you're probably better off writing the tests from scratch and not
109
+ include GenericSCMTests. Instead, just make sure you have an SCM repository set up somewhere
110
+ and write tests to work against that repository. This way you won't be able to pass the
111
+ generic acceptance test suite, and other people (like the RSCM dev team) will probably
112
+ not be able to run the tests for it. -But it's better than nothing. We'll happily accept
113
+ contributions that don't use the generic tests, although it would be best if they did.
114
+
115
+ OK, back to mooky. As you will see in a minute, the generic test suite will be of great
116
+ help when developing the Mooky class. The tests will attempt to check in some sample files
117
+ and call various methods on the mooky object to verify that it behaves correctly according
118
+ to the RSCM API. (The sample files consist of some java sources, but you don't need Java installed.
119
+ They're just files).
120
+
121
+ Let's implement the Mooky class. Take a look at.
122
+
123
+ lib/rscm/mooky/mooky.rb
124
+
125
+ Try running Mooky's test:
126
+
127
+ rake test TEST=test/rscm/mooky/mooky_test.rb
128
+
129
+ Whoops - we got some failures! It failed because our checkout method returned
130
+ nothing (nil). Let'see if we can get the a little further by implementing this
131
+ method.
132
+
133
+ The Mooky SCM happens to have a command line utility to perform a checkout.
134
+ From the command line a checkout with mooky would be done like this:
135
+
136
+ cd somewhere
137
+ mooky checkout --repo mooky://some/where/else
138
+
139
+ Running this command will print the following to standard out:
140
+
141
+ checkout build.xml
142
+ checkout project.xml
143
+ checkout src/java/com/thoughtworks/damagecontrolled/Thingy.java
144
+ checkout src/test/com/thoughtworks/damagecontrolled/ThingyTestCase.java
145
+
146
+ What we need to do is to execute these commands from Ruby. We also need to
147
+ parse the output from the mooky command to determine the files that were checked out,
148
+ so that we can return an array with the file names of the checked out files (the method
149
+ should also yield each file name as the execution proceeds).
150
+
151
+ Once your checkout command works okay, the test will get you a little further. Just keep
152
+ on going until all tests pass.
153
+
154
+ NOTE: If the SCM doesn't have a command line utility or a 3rd party Ruby API, but instead
155
+ provides libraries (perhaps in C), then you should consider writing a Ruby C extension
156
+ instead.
157
+
158
+ If the SCM has a Java interface, you can take the same approach as for StarTeam. There are
159
+ Java classes for ChangeSets that allow easy interaction between Ruby and Java over YAML.
160
+ You can reuse these classes for other Java based SCMs (if there are any, I don't know).
161
+
162
+ == Writing the web interface (DamageControl only)
163
+
164
+ If you're contributing a new SCM it would be nice if you took the time to implement a web interface
165
+ that can be used to embed it into DamageControl.
166
+
167
+ In trunk/damagecontrol, start by creating +app/views/project/_mooky.rhtml+.
168
+
169
+ Create a table with 2 columns and a row for each +attr_accessor+ in the Mooky class, preferrably
170
+ with some explanatory text for the users so they know how to fill it in. See how the tooltips
171
+ are used in some of the existing implementations.
172
+
173
+ You also need a javascript section at the top
174
+ with a function called +mooky_init()+. This will be called when the page is loaded. If you don't
175
+ have plans to do anything fancy here, just leave it blank.
176
+
177
+ === Wiring it all up
178
+
179
+ Just edit +lib/rscm.rb+ and +require+ your new scm class. Now you should see it coming up
180
+ on the Source Control tab in the web interface.
181
+
182
+ = Building RSCM
183
+ This section is for developers who are new to ruby development and do not already know how to build and install Ruby gems.
184
+
185
+ You need to install rubygems from http://rubyforge.org/projects/rubygems
186
+ Afterwards you need to install rake and rails
187
+
188
+ gem install rake
189
+
190
+ Now change to the RSCM root directory and type
191
+
192
+ rake gem
193
+
194
+ This will create a gem for RSCM. To install this gem, you have to change to the pgk directory and type
195
+
196
+ sudo gem install pkg/rscm-0.1.0.gem
197
+
198
+ Now you can use RSCM in other Ruby apps.
data/Rakefile ADDED
@@ -0,0 +1,118 @@
1
+ require 'rubygems'
2
+ require 'rake'
3
+ require 'rake/testtask'
4
+ require 'rake/rdoctask'
5
+ require 'rake/packagetask'
6
+ require 'rake/gempackagetask'
7
+ require 'rake/contrib/compositepublisher'
8
+ require 'rake/contrib/sshpublisher'
9
+ require 'rake/contrib/rubyforgepublisher'
10
+
11
+ PKG_BUILD = ENV['PKG_BUILD'] ? '.' + ENV['PKG_BUILD'] : ''
12
+ PKG_NAME = 'rscm'
13
+ PKG_VERSION = '0.1.0' + PKG_BUILD
14
+ PKG_FILE_NAME = "#{PKG_NAME}-#{PKG_VERSION}"
15
+
16
+ desc "Default Task"
17
+ task :default => [ :all ]
18
+ task :all => [:ant, :gem]
19
+
20
+ # Run the unit tests
21
+ # To run a specific test: rake test TEST=path/to/test
22
+ fl = FileList.new('test/**/*_test.rb')
23
+ fl.exclude('test/**/mooky*.rb')
24
+ fl.exclude('test/**/monotone*.rb') # Incomplete/unsupported for now - reactivate when more complete!
25
+ fl.exclude('test/**/darcs*.rb') # Incomplete/unsupported for now - reactivate when more complete!
26
+ fl.exclude('test/**/perforce*.rb') # Incomplete/unsupported for now - reactivate when more complete!
27
+ fl.exclude('test/**/starteam*.rb') # Too bloody hard to test without a StarTeam server license! Tested ad-hoc.
28
+ Rake::TestTask.new { |t|
29
+ t.libs << "test"
30
+ t.test_files = fl
31
+ t.verbose = true
32
+ }
33
+
34
+ rd = Rake::RDocTask.new { |rdoc|
35
+ rdoc.title = 'RSCM - Ruby Source Control Management API'
36
+ rdoc.options << '--line-numbers' << '--inline-source'
37
+ rdoc.rdoc_files.include('README')
38
+ rdoc.rdoc_files.include('lib/**/*.rb')
39
+ rdoc.rdoc_files.include('docs/**/*.rd')
40
+ }
41
+
42
+ task :ant do |t|
43
+ if(!ENV['RSCM_STARTEAM'])
44
+ puts "WARNING - NOT BUILDING STARTEAM SUPPORT SINCE 'RSCM_STARTEAM' IS NOT DEFINED."
45
+ else
46
+ ant = RUBY_PLATFORM == "i386-mswin32" ? "ant.bat" : system("which ant.sh") ? "ant.sh" : "ant"
47
+ IO.popen("#{ant} -f ext/java/build.xml clean jar") do |io|
48
+ io.each_line do |line|
49
+ puts line
50
+ end
51
+ end
52
+ end
53
+ end
54
+
55
+ PKG_FILES = FileList[
56
+ '[A-Z]*',
57
+ 'lib/**/*.rb',
58
+ 'test/**/*.rb',
59
+ 'doc/**/*',
60
+ 'ext/rscm.jar'
61
+ ]
62
+
63
+ if ! defined?(Gem)
64
+ puts "Package Target requires RubyGEMs"
65
+ else
66
+ spec = Gem::Specification.new do |s|
67
+
68
+ #### Basic information.
69
+
70
+ s.name = PKG_NAME
71
+ s.version = PKG_VERSION
72
+ s.summary = "RSCM - Ruby Source Control Management"
73
+ s.description = <<-EOF
74
+ RSCM is a Ruby library for various Source Control Management (SCM) systems.
75
+ EOF
76
+
77
+ #### Which files are to be included in this gem? Everything! (Except CVS directories.)
78
+
79
+ s.files = PKG_FILES.to_a
80
+
81
+ #### Load-time details: library and application (you will need one or both).
82
+
83
+ s.require_path = 'lib'
84
+ s.autorequire = 'rscm'
85
+
86
+ #### Documentation and testing.
87
+
88
+ s.has_rdoc = true
89
+ s.extra_rdoc_files = rd.rdoc_files.reject { |fn| fn =~ /\.rb$/ }.to_a
90
+ rd.options.each do |op|
91
+ s.rdoc_options << op
92
+ end
93
+
94
+ #### Author and project details.
95
+
96
+ s.author = "Aslak Hellesoy"
97
+ s.email = "dev@damagecontrol.codehaus.org"
98
+ s.homepage = "http://rscm.rubyforge.org"
99
+ s.rubyforge_project = "rscm"
100
+ end
101
+
102
+ Rake::GemPackageTask.new(spec) do |pkg|
103
+ pkg.need_zip = true
104
+ pkg.need_tar = true
105
+ end
106
+ end
107
+
108
+ task :publish => [:rdoc] do
109
+ publisher = Rake::CompositePublisher.new
110
+ publisher.add Rake::RubyForgePublisher.new('rscm', 'aslak_hellesoy')
111
+ #publisher.add Rake::SshFilePublisher.new(
112
+ # 'umlcoop',
113
+ # 'htdocs/software/rake',
114
+ # '.',
115
+ # 'rake.blurb')
116
+
117
+ publisher.upload
118
+ end
data/ext/rscm.jar ADDED
Binary file
data/lib/rscm.rb ADDED
@@ -0,0 +1,10 @@
1
+ require 'rscm/abstract_scm'
2
+ require 'rscm/changes'
3
+ require 'rscm/logging'
4
+ require 'rscm/time_ext'
5
+ # scms
6
+ require 'rscm/mooky/mooky' # for demo purposes only
7
+ require 'rscm/cvs/cvs'
8
+ require 'rscm/darcs/darcs'
9
+ require 'rscm/starteam/starteam'
10
+ require 'rscm/svn/svn'
@@ -0,0 +1,49 @@
1
+ module RSCM
2
+
3
+ # NOTE: It is recommended to use the Parser class in parser.rb
4
+ # as a basis for new SCM parsers
5
+ #
6
+ # Some utilities for log-parsers
7
+ # TODO: make this a module and remove the attr_reader
8
+ class AbstractLogParser
9
+
10
+ attr_reader :io
11
+
12
+ def initialize(io)
13
+ @io = io
14
+ @current_line_number = 0
15
+ @had_error = false
16
+ end
17
+
18
+ def read_until_matching_line(regexp)
19
+ return nil if io.eof?
20
+ result = ""
21
+ io.each_line do |line|
22
+ @current_line_number += 1
23
+ line.gsub!(/\r\n$/, "\n")
24
+ break if line=~regexp
25
+ result<<line
26
+ end
27
+ if result.strip == ""
28
+ read_until_matching_line(regexp)
29
+ else
30
+ result
31
+ end
32
+ end
33
+
34
+ def convert_all_slashes_to_forward_slashes(file)
35
+ file.gsub(/\\/, "/")
36
+ end
37
+
38
+ def error(msg)
39
+ @had_error=true
40
+ $stderr.puts(msg + "\ncurrent line: #{@current_line}\nstack trace:\n")
41
+ $stderr.puts(caller.backtrace.join('\n\t'))
42
+ end
43
+
44
+ def had_error?
45
+ @had_error
46
+ end
47
+ end
48
+
49
+ end
@@ -0,0 +1,229 @@
1
+ require 'fileutils'
2
+ require 'rscm/changes'
3
+ require 'rscm/path_converter'
4
+
5
+ class String
6
+ # Turns a String into a Time or an int
7
+ def to_identifier
8
+ if(self =~ /20\d\d\d\d\d\d\d\d\d\d\d\d/)
9
+ # Assume it's a timestamp string - convert to time.
10
+ Time.parse_ymdHMS(self)
11
+ else
12
+ # Assume it's an arbitrary integer.
13
+ self.to_i
14
+ end
15
+ end
16
+ end
17
+
18
+ class Time
19
+ def to_s
20
+ self.ymdHMS
21
+ end
22
+ end
23
+
24
+ module RSCM
25
+ # This class defines the RSCM API. The documentation of the various methods
26
+ # uses CVS' terminology.
27
+ #
28
+ # Some of the methods in this API use +from_identifier+ and +to_identifier+.
29
+ # These identifiers can be either a UTC Time (according to the SCM's clock)
30
+ # or a String representing a label (according to the SCM's label system).
31
+ #
32
+ # If +from_identifier+ or +to_identifier+ are +nil+ they respectively represent
33
+ # epoch or the infinite future.
34
+ #
35
+ # Most of the methods take a mandatory +checkout_dir+ - even if this may seem
36
+ # unnecessary. The reason is that some SCMs require a working copy to be checked
37
+ # out in order to perform certain operations. In order to develop portable code,
38
+ # you should always pass in a non +nil+ value for +checkout_dir+.
39
+ #
40
+ class AbstractSCM
41
+ include FileUtils
42
+
43
+ # TODO: Make changesets yield changesets as they are determined, to avoid
44
+ # having to load them all into memory before the method exits. Careful not to
45
+ # use yielded changesets to do another scm hit - like get diffs. Some SCMs
46
+ # might dead lock on this. Implement a guard for that.
47
+ # TODO: Add some visitor support here too?
48
+
49
+ public
50
+
51
+ # Whether the physical SCM represented by this instance exists.
52
+ #
53
+ def exists?
54
+ # The default implementation assumes yes - override if it can be
55
+ # determined programmatically.
56
+ true
57
+ end
58
+
59
+ # Whether or not this SCM is transactional.
60
+ #
61
+ def transactional?
62
+ false
63
+ end
64
+
65
+ # Creates a new repository. Throws an exception if the
66
+ # repository cannot be created.
67
+ #
68
+ def create
69
+ end
70
+
71
+ # Whether a repository can be created.
72
+ #
73
+ def can_create?
74
+ end
75
+
76
+ # Recursively imports files from a directory
77
+ #
78
+ def import(dir, message)
79
+ end
80
+
81
+ # The display name of this SCM
82
+ #
83
+ def name
84
+ # Should be overridden by subclasses to display a nicer name
85
+ self.class.name
86
+ end
87
+
88
+ # Gets the label for the working copy currently checked out in +checkout_dir+.
89
+ #
90
+ def label(checkout_dir)
91
+ end
92
+
93
+ # Open a file for edit - required by scms that checkout files in read-only mode e.g. perforce
94
+ #
95
+ def edit(file)
96
+ end
97
+
98
+ # Checks out or updates contents from an SCM to +checkout_dir+ - a local working copy.
99
+ #
100
+ # The +to_identifier+ parameter may be optionally specified to obtain files up to a
101
+ # particular time or label. +time_label+ should either be a Time (in UTC - according to
102
+ # the clock on the SCM machine) or a String - reprsenting a label.
103
+ #
104
+ # This method will yield the relative file name of each checked out file, and also return
105
+ # them in an array. Only files, not directories, should be yielded/returned.
106
+ #
107
+ # This method should be overridden for SCMs that are able to yield checkouts as they happen.
108
+ # For some SCMs this is not possible, or at least very hard. In that case, just override
109
+ # the checkout_silent method instead of this method (should be protected).
110
+ def checkout(checkout_dir, to_identifier=Time.infinity) # :yield: file
111
+ before = Dir["#{checkout_dir}/**/*"]
112
+
113
+ # We expect subclasses to implement this as a protected method (unless this whole method is overridden).
114
+ checkout_silent(checkout_dir, to_identifier)
115
+
116
+ after = Dir["#{checkout_dir}/**/*"]
117
+ added = (after - before)
118
+ ignore_paths.each do |regex|
119
+ added.delete_if{|path| path =~ regex}
120
+ end
121
+ added_file_paths = added.find_all do |path|
122
+ File.file?(path)
123
+ end
124
+ relative_added_file_paths = to_relative(checkout_dir, added_file_paths)
125
+ relative_added_file_paths.each do |path|
126
+ yield path
127
+ end
128
+ relative_added_file_paths
129
+ end
130
+
131
+ # Returns a ChangeSets object for the period specified by +from_identifier+
132
+ # and +to_identifier+. See AbstractSCM for details about the parameters.
133
+ #
134
+ def changesets(checkout_dir, from_identifier, to_identifier=Time.infinity, files=nil)
135
+ # Should be overridden by subclasses
136
+ changesets = ChangeSets.new
137
+ changesets.add(
138
+ Change.new(
139
+ "up/the/chimney",
140
+ "DamageControl",
141
+ "The #{name} SCM class in #{__FILE__} doesn't\n" +
142
+ "correctly implement the changesets method. This is\n" +
143
+ "not a real changeset, but a hint to the developer to go and implement it.\n\n" +
144
+ "Do It Now!",
145
+ "999",
146
+ Time.now.utc
147
+ )
148
+ )
149
+ changesets
150
+ end
151
+
152
+ # Whether the working copy in +checkout_dir+ is uptodate with the repository
153
+ # since +from_identifier+.
154
+ #
155
+ def uptodate?(checkout_dir, from_identifier)
156
+ # Suboptimal algorithm that works for all SCMs.
157
+ # Subclasses can override this to increase efficiency.
158
+
159
+ changesets(checkout_dir, from_identifier).empty?
160
+ end
161
+
162
+ # Whether the project is checked out or not.
163
+ # Subclasses should override this to check for SCM-specific administrative
164
+ # files if appliccable
165
+ def checked_out?(checkout_dir)
166
+ File.exists?(checkout_dir)
167
+ end
168
+
169
+ # Whether triggers are supported by this SCM
170
+ def supports_trigger?
171
+ # The default implementation assumes no - override if it can be
172
+ # determined programmatically.
173
+ false
174
+ end
175
+
176
+ # Installs +trigger_command+ in the SCM.
177
+ # The +install_dir+ parameter should be an empty local
178
+ # directory that the SCM can use for temporary files
179
+ # if necessary (CVS needs this to check out its administrative files).
180
+ # Most implementations will ignore this parameter.
181
+ #
182
+ def install_trigger(trigger_command, install_dir)
183
+ end
184
+
185
+ # Uninstalls +trigger_command+ from the SCM.
186
+ #
187
+ def uninstall_trigger(trigger_command, install_dir)
188
+ end
189
+
190
+ # Whether the command denoted by +trigger_command+ is installed in the SCM.
191
+ #
192
+ def trigger_installed?(trigger_command, install_dir)
193
+ end
194
+
195
+ # The command line to run in order to check out a fresh working copy.
196
+ #
197
+ def checkout_commandline(to_identifier=Time.infinity)
198
+ end
199
+
200
+ # The command line to run in order to update a working copy.
201
+ #
202
+ def update_commandline(to_identifier=Time.infinity)
203
+ end
204
+
205
+ # Returns/yields an IO containing the unified diff of the change.
206
+ def diff(checkout_dir, change, &block)
207
+ return(yield("Diff not implemented"))
208
+ end
209
+
210
+ def ==(other_scm)
211
+ return false if self.class != other_scm.class
212
+ self.instance_variables.each do |var|
213
+ return false if self.instance_eval(var) != other_scm.instance_eval(var)
214
+ end
215
+ true
216
+ end
217
+
218
+ protected
219
+
220
+ # Takes an array of +absolute_path+s and turn them into an array
221
+ # of paths relative to +dir+
222
+ #
223
+ def to_relative(dir, absolute_paths)
224
+ dir = File.expand_path(dir)
225
+ absolute_paths.collect{|p| File.expand_path(p)[dir.length+1..-1]}
226
+ end
227
+
228
+ end
229
+ end