rscm 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|