rscm 0.4.3 → 0.4.4
Sign up to get free protection for your applications and to get access to all the features.
- data/CHANGES +15 -0
- data/README +91 -81
- data/Rakefile +5 -1
- data/lib/rscm/base.rb +20 -27
- data/lib/rscm/command_line.rb +2 -2
- data/lib/rscm/difftool.rb +19 -8
- data/lib/rscm/revision.rb +14 -1
- data/lib/rscm/revision_file.rb +10 -0
- data/lib/rscm/scm/cvs.rb +3 -3
- data/lib/rscm/scm/mooky.rb +0 -6
- data/lib/rscm/scm/perforce.rb +112 -451
- data/lib/rscm/tempdir.rb +5 -0
- data/lib/rscm/version.rb +1 -1
- data/test/rscm/compatibility/config.yml +4 -0
- data/test/rscm/compatibility/cvs_metaproject/files_0.yml +13 -0
- data/test/rscm/compatibility/cvs_metaproject/revisions.yml +129 -0
- data/test/rscm/compatibility/cvs_metaproject/scm.yml +3 -0
- data/test/rscm/compatibility/damage_control_minimal.rb +104 -0
- data/test/rscm/{generic_scm_tests.rb → compatibility/full.rb} +8 -15
- data/test/rscm/compatibility/subversion_rscm/files_0.yml +35 -0
- data/test/rscm/compatibility/subversion_rscm/revisions.yml +69 -0
- data/test/rscm/compatibility/subversion_rscm/scm.yml +2 -0
- data/test/rscm/compatibility/subversion_rscm/svn_log_bug_irc.txt +87 -0
- data/test/rscm/scm/cvs_log_parser_test.rb +1 -0
- data/test/rscm/scm/cvs_test.rb +9 -15
- data/test/rscm/scm/mooky_test.rb +2 -9
- data/test/rscm/scm/perforce_test.rb +2 -31
- data/test/rscm/scm/subversion_test.rb +3 -6
- data/test/rscm/test_helper.rb +9 -0
- metadata +16 -4
- data/lib/rscm/scm/subversion_log_parser.rb.rej +0 -39
data/lib/rscm/revision.rb
CHANGED
@@ -62,6 +62,14 @@ module RSCM
|
|
62
62
|
result
|
63
63
|
end
|
64
64
|
|
65
|
+
def first
|
66
|
+
@revisions.first
|
67
|
+
end
|
68
|
+
|
69
|
+
def last
|
70
|
+
@revisions.last
|
71
|
+
end
|
72
|
+
|
65
73
|
# The latest Revision (with the latest time)
|
66
74
|
# or nil if there are none.
|
67
75
|
def latest
|
@@ -164,7 +172,12 @@ module RSCM
|
|
164
172
|
end
|
165
173
|
|
166
174
|
def ==(other)
|
167
|
-
other.is_a?(self.class) &&
|
175
|
+
other.is_a?(self.class) &&
|
176
|
+
@developer == other.developer &&
|
177
|
+
@identifier == other.identifier &&
|
178
|
+
@message == other.message &&
|
179
|
+
@time == other.time &&
|
180
|
+
@files == other.files
|
168
181
|
end
|
169
182
|
|
170
183
|
def <=>(other)
|
data/lib/rscm/revision_file.rb
CHANGED
@@ -35,6 +35,15 @@ module RSCM
|
|
35
35
|
@path, @developer, @message, @native_revision_identifier, @time, @status = path, developer, message, native_revision_identifier, time, status
|
36
36
|
end
|
37
37
|
|
38
|
+
def to_yaml_properties #:nodoc:
|
39
|
+
# We remove properties that are duplicated in the parent revision.
|
40
|
+
props = instance_variables
|
41
|
+
props.delete("@developer")
|
42
|
+
props.delete("@message")
|
43
|
+
props.delete("@time")
|
44
|
+
props.sort!
|
45
|
+
end
|
46
|
+
|
38
47
|
# Returns/yields an IO containing the contents of this file, using the +scm+ this
|
39
48
|
# file lives in.
|
40
49
|
def open(scm, options={}, &block) #:yield: io
|
@@ -58,6 +67,7 @@ module RSCM
|
|
58
67
|
|
59
68
|
def ==(other)
|
60
69
|
return false if !other.is_a?(self.class)
|
70
|
+
self.status == other.status &&
|
61
71
|
self.path == other.path &&
|
62
72
|
self.developer == other.developer &&
|
63
73
|
self.message == other.message &&
|
data/lib/rscm/scm/cvs.rb
CHANGED
@@ -211,14 +211,14 @@ module RSCM
|
|
211
211
|
options = options.dup.merge({
|
212
212
|
:dir => File.dirname(@checkout_dir)
|
213
213
|
})
|
214
|
-
cvs(checkout_command(target_dir, to_identifier), options)
|
214
|
+
cvs(checkout_command(target_dir, to_identifier), options, &proc)
|
215
215
|
end
|
216
216
|
end
|
217
217
|
|
218
218
|
def ignore_paths
|
219
219
|
[/CVS\/.*/]
|
220
220
|
end
|
221
|
-
|
221
|
+
|
222
222
|
private
|
223
223
|
|
224
224
|
def cvs(cmd, options={}, &proc)
|
@@ -271,7 +271,7 @@ module RSCM
|
|
271
271
|
if(from_identifier.nil? && to_identifier.nil?)
|
272
272
|
""
|
273
273
|
else
|
274
|
-
"-d\"#{cvsdate(from_identifier)}<#{cvsdate(to_identifier)}\" "
|
274
|
+
"-d\"#{cvsdate(from_identifier)}<#{cvsdate(to_identifier+1)}\" "
|
275
275
|
end
|
276
276
|
end
|
277
277
|
|
data/lib/rscm/scm/mooky.rb
CHANGED
data/lib/rscm/scm/perforce.rb
CHANGED
@@ -1,488 +1,149 @@
|
|
1
|
+
# TODO
|
2
|
+
# Support int revision numbers AND dates
|
3
|
+
# Leverage default P4 client settings (optional)
|
4
|
+
|
1
5
|
require 'rscm/base'
|
2
6
|
require 'rscm/path_converter'
|
3
7
|
require 'rscm/line_editor'
|
4
8
|
|
5
9
|
require 'fileutils'
|
10
|
+
require 'time'
|
6
11
|
require 'socket'
|
7
|
-
require 'pp'
|
8
|
-
require 'parsedate'
|
9
|
-
require 'stringio'
|
10
12
|
|
11
13
|
module RSCM
|
12
|
-
# Perforce RSCM implementation.
|
13
|
-
#
|
14
|
-
# Understands operations against multiple client-workspaces
|
15
|
-
# You need the p4/p4d executable on the PATH in order for it to work.
|
16
|
-
#
|
17
14
|
class Perforce < Base
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
def can_create_central?
|
39
|
-
true
|
40
|
-
end
|
41
|
-
|
42
|
-
def create_central
|
43
|
-
raise "perforce depot can be created only from tests" unless @repository_root_dir
|
44
|
-
@p4d = P4Daemon.new(@repository_root_dir)
|
45
|
-
@p4d.start
|
46
|
-
end
|
47
|
-
|
48
|
-
def destroy_central
|
49
|
-
@p4d.shutdown
|
50
|
-
end
|
51
|
-
|
52
|
-
def central_exists?
|
53
|
-
p4admin.central_exists?
|
54
|
-
end
|
55
|
-
|
56
|
-
def can_create_central?
|
57
|
-
true
|
58
|
-
end
|
59
|
-
|
60
|
-
def supports_trigger?
|
61
|
-
true
|
62
|
-
end
|
63
|
-
|
64
|
-
def transactional?
|
65
|
-
true
|
66
|
-
end
|
67
|
-
|
68
|
-
def import_central(dir, comment)
|
69
|
-
with_create_client(dir) do |client|
|
70
|
-
client.add_all(list_files)
|
71
|
-
client.submit(comment)
|
15
|
+
DATE_FORMAT = "%Y/%m/%d:%H:%M:%S"
|
16
|
+
# Doesn't work for empty messages, (Like 21358 in Aslak's P4 repo)
|
17
|
+
CHANGELIST_PATTERN = /^Change \d+ by (.*)@.* on (.*)\n\n(.*)\n\nAffected files ...\n\n(.*)/m
|
18
|
+
# But this one does
|
19
|
+
CHANGELIST_PATTERN_NO_MSG = /^Change \d+ by (.*)@.* on (.*)\n\nAffected files ...\n\n(.*)/m
|
20
|
+
|
21
|
+
def initialize(user=nil, password=nil, view=nil)
|
22
|
+
@user, @password = user, password
|
23
|
+
end
|
24
|
+
|
25
|
+
def revisions(from_identifier, options={})
|
26
|
+
from_identifier = Time.epoch unless from_identifier
|
27
|
+
from_identifier = Time.epoch if (from_identifier.is_a? Time and from_identifier < Time.epoch)
|
28
|
+
from = revision_spec(from_identifier+1) # We have to add 1 because of the constract of this method.
|
29
|
+
|
30
|
+
to_identifier = options[:to_identifier] ? options[:to_identifier] : Time.infinity
|
31
|
+
to = revision_spec(to_identifier)
|
32
|
+
|
33
|
+
changes = execute("p4 #{p4_opts(false)} changes //...@#{from},#{to}", options) do |io|
|
34
|
+
io.read
|
72
35
|
end
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
def revisions(from_identifier, to_identifier=Time.infinity)
|
92
|
-
p4client.revisions(from_identifier, to_identifier)
|
93
|
-
end
|
94
|
-
|
95
|
-
def uptodate?(from_identifier)
|
96
|
-
p4client.uptodate?
|
97
|
-
end
|
98
|
-
|
99
|
-
def edit(file)
|
100
|
-
p4client.edit(file)
|
101
|
-
end
|
102
|
-
|
103
|
-
def trigger_installed?(trigger_command, trigger_files_checkout_dir)
|
104
|
-
p4admin.trigger_installed?(trigger_command)
|
105
|
-
end
|
106
|
-
|
107
|
-
def install_trigger(trigger_command, damagecontrol_install_dir)
|
108
|
-
p4admin.install_trigger(trigger_command)
|
109
|
-
end
|
110
|
-
|
111
|
-
def uninstall_trigger(trigger_command, trigger_files_checkout_dir)
|
112
|
-
p4admin.uninstall_trigger(trigger_command)
|
113
|
-
end
|
114
|
-
|
115
|
-
def trigger_mechanism
|
116
|
-
"p4 triggers -i"
|
117
|
-
end
|
118
|
-
|
119
|
-
def diff(revfile, &proc)
|
120
|
-
p4client.diff(revfile, &proc)
|
121
|
-
end
|
36
|
+
|
37
|
+
revs = changes.collect do |line|
|
38
|
+
revision = Revision.new
|
39
|
+
revision.identifier = line.match(/^Change (\d+)/)[1].to_i
|
40
|
+
|
41
|
+
execute("p4 #{p4_opts(false)} describe -s #{revision.identifier}", options) do |io|
|
42
|
+
log = io.read
|
43
|
+
|
44
|
+
if log =~ CHANGELIST_PATTERN
|
45
|
+
revision.developer, time, revision.message, files = $1, $2, $3, $4
|
46
|
+
elsif log =~ CHANGELIST_PATTERN_NO_MSG
|
47
|
+
revision.developer, time, files = $1, $2, $3
|
48
|
+
else
|
49
|
+
puts "PARSE ERROR:"
|
50
|
+
puts log
|
51
|
+
puts "\nDIDN'T MATCH:"
|
52
|
+
puts CHANGELIST_PATTERN
|
53
|
+
end
|
122
54
|
|
123
|
-
|
55
|
+
revision.time = Time.parse(time)
|
56
|
+
|
57
|
+
files.each_line do |line|
|
58
|
+
if line =~ /^\.\.\. (\/\/.+)#(\d+) (.+)/
|
59
|
+
if(STATES[$3.strip])
|
60
|
+
file = RevisionFile.new
|
61
|
+
file.path = $1
|
62
|
+
file.native_revision_identifier = $2.to_i
|
63
|
+
file.previous_native_revision_identifier = file.native_revision_identifier-1
|
64
|
+
file.status = STATES[$3.strip]
|
65
|
+
file.time = revision.time
|
66
|
+
file.message = revision.message
|
67
|
+
file.developer = revision.developer
|
68
|
+
revision << file
|
69
|
+
end
|
70
|
+
end
|
71
|
+
end
|
124
72
|
|
125
|
-
def with_create_client(rootdir)
|
126
|
-
raise "needs a block" unless block_given?
|
127
|
-
rootdir = File.expand_path(rootdir)
|
128
|
-
with_working_dir(rootdir) do
|
129
|
-
FileUtils.mkdir_p(rootdir)
|
130
|
-
client = p4admin.create_client(rootdir, Perforce.next_client_name)
|
131
|
-
begin
|
132
|
-
yield client
|
133
|
-
ensure
|
134
|
-
delete_client(client)
|
135
73
|
end
|
136
|
-
end
|
137
|
-
end
|
138
|
-
|
139
|
-
def self.next_client_name
|
140
|
-
"temp_client_#{@@counter += 1}"
|
141
|
-
end
|
142
|
-
|
143
|
-
def delete_client(client)
|
144
|
-
p4admin.delete_client(client)
|
145
|
-
end
|
146
|
-
|
147
|
-
def list_files
|
148
|
-
files = Dir["**/*"].delete_if{|f| File.directory?(f)}
|
149
|
-
files.collect{|f| File.expand_path(f)}
|
150
|
-
end
|
151
|
-
end
|
152
74
|
|
153
|
-
|
154
|
-
class P4Admin
|
155
|
-
|
156
|
-
def initialize(port, user, pwd)
|
157
|
-
@port, @user, @pwd = port, user, pwd
|
158
|
-
end
|
159
|
-
|
160
|
-
def create_client(rootdir, clientname)
|
161
|
-
rootdir = File.expand_path(rootdir) if rootdir =~ /\.\./
|
162
|
-
unless client_exists?(rootdir, clientname)
|
163
|
-
execute_popen("client -i", "w+", clientspec(clientname, rootdir))
|
75
|
+
revision
|
164
76
|
end
|
165
|
-
|
166
|
-
end
|
167
|
-
|
168
|
-
def client_exists?(rootdir, clientname)
|
169
|
-
dir_regex = Regexp.new(rootdir)
|
170
|
-
name_regex = Regexp.new(clientname)
|
171
|
-
execute("clients").split("\n").find {|c| c =~ dir_regex && c =~ name_regex}
|
77
|
+
Revisions.new(revs.reverse)
|
172
78
|
end
|
173
79
|
|
174
|
-
|
175
|
-
execute("client -d #{client.name}")
|
176
|
-
end
|
177
|
-
|
178
|
-
def trigger_installed?(trigger_command)
|
179
|
-
triggers.any? {|line| line =~ /#{trigger_command}/}
|
180
|
-
end
|
181
|
-
|
182
|
-
def install_trigger(trigger_command)
|
183
|
-
execute_popen("triggers -i", "a+", triggerspec_append(trigger_command))
|
184
|
-
end
|
185
|
-
|
186
|
-
def uninstall_trigger(trigger_command)
|
187
|
-
execute_popen("triggers -i", "a+", triggerspec_remove(trigger_command))
|
188
|
-
end
|
189
|
-
|
190
|
-
def triggerspec_append(trigger_command)
|
191
|
-
new_trigger = " damagecontrol commit //depot/... \"#{trigger_command}\" "
|
192
|
-
triggers + $/ + new_trigger
|
193
|
-
end
|
194
|
-
|
195
|
-
def triggerspec_remove(trigger_command)
|
196
|
-
triggers.reject {|line| line =~ /#{trigger_command}/}.join
|
197
|
-
end
|
80
|
+
protected
|
198
81
|
|
199
|
-
def
|
200
|
-
|
82
|
+
def checkout_silent(to_identifier, options)
|
83
|
+
checkout_dir = PathConverter.filepath_to_nativepath(@checkout_dir, false)
|
84
|
+
FileUtils.mkdir_p(@checkout_dir)
|
85
|
+
|
86
|
+
ensure_client(options)
|
87
|
+
execute("p4 #{p4_opts} sync", options)
|
201
88
|
end
|
202
89
|
|
203
|
-
def
|
204
|
-
|
205
|
-
s.puts "Client: #{name}"
|
206
|
-
s.puts "Owner: #{ENV["LOGNAME"]}"
|
207
|
-
s.puts "Host: #{ENV["HOSTNAME"]}"
|
208
|
-
s.puts "Description: another one"
|
209
|
-
s.puts "Root: #{rootdir}"
|
210
|
-
s.puts "Options: noallwrite noclobber nocompress unlocked nomodtime normdir"
|
211
|
-
s.puts "LineEnd: local"
|
212
|
-
s.puts "View: //depot/... //#{name}/..."
|
213
|
-
s.string
|
214
|
-
end
|
215
|
-
|
216
|
-
def triggers
|
217
|
-
execute("triggers -o")
|
218
|
-
end
|
219
|
-
|
220
|
-
def execute_popen(cmd, mode, input)
|
221
|
-
IO.popen(format_cmd(cmd), mode) do |io|
|
222
|
-
io.puts(input)
|
223
|
-
io.close_write
|
224
|
-
io.each_line {|line| debug(line)}
|
225
|
-
end
|
226
|
-
end
|
227
|
-
|
228
|
-
def execute(cmd)
|
229
|
-
cmd = format_cmd(cmd)
|
230
|
-
$stderr.puts "> executing: #{cmd}"
|
231
|
-
`#{cmd}`
|
232
|
-
end
|
233
|
-
|
234
|
-
def format_cmd(cmd)
|
235
|
-
"p4 -p #{@port} -u '#{@user}' -P '#{@pwd}' #{cmd} 2>&1"
|
236
|
-
end
|
237
|
-
end
|
238
|
-
|
239
|
-
# Understands operations against a client-workspace
|
240
|
-
class P4Client
|
241
|
-
DATE_FORMAT = "%Y/%m/%d:%H:%M:%S"
|
242
|
-
STATUS = { "add" => RevisionFile::ADDED, "edit" => RevisionFile::MODIFIED, "delete" => RevisionFile::DELETED }
|
243
|
-
|
244
|
-
def initialize(checkout_dir, name, port, user, pwd)
|
245
|
-
@checkout_dir, @name, @port, @user, @pwd = checkout_dir, name, port, user, pwd
|
246
|
-
end
|
247
|
-
|
248
|
-
def uptodate?
|
249
|
-
p4("sync -n").empty?
|
250
|
-
end
|
251
|
-
|
252
|
-
def revisions(from_identifier, to_identifier)
|
253
|
-
revisions = changelists(from_identifier, to_identifier).collect {|changelist| to_revision(changelist)}
|
254
|
-
# We have to reverse the revisions in order to make them appear in chronological order,
|
255
|
-
# P4 lists the newest ones first.
|
256
|
-
Revisions.new(revisions).reverse
|
257
|
-
end
|
258
|
-
|
259
|
-
def name
|
260
|
-
@name
|
261
|
-
end
|
262
|
-
|
263
|
-
def edit(file)
|
264
|
-
file = File.expand_path(file)
|
265
|
-
p4("edit #{file}")
|
266
|
-
end
|
267
|
-
|
268
|
-
def add(relative_path)
|
269
|
-
add_file(rootdir + "/" + relative_path)
|
90
|
+
def ignore_paths
|
91
|
+
[]
|
270
92
|
end
|
271
93
|
|
272
|
-
# http://www.perforce.com/perforce/doc.051/manuals/cmdref/rename.html#1040665
|
273
|
-
def move(checkout_dir, relative_src, relative_dest)
|
274
|
-
with_working_dir(checkout_dir) do
|
275
|
-
absolute_src = PathConverter.filepath_to_nativepath(relative_src, true)
|
276
|
-
absolute_dest = PathConverter.filepath_to_nativepath(relative_dest, true)
|
277
|
-
FileUtils.mv(absolute_src, absolute_dest)
|
278
|
-
p4("integrate #{absolute_src} #{absolute_dest}")
|
279
|
-
p4("delete #{absolute_src}")
|
280
|
-
end
|
281
|
-
# p4("submit #{absolute_src}")
|
282
|
-
end
|
283
|
-
|
284
|
-
def add_all(files)
|
285
|
-
files.each {|file| add_file(file)}
|
286
|
-
end
|
287
|
-
|
288
|
-
def submit(comment)
|
289
|
-
IO.popen(p4cmd("submit -i"), "w+") do |io|
|
290
|
-
io.puts(submitspec(comment))
|
291
|
-
io.close_write
|
292
|
-
io.each_line {|progress| debug progress}
|
293
|
-
end
|
294
|
-
end
|
295
|
-
|
296
|
-
def checkout(to_identifier)
|
297
|
-
cmd = to_identifier.nil? ? "sync" : "sync //...@#{to_identifier}"
|
298
|
-
checked_out_files = []
|
299
|
-
p4(cmd).collect do |output|
|
300
|
-
#puts "output: '#{output}'"
|
301
|
-
if(output =~ /.* - (added as|updating|deleted as) #{rootdir}[\/|\\](.*)/)
|
302
|
-
path = $2.gsub(/\\/, "/")
|
303
|
-
checked_out_files << path
|
304
|
-
yield path if block_given?
|
305
|
-
end
|
306
|
-
end
|
307
|
-
checked_out_files
|
308
|
-
end
|
309
|
-
|
310
|
-
def diff(r)
|
311
|
-
path = File.expand_path(@checkout_dir + "/" + r.path)
|
312
|
-
from = r.previous_native_revision_identifier
|
313
|
-
to = r.native_revision_identifier
|
314
|
-
cmd = p4cmd("diff2 -du #{path}@#{from} #{path}@#{to}")
|
315
|
-
Better.popen(cmd) do |io|
|
316
|
-
return(yield(io))
|
317
|
-
end
|
318
|
-
end
|
319
|
-
|
320
94
|
private
|
321
|
-
|
322
|
-
|
323
|
-
|
324
|
-
|
325
|
-
|
326
|
-
|
327
|
-
|
95
|
+
|
96
|
+
STATES = {
|
97
|
+
"add" => RevisionFile::ADDED,
|
98
|
+
"edit" => RevisionFile::MODIFIED,
|
99
|
+
"delete" => RevisionFile::DELETED
|
100
|
+
}
|
101
|
+
|
102
|
+
def p4_opts(with_client=true)
|
103
|
+
user_opt = @user.to_s.empty? ? "" : "-u #{@user}"
|
104
|
+
password_opt = @password.to_s.empty? ? "" : "-P #{@password}"
|
105
|
+
client_opt = with_client ? "-c \"#{client_name}\"" : ""
|
106
|
+
"#{user_opt} #{password_opt} #{client_opt}"
|
328
107
|
end
|
329
|
-
|
330
|
-
def
|
331
|
-
|
332
|
-
|
108
|
+
|
109
|
+
def client_name
|
110
|
+
raise "checkout_dir not set" unless @checkout_dir
|
111
|
+
Socket.gethostname + ":" + @checkout_dir
|
333
112
|
end
|
334
|
-
|
335
|
-
def
|
336
|
-
|
337
|
-
if line =~ /^Change (\d+) /
|
338
|
-
log = p4describe($1)
|
339
|
-
P4Changelist.new(log) unless log == ""
|
340
|
-
end
|
341
|
-
end
|
113
|
+
|
114
|
+
def ensure_client(options)
|
115
|
+
create_client(options)
|
342
116
|
end
|
343
|
-
|
344
|
-
def
|
345
|
-
|
346
|
-
|
347
|
-
|
348
|
-
|
349
|
-
change.previous_native_revision_identifier = filespec.revision - 1
|
350
|
-
change
|
117
|
+
|
118
|
+
def create_client(options)
|
119
|
+
options = {:mode => "w+"}.merge(options)
|
120
|
+
execute("p4 #{p4_opts(false)} client -i", options) do |io|
|
121
|
+
io.puts(client_spec)
|
122
|
+
io.close_write
|
351
123
|
end
|
352
|
-
revision = Revision.new(changes)
|
353
|
-
revision.identifier = changelist.number
|
354
|
-
revision.developer = changelist.developer
|
355
|
-
revision.message = changelist.message
|
356
|
-
revision.time = changelist.time
|
357
|
-
revision
|
358
124
|
end
|
359
|
-
|
360
|
-
def
|
361
|
-
|
362
|
-
|
363
|
-
|
364
|
-
|
125
|
+
|
126
|
+
def client_spec
|
127
|
+
<<-EOF
|
128
|
+
Client: #{client_name}
|
129
|
+
Owner: #{@user}
|
130
|
+
Host: #{Socket.gethostname}
|
131
|
+
Description: RSCM client
|
132
|
+
Root: #{@checkout_dir}
|
133
|
+
Options: noallwrite noclobber nocompress unlocked nomodtime normdir
|
134
|
+
LineEnd: local
|
135
|
+
View: #{@view} //#{client_name}/...
|
136
|
+
EOF
|
365
137
|
end
|
366
|
-
|
367
|
-
def
|
368
|
-
identifier = default if identifier.nil?
|
138
|
+
|
139
|
+
def revision_spec(identifier)
|
369
140
|
if identifier.is_a?(Time)
|
370
|
-
identifier
|
371
|
-
(identifier+1).strftime(DATE_FORMAT)
|
141
|
+
identifier.strftime(DATE_FORMAT)
|
372
142
|
else
|
373
|
-
|
374
|
-
end
|
375
|
-
end
|
376
|
-
|
377
|
-
def p4describe(chnum)
|
378
|
-
p4("describe -s #{chnum}")
|
379
|
-
end
|
380
|
-
|
381
|
-
def p4(cmd)
|
382
|
-
cmd = "#{p4cmd(cmd)}"
|
383
|
-
$stderr.puts "> executing: #{cmd}"
|
384
|
-
output = `#{cmd}`
|
385
|
-
#puts output
|
386
|
-
output
|
387
|
-
end
|
388
|
-
|
389
|
-
def p4cmd(cmd)
|
390
|
-
"p4 -p #{@port} -c '#{@name}' -u '#{@user}' -P '#{@pwd}' #{cmd}"
|
391
|
-
end
|
392
|
-
|
393
|
-
def submitspec(comment)
|
394
|
-
s = StringIO.new
|
395
|
-
s.puts "Change: new"
|
396
|
-
s.puts "Client: #{@name}"
|
397
|
-
s.puts "Description: #{comment.gsub(/\n/, "\n\t")}"
|
398
|
-
s.puts "Files: "
|
399
|
-
p4("opened").each do |line|
|
400
|
-
if line =~ /^(.+)#\d+ - (\w+) /
|
401
|
-
status, revision = $1, $2
|
402
|
-
s.puts "\t#{status} # #{revision}"
|
403
|
-
end
|
404
|
-
end
|
405
|
-
s.string
|
406
|
-
end
|
407
|
-
|
408
|
-
FileSpec = Struct.new(:path, :revision, :status)
|
409
|
-
|
410
|
-
class P4Changelist
|
411
|
-
attr_reader :number, :developer, :message, :time, :files
|
412
|
-
|
413
|
-
def initialize(log)
|
414
|
-
debug log
|
415
|
-
if(log =~ /^Change (\d+) by (.*) on (.*)$/)
|
416
|
-
#@number, @developer, @time = $1.to_i, $2, Time.utc(*ParseDate.parsedate($3)[0..5])
|
417
|
-
@number, @developer, @time = $1.to_i, $2, Time.utc(*ParseDate.parsedate($3))
|
418
|
-
else
|
419
|
-
raise "Bad log format: '#{log}'"
|
420
|
-
end
|
421
|
-
|
422
|
-
if log =~ /Change (.*)\n\n(.*)\n\nAffected/m
|
423
|
-
@message = $2.strip.gsub(/\n\t/, "\n")
|
424
|
-
end
|
425
|
-
|
426
|
-
@files = []
|
427
|
-
log.each do |line|
|
428
|
-
if line =~ /^\.\.\. \/\/depot\/(.+)#(\d+) (.+)/
|
429
|
-
files << FileSpec.new($1, Integer($2), $3)
|
430
|
-
end
|
431
|
-
end
|
143
|
+
identifier.to_i
|
432
144
|
end
|
433
145
|
end
|
434
|
-
end
|
435
|
-
|
436
|
-
class P4Daemon
|
437
|
-
include FileUtils
|
438
|
-
|
439
|
-
def initialize(depotpath)
|
440
|
-
@depotpath = depotpath
|
441
|
-
end
|
442
|
-
|
443
|
-
def start
|
444
|
-
launch
|
445
|
-
assert_running
|
446
|
-
end
|
447
|
-
|
448
|
-
def assert_running
|
449
|
-
raise "p4d did not start properly" if timeout(10) { running? }
|
450
|
-
end
|
451
|
-
|
452
|
-
def launch
|
453
|
-
fork do
|
454
|
-
mkdir_p(@depotpath)
|
455
|
-
cd(@depotpath)
|
456
|
-
debug "starting p4 server"
|
457
|
-
exec("p4d")
|
458
|
-
end
|
459
|
-
at_exit { shutdown }
|
460
|
-
end
|
461
|
-
|
462
|
-
def shutdown
|
463
|
-
`p4 -p 1666 admin stop` if running?
|
464
|
-
end
|
465
|
-
|
466
|
-
def running?
|
467
|
-
`p4 -p 1666 info 2>&1`!~ /Connect to server failed/
|
468
|
-
end
|
469
|
-
end
|
470
|
-
end
|
471
|
-
|
472
|
-
module Kernel
|
473
|
-
|
474
|
-
# TODO: use Ruby's built-in timeout? (require 'timeout')
|
475
|
-
def timeout(attempts=5, &proc)
|
476
|
-
0.upto(attempts) do
|
477
|
-
sleep 1
|
478
|
-
return false if proc.call
|
479
|
-
end
|
480
|
-
true
|
481
|
-
end
|
482
146
|
|
483
|
-
#todo: replace with logger
|
484
|
-
def debug(msg)
|
485
|
-
#puts msg
|
486
147
|
end
|
487
148
|
|
488
149
|
end
|