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.
- data/README +198 -0
- data/Rakefile +118 -0
- data/ext/rscm.jar +0 -0
- data/lib/rscm.rb +10 -0
- data/lib/rscm/abstract_log_parser.rb +49 -0
- data/lib/rscm/abstract_scm.rb +229 -0
- data/lib/rscm/changes.rb +271 -0
- data/lib/rscm/cvs/cvs.rb +363 -0
- data/lib/rscm/cvs/cvs_log_parser.rb +161 -0
- data/lib/rscm/darcs/darcs.rb +69 -0
- data/lib/rscm/line_editor.rb +46 -0
- data/lib/rscm/logging.rb +5 -0
- data/lib/rscm/monotone/monotone.rb +107 -0
- data/lib/rscm/mooky/mooky.rb +13 -0
- data/lib/rscm/parser.rb +39 -0
- data/lib/rscm/path_converter.rb +92 -0
- data/lib/rscm/perforce/perforce.rb +415 -0
- data/lib/rscm/starteam/starteam.rb +99 -0
- data/lib/rscm/svn/svn.rb +337 -0
- data/lib/rscm/svn/svn_log_parser.rb +134 -0
- data/lib/rscm/time_ext.rb +125 -0
- data/test/rscm/apply_label_scm_tests.rb +26 -0
- data/test/rscm/changes_fixture.rb +20 -0
- data/test/rscm/changes_test.rb +129 -0
- data/test/rscm/cvs/cvs_log_parser_test.rb +575 -0
- data/test/rscm/cvs/cvs_test.rb +22 -0
- data/test/rscm/darcs/darcs_test.rb +14 -0
- data/test/rscm/difftool_test.rb +40 -0
- data/test/rscm/file_ext.rb +12 -0
- data/test/rscm/generic_scm_tests.rb +282 -0
- data/test/rscm/line_editor_test.rb +76 -0
- data/test/rscm/mockit.rb +130 -0
- data/test/rscm/mockit_test.rb +117 -0
- data/test/rscm/monotone/monotone_test.rb +19 -0
- data/test/rscm/mooky/mooky_test.rb +14 -0
- data/test/rscm/parser_test.rb +47 -0
- data/test/rscm/path_converter_test.rb +52 -0
- data/test/rscm/perforce/perforce_test.rb +14 -0
- data/test/rscm/starteam/starteam_test.rb +36 -0
- data/test/rscm/svn/svn_log_parser_test.rb +111 -0
- data/test/rscm/svn/svn_test.rb +28 -0
- data/test/rscm/tempdir.rb +12 -0
- 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
|