realityforge-piston 1.4.1

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