| |   |
| 1 | 1 | require "piston" |
| 2 | | require "piston/command" |
| 3 | | require 'find' |
| 4 | 2 | |
| 5 | 3 | module Piston |
| 6 | 4 | 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 find_targets |
| 15 | | targets = Array.new |
| 16 | | svn(:propget, '--recursive', Piston::ROOT).each_line do |line| |
| 17 | | next unless line =~ /^([^ ]+)\s-\s.*$/ |
| 18 | | targets << $1 |
| 19 | | end |
| 20 | | |
| 21 | | targets |
| 5 | class Update |
| 6 | def initialize(paths=nil, options={}) |
| 7 | @options = options |
| 8 | @paths = paths.kind_of?(Enumerable) ? paths : [paths] |
| 9 | @wcs = @paths.map {|path| Piston::WorkingCopy.new(path, :logger => options[:logger])} |
| 22 | 10 | end |
| 23 | 11 | |
| 24 | | def update(dir) |
| 25 | | return unless File.directory?(dir) |
| 26 | | return skip(dir, "locked") unless svn(:propget, Piston::LOCKED, dir) == '' |
| 27 | | status = svn(:status, '--show-updates', dir) |
| 28 | | new_local_rev = nil |
| 29 | | new_status = Array.new |
| 30 | | status.each_line do |line| |
| 31 | | if line =~ /status.+\s(\d+)$/i then |
| 32 | | new_local_rev = $1.to_i |
| 33 | | else |
| 34 | | new_status << line unless line =~ /^\?/ |
| 12 | def run |
| 13 | @wcs.each do |wc| |
| 14 | begin |
| 15 | update(wc) |
| 16 | rescue |
| 17 | raise if @wcs.size == 1 |
| 18 | warn {$!.message} |
| 35 | 19 | end |
| 36 | 20 | end |
| 37 | | raise "Unable to parse status\n#{status}" unless new_local_rev |
| 38 | | return skip(dir, "pending updates -- run \"svn update #{dir}\"\n#{new_status}") if new_status.size > 0 |
| 21 | end |
| 39 | 22 | |
| 40 | | logging_stream.puts "Processing '#{dir}'..." |
| 41 | | repos = svn(:propget, Piston::ROOT, dir).chomp |
| 42 | | uuid = svn(:propget, Piston::UUID, dir).chomp |
| 43 | | remote_revision = svn(:propget, Piston::REMOTE_REV, dir).chomp.to_i |
| 44 | | local_revision = svn(:propget, Piston::LOCAL_REV, dir).chomp.to_i |
| 45 | | local_revision = local_revision.succ |
| 23 | def update(wc) |
| 24 | info {"Updating #{wc}"} |
| 25 | vrepos = Piston::Repository.new(wc.propget(Piston::REMOTE_ROOT), :logger => @options[:logger]) |
| 46 | 26 | |
| 47 | | logging_stream.puts " Fetching remote repository's latest revision and UUID" |
| 48 | | info = YAML::load(svn(:info, repos)) |
| 49 | | return skip(dir, "Repository UUID changed\n Expected #{uuid}\n Found #{info['Repository UUID']}\n Repository: #{repos}") unless uuid == info['Repository UUID'] |
| 27 | check_preconditions!(vrepos, wc) |
| 50 | 28 | |
| 51 | | new_remote_rev = info['Last Changed Rev'].to_i |
| 52 | | return skip(dir, "unchanged from revision #{remote_revision}", false) if remote_revision == new_remote_rev |
| 29 | new_vendor_head = vrepos.last_changed_rev |
| 30 | vwc = Piston::WorkingCopy.new(wc.up.path + ".#{File.basename(wc.path)}.tmp", :logger => @options[:logger]) |
| 31 | prior_vendor_head = wc.propget(Piston::REMOTE_REVISION) |
| 32 | vwc.checkout(vrepos.url, prior_vendor_head) |
| 33 | vwc.update(new_vendor_head) |
| 34 | changes = vwc.log(prior_vendor_head.succ .. new_vendor_head) |
| 35 | debug { changes.inspect } |
| 36 | changes.each do |op, filename| |
| 37 | case op |
| 38 | when :add |
| 39 | if File.directory?(vwc.wc_path(filename)) then |
| 40 | wc.mkdir(filename) |
| 41 | else |
| 42 | wc.create!(filename, File.read(vwc.wc_path(filename))) |
| 43 | end |
| 53 | 44 | |
| 54 | | revisions = (remote_revision .. (revision || new_remote_rev)) |
| 45 | when :modify |
| 46 | wc.edit!(filename, File.read(vwc.wc_path(filename))) |
| 55 | 47 | |
| 56 | | logging_stream.puts " Restoring remote repository to known state at r#{revisions.first}" |
| 57 | | svn :checkout, '--ignore-externals', '--quiet', '--revision', revisions.first, repos, dir.tmp |
| 48 | when :delete |
| 49 | wc.delete(filename) |
| 58 | 50 | |
| 59 | | logging_stream.puts " Updating remote repository to r#{revisions.last}" |
| 60 | | updates = svn :update, '--ignore-externals', '--revision', revisions.last, dir.tmp |
| 51 | when :move |
| 52 | from, to = filename.to_a.flatten |
| 53 | wc.move(from, to) |
| 61 | 54 | |
| 62 | | logging_stream.puts " Processing adds/deletes" |
| 63 | | merges = Array.new |
| 64 | | changes = 0 |
| 65 | | updates.each_line do |line| |
| 66 | | next unless line =~ %r{^([A-Z]).*\s+#{Regexp.escape(dir.tmp)}[\\/](.+)$} |
| 67 | | op, file = $1, $2 |
| 68 | | changes += 1 |
| 55 | when :copy |
| 56 | from, to = filename.to_a.flatten |
| 57 | wc.copy(from, to) |
| 69 | 58 | |
| 70 | | case op |
| 71 | | when 'A' |
| 72 | | if File.directory?(File.join(dir.tmp, file)) then |
| 73 | | svn :mkdir, '--quiet', File.join(dir, file) |
| 74 | | else |
| 75 | | copy(dir, file) |
| 76 | | svn :add, '--quiet', '--force', File.join(dir, file) |
| 77 | | end |
| 78 | | when 'D' |
| 79 | | svn :remove, '--quiet', '--force', File.join(dir, file) |
| 80 | 59 | else |
| 81 | | copy(dir, file) |
| 82 | | merges << file |
| 60 | raise SyntaxError, "Unknown repository operation: #{op.inspect}" |
| 83 | 61 | end |
| 84 | 62 | end |
| 85 | 63 | |
| 86 | | # Determine if there are any local changes in the pistoned directory |
| 87 | | log = svn(:log, '--quiet', '--revision', (local_revision .. new_local_rev).to_svn, '--limit', '2', dir) |
| 88 | | |
| 89 | | # If none, we skip the merge process |
| 90 | | if local_revision < new_local_rev && log.count("\n") > 3 then |
| 91 | | logging_stream.puts " Merging local changes back in" |
| 92 | | merges.each do |file| |
| 93 | | begin |
| 94 | | svn(:merge, '--quiet', '--revision', (local_revision .. new_local_rev).to_svn, |
| 95 | | File.join(dir, file), File.join(dir, file)) |
| 96 | | rescue RuntimeError |
| 97 | | next if $!.message =~ /Unable to find repository location for/ |
| 98 | | end |
| 99 | | end |
| 100 | | end |
| 64 | prior_local_head = wc.propget(Piston::LOCAL_REVISION).to_i.succ |
| 65 | new_local_head = wc.youngest |
| 66 | changes.map {|op, filename| filename if op == :modify}.compact.each do |filename| |
| 67 | wc.merge(filename, prior_local_head .. new_local_head) |
| 68 | end unless prior_local_head == new_local_head |
| 101 | 69 | |
| 102 | | logging_stream.puts " Removing temporary files / folders" |
| 103 | | FileUtils.rm_rf dir.tmp |
| 70 | wc.propset(Piston::REMOTE_REVISION, new_vendor_head) |
| 71 | wc.propset(Piston::LOCAL_REVISION, new_local_head) unless prior_local_head == new_local_head |
| 72 | vwc.destroy! |
| 73 | end |
| 104 | 74 | |
| 105 | | logging_stream.puts " Updating Piston properties" |
| 106 | | svn :propset, Piston::REMOTE_REV, revisions.last, dir |
| 107 | | svn :propset, Piston::LOCAL_REV, new_local_rev, dir |
| 108 | | svn :propset, Piston::LOCKED, revisions.last, dir if lock |
| 75 | def check_preconditions!(vrepos, wc) |
| 76 | local_vendor_uuid = wc.propget(Piston::REMOTE_UUID) |
| 77 | raise UnknownRepository.new(vrepos.url) unless vrepos.exists? |
| 109 | 78 | |
| 110 | | logging_stream.puts " Updated to r#{revisions.last} (#{changes} changes)" |
| 79 | vendor_uuid = vrepos.uuid |
| 80 | raise RepositoryUuidChanged.new(vrepos.url, local_vendor_uuid, vendor_uuid) unless local_vendor_uuid == vendor_uuid |
| 111 | 81 | end |
| 112 | 82 | |
| 113 | | def copy(dir, file) |
| 114 | | FileUtils.cp(File.join(dir.tmp, file), File.join(dir, file)) |
| 83 | def debug(&block) |
| 84 | self.logger.debug(&block) if self.logger |
| 115 | 85 | end |
| 116 | 86 | |
| 117 | | def skip(dir, msg, header=true) |
| 118 | | logging_stream.print "Skipping '#{dir}': " if header |
| 119 | | logging_stream.puts msg |
| 87 | def info(&block) |
| 88 | self.logger.info(&block) if self.logger |
| 120 | 89 | end |
| 121 | 90 | |
| 122 | | def self.help |
| 123 | | "Updates all or specified folders to the latest revision" |
| 91 | def warn(&block) |
| 92 | self.logger.warn(&block) if self.logger |
| 124 | 93 | end |
| 125 | 94 | |
| 126 | | def self.detailed_help |
| 127 | | <<EOF |
| 128 | | usage: update [DIR [...]] |
| 129 | | |
| 130 | | This operation has the effect of downloading all remote changes back to our |
| 131 | | working copy. If any local modifications were done, they will be preserved. |
| 132 | | If merge conflicts occur, they will not be taken care of, and your subsequent |
| 133 | | commit will fail. |
| 95 | def error(&block) |
| 96 | self.logger.error(&block) if self.logger |
| 97 | end |
| 134 | 98 | |
| 135 | | Piston will refuse to update a folder if it has pending updates. Run |
| 136 | | 'svn update' on the target folder to update it before running Piston |
| 137 | | again. |
| 138 | | EOF |
| 99 | def fatal(&block) |
| 100 | self.logger.fatal(&block) if self.logger |
| 139 | 101 | end |
| 140 | 102 | |
| 141 | | def self.aliases |
| 142 | | %w(up) |
| 103 | def logger |
| 104 | @options[:logger] |
| 143 | 105 | end |
| 144 | 106 | end |
| 145 | 107 | end |
| toggle raw diff |
--- a/lib/piston/commands/update.rb
+++ b/lib/piston/commands/update.rb
@@ -1,145 +1,107 @@
require "piston"
-require "piston/command"
-require 'find'
module Piston
module Commands
- class Update < Piston::Command
- def run
- (args.empty? ? find_targets : args).each do |dir|
- update dir
- end
- end
-
- def find_targets
- targets = Array.new
- svn(:propget, '--recursive', Piston::ROOT).each_line do |line|
- next unless line =~ /^([^ ]+)\s-\s.*$/
- targets << $1
- end
-
- targets
+ class Update
+ def initialize(paths=nil, options={})
+ @options = options
+ @paths = paths.kind_of?(Enumerable) ? paths : [paths]
+ @wcs = @paths.map {|path| Piston::WorkingCopy.new(path, :logger => options[:logger])}
end
- def update(dir)
- return unless File.directory?(dir)
- return skip(dir, "locked") unless svn(:propget, Piston::LOCKED, dir) == ''
- status = svn(:status, '--show-updates', dir)
- new_local_rev = nil
- new_status = Array.new
- status.each_line do |line|
- if line =~ /status.+\s(\d+)$/i then
- new_local_rev = $1.to_i
- else
- new_status << line unless line =~ /^\?/
+ def run
+ @wcs.each do |wc|
+ begin
+ update(wc)
+ rescue
+ raise if @wcs.size == 1
+ warn {$!.message}
end
end
- raise "Unable to parse status\n#{status}" unless new_local_rev
- return skip(dir, "pending updates -- run \"svn update #{dir}\"\n#{new_status}") if new_status.size > 0
+ end
- logging_stream.puts "Processing '#{dir}'..."
- repos = svn(:propget, Piston::ROOT, dir).chomp
- uuid = svn(:propget, Piston::UUID, dir).chomp
- remote_revision = svn(:propget, Piston::REMOTE_REV, dir).chomp.to_i
- local_revision = svn(:propget, Piston::LOCAL_REV, dir).chomp.to_i
- local_revision = local_revision.succ
+ def update(wc)
+ info {"Updating #{wc}"}
+ vrepos = Piston::Repository.new(wc.propget(Piston::REMOTE_ROOT), :logger => @options[:logger])
- logging_stream.puts " Fetching remote repository's latest revision and UUID"
- info = YAML::load(svn(:info, repos))
- return skip(dir, "Repository UUID changed\n Expected #{uuid}\n Found #{info['Repository UUID']}\n Repository: #{repos}") unless uuid == info['Repository UUID']
+ check_preconditions!(vrepos, wc)
- new_remote_rev = info['Last Changed Rev'].to_i
- return skip(dir, "unchanged from revision #{remote_revision}", false) if remote_revision == new_remote_rev
+ new_vendor_head = vrepos.last_changed_rev
+ vwc = Piston::WorkingCopy.new(wc.up.path + ".#{File.basename(wc.path)}.tmp", :logger => @options[:logger])
+ prior_vendor_head = wc.propget(Piston::REMOTE_REVISION)
+ vwc.checkout(vrepos.url, prior_vendor_head)
+ vwc.update(new_vendor_head)
+ changes = vwc.log(prior_vendor_head.succ .. new_vendor_head)
+ debug { changes.inspect }
+ changes.each do |op, filename|
+ case op
+ when :add
+ if File.directory?(vwc.wc_path(filename)) then
+ wc.mkdir(filename)
+ else
+ wc.create!(filename, File.read(vwc.wc_path(filename)))
+ end
- revisions = (remote_revision .. (revision || new_remote_rev))
+ when :modify
+ wc.edit!(filename, File.read(vwc.wc_path(filename)))
- logging_stream.puts " Restoring remote repository to known state at r#{revisions.first}"
- svn :checkout, '--ignore-externals', '--quiet', '--revision', revisions.first, repos, dir.tmp
+ when :delete
+ wc.delete(filename)
- logging_stream.puts " Updating remote repository to r#{revisions.last}"
- updates = svn :update, '--ignore-externals', '--revision', revisions.last, dir.tmp
+ when :move
+ from, to = filename.to_a.flatten
+ wc.move(from, to)
- logging_stream.puts " Processing adds/deletes"
- merges = Array.new
- changes = 0
- updates.each_line do |line|
- next unless line =~ %r{^([A-Z]).*\s+#{Regexp.escape(dir.tmp)}[\\/](.+)$}
- op, file = $1, $2
- changes += 1
+ when :copy
+ from, to = filename.to_a.flatten
+ wc.copy(from, to)
- case op
- when 'A'
- if File.directory?(File.join(dir.tmp, file)) then
- svn :mkdir, '--quiet', File.join(dir, file)
- else
- copy(dir, file)
- svn :add, '--quiet', '--force', File.join(dir, file)
- end
- when 'D'
- svn :remove, '--quiet', '--force', File.join(dir, file)
else
- copy(dir, file)
- merges << file
+ raise SyntaxError, "Unknown repository operation: #{op.inspect}"
end
end
- # Determine if there are any local changes in the pistoned directory
- log = svn(:log, '--quiet', '--revision', (local_revision .. new_local_rev).to_svn, '--limit', '2', dir)
-
- # If none, we skip the merge process
- if local_revision < new_local_rev && log.count("\n") > 3 then
- logging_stream.puts " Merging local changes back in"
- merges.each do |file|
- begin
- svn(:merge, '--quiet', '--revision', (local_revision .. new_local_rev).to_svn,
- File.join(dir, file), File.join(dir, file))
- rescue RuntimeError
- next if $!.message =~ /Unable to find repository location for/
- end
- end
- end
+ prior_local_head = wc.propget(Piston::LOCAL_REVISION).to_i.succ
+ new_local_head = wc.youngest
+ changes.map {|op, filename| filename if op == :modify}.compact.each do |filename|
+ wc.merge(filename, prior_local_head .. new_local_head)
+ end unless prior_local_head == new_local_head
- logging_stream.puts " Removing temporary files / folders"
- FileUtils.rm_rf dir.tmp
+ wc.propset(Piston::REMOTE_REVISION, new_vendor_head)
+ wc.propset(Piston::LOCAL_REVISION, new_local_head) unless prior_local_head == new_local_head
+ vwc.destroy!
+ end
- logging_stream.puts " Updating Piston properties"
- svn :propset, Piston::REMOTE_REV, revisions.last, dir
- svn :propset, Piston::LOCAL_REV, new_local_rev, dir
- svn :propset, Piston::LOCKED, revisions.last, dir if lock
+ def check_preconditions!(vrepos, wc)
+ local_vendor_uuid = wc.propget(Piston::REMOTE_UUID)
+ raise UnknownRepository.new(vrepos.url) unless vrepos.exists?
- logging_stream.puts " Updated to r#{revisions.last} (#{changes} changes)"
+ vendor_uuid = vrepos.uuid
+ raise RepositoryUuidChanged.new(vrepos.url, local_vendor_uuid, vendor_uuid) unless local_vendor_uuid == vendor_uuid
end
- def copy(dir, file)
- FileUtils.cp(File.join(dir.tmp, file), File.join(dir, file))
+ def debug(&block)
+ self.logger.debug(&block) if self.logger
end
- def skip(dir, msg, header=true)
- logging_stream.print "Skipping '#{dir}': " if header
- logging_stream.puts msg
+ def info(&block)
+ self.logger.info(&block) if self.logger
end
- def self.help
- "Updates all or specified folders to the latest revision"
+ def warn(&block)
+ self.logger.warn(&block) if self.logger
end
- def self.detailed_help
- <<EOF
-usage: update [DIR [...]]
-
- This operation has the effect of downloading all remote changes back to our
- working copy. If any local modifications were done, they will be preserved.
- If merge conflicts occur, they will not be taken care of, and your subsequent
- commit will fail.
+ def error(&block)
+ self.logger.error(&block) if self.logger
+ end
- Piston will refuse to update a folder if it has pending updates. Run
- 'svn update' on the target folder to update it before running Piston
- again.
-EOF
+ def fatal(&block)
+ self.logger.fatal(&block) if self.logger
end
- def self.aliases
- %w(up)
+ def logger
+ @options[:logger]
end
end
end |
| |   |
| 1 | require File.dirname(__FILE__) + "/spec_helper" |
| 2 | require "piston/commands/import" |
| 3 | require "piston/commands/update" |
| 4 | |
| 5 | describe Piston::Commands::Update, "#run" do |
| 6 | it_should_behave_like "An upstream repository with no copies/renames" |
| 7 | |
| 8 | it "should find all pistonized folders and update them in turn" |
| 9 | end |
| 10 | |
| 11 | describe Piston::Commands::Update, "#run('vendor')" do |
| 12 | it_should_behave_like "An upstream repository with no copies/renames" |
| 13 | it_should_behave_like "A working copy against a local repository" |
| 14 | |
| 15 | before do |
| 16 | Piston::Commands::Import.new(@upstream.url, @wc.wc_path("vendor"), :revision => 1, :logger => logger).run |
| 17 | @wc.commit "Pistonized vendor@r1" |
| 18 | @vendor = @wc.down("vendor") |
| 19 | |
| 20 | @command = Piston::Commands::Update.new(@wc.wc_path("vendor"), :logger => logger) |
| 21 | end |
| 22 | |
| 23 | after do |
| 24 | @wc.destroy! |
| 25 | end |
| 26 | |
| 27 | it "should receive new folders" do |
| 28 | @upwc.mkdir("lib") |
| 29 | @upwc.create!("lib/calc.c", "int calc() { /* TBD */ }") |
| 30 | @upwc.commit |
| 31 | @command.run |
| 32 | status = @wc.status.map {|st| st.sub(@wc.path, "")} |
| 33 | status.sort.should == [ |
| 34 | " M /vendor", |
| 35 | "A /vendor/lib", |
| 36 | "A /vendor/lib/calc.c", |
| 37 | "A /vendor/LICENSE", |
| 38 | "A /vendor/README", |
| 39 | "M /vendor/main.c", |
| 40 | "M /vendor/CHANGELOG"].sort |
| 41 | end |
| 42 | end |
| 43 | |
| 44 | describe Piston::Commands::Update, "#run('vendor')" do |
| 45 | it_should_behave_like "An upstream repository with no copies/renames" |
| 46 | it_should_behave_like "A working copy against a local repository" |
| 47 | |
| 48 | before do |
| 49 | Piston::Commands::Import.new(@upstream.url, @wc.wc_path("vendor"), :revision => 1, :logger => logger).run |
| 50 | @wc.commit "Pistonized vendor@r1" |
| 51 | @vendor = @wc.down("vendor") |
| 52 | |
| 53 | @command = Piston::Commands::Update.new(@wc.wc_path("vendor"), :logger => logger) |
| 54 | end |
| 55 | |
| 56 | after do |
| 57 | @wc.destroy! |
| 58 | end |
| 59 | |
| 60 | it "should delete deleted files" do |
| 61 | @upwc.delete("CHANGELOG") |
| 62 | @upwc.commit |
| 63 | @command.run |
| 64 | status = @wc.status.map {|st| st.sub(@wc.path, "")} |
| 65 | status.should include("D /vendor/CHANGELOG") |
| 66 | end |
| 67 | end |
| 68 | |
| 69 | describe Piston::Commands::Update, "#run('vendor')" do |
| 70 | it_should_behave_like "An upstream repository with no copies/renames" |
| 71 | it_should_behave_like "A working copy against a local repository" |
| 72 | |
| 73 | before do |
| 74 | Piston::Commands::Import.new(@upstream.url, @wc.wc_path("vendor"), :revision => 1, :logger => logger).run |
| 75 | @wc.commit "Pistonized vendor@r1" |
| 76 | @vendor = @wc.down("vendor") |
| 77 | |
| 78 | @command = Piston::Commands::Update.new(@wc.wc_path("vendor"), :logger => logger) |
| 79 | end |
| 80 | |
| 81 | after do |
| 82 | @wc.destroy! |
| 83 | end |
| 84 | |
| 85 | it "should move files" do |
| 86 | @upwc.move("CHANGELOG", "Changelog.txt") |
| 87 | @upwc.commit |
| 88 | @command.run |
| 89 | status = @wc.status.map {|st| st.sub(@wc.path, "")} |
| 90 | status.should include("D /vendor/CHANGELOG") |
| 91 | status.should include("A + /vendor/Changelog.txt") |
| 92 | end |
| 93 | end |
| 94 | |
| 95 | describe Piston::Commands::Update, "#run('vendor')" do |
| 96 | it_should_behave_like "An upstream repository with no copies/renames" |
| 97 | it_should_behave_like "A working copy against a local repository" |
| 98 | |
| 99 | before do |
| 100 | Piston::Commands::Import.new(@upstream.url, @wc.wc_path("vendor"), :revision => 1, :logger => logger).run |
| 101 | @wc.commit "Pistonized vendor@r1" |
| 102 | @vendor = @wc.down("vendor") |
| 103 | |
| 104 | @command = Piston::Commands::Update.new(@wc.wc_path("vendor"), :logger => logger) |
| 105 | end |
| 106 | |
| 107 | after do |
| 108 | @wc.destroy! |
| 109 | end |
| 110 | |
| 111 | it "should copy files" do |
| 112 | @upwc.copy("CHANGELOG", "old-changelog.txt") |
| 113 | @upwc.commit |
| 114 | @command.run |
| 115 | status = @wc.status.map {|st| st.sub(@wc.path, "")} |
| 116 | status.should include("A + /vendor/old-changelog.txt") |
| 117 | end |
| 118 | end |
| 119 | |
| 120 | describe Piston::Commands::Update, "#run('vendor')" do |
| 121 | it_should_behave_like "An upstream repository with no copies/renames" |
| 122 | it_should_behave_like "A working copy against a local repository" |
| 123 | |
| 124 | before do |
| 125 | Piston::Commands::Import.new(@upstream.url, @wc.wc_path("vendor"), :revision => 1, :logger => logger).run |
| 126 | @wc.commit "Pistonized vendor@r1" |
| 127 | @vendor = @wc.down("vendor") |
| 128 | |
| 129 | @command = Piston::Commands::Update.new(@wc.wc_path("vendor"), :logger => logger) |
| 130 | end |
| 131 | |
| 132 | after do |
| 133 | @wc.destroy! |
| 134 | end |
| 135 | |
| 136 | it "should reject the update if the repository UUID is not as expected" do |
| 137 | @vendor.propset(Piston::REMOTE_UUID, "some-random-string") |
| 138 | lambda { @command.run }.should raise_error(Piston::RepositoryUuidChanged) |
| 139 | end |
| 140 | |
| 141 | it "should reject the update when the repository is gone" do |
| 142 | @upstream.destroy! |
| 143 | lambda { @command.run }.should raise_error(Piston::UnknownRepository) |
| 144 | end |
| 145 | end |
| 146 | |
| 147 | describe Piston::Commands::Update, "#run('vendor')" do |
| 148 | it_should_behave_like "An upstream repository with no copies/renames" |
| 149 | it_should_behave_like "A working copy against a local repository" |
| 150 | |
| 151 | before do |
| 152 | Piston::Commands::Import.new(@upstream.url, @wc.wc_path("vendor"), :revision => 1, :logger => logger).run |
| 153 | @wc.commit "Pistonized vendor@r1" |
| 154 | @vendor = @wc.down("vendor") |
| 155 | |
| 156 | @command = Piston::Commands::Update.new(@wc.wc_path("vendor"), :logger => logger) |
| 157 | @command.run |
| 158 | end |
| 159 | |
| 160 | after do |
| 161 | @wc.destroy! |
| 162 | end |
| 163 | |
| 164 | it "should receive vendor's HEAD changes" do |
| 165 | status = @wc.status |
| 166 | status.map! do |st| |
| 167 | st.sub(@wc.path, "") |
| 168 | end |
| 169 | status.sort.should == [ |
| 170 | " M /vendor", |
| 171 | "A /vendor/LICENSE", |
| 172 | "A /vendor/README", |
| 173 | "M /vendor/main.c", |
| 174 | "M /vendor/CHANGELOG"].sort |
| 175 | end |
| 176 | |
| 177 | it "should record new vendor's HEAD" do |
| 178 | @vendor.propget(Piston::REMOTE_REVISION).to_i.should == @upstream.youngest |
| 179 | end |
| 180 | |
| 181 | it "should record new local HEAD (unchanged, since no changes in local repos)" do |
| 182 | @vendor.propget(Piston::LOCAL_REVISION).to_i.should == 0 |
| 183 | end |
| 184 | end |
| 185 | |
| 186 | describe Piston::Commands::Update, "#run('vendor') when local modifications have been made" do |
| 187 | it_should_behave_like "An upstream repository with no copies/renames" |
| 188 | it_should_behave_like "A working copy against a local repository" |
| 189 | |
| 190 | before do |
| 191 | Piston::Commands::Import.new(@upstream.url, @wc.wc_path("vendor"), :revision => 1, :logger => logger).run |
| 192 | @wc.commit "Pistonized vendor@r1" |
| 193 | @vendor = @wc.down("vendor") |
| 194 | |
| 195 | @vendor.edit!("main.c", <<EOF |
| 196 | /** |
| 197 | * |
| 198 | * main.c: the main program file. |
| 199 | * |
| 200 | * This program does absolutely nothing. |
| 201 | * |
| 202 | */ |
| 203 | #include <stdio.h> |
| 204 | |
| 205 | int main(int argc, char** argv) { |
| 206 | return 0; |
| 207 | } |
| 208 | EOF |
| 209 | ) |
| 210 | @vendor.commit |
| 211 | @vendor.update |
| 212 | |
| 213 | @command = Piston::Commands::Update.new(@wc.wc_path("vendor"), :logger => logger) |
| 214 | @command.run |
| 215 | end |
| 216 | |
| 217 | it "should record new local HEAD" do |
| 218 | @vendor.propget(Piston::LOCAL_REVISION).to_i.should == 2 |
| 219 | end |
| 220 | |
| 221 | it "should merge local modifications" do |
| 222 | File.read(@vendor.wc_path("main.c")).should == <<EOF |
| 223 | /** |
| 224 | * |
| 225 | * main.c: the main program file. |
| 226 | * |
| 227 | * This program does absolutely nothing. |
| 228 | * |
| 229 | */ |
| 230 | #include <stdio.h> |
| 231 | |
| 232 | /** Main program file */ |
| 233 | int main(int argc, char** argv) { |
| 234 | return do_work(argc); |
| 235 | } |
| 236 | |
| 237 | int do_work(int argc) { |
| 238 | return 2*argc; |
| 239 | } |
| 240 | EOF |
| 241 | end |
| 242 | end |
| toggle raw diff |
--- /dev/null
+++ b/spec/update_spec.rb
@@ -0,0 +1,242 @@
+require File.dirname(__FILE__) + "/spec_helper"
+require "piston/commands/import"
+require "piston/commands/update"
+
+describe Piston::Commands::Update, "#run" do
+ it_should_behave_like "An upstream repository with no copies/renames"
+
+ it "should find all pistonized folders and update them in turn"
+end
+
+describe Piston::Commands::Update, "#run('vendor')" do
+ it_should_behave_like "An upstream repository with no copies/renames"
+ it_should_behave_like "A working copy against a local repository"
+
+ before do
+ Piston::Commands::Import.new(@upstream.url, @wc.wc_path("vendor"), :revision => 1, :logger => logger).run
+ @wc.commit "Pistonized vendor@r1"
+ @vendor = @wc.down("vendor")
+
+ @command = Piston::Commands::Update.new(@wc.wc_path("vendor"), :logger => logger)
+ end
+
+ after do
+ @wc.destroy!
+ end
+
+ it "should receive new folders" do
+ @upwc.mkdir("lib")
+ @upwc.create!("lib/calc.c", "int calc() { /* TBD */ }")
+ @upwc.commit
+ @command.run
+ status = @wc.status.map {|st| st.sub(@wc.path, "")}
+ status.sort.should == [
+ " M /vendor",
+ "A /vendor/lib",
+ "A /vendor/lib/calc.c",
+ "A /vendor/LICENSE",
+ "A /vendor/README",
+ "M /vendor/main.c",
+ "M /vendor/CHANGELOG"].sort
+ end
+end
+
+describe Piston::Commands::Update, "#run('vendor')" do
+ it_should_behave_like "An upstream repository with no copies/renames"
+ it_should_behave_like "A working copy against a local repository"
+
+ before do
+ Piston::Commands::Import.new(@upstream.url, @wc.wc_path("vendor"), :revision => 1, :logger => logger).run
+ @wc.commit "Pistonized vendor@r1"
+ @vendor = @wc.down("vendor")
+
+ @command = Piston::Commands::Update.new(@wc.wc_path("vendor"), :logger => logger)
+ end
+
+ after do
+ @wc.destroy!
+ end
+
+ it "should delete deleted files" do
+ @upwc.delete("CHANGELOG")
+ @upwc.commit
+ @command.run
+ status = @wc.status.map {|st| st.sub(@wc.path, "")}
+ status.should include("D /vendor/CHANGELOG")
+ end
+end
+
+describe Piston::Commands::Update, "#run('vendor')" do
+ it_should_behave_like "An upstream repository with no copies/renames"
+ it_should_behave_like "A working copy against a local repository"
+
+ before do
+ Piston::Commands::Import.new(@upstream.url, @wc.wc_path("vendor"), :revision => 1, :logger => logger).run
+ @wc.commit "Pistonized vendor@r1"
+ @vendor = @wc.down("vendor")
+
+ @command = Piston::Commands::Update.new(@wc.wc_path("vendor"), :logger => logger)
+ end
+
+ after do
+ @wc.destroy!
+ end
+
+ it "should move files" do
+ @upwc.move("CHANGELOG", "Changelog.txt")
+ @upwc.commit
+ @command.run
+ status = @wc.status.map {|st| st.sub(@wc.path, "")}
+ status.should include("D /vendor/CHANGELOG")
+ status.should include("A + /vendor/Changelog.txt")
+ end
+end
+
+describe Piston::Commands::Update, "#run('vendor')" do
+ it_should_behave_like "An upstream repository with no copies/renames"
+ it_should_behave_like "A working copy against a local repository"
+
+ before do
+ Piston::Commands::Import.new(@upstream.url, @wc.wc_path("vendor"), :revision => 1, :logger => logger).run
+ @wc.commit "Pistonized vendor@r1"
+ @vendor = @wc.down("vendor")
+
+ @command = Piston::Commands::Update.new(@wc.wc_path("vendor"), :logger => logger)
+ end
+
+ after do
+ @wc.destroy!
+ end
+
+ it "should copy files" do
+ @upwc.copy("CHANGELOG", "old-changelog.txt")
+ @upwc.commit
+ @command.run
+ status = @wc.status.map {|st| st.sub(@wc.path, "")}
+ status.should include("A + /vendor/old-changelog.txt")
+ end
+end
+
+describe Piston::Commands::Update, "#run('vendor')" do
+ it_should_behave_like "An upstream repository with no copies/renames"
+ it_should_behave_like "A working copy against a local repository"
+
+ before do
+ Piston::Commands::Import.new(@upstream.url, @wc.wc_path("vendor"), :revision => 1, :logger => logger).run
+ @wc.commit "Pistonized vendor@r1"
+ @vendor = @wc.down("vendor")
+
+ @command = Piston::Commands::Update.new(@wc.wc_path("vendor"), :logger => logger)
+ end
+
+ after do
+ @wc.destroy!
+ end
+
+ it "should reject the update if the repository UUID is not as expected" do
+ @vendor.propset(Piston::REMOTE_UUID, "some-random-string")
+ lambda { @command.run }.should raise_error(Piston::RepositoryUuidChanged)
+ end
+
+ it "should reject the update when the repository is gone" do
+ @upstream.destroy!
+ lambda { @command.run }.should raise_error(Piston::UnknownRepository)
+ end
+end
+
+describe Piston::Commands::Update, "#run('vendor')" do
+ it_should_behave_like "An upstream repository with no copies/renames"
+ it_should_behave_like "A working copy against a local repository"
+
+ before do
+ Piston::Commands::Import.new(@upstream.url, @wc.wc_path("vendor"), :revision => 1, :logger => logger).run
+ @wc.commit "Pistonized vendor@r1"
+ @vendor = @wc.down("vendor")
+
+ @command = Piston::Commands::Update.new(@wc.wc_path("vendor"), :logger => logger)
+ @command.run
+ end
+
+ after do
+ @wc.destroy!
+ end
+
+ it "should receive vendor's HEAD changes" do
+ status = @wc.status
+ status.map! do |st|
+ st.sub(@wc.path, "")
+ end
+ status.sort.should == [
+ " M /vendor",
+ "A /vendor/LICENSE",
+ "A /vendor/README",
+ "M /vendor/main.c",
+ "M /vendor/CHANGELOG"].sort
+ end
+
+ it "should record new vendor's HEAD" do
+ @vendor.propget(Piston::REMOTE_REVISION).to_i.should == @upstream.youngest
+ end
+
+ it "should record new local HEAD (unchanged, since no changes in local repos)" do
+ @vendor.propget(Piston::LOCAL_REVISION).to_i.should == 0
+ end
+end
+
+describe Piston::Commands::Update, "#run('vendor') when local modifications have been made" do
+ it_should_behave_like "An upstream repository with no copies/renames"
+ it_should_behave_like "A working copy against a local repository"
+
+ before do
+ Piston::Commands::Import.new(@upstream.url, @wc.wc_path("vendor"), :revision => 1, :logger => logger).run
+ @wc.commit "Pistonized vendor@r1"
+ @vendor = @wc.down("vendor")
+
+ @vendor.edit!("main.c", <<EOF
+/**
+ *
+ * main.c: the main program file.
+ *
+ * This program does absolutely nothing.
+ *
+ */
+#include <stdio.h>
+
+int main(int argc, char** argv) {
+ return 0;
+}
+EOF
+)
+ @vendor.commit
+ @vendor.update
+
+ @command = Piston::Commands::Update.new(@wc.wc_path("vendor"), :logger => logger)
+ @command.run
+ end
+
+ it "should record new local HEAD" do
+ @vendor.propget(Piston::LOCAL_REVISION).to_i.should == 2
+ end
+
+ it "should merge local modifications" do
+ File.read(@vendor.wc_path("main.c")).should == <<EOF
+/**
+ *
+ * main.c: the main program file.
+ *
+ * This program does absolutely nothing.
+ *
+ */
+#include <stdio.h>
+
+/** Main program file */
+int main(int argc, char** argv) {
+ return do_work(argc);
+}
+
+int do_work(int argc) {
+ return 2*argc;
+}
+EOF
+ end
+end |