perforce 0.5.0

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.
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
+