realityforge-piston 1.4.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/CHANGELOG +81 -0
- data/LICENSE +19 -0
- data/README.rdoc +134 -0
- data/Rakefile +25 -0
- data/bin/piston +10 -0
- data/contrib/piston +43 -0
- data/lib/core_ext/range.rb +5 -0
- data/lib/core_ext/string.rb +9 -0
- data/lib/piston.rb +70 -0
- data/lib/piston/command.rb +68 -0
- data/lib/piston/command_error.rb +6 -0
- data/lib/piston/commands/convert.rb +80 -0
- data/lib/piston/commands/diff.rb +55 -0
- data/lib/piston/commands/import.rb +75 -0
- data/lib/piston/commands/lock.rb +30 -0
- data/lib/piston/commands/status.rb +82 -0
- data/lib/piston/commands/switch.rb +139 -0
- data/lib/piston/commands/unlock.rb +29 -0
- data/lib/piston/commands/update.rb +131 -0
- data/lib/piston/version.rb +9 -0
- data/lib/transat/parser.rb +189 -0
- data/piston.gemspec +22 -0
- metadata +95 -0
@@ -0,0 +1,29 @@
|
|
1
|
+
require "piston"
|
2
|
+
require "piston/command"
|
3
|
+
|
4
|
+
module Piston
|
5
|
+
module Commands
|
6
|
+
class Unlock < Piston::Command
|
7
|
+
def run
|
8
|
+
raise Piston::CommandError, "No targets to run against" if args.empty?
|
9
|
+
svn :propdel, Piston::LOCKED, *args
|
10
|
+
args.each do |dir|
|
11
|
+
logging_stream.puts "Unlocked '#{dir}'"
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
def self.help
|
16
|
+
"Undoes the changes enabled by lock"
|
17
|
+
end
|
18
|
+
|
19
|
+
def self.detailed_help
|
20
|
+
<<EOF
|
21
|
+
usage: unlock DIR [DIR [...]]
|
22
|
+
|
23
|
+
Unlocked folders are free to be updated to the latest revision when
|
24
|
+
updating.
|
25
|
+
EOF
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
@@ -0,0 +1,131 @@
|
|
1
|
+
require "piston"
|
2
|
+
require "piston/command"
|
3
|
+
require 'find'
|
4
|
+
|
5
|
+
module Piston
|
6
|
+
module Commands
|
7
|
+
class Update < Piston::Command
|
8
|
+
def run
|
9
|
+
(args.empty? ? find_targets : args).each do |dir|
|
10
|
+
update dir
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
def update(dir)
|
15
|
+
return unless File.directory?(dir)
|
16
|
+
return skip(dir, "locked") unless svn(:propget, Piston::LOCKED, dir) == ''
|
17
|
+
status = svn(:status, '--show-updates', dir)
|
18
|
+
new_local_rev = nil
|
19
|
+
new_status = Array.new
|
20
|
+
status.each_line do |line|
|
21
|
+
if line =~ /status.+\s(\d+)$/i then
|
22
|
+
new_local_rev = $1.to_i
|
23
|
+
else
|
24
|
+
new_status << line unless line =~ /^\?/
|
25
|
+
end
|
26
|
+
end
|
27
|
+
raise "Unable to parse status\n#{status}" unless new_local_rev
|
28
|
+
return skip(dir, "pending updates -- run \"svn update #{dir}\"\n#{new_status}") if new_status.size > 0
|
29
|
+
|
30
|
+
logging_stream.puts "Processing '#{dir}'..."
|
31
|
+
repos = svn(:propget, Piston::ROOT, dir).chomp
|
32
|
+
uuid = svn(:propget, Piston::UUID, dir).chomp
|
33
|
+
remote_revision = svn(:propget, Piston::REMOTE_REV, dir).chomp.to_i
|
34
|
+
local_revision = svn(:propget, Piston::LOCAL_REV, dir).chomp.to_i
|
35
|
+
local_revision = local_revision.succ
|
36
|
+
|
37
|
+
logging_stream.puts " Fetching remote repository's latest revision and UUID"
|
38
|
+
info = YAML::load(svn(:info, repos))
|
39
|
+
return skip(dir, "Repository UUID changed\n Expected #{uuid}\n Found #{info['Repository UUID']}\n Repository: #{repos}") unless uuid == info['Repository UUID']
|
40
|
+
|
41
|
+
new_remote_rev = info['Last Changed Rev'].to_i
|
42
|
+
return skip(dir, "unchanged from revision #{remote_revision}", false) if remote_revision == new_remote_rev
|
43
|
+
|
44
|
+
revisions = (remote_revision .. (revision || new_remote_rev))
|
45
|
+
|
46
|
+
logging_stream.puts " Restoring remote repository to known state at r#{revisions.first}"
|
47
|
+
svn :checkout, '--ignore-externals', '--quiet', '--revision', revisions.first, repos, dir.tmp
|
48
|
+
|
49
|
+
logging_stream.puts " Updating remote repository to r#{revisions.last}"
|
50
|
+
updates = svn :update, '--ignore-externals', '--revision', revisions.last, dir.tmp
|
51
|
+
|
52
|
+
logging_stream.puts " Processing adds/deletes"
|
53
|
+
merges = Array.new
|
54
|
+
changes = 0
|
55
|
+
updates.each_line do |line|
|
56
|
+
next unless line =~ %r{^([A-Z]).*\s+#{Regexp.escape(dir.tmp)}[\\/](.+)$}
|
57
|
+
op, file = $1, $2
|
58
|
+
changes += 1
|
59
|
+
|
60
|
+
case op
|
61
|
+
when 'A'
|
62
|
+
if File.directory?(File.join(dir.tmp, file)) then
|
63
|
+
svn :mkdir, '--quiet', File.join(dir, file)
|
64
|
+
else
|
65
|
+
copy(dir, file)
|
66
|
+
svn :add, '--quiet', '--force', File.join(dir, file)
|
67
|
+
end
|
68
|
+
when 'D'
|
69
|
+
svn :remove, '--quiet', '--force', File.join(dir, file)
|
70
|
+
else
|
71
|
+
copy(dir, file)
|
72
|
+
merges << file
|
73
|
+
end
|
74
|
+
end
|
75
|
+
|
76
|
+
# Determine if there are any local changes in the pistoned directory
|
77
|
+
log = svn(:log, '--quiet', '--revision', (local_revision .. new_local_rev).to_svn, '--limit', '2', dir)
|
78
|
+
|
79
|
+
# If none, we skip the merge process
|
80
|
+
if local_revision < new_local_rev && log.count("\n") > 3 then
|
81
|
+
logging_stream.puts " Merging local changes back in"
|
82
|
+
merges.each do |file|
|
83
|
+
begin
|
84
|
+
svn(:merge, '--quiet', '--revision', (local_revision .. new_local_rev).to_svn,
|
85
|
+
File.join(dir, file), File.join(dir, file))
|
86
|
+
rescue RuntimeError
|
87
|
+
next if $!.message =~ /Unable to find repository location for/
|
88
|
+
end
|
89
|
+
end
|
90
|
+
end
|
91
|
+
|
92
|
+
logging_stream.puts " Removing temporary files / folders"
|
93
|
+
FileUtils.rm_rf dir.tmp
|
94
|
+
|
95
|
+
logging_stream.puts " Updating Piston properties"
|
96
|
+
svn :propset, Piston::REMOTE_REV, revisions.last, dir
|
97
|
+
svn :propset, Piston::LOCAL_REV, new_local_rev, dir
|
98
|
+
svn :propset, Piston::LOCKED, revisions.last, dir if lock
|
99
|
+
|
100
|
+
logging_stream.puts " Updated to r#{revisions.last} (#{changes} changes)"
|
101
|
+
end
|
102
|
+
|
103
|
+
def copy(dir, file)
|
104
|
+
FileUtils.cp(File.join(dir.tmp, file), File.join(dir, file))
|
105
|
+
end
|
106
|
+
|
107
|
+
def self.help
|
108
|
+
"Updates all or specified folders to the latest revision"
|
109
|
+
end
|
110
|
+
|
111
|
+
def self.detailed_help
|
112
|
+
<<EOF
|
113
|
+
usage: update [DIR [...]]
|
114
|
+
|
115
|
+
This operation has the effect of downloading all remote changes back to our
|
116
|
+
working copy. If any local modifications were done, they will be preserved.
|
117
|
+
If merge conflicts occur, they will not be taken care of, and your subsequent
|
118
|
+
commit will fail.
|
119
|
+
|
120
|
+
Piston will refuse to update a folder if it has pending updates. Run
|
121
|
+
'svn update' on the target folder to update it before running Piston
|
122
|
+
again.
|
123
|
+
EOF
|
124
|
+
end
|
125
|
+
|
126
|
+
def self.aliases
|
127
|
+
%w(up)
|
128
|
+
end
|
129
|
+
end
|
130
|
+
end
|
131
|
+
end
|
@@ -0,0 +1,189 @@
|
|
1
|
+
require "optparse"
|
2
|
+
|
3
|
+
module Transat
|
4
|
+
class VersionNeeded < StandardError; end
|
5
|
+
|
6
|
+
class HelpNeeded < StandardError
|
7
|
+
attr_reader :command
|
8
|
+
|
9
|
+
def initialize(command)
|
10
|
+
@command = command
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
class NoCommandGiven < StandardError
|
15
|
+
def message
|
16
|
+
"No command given"
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
class UnknownOptions < StandardError
|
21
|
+
attr_reader :command
|
22
|
+
|
23
|
+
def initialize(command, unrecognized_options)
|
24
|
+
@command, @unrecognized_options = command, unrecognized_options
|
25
|
+
end
|
26
|
+
|
27
|
+
def message
|
28
|
+
"Command #{@command} does not accept options #{@unrecognized_options.join(", ")}"
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
class UnknownCommand < StandardError
|
33
|
+
def initialize(command, parser)
|
34
|
+
@command, @parser = command, parser
|
35
|
+
end
|
36
|
+
|
37
|
+
def message
|
38
|
+
"Unknown command: #{@command.inspect}"
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
class BaseCommand
|
43
|
+
attr_reader :non_options, :options
|
44
|
+
def initialize(non_options, options)
|
45
|
+
@non_options, @options = non_options, options
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
class VersionCommand < BaseCommand
|
50
|
+
def run
|
51
|
+
raise VersionNeeded
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
class HelpCommand < BaseCommand
|
56
|
+
def run
|
57
|
+
raise HelpNeeded.new(non_options.first)
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
class Parser
|
62
|
+
def initialize(&block)
|
63
|
+
@valid_options, @received_options, @commands = [], {}, {}
|
64
|
+
@option_parser = OptionParser.new
|
65
|
+
|
66
|
+
command(:help, Transat::HelpCommand)
|
67
|
+
command(:version, Transat::VersionCommand)
|
68
|
+
instance_eval(&block) if block_given?
|
69
|
+
end
|
70
|
+
|
71
|
+
def option(name, options={})
|
72
|
+
options[:long] = name.to_s.gsub("_", "-") unless options[:long]
|
73
|
+
@valid_options << name
|
74
|
+
@received_options[name] = nil
|
75
|
+
|
76
|
+
opt_args = []
|
77
|
+
opt_args << "-#{options[:short]}" if options.has_key?(:short)
|
78
|
+
opt_args << "--#{options[:long] || name}"
|
79
|
+
opt_args << "=#{options[:param_name]}" if options.has_key?(:param_name)
|
80
|
+
opt_args << options[:message]
|
81
|
+
case options[:type]
|
82
|
+
when :int, :integer
|
83
|
+
opt_args << Integer
|
84
|
+
when :float
|
85
|
+
opt_args << Float
|
86
|
+
when nil
|
87
|
+
# NOP
|
88
|
+
else
|
89
|
+
raise ArgumentError, "Option #{name} has a bad :type parameter: #{options[:type].inspect}"
|
90
|
+
end
|
91
|
+
|
92
|
+
@option_parser.on(*opt_args.compact) do |value|
|
93
|
+
@received_options[name] = value
|
94
|
+
end
|
95
|
+
end
|
96
|
+
|
97
|
+
def command(name, klass, options={})
|
98
|
+
@commands[name.to_s] = options.merge(:class => klass)
|
99
|
+
end
|
100
|
+
|
101
|
+
def parse_and_execute(args=ARGV)
|
102
|
+
begin
|
103
|
+
command, non_options = parse(args)
|
104
|
+
execute(command, non_options)
|
105
|
+
rescue HelpNeeded
|
106
|
+
$stderr.puts usage($!.command)
|
107
|
+
exit 1
|
108
|
+
rescue VersionNeeded
|
109
|
+
puts "#{program_name} #{version}"
|
110
|
+
exit 0
|
111
|
+
rescue NoCommandGiven, UnknownOptions, UnknownCommand
|
112
|
+
$stderr.puts "ERROR: #{$!.message}"
|
113
|
+
$stderr.puts usage($!.respond_to?(:command) ? $!.command : nil)
|
114
|
+
exit 1
|
115
|
+
end
|
116
|
+
end
|
117
|
+
|
118
|
+
def parse(args)
|
119
|
+
non_options = @option_parser.parse(args)
|
120
|
+
command = non_options.shift
|
121
|
+
raise NoCommandGiven unless command
|
122
|
+
return command, non_options
|
123
|
+
end
|
124
|
+
|
125
|
+
def execute(command, non_options)
|
126
|
+
found = false
|
127
|
+
@commands.each do |command_name, options|
|
128
|
+
command_klass = options[:class]
|
129
|
+
aliases = [command_name]
|
130
|
+
aliases += command_klass.aliases if command_klass.respond_to?(:aliases)
|
131
|
+
return command_klass.new(non_options, @received_options).run if aliases.include?(command)
|
132
|
+
end
|
133
|
+
|
134
|
+
raise UnknownCommand.new(command, self)
|
135
|
+
end
|
136
|
+
|
137
|
+
def usage(command=nil)
|
138
|
+
message = []
|
139
|
+
|
140
|
+
if command then
|
141
|
+
command_klass = @commands[command][:class]
|
142
|
+
help =
|
143
|
+
if command_klass.respond_to?(:aliases) then
|
144
|
+
"#{command} (#{command_klass.aliases.join(", ")})"
|
145
|
+
else
|
146
|
+
"#{command}"
|
147
|
+
end
|
148
|
+
help = "#{help}: #{command_klass.help}" if command_klass.respond_to?(:help)
|
149
|
+
message << help
|
150
|
+
message << command_klass.detailed_help if command_klass.respond_to?(:detailed_help)
|
151
|
+
message << ""
|
152
|
+
message << "Valid options:"
|
153
|
+
@option_parser.summarize(message)
|
154
|
+
else
|
155
|
+
message << "usage: #{program_name.downcase} <SUBCOMMAND> [OPTIONS] [ARGS...]"
|
156
|
+
message << "Type '#{program_name.downcase} help <SUBCOMMAND>' for help on a specific subcommand."
|
157
|
+
message << "Type '#{program_name.downcase} version' to get this program's version."
|
158
|
+
message << ""
|
159
|
+
message << "Available subcommands are:"
|
160
|
+
@commands.sort.each do |command, options|
|
161
|
+
command_klass = options[:class]
|
162
|
+
if command_klass.respond_to?(:aliases) then
|
163
|
+
message << " #{command} (#{command_klass.aliases.join(", ")})"
|
164
|
+
else
|
165
|
+
message << " #{command}"
|
166
|
+
end
|
167
|
+
end
|
168
|
+
end
|
169
|
+
|
170
|
+
message.map {|line| line.chomp}.join("\n")
|
171
|
+
end
|
172
|
+
|
173
|
+
def program_name(value=nil)
|
174
|
+
value ? @program_name = value : @program_name
|
175
|
+
end
|
176
|
+
|
177
|
+
def version(value=nil)
|
178
|
+
if value then
|
179
|
+
@version = value.respond_to?(:join) ? value.join(".") : value
|
180
|
+
else
|
181
|
+
@version
|
182
|
+
end
|
183
|
+
end
|
184
|
+
|
185
|
+
def self.parse_and_execute(args=ARGV, &block)
|
186
|
+
self.new(&block).parse_and_execute(args)
|
187
|
+
end
|
188
|
+
end
|
189
|
+
end
|
data/piston.gemspec
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
Gem::Specification.new do |spec|
|
2
|
+
spec.name = 'realityforge-piston'
|
3
|
+
spec.version = Piston::VERSION::STRING
|
4
|
+
spec.authors = ['Francois Beausoleil','Peter Donald']
|
5
|
+
spec.email = ["francois@teksol.info", "peter@realityforge.org"]
|
6
|
+
spec.homepage = "http://github.com/realityforge/piston"
|
7
|
+
spec.summary = "Piston is a utility that eases vendor branch management in subversion."
|
8
|
+
spec.description = <<-TEXT
|
9
|
+
Piston is a utility that eases vendor branch management in subversion.
|
10
|
+
TEXT
|
11
|
+
spec.files = Dir['{contrib,bin,lib,spec}/**/*', '*.gemspec'] +
|
12
|
+
['LICENSE', 'README.rdoc', 'CHANGELOG', 'Rakefile']
|
13
|
+
spec.require_paths = ['lib']
|
14
|
+
|
15
|
+
spec.bindir = "bin" # Use these for applications.
|
16
|
+
spec.executables = ["piston"]
|
17
|
+
spec.default_executable = "piston"
|
18
|
+
|
19
|
+
spec.has_rdoc = true
|
20
|
+
spec.extra_rdoc_files = 'README.rdoc', 'LICENSE', 'CHANGELOG'
|
21
|
+
spec.rdoc_options = '--title', "#{spec.name} #{spec.version}", '--main', 'README.rdoc'
|
22
|
+
end
|
metadata
ADDED
@@ -0,0 +1,95 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: realityforge-piston
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
prerelease: false
|
5
|
+
segments:
|
6
|
+
- 1
|
7
|
+
- 4
|
8
|
+
- 1
|
9
|
+
version: 1.4.1
|
10
|
+
platform: ruby
|
11
|
+
authors:
|
12
|
+
- Francois Beausoleil
|
13
|
+
- Peter Donald
|
14
|
+
autorequire:
|
15
|
+
bindir: bin
|
16
|
+
cert_chain: []
|
17
|
+
|
18
|
+
date: 2010-09-28 00:00:00 +10:00
|
19
|
+
default_executable: piston
|
20
|
+
dependencies: []
|
21
|
+
|
22
|
+
description: |
|
23
|
+
Piston is a utility that eases vendor branch management in subversion.
|
24
|
+
|
25
|
+
email:
|
26
|
+
- francois@teksol.info
|
27
|
+
- peter@realityforge.org
|
28
|
+
executables:
|
29
|
+
- piston
|
30
|
+
extensions: []
|
31
|
+
|
32
|
+
extra_rdoc_files:
|
33
|
+
- README.rdoc
|
34
|
+
- LICENSE
|
35
|
+
- CHANGELOG
|
36
|
+
files:
|
37
|
+
- contrib/piston
|
38
|
+
- bin/piston
|
39
|
+
- lib/piston.rb
|
40
|
+
- lib/core_ext/range.rb
|
41
|
+
- lib/core_ext/string.rb
|
42
|
+
- lib/piston/command.rb
|
43
|
+
- lib/piston/command_error.rb
|
44
|
+
- lib/piston/version.rb
|
45
|
+
- lib/piston/commands/convert.rb
|
46
|
+
- lib/piston/commands/diff.rb
|
47
|
+
- lib/piston/commands/import.rb
|
48
|
+
- lib/piston/commands/lock.rb
|
49
|
+
- lib/piston/commands/status.rb
|
50
|
+
- lib/piston/commands/switch.rb
|
51
|
+
- lib/piston/commands/unlock.rb
|
52
|
+
- lib/piston/commands/update.rb
|
53
|
+
- lib/transat/parser.rb
|
54
|
+
- piston.gemspec
|
55
|
+
- LICENSE
|
56
|
+
- README.rdoc
|
57
|
+
- CHANGELOG
|
58
|
+
- Rakefile
|
59
|
+
has_rdoc: true
|
60
|
+
homepage: http://github.com/realityforge/piston
|
61
|
+
licenses: []
|
62
|
+
|
63
|
+
post_install_message:
|
64
|
+
rdoc_options:
|
65
|
+
- --title
|
66
|
+
- realityforge-piston 1.4.1
|
67
|
+
- --main
|
68
|
+
- README.rdoc
|
69
|
+
require_paths:
|
70
|
+
- lib
|
71
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
72
|
+
none: false
|
73
|
+
requirements:
|
74
|
+
- - ">="
|
75
|
+
- !ruby/object:Gem::Version
|
76
|
+
segments:
|
77
|
+
- 0
|
78
|
+
version: "0"
|
79
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
80
|
+
none: false
|
81
|
+
requirements:
|
82
|
+
- - ">="
|
83
|
+
- !ruby/object:Gem::Version
|
84
|
+
segments:
|
85
|
+
- 0
|
86
|
+
version: "0"
|
87
|
+
requirements: []
|
88
|
+
|
89
|
+
rubyforge_project:
|
90
|
+
rubygems_version: 1.3.7
|
91
|
+
signing_key:
|
92
|
+
specification_version: 3
|
93
|
+
summary: Piston is a utility that eases vendor branch management in subversion.
|
94
|
+
test_files: []
|
95
|
+
|