rscm 0.3.16 → 0.4.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.
@@ -4,21 +4,6 @@ WINDOWS = WIN32 || CYGWIN
4
4
 
5
5
  require 'fileutils'
6
6
 
7
- def with_working_dir(dir)
8
- # Can't use Dir.chdir{ block } - will fail with multithreaded code.
9
- # http://www.ruby-doc.org/core/classes/Dir.html#M000790
10
- #
11
- prev = Dir.pwd
12
- begin
13
- dir = File.expand_path(dir)
14
- FileUtils.mkdir_p(dir)
15
- Dir.chdir(dir)
16
- yield
17
- ensure
18
- Dir.chdir(prev)
19
- end
20
- end
21
-
22
7
  # Utility for converting between win32 and cygwin paths. Does nothing on *nix.
23
8
  module RSCM
24
9
  module PathConverter
@@ -50,6 +35,7 @@ module RSCM
50
35
 
51
36
  def nativepath_to_filepath(path)
52
37
  return nil if path.nil?
38
+ path = File.expand_path(path)
53
39
  if(WIN32)
54
40
  path.gsub(/\//, "\\")
55
41
  elsif(CYGWIN)
@@ -0,0 +1,25 @@
1
+ require 'rbconfig'
2
+
3
+ module RSCM
4
+ module Platform
5
+ def family
6
+ target_os = Config::CONFIG["target_os"] or ""
7
+ return "powerpc-darwin" if target_os.downcase =~ /darwin/
8
+ return "mswin32" if target_os.downcase =~ /32/
9
+ return "cygwin" if target_os.downcase =~ /cyg/
10
+ # TODO: distinguish between different binary formats like ELF and a.out (or whatever it's called)
11
+ return "linux"
12
+ end
13
+ module_function :family
14
+
15
+ def user
16
+ family == "mswin32" ? ENV['USERNAME'] : ENV['USER']
17
+ end
18
+ module_function :user
19
+
20
+ def prompt(dir=Dir.pwd)
21
+ prompt = "#{dir.gsub(/\//, File::SEPARATOR)} #{user}$"
22
+ end
23
+ module_function :prompt
24
+ end
25
+ end
@@ -37,13 +37,13 @@ module RSCM
37
37
 
38
38
  # Returns/yields an IO containing the contents of this file, using the +scm+ this
39
39
  # file lives in.
40
- def open(scm, &block) #:yield: io
41
- scm.open(self, &block)
40
+ def open(scm, options={}, &block) #:yield: io
41
+ scm.open(self, options, &block)
42
42
  end
43
43
 
44
44
  # Yields the diff as an IO for this file
45
- def diff(scm, &block)
46
- scm.diff(self, &block)
45
+ def diff(scm, options={}, &block)
46
+ scm.diff(self, options, &block)
47
47
  end
48
48
 
49
49
  # Accepts a visitor that must respond to +visit_file(revision_file)+
@@ -9,25 +9,26 @@ module RSCM
9
9
 
10
10
 
11
11
  # Polls new revisions for since +last_revision+,
12
- # or if +last_revision+ is nil, polls since 'now' - +seconds_before_now+.
13
- # If no revisions are found AND the poll was using +seconds_before_now+
12
+ # or if +last_revision+ is nil, polls since 'now' - +options[:seconds_before_now]+.
13
+ # If no revisions are found AND the poll was using +options[:seconds_before_now]+
14
14
  # (i.e. it's the first poll, and no revisions were found),
15
- # calls itself recursively with twice the +seconds_before_now+.
16
- # This happens until revisions are found, ot until the +seconds_before_now+
15
+ # calls itself recursively with twice the +options[:seconds_before_now]+.
16
+ # This happens until revisions are found, ot until the +options[:seconds_before_now]+
17
17
  # Exceeds 32 weeks, which means it's probably not worth looking further in
18
18
  # the past, the scm is either completely idle or not yet active.
19
- def poll_new_revisions(latest_revision=nil, quiet_period=DEFAULT_QUIET_PERIOD, seconds_before_now=TWO_WEEKS_AGO, max_time_before_now=THIRTY_TWO_WEEKS_AGO)
20
- max_past = Time.new.utc - max_time_before_now
19
+ def poll_new_revisions(options)
20
+ options = {
21
+ :latest_revision => nil,
22
+ :quiet_period => DEFAULT_QUIET_PERIOD,
23
+ :seconds_before_now => TWO_WEEKS_AGO,
24
+ :max_time_before_now => THIRTY_TWO_WEEKS_AGO,
25
+ }.merge(options)
26
+ max_past = Time.new.utc - options[:max_time_before_now]
21
27
 
22
- if(!central_exists?)
23
- logger.info "Not polling for revisions - central scm repo doesn't seem to exist" if logger
24
- return []
25
- end
26
-
27
28
  # Default value for start time (in case there are no detected revisions yet)
28
- from = Time.new.utc - seconds_before_now
29
- if(latest_revision)
30
- from = latest_revision.identifier
29
+ from = Time.new.utc - options[:seconds_before_now]
30
+ if(options[:latest_revision])
31
+ from = options[:latest_revision].identifier
31
32
  else
32
33
  if(from < max_past)
33
34
  logger.info "Checked for revisions as far back as #{max_past}. There were none, so we give up." if logger
@@ -39,13 +40,15 @@ module RSCM
39
40
 
40
41
  logger.info "Polling revisions after #{from} (#{from.class.name})" if logger
41
42
 
42
- revisions = revisions(from)
43
+ revisions = revisions(from, options)
43
44
  if(revisions.empty?)
44
45
  logger.info "No new revisions after #{from}" if logger
45
- unless(latest_revision)
46
- double_seconds_before_now = 2*seconds_before_now
46
+ unless(options[:latest_revision])
47
+ double_seconds_before_now = 2*options[:seconds_before_now]
47
48
  logger.info "Last revision still not found, checking since #{double_seconds_before_now.ago}" if logger
48
- return poll_new_revisions(project, double_seconds_before_now, max_time_before_now)
49
+ new_opts = options.dup
50
+ new_opts[:seconds_before_now] = double_seconds_before_now
51
+ return poll_new_revisions(new_opts)
49
52
  end
50
53
  else
51
54
  logger.info "There were #{revisions.length} new revision(s) after #{from}" if logger
@@ -61,9 +64,9 @@ module RSCM
61
64
  # revision (on next poll).
62
65
  commit_in_progress = true
63
66
  while(commit_in_progress)
64
- logger.info "Sleeping for #{quiet_period} seconds because #{visual_name} is not transactional." if logger
67
+ logger.info "Sleeping for #{options[:quiet_period]} seconds because #{visual_name} is not transactional." if logger
65
68
 
66
- sleep(quiet_period)
69
+ sleep(options[:quiet_period])
67
70
  previous_revisions = revisions
68
71
  revisions = revisions(from)
69
72
  commit_in_progress = revisions != previous_revisions
data/lib/rscm/scm/cvs.rb CHANGED
@@ -21,137 +21,87 @@ module RSCM
21
21
  @root, @mod, @branch, @password = root, mod, branch, password
22
22
  end
23
23
 
24
- def import_central(dir, message)
25
- modname = File.basename(dir)
26
- cvs(dir, "import -m \"#{message}\" #{modname} VENDOR START")
24
+ def import_central(options={})
25
+ modname = File.basename(options[:dir])
26
+ cvs("import -m \"#{options[:message]}\" #{modname} VENDOR START", options)
27
27
  end
28
28
 
29
- def add(relative_filename)
30
- cvs(@checkout_dir, "add #{relative_filename}")
29
+ def add(relative_filename, options={})
30
+ cvs("add #{relative_filename}", options)
31
31
  end
32
32
 
33
- def move(relative_src, relative_dest)
33
+ def move(relative_src, relative_dest, options={})
34
34
  FileUtils.mv(@checkout_dir + '/' + relative_src, @checkout_dir + '/' + relative_dest, :force=>true)
35
- cvs(@checkout_dir, "rm #{relative_src}")
35
+ cvs("rm #{relative_src}", options)
36
36
  # This will fail if the directories are new. More advanced support for adding can be added if needed.
37
- cvs(@checkout_dir, "add #{relative_dest}")
37
+ cvs("add #{relative_dest}", options)
38
38
  end
39
39
 
40
- # The extra simulate parameter is not in accordance with the AbstractSCM API,
41
- # but it's optional and is only being used from within this class (uptodate? method).
42
- def checkout(to_identifier=nil, simulate=false)
43
- checked_out_files = []
44
- if(checked_out?)
45
- path_regex = /^[U|P] (.*)/
46
- cvs(@checkout_dir, update_command(to_identifier), simulate) do |line|
47
- if(line =~ path_regex)
48
- path = $1.chomp
49
- yield path if block_given?
50
- checked_out_files << path
51
- end
52
- end
53
- else
54
- prefix = File.basename(@checkout_dir)
55
- path_regex = /^[U|P] #{prefix}\/(.*)/
56
- # This is a workaround for the fact that -d . doesn't work - must be an existing sub folder.
57
- FileUtils.mkdir_p(@checkout_dir) unless File.exist?(@checkout_dir)
58
- target_dir = File.basename(@checkout_dir)
59
- run_checkout_command_dir = File.dirname(@checkout_dir)
60
- # -D is sticky, but subsequent updates will reset stickiness with -A
61
- cvs(run_checkout_command_dir, checkout_command(target_dir, to_identifier), simulate) do |line|
62
- if(line =~ path_regex)
63
- path = $1.chomp
64
- yield path if block_given?
65
- checked_out_files << path
66
- end
67
- end
68
- end
69
- checked_out_files
70
- end
71
-
72
- def commit(message)
73
- cvs(@checkout_dir, commit_command(message))
40
+ def commit(message, options={})
41
+ cvs(commit_command(message), options)
74
42
  end
75
43
 
76
- def uptodate?(identifier)
44
+ def uptodate?(identifier, options={})
77
45
  if(!checked_out?)
78
46
  return false
79
47
  end
80
48
 
81
- # simulate a checkout
82
- files = checkout(identifier, true)
83
- files.empty?
49
+ checkout_silent(identifier, options.dup.merge({:simulate => true})) do |io|
50
+ path_regex = /^[U|P|C] (.*)/
51
+ io.each_line do |line|
52
+ return false if(line =~ path_regex)
53
+ end
54
+ end
55
+ return true
84
56
  end
85
57
 
86
- def revisions(from_identifier, to_identifier=Time.infinity, relative_path=nil)
87
- checkout(to_identifier) unless uptodate?(to_identifier) # must checkout to get revisions
88
- parse_log(changes_command(from_identifier, to_identifier, relative_path))
58
+ def revisions(from_identifier, options={})
59
+ options = {
60
+ :from_identifier => from_identifier,
61
+ :to_identifier => Time.infinity,
62
+ :relative_path => nil
63
+ }.merge(options)
64
+ checkout(options[:to_identifier], options) unless checked_out? # must checkout to get revisions
65
+ parse_log(changes_command(options), options)
89
66
  end
90
67
 
91
- def diff(revision_file)
92
- with_working_dir(@checkout_dir) do
93
- opts = case revision_file.status
94
- when /#{RevisionFile::MODIFIED}/; "#{revision_option(revision_file.previous_native_revision_identifier)} #{revision_option(revision_file.native_revision_identifier)}"
95
- when /#{RevisionFile::DELETED}/; "#{revision_option(revision_file.previous_native_revision_identifier)}"
96
- when /#{RevisionFile::ADDED}/; "#{revision_option(Time.epoch)} #{revision_option(revision_file.native_revision_identifier)}"
97
- end
98
- # IMPORTANT! CVS NT has a bug in the -N diff option
99
- # http://www.cvsnt.org/pipermail/cvsnt-bugs/2004-November/000786.html
100
- cmd = command_line("diff -Nu #{opts} #{revision_file.path}")
101
- Better.popen(cmd, "r", 1) do |io|
102
- return(yield(io))
103
- end
68
+ def diff(revision_file, options={})
69
+ opts = case revision_file.status
70
+ when /#{RevisionFile::MODIFIED}/; "#{revision_option(revision_file.previous_native_revision_identifier)} #{revision_option(revision_file.native_revision_identifier)}"
71
+ when /#{RevisionFile::DELETED}/; "#{revision_option(revision_file.previous_native_revision_identifier)}"
72
+ when /#{RevisionFile::ADDED}/; "#{revision_option(Time.epoch)} #{revision_option(revision_file.native_revision_identifier)}"
104
73
  end
105
- end
106
-
107
- def open(revision_file, &block)
108
- with_working_dir(@checkout_dir) do
109
- cmd = "cvs -Q update -p -r #{revision_file.native_revision_identifier} #{revision_file.path}"
110
- Better.popen(cmd) do |io|
111
- block.call io
112
- end
74
+
75
+ # IMPORTANT! CVS NT has a bug in the -N diff option
76
+ # http://www.cvsnt.org/pipermail/cvsnt-bugs/2004-November/000786.html
77
+ cmd = command_line("diff -Nu #{opts} #{revision_file.path}")
78
+ execute(cmd, options.dup.merge({:exitstatus => 1})) do |io|
79
+ yield io
113
80
  end
114
81
  end
115
82
 
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
83
+ def open(revision_file, options, &block)
84
+ cmd = "cvs -Q update -p -r #{revision_file.native_revision_identifier} #{revision_file.path}"
85
+ execute(cmd, options) do |io|
86
+ block.call io
123
87
  end
124
88
  end
125
89
 
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
139
-
140
90
  def apply_label(label)
141
- cvs(@checkout_dir, "tag -c #{label}")
91
+ cvs("tag -c #{label}")
142
92
  end
143
93
 
144
94
  def trigger_mechanism
145
95
  "CVSROOT/loginfo"
146
96
  end
147
97
 
148
- def trigger_installed?(trigger_command, trigger_files_checkout_dir)
98
+ def trigger_installed?(trigger_command, trigger_files_checkout_dir, options={})
149
99
  loginfo_line = "#{mod} #{trigger_command}"
150
100
  regex = Regexp.new(Regexp.escape(loginfo_line))
151
101
 
152
102
  root_cvs = create_root_cvs(trigger_files_checkout_dir)
153
103
  begin
154
- root_cvs.checkout
104
+ root_cvs.checkout(nil, options)
155
105
  loginfo = File.join(trigger_files_checkout_dir, "loginfo")
156
106
  return false if !File.exist?(loginfo)
157
107
 
@@ -169,45 +119,45 @@ module RSCM
169
119
  end
170
120
  end
171
121
 
172
- def install_trigger(trigger_command, trigger_files_checkout_dir)
122
+ def install_trigger(trigger_command, trigger_files_checkout_dir, options={})
173
123
  raise "mod can't be null or empty" if (mod.nil? || mod == "")
174
124
 
175
125
  root_cvs = create_root_cvs(trigger_files_checkout_dir)
176
- root_cvs.checkout
177
- with_working_dir(trigger_files_checkout_dir) do
126
+ root_cvs.checkout(nil, options)
127
+ Dir.chdir(trigger_files_checkout_dir) do
178
128
  trigger_line = "#{mod} #{trigger_command}\n"
179
129
  File.open("loginfo", File::WRONLY | File::APPEND) do |file|
180
130
  file.puts(trigger_line)
181
131
  end
182
- begin
183
- root_cvs.commit("Installed trigger for CVS module '#{mod}'")
184
- rescue
185
- raise ["Didn't have permission to commit CVSROOT/loginfo.",
186
- "Try to manually add the following line:",
187
- trigger_command,
188
- "Finally make commit the file to the repository"]
189
- end
132
+ end
133
+
134
+ begin
135
+ root_cvs.commit("Installed trigger for CVS module '#{mod}'", options)
136
+ rescue Errno::EACCES
137
+ raise ["Didn't have permission to commit CVSROOT/loginfo.",
138
+ "Try to manually add the following line:",
139
+ trigger_command,
140
+ "Finally make commit the file to the repository"].join("\n")
190
141
  end
191
142
  end
192
143
 
193
- def uninstall_trigger(trigger_command, trigger_files_checkout_dir)
144
+ def uninstall_trigger(trigger_command, trigger_files_checkout_dir, options={})
194
145
  loginfo_line = "#{mod} #{trigger_command}"
195
146
  regex = Regexp.new(Regexp.escape(loginfo_line))
196
147
 
197
148
  root_cvs = create_root_cvs(trigger_files_checkout_dir)
198
- root_cvs.checkout
149
+ root_cvs.checkout nil, options
199
150
  loginfo_path = File.join(trigger_files_checkout_dir, "loginfo")
200
151
  File.comment_out(loginfo_path, regex, "# ")
201
- with_working_dir(trigger_files_checkout_dir) do
202
- root_cvs.commit("Uninstalled trigger for CVS mod '#{mod}'")
203
- end
204
- raise "Couldn't uninstall/commit trigger to loginfo" if trigger_installed?(trigger_command, trigger_files_checkout_dir)
152
+ root_cvs.commit("Uninstalled trigger for CVS mod '#{mod}'", options)
153
+ raise "Couldn't uninstall/commit trigger to loginfo" if trigger_installed?(trigger_command, trigger_files_checkout_dir, options)
205
154
  end
206
155
 
207
- def create_central
156
+ def create_central(options={})
157
+ options = options.dup.merge({:dir => path})
208
158
  raise "Can't create central CVS repository for #{root}" unless can_create_central?
209
159
  File.mkpath(path)
210
- cvs(path, "init")
160
+ cvs("init", options)
211
161
  end
212
162
 
213
163
  def destroy_central
@@ -243,40 +193,62 @@ module RSCM
243
193
  rootcvs = File.expand_path("#{checkout_dir}/CVS/Root")
244
194
  File.exists?(rootcvs)
245
195
  end
196
+
197
+ protected
198
+
199
+ def checkout_silent(to_identifier, options={}, &proc)
200
+ to_identifier = nil if to_identifier == Time.infinity
201
+ if(checked_out?)
202
+ options = options.dup.merge({
203
+ :dir => @checkout_dir
204
+ })
205
+ cvs(update_command(to_identifier), options, &proc)
206
+ else
207
+ # This is a workaround for the fact that -d . doesn't work - must be an existing sub folder.
208
+ FileUtils.mkdir_p(@checkout_dir) unless File.exist?(@checkout_dir)
209
+ target_dir = File.basename(@checkout_dir)
210
+ # -D is sticky, but subsequent updates will reset stickiness with -A
211
+ options = options.dup.merge({
212
+ :dir => File.dirname(@checkout_dir)
213
+ })
214
+ cvs(checkout_command(target_dir, to_identifier), options)
215
+ end
216
+ end
217
+
218
+ def ignore_paths
219
+ [/CVS\/.*/]
220
+ end
246
221
 
247
222
  private
248
223
 
249
- def cvs(dir, cmd, simulate=false)
250
- dir = PathConverter.nativepath_to_filepath(dir)
251
- dir = File.expand_path(dir)
252
- execed_command_line = command_line(cmd, password, simulate)
253
- with_working_dir(dir) do
254
- Better.popen(execed_command_line) do |stdout|
255
- stdout.each_line do |progress|
256
- yield progress if block_given?
257
- end
258
- end
259
- end
224
+ def cvs(cmd, options={}, &proc)
225
+ options = {
226
+ :simulate => false,
227
+ :dir => @checkout_dir
228
+ }.merge(options)
229
+
230
+ options[:dir] = PathConverter.nativepath_to_filepath(options[:dir])
231
+ execed_command_line = command_line(cmd, password, options[:simulate])
232
+ execute(execed_command_line, options, &proc)
260
233
  end
261
234
 
262
- def parse_log(cmd, &proc)
235
+ def parse_log(cmd, options, &proc)
263
236
  execed_command_line = command_line(cmd, password)
264
237
  revisions = nil
265
- with_working_dir(@checkout_dir) do
266
- Better.popen(execed_command_line) do |stdout|
267
- parser = CvsLogParser.new(stdout)
268
- parser.cvspath = path
269
- parser.cvsmodule = mod
270
- revisions = parser.parse_revisions
271
- end
238
+
239
+ execute(execed_command_line, options) do |io|
240
+ parser = CvsLogParser.new(io)
241
+ parser.cvspath = path
242
+ parser.cvsmodule = mod
243
+ revisions = parser.parse_revisions
272
244
  end
273
245
  revisions
274
246
  end
275
247
 
276
- def changes_command(from_identifier, to_identifier, relative_path)
248
+ def changes_command(options)
277
249
  # https://www.cvshome.org/docs/manual/cvs-1.11.17/cvs_16.html#SEC144
278
250
  # -N => Suppress the header if no RevisionFiles are selected.
279
- "log #{branch_option} -N #{period_option(from_identifier, to_identifier)} #{relative_path}"
251
+ "log #{branch_option} -N #{period_option(options[:from_identifier], options[:to_identifier])} #{options[:relative_path]}"
280
252
  end
281
253
 
282
254
  def branch_specified?
@@ -323,13 +295,13 @@ module RSCM
323
295
 
324
296
  def command_line(cmd, password=nil, simulate=false)
325
297
  cvs_options = simulate ? "-n" : ""
326
- dev_null_redirection = WIN32 ? "" : "2> /dev/null"
327
- "cvs -f \"-d#{root_with_password(password)}\" #{cvs_options} -q #{cmd} #{dev_null_redirection}"
298
+ "cvs -f \"-d#{root_with_password(password)}\" #{cvs_options} -q #{cmd}"
328
299
  end
329
300
 
330
301
  def create_root_cvs(checkout_dir)
331
302
  cvs = Cvs.new(self.root, "CVSROOT", nil, self.password)
332
303
  cvs.checkout_dir = checkout_dir
304
+ cvs.default_options = default_options
333
305
  cvs
334
306
  end
335
307