realityforge-piston 1.4.1
Sign up to get free protection for your applications and to get access to all the features.
- 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
|
+
|