rscm 0.3.14 → 0.3.15

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/CHANGES CHANGED
@@ -1,5 +1,12 @@
1
1
  = RSCM Changelog
2
2
 
3
+ == Version 0.3.15
4
+
5
+ This release adds support for directory listings and fixes some incompatibilities with CVS 1.12.x
6
+
7
+ * Added support for directory listings.
8
+ * Added support for parsing of revisions for CVS 1.12.x, which uses a slightly different time format.
9
+
3
10
  == Version 0.3.14
4
11
 
5
12
  Improved error messages
data/README CHANGED
@@ -1,4 +1,4 @@
1
- = RSCM - Ruby Source Control Management (0.3.14)
1
+ = RSCM - Ruby Source Control Management (0.3.15)
2
2
 
3
3
  RSCM is to SCM what DBI/JDBC/ODBC are to databases - an SCM-independent API for accessing different SCMs. The high level features are roughly:
4
4
 
data/Rakefile CHANGED
@@ -10,7 +10,7 @@ require 'meta_project'
10
10
 
11
11
  PKG_BUILD = ENV['PKG_BUILD'] ? '.' + ENV['PKG_BUILD'] : ''
12
12
  PKG_NAME = 'rscm'
13
- PKG_VERSION = '0.3.14' + PKG_BUILD
13
+ PKG_VERSION = '0.3.15' + PKG_BUILD
14
14
  PKG_FILE_NAME = "#{PKG_NAME}-#{PKG_VERSION}"
15
15
 
16
16
  desc "Default Task"
@@ -7,22 +7,17 @@ module RSCM
7
7
  # TODO: make this a module and remove the attr_reader
8
8
  class AbstractLogParser
9
9
 
10
- attr_reader :io
11
-
12
10
  def initialize(io)
13
11
  @io = io
14
- @current_line_number = 0
15
- @had_error = false
16
12
  end
17
13
 
18
14
  def read_until_matching_line(regexp)
19
- return nil if io.eof?
15
+ return nil if @io.eof?
20
16
  result = ""
21
- io.each_line do |line|
22
- @current_line_number += 1
17
+ @io.each_line do |line|
23
18
  line.gsub!(/\r\n$/, "\n")
24
- break if line=~regexp
25
- result<<line
19
+ break if line =~ regexp
20
+ result << line
26
21
  end
27
22
  if result.strip == ""
28
23
  read_until_matching_line(regexp)
@@ -35,15 +30,6 @@ module RSCM
35
30
  file.gsub(/\\/, "/")
36
31
  end
37
32
 
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
33
  end
48
34
 
49
35
  end
@@ -16,6 +16,7 @@ module RSCM
16
16
  # * checked_out?
17
17
  # * diff
18
18
  # * edit
19
+ # * ls
19
20
  # * move
20
21
  # * revisions
21
22
  # * uptodate?
@@ -108,13 +109,14 @@ module RSCM
108
109
  # example if the repository is 'remote' or if it already exists).
109
110
  #
110
111
  def create_central
111
- raise "Not implemented"
112
+ raise NotImplementedError
112
113
  end
113
114
 
114
115
  # Destroys the central repository. Shuts down any server processes and deletes the repository.
115
116
  # WARNING: calling this may result in loss of data. Only call this if you really want to wipe
116
117
  # it out for good!
117
118
  def destroy_central
119
+ raise NotImplementedError
118
120
  end
119
121
 
120
122
  # Whether a repository can be created.
@@ -124,12 +126,14 @@ module RSCM
124
126
 
125
127
  # Adds +relative_filename+ to the working copy.
126
128
  def add(relative_filename)
129
+ raise NotImplementedError
127
130
  end
128
131
 
129
132
  # Schedules a move of +relative_src+ to +relative_dest+
130
133
  # Should not take effect in the central repository until
131
134
  # +commit+ is invoked.
132
135
  def move(relative_src, relative_dest)
136
+ raise NotImplementedError
133
137
  end
134
138
 
135
139
  # Recursively imports files from a +dir+ into the central scm
@@ -143,6 +147,7 @@ module RSCM
143
147
 
144
148
  # Commit (check in) modified files.
145
149
  def commit(message)
150
+ raise NotImplementedError
146
151
  end
147
152
 
148
153
  # Checks out or updates contents from a central SCM to +checkout_dir+ - a local working copy.
@@ -190,27 +195,22 @@ module RSCM
190
195
  # revisions pertaining to that path.
191
196
  #
192
197
  def revisions(from_identifier, to_identifier=Time.infinity, relative_path=nil)
193
- # Should be overridden by subclasses
194
- revisions = Revisions.new
195
- revisions.add(
196
- Revision.new(
197
- "up/the/chimney",
198
- Revision::DELETED,
199
- "DamageControl",
200
- "The #{name} class doesn't\n" +
201
- "correctly implement the revisions method. This is\n" +
202
- "not a real revision, but a hint to the developer to go and implement it.\n\n" +
203
- "Do It Now!",
204
- "999",
205
- Time.now.utc
206
- )
207
- )
208
- revisions
198
+ raise NotImplementedError
199
+ end
200
+
201
+ # Returns the HistoricFile representing the root of the repo
202
+ def rootdir
203
+ file("", true)
209
204
  end
210
205
 
211
206
  # Returns a HistoricFile for +relative_path+
212
- def file(relative_path)
213
- HistoricFile.new(relative_path, self)
207
+ def file(relative_path, dir)
208
+ HistoricFile.new(relative_path, dir, self)
209
+ end
210
+
211
+ # Returns an Array of the children under +relative_path+
212
+ def ls(relative_path)
213
+ raise NotImplementedError
214
214
  end
215
215
 
216
216
  # Whether the working copy is in synch with the central
@@ -243,7 +243,7 @@ module RSCM
243
243
 
244
244
  # Descriptive name of the trigger mechanism
245
245
  def trigger_mechanism
246
- "Unknown"
246
+ raise NotImplementedError
247
247
  end
248
248
 
249
249
  # Installs +trigger_command+ in the SCM.
@@ -253,37 +253,37 @@ module RSCM
253
253
  # Most implementations will ignore this parameter.
254
254
  #
255
255
  def install_trigger(trigger_command, install_dir)
256
- raise "Not implemented"
256
+ raise NotImplementedError
257
257
  end
258
258
 
259
259
  # Uninstalls +trigger_command+ from the SCM.
260
260
  #
261
261
  def uninstall_trigger(trigger_command, install_dir)
262
- raise "Not implemented"
262
+ raise NotImplementedError
263
263
  end
264
264
 
265
265
  # Whether the command denoted by +trigger_command+ is installed in the SCM.
266
266
  #
267
267
  def trigger_installed?(trigger_command, install_dir)
268
- raise "Not implemented"
268
+ raise NotImplementedError
269
269
  end
270
270
 
271
271
  # The command line to run in order to check out a fresh working copy.
272
272
  #
273
273
  def checkout_commandline(to_identifier=Time.infinity)
274
- raise "Not implemented"
274
+ raise NotImplementedError
275
275
  end
276
276
 
277
277
  # The command line to run in order to update a working copy.
278
278
  #
279
279
  def update_commandline(to_identifier=Time.infinity)
280
- raise "Not implemented"
280
+ raise NotImplementedError
281
281
  end
282
282
 
283
283
  # Returns/yields an IO containing the unified diff of the change.
284
284
  # Also see RevisionFile#diff
285
285
  def diff(change, &block)
286
- return(yield("Not implemented"))
286
+ raise NotImplementedError
287
287
  end
288
288
 
289
289
  def ==(other_scm)
@@ -1,8 +1,14 @@
1
1
  module RSCM
2
- # Represents the full history of a single file
2
+ # Represents the full history of a single file or directory.
3
3
  class HistoricFile
4
- def initialize(relative_path, scm)
5
- @relative_path, @scm = relative_path, scm
4
+ attr_reader :relative_path
5
+
6
+ def initialize(relative_path, directory, scm)
7
+ @relative_path, @directory, @scm = relative_path, directory, scm
8
+ end
9
+
10
+ def directory?
11
+ @directory
6
12
  end
7
13
 
8
14
  # Returns an Array of RevisionFile - from Time.epoch until Time.infinity (now)
@@ -18,5 +24,10 @@ module RSCM
18
24
  revision.files[0]
19
25
  end
20
26
  end
27
+
28
+ def children
29
+ raise "Not a directory" unless directory?
30
+ @scm.ls(@relative_path)
31
+ end
21
32
  end
22
33
  end
@@ -1,4 +1,3 @@
1
- require 'xmlrpc/utils'
2
1
  require 'rscm/time_ext'
3
2
  require 'rscm/revision_file'
4
3
 
@@ -7,7 +6,6 @@ module RSCM
7
6
  # A collection of Revision.
8
7
  class Revisions
9
8
  include Enumerable
10
- include XMLRPC::Marshallable
11
9
 
12
10
  attr_accessor :revisions
13
11
 
@@ -115,7 +113,6 @@ module RSCM
115
113
  # same commit message, and within a "reasonably" small timespan.
116
114
  class Revision
117
115
  include Enumerable
118
- include XMLRPC::Marshallable
119
116
 
120
117
  attr_reader :files
121
118
  attr_accessor :identifier
@@ -167,8 +164,7 @@ module RSCM
167
164
  end
168
165
 
169
166
  def ==(other)
170
- return false if !other.is_a?(self.class)
171
- @files == other.files
167
+ other.is_a?(self.class) && @files == other.files
172
168
  end
173
169
 
174
170
  def <=>(other)
@@ -177,7 +173,7 @@ module RSCM
177
173
 
178
174
  # Whether this instance can contain a File. Used
179
175
  # by non-transactional SCMs.
180
- def can_contain?(file)
176
+ def can_contain?(file) #:nodoc:
181
177
  self.developer == file.developer &&
182
178
  self.message == file.message &&
183
179
  (self.time - file.time).abs < 60
@@ -2,7 +2,6 @@ module RSCM
2
2
  # Represents a file within a Revision, and also information about how this file
3
3
  # was modified compared with the previous revision.
4
4
  class RevisionFile
5
- include XMLRPC::Marshallable
6
5
 
7
6
  MODIFIED = "MODIFIED"
8
7
  DELETED = "DELETED"
@@ -106,12 +106,36 @@ module RSCM
106
106
 
107
107
  def open(revision_file, &block)
108
108
  with_working_dir(@checkout_dir) do
109
- diff_cmd = "cvs -Q update -p -r #{revision_file.native_revision_identifier} #{revision_file.path}"
110
- Better.popen(diff_cmd) do |io|
109
+ cmd = "cvs -Q update -p -r #{revision_file.native_revision_identifier} #{revision_file.path}"
110
+ Better.popen(cmd) do |io|
111
111
  block.call io
112
112
  end
113
113
  end
114
114
  end
115
+
116
+ def ls(relative_path)
117
+ prefix = relative_path == "" ? relative_path : "#{relative_path}/"
118
+ with_working_dir(@checkout_dir) do
119
+ cmd = "cvs -Q ls -l #{relative_path}"
120
+ Better.popen(cmd) do |io|
121
+ parse_ls_log(io, prefix)
122
+ end
123
+ end
124
+ end
125
+
126
+ def parse_ls_log(io, prefix) #:nodoc:
127
+ io.collect do |line|
128
+ line.strip!
129
+ if(
130
+ (line =~ /(d)... \d\d\d\d\-\d\d\-\d\d \d\d:\d\d:\d\d \-\d\d\d\d (.*)/) ||
131
+ (line =~ /(.)... \d\d\d\d\-\d\d\-\d\d \d\d:\d\d:\d\d \-\d\d\d\d \d[\.\d]+ (.*)/)
132
+ )
133
+ directory = $1 == "d"
134
+ name = $2.strip
135
+ HistoricFile.new("#{prefix}#{name}", directory, self)
136
+ end
137
+ end
138
+ end
115
139
 
116
140
  def apply_label(label)
117
141
  cvs(@checkout_dir, "tag -c #{label}")
@@ -236,9 +260,6 @@ module RSCM
236
260
  end
237
261
 
238
262
  def parse_log(cmd, &proc)
239
- logged_command_line = command_line(cmd, hidden_password)
240
- yield logged_command_line if block_given?
241
-
242
263
  execed_command_line = command_line(cmd, password)
243
264
  revisions = nil
244
265
  with_working_dir(@checkout_dir) do
@@ -274,14 +295,6 @@ module RSCM
274
295
  "checkout #{branch_option} -d #{target_dir} #{revision_option(to_identifier)} #{mod}"
275
296
  end
276
297
 
277
- def hidden_password
278
- if(password && password != "")
279
- "********"
280
- else
281
- ""
282
- end
283
- end
284
-
285
298
  def period_option(from_identifier, to_identifier)
286
299
  if(from_identifier.nil? && to_identifier.nil?)
287
300
  ""
@@ -1,7 +1,7 @@
1
1
  require 'rscm/revision'
2
2
  require 'rscm/abstract_log_parser'
3
-
4
3
  require 'ftools'
4
+ require 'time'
5
5
 
6
6
  module RSCM
7
7
 
@@ -14,14 +14,11 @@ module RSCM
14
14
 
15
15
  def initialize(io)
16
16
  super(io)
17
- @log = ""
18
17
  end
19
18
 
20
19
  def parse_revisions
21
20
  revisions = Revisions.new
22
21
  while(log_entry = next_log_entry)
23
- @log<<log_entry
24
- @log<<""
25
22
  begin
26
23
  parse_files(log_entry, revisions)
27
24
  rescue Exception => e
@@ -91,7 +88,13 @@ module RSCM
91
88
  file.native_revision_identifier = extract_match(file_entry_lines[0], /revision (.*)$/)
92
89
 
93
90
  file.previous_native_revision_identifier = determine_previous_native_revision_identifier(file.native_revision_identifier)
94
- file.time = parse_cvs_time(extract_required_match(file_entry_lines[1], /date: (.*?)(;|$)/))
91
+
92
+ time = extract_required_match(file_entry_lines[1], /date: (.*?)(;|$)/)
93
+ if(time.strip.length == 19)
94
+ # CVS 1.11.x doesn't specify timezone (but assumes UTC), so we'll add it here.
95
+ time += " +0000"
96
+ end
97
+ file.time = Time.parse(time).utc
95
98
  file.developer = extract_match(file_entry_lines[1], /author: (.*?);/)
96
99
 
97
100
  state = extract_match(file_entry_lines[1], /state: (.*?);/)
@@ -128,13 +131,8 @@ module RSCM
128
131
  end
129
132
  end
130
133
 
131
- def parse_cvs_time(time)
132
- # 2003/11/09 15:39:25
133
- Time.utc(time[0..3], time[5..6], time[8..9], time[11..12], time[14..15], time[17..18])
134
- end
135
-
136
134
  def extract_required_match(string, regexp)
137
- if string=~regexp
135
+ if(string =~ regexp)
138
136
  return($1)
139
137
  else
140
138
  $stderr.puts("can't parse: '#{string}'\nexpected to match regexp: #{regexp.to_s}")
@@ -104,6 +104,22 @@ module RSCM
104
104
  end
105
105
  end
106
106
 
107
+ def ls(relative_path)
108
+ prefix = relative_path == "" ? relative_path : "#{relative_path}/"
109
+ cmd = "svn ls #{url}/#{relative_path}"
110
+ Better.popen(cmd) do |io|
111
+ io.collect do |line|
112
+ name = line.strip
113
+ dir = false
114
+ if(name =~ /(.*)\/$/)
115
+ name = $1
116
+ dir = true
117
+ end
118
+ HistoricFile.new("#{prefix}#{name}", dir, self)
119
+ end
120
+ end
121
+ end
122
+
107
123
  def can_create_central?
108
124
  local?
109
125
  end
@@ -11,8 +11,8 @@ module RSCM
11
11
  def teardown
12
12
  if @scm
13
13
  begin
14
- @scm.destroy_working_copy
15
- @scm.destroy_central
14
+ # @scm.destroy_working_copy
15
+ # @scm.destroy_central
16
16
  rescue => e
17
17
  # Fails on windows with TortoiseCVS' cvs because of resident cvslock.exe
18
18
  STDERR.puts "Couldn't destroy central #{@scm.class.name}: #{e.message}"
@@ -40,6 +40,7 @@ module RSCM
40
40
  # 16) Verify that OtherWorkingCopy is now uptodate
41
41
  # 17) Add and commit a file in WorkingCopy
42
42
  # 18) Verify that the revision (since last revision) for CheckoutHereToo contains only one file
43
+ # 19) Get directory listings
43
44
  def test_basics
44
45
  work_dir = RSCM.new_temp_dir("basics")
45
46
  checkout_dir = "#{work_dir}/WorkingCopy"
@@ -140,13 +141,29 @@ module RSCM
140
141
 
141
142
  # 16
142
143
  assert(other_scm.uptodate?(nil))
144
+
145
+ # 17
143
146
  add_or_edit_and_commit_file(scm, checkout_dir, "src/java/com/thoughtworks/damagecontrolled/Hello.txt", "Bla bla")
144
147
  assert(!other_scm.uptodate?(nil))
145
148
  revisions = other_scm.revisions(revisions.latest.identifier)
149
+
150
+ # 18
146
151
  assert_equal(1, revisions.length)
147
152
  assert_equal(1, revisions[0].length)
148
- assert("src/java/com/thoughtworks/damagecontrolled/Hello.txt", revisions[0][0].path)
149
- assert("src/java/com/thoughtworks/damagecontrolled/Hello.txt", other_scm.checkout.sort[0])
153
+ assert_equal("src/java/com/thoughtworks/damagecontrolled/Hello.txt", revisions[0][0].path)
154
+
155
+ # 19
156
+ root_children = scm.rootdir.children
157
+ assert_equal "build.xml", root_children[0].relative_path
158
+ assert !root_children[0].directory?
159
+ assert_equal "project.xml", root_children[1].relative_path
160
+ assert !root_children[1].directory?
161
+ assert_equal "src", root_children[2].relative_path
162
+ assert root_children[2].directory?
163
+
164
+ src_children = root_children[2].children
165
+ assert_equal "src/java", src_children[0].relative_path
166
+ assert src_children[0].directory?
150
167
  end
151
168
 
152
169
  def test_create_destroy
@@ -310,7 +327,7 @@ EOF
310
327
  assert(got_diff)
311
328
 
312
329
  # TODO: make separate test. Make helper method for the cumbersome setup!
313
- historic_afile = scm.file("afile.txt")
330
+ historic_afile = scm.file("afile.txt", false)
314
331
  revision_files = historic_afile.revision_files
315
332
  assert_equal(Array, revision_files.class)
316
333
  assert(revision_files.length >= 2)
@@ -14,10 +14,6 @@ module RSCM
14
14
  @parser.cvsmodule = "damagecontrol"
15
15
  end
16
16
 
17
- def teardown
18
- assert(!@parser.had_error?, "parser had errors") unless @parser.nil?
19
- end
20
-
21
17
  def test_read_log_entry
22
18
  assert_equal(nil, CvsLogParser.new(StringIO.new("")).next_log_entry)
23
19
  end
@@ -330,8 +326,6 @@ EOF
330
326
  assert_equal("website/templates/logo.gif", @parser.parse_path(LOG_ENTRY_FROM_06_07_2004_19_25_2))
331
327
  end
332
328
 
333
- # stand in picocontainer's java folder
334
- # cvs log -d"2003/07/25 12:38:41<=2004/07/08 12:38:41" build.xml
335
329
  LOG_WITH_DELETIONS= <<EOF
336
330
  RCS file: /home/projects/picocontainer/scm/java/Attic/build.xml,v
337
331
  Working file: build.xml
@@ -348,7 +342,7 @@ total revisions: 11; selected revisions: 2
348
342
  description:
349
343
  ----------------------------
350
344
  revision 1.11
351
- date: 2003/10/13 00:04:54; author: rinkrank; state: dead; lines: +0 -0
345
+ date: 2003/10/13 00:04:54 -0500; author: rinkrank; state: dead; lines: +0 -0
352
346
  Obsolete
353
347
  ----------------------------
354
348
  revision 1.10
@@ -363,8 +357,7 @@ EOF
363
357
  assert_equal(2, revisions.length)
364
358
 
365
359
  revision_delete = revisions[1]
366
- # assert_equal("MAIN:rinkrank:20031013000454", revision_delete.revision)
367
- assert_equal(Time.utc(2003,10,13,00,04,54,0), revision_delete.time)
360
+ assert_equal(Time.utc(2003,10,13,05,04,54,0), revision_delete.time)
368
361
  assert_equal("Obsolete", revision_delete.message)
369
362
  assert_equal("rinkrank", revision_delete.developer)
370
363
  assert_equal(1, revision_delete.length)
@@ -374,7 +367,6 @@ EOF
374
367
  assert(RevisionFile::DELETED, revision_delete[0].status)
375
368
 
376
369
  revision_fix_url = revisions[0]
377
- # assert_equal("MAIN:rinkrank:20030725163239", revision_fix_url.revision)
378
370
  assert_equal(Time.utc(2003,07,25,16,32,39,0), revision_fix_url.time)
379
371
  assert_equal("fixed broken url (NANO-8)", revision_fix_url.message)
380
372
  assert_equal("rinkrank", revision_fix_url.developer)
@@ -472,8 +464,8 @@ EOF
472
464
  # https://sitemesh.dev.java.net/source/browse/sitemesh/.cvsignore
473
465
  # The default commit message probably showed up in vi, and the committer
474
466
  # probably just left it there. Not sure why CVS kept it this way
475
- # (lines starting with CVS: should be ignored in commit message afik).
476
- # Anyway, the parser nw knows how to deal with this. (AH)
467
+ # (lines starting with CVS: should be ignored in commit message afaik).
468
+ # Anyway, the parser now knows how to deal with this. (AH)
477
469
  LOG_WITH_WEIRD_CVS_AND_MANY_DASHES = <<EOF
478
470
  ? log.txt
479
471
 
@@ -2,6 +2,7 @@ require 'test/unit'
2
2
  require 'rscm/path_converter'
3
3
  require 'rscm'
4
4
  require 'rscm/generic_scm_tests'
5
+ require 'stringio'
5
6
 
6
7
  module RSCM
7
8
 
@@ -28,5 +29,24 @@ module RSCM
28
29
  end
29
30
  end
30
31
 
32
+ LS_LOG = <<-EOF
33
+ ---- 2005-11-22 21:24:40 -0500 1.1 afile
34
+ ---- 2005-11-22 22:04:20 -0500 1.2 build.xml
35
+ ---- 2005-11-22 22:12:43 -0500 1.1 foo bar
36
+ ---- 2005-11-22 21:24:37 -0500 1.1.1.1 1.1 project.xml
37
+ d--- 2005-11-22 22:12:43 -0500 1.1 src
38
+ d--- 2005-11-22 22:12:43 -0500 togo
39
+ EOF
40
+ def test_should_parse_ls_log
41
+ history_files = Cvs.new.parse_ls_log(StringIO.new(LS_LOG), "")
42
+ assert_equal("afile", history_files[0].relative_path)
43
+ assert_equal("foo bar", history_files[2].relative_path)
44
+ assert_equal("1.1 project.xml", history_files[3].relative_path)
45
+ assert(!history_files[3].directory?)
46
+ assert_equal("1.1 src", history_files[4].relative_path)
47
+ assert(history_files[4].directory?)
48
+ assert_equal("togo", history_files[5].relative_path)
49
+ assert(history_files[5].directory?)
50
+ end
31
51
  end
32
52
  end
metadata CHANGED
@@ -3,8 +3,8 @@ rubygems_version: 0.8.10
3
3
  specification_version: 1
4
4
  name: rscm
5
5
  version: !ruby/object:Gem::Version
6
- version: 0.3.14
7
- date: 2005-11-16
6
+ version: 0.3.15
7
+ date: 2005-11-23
8
8
  summary: "RSCM - Ruby Source Control Management"
9
9
  require_paths:
10
10
  - lib