p4util 0.0.3 → 0.1.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.
- checksums.yaml +4 -4
- data/Gemfile +1 -1
- data/README.md +21 -10
- data/Rakefile +6 -1
- data/bin/p4util +1 -1
- data/lib/commands/download.rb +15 -2
- data/lib/commands/init/changelist_model.rb +101 -0
- data/lib/commands/init/file_definition.rb +16 -0
- data/lib/commands/init/init_model.rb +95 -0
- data/lib/commands/init/p4_helpers.rb +75 -0
- data/lib/commands/init/system_settings_model.rb +120 -0
- data/lib/commands/init/user_model.rb +84 -0
- data/lib/commands/init.rb +155 -0
- data/lib/commands/kill.rb +16 -4
- data/lib/commands/start.rb +10 -2
- data/lib/commands/util.rb +27 -0
- data/lib/commands.rb +1 -0
- data/lib/conventions.rb +4 -0
- data/lib/osutil.rb +12 -0
- data/lib/p4util/tasks.rb +94 -0
- data/lib/p4util/version.rb +3 -0
- data/lib/{p4_util.rb → p4util.rb} +1 -1
- data/lib/random_util.rb +7 -0
- data/p4init/287.jpg +0 -0
- data/p4init/test_changelists.rb +36 -0
- data/p4init/test_system_settings.rb +4 -0
- data/p4init/test_users.rb +15 -0
- data/p4util.gemspec +3 -2
- metadata +39 -11
- data/lib/p4_util/version.rb +0 -3
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 45a2fa04d57ee5b6aca04b509acc1705dffb1f35
|
4
|
+
data.tar.gz: 0561143880e7350548859aa309f2c499d3ac3d70
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 1c01e49961c05d2fe79b25c504e7748349e014407ef0975c6f1dc1434b24e02a344512d840758f68601fcb3dcd434769bcd2281eb36ed5e700de064a25039292
|
7
|
+
data.tar.gz: d4e3bc148311ae182387bfe3db5541d10e5a1dae0c26fe8d8bd12a432f8749d181adfcbfc5ac27b766b18b0d38b07645f15bfd0e22267eec613254c63d8f1be2
|
data/Gemfile
CHANGED
data/README.md
CHANGED
@@ -1,6 +1,20 @@
|
|
1
|
-
#
|
1
|
+
# p4util - common p4 scripting tasks for ruby projects
|
2
|
+
|
3
|
+
When building any kind of tool that uses Perforce underneath, it's pretty common
|
4
|
+
to want a few capabilities:
|
5
|
+
|
6
|
+
* Download different versions of p4, p4d, and the C++ API
|
7
|
+
* Initialize up a consistent starting environment of p4d
|
8
|
+
|
9
|
+
This is a binary script and set of rake tasks that allows this.
|
10
|
+
|
11
|
+
|
12
|
+
## Usage
|
13
|
+
|
14
|
+
After installation, `p4util help` will list available commands, and
|
15
|
+
`p4util help [command]` will list instructions. Some commands, like `p4util init`
|
16
|
+
are quite extensive, and should be used as the reference.
|
2
17
|
|
3
|
-
TODO: Write a gem description
|
4
18
|
|
5
19
|
## Installation
|
6
20
|
|
@@ -18,14 +32,11 @@ Or install it yourself as:
|
|
18
32
|
|
19
33
|
$ gem install p4util
|
20
34
|
|
21
|
-
## Usage
|
22
35
|
|
23
|
-
|
36
|
+
## Changes
|
24
37
|
|
25
|
-
|
38
|
+
* 0.1.0: Added `p4util init` command that can do some common p4d configurations,
|
39
|
+
like setting unicode and security settings, along with users and basic
|
40
|
+
adds and edits of files.
|
26
41
|
|
27
|
-
1.
|
28
|
-
2. Create your feature branch (`git checkout -b my-new-feature`)
|
29
|
-
3. Commit your changes (`git commit -am 'Add some feature'`)
|
30
|
-
4. Push to the branch (`git push origin my-new-feature`)
|
31
|
-
5. Create a new Pull Request
|
42
|
+
* 0.0.1-0.0.3: Basic download capabilities
|
data/Rakefile
CHANGED
data/bin/p4util
CHANGED
data/lib/commands/download.rb
CHANGED
@@ -5,6 +5,8 @@ require 'net/ftp'
|
|
5
5
|
|
6
6
|
module Commands
|
7
7
|
|
8
|
+
# TODO the p4ruby extconf.rb file mechanism has some logic to search the ftp
|
9
|
+
# site for things. We might also want to use HTTP
|
8
10
|
def Commands.download(options=nil)
|
9
11
|
version = 'r14.2'
|
10
12
|
binary = 'p4d'
|
@@ -25,6 +27,8 @@ module Commands
|
|
25
27
|
end
|
26
28
|
|
27
29
|
case binary
|
30
|
+
when 'p4'
|
31
|
+
download_p4_via_ftp(version)
|
28
32
|
when 'p4d'
|
29
33
|
download_p4d_via_ftp(version)
|
30
34
|
when 'p4api'
|
@@ -36,15 +40,16 @@ module Commands
|
|
36
40
|
|
37
41
|
def Commands.print_download_help
|
38
42
|
puts <<-END.gsub(/^ {6}/, '')
|
39
|
-
p4util download [p4d|p4api]
|
43
|
+
p4util download [p4|p4d|p4api]
|
40
44
|
|
41
45
|
Downloads one of the following utilities (in lieu of an installer) into
|
42
46
|
a local work/ directory.
|
43
47
|
|
48
|
+
* p4
|
44
49
|
* p4d
|
45
50
|
* p4api
|
46
51
|
|
47
|
-
Will default to the
|
52
|
+
Will default to the r14.2 release.
|
48
53
|
|
49
54
|
Options:
|
50
55
|
|
@@ -55,6 +60,14 @@ module Commands
|
|
55
60
|
|
56
61
|
private
|
57
62
|
|
63
|
+
def Commands.download_p4_via_ftp(version)
|
64
|
+
download_via_ftp(version, OsUtil.p4_executable, OsUtil.p4_path)
|
65
|
+
|
66
|
+
if !File.executable?(OsUtil.p4_path)
|
67
|
+
File.chmod(0755, OsUtil.p4_path)
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
58
71
|
def Commands.download_p4d_via_ftp(version)
|
59
72
|
download_via_ftp(version, OsUtil.p4d_executable, OsUtil.p4d_path)
|
60
73
|
|
@@ -0,0 +1,101 @@
|
|
1
|
+
require 'commands/init/init_model'
|
2
|
+
require 'commands/init/p4_helpers'
|
3
|
+
require 'fileutils'
|
4
|
+
|
5
|
+
module Commands
|
6
|
+
module Init
|
7
|
+
|
8
|
+
class ChangelistModel < InitModel
|
9
|
+
include Commands::Init::P4Helpers
|
10
|
+
|
11
|
+
inheritable_attributes :description, :adds, :edits, :user
|
12
|
+
|
13
|
+
# Please make this descriptive
|
14
|
+
@description = 'Init changelist'
|
15
|
+
|
16
|
+
# An array of 'file definitions'
|
17
|
+
@adds = []
|
18
|
+
|
19
|
+
# An array of 'file definitions' expected to already exist
|
20
|
+
@edits = []
|
21
|
+
|
22
|
+
# The user we should operate as if you don't want to do this as the super
|
23
|
+
# user
|
24
|
+
@user = nil
|
25
|
+
|
26
|
+
#========================================================================
|
27
|
+
# Internal implementation
|
28
|
+
#========================================================================
|
29
|
+
|
30
|
+
def self.abstract
|
31
|
+
true
|
32
|
+
end
|
33
|
+
|
34
|
+
attr_accessor :description, :adds, :edits, :user
|
35
|
+
|
36
|
+
def initialize
|
37
|
+
@description = self.class.description
|
38
|
+
@adds = self.class.adds
|
39
|
+
@edits = self.class.edits
|
40
|
+
@user = self.class.user
|
41
|
+
end
|
42
|
+
|
43
|
+
def execute(p4, models, super_user)
|
44
|
+
# Set up our options with a user context if specified by searching the
|
45
|
+
# defined models.
|
46
|
+
options = {:p4 => p4}
|
47
|
+
if user
|
48
|
+
model = models.find {|m| m.class < UserModel && m.login == user }
|
49
|
+
options[:user] = model.login
|
50
|
+
options[:password] = model.password
|
51
|
+
options[:olduser] = super_user.login
|
52
|
+
options[:oldpass] = super_user.password
|
53
|
+
end
|
54
|
+
|
55
|
+
# Define the logic of what the changelist model really does: make adds
|
56
|
+
# and edits for the most part
|
57
|
+
open_client(options) do |client_path, name|
|
58
|
+
|
59
|
+
change_spec = p4.fetch_change
|
60
|
+
change_spec._description = description
|
61
|
+
results = p4.save_change(change_spec)
|
62
|
+
change_id = results[0].gsub(/^Change (\d+) created./, '\1')
|
63
|
+
|
64
|
+
@adds.each do |add|
|
65
|
+
add_path = File.join(client_path, add.path)
|
66
|
+
dir = File.dirname(add_path)
|
67
|
+
if dir && !dir.empty?
|
68
|
+
if !Dir.exist?(dir)
|
69
|
+
FileUtils.mkpath(dir)
|
70
|
+
end
|
71
|
+
end
|
72
|
+
if add.content
|
73
|
+
IO.write(add_path, add.content)
|
74
|
+
elsif add.local_path
|
75
|
+
FileUtils.copy(add.local_path, add_path)
|
76
|
+
end
|
77
|
+
p4.run_add('-c', change_id, add_path)
|
78
|
+
end
|
79
|
+
|
80
|
+
@edits.each do |edit|
|
81
|
+
edit_path = File.join(client_path, edit.path)
|
82
|
+
p4.run_edit('-c', change_id, edit_path)
|
83
|
+
|
84
|
+
if edit.content
|
85
|
+
IO.write(edit_path, edit.content)
|
86
|
+
else
|
87
|
+
FileUtils.copy(edit.local_path, edit_path)
|
88
|
+
end
|
89
|
+
end
|
90
|
+
|
91
|
+
p4.run_submit('-c', change_id)
|
92
|
+
end
|
93
|
+
end
|
94
|
+
|
95
|
+
private
|
96
|
+
|
97
|
+
# do stuff
|
98
|
+
end
|
99
|
+
|
100
|
+
end
|
101
|
+
end
|
@@ -0,0 +1,16 @@
|
|
1
|
+
|
2
|
+
module Commands
|
3
|
+
module Init
|
4
|
+
|
5
|
+
class FileDefinition
|
6
|
+
attr_accessor :path, :content, :local_path
|
7
|
+
|
8
|
+
# Options should indicate :content or :local_path, but not both
|
9
|
+
def initialize(options)
|
10
|
+
@path = options[:path]
|
11
|
+
@content = options[:content] if options.key?(:content)
|
12
|
+
@local_path = options[:local_path] if options.key?(:local_path)
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
@@ -0,0 +1,95 @@
|
|
1
|
+
require 'P4'
|
2
|
+
|
3
|
+
module Commands
|
4
|
+
module Init
|
5
|
+
# Base class of 'initializers'.
|
6
|
+
#
|
7
|
+
# This actually acts more as a registry of defined classes.
|
8
|
+
class InitModel
|
9
|
+
# Child classes should define this number greater than one. 0 should be
|
10
|
+
# reserved for a single system settings instance.
|
11
|
+
def rank
|
12
|
+
1
|
13
|
+
end
|
14
|
+
|
15
|
+
# It's likely that the main child class will handle overriding this method.
|
16
|
+
# Our default method does nothing.
|
17
|
+
def execute(p4)
|
18
|
+
end
|
19
|
+
|
20
|
+
#========================================================================
|
21
|
+
# Ok, the real business starts here.
|
22
|
+
#
|
23
|
+
# Don't even think about overriding the following rules
|
24
|
+
#========================================================================
|
25
|
+
|
26
|
+
class << self
|
27
|
+
attr_accessor :model_classes
|
28
|
+
end
|
29
|
+
|
30
|
+
# The registered set of non-abstract model classes.
|
31
|
+
#
|
32
|
+
@model_classes = []
|
33
|
+
|
34
|
+
@inheritable_attributes ||= [:inheritable_attributes]
|
35
|
+
|
36
|
+
# A lot of our settings are actually class instance variables, which
|
37
|
+
# can be overridden. This allows the child classes that depend on these
|
38
|
+
# values to define what they are.
|
39
|
+
def self.inheritable_attributes(*args)
|
40
|
+
@inheritable_attributes += args
|
41
|
+
args.each do |arg|
|
42
|
+
class_eval %(
|
43
|
+
class << self; attr_accessor :#{arg} end
|
44
|
+
)
|
45
|
+
end
|
46
|
+
@inheritable_attributes
|
47
|
+
end
|
48
|
+
|
49
|
+
def self.inherited(subclass)
|
50
|
+
# Copy 'down' any inheritable attribute to the new child class
|
51
|
+
@inheritable_attributes.each do |inheritable_attribute|
|
52
|
+
instance_var = "@#{inheritable_attribute}"
|
53
|
+
subclass.instance_variable_set(instance_var, instance_variable_get(instance_var))
|
54
|
+
end
|
55
|
+
|
56
|
+
if subclass.respond_to?(:abstract) && subclass.send(:abstract)
|
57
|
+
InitModel.model_classes.push(subclass)
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
def self.run(p4port)
|
62
|
+
system_settings = nil
|
63
|
+
super_user = nil
|
64
|
+
models = []
|
65
|
+
|
66
|
+
InitModel.model_classes.each do |model|
|
67
|
+
if model <= DefaultSuperUser
|
68
|
+
# do nothing
|
69
|
+
elsif model <= SystemSettingsModel
|
70
|
+
system_settings = model.new
|
71
|
+
elsif !super_user and model <= UserModel and model.super
|
72
|
+
super_user = model.new
|
73
|
+
else
|
74
|
+
models << model.new
|
75
|
+
end
|
76
|
+
end
|
77
|
+
|
78
|
+
if !super_user
|
79
|
+
super_user = DefaultSuperUser.new
|
80
|
+
end
|
81
|
+
|
82
|
+
models.sort! { |a, b| a.rank <=> b.rank }
|
83
|
+
|
84
|
+
p4 = P4.new
|
85
|
+
p4.port = p4port
|
86
|
+
p4.connect
|
87
|
+
p4.exception_level = P4::RAISE_ERRORS
|
88
|
+
|
89
|
+
system_settings.execute(p4, super_user)
|
90
|
+
|
91
|
+
models.each { |m| m.execute(p4, models, super_user) }
|
92
|
+
end
|
93
|
+
end
|
94
|
+
end
|
95
|
+
end
|
@@ -0,0 +1,75 @@
|
|
1
|
+
|
2
|
+
require 'conventions'
|
3
|
+
require 'random_util'
|
4
|
+
|
5
|
+
module Commands
|
6
|
+
module Init
|
7
|
+
module P4Helpers
|
8
|
+
|
9
|
+
# Intended to be a block helper that creates a 'temporary client'
|
10
|
+
#
|
11
|
+
# Your block should take the client name and path.
|
12
|
+
#
|
13
|
+
# Example:
|
14
|
+
#
|
15
|
+
# open_client(p4) do |path, name|
|
16
|
+
# puts "my client #{name}'s root is #{path}"
|
17
|
+
# end
|
18
|
+
def open_client(options)
|
19
|
+
p4 = options[:p4]
|
20
|
+
|
21
|
+
# Switch user only if specified
|
22
|
+
user = nil
|
23
|
+
password = nil
|
24
|
+
olduser = nil
|
25
|
+
oldpass = nil
|
26
|
+
if (options[:user])
|
27
|
+
user = options[:user]
|
28
|
+
password = options[:password] if options.key?(:password)
|
29
|
+
olduser = options[:olduser]
|
30
|
+
oldpass = options[:oldpass] if options.key?(:oldpass)
|
31
|
+
p4.user = user
|
32
|
+
p4.password = password
|
33
|
+
p4.run_login if password
|
34
|
+
end
|
35
|
+
|
36
|
+
name = RandomUtil.randstr
|
37
|
+
dir = File.join(Conventions.client_root_dir, name)
|
38
|
+
|
39
|
+
if !Dir.exist?(dir)
|
40
|
+
FileUtils.mkpath(dir)
|
41
|
+
end
|
42
|
+
|
43
|
+
spec = p4.fetch_client
|
44
|
+
spec._root = dir
|
45
|
+
spec._client = name
|
46
|
+
spec._description = 'p4util init temp client'
|
47
|
+
spec._view = ["//depot/... //#{name}/depot/..."]
|
48
|
+
|
49
|
+
p4.save_client(spec)
|
50
|
+
|
51
|
+
p4.client = name
|
52
|
+
|
53
|
+
p4.run_sync('-f', '//...')
|
54
|
+
|
55
|
+
if block_given?
|
56
|
+
yield dir, name
|
57
|
+
else
|
58
|
+
return dir, name
|
59
|
+
end
|
60
|
+
ensure
|
61
|
+
if block_given?
|
62
|
+
if (user)
|
63
|
+
p4.user = olduser
|
64
|
+
p4.password = oldpass
|
65
|
+
p4.run_login if oldpass
|
66
|
+
end
|
67
|
+
|
68
|
+
p4.run_client('-d', '-f', name)
|
69
|
+
p4.client = 'invalid'
|
70
|
+
FileUtils.rmtree(dir)
|
71
|
+
end
|
72
|
+
end
|
73
|
+
end
|
74
|
+
end
|
75
|
+
end
|
@@ -0,0 +1,120 @@
|
|
1
|
+
|
2
|
+
require 'commands'
|
3
|
+
require 'commands/init/init_model'
|
4
|
+
|
5
|
+
module Commands
|
6
|
+
module Init
|
7
|
+
|
8
|
+
class SystemSettingsModel < InitModel
|
9
|
+
inheritable_attributes :unicode, :security_level, :configure_settings
|
10
|
+
|
11
|
+
# Pick some normal defaults
|
12
|
+
|
13
|
+
# This basically will upgrade the DB before anything happens to unicode
|
14
|
+
# mode. The default is false, though you probably should use true for
|
15
|
+
# most default 'setups' these days.
|
16
|
+
@unicode = false
|
17
|
+
|
18
|
+
# The security level is set basically as the first major setting, before
|
19
|
+
# most users are generated, to avoid password mayhem
|
20
|
+
@security_level = 0
|
21
|
+
|
22
|
+
# These basically map to 'p4 configure set #{key}=#{value}'
|
23
|
+
@configure_settings = {}
|
24
|
+
|
25
|
+
#========================================================================
|
26
|
+
# Internal implementation - don't mess with this
|
27
|
+
#========================================================================
|
28
|
+
|
29
|
+
def rank
|
30
|
+
0
|
31
|
+
end
|
32
|
+
|
33
|
+
def self.abstract
|
34
|
+
true
|
35
|
+
end
|
36
|
+
|
37
|
+
attr_reader :unicode, :security_level, :configure_settings
|
38
|
+
|
39
|
+
def initialize
|
40
|
+
@unicode = self.class.unicode
|
41
|
+
@security_level = self.class.security_level
|
42
|
+
@configure_settings = self.class.configure_settings
|
43
|
+
end
|
44
|
+
|
45
|
+
# If the security level is > 0, we generate the super_user first, change
|
46
|
+
# security, then reset the super's password. The p4 connection is
|
47
|
+
# established here with whatever credentials we end up with the super
|
48
|
+
# user as.
|
49
|
+
def execute(p4, super_user)
|
50
|
+
set_unicode_mode(p4)
|
51
|
+
set_security_and_super_user(p4, super_user)
|
52
|
+
create_default_protections(p4)
|
53
|
+
end
|
54
|
+
|
55
|
+
private
|
56
|
+
|
57
|
+
def set_unicode_mode(p4)
|
58
|
+
if unicode
|
59
|
+
p4.disconnect
|
60
|
+
|
61
|
+
# Halt any exiting perforce server
|
62
|
+
Commands.kill
|
63
|
+
|
64
|
+
# Execute the upgrade of the p4d instance
|
65
|
+
Commands.unicode_upgrade
|
66
|
+
|
67
|
+
# Restart: this assumes the cwd of the init call is the same as the
|
68
|
+
# 'start' call. This is a pretty safe assumption, since our .init
|
69
|
+
# function will call start if it's not running.
|
70
|
+
Commands.start
|
71
|
+
|
72
|
+
while !Commands.p4d_running?
|
73
|
+
sleep 0.2
|
74
|
+
end
|
75
|
+
|
76
|
+
# I'm not sure this should be anything else for the purposes of
|
77
|
+
# initialization
|
78
|
+
p4.charset = 'auto'
|
79
|
+
|
80
|
+
p4.connect
|
81
|
+
end
|
82
|
+
end
|
83
|
+
|
84
|
+
def set_security_and_super_user(p4, super_user)
|
85
|
+
# update security mode if needed
|
86
|
+
if security_level > 0
|
87
|
+
# On some security levels, you *must* reset passwords when updating
|
88
|
+
# even if the base password is strong.
|
89
|
+
super_pwd = super_user.password
|
90
|
+
super_user.password = ''
|
91
|
+
super_user.execute(p4)
|
92
|
+
|
93
|
+
p4.run('configure', 'set', "security=#{security_level}")
|
94
|
+
|
95
|
+
p4.user = super_user.login
|
96
|
+
p4.password = super_user.password
|
97
|
+
|
98
|
+
p4.run_password(super_user.password, super_pwd)
|
99
|
+
super_user.password = super_pwd
|
100
|
+
|
101
|
+
p4.password = super_user.password
|
102
|
+
p4.run_login
|
103
|
+
else
|
104
|
+
# Just create the user
|
105
|
+
super_user.execute(p4)
|
106
|
+
if super_user.password
|
107
|
+
p4.password = super_user.password
|
108
|
+
p4.run_login
|
109
|
+
end
|
110
|
+
end
|
111
|
+
end
|
112
|
+
|
113
|
+
def create_default_protections(p4)
|
114
|
+
# This actually will establish the defaults with our super user as the
|
115
|
+
# only entry
|
116
|
+
results = p4.run_protect('-o')
|
117
|
+
end
|
118
|
+
end
|
119
|
+
end
|
120
|
+
end
|
@@ -0,0 +1,84 @@
|
|
1
|
+
require 'commands/init/init_model'
|
2
|
+
|
3
|
+
module Commands
|
4
|
+
module Init
|
5
|
+
|
6
|
+
class UserModel < InitModel
|
7
|
+
inheritable_attributes :login, :full_name, :password, :email, :super
|
8
|
+
|
9
|
+
@login = nil
|
10
|
+
@full_name = nil
|
11
|
+
@password = nil
|
12
|
+
@email = nil
|
13
|
+
# Set this to true if you want the user to be our 'super user'. We'll
|
14
|
+
# generally need one of these for each init step. We will make sure a
|
15
|
+
# protections entry for this user exists
|
16
|
+
@super = false
|
17
|
+
|
18
|
+
#========================================================================
|
19
|
+
# Internal implementation - don't mess with this
|
20
|
+
#========================================================================
|
21
|
+
|
22
|
+
def self.abstract
|
23
|
+
true
|
24
|
+
end
|
25
|
+
|
26
|
+
attr_accessor :login, :password
|
27
|
+
|
28
|
+
def initialize
|
29
|
+
@login = self.class.login
|
30
|
+
@full_name = self.class.full_name
|
31
|
+
@password = self.class.password
|
32
|
+
@email = self.class.email
|
33
|
+
@super = self.class.super
|
34
|
+
end
|
35
|
+
|
36
|
+
def email
|
37
|
+
@email || "#{login}@example.com"
|
38
|
+
end
|
39
|
+
|
40
|
+
def full_name
|
41
|
+
@full_name || login
|
42
|
+
end
|
43
|
+
|
44
|
+
def super?
|
45
|
+
@super
|
46
|
+
end
|
47
|
+
|
48
|
+
def to_s
|
49
|
+
"UserModel: login=#{login} email=#{email} full_name=#{full_name} password=#{password}"
|
50
|
+
end
|
51
|
+
|
52
|
+
def to_spec
|
53
|
+
spec = {
|
54
|
+
'User' => login,
|
55
|
+
'Email' => email,
|
56
|
+
'FullName' => full_name
|
57
|
+
}
|
58
|
+
spec
|
59
|
+
end
|
60
|
+
|
61
|
+
def execute(p4, models=nil, super_user=nil)
|
62
|
+
p4.save_user(to_spec, '-f')
|
63
|
+
|
64
|
+
p4.user = login
|
65
|
+
p4.password = ''
|
66
|
+
|
67
|
+
p4.run_password('', password) if password
|
68
|
+
|
69
|
+
if super_user
|
70
|
+
p4.user = super_user.login
|
71
|
+
p4.password = super_user.password
|
72
|
+
end
|
73
|
+
end
|
74
|
+
end
|
75
|
+
|
76
|
+
class DefaultSuperUser < UserModel
|
77
|
+
@login = 'p4super'
|
78
|
+
@password = 'superuser1A!'
|
79
|
+
@full_name = 'Super User'
|
80
|
+
@super = true
|
81
|
+
end
|
82
|
+
|
83
|
+
end
|
84
|
+
end
|
@@ -0,0 +1,155 @@
|
|
1
|
+
|
2
|
+
require 'commands/start'
|
3
|
+
require 'commands/init/init_model'
|
4
|
+
require 'commands/init/changelist_model'
|
5
|
+
require 'commands/init/file_definition'
|
6
|
+
require 'commands/init/system_settings_model'
|
7
|
+
require 'commands/init/user_model'
|
8
|
+
|
9
|
+
module Commands
|
10
|
+
|
11
|
+
def Commands.init(options=nil)
|
12
|
+
init_dir = 'p4init'
|
13
|
+
# TODO we may want to allow this to be overridden, though I find this a
|
14
|
+
# spurious and dangerous use case
|
15
|
+
p4port = '127.0.0.1:1666'
|
16
|
+
|
17
|
+
if options and !options.params.empty?
|
18
|
+
init_dir = options.params.shift
|
19
|
+
end
|
20
|
+
|
21
|
+
if !p4d_running?
|
22
|
+
# The way this works: if you specify any options, like what to download
|
23
|
+
# you must specify the initialization directory.
|
24
|
+
Commands.start(options)
|
25
|
+
end
|
26
|
+
|
27
|
+
initialize_p4d(init_dir, p4port)
|
28
|
+
end
|
29
|
+
|
30
|
+
def Commands.print_init_help
|
31
|
+
puts <<-END.gsub(/^ {6}/, '')
|
32
|
+
p4util init [p4_init_dir]
|
33
|
+
|
34
|
+
Reads definitions in the directory p4_init_dir - by default, just
|
35
|
+
'p4init' - and then calls methods on a p4d instance assumed to be running
|
36
|
+
locally.
|
37
|
+
|
38
|
+
This assumes we are basically starting from scratch. If you have an
|
39
|
+
existing p4d instance, it's highly likely your initialization run will
|
40
|
+
fail. Delete your settings and working area and reset.
|
41
|
+
|
42
|
+
## Definition Files ##
|
43
|
+
|
44
|
+
Files are Ruby scripts, each basically extending a model class defined
|
45
|
+
by this application.
|
46
|
+
|
47
|
+
Each model type is defined separately later, however, you should have
|
48
|
+
at least one User model that's marked as a super user. If you don't do
|
49
|
+
this, you will always have a super user created with the login 'p4super'
|
50
|
+
and the password 'superuser1A!'.
|
51
|
+
|
52
|
+
## Execution Order ##
|
53
|
+
|
54
|
+
Each model you define has a 'rank'. Models classes generate instances, and
|
55
|
+
each instance is sorted based on this rank. If you specify no rank, or
|
56
|
+
any rank is equivalent, well, you submit your will to the gods of random.
|
57
|
+
|
58
|
+
The only special model type that does not obey these rules is the
|
59
|
+
SystemSettings model, which is always handled in a very particular order.
|
60
|
+
|
61
|
+
|
62
|
+
## SystemSettingsModel ##
|
63
|
+
|
64
|
+
Example:
|
65
|
+
|
66
|
+
class MySystemSettings < SystemSettingsModel
|
67
|
+
# These are default settings
|
68
|
+
@unicode = true
|
69
|
+
@security_level = 0
|
70
|
+
|
71
|
+
# By default this is empty, but here's an example of usage
|
72
|
+
@configure_settings = {
|
73
|
+
'dm.keys.hide' => '2'
|
74
|
+
}
|
75
|
+
end
|
76
|
+
|
77
|
+
When `unicode` is enabled, this assumes that the `p4util init` command
|
78
|
+
is run in the *same directory* as `p4util start`.
|
79
|
+
|
80
|
+
|
81
|
+
## UserModel ##
|
82
|
+
|
83
|
+
Example of a super user (you need one):
|
84
|
+
|
85
|
+
class SuperUser < UserModel
|
86
|
+
@super = true
|
87
|
+
# if you don't set, we'll just use this for the full_name and email
|
88
|
+
@login = 'super'
|
89
|
+
@password = 'superuser1A'
|
90
|
+
end
|
91
|
+
|
92
|
+
Example of a normal user:
|
93
|
+
|
94
|
+
class JohnDoeUser < UserModel
|
95
|
+
def rank; 100; end
|
96
|
+
@login = 'jdoe'
|
97
|
+
@full_name = 'John Doe'
|
98
|
+
@email = 'jdoe@example.com'
|
99
|
+
@password = 'johndoe1A!'
|
100
|
+
end
|
101
|
+
|
102
|
+
Note that with the super user, you don't really need a rank, but with
|
103
|
+
your other models, it's a good idea. (You can mix when users come and go
|
104
|
+
with other changes.)
|
105
|
+
|
106
|
+
|
107
|
+
## ChangelistModel ##
|
108
|
+
|
109
|
+
Example of a changelist with an add and edit:
|
110
|
+
|
111
|
+
class Changelist2 < ChangelistModel
|
112
|
+
def rank; 1001 end
|
113
|
+
@description = 'An example add and edit'
|
114
|
+
@user = 'jdoe'
|
115
|
+
@edits = [
|
116
|
+
FileDefinition.new(:path => 'depot/README.txt',
|
117
|
+
:content => <<-STOP.gsub(/^ {8}/, '')
|
118
|
+
This is an example readme.
|
119
|
+
Added a second line
|
120
|
+
STOP
|
121
|
+
)
|
122
|
+
]
|
123
|
+
@adds = [
|
124
|
+
FileDefinition.new(:path => 'depot/main/project2/example.txt',
|
125
|
+
:local_path => 'p4init/some_text.txt')
|
126
|
+
]
|
127
|
+
end
|
128
|
+
|
129
|
+
Note that adds an edits are specified with 'FileDefinition' objects. Each
|
130
|
+
file definition instance can define text content inline, or via a
|
131
|
+
'local_path' to a file relative to the current working directory.
|
132
|
+
|
133
|
+
The `@user` is not necessary, but you probably don't want to add everything
|
134
|
+
as your super user, so set this to a UserModel instance that should exist
|
135
|
+
at this point.
|
136
|
+
|
137
|
+
END
|
138
|
+
end
|
139
|
+
|
140
|
+
private
|
141
|
+
|
142
|
+
def Commands.initialize_p4d(init_dir, p4port)
|
143
|
+
# Go through our init_dir, and evaluate each script as if it were defined
|
144
|
+
# in the Commands::Init module.
|
145
|
+
Dir.glob("#{init_dir}/**/*.rb") do |file|
|
146
|
+
contents = IO.read(file)
|
147
|
+
Commands::Init.class_eval(contents, file)
|
148
|
+
end
|
149
|
+
|
150
|
+
# Note that nothing is actually done until this line. This allows classes
|
151
|
+
# to re-define methods and do fancy shit, like, 'oh in security_settings 0
|
152
|
+
# this guy actually doesn't have a password'.
|
153
|
+
Commands::Init::InitModel.run(p4port)
|
154
|
+
end
|
155
|
+
end
|
data/lib/commands/kill.rb
CHANGED
@@ -1,3 +1,4 @@
|
|
1
|
+
require 'commands/util'
|
1
2
|
require 'sys/proctable'
|
2
3
|
|
3
4
|
include Sys
|
@@ -5,8 +6,21 @@ include Sys
|
|
5
6
|
module Commands
|
6
7
|
|
7
8
|
def Commands.kill(options=nil)
|
8
|
-
ProcTable.ps().find_all{|p| p.comm =~ /p4d/}
|
9
|
-
|
9
|
+
ProcTable.ps().find_all{|p| p.comm =~ /p4d/}.each do |p|
|
10
|
+
begin
|
11
|
+
Process.kill('TERM', p.pid)
|
12
|
+
rescue
|
13
|
+
puts "Problem killing #{p}, ignoring"
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
is_running = true
|
18
|
+
while is_running
|
19
|
+
is_running = p4d_running?
|
20
|
+
if is_running
|
21
|
+
sleep 0.2
|
22
|
+
end
|
23
|
+
end
|
10
24
|
end
|
11
25
|
|
12
26
|
def Commands.print_kill_help
|
@@ -15,8 +29,6 @@ module Commands
|
|
15
29
|
|
16
30
|
Finds local p4d processes and kills them.
|
17
31
|
|
18
|
-
There should be a timeout that will force kill anything that appears stuck.
|
19
|
-
|
20
32
|
On unix machines, will probably use `ps -x` and 'p4d', then will send
|
21
33
|
SIGTERM signals to each process.
|
22
34
|
END
|
data/lib/commands/start.rb
CHANGED
@@ -1,9 +1,9 @@
|
|
1
1
|
|
2
2
|
module Commands
|
3
3
|
|
4
|
-
def Commands.start(options)
|
4
|
+
def Commands.start(options=nil)
|
5
5
|
if !File.exists?(OsUtil.p4d_path)
|
6
|
-
Commands.download
|
6
|
+
Commands.download(options)
|
7
7
|
end
|
8
8
|
Conventions.init_p4droot_dir
|
9
9
|
spawn_p4d
|
@@ -13,6 +13,14 @@ module Commands
|
|
13
13
|
pid = Process.spawn("#{OsUtil.p4d_path} -r #{Conventions.p4droot_dir} "+
|
14
14
|
"-v server=1 -L #{Conventions.p4d_log_path}")
|
15
15
|
Process.detach(pid)
|
16
|
+
|
17
|
+
while !p4d_running?
|
18
|
+
sleep 0.2
|
19
|
+
end
|
20
|
+
|
21
|
+
while !p4d_available?
|
22
|
+
sleep 0.1
|
23
|
+
end
|
16
24
|
end
|
17
25
|
|
18
26
|
def Commands.print_start_help
|
@@ -0,0 +1,27 @@
|
|
1
|
+
require 'sys/proctable'
|
2
|
+
require 'P4'
|
3
|
+
|
4
|
+
module Commands
|
5
|
+
def Commands.p4d_running?
|
6
|
+
!ProcTable.ps().find_all { |p| p.comm =~ /p4d/ }.empty?
|
7
|
+
end
|
8
|
+
|
9
|
+
def Commands.p4d_available?(port=':1666')
|
10
|
+
begin
|
11
|
+
p4 = P4.new
|
12
|
+
p4.port = port
|
13
|
+
p4.connect
|
14
|
+
p4.disconnect
|
15
|
+
true
|
16
|
+
rescue
|
17
|
+
false
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
def Commands.unicode_upgrade
|
22
|
+
system("#{OsUtil.p4d_path} -r #{Conventions.p4droot_dir} "+
|
23
|
+
"-v server=1 -L #{Conventions.p4d_log_path} " +
|
24
|
+
"-xi")
|
25
|
+
|
26
|
+
end
|
27
|
+
end
|
data/lib/commands.rb
CHANGED
data/lib/conventions.rb
CHANGED
@@ -17,6 +17,10 @@ module Conventions
|
|
17
17
|
File.expand_path(File.join(working_dir,'p4droot'))
|
18
18
|
end
|
19
19
|
|
20
|
+
def Conventions.client_root_dir
|
21
|
+
File.expand_path(File.join(working_dir,'clients'))
|
22
|
+
end
|
23
|
+
|
20
24
|
def Conventions.init_p4droot_dir
|
21
25
|
if !File.directory?(p4droot_dir)
|
22
26
|
FileUtils::makedirs(p4droot_dir)
|
data/lib/osutil.rb
CHANGED
@@ -15,6 +15,18 @@ module OsUtil
|
|
15
15
|
File.expand_path(File.join(Conventions.working_dir, OsUtil.p4d_executable))
|
16
16
|
end
|
17
17
|
|
18
|
+
def OsUtil.p4_executable
|
19
|
+
if windows?
|
20
|
+
'p4.exe'
|
21
|
+
else
|
22
|
+
'p4'
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
def OsUtil.p4_path
|
27
|
+
File.expand_path(File.join(Conventions.working_dir, OsUtil.p4_executable))
|
28
|
+
end
|
29
|
+
|
18
30
|
def OsUtil.osx?
|
19
31
|
RbConfig::CONFIG['host_os'] =~ /darwin/
|
20
32
|
end
|
data/lib/p4util/tasks.rb
ADDED
@@ -0,0 +1,94 @@
|
|
1
|
+
# p4util/task.rb - Rake tasks
|
2
|
+
|
3
|
+
require 'commands'
|
4
|
+
require 'conventions'
|
5
|
+
require 'fileutils'
|
6
|
+
require 'ostruct'
|
7
|
+
require 'rake/tasklib'
|
8
|
+
|
9
|
+
module P4Util
|
10
|
+
# Creates a few tasks to allow launching init and kill commands via rake
|
11
|
+
# tasks, which should make it easy to script with test tasks, for example.
|
12
|
+
#
|
13
|
+
# Example:
|
14
|
+
#
|
15
|
+
# require 'p4util/tasks'
|
16
|
+
#
|
17
|
+
# P4Util::Tasks.new do |p4util|
|
18
|
+
# p4util.version = 'r14.2' # Indicate p4d version to download
|
19
|
+
# end
|
20
|
+
#
|
21
|
+
# Tasks:
|
22
|
+
# - p4init
|
23
|
+
# - p4kill
|
24
|
+
# - p4reset
|
25
|
+
class Tasks < Rake::TaskLib
|
26
|
+
# The task base name, defaults to ':p4'
|
27
|
+
attr_accessor :basename
|
28
|
+
|
29
|
+
# P4 Version to use, defaults to 'r14.2'
|
30
|
+
attr_accessor :version
|
31
|
+
|
32
|
+
# The directory containing p4 init scripts, defaults to 'p4init'
|
33
|
+
attr_accessor :p4_init_dir
|
34
|
+
|
35
|
+
def initialize basename = :p4
|
36
|
+
@basename = basename
|
37
|
+
@version = 'r14.2'
|
38
|
+
@p4_init_dir = 'p4init'
|
39
|
+
|
40
|
+
yield self if block_given?
|
41
|
+
|
42
|
+
define_tasks
|
43
|
+
end
|
44
|
+
|
45
|
+
# Create the tasks defined by this task library
|
46
|
+
def define_tasks
|
47
|
+
desc init_task_description
|
48
|
+
task init_task_name do
|
49
|
+
options = OpenStruct.new
|
50
|
+
options.params = [p4_init_dir, '--version', version]
|
51
|
+
Commands.init(options)
|
52
|
+
end
|
53
|
+
|
54
|
+
desc kill_task_description
|
55
|
+
task kill_task_name do
|
56
|
+
options = OpenStruct.new
|
57
|
+
options.params = ['--version', version]
|
58
|
+
Commands.kill(options)
|
59
|
+
end
|
60
|
+
|
61
|
+
desc reset_task_description
|
62
|
+
task reset_task_name => kill_task_name do
|
63
|
+
FileUtils.rmtree(Conventions.p4droot_dir)
|
64
|
+
end
|
65
|
+
|
66
|
+
self
|
67
|
+
end
|
68
|
+
|
69
|
+
def init_task_description
|
70
|
+
"Initializes a p4d instance, and ensures it's downloaded and running"
|
71
|
+
end
|
72
|
+
|
73
|
+
def init_task_name
|
74
|
+
"#{basename}init"
|
75
|
+
end
|
76
|
+
|
77
|
+
def kill_task_description
|
78
|
+
'Halt any locally running p4d instance'
|
79
|
+
end
|
80
|
+
|
81
|
+
def kill_task_name
|
82
|
+
"#{basename}kill"
|
83
|
+
end
|
84
|
+
|
85
|
+
def reset_task_description
|
86
|
+
'Cleans out the current p4droot working directory (after killing p4d)'
|
87
|
+
end
|
88
|
+
|
89
|
+
def reset_task_name
|
90
|
+
"#{basename}reset"
|
91
|
+
end
|
92
|
+
end
|
93
|
+
end
|
94
|
+
|
data/lib/random_util.rb
ADDED
data/p4init/287.jpg
ADDED
Binary file
|
@@ -0,0 +1,36 @@
|
|
1
|
+
|
2
|
+
class Changelist1 < ChangelistModel
|
3
|
+
def rank; 1000 end
|
4
|
+
@description = 'A couple of new files'
|
5
|
+
@adds = [
|
6
|
+
FileDefinition.new(:path => 'depot/README.txt',
|
7
|
+
:content => <<-END.gsub(/^ {8}/, '')
|
8
|
+
This is an example readme.
|
9
|
+
END
|
10
|
+
),
|
11
|
+
FileDefinition.new(:path => 'depot/dev/memorabilia/kitty.jpg',
|
12
|
+
:local_path => 'p4init/287.jpg'
|
13
|
+
)
|
14
|
+
]
|
15
|
+
end
|
16
|
+
|
17
|
+
class Changelist2 < ChangelistModel
|
18
|
+
def rank; 1001 end
|
19
|
+
@description = 'An example add and edit'
|
20
|
+
@user = 'jdoe'
|
21
|
+
@edits = [
|
22
|
+
FileDefinition.new(:path => 'depot/README.txt',
|
23
|
+
:content => <<-END.gsub(/^ {8}/, '')
|
24
|
+
This is an example readme.
|
25
|
+
Added a second line
|
26
|
+
END
|
27
|
+
)
|
28
|
+
]
|
29
|
+
@adds = [
|
30
|
+
FileDefinition.new(:path => 'depot/main/project2/example.txt',
|
31
|
+
:content => <<-END.gsub(/^ {8}/, '')
|
32
|
+
Example text file.
|
33
|
+
END
|
34
|
+
)
|
35
|
+
]
|
36
|
+
end
|
@@ -0,0 +1,15 @@
|
|
1
|
+
# User ranks should generally be 10-100, creating them early makes sense.
|
2
|
+
|
3
|
+
class TestSuperUser < UserModel
|
4
|
+
@login = 'p4super'
|
5
|
+
@password = 'superuser1A!'
|
6
|
+
@full_name = 'Super User'
|
7
|
+
@super = true
|
8
|
+
end
|
9
|
+
|
10
|
+
class JohnDoeUser < UserModel
|
11
|
+
def rank; 10 end
|
12
|
+
@login = 'jdoe'
|
13
|
+
@full_name = 'John Doe'
|
14
|
+
@password = 'johndoe1A!'
|
15
|
+
end
|
data/p4util.gemspec
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
# coding: utf-8
|
2
2
|
lib = File.expand_path('../lib', __FILE__)
|
3
3
|
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
|
-
require '
|
4
|
+
require 'p4util/version'
|
5
5
|
|
6
6
|
Gem::Specification.new do |spec|
|
7
7
|
spec.name = 'p4util'
|
@@ -19,7 +19,8 @@ Gem::Specification.new do |spec|
|
|
19
19
|
spec.require_paths = ['lib']
|
20
20
|
|
21
21
|
spec.add_development_dependency 'bundler', '~> 1.7'
|
22
|
-
spec.add_development_dependency 'rake', '~> 10.0'
|
23
22
|
|
24
23
|
spec.add_runtime_dependency 'sys-proctable', '~> 0.9'
|
24
|
+
spec.add_runtime_dependency 'p4ruby', '2014.2.0.pre1'
|
25
|
+
spec.add_runtime_dependency 'rake', '~> 10.3'
|
25
26
|
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: p4util
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.0
|
4
|
+
version: 0.1.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Tristan Juricek
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2014-
|
11
|
+
date: 2014-11-13 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: bundler
|
@@ -25,33 +25,47 @@ dependencies:
|
|
25
25
|
- !ruby/object:Gem::Version
|
26
26
|
version: '1.7'
|
27
27
|
- !ruby/object:Gem::Dependency
|
28
|
-
name:
|
28
|
+
name: sys-proctable
|
29
29
|
requirement: !ruby/object:Gem::Requirement
|
30
30
|
requirements:
|
31
31
|
- - "~>"
|
32
32
|
- !ruby/object:Gem::Version
|
33
|
-
version: '
|
34
|
-
type: :
|
33
|
+
version: '0.9'
|
34
|
+
type: :runtime
|
35
35
|
prerelease: false
|
36
36
|
version_requirements: !ruby/object:Gem::Requirement
|
37
37
|
requirements:
|
38
38
|
- - "~>"
|
39
39
|
- !ruby/object:Gem::Version
|
40
|
-
version: '
|
40
|
+
version: '0.9'
|
41
41
|
- !ruby/object:Gem::Dependency
|
42
|
-
name:
|
42
|
+
name: p4ruby
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - '='
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: 2014.2.0.pre1
|
48
|
+
type: :runtime
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - '='
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: 2014.2.0.pre1
|
55
|
+
- !ruby/object:Gem::Dependency
|
56
|
+
name: rake
|
43
57
|
requirement: !ruby/object:Gem::Requirement
|
44
58
|
requirements:
|
45
59
|
- - "~>"
|
46
60
|
- !ruby/object:Gem::Version
|
47
|
-
version: '
|
61
|
+
version: '10.3'
|
48
62
|
type: :runtime
|
49
63
|
prerelease: false
|
50
64
|
version_requirements: !ruby/object:Gem::Requirement
|
51
65
|
requirements:
|
52
66
|
- - "~>"
|
53
67
|
- !ruby/object:Gem::Version
|
54
|
-
version: '
|
68
|
+
version: '10.3'
|
55
69
|
description: The p4util command itself provides other commands, see 'p4util help'
|
56
70
|
after install. This allows you to things like download a p4d, start it, kill it,
|
57
71
|
etc mostly for quick setup of testing systems.
|
@@ -70,12 +84,26 @@ files:
|
|
70
84
|
- "./lib/commands.rb"
|
71
85
|
- "./lib/commands/download.rb"
|
72
86
|
- "./lib/commands/help.rb"
|
87
|
+
- "./lib/commands/init.rb"
|
88
|
+
- "./lib/commands/init/changelist_model.rb"
|
89
|
+
- "./lib/commands/init/file_definition.rb"
|
90
|
+
- "./lib/commands/init/init_model.rb"
|
91
|
+
- "./lib/commands/init/p4_helpers.rb"
|
92
|
+
- "./lib/commands/init/system_settings_model.rb"
|
93
|
+
- "./lib/commands/init/user_model.rb"
|
73
94
|
- "./lib/commands/kill.rb"
|
74
95
|
- "./lib/commands/start.rb"
|
96
|
+
- "./lib/commands/util.rb"
|
75
97
|
- "./lib/conventions.rb"
|
76
98
|
- "./lib/osutil.rb"
|
77
|
-
- "./lib/
|
78
|
-
- "./lib/
|
99
|
+
- "./lib/p4util.rb"
|
100
|
+
- "./lib/p4util/tasks.rb"
|
101
|
+
- "./lib/p4util/version.rb"
|
102
|
+
- "./lib/random_util.rb"
|
103
|
+
- "./p4init/287.jpg"
|
104
|
+
- "./p4init/test_changelists.rb"
|
105
|
+
- "./p4init/test_system_settings.rb"
|
106
|
+
- "./p4init/test_users.rb"
|
79
107
|
- "./p4util.gemspec"
|
80
108
|
- bin/p4util
|
81
109
|
homepage: http://perforce.com
|
data/lib/p4_util/version.rb
DELETED