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.
@@ -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,9 @@
1
+ module Piston
2
+ module VERSION #:nodoc:
3
+ MAJOR = 1
4
+ MINOR = 4
5
+ TINY = 1
6
+
7
+ STRING = [MAJOR, MINOR, TINY].join('.')
8
+ end
9
+ 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
@@ -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
+