rscm 0.4.3 → 0.4.4
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.
- 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
|