rscm 0.2.1.1404 → 0.3.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 (50) hide show
  1. data/README +34 -23
  2. data/Rakefile +24 -29
  3. data/bin/touch.exe +0 -0
  4. data/lib/rscm.rb +6 -3
  5. data/lib/rscm/annotations.rb +26 -7
  6. data/lib/rscm/{abstract_scm.rb → base.rb} +109 -71
  7. data/lib/rscm/better.rb +16 -0
  8. data/lib/rscm/logging.rb +11 -5
  9. data/lib/rscm/path_converter.rb +9 -16
  10. data/lib/rscm/revision.rb +201 -0
  11. data/lib/rscm/revision_file.rb +71 -0
  12. data/lib/rscm/scm/clearcase.rb +7 -7
  13. data/lib/rscm/scm/cvs.rb +69 -70
  14. data/lib/rscm/scm/cvs_log_parser.rb +29 -29
  15. data/lib/rscm/scm/darcs.rb +82 -34
  16. data/lib/rscm/scm/darcs_log_parser.rb +65 -0
  17. data/lib/rscm/scm/monotone.rb +249 -77
  18. data/lib/rscm/scm/monotone_log_parser.rb +57 -43
  19. data/lib/rscm/scm/mooky.rb +3 -3
  20. data/lib/rscm/scm/perforce.rb +196 -134
  21. data/lib/rscm/scm/star_team.rb +10 -10
  22. data/lib/rscm/scm/subversion.rb +106 -77
  23. data/lib/rscm/scm/subversion_log_parser.rb +76 -47
  24. data/lib/rscm/time_ext.rb +2 -116
  25. data/test/rscm/annotations_test.rb +15 -2
  26. data/test/rscm/{abstract_scm_test.rb → base_test.rb} +3 -3
  27. data/test/rscm/difftool_test.rb +9 -3
  28. data/test/rscm/generic_scm_tests.rb +195 -124
  29. data/test/rscm/revision_fixture.rb +20 -0
  30. data/test/rscm/revision_test.rb +129 -0
  31. data/test/rscm/{changesets.yaml → revisions.yaml} +10 -10
  32. data/test/rscm/scm/clearcase.log +608 -0
  33. data/test/rscm/scm/clearcase_test.rb +39 -0
  34. data/test/rscm/scm/cvs_log_parser_test.rb +73 -73
  35. data/test/rscm/scm/cvs_test.rb +1 -1
  36. data/test/rscm/scm/darcs_log_parser_test.rb +171 -0
  37. data/test/rscm/scm/monotone_log_parser_test.rb +49 -31
  38. data/test/rscm/scm/monotone_test.rb +3 -2
  39. data/test/rscm/scm/p4client_test.rb +33 -0
  40. data/test/rscm/scm/perforce_test.rb +25 -3
  41. data/test/rscm/scm/star_team.rb +9 -9
  42. data/test/rscm/scm/subversion_log_parser_test.rb +107 -47
  43. metadata +17 -13
  44. data/lib/multipart.rb +0 -95
  45. data/lib/rscm/RSS.txt +0 -41
  46. data/lib/rscm/changes.rb +0 -268
  47. data/lib/rscm/example.yaml +0 -21
  48. data/lib/rubyforge_file_publisher.rb +0 -176
  49. data/test/rscm/changes_fixture.rb +0 -20
  50. data/test/rscm/changes_test.rb +0 -129
@@ -0,0 +1,16 @@
1
+ module RSCM
2
+ class Better
3
+ @@logger = nil
4
+ @@logger = RSCM_DEFAULT_LOGGER
5
+
6
+ def self.popen(cmd, mode="r", expected_exit=0, &proc)
7
+ @@logger.info "Executing command: '#{cmd}'" if @@logger
8
+ ret = IO.popen(cmd, mode) do |io|
9
+ proc.call(io)
10
+ end
11
+ exit_code = $? >> 8
12
+ raise "Command\n'#{cmd}'\nfailed with code #{exit_code} in\n#{Dir.pwd}\nExpected exit code: #{expected_exit}" if exit_code != expected_exit
13
+ ret
14
+ end
15
+ end
16
+ end
@@ -1,6 +1,12 @@
1
- require 'rubygems'
2
- require_gem 'log4r'
1
+ require 'logger'
3
2
 
4
- Log = Log4r::Logger.new("rscm")
5
- Log.level = ENV["LOG4R_LEVEL"] ? ENV["LOG4R_LEVEL"].to_i : 0
6
- Log.add Log4r::Outputter.stderr
3
+ begin
4
+ RSCM_DEFAULT_LOGGER = Logger.new("#{HOMEDIR}/.rscm.log")
5
+ rescue StandardError
6
+ RSCM_DEFAULT_LOGGER = Logger.new(STDERR)
7
+ RSCM_DEFAULT_LOGGER.level = Logger::WARN
8
+ RSCM_DEFAULT_LOGGER.warn(
9
+ "RSCM Error: Unable to access log file. Please ensure that #{HOMEDIR}/.rscm.log exists and is chmod 0666. " +
10
+ "The log level has been raised to WARN and the output directed to STDERR until the problem is fixed."
11
+ )
12
+ end
@@ -1,20 +1,15 @@
1
- require 'fileutils'
2
- require 'rscm/logging'
3
-
4
1
  WIN32 = RUBY_PLATFORM == "i386-mswin32"
5
2
  CYGWIN = RUBY_PLATFORM == "i386-cygwin"
6
3
  WINDOWS = WIN32 || CYGWIN
7
-
8
- # TODO: change to override IO.popen, using that neat trick we
9
- # used in threadfile.rb (which is now gone)
10
- def safer_popen(cmd, mode="r", expected_exit=0, &proc)
11
- Log.info "Executing command: '#{cmd}'"
12
- ret = IO.popen(cmd, mode, &proc)
13
- exit_code = $? >> 8
14
- raise "#{cmd} failed with code #{exit_code} in #{Dir.pwd}. Expected exit code: #{expected_exit}" if exit_code != expected_exit
15
- ret
4
+ if(WINDOWS)
5
+ HOMEDIR = RSCM::PathConverter.nativepath_to_filepath("#{ENV['HOMEDRIVE']}#{ENV['HOMEPATH']}").gsub(/\\/, "/")
6
+ else
7
+ HOMEDIR = ENV['HOME']
16
8
  end
17
9
 
10
+ require 'fileutils'
11
+ require 'rscm/logging'
12
+
18
13
  def with_working_dir(dir)
19
14
  # Can't use Dir.chdir{ block } - will fail with multithreaded code.
20
15
  # http://www.ruby-doc.org/core/classes/Dir.html#M000790
@@ -22,10 +17,8 @@ def with_working_dir(dir)
22
17
  prev = Dir.pwd
23
18
  begin
24
19
  dir = File.expand_path(dir)
25
- Log.info "Making directory: '#{dir}'"
26
20
  FileUtils.mkdir_p(dir)
27
21
  Dir.chdir(dir)
28
- Log.info "In directory: '#{dir}'"
29
22
  yield
30
23
  ensure
31
24
  Dir.chdir(prev)
@@ -42,7 +35,7 @@ module RSCM
42
35
  path.gsub(/\//, "\\")
43
36
  elsif(CYGWIN)
44
37
  cmd = "cygpath --windows #{path}"
45
- safer_popen(cmd) do |io|
38
+ Better.popen(cmd) do |io|
46
39
  cygpath = io.read.chomp
47
40
  escaped ? cygpath.gsub(/\\/, "\\\\\\\\") : cygpath
48
41
  end
@@ -68,7 +61,7 @@ module RSCM
68
61
  elsif(CYGWIN)
69
62
  path = path.gsub(/\\/, "/")
70
63
  cmd = "cygpath --unix #{path}"
71
- safer_popen(cmd) do |io|
64
+ Better.popen(cmd) do |io|
72
65
  io.read.chomp
73
66
  end
74
67
  else
@@ -0,0 +1,201 @@
1
+ require 'xmlrpc/utils'
2
+ require 'rscm/time_ext'
3
+ require 'rscm/revision_file'
4
+
5
+ module RSCM
6
+
7
+ # A collection of Revision.
8
+ class Revisions
9
+ include Enumerable
10
+ include XMLRPC::Marshallable
11
+
12
+ attr_accessor :revisions
13
+
14
+ def initialize(revisions=[])
15
+ @revisions = revisions
16
+ end
17
+
18
+ # Accepts a visitor that will receive callbacks while
19
+ # iterating over this instance's internal structure.
20
+ # The visitor should respond to the following methods:
21
+ #
22
+ # * visit_revisions(revisions)
23
+ # * visit_revision(revision)
24
+ # * visit_file(file)
25
+ #
26
+ def accept(visitor)
27
+ visitor.visit_revisions(self)
28
+ self.each{|revision| revision.accept(visitor)}
29
+ end
30
+
31
+ def [](file)
32
+ @revisions[file]
33
+ end
34
+
35
+ def each(&block)
36
+ @revisions.each(&block)
37
+ end
38
+
39
+ def reverse
40
+ r = clone
41
+ r.revisions = @revisions.dup.reverse
42
+ r
43
+ end
44
+
45
+ def length
46
+ @revisions.length
47
+ end
48
+
49
+ def ==(other)
50
+ return false if !other.is_a?(self.class)
51
+ @revisions == other.revisions
52
+ end
53
+
54
+ def empty?
55
+ @revisions.empty?
56
+ end
57
+
58
+ # The set of developers that contributed to all of the contained Revision s.
59
+ def developers
60
+ result = []
61
+ each do |revision|
62
+ result << revision.developer unless result.index(revision.developer)
63
+ end
64
+ result
65
+ end
66
+
67
+ # The latest Revision (with the latest time)
68
+ # or nil if there are none.
69
+ def latest
70
+ result = nil
71
+ each do |revision|
72
+ result = revision if result.nil? || result.time < revision.time
73
+ end
74
+ result
75
+ end
76
+
77
+ # Adds a File or a Revision.
78
+ # If the argument is a File and no corresponding Revision exists,
79
+ # a new Revision is created, added, and the File is added to that Revision -
80
+ # and then finally the newly created Revision is returned.
81
+ # Otherwise nil is returned.
82
+ def add(file_or_revision)
83
+ if(file_or_revision.is_a?(Revision))
84
+ @revisions << file_or_revision
85
+ return file_or_revision
86
+ else
87
+ revision = @revisions.find { |a_revision| a_revision.can_contain?(file_or_revision) }
88
+ if(revision.nil?)
89
+ revision = Revision.new
90
+ @revisions << revision
91
+ revision << file_or_revision
92
+ return revision
93
+ end
94
+ revision << file_or_revision
95
+ return nil
96
+ end
97
+ end
98
+
99
+ def push(*file_or_revisions)
100
+ file_or_revisions.each { |file_or_revision| self << (file_or_revision) }
101
+ self
102
+ end
103
+
104
+ # Sorts the revisions according to time
105
+ def sort!
106
+ @revisions.sort!
107
+ self
108
+ end
109
+
110
+ end
111
+
112
+ # Represents a collection of File that were committed at the same time.
113
+ # Non-transactional SCMs (such as CVS and StarTeam) emulate Revision
114
+ # by grouping File s that were committed by the same developer, with the
115
+ # same commit message, and within a "reasonably" small timespan.
116
+ class Revision
117
+ include Enumerable
118
+ include XMLRPC::Marshallable
119
+
120
+ attr_reader :files
121
+ attr_accessor :identifier
122
+ attr_accessor :developer
123
+ attr_accessor :message
124
+ attr_accessor :time
125
+
126
+ def initialize(files=[])
127
+ @files = files
128
+ end
129
+
130
+ def accept(visitor)
131
+ visitor.visit_revision(self)
132
+ @files.each{|file| file.accept(visitor)}
133
+ end
134
+
135
+ def << (file)
136
+ @files << file
137
+ if(self.time.nil? || self.time < file.time unless file.time.nil?)
138
+ self.time = file.time
139
+ self.identifier = self.time if(self.identifier.nil? || self.identifier.is_a?(Time))
140
+ end
141
+ self.developer = file.developer if file.developer
142
+ self.message = file.message if file.message
143
+ end
144
+
145
+ def [] (file)
146
+ @files[file]
147
+ end
148
+
149
+ def each(&block)
150
+ @files.each(&block)
151
+ end
152
+
153
+ def pop
154
+ @files.pop
155
+ end
156
+
157
+ def length
158
+ @files.length
159
+ end
160
+
161
+ def time=(t)
162
+ raise "time must be a Time object - it was a #{t.class.name} with the string value #{t}" unless t.is_a?(Time)
163
+ raise "can't set time to an inferiour value than the previous value" if @time && (t < @time)
164
+ @time = t
165
+ end
166
+
167
+ def ==(other)
168
+ return false if !other.is_a?(self.class)
169
+ @files == other.files
170
+ end
171
+
172
+ def <=>(other)
173
+ @time <=> other.time
174
+ end
175
+
176
+ # Whether this instance can contain a File. Used
177
+ # by non-transactional SCMs.
178
+ def can_contain?(file)
179
+ self.developer == file.developer &&
180
+ self.message == file.message &&
181
+ (self.time - file.time).abs < 60
182
+ end
183
+
184
+ # String representation that can be used for debugging.
185
+ def to_s
186
+ result = "#{identifier} | #{developer} | #{time} | #{message}\n"
187
+ self.each do |file|
188
+ result << " " << file.to_s << "\n"
189
+ end
190
+ result
191
+ end
192
+
193
+ # Returns the identifier of the revision. This is the revision
194
+ # (if defined) or an UTC time if it is not natively supported by the scm.
195
+ def identifierAA
196
+ @identifier || @time
197
+ end
198
+
199
+ end
200
+
201
+ end
@@ -0,0 +1,71 @@
1
+ module RSCM
2
+ # Represents a file within a Revision, and also information about how this file
3
+ # was modified compared with the previous revision.
4
+ class RevisionFile
5
+ include XMLRPC::Marshallable
6
+
7
+ MODIFIED = "MODIFIED"
8
+ DELETED = "DELETED"
9
+ ADDED = "ADDED"
10
+ MOVED = "MOVED"
11
+
12
+ attr_accessor :status
13
+ attr_accessor :path
14
+ attr_accessor :previous_native_revision_identifier
15
+ # The native SCM's revision for this file. For non-transactional SCMs this is different from
16
+ # the parent Revision's
17
+ attr_accessor :native_revision_identifier
18
+
19
+ attr_accessor :developer
20
+ attr_accessor :message
21
+ # This is a UTC ruby time
22
+ attr_accessor :time
23
+
24
+ def initialize(path=nil, status=nil, developer=nil, message=nil, native_revision_identifier=nil, time=nil)
25
+ @path, @developer, @message, @native_revision_identifier, @time, @status = path, developer, message, native_revision_identifier, time, status
26
+ end
27
+
28
+ def accept(visitor)
29
+ visitor.visit_file(self)
30
+ end
31
+
32
+ def to_s
33
+ "#{path} | #{native_revision_identifier}"
34
+ end
35
+
36
+ def developer=(developer)
37
+ raise "can't be null" if developer.nil?
38
+ @developer = developer
39
+ end
40
+
41
+ def message=(message)
42
+ raise "can't be null" if message.nil?
43
+ @message = message
44
+ end
45
+
46
+ def path=(path)
47
+ raise "can't be null" if path.nil?
48
+ @path = path
49
+ end
50
+
51
+ def native_revision_identifier=(id)
52
+ raise "can't be null" if id.nil?
53
+ @native_revision_identifier = id
54
+ end
55
+
56
+ def time=(time)
57
+ raise "time must be a Time object" unless time.is_a?(Time)
58
+ @time = time
59
+ end
60
+
61
+ def ==(other)
62
+ return false if !other.is_a?(self.class)
63
+ self.path == other.path &&
64
+ self.developer == other.developer &&
65
+ self.message == other.message &&
66
+ self.native_revision_identifier == other.native_revision_identifier &&
67
+ self.time == other.time
68
+ end
69
+
70
+ end
71
+ end
@@ -1,9 +1,9 @@
1
- require 'rscm/abstract_scm'
1
+ require 'rscm/base'
2
2
  require 'rscm/path_converter'
3
3
  require 'fileutils'
4
4
 
5
5
  module RSCM
6
- class ClearCase < AbstractSCM
6
+ class ClearCase < Base
7
7
 
8
8
  LOG_FORMAT = "Developer:%u\\nTime:%Nd\\nExtendedName:%Xn\\nVersionId:%Vn\\nPreviousVersionId:%PVn\\nElementName:%En\\nOID:%On\\nO:%o\\nMessage:%Nc\\n------------------------------------------\\n"
9
9
 
@@ -11,13 +11,13 @@ module RSCM
11
11
  "ClearCase"
12
12
  end
13
13
 
14
- def changesets(checkout_dir, from_identifier, to_identifier=Time.infinity)
15
- result = ChangeSets.new
14
+ def revisions(checkout_dir, from_identifier, to_identifier=Time.infinity)
15
+ result = Revisions.new
16
16
  with_working_dir(checkout_dir) do
17
17
  since = from_identifier.strftime("%d-%b-%Y.%H:%M:%S")
18
18
  cleartool("lshistory -recurse -nco -since #{since} -fmt #{LOG_FORMAT}") do |io|
19
19
  io.each_line {|l| puts l}
20
- changesets << ChangeSet.new()
20
+ revisions << Revision.new()
21
21
  end
22
22
  end
23
23
  result
@@ -25,7 +25,7 @@ module RSCM
25
25
 
26
26
  def diff(checkout_dir, change)
27
27
  with_working_dir(checkout_dir) do
28
- cleartool("diff -diff_format #{change.path}@@#{change.previous_revision} #{change.path}@@#{change.revision}")
28
+ cleartool("diff -diff_format #{change.path}@@#{change.previous_native_revision_identifier} #{change.path}@@#{change.revision}")
29
29
  end
30
30
  end
31
31
 
@@ -71,7 +71,7 @@ module RSCM
71
71
 
72
72
  def cleartool(cleartool_cmd)
73
73
  cmd = "cleartool #{cleartool_cmd}"
74
- safer_popen(cmd, "r+") do |io|
74
+ Better.popen(cmd, "r+") do |io|
75
75
  if(block_given?)
76
76
  return(yield(io))
77
77
  else
@@ -1,4 +1,4 @@
1
- require 'rscm/abstract_scm'
1
+ require 'rscm/base'
2
2
  require 'rscm/path_converter'
3
3
  require 'rscm/line_editor'
4
4
  require 'rscm/scm/cvs_log_parser'
@@ -10,7 +10,7 @@ module RSCM
10
10
  # You need a cvs executable on the PATH in order for it to work.
11
11
  #
12
12
  # NOTE: On Cygwin this has to be the win32 build of cvs and not the Cygwin one.
13
- class Cvs < AbstractSCM
13
+ class Cvs < Base
14
14
  register self
15
15
 
16
16
  ann :description => "CVSROOT"
@@ -33,22 +33,22 @@ module RSCM
33
33
  "CVS"
34
34
  end
35
35
 
36
- def import(dir, message)
36
+ def import_central(dir, message)
37
37
  modname = File.basename(dir)
38
38
  cvs(dir, "import -m \"#{message}\" #{modname} VENDOR START")
39
39
  end
40
40
 
41
- def add(checkout_dir, relative_filename)
42
- cvs(checkout_dir, "add #{relative_filename}")
41
+ def add(relative_filename)
42
+ cvs(@checkout_dir, "add #{relative_filename}")
43
43
  end
44
44
 
45
45
  # The extra simulate parameter is not in accordance with the AbstractSCM API,
46
46
  # but it's optional and is only being used from within this class (uptodate? method).
47
- def checkout(checkout_dir, to_identifier=nil, simulate=false)
47
+ def checkout(to_identifier=nil, simulate=false)
48
48
  checked_out_files = []
49
- if(checked_out?(checkout_dir))
49
+ if(checked_out?)
50
50
  path_regex = /^[U|P] (.*)/
51
- cvs(checkout_dir, update_command(to_identifier), simulate) do |line|
51
+ cvs(@checkout_dir, update_command(to_identifier), simulate) do |line|
52
52
  if(line =~ path_regex)
53
53
  path = $1.chomp
54
54
  yield path if block_given?
@@ -56,12 +56,12 @@ module RSCM
56
56
  end
57
57
  end
58
58
  else
59
- prefix = File.basename(checkout_dir)
59
+ prefix = File.basename(@checkout_dir)
60
60
  path_regex = /^[U|P] #{prefix}\/(.*)/
61
61
  # This is a workaround for the fact that -d . doesn't work - must be an existing sub folder.
62
- mkdir_p(checkout_dir) unless File.exist?(checkout_dir)
63
- target_dir = File.basename(checkout_dir)
64
- run_checkout_command_dir = File.dirname(checkout_dir)
62
+ mkdir_p(@checkout_dir) unless File.exist?(@checkout_dir)
63
+ target_dir = File.basename(@checkout_dir)
64
+ run_checkout_command_dir = File.dirname(@checkout_dir)
65
65
  # -D is sticky, but subsequent updates will reset stickiness with -A
66
66
  cvs(run_checkout_command_dir, checkout_command(target_dir, to_identifier), simulate) do |line|
67
67
  if(line =~ path_regex)
@@ -74,65 +74,57 @@ module RSCM
74
74
  checked_out_files
75
75
  end
76
76
 
77
- def checkout_commandline(to_identifier=Time.infinity)
78
- "cvs checkout #{branch_option} #{revision_option(to_identifier)} #{mod}"
77
+ def commit(message, &proc)
78
+ cvs(@checkout_dir, commit_command(message), &proc)
79
79
  end
80
80
 
81
- def update_commandline
82
- "cvs update #{branch_option} -d -P -A"
83
- end
84
-
85
- def commit(checkout_dir, message, &proc)
86
- cvs(checkout_dir, commit_command(message), &proc)
87
- end
88
-
89
- def uptodate?(checkout_dir, since)
90
- if(!checked_out?(checkout_dir))
81
+ def uptodate?(identifier)
82
+ if(!checked_out?)
91
83
  return false
92
84
  end
93
85
 
94
86
  # simulate a checkout
95
- files = checkout(checkout_dir, nil, true)
87
+ files = checkout(identifier, true)
96
88
  files.empty?
97
89
  end
98
90
 
99
- def changesets(checkout_dir, from_identifier, to_identifier=Time.infinity)
100
- checkout(checkout_dir) unless uptodate?(checkout_dir, nil) # must checkout to get changesets
101
- parse_log(checkout_dir, changes_command(from_identifier, to_identifier))
91
+ def revisions(from_identifier, to_identifier=Time.infinity)
92
+ checkout(to_identifier) unless uptodate?(to_identifier) # must checkout to get revisions
93
+ parse_log(changes_command(from_identifier, to_identifier))
102
94
  end
103
95
 
104
- def diff(checkout_dir, change)
105
- with_working_dir(checkout_dir) do
96
+ def diff(change)
97
+ with_working_dir(@checkout_dir) do
106
98
  opts = case change.status
107
- when /#{Change::MODIFIED}/; "#{revision_option(change.previous_revision)} #{revision_option(change.revision)}"
108
- when /#{Change::DELETED}/; "#{revision_option(change.previous_revision)}"
109
- when /#{Change::ADDED}/; "#{revision_option(Time.epoch)} #{revision_option(change.revision)}"
99
+ when /#{RevisionFile::MODIFIED}/; "#{revision_option(change.previous_native_revision_identifier)} #{revision_option(change.native_revision_identifier)}"
100
+ when /#{RevisionFile::DELETED}/; "#{revision_option(change.previous_native_revision_identifier)}"
101
+ when /#{RevisionFile::ADDED}/; "#{revision_option(Time.epoch)} #{revision_option(change.native_revision_identifier)}"
110
102
  end
111
103
  # IMPORTANT! CVS NT has a bug in the -N diff option
112
104
  # http://www.cvsnt.org/pipermail/cvsnt-bugs/2004-November/000786.html
113
105
  cmd = command_line("diff -Nu #{opts} #{change.path}")
114
- safer_popen(cmd, "r", 1) do |io|
106
+ Better.popen(cmd, "r", 1) do |io|
115
107
  return(yield(io))
116
108
  end
117
109
  end
118
110
  end
119
111
 
120
- def apply_label(checkout_dir, label)
121
- cvs(checkout_dir, "tag -c #{label}")
112
+ def apply_label(label)
113
+ cvs(@checkout_dir, "tag -c #{label}")
122
114
  end
123
115
 
124
116
  def install_trigger(trigger_command, trigger_files_checkout_dir)
125
117
  raise "mod can't be null or empty" if (mod.nil? || mod == "")
126
118
 
127
- root_cvs = create_root_cvs
128
- root_cvs.checkout(trigger_files_checkout_dir)
119
+ root_cvs = create_root_cvs(trigger_files_checkout_dir)
120
+ root_cvs.checkout
129
121
  with_working_dir(trigger_files_checkout_dir) do
130
122
  trigger_line = "#{mod} #{trigger_command}\n"
131
123
  File.open("loginfo", File::WRONLY | File::APPEND) do |file|
132
124
  file.puts(trigger_line)
133
125
  end
134
126
  begin
135
- commit(trigger_files_checkout_dir, "Installed trigger for CVS module '#{mod}'")
127
+ root_cvs.commit("Installed trigger for CVS module '#{mod}'")
136
128
  rescue
137
129
  raise "Couldn't commit the trigger back to CVS. Try to manually check out CVSROOT/loginfo, " +
138
130
  "add the following line and commit it back:\n\n#{trigger_line}"
@@ -144,9 +136,9 @@ module RSCM
144
136
  loginfo_line = "#{mod} #{trigger_command}"
145
137
  regex = Regexp.new(Regexp.escape(loginfo_line))
146
138
 
147
- root_cvs = create_root_cvs
139
+ root_cvs = create_root_cvs(trigger_files_checkout_dir)
148
140
  begin
149
- root_cvs.checkout(trigger_files_checkout_dir)
141
+ root_cvs.checkout
150
142
  loginfo = File.join(trigger_files_checkout_dir, "loginfo")
151
143
  return false if !File.exist?(loginfo)
152
144
 
@@ -168,23 +160,36 @@ module RSCM
168
160
  loginfo_line = "#{mod} #{trigger_command}"
169
161
  regex = Regexp.new(Regexp.escape(loginfo_line))
170
162
 
171
- root_cvs = create_root_cvs
172
- root_cvs.checkout(trigger_files_checkout_dir)
163
+ root_cvs = create_root_cvs(trigger_files_checkout_dir)
164
+ root_cvs.checkout
173
165
  loginfo_path = File.join(trigger_files_checkout_dir, "loginfo")
174
166
  File.comment_out(loginfo_path, regex, "# ")
175
167
  with_working_dir(trigger_files_checkout_dir) do
176
- commit(trigger_files_checkout_dir, "Uninstalled trigger for CVS mod '#{mod}'")
168
+ root_cvs.commit("Uninstalled trigger for CVS mod '#{mod}'")
177
169
  end
178
170
  raise "Couldn't uninstall/commit trigger to loginfo" if trigger_installed?(trigger_command, trigger_files_checkout_dir)
179
171
  end
180
172
 
181
- def create
182
- raise "Can't create CVS repository for #{root}" unless can_create?
173
+ def create_central
174
+ raise "Can't create central CVS repository for #{root}" unless can_create_central?
183
175
  File.mkpath(path)
184
176
  cvs(path, "init")
185
177
  end
178
+
179
+ def destroy_central
180
+ FileUtils.rm_rf(path)
181
+ end
186
182
 
187
- def can_create?
183
+ def central_exists?
184
+ if(local?)
185
+ File.exists?("#{path}/CVSROOT/loginfo")
186
+ else
187
+ # don't know. assume yes.
188
+ true
189
+ end
190
+ end
191
+
192
+ def can_create_central?
188
193
  begin
189
194
  local?
190
195
  rescue
@@ -196,16 +201,7 @@ module RSCM
196
201
  true
197
202
  end
198
203
 
199
- def exists?
200
- if(local?)
201
- File.exists?("#{path}/CVSROOT/loginfo")
202
- else
203
- # don't know. assume yes.
204
- true
205
- end
206
- end
207
-
208
- def checked_out?(checkout_dir)
204
+ def checked_out?
209
205
  rootcvs = File.expand_path("#{checkout_dir}/CVS/Root")
210
206
  File.exists?(rootcvs)
211
207
  end
@@ -217,7 +213,7 @@ module RSCM
217
213
  dir = File.expand_path(dir)
218
214
  execed_command_line = command_line(cmd, password, simulate)
219
215
  with_working_dir(dir) do
220
- safer_popen(execed_command_line) do |stdout|
216
+ Better.popen(execed_command_line) do |stdout|
221
217
  stdout.each_line do |progress|
222
218
  yield progress if block_given?
223
219
  end
@@ -225,26 +221,26 @@ module RSCM
225
221
  end
226
222
  end
227
223
 
228
- def parse_log(checkout_dir, cmd, &proc)
224
+ def parse_log(cmd, &proc)
229
225
  logged_command_line = command_line(cmd, hidden_password)
230
226
  yield logged_command_line if block_given?
231
227
 
232
228
  execed_command_line = command_line(cmd, password)
233
- changesets = nil
234
- with_working_dir(checkout_dir) do
235
- safer_popen(execed_command_line) do |stdout|
229
+ revisions = nil
230
+ with_working_dir(@checkout_dir) do
231
+ Better.popen(execed_command_line) do |stdout|
236
232
  parser = CvsLogParser.new(stdout)
237
233
  parser.cvspath = path
238
234
  parser.cvsmodule = mod
239
- changesets = parser.parse_changesets
235
+ revisions = parser.parse_revisions
240
236
  end
241
237
  end
242
- changesets
238
+ revisions
243
239
  end
244
240
 
245
241
  def changes_command(from_identifier, to_identifier)
246
242
  # https://www.cvshome.org/docs/manual/cvs-1.11.17/cvs_16.html#SEC144
247
- # -N => Suppress the header if no revisions are selected.
243
+ # -N => Suppress the header if no RevisionFiles are selected.
248
244
  "log #{branch_option} -N #{period_option(from_identifier, to_identifier)}"
249
245
  end
250
246
 
@@ -261,7 +257,7 @@ module RSCM
261
257
  end
262
258
 
263
259
  def checkout_command(target_dir, to_identifier)
264
- "checkout #{branch_option} -d #{target_dir} #{mod} #{revision_option(to_identifier)}"
260
+ "checkout #{branch_option} -d #{target_dir} #{revision_option(to_identifier)} #{mod}"
265
261
  end
266
262
 
267
263
  def hidden_password
@@ -276,7 +272,7 @@ module RSCM
276
272
  if(from_identifier.nil? && to_identifier.nil?)
277
273
  ""
278
274
  else
279
- "-d\"#{cvsdate(from_identifier)}<=#{cvsdate(to_identifier)}\" "
275
+ "-d\"#{cvsdate(from_identifier)}<#{cvsdate(to_identifier)}\" "
280
276
  end
281
277
  end
282
278
 
@@ -300,11 +296,14 @@ module RSCM
300
296
 
301
297
  def command_line(cmd, password=nil, simulate=false)
302
298
  cvs_options = simulate ? "-n" : ""
303
- "cvs -f \"-d#{root_with_password(password)}\" #{cvs_options} -q #{cmd}"
299
+ dev_null = WIN32 ? "nul" : "/dev/null"
300
+ "cvs -f \"-d#{root_with_password(password)}\" #{cvs_options} -q #{cmd} 2> #{dev_null}"
304
301
  end
305
302
 
306
- def create_root_cvs
307
- Cvs.new(self.root, "CVSROOT", nil, self.password)
303
+ def create_root_cvs(checkout_dir)
304
+ cvs = Cvs.new(self.root, "CVSROOT", nil, self.password)
305
+ cvs.checkout_dir = checkout_dir
306
+ cvs
308
307
  end
309
308
 
310
309
  def revision_option(identifier)