require "English" require "svn/error" require "svn/util" require "svn/core" require "svn/ext/delta" module Svn module Delta Util.set_constants(Ext::Delta, self) Util.set_methods(Ext::Delta, self) class << self alias _path_driver path_driver end alias _path_driver path_driver module_function def svndiff_handler(output, version=nil) args = [output, version || 0] handler, handler_baton = Delta.txdelta_to_svndiff2_wrapper(*args) handler.baton = handler_baton handler end def read_svndiff_window(stream, version) Delta.txdelta_read_svndiff_window(stream, version) end def skip_svndiff_window(file, version) Delta.txdelta_skip_svndiff_window(file, version) end def path_driver(editor, revision, paths, &callback_func) Delta._path_driver(editor, revision, paths, callback_func) end def send(string_or_stream, handler=nil) if handler.nil? and block_given? handler = Proc.new {|window| yield(window)} Delta.setup_handler_wrapper(handler) end if string_or_stream.is_a?(TextDeltaStream) string_or_stream.send(handler) elsif string_or_stream.is_a?(String) Delta.txdelta_send_string(string_or_stream, handler) else Delta.txdelta_send_stream(string_or_stream, handler) end end def apply(source, target, error_info=nil) result = Delta.txdelta_apply_wrapper(source, target, error_info) digest, handler, handler_baton = result handler.baton = handler_baton [handler, digest] end def parse_svndiff(error_on_early_close=true, &handler) Delta.txdelta_parse_svndiff(handler, error_on_early_close) end def setup_handler_wrapper(wrapper) Proc.new do |window| Delta.txdelta_invoke_window_handler_wrapper(wrapper, window) end end TextDeltaStream = SWIG::TYPE_p_svn_txdelta_stream_t class TextDeltaStream class << self def new(source, target) Delta.txdelta(source, target) end def push_target(source, &handler) Delta.txdelta_target_push(handler, source) end end def md5_digest Delta.txdelta_md5_digest_as_cstring(self) end def next_window Delta.txdelta_next_window(self, Svn::Core::Pool.new) end def each while window = next_window yield(window) end end def send(handler=nil) if handler.nil? and block_given? handler = Proc.new {|window| yield(window)} Delta.setup_handler_wrapper(handler) end Delta.txdelta_send_txstream(self, handler) end end TextDeltaWindow = TxdeltaWindow class TextDeltaWindow def compose(other_window) Delta.txdelta_compose_windows(other_window, self) end def ops Delta.txdelta_window_t_ops_get(self) end def apply_instructions(source_buffer) Delta.swig_rb_txdelta_apply_instructions(self, source_buffer) end end TextDeltaWindowHandler = SWIG::TYPE_p_f_p_svn_txdelta_window_t_p_void__p_svn_error_t class TextDeltaWindowHandler attr_accessor :baton def call(window) Delta.txdelta_invoke_window_handler(self, window) end def send(string_or_stream) if string_or_stream.is_a?(TextDeltaStream) Delta.txdelta_send_txstream(string_or_stream, self, @baton) elsif string_or_stream.is_a?(String) Delta.txdelta_send_string(string_or_stream, self, @baton) else Delta.txdelta_send_stream(string_or_stream, self, @baton) end end end class Editor %w(set_target_revision open_root delete_entry add_directory open_directory change_dir_prop close_directory absent_directory add_file open_file apply_textdelta change_file_prop close_file absent_file close_edit abort_edit).each do |name| alias_method("_#{name}", name) alias_method("_#{name}=", "#{name}=") end attr_accessor :baton def set_target_revision(target_revision) args = [self, @baton, target_revision] Svn::Delta.editor_invoke_set_target_revision(*args) end def open_root(base_revision) args = [self, @baton, base_revision] Svn::Delta.editor_invoke_open_root(*args) end def delete_entry(path, revision, parent_baton) args = [self, path, revision, parent_baton] Svn::Delta.editor_invoke_delete_entry(*args) end def add_directory(path, parent_baton, copyfrom_path, copyfrom_revision) args = [self, path, parent_baton, copyfrom_path, copyfrom_revision] Svn::Delta.editor_invoke_add_directory(*args) end def open_directory(path, parent_baton, base_revision) args = [self, path, parent_baton, base_revision] Svn::Delta.editor_invoke_open_directory(*args) end def change_dir_prop(dir_baton, name, value) args = [self, dir_baton, name, value] Svn::Delta.editor_invoke_change_dir_prop(*args) end def close_directory(dir_baton) args = [self, dir_baton] Svn::Delta.editor_invoke_close_directory(*args) end def absent_directory(path, parent_baton) args = [self, path, parent_baton] Svn::Delta.editor_invoke_absent_directory(*args) end def add_file(path, parent_baton, copyfrom_path, copyfrom_revision) args = [self, path, parent_baton, copyfrom_path, copyfrom_revision] Svn::Delta.editor_invoke_add_file(*args) end def open_file(path, parent_baton, base_revision) args = [self, path, parent_baton, base_revision] Svn::Delta.editor_invoke_open_file(*args) end def apply_textdelta(file_baton, base_checksum) args = [self, file_baton, base_checksum] handler, handler_baton = Svn::Delta.editor_invoke_apply_textdelta(*args) handler.baton = handler_baton handler end def change_file_prop(file_baton, name, value) args = [self, file_baton, name, value] Svn::Delta.editor_invoke_change_file_prop(*args) end def close_file(file_baton, text_checksum) args = [self, file_baton, text_checksum] Svn::Delta.editor_invoke_close_file(*args) end def absent_file(path, parent_baton) args = [self, path, parent_baton] Svn::Delta.editor_invoke_absent_file(*args) end def close_edit args = [self, @baton] Svn::Delta.editor_invoke_close_edit(*args) end def abort_edit args = [self, @baton] Svn::Delta.editor_invoke_abort_edit(*args) end end class BaseEditor # open_root -> add_directory -> open_directory -> add_file -> open_file def set_target_revision(target_revision) end def open_root(base_revision) end def delete_entry(path, revision, parent_baton) end def add_directory(path, parent_baton, copyfrom_path, copyfrom_revision) end def open_directory(path, parent_baton, base_revision) end def change_dir_prop(dir_baton, name, value) end def close_directory(dir_baton) end def absent_directory(path, parent_baton) end def add_file(path, parent_baton, copyfrom_path, copyfrom_revision) end def open_file(path, parent_baton, base_revision) end # return nil or object which has `call' method. def apply_textdelta(file_baton, base_checksum) end def change_file_prop(file_baton, name, value) end def close_file(file_baton, text_checksum) end def absent_file(path, parent_baton) end def close_edit(baton) end def abort_edit(baton) end end class CopyDetectableEditor < BaseEditor def add_directory(path, parent_baton, copyfrom_path, copyfrom_revision) end def add_file(path, parent_baton, copyfrom_path, copyfrom_revision) end end class ChangedDirsEditor < BaseEditor attr_reader :changed_dirs def initialize @changed_dirs = [] end def open_root(base_revision) [true, ''] end def delete_entry(path, revision, parent_baton) dir_changed(parent_baton) end def add_directory(path, parent_baton, copyfrom_path, copyfrom_revision) dir_changed(parent_baton) [true, path] end def open_directory(path, parent_baton, base_revision) [true, path] end def change_dir_prop(dir_baton, name, value) dir_changed(dir_baton) end def add_file(path, parent_baton, copyfrom_path, copyfrom_revision) dir_changed(parent_baton) end def open_file(path, parent_baton, base_revision) dir_changed(parent_baton) end def close_edit(baton) @changed_dirs.sort! end private def dir_changed(baton) if baton[0] # the directory hasn't been printed yet. do it. @changed_dirs << "#{baton[1]}/" baton[0] = nil end end end class ChangedEditor < BaseEditor attr_reader :copied_files, :copied_dirs attr_reader :added_files, :added_dirs attr_reader :deleted_files, :deleted_dirs attr_reader :updated_files, :updated_dirs def initialize(root, base_root) @root = root @base_root = base_root @in_copied_dir = [] @copied_files = [] @copied_dirs = [] @added_files = [] @added_dirs = [] @deleted_files = [] @deleted_dirs = [] @updated_files = [] @updated_dirs = [] end def open_root(base_revision) [true, ''] end def delete_entry(path, revision, parent_baton) if @base_root.dir?("/#{path}") @deleted_dirs << "#{path}/" else @deleted_files << path end end def add_directory(path, parent_baton, copyfrom_path, copyfrom_revision) copyfrom_rev, copyfrom_path = @root.copied_from(path) if in_copied_dir? @in_copied_dir.push(true) elsif Util.copy?(copyfrom_path, copyfrom_rev) @copied_dirs << ["#{path}/", "#{copyfrom_path.sub(/^\//, '')}/", copyfrom_rev] @in_copied_dir.push(true) else @added_dirs << "#{path}/" @in_copied_dir.push(false) end [false, path] end def open_directory(path, parent_baton, base_revision) [true, path] end def change_dir_prop(dir_baton, name, value) if dir_baton[0] @updated_dirs << "#{dir_baton[1]}/" dir_baton[0] = false end dir_baton end def close_directory(dir_baton) unless dir_baton[0] @in_copied_dir.pop end end def add_file(path, parent_baton, copyfrom_path, copyfrom_revision) copyfrom_rev, copyfrom_path = @root.copied_from(path) if in_copied_dir? # do nothing elsif Util.copy?(copyfrom_path, copyfrom_rev) @copied_files << [path, copyfrom_path.sub(/^\//, ''), copyfrom_rev] else @added_files << path end [nil, nil, nil] end def open_file(path, parent_baton, base_revision) [nil, nil, path] end def apply_textdelta(file_baton, base_checksum) file_baton[0] = :update nil end def change_file_prop(file_baton, name, value) file_baton[1] = :update end def close_file(file_baton, text_checksum) text_mod, prop_mod, path = file_baton # test the path. it will be nil if we added this file. if path if [text_mod, prop_mod] != [nil, nil] @updated_files << path end end end def close_edit(baton) @copied_files.sort! {|a, b| a[0] <=> b[0]} @copied_dirs.sort! {|a, b| a[0] <=> b[0]} @added_files.sort! @added_dirs.sort! @deleted_files.sort! @deleted_dirs.sort! @updated_files.sort! @updated_dirs.sort! end private def in_copied_dir? @in_copied_dir.last end end end end