rscm 0.2.1.1404 → 0.3.0

Sign up to get free protection for your applications and to get access to all the features.
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
@@ -6,49 +6,56 @@ module RSCM
6
6
 
7
7
  class MonotoneLogParser
8
8
 
9
- def parse_changesets(io, from_identifier=Time.epoch, to_identifier=Time.infinity)
9
+ def parse_revisions(io, from_identifier=Time.epoch, to_identifier=Time.infinity)
10
10
  # skip first separator
11
11
  io.readline
12
12
 
13
- changesets = ChangeSets.new
14
- changeset_string = ""
13
+ all_revisions = []
14
+ revision_string = ""
15
15
 
16
16
  # hash of path => [array of revisions]
17
17
  path_revisions = {}
18
18
  io.each_line do |line|
19
19
  if(line =~ /-----------------------------------------------------------------/)
20
- changeset = parse_changeset(StringIO.new(changeset_string), path_revisions)
21
- changesets.add(changeset)
22
- changeset_string = ""
20
+ revision = parse_revision(StringIO.new(revision_string), path_revisions)
21
+ all_revisions << revision
22
+ revision_string = ""
23
23
  else
24
- changeset_string << line
24
+ revision_string << line
25
25
  end
26
26
  end
27
- changeset = parse_changeset(StringIO.new(changeset_string), path_revisions)
28
- if((from_identifier <= changeset.time) && (changeset.time <= to_identifier))
29
- changesets.add(changeset)
30
- end
31
-
32
- # set the previous revisions. most recent is at index 0.
33
- changesets.each do |changeset|
34
- changeset.each do |change|
35
- current_index = path_revisions[change.path].index(change.revision)
36
- change.previous_revision = path_revisions[change.path][current_index + 1]
27
+ revision = parse_revision(StringIO.new(revision_string), path_revisions)
28
+ all_revisions << revision
29
+
30
+ # Filter out the revisions and set the previous revisions, knowing that most recent is at index 0.
31
+
32
+ from_time = time(all_revisions, from_identifier, Time.epoch)
33
+ to_time = time(all_revisions, to_identifier, Time.infinity)
34
+
35
+ revisions = Revisions.new
36
+
37
+ all_revisions.each do |revision|
38
+ if((from_time < revision.time) && (revision.time <= to_time))
39
+ revisions.add(revision)
40
+ revision.each do |change|
41
+ current_index = path_revisions[change.path].index(change.native_revision_identifier)
42
+ change.previous_native_revision_identifier = path_revisions[change.path][current_index + 1]
43
+ end
37
44
  end
38
45
  end
39
- changesets
40
- end
41
-
42
- def parse_changeset(changeset_io, path_revisions)
43
- changeset = ChangeSet.new
46
+ revisions
47
+ end
48
+
49
+ def parse_revision(revision_io, path_revisions)
50
+ revision = Revision.new
44
51
  state = nil
45
- changeset_io.each_line do |line|
46
- if(line =~ /^Revision: (.*)$/ && changeset.revision.nil?)
47
- changeset.revision = $1
48
- elsif(line =~ /^Author: (.*)$/ && changeset.developer.nil?)
49
- changeset.developer = $1
50
- elsif(line =~ /^Date: (.*)$/ && changeset.time.nil?)
51
- changeset.time = Time.utc(
52
+ revision_io.each_line do |line|
53
+ if(line =~ /^Revision: (.*)$/ && revision.identifier.nil?)
54
+ revision.identifier = $1
55
+ elsif(line =~ /^Author: (.*)$/ && revision.developer.nil?)
56
+ revision.developer = $1
57
+ elsif(line =~ /^Date: (.*)$/ && revision.time.nil?)
58
+ revision.time = Time.utc(
52
59
  $1[0..3].to_i,
53
60
  $1[5..6].to_i,
54
61
  $1[8..9].to_i,
@@ -56,37 +63,44 @@ module RSCM
56
63
  $1[14..15].to_i,
57
64
  $1[17..18].to_i
58
65
  )
59
- elsif(line =~ /^ChangeLog:$/ && changeset.message.nil?)
66
+ elsif(line =~ /^ChangeLog:\s*$/ && revision.message.nil?)
60
67
  state = :message
61
- elsif(state == :message && changeset.message.nil?)
62
- changeset.message = ""
63
- elsif(state == :message && changeset.message)
64
- changeset.message << line
65
- elsif(line =~ /^Added files:$/)
68
+ elsif(state == :message && revision.message.nil?)
69
+ revision.message = ""
70
+ elsif(state == :message && revision.message)
71
+ revision.message << line
72
+ elsif(line =~ /^Added files:\s*$/)
66
73
  state = :added
67
74
  elsif(state == :added)
68
- add_changes(changeset, line, Change::ADDED, path_revisions)
69
- elsif(line =~ /^Modified files:$/)
75
+ add_changes(revision, line, RevisionFile::ADDED, path_revisions)
76
+ elsif(line =~ /^Modified files:\s*$/)
70
77
  state = :modified
71
78
  elsif(state == :modified)
72
- add_changes(changeset, line, Change::MODIFIED, path_revisions)
79
+ add_changes(revision, line, RevisionFile::MODIFIED, path_revisions)
73
80
  end
74
81
  end
75
- changeset.message.chomp!
76
- changeset
82
+ revision.message.chomp! rescue revision.message = ''
83
+ revision
77
84
  end
78
85
 
79
86
  private
80
87
 
81
- def add_changes(changeset, line, state, path_revisions)
88
+ def time(revisions, identifier, default)
89
+ cs = revisions.find do |revision|
90
+ revision.identifier == identifier
91
+ end
92
+ cs ? cs.time : (identifier.is_a?(Time) ? identifier : default)
93
+ end
94
+
95
+ def add_changes(revision, line, state, path_revisions)
82
96
  paths = line.split(" ")
83
97
  paths.each do |path|
84
- changeset << Change.new(path, state, changeset.developer, nil, changeset.revision, changeset.time)
98
+ revision << RevisionFile.new(path, state, revision.developer, nil, revision.identifier, revision.time)
85
99
 
86
100
  # now record path revisions so we can keep track of previous rev for each path
87
101
  # doesn't work for moved files, and have no idea how to make it work either.
88
102
  path_revisions[path] ||= []
89
- path_revisions[path] << changeset.revision
103
+ path_revisions[path] << revision.identifier
90
104
  end
91
105
 
92
106
  end
@@ -1,8 +1,8 @@
1
- require 'rscm/abstract_scm'
1
+ require 'rscm/base'
2
2
 
3
3
  module RSCM
4
- class Mooky < AbstractSCM
5
- register self
4
+ class Mooky < Base
5
+ #register self
6
6
 
7
7
  ann :description => "The Foo", :tip => "Foo is nonsense"
8
8
  attr_accessor :foo
@@ -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
 
@@ -9,26 +9,64 @@ require 'parsedate'
9
9
  require 'stringio'
10
10
 
11
11
  module RSCM
12
- # RSCM implementation for Perforce.
12
+ # Perforce RSCM implementation.
13
13
  #
14
14
  # Understands operations against multiple client-workspaces
15
15
  # You need the p4/p4d executable on the PATH in order for it to work.
16
16
  #
17
- class Perforce < AbstractSCM
17
+ class Perforce < Base
18
18
  register self
19
19
 
20
20
  include FileUtils
21
21
 
22
- ann :description => "Depot path", :tip => "The path to the Perforce depot"
23
- attr_accessor :depotpath
22
+ @@counter = 0
23
+
24
+ ann :description => "P4CLIENT: workspace name"
25
+ attr_accessor :client_name
26
+
27
+ ann :description => "P4PORT: [host:port]", :tip => "perforce server address e.g. 10.12.1.55:1666"
28
+ attr_accessor :port
29
+
30
+ ann :description => "P4USER", :tip => "username"
31
+ attr_accessor :user
32
+
33
+ ann :description => "P4PASSWD", :tip => "password"
34
+ attr_accessor :pwd
35
+
36
+ attr_accessor :repository_root_dir
24
37
 
25
- def initialize(repository_root_dir = "")
26
- @clients = {}
27
- @depotpath = repository_root_dir
38
+ def initialize(port = "1666", user = ENV["LOGNAME"], pwd = "", client_name = Perforce.next_client_name)
39
+ @port, @user, @pwd, @client_name = port, user, pwd, client_name
28
40
  end
29
41
 
30
- def create
31
- P4Daemon.new(@depotpath).start
42
+ def p4admin
43
+ @p4admin ||= P4Admin.new(@port, @user, @pwd)
44
+ end
45
+
46
+ def p4client
47
+ @p4client ||= p4admin.create_client(@checkout_dir, @client_name)
48
+ end
49
+
50
+ def can_create_central?
51
+ true
52
+ end
53
+
54
+ def create_central
55
+ raise "perforce depot can be created only from tests" unless @repository_root_dir
56
+ @p4d = P4Daemon.new(@repository_root_dir)
57
+ @p4d.start
58
+ end
59
+
60
+ def destroy_central
61
+ @p4d.shutdown
62
+ end
63
+
64
+ def central_exists?
65
+ p4admin.central_exists?
66
+ end
67
+
68
+ def can_create_central?
69
+ true
32
70
  end
33
71
 
34
72
  def name
@@ -39,35 +77,35 @@ module RSCM
39
77
  true
40
78
  end
41
79
 
42
- def import(dir, comment)
80
+ def import_central(dir, comment)
43
81
  with_create_client(dir) do |client|
44
82
  client.add_all(list_files)
45
83
  client.submit(comment)
46
84
  end
47
85
  end
48
86
 
49
- def checkout(checkout_dir, to_identifier = nil, &proc)
50
- client(checkout_dir).checkout(to_identifier, &proc)
87
+ def checkout(to_identifier = nil, &proc)
88
+ p4client.checkout(to_identifier, &proc)
51
89
  end
52
90
 
53
- def add(checkout_dir, relative_filename)
54
- client(checkout_dir).add(relative_filename)
91
+ def add(relative_filename)
92
+ p4client.add(relative_filename)
55
93
  end
56
94
 
57
- def commit(checkout_dir, message, &proc)
58
- client(checkout_dir).submit(message, &proc)
95
+ def commit(message, &proc)
96
+ p4client.submit(message, &proc)
59
97
  end
60
98
 
61
- def changesets(checkout_dir, from_identifier, to_identifier=Time.infinity)
62
- client(checkout_dir).changesets(from_identifier, to_identifier)
99
+ def revisions(from_identifier, to_identifier=Time.infinity)
100
+ p4client.revisions(from_identifier, to_identifier)
63
101
  end
64
102
 
65
- def uptodate?(checkout_dir, from_identifier)
66
- client(checkout_dir).uptodate?
103
+ def uptodate?(from_identifier)
104
+ p4client.uptodate?
67
105
  end
68
106
 
69
107
  def edit(file)
70
- client_containing(file).edit(file)
108
+ p4client.edit(file)
71
109
  end
72
110
 
73
111
  def trigger_installed?(trigger_command, trigger_files_checkout_dir)
@@ -82,25 +120,18 @@ module RSCM
82
120
  p4admin.uninstall_trigger(trigger_command)
83
121
  end
84
122
 
85
- private
86
-
87
- def p4admin
88
- @p4admin ||= P4Admin.new
123
+ def diff(revfile, &proc)
124
+ p4client.diff(revfile, &proc)
89
125
  end
90
126
 
91
- def client_containing(path)
92
- @clients.values.find {|client| client.contains?(path)}
93
- end
94
-
95
- def client(rootdir)
96
- @clients[rootdir] ||= create_client(rootdir)
97
- end
127
+ private
98
128
 
99
129
  def with_create_client(rootdir)
100
130
  raise "needs a block" unless block_given?
101
131
  rootdir = File.expand_path(rootdir)
102
132
  with_working_dir(rootdir) do
103
- client = create_client(rootdir)
133
+ mkdir_p(rootdir)
134
+ client = p4admin.create_client(rootdir, Perforce.next_client_name)
104
135
  begin
105
136
  yield client
106
137
  ensure
@@ -109,70 +140,43 @@ module RSCM
109
140
  end
110
141
  end
111
142
 
112
- def delete_client(client)
113
- p4admin.delete_client(client.name)
143
+ def self.next_client_name
144
+ "temp_client_#{@@counter += 1}"
114
145
  end
115
146
 
116
- def create_client(rootdir)
117
- rootdir = File.expand_path(rootdir) if rootdir =~ /\.\./
118
- mkdir_p(rootdir)
119
- p4admin.create_client(rootdir)
147
+ def delete_client(client)
148
+ p4admin.delete_client(client)
120
149
  end
121
150
 
122
151
  def list_files
123
152
  files = Dir["**/*"].delete_if{|f| File.directory?(f)}
124
153
  files.collect{|f| File.expand_path(f)}
125
154
  end
155
+ end
126
156
 
127
- class P4Daemon
128
- include FileUtils
129
-
130
- def initialize(depotpath)
131
- @depotpath = depotpath
132
- end
133
-
134
- def start
135
- shutdown if running?
136
- launch
137
- assert_running
138
- end
139
-
140
- def assert_running
141
- raise "p4d did not start properly" if timeout(10) { running? }
142
- end
143
-
144
- def launch
145
- fork do
146
- mkdir_p(@depotpath)
147
- cd(@depotpath)
148
- debug "starting p4 server"
149
- exec("p4d")
150
- end
151
- at_exit { shutdown }
152
- end
157
+ # Understands p4 administrative operations (not specific to a client)
158
+ class P4Admin
153
159
 
154
- def shutdown
155
- `p4 -p 1666 admin stop`
156
- end
160
+ def initialize(port, user, pwd)
161
+ @port, @user, @pwd = port, user, pwd
162
+ end
157
163
 
158
- def running?
159
- !`p4 -p 1666 info`.empty?
164
+ def create_client(rootdir, clientname)
165
+ rootdir = File.expand_path(rootdir) if rootdir =~ /\.\./
166
+ unless client_exists?(rootdir, clientname)
167
+ execute_popen("client -i", "w+", clientspec(clientname, rootdir))
160
168
  end
169
+ P4Client.new(rootdir, clientname, @port, @user, @pwd)
161
170
  end
162
- end
163
-
164
- # Understands p4 administrative operations (not specific to a client)
165
- class P4Admin
166
- @@counter = 0
167
171
 
168
- def create_client(rootdir)
169
- name = next_name
170
- popen("client -i", "w+", clientspec(name, rootdir))
171
- P4Client.new(name, rootdir)
172
+ def client_exists?(rootdir, clientname)
173
+ dir_regex = Regexp.new(rootdir)
174
+ name_regex = Regexp.new(clientname)
175
+ execute("clients").split("\n").find {|c| c =~ dir_regex && c =~ name_regex}
172
176
  end
173
177
 
174
- def delete_client(name)
175
- execute("client -d #{name}")
178
+ def delete_client(client)
179
+ execute("client -d #{client.name}")
176
180
  end
177
181
 
178
182
  def trigger_installed?(trigger_command)
@@ -180,22 +184,26 @@ module RSCM
180
184
  end
181
185
 
182
186
  def install_trigger(trigger_command)
183
- popen("triggers -i", "a+", triggerspec_with(trigger_command))
187
+ execute_popen("triggers -i", "a+", triggerspec_append(trigger_command))
184
188
  end
185
189
 
186
190
  def uninstall_trigger(trigger_command)
187
- popen("triggers -i", "a+", triggerspec_without(trigger_command))
191
+ execute_popen("triggers -i", "a+", triggerspec_remove(trigger_command))
188
192
  end
189
193
 
190
- def triggerspec_with(trigger_command)
194
+ def triggerspec_append(trigger_command)
191
195
  new_trigger = " damagecontrol commit //depot/... \"#{trigger_command}\" "
192
196
  triggers + $/ + new_trigger
193
197
  end
194
198
 
195
- def triggerspec_without(trigger_command)
199
+ def triggerspec_remove(trigger_command)
196
200
  triggers.reject {|line| line =~ /#{trigger_command}/}.join
197
201
  end
198
202
 
203
+ def central_exists?
204
+ execute("info").split.join(" ") !~ /Connect to server failed/
205
+ end
206
+
199
207
  def clientspec(name, rootdir)
200
208
  s = StringIO.new
201
209
  s.puts "Client: #{name}"
@@ -213,8 +221,8 @@ module RSCM
213
221
  execute("triggers -o")
214
222
  end
215
223
 
216
- def popen(cmd, mode, input)
217
- IO.popen("p4 -p 1666 #{cmd}", mode) do |io|
224
+ def execute_popen(cmd, mode, input)
225
+ IO.popen(format_cmd(cmd), mode) do |io|
218
226
  io.puts(input)
219
227
  io.close_write
220
228
  io.each_line {|line| debug(line)}
@@ -222,41 +230,38 @@ module RSCM
222
230
  end
223
231
 
224
232
  def execute(cmd)
225
- cmd = "p4 -p 1666 #{cmd}"
226
- puts "> executing: #{cmd}"
233
+ cmd = format_cmd(cmd)
234
+ $stderr.puts "> executing: #{cmd}"
227
235
  `#{cmd}`
228
236
  end
229
237
 
230
- def next_name
231
- "client#{@@counter += 1}"
238
+ def format_cmd(cmd)
239
+ "p4 -p #{@port} -u '#{@user}' -P '#{@pwd}' #{cmd} 2>&1"
232
240
  end
233
241
  end
234
242
 
235
243
  # Understands operations against a client-workspace
236
244
  class P4Client
237
245
  DATE_FORMAT = "%Y/%m/%d:%H:%M:%S"
238
- STATUS = { "add" => Change::ADDED, "edit" => Change::MODIFIED, "delete" => Change::DELETED }
239
- PERFORCE_EPOCH = Time.utc(1970, 1, 1, 6, 0, 1) #perforce doesn't like Time.utc(1970)
240
-
241
- attr_accessor :name, :rootdir
242
-
243
- def initialize(name, rootdir)
244
- @name = name
245
- @rootdir = rootdir
246
- end
246
+ STATUS = { "add" => RevisionFile::ADDED, "edit" => RevisionFile::MODIFIED, "delete" => RevisionFile::DELETED }
247
247
 
248
- def contains?(file)
249
- file = File.expand_path(file)
250
- file =~ /^#{@rootdir}/
248
+ def initialize(checkout_dir, name, port, user, pwd)
249
+ @checkout_dir, @name, @port, @user, @pwd = checkout_dir, name, port, user, pwd
251
250
  end
252
251
 
253
252
  def uptodate?
254
253
  p4("sync -n").empty?
255
254
  end
256
255
 
257
- def changesets(from_identifier, to_identifier)
258
- changesets = changelists(from_identifier, to_identifier).collect {|changelist| to_changeset(changelist)}
259
- ChangeSets.new(changesets)
256
+ def revisions(from_identifier, to_identifier)
257
+ revisions = changelists(from_identifier, to_identifier).collect {|changelist| to_revision(changelist)}
258
+ # We have to reverse the revisions in order to make them appear in chronological order,
259
+ # P4 lists the newest ones first.
260
+ Revisions.new(revisions).reverse
261
+ end
262
+
263
+ def name
264
+ @name
260
265
  end
261
266
 
262
267
  def edit(file)
@@ -265,7 +270,7 @@ module RSCM
265
270
  end
266
271
 
267
272
  def add(relative_path)
268
- add_file(@rootdir + "/" + relative_path)
273
+ add_file(rootdir + "/" + relative_path)
269
274
  end
270
275
 
271
276
  def add_all(files)
@@ -284,8 +289,8 @@ module RSCM
284
289
  cmd = to_identifier.nil? ? "sync" : "sync //...@#{to_identifier}"
285
290
  checked_out_files = []
286
291
  p4(cmd).collect do |output|
287
- puts "output: '#{output}'"
288
- if(output =~ /.* - (added as|updating|deleted as) #{@rootdir}[\/|\\](.*)/)
292
+ #puts "output: '#{output}'"
293
+ if(output =~ /.* - (added as|updating|deleted as) #{rootdir}[\/|\\](.*)/)
289
294
  path = $2.gsub(/\\/, "/")
290
295
  checked_out_files << path
291
296
  yield path if block_given?
@@ -294,8 +299,26 @@ module RSCM
294
299
  checked_out_files
295
300
  end
296
301
 
302
+ def diff(r)
303
+ path = File.expand_path(@checkout_dir + "/" + r.path)
304
+ from = r.previous_native_revision_identifier
305
+ to = r.native_revision_identifier
306
+ cmd = p4cmd("diff2 -du #{path}@#{from} #{path}@#{to}")
307
+ Better.popen(cmd) do |io|
308
+ return(yield(io))
309
+ end
310
+ end
311
+
297
312
  private
298
313
 
314
+ def rootdir
315
+ unless @rootdir
316
+ p4("info") =~ /Client root: (.+)/
317
+ @rootdir = $1
318
+ end
319
+ @rootdir
320
+ end
321
+
299
322
  def add_file(absolute_path)
300
323
  absolute_path = PathConverter.filepath_to_nativepath(absolute_path, true)
301
324
  p4("add #{absolute_path}")
@@ -310,48 +333,53 @@ module RSCM
310
333
  end
311
334
  end
312
335
 
313
- def to_changeset(changelist)
336
+ def to_revision(changelist)
314
337
  return nil if changelist.nil? # Ugly, but it seems to be nil some times on windows.
315
338
  changes = changelist.files.collect do |filespec|
316
- change = Change.new(filespec.path, changelist.developer, changelist.message, filespec.revision, changelist.time)
339
+ change = RevisionFile.new(filespec.path, changelist.developer, changelist.message, filespec.revision, changelist.time)
317
340
  change.status = STATUS[filespec.status]
318
- change.previous_revision = filespec.revision - 1
341
+ change.previous_native_revision_identifier = filespec.revision - 1
319
342
  change
320
343
  end
321
- changeset = ChangeSet.new(changes)
322
- changeset.revision = changelist.number
323
- changeset.developer = changelist.developer
324
- changeset.message = changelist.message
325
- changeset.time = changelist.time
326
- changeset
344
+ revision = Revision.new(changes)
345
+ revision.identifier = changelist.number
346
+ revision.developer = changelist.developer
347
+ revision.message = changelist.message
348
+ revision.time = changelist.time
349
+ revision
327
350
  end
328
351
 
329
- def p4describe(chnum)
330
- p4("describe -s #{chnum}")
352
+ def p4changes(from_identifier, to_identifier)
353
+ from = p4timespec(from_identifier, Time.epoch)
354
+ to = p4timespec(to_identifier, Time.infinity)
355
+ $stderr.puts "in p4changes translated #{from_identifier},#{to_identifier} to #{from},#{to}"
356
+ p4("changes //...@#{from},#{to}")
331
357
  end
332
358
 
333
- def p4changes(from_identifier, to_identifier)
334
- if from_identifier.nil? || from_identifier.is_a?(Time)
335
- from_identifier = PERFORCE_EPOCH if from_identifier.nil? || from_identifier < PERFORCE_EPOCH
336
- to_identifier = Time.infinity if to_identifier.nil?
337
- from = from_identifier.strftime(DATE_FORMAT)
338
- to = to_identifier.strftime(DATE_FORMAT)
339
- p4("changes //...@#{from},#{to}")
359
+ def p4timespec(identifier, default)
360
+ identifier = default if identifier.nil?
361
+ if identifier.is_a?(Time)
362
+ identifier = Time.epoch if identifier < Time.epoch
363
+ (identifier+1).strftime(DATE_FORMAT)
340
364
  else
341
- p4("changes //...@#{from_identifier},#{from_identifier}")
365
+ "#{identifier + 1}"
342
366
  end
343
367
  end
344
368
 
369
+ def p4describe(chnum)
370
+ p4("describe -s #{chnum}")
371
+ end
372
+
345
373
  def p4(cmd)
346
374
  cmd = "#{p4cmd(cmd)}"
347
- puts "> executing: #{cmd}"
375
+ $stderr.puts "> executing: #{cmd}"
348
376
  output = `#{cmd}`
349
- puts output
377
+ #puts output
350
378
  output
351
379
  end
352
380
 
353
381
  def p4cmd(cmd)
354
- "p4 -p 1666 -c #{@name} #{cmd}"
382
+ "p4 -p #{@port} -c '#{@name}' -u '#{@user}' -P '#{@pwd}' #{cmd}"
355
383
  end
356
384
 
357
385
  def submitspec(comment)
@@ -377,7 +405,7 @@ module RSCM
377
405
  def initialize(log)
378
406
  debug log
379
407
  if(log =~ /^Change (\d+) by (.*) on (.*)$/)
380
- # @number, @developer, @time = $1.to_i, $2, Time.utc(*ParseDate.parsedate($3)[0..5])
408
+ #@number, @developer, @time = $1.to_i, $2, Time.utc(*ParseDate.parsedate($3)[0..5])
381
409
  @number, @developer, @time = $1.to_i, $2, Time.utc(*ParseDate.parsedate($3))
382
410
  else
383
411
  raise "Bad log format: '#{log}'"
@@ -397,6 +425,40 @@ module RSCM
397
425
  end
398
426
  end
399
427
 
428
+ class P4Daemon
429
+ include FileUtils
430
+
431
+ def initialize(depotpath)
432
+ @depotpath = depotpath
433
+ end
434
+
435
+ def start
436
+ launch
437
+ assert_running
438
+ end
439
+
440
+ def assert_running
441
+ raise "p4d did not start properly" if timeout(10) { running? }
442
+ end
443
+
444
+ def launch
445
+ fork do
446
+ mkdir_p(@depotpath)
447
+ cd(@depotpath)
448
+ debug "starting p4 server"
449
+ exec("p4d")
450
+ end
451
+ at_exit { shutdown }
452
+ end
453
+
454
+ def shutdown
455
+ `p4 -p 1666 admin stop` if running?
456
+ end
457
+
458
+ def running?
459
+ `p4 -p 1666 info 2>&1`!~ /Connect to server failed/
460
+ end
461
+ end
400
462
  end
401
463
 
402
464
  module Kernel
@@ -412,7 +474,7 @@ module Kernel
412
474
 
413
475
  #todo: replace with logger
414
476
  def debug(msg)
415
- puts msg
477
+ #puts msg
416
478
  end
417
479
 
418
480
  end