perforce 0.5.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (6) hide show
  1. data/README +58 -0
  2. data/install.rb +18 -0
  3. data/lib/perforce.rb +396 -0
  4. data/perforce.gemspec +22 -0
  5. data/test/test_main.rb +84 -0
  6. metadata +69 -0
data/README ADDED
@@ -0,0 +1,58 @@
1
+
2
+ = Perforce - Streamlined wrapper for p4ruby.
3
+
4
+ The classes Perforce and Perforce::Changelist present a simplified
5
+ interface to the Perforce API. They encapsulate all the functionality
6
+ I needed for scripting Perforce in a recent project.
7
+
8
+ These are small classes implemented atop P4, the class installed by
9
+ the P4Ruby package. The underlying P4 object is always available
10
+ with Perforce#p4. See http://p4ruby.rubyforge.org.
11
+
12
+ ==== Install
13
+
14
+ % gem install perforce
15
+
16
+ Or for the regular (non-gem) .tgz package,
17
+
18
+ % ruby install.rb [--uninstall]
19
+
20
+ ==== Download
21
+
22
+ * http://rubyforge.org/frs/?group_id=6958
23
+
24
+ ==== Repository
25
+
26
+ * http://github.com/quix/perforce
27
+
28
+ ==== Credits
29
+
30
+ P4Ruby and the Perforce API are Copyright (c) 1997-2008 Perforce
31
+ Software. All rights reserved.
32
+
33
+ This ruby package (perforce.rb and associated files) was written by
34
+ James M. Lawrence, Copyright (c) 2008 ImaginEngine, Inc.
35
+ Distributed under the MIT license. This package contains no code
36
+ owned by Perforce Software, in whole or in part. The following
37
+ license pertains to this package only.
38
+
39
+ Permission is hereby granted, free of charge, to any person
40
+ obtaining a copy of this software and associated documentation files
41
+ (the "Software"), to deal in the Software without restriction,
42
+ including without limitation the rights to use, copy, modify, merge,
43
+ publish, distribute, sublicense, and/or sell copies of the Software,
44
+ and to permit persons to whom the Software is furnished to do so,
45
+ subject to the following conditions:
46
+
47
+ The above copyright notice and this permission notice shall be
48
+ included in all copies or substantial portions of the Software.
49
+
50
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
51
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
52
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
53
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
54
+ BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
55
+ ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
56
+ CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
57
+ SOFTWARE.
58
+
data/install.rb ADDED
@@ -0,0 +1,18 @@
1
+
2
+ require 'rbconfig'
3
+ require 'fileutils'
4
+
5
+ include FileUtils
6
+
7
+ source = "lib/perforce.rb"
8
+ dest = File.join(Config::CONFIG["sitelibdir"], File.basename(source))
9
+
10
+ if ARGV.include? "--uninstall"
11
+ if File.exist? dest
12
+ puts "delete #{dest}"
13
+ rm_f dest
14
+ end
15
+ else
16
+ puts "#{source} --> #{dest}"
17
+ install source, dest
18
+ end
data/lib/perforce.rb ADDED
@@ -0,0 +1,396 @@
1
+
2
+ require 'thread'
3
+ require 'P4'
4
+
5
+ #
6
+ # Perforce -- class representing a connection to a perforce server.
7
+ #
8
+ class Perforce
9
+ CYGWIN = (RUBY_PLATFORM =~ %r!cygwin!) #:nodoc:
10
+
11
+ #
12
+ # Connect to a perforce depot.
13
+ #
14
+ # The argument <em>spec</em> is a hash of <em>(method, value)</em>
15
+ # pairs sent to the underlying P4 object.
16
+ #
17
+ # The given values override the P4PORT, P4CLIENT, etc. environment
18
+ # variables.
19
+ #
20
+ # Example:
21
+ #
22
+ # #
23
+ # # This calls P4#user=, P4#password=, P4#client=, P4#port= with
24
+ # # these values, then calls P4#connect.
25
+ # #
26
+ # Perforce.new(
27
+ # :user => "iggy_fenton",
28
+ # :password => "<password or ticket number>",
29
+ # :client => "iggy_fenton_project",
30
+ # :port => "server_name:1666")
31
+ #
32
+ def initialize(spec = {})
33
+ #
34
+ # Remove PWD during creation to avoid some troubles. The user
35
+ # probably wants perforce to use the absolute path of the current
36
+ # directory, not a path infected by symlinks.
37
+ #
38
+ # Since ~/project was a symlink to my perforce-based project,
39
+ # perforce would not run when I got there via "cd project" from my
40
+ # home directory.
41
+ #
42
+ @p4 =
43
+ if self.class.use_pwd_symlinks
44
+ P4.new
45
+ else
46
+ Thread.exclusive {
47
+ previous_pwd = ENV["PWD"]
48
+ ENV.delete("PWD")
49
+ begin
50
+ P4.new
51
+ ensure
52
+ if previous_pwd
53
+ ENV["PWD"] = previous_pwd
54
+ end
55
+ end
56
+ }
57
+ end
58
+ spec.each_pair { |key, value|
59
+ @p4.send("#{key}=", value)
60
+ }
61
+ unless spec.has_key?(:user)
62
+ # guess user
63
+ @p4.user = [
64
+ ENV["USER"],
65
+ ENV["USERNAME"],
66
+ ].select { |name|
67
+ name != nil and name != ""
68
+ }.first.tap { |user|
69
+ unless user
70
+ raise "Could not determine username"
71
+ end
72
+ }
73
+ end
74
+ @p4.exception_level = P4::RAISE_ERRORS
75
+ @p4.connect
76
+ end
77
+
78
+ #
79
+ # The underlying P4 object from the P4Ruby package.
80
+ #
81
+ def p4
82
+ @p4
83
+ end
84
+
85
+ #
86
+ # Create a Changelist with the given description (a string).
87
+ #
88
+ def new_changelist(desc)
89
+ input = {
90
+ "Change" => "new",
91
+ "Description" => desc,
92
+ }
93
+
94
+ number =
95
+ run_with_input(input, "change", "-i").
96
+ first.
97
+ match(%r!\AChange (\d+) created\.!).
98
+ captures.
99
+ first
100
+
101
+ Changelist.new(self, number)
102
+ end
103
+
104
+ #
105
+ # Revert these files.
106
+ #
107
+ def revert_files(files)
108
+ unless files.empty?
109
+ run("revert", *files)
110
+ end
111
+ end
112
+
113
+ #
114
+ # Return the pending changelists (as Changelist objects).
115
+ #
116
+ def pending_changelists
117
+ command = %W(changelists -u #{@p4.user} -c #{@p4.client} -s pending)
118
+ run(*command).map { |elem|
119
+ Changelist.new(self, elem["change"].to_i)
120
+ }
121
+ end
122
+
123
+ #
124
+ # Delete all empty changelists.
125
+ #
126
+ def delete_empty_changelists
127
+ pending_changelists.each { |changelist|
128
+ changelist.delete
129
+ }
130
+ end
131
+
132
+ #
133
+ # Calls <code>run("sync", *args)</code>
134
+ #
135
+ def sync(*args)
136
+ run("sync", *args)
137
+ end
138
+
139
+ #
140
+ # Edit and submit files in one step.
141
+ #
142
+ # Example:
143
+ # perforce.edit_and_submit("remove trailing whitespace", files) {
144
+ # #
145
+ # # Do stuff with the files.
146
+ # # ...
147
+ # #
148
+ # }
149
+ # #
150
+ # # Changes are submitted when the block ends.
151
+ # #
152
+ #
153
+ def edit_and_submit(changelist_description, files)
154
+ changelist = new_changelist(changelist_description)
155
+ changelist.add_files(files)
156
+ yield
157
+ changelist.submit
158
+ end
159
+
160
+ #
161
+ # Client root directory.
162
+ #
163
+ def root
164
+ dir = run(*%w(client -o)).first["Root"]
165
+ if CYGWIN
166
+ unix_dir = Util.unix_path(dir)
167
+ if dir != unix_dir
168
+ add_unix_root
169
+ end
170
+ unix_dir
171
+ else
172
+ dir
173
+ end
174
+ end
175
+
176
+ if CYGWIN
177
+ #
178
+ # <em>Cygwin-only.</em>
179
+ #
180
+ # Add a UNIX-style AltRoot.
181
+ #
182
+ # This allows P4Win and cygwin to work in the same client spec.
183
+ #
184
+ def add_unix_root #:nodoc:
185
+ client = run(*%w(client -o)).first
186
+ alt_roots = client["AltRoots"]
187
+ if alt_roots and alt_roots.include?(Util.unix_path(client["Root"]))
188
+ # has proper alt root
189
+ else
190
+ client["AltRoots"] =
191
+ alt_roots.to_a + [Util.unix_path(client["Root"])]
192
+ run_with_input(client, "client", "-i")
193
+ puts("Note: added unix AltRoot to client")
194
+ end
195
+ end
196
+ end
197
+
198
+ #
199
+ # Change working directory (locally and remotely).
200
+ #
201
+ def chdir(dir)
202
+ previous_dir = File.expand_path(".")
203
+ Dir.chdir(dir) {
204
+ @p4.cwd = File.expand_path(".")
205
+ yield
206
+ }
207
+ @p4.cwd = previous_dir
208
+ end
209
+
210
+ #
211
+ # Call P4#input(input) and then P4#run(*args)
212
+ #
213
+ def run_with_input(input, *args)
214
+ @p4.input = input
215
+ run(*args)
216
+ end
217
+
218
+ #
219
+ # Run a general p4 command.
220
+ #
221
+ # Example:
222
+ # puts "Your server version is: "
223
+ # puts perforce.run("info").first["serverVersion"]
224
+ #
225
+ def run(*args)
226
+ go = lambda {
227
+ @p4.run(*args).tap {
228
+ puts(@p4.warnings)
229
+ }
230
+ }
231
+ if CYGWIN
232
+ begin
233
+ go.call
234
+ rescue P4Exception
235
+ if @p4.connected?
236
+ # maybe unix root is not present; try again
237
+ add_unix_root
238
+ go.call
239
+ else
240
+ raise
241
+ end
242
+ end
243
+ else
244
+ # not CYGWIN
245
+ go.call
246
+ end
247
+ end
248
+
249
+ if CYGWIN
250
+ module Util #:nodoc:
251
+ def unix_path(dos_path) #:nodoc:
252
+ escaped_path = dos_path.sub(%r!\\+\Z!, "").gsub("\\", "\\\\\\\\")
253
+ `cygpath #{escaped_path}`.chomp
254
+ end
255
+ extend self
256
+ end
257
+ end
258
+
259
+ @use_pwd_symlinks = true
260
+ class << self
261
+ #
262
+ # Whether the current directory as reported to the perforce server
263
+ # can contain symlinks. Default is false.
264
+ #
265
+ attr_accessor :use_pwd_symlinks
266
+ end
267
+
268
+ #
269
+ # A Perforce changelist.
270
+ #
271
+ # Use Perforce#new_changelist to create a new changelist.
272
+ #
273
+ class Changelist
274
+ def initialize(perforce, number) #:nodoc:
275
+ @perforce = perforce
276
+ @number = number
277
+ end
278
+
279
+ #
280
+ # Changelist number.
281
+ #
282
+ attr_reader :number
283
+
284
+ #
285
+ # Add files to this Changelist.
286
+ #
287
+ # This is used for both editing files and adding new files.
288
+ #
289
+ def add_files(files)
290
+ unless files.empty?
291
+ @perforce.run("edit", "-c", @number, *files)
292
+ @perforce.run("add", "-c", @number, *files)
293
+ end
294
+ end
295
+
296
+ #
297
+ # Revert these files in this changelist.
298
+ #
299
+ def revert_files(files)
300
+ unless files.empty?
301
+ @perforce.run("revert", "-c", @number, *files)
302
+ end
303
+ end
304
+
305
+ #
306
+ # Open files for deletion. This action is added to the changelist.
307
+ #
308
+ def delete_files(files)
309
+ unless files.empty?
310
+ @perforce.run("delete", "-c", @number, *files)
311
+ end
312
+ end
313
+
314
+ #
315
+ # Submit this Changelist.
316
+ #
317
+ def submit
318
+ revert_unchanged_files
319
+ if empty?
320
+ delete
321
+ else
322
+ @perforce.run("submit", "-c", @number)
323
+ end
324
+ end
325
+
326
+ #
327
+ # True if there are no files in this Changelist.
328
+ #
329
+ def empty?
330
+ not @perforce.run("describe", @number).first.has_key?("depotFile")
331
+ end
332
+
333
+ #
334
+ # If empty, delete this Changelist.
335
+ #
336
+ def delete
337
+ if empty?
338
+ @perforce.run("change", "-d", @number)
339
+ end
340
+ end
341
+
342
+ #
343
+ # Info hash for this Changelist.
344
+ #
345
+ def info
346
+ @perforce.run("change", "-o", @number).first
347
+ end
348
+
349
+ #
350
+ # Files in this Changelist.
351
+ #
352
+ def files
353
+ info["Files"].to_a
354
+ end
355
+
356
+ #
357
+ # Description of this changelist
358
+ #
359
+ def description
360
+ info["Description"]
361
+ end
362
+
363
+ #
364
+ # Status of this changelist
365
+ #
366
+ def status
367
+ info["Status"]
368
+ end
369
+
370
+ #
371
+ # Revert unchanged files in this Changelist.
372
+ #
373
+ def revert_unchanged_files(in_files = nil)
374
+ files =
375
+ if in_files.nil?
376
+ self.files
377
+ else
378
+ in_files
379
+ end
380
+
381
+ unless files.empty?
382
+ @perforce.run("revert", "-a", "-c", @number, *files)
383
+ end
384
+ end
385
+ end
386
+ end
387
+
388
+ # version < 1.8.7 compatibility
389
+ unless respond_to? :tap
390
+ module Kernel #:nodoc:
391
+ def tap #:nodoc:
392
+ yield self
393
+ self
394
+ end
395
+ end
396
+ end
data/perforce.gemspec ADDED
@@ -0,0 +1,22 @@
1
+
2
+ Gem::Specification.new { |t|
3
+ t.name = "perforce"
4
+ t.version = "0.5.0"
5
+ t.summary = "Streamlined wrapper for p4ruby"
6
+ t.author = "James M. Lawrence"
7
+ t.email = "quixoticsycophant@gmail.com"
8
+ t.rubyforge_project = "perforce"
9
+ t.homepage = "perforce.rubyforge.org"
10
+ t.has_rdoc = true
11
+ t.extra_rdoc_files = ['README']
12
+ t.rdoc_options += %w{--title Perforce --main README}
13
+ t.add_dependency('p4ruby')
14
+ t.files = %w{
15
+ README
16
+ perforce.gemspec
17
+ install.rb
18
+ lib/perforce.rb
19
+ test/test_main.rb
20
+ }
21
+ }
22
+
data/test/test_main.rb ADDED
@@ -0,0 +1,84 @@
1
+
2
+ require 'rubygems'
3
+ require 'perforce'
4
+ require 'pp'
5
+
6
+ def report(desc)
7
+ if block_given?
8
+ puts "#{desc}:"
9
+ pp yield
10
+ else
11
+ puts "#{desc}."
12
+ end
13
+ puts
14
+ end
15
+
16
+ def with_dummy_file(file)
17
+ File.open(file, "w") { |out| out.puts "test" }
18
+ begin
19
+ yield file
20
+ ensure
21
+ File.unlink(file)
22
+ end
23
+ end
24
+
25
+ perforce = Perforce.new
26
+
27
+ report("Server info") {
28
+ perforce.run("info").first
29
+ }
30
+
31
+ root = perforce.root
32
+
33
+ report("Client root") {
34
+ root
35
+ }
36
+
37
+ report("Pending changelists") {
38
+ perforce.pending_changelists
39
+ }
40
+
41
+ perforce.chdir(root) {
42
+ changelist = perforce.new_changelist("**RUBY TEST**")
43
+
44
+ report("Created test changelist: ##{changelist.number}")
45
+
46
+ report("Pending changelists") {
47
+ perforce.pending_changelists
48
+ }
49
+
50
+ with_dummy_file("dummy-test-file-for-ruby-perforce.txt") { |file|
51
+ changelist.add_files([file])
52
+
53
+ report("Added a dummy file to the changelist")
54
+
55
+ report("Files in changelist") {
56
+ changelist.files
57
+ }
58
+
59
+ report("Description for this changelist") {
60
+ changelist.description
61
+ }
62
+
63
+ report("Status of this changelist") {
64
+ changelist.status
65
+ }
66
+
67
+ changelist.revert_files([file])
68
+
69
+ report("Reverted the dummy file")
70
+
71
+ report("Files in changelist") {
72
+ changelist.files
73
+ }
74
+
75
+ changelist.delete
76
+
77
+ report("Deleted changelist")
78
+
79
+ report("Pending changelists") {
80
+ perforce.pending_changelists
81
+ }
82
+ }
83
+ }
84
+
metadata ADDED
@@ -0,0 +1,69 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: perforce
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.5.0
5
+ platform: ruby
6
+ authors:
7
+ - James M. Lawrence
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+
12
+ date: 2008-09-07 00:00:00 -04:00
13
+ default_executable:
14
+ dependencies:
15
+ - !ruby/object:Gem::Dependency
16
+ name: p4ruby
17
+ type: :runtime
18
+ version_requirement:
19
+ version_requirements: !ruby/object:Gem::Requirement
20
+ requirements:
21
+ - - ">="
22
+ - !ruby/object:Gem::Version
23
+ version: "0"
24
+ version:
25
+ description:
26
+ email: quixoticsycophant@gmail.com
27
+ executables: []
28
+
29
+ extensions: []
30
+
31
+ extra_rdoc_files:
32
+ - README
33
+ files:
34
+ - README
35
+ - perforce.gemspec
36
+ - install.rb
37
+ - lib/perforce.rb
38
+ - test/test_main.rb
39
+ has_rdoc: true
40
+ homepage: perforce.rubyforge.org
41
+ post_install_message:
42
+ rdoc_options:
43
+ - --title
44
+ - Perforce
45
+ - --main
46
+ - README
47
+ require_paths:
48
+ - lib
49
+ required_ruby_version: !ruby/object:Gem::Requirement
50
+ requirements:
51
+ - - ">="
52
+ - !ruby/object:Gem::Version
53
+ version: "0"
54
+ version:
55
+ required_rubygems_version: !ruby/object:Gem::Requirement
56
+ requirements:
57
+ - - ">="
58
+ - !ruby/object:Gem::Version
59
+ version: "0"
60
+ version:
61
+ requirements: []
62
+
63
+ rubyforge_project: perforce
64
+ rubygems_version: 1.2.0
65
+ signing_key:
66
+ specification_version: 2
67
+ summary: Streamlined wrapper for p4ruby
68
+ test_files: []
69
+