rscm 0.3.16 → 0.4.0

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