p4util 0.0.3 → 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- 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