rscm 0.1.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.
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