Index: app/helpers/repositories_helper.rb =================================================================== --- app/helpers/repositories_helper.rb (revision 1438) +++ app/helpers/repositories_helper.rb (working copy) @@ -75,6 +75,14 @@ :onchange => "this.name='repository[password]';")) end + def visual_source_safe_field_tags(form, repository) + content_tag('p', form.text_field(:url, :size => 60, :required => true, :disabled => (repository && !repository.root_url.blank?)) + + '
(*/srcsafe.ini)') + + content_tag('p', form.text_field(:login, :size => 30)) + + content_tag('p', form.password_field(:password, :size => 30)) + #content_tag('p', select_tag('vss_lang', "")) + end + def darcs_field_tags(form, repository) content_tag('p', form.text_field(:url, :label => 'Root directory', :size => 60, :required => true, :disabled => (repository && !repository.new_record?))) end Index: app/models/repository/visual_source_safe.rb =================================================================== --- app/models/repository/visual_source_safe.rb (revision 0) +++ app/models/repository/visual_source_safe.rb (revision 0) @@ -0,0 +1,144 @@ +# redMine - project management software +# Copyright (C) 2006-2007 Jean-Philippe Lang +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + +require 'redmine/scm/adapters/visual_source_safe_adapter' +require 'redmine/scm/adapters/abstract_adapter' + +class Repository::VisualSourceSafe < Repository + attr_protected :root_url + validates_presence_of :url + validates_format_of :url, :with => /^.*srcsafe\.ini/i + + def scm_adapter + Redmine::Scm::Adapters::VisualSourceSafeAdapter + end + + def self.scm_name + 'VisualSourceSafe' + end + + def fetch_changesets + #use like CVS + + STDERR.puts "fetch_changeset start!" + + #not the preferred way with CVS. maybe we should introduce always a cron-job for this + last_commit = changesets.maximum(:committed_on) + + # some nifty bits to introduce a commit-id with cvs + # natively cvs doesn't provide any kind of changesets, there is only a revision per file. + # we now take a guess using the author, the commitlog and the commit-date. + + # last one is the next step to take. the commit-date is not equal for all + # commits in one changeset. cvs update the commit-date when the *,v file was touched. so + # we use a small delta here, to merge all changes belonging to _one_ changeset + #time_delta=10.seconds + + time_delta=10.seconds + fetch_since = latest_changeset ? latest_changeset.committed_on : nil + revisions = nil + transaction do + revisions = scm.revisions('', fetch_since, nil, :with_paths => true) + STDERR.puts "fetch_changeset revisions.size = #{revisions.size}" + STDERR.puts "changesets.size = #{changesets.size}" + tmp_rev_num = changesets.size + 1 + revisions.each do |revision| + # only add the change to the database, if it doen't exists. the cvs log + # is not exclusive at all. + cs1 = changes.find_by_path_and_revision(scm.with_leading_slash(revision.paths[0][:path]), revision.paths[0][:revision]) + unless cs1 + revision + cs=Changeset.find(:first, :conditions=>{ + :committed_on=>revision.time-time_delta..revision.time+time_delta, + :committer=>revision.author, + :comments=>revision.message + }) + + # create a new changeset.... + unless cs + # we use a temporaray revision number here (just for inserting) + # later on, we calculate a continous positive number + latest = changesets.find(:first, :order => 'id DESC') + cs = Changeset.create(:repository => self, + :revision => "_#{tmp_rev_num}", + :committer => revision.author, + :committed_on => revision.time, + :comments => revision.message) + tmp_rev_num += 1 + STDERR.puts "Create ChangeSet" + end + + #convert CVS-File-States to internal Action-abbrevations + #default action is (M)odified + action="M" + act = revision.paths[0][:action] + #need check only 'Added' or 'Created' or 'Deleted' + if /^Added/ =~ act or /^Created/ =~ act or /^Recovered/ =~ act or /^Shared/ =~ act + action="A" + elsif /^Deleted/ =~ act + action="D" + end + Change.create(:changeset => cs, + :action => action, + :path => scm.with_leading_slash(revision.paths[0][:path]), + :revision => revision.paths[0][:revision], + :branch => revision.paths[0][:branch] + ) + end + end + + c = changesets.find(:first, :order => 'committed_on DESC, id DESC', :conditions => "revision NOT LIKE '_%'") + next_rev = c.nil? ? 1 : (c.revision.to_i + 1) + changesets.find(:all, :order => 'committed_on ASC, id ASC', :conditions => "revision LIKE '_%'").each do |changeset| + changeset.update_attribute :revision, next_rev + next_rev += 1 + end + end + STDERR.puts "fetch_changeset end!" + end + + def diff(path, rev, rev_to, type2) + #convert rev to revision. CVS can't handle changesets here + diffx = Redmine::Scm::Adapters::DiffTableList.new([], type2) + changeset_from=changesets.find_by_revision(rev) + if rev_to.to_i > 0 + changeset_to=changesets.find_by_revision(rev_to) + end + changeset_from.changes.each() do |change_from| + + revision_from=nil + revision_to=nil + + revision_from=change_from.revision if path.nil? || (change_from.path.starts_with? scm.with_leading_slash(path)) + + if revision_from + if changeset_to + changeset_to.changes.each() do |change_to| + revision_to=change_to.revision if change_to.path==change_from.path + end + end + unless revision_to + revision_to=scm.get_previous_revision(change_from.path, revision_from) + end + diff2 = scm.diff(change_from.path, revision_from, revision_to, type2) + diffx=diff2 if diff2 + end + end + return diffx + end + +end Index: lib/redmine.rb =================================================================== --- lib/redmine.rb (revision 1438) +++ lib/redmine.rb (working copy) @@ -11,7 +11,7 @@ # RMagick is not available end -REDMINE_SUPPORTED_SCM = %w( Subversion Darcs Mercurial Cvs Bazaar Git ) +REDMINE_SUPPORTED_SCM = %w( Subversion Darcs Mercurial Cvs Bazaar Git VisualSourceSafe) # Permissions Redmine::AccessControl.map do |map| Index: lib/redmine/scm/adapters/visual_source_safe_adapter.rb =================================================================== --- lib/redmine/scm/adapters/visual_source_safe_adapter.rb (revision 0) +++ lib/redmine/scm/adapters/visual_source_safe_adapter.rb (revision 0) @@ -0,0 +1,2311 @@ +# redMine - project management software +# Copyright (C) 2006-2007 Jean-Philippe Lang +# Visual Source Safe modules: Aruo Miura +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + +require 'redmine/scm/adapters/abstract_adapter' +require 'rexml/document' +require 'win32ole' +require 'tempfile' +#require 'nkf' + +#============copy of diff-lcs, only change names. Diff -> DiffX + +#lcs.rb + +module DiffX + # = Diff::LCS 1.1.2 + # Computes "intelligent" differences between two sequenced Enumerables. + # This is an implementation of the McIlroy-Hunt "diff" algorithm for + # Enumerable objects that include Diffable. + # + # Based on Mario I. Wolczko's Smalltalk version + # (1.2, 1993) and Ned Konz's Perl version + # (Algorithm::Diff). + # + # == Synopsis + # require 'diff/lcs' + # + # seq1 = %w(a b c e h j l m n p) + # seq2 = %w(b c d e f j k l m r s t) + # + # lcs = Diff::LCS.LCS(seq1, seq2) + # diffs = Diff::LCS.diff(seq1, seq2) + # sdiff = Diff::LCS.sdiff(seq1, seq2) + # seq = Diff::LCS.traverse_sequences(seq1, seq2, callback_obj) + # bal = Diff::LCS.traverse_balanced(seq1, seq2, callback_obj) + # seq2 == Diff::LCS.patch(seq1, diffs) + # seq2 == Diff::LCS.patch!(seq1, diffs) + # seq1 == Diff::LCS.unpatch(seq2, diffs) + # seq1 == Diff::LCS.unpatch!(seq2, diffs) + # seq2 == Diff::LCS.patch(seq1, sdiff) + # seq2 == Diff::LCS.patch!(seq1, sdiff) + # seq1 == Diff::LCS.unpatch(seq2, sdiff) + # seq1 == Diff::LCS.unpatch!(seq2, sdiff) + # + # Alternatively, objects can be extended with Diff::LCS: + # + # seq1.extend(Diff::LCS) + # lcs = seq1.lcs(seq2) + # diffs = seq1.diff(seq2) + # sdiff = seq1.sdiff(seq2) + # seq = seq1.traverse_sequences(seq2, callback_obj) + # bal = seq1.traverse_balanced(seq2, callback_obj) + # seq2 == seq1.patch(diffs) + # seq2 == seq1.patch!(diffs) + # seq1 == seq2.unpatch(diffs) + # seq1 == seq2.unpatch!(diffs) + # seq2 == seq1.patch(sdiff) + # seq2 == seq1.patch!(sdiff) + # seq1 == seq2.unpatch(sdiff) + # seq1 == seq2.unpatch!(sdiff) + # + # Default extensions are provided for Array and String objects through + # the use of 'diff/lcs/array' and 'diff/lcs/string'. + # + # == Introduction (by Mark-Jason Dominus) + # + # The following text is from the Perl documentation. The only + # changes have been to make the text appear better in Rdoc. + # + # I once read an article written by the authors of +diff+; they said + # that they hard worked very hard on the algorithm until they found the + # right one. + # + # I think what they ended up using (and I hope someone will correct me, + # because I am not very confident about this) was the `longest common + # subsequence' method. In the LCS problem, you have two sequences of + # items: + # + # a b c d f g h j q z + # a b c d e f g i j k r x y z + # + # and you want to find the longest sequence of items that is present in + # both original sequences in the same order. That is, you want to find a + # new sequence *S* which can be obtained from the first sequence by + # deleting some items, and from the second sequence by deleting other + # items. You also want *S* to be as long as possible. In this case *S* + # is: + # + # a b c d f g j z + # + # From there it's only a small step to get diff-like output: + # + # e h i k q r x y + # + - + + - + + + + # + # This module solves the LCS problem. It also includes a canned function + # to generate +diff+-like output. + # + # It might seem from the example above that the LCS of two sequences is + # always pretty obvious, but that's not always the case, especially when + # the two sequences have many repeated elements. For example, consider + # + # a x b y c z p d q + # a b c a x b y c z + # + # A naive approach might start by matching up the +a+ and +b+ that + # appear at the beginning of each sequence, like this: + # + # a x b y c z p d q + # a b c a b y c z + # + # This finds the common subsequence +a b c z+. But actually, the LCS is + # +a x b y c z+: + # + # a x b y c z p d q + # a b c a x b y c z + # + # == Author + # This version is by Austin Ziegler . + # + # It is based on the Perl Algorithm::Diff by Ned Konz + # , copyright © 2000 - 2002 and the Smalltalk + # diff version by Mario I. Wolczko , copyright © + # 1993. Documentation includes work by Mark-Jason Dominus. + # + # == Licence + # Copyright © 2004 Austin Ziegler + # This program is free software; you can redistribute it and/or modify it + # under the same terms as Ruby, or alternatively under the Perl Artistic + # licence. + # + # == Credits + # Much of the documentation is taken directly from the Perl + # Algorithm::Diff implementation and was written originally by Mark-Jason + # Dominus and later by Ned Konz. The basic Ruby + # implementation was re-ported from the Smalltalk implementation, available + # at ftp://st.cs.uiuc.edu/pub/Smalltalk/MANCHESTER/manchester/4.0/diff.st + # + # #sdiff and #traverse_balanced were written for the Perl version by Mike + # Schilli . + # + # "The algorithm is described in A Fast Algorithm for Computing Longest + # Common Subsequences, CACM, vol.20, no.5, pp.350-353, May 1977, with + # a few minor improvements to improve the speed." + module LCS + VERSION = '1.1.2' + end +end + +module DiffX::LCS + # Returns an Array containing the longest common subsequence(s) between + # +self+ and +other+. See Diff::LCS#LCS. + # + # lcs = seq1.lcs(seq2) + def lcs(other, &block) #:yields self[ii] if there are matched subsequences: + DiffX::LCS.LCS(self, other, &block) + end + + # Returns the difference set between +self+ and +other+. See + # Diff::LCS#diff. + def diff(other, callbacks = nil, &block) + DiffX::LCS::diff(self, other, callbacks, &block) + end + + # Returns the balanced ("side-by-side") difference set between +self+ and + # +other+. See Diff::LCS#sdiff. + def sdiff(other, callbacks = nil, &block) + DiffX::LCS::sdiff(self, other, callbacks, &block) + end + + # Traverses the discovered longest common subsequences between +self+ and + # +other+. See Diff::LCS#traverse_sequences. + def traverse_sequences(other, callbacks = nil, &block) + traverse_sequences(self, other, callbacks || DiffX::LCS::YieldingCallbacks, + &block) + end + + # Traverses the discovered longest common subsequences between +self+ and + # +other+ using the alternate, balanced algorithm. See + # Diff::LCS#traverse_balanced. + def traverse_balanced(other, callbacks = nil, &block) + traverse_balanced(self, other, callbacks || DiffX::LCS::YieldingCallbacks, + &block) + end + + # Attempts to patch a copy of +self+ with the provided +patchset+. See + # Diff::LCS#patch. + def patch(patchset) + DiffX::LCS::patch(self.dup, patchset) + end + + # Attempts to unpatch a copy of +self+ with the provided +patchset+. + # See Diff::LCS#patch. + def unpatch(patchset) + DiffX::LCS::unpatch(self.dup, patchset) + end + + # Attempts to patch +self+ with the provided +patchset+. See + # Diff::LCS#patch!. Does no autodiscovery. + def patch!(patchset) + DiffX::LCS::patch!(self, patchset) + end + + # Attempts to unpatch +self+ with the provided +patchset+. See + # Diff::LCS#unpatch. Does no autodiscovery. + def unpatch!(patchset) + DiffX::LCS::unpatch!(self, patchset) + end +end + +module DiffX::LCS + class << self + # Given two sequenced Enumerables, LCS returns an Array containing their + # longest common subsequences. + # + # lcs = Diff::LCS.LCS(seq1, seq2) + # + # This array whose contents is such that: + # + # lcs.each_with_index do |ee, ii| + # assert(ee.nil? || (seq1[ii] == seq2[ee])) + # end + # + # If a block is provided, the matching subsequences will be yielded from + # +seq1+ in turn and may be modified before they are placed into the + # returned Array of subsequences. + def LCS(seq1, seq2, &block) #:yields seq1[ii] for each matched: + matches = DiffX::LCS.__lcs(seq1, seq2) + ret = [] + matches.each_with_index do |ee, ii| + unless matches[ii].nil? + if block_given? + ret << (yield seq1[ii]) + else + ret << seq1[ii] + end + end + end + ret + end + + # Diff::LCS.diff computes the smallest set of additions and deletions + # necessary to turn the first sequence into the second, and returns a + # description of these changes. + # + # See Diff::LCS::DiffCallbacks for the default behaviour. An alternate + # behaviour may be implemented with Diff::LCS::ContextDiffCallbacks. + # If a Class argument is provided for +callbacks+, #diff will attempt + # to initialise it. If the +callbacks+ object (possibly initialised) + # responds to #finish, it will be called. + def diff(seq1, seq2, callbacks = nil, &block) # :yields diff changes: + callbacks ||= DiffX::LCS::DiffCallbacks + if callbacks.kind_of?(Class) + cb = callbacks.new rescue callbacks + callbacks = cb + end + traverse_sequences(seq1, seq2, callbacks) + callbacks.finish if callbacks.respond_to?(:finish) + + if block_given? + res = callbacks.diffs.map do |hunk| + if hunk.kind_of?(Array) + hunk = hunk.map { |block| yield block } + else + yield hunk + end + end + res + else + callbacks.diffs + end + end + + # Diff::LCS.sdiff computes all necessary components to show two sequences + # and their minimized differences side by side, just like the Unix + # utility sdiff does: + # + # old < - + # same same + # before | after + # - > new + # + # See Diff::LCS::SDiffCallbacks for the default behaviour. An alternate + # behaviour may be implemented with Diff::LCS::ContextDiffCallbacks. If + # a Class argument is provided for +callbacks+, #diff will attempt to + # initialise it. If the +callbacks+ object (possibly initialised) + # responds to #finish, it will be called. + def sdiff(seq1, seq2, callbacks = nil, &block) #:yields diff changes: + callbacks ||= DiffX::LCS::SDiffCallbacks + if callbacks.kind_of?(Class) + cb = callbacks.new rescue callbacks + callbacks = cb + end + traverse_balanced(seq1, seq2, callbacks) + callbacks.finish if callbacks.respond_to?(:finish) + + if block_given? + res = callbacks.diffs.map do |hunk| + if hunk.kind_of?(Array) + hunk = hunk.map { |block| yield block } + else + yield hunk + end + end + res + else + callbacks.diffs + end + end + + # Diff::LCS.traverse_sequences is the most general facility provided by this + # module; +diff+ and +LCS+ are implemented as calls to it. + # + # The arguments to #traverse_sequences are the two sequences to + # traverse, and a callback object, like this: + # + # traverse_sequences(seq1, seq2, Diff::LCS::ContextDiffCallbacks.new) + # + # #diff is implemented with #traverse_sequences. + # + # == Callback Methods + # Optional callback methods are emphasized. + # + # callbacks#match:: Called when +a+ and +b+ are pointing + # to common elements in +A+ and +B+. + # callbacks#discard_a:: Called when +a+ is pointing to an + # element not in +B+. + # callbacks#discard_b:: Called when +b+ is pointing to an + # element not in +A+. + # callbacks#finished_a:: Called when +a+ has reached the end of + # sequence +A+. + # callbacks#finished_b:: Called when +b+ has reached the end of + # sequence +B+. + # + # == Algorithm + # a---+ + # v + # A = a b c e h j l m n p + # B = b c d e f j k l m r s t + # ^ + # b---+ + # + # If there are two arrows (+a+ and +b+) pointing to elements of + # sequences +A+ and +B+, the arrows will initially point to the first + # elements of their respective sequences. #traverse_sequences will + # advance the arrows through the sequences one element at a time, + # calling a method on the user-specified callback object before each + # advance. It will advance the arrows in such a way that if there are + # elements A[ii] and B[jj] which are both equal and + # part of the longest common subsequence, there will be some moment + # during the execution of #traverse_sequences when arrow +a+ is pointing + # to A[ii] and arrow +b+ is pointing to B[jj]. When + # this happens, #traverse_sequences will call callbacks#match + # and then it will advance both arrows. + # + # Otherwise, one of the arrows is pointing to an element of its sequence + # that is not part of the longest common subsequence. + # #traverse_sequences will advance that arrow and will call + # callbacks#discard_a or callbacks#discard_b, depending + # on which arrow it advanced. If both arrows point to elements that are + # not part of the longest common subsequence, then #traverse_sequences + # will advance one of them and call the appropriate callback, but it is + # not specified which it will call. + # + # The methods for callbacks#match, callbacks#discard_a, + # and callbacks#discard_b are invoked with an event comprising + # the action ("=", "+", or "-", respectively), the indicies +ii+ and + # +jj+, and the elements A[ii] and B[jj]. Return + # values are discarded by #traverse_sequences. + # + # === End of Sequences + # If arrow +a+ reaches the end of its sequence before arrow +b+ does, + # #traverse_sequence try to call callbacks#finished_a with the + # last index and element of +A+ (A[-1]) and the current index + # and element of +B+ (B[jj]). If callbacks#finished_a + # does not exist, then callbacks#discard_b will be called on + # each element of +B+ until the end of the sequence is reached (the call + # will be done with A[-1] and B[jj] for each element). + # + # If +b+ reaches the end of +B+ before +a+ reaches the end of +A+, + # callbacks#finished_b will be called with the current index + # and element of +A+ (A[ii]) and the last index and element of + # +B+ (A[-1]). Again, if callbacks#finished_b does not + # exist on the callback object, then callbacks#discard_a will + # be called on each element of +A+ until the end of the sequence is + # reached (A[ii] and B[-1]). + # + # There is a chance that one additional callbacks#discard_a or + # callbacks#discard_b will be called after the end of the + # sequence is reached, if +a+ has not yet reached the end of +A+ or +b+ + # has not yet reached the end of +B+. + def traverse_sequences(seq1, seq2, callbacks = DiffX::LCS::SequenceCallbacks, &block) #:yields change events: + matches = DiffX::LCS.__lcs(seq1, seq2) + + run_finished_a = run_finished_b = false + string = seq1.kind_of?(String) + + a_size = seq1.size + b_size = seq2.size + ai = bj = 0 + + (0 .. matches.size).each do |ii| + b_line = matches[ii] + + ax = string ? seq1[ii, 1] : seq1[ii] + bx = string ? seq2[bj, 1] : seq2[bj] + + if b_line.nil? + unless ax.nil? + event = DiffX::LCS::ContextChange.new('-', ii, ax, bj, bx) + event = yield event if block_given? + callbacks.discard_a(event) + end + else + loop do + break unless bj < b_line + bx = string ? seq2[bj, 1] : seq2[bj] + event = DiffX::LCS::ContextChange.new('+', ii, ax, bj, bx) + event = yield event if block_given? + callbacks.discard_b(event) + bj += 1 + end + bx = string ? seq2[bj, 1] : seq2[bj] + event = DiffX::LCS::ContextChange.new('=', ii, ax, bj, bx) + event = yield event if block_given? + callbacks.match(event) + bj += 1 + end + ai = ii + end + ai += 1 + + # The last entry (if any) processed was a match. +ai+ and +bj+ point + # just past the last matching lines in their sequences. + while (ai < a_size) or (bj < b_size) + # last A? + if ai == a_size and bj < b_size + if callbacks.respond_to?(:finished_a) and not run_finished_a + ax = string ? seq1[-1, 1] : seq1[-1] + bx = string ? seq2[bj, 1] : seq2[bj] + event = DiffX::LCS::ContextChange.new('>', (a_size - 1), ax, bj, bx) + event = yield event if block_given? + callbacks.finished_a(event) + run_finished_a = true + else + ax = string ? seq1[ai, 1] : seq1[ai] + loop do + bx = string ? seq2[bj, 1] : seq2[bj] + event = DiffX::LCS::ContextChange.new('+', ai, ax, bj, bx) + event = yield event if block_given? + callbacks.discard_b(event) + bj += 1 + break unless bj < b_size + end + end + end + + # last B? + if bj == b_size and ai < a_size + if callbacks.respond_to?(:finished_b) and not run_finished_b + ax = string ? seq1[ai, 1] : seq1[ai] + bx = string ? seq2[-1, 1] : seq2[-1] + event = DiffX::LCS::ContextChange.new('<', ai, ax, (b_size - 1), bx) + event = yield event if block_given? + callbacks.finished_b(event) + run_finished_b = true + else + bx = string ? seq2[bj, 1] : seq2[bj] + loop do + ax = string ? seq1[ai, 1] : seq1[ai] + event = DiffX::LCS::ContextChange.new('-', ai, ax, bj, bx) + event = yield event if block_given? + callbacks.discard_a(event) + ai += 1 + break unless bj < b_size + end + end + end + + if ai < a_size + ax = string ? seq1[ai, 1] : seq1[ai] + bx = string ? seq2[bj, 1] : seq2[bj] + event = DiffX::LCS::ContextChange.new('-', ai, ax, bj, bx) + event = yield event if block_given? + callbacks.discard_a(event) + ai += 1 + end + + if bj < b_size + ax = string ? seq1[ai, 1] : seq1[ai] + bx = string ? seq2[bj, 1] : seq2[bj] + event = DiffX::LCS::ContextChange.new('+', ai, ax, bj, bx) + event = yield event if block_given? + callbacks.discard_b(event) + bj += 1 + end + end + end + + # #traverse_balanced is an alternative to #traverse_sequences. It + # uses a different algorithm to iterate through the entries in the + # computed longest common subsequence. Instead of viewing the changes as + # insertions or deletions from one of the sequences, #traverse_balanced + # will report changes between the sequences. To represent a + # + # The arguments to #traverse_balanced are the two sequences to traverse + # and a callback object, like this: + # + # traverse_balanced(seq1, seq2, Diff::LCS::ContextDiffCallbacks.new) + # + # #sdiff is implemented with #traverse_balanced. + # + # == Callback Methods + # Optional callback methods are emphasized. + # + # callbacks#match:: Called when +a+ and +b+ are pointing + # to common elements in +A+ and +B+. + # callbacks#discard_a:: Called when +a+ is pointing to an + # element not in +B+. + # callbacks#discard_b:: Called when +b+ is pointing to an + # element not in +A+. + # callbacks#change:: Called when +a+ and +b+ are pointing + # to the same relative position, but + # A[a] and B[b] are + # not the same; a change has + # occurred. + # + # #traverse_balanced might be a bit slower than #traverse_sequences, + # noticable only while processing huge amounts of data. + # + # The +sdiff+ function of this module is implemented as call to + # #traverse_balanced. + # + # == Algorithm + # a---+ + # v + # A = a b c e h j l m n p + # B = b c d e f j k l m r s t + # ^ + # b---+ + # + # === Matches + # If there are two arrows (+a+ and +b+) pointing to elements of + # sequences +A+ and +B+, the arrows will initially point to the first + # elements of their respective sequences. #traverse_sequences will + # advance the arrows through the sequences one element at a time, + # calling a method on the user-specified callback object before each + # advance. It will advance the arrows in such a way that if there are + # elements A[ii] and B[jj] which are both equal and + # part of the longest common subsequence, there will be some moment + # during the execution of #traverse_sequences when arrow +a+ is pointing + # to A[ii] and arrow +b+ is pointing to B[jj]. When + # this happens, #traverse_sequences will call callbacks#match + # and then it will advance both arrows. + # + # === Discards + # Otherwise, one of the arrows is pointing to an element of its sequence + # that is not part of the longest common subsequence. + # #traverse_sequences will advance that arrow and will call + # callbacks#discard_a or callbacks#discard_b, + # depending on which arrow it advanced. + # + # === Changes + # If both +a+ and +b+ point to elements that are not part of the longest + # common subsequence, then #traverse_sequences will try to call + # callbacks#change and advance both arrows. If + # callbacks#change is not implemented, then + # callbacks#discard_a and callbacks#discard_b will be + # called in turn. + # + # The methods for callbacks#match, callbacks#discard_a, + # callbacks#discard_b, and callbacks#change are + # invoked with an event comprising the action ("=", "+", "-", or "!", + # respectively), the indicies +ii+ and +jj+, and the elements + # A[ii] and B[jj]. Return values are discarded by + # #traverse_balanced. + # + # === Context + # Note that +ii+ and +jj+ may not be the same index position, even if + # +a+ and +b+ are considered to be pointing to matching or changed + # elements. + def traverse_balanced(seq1, seq2, callbacks = DiffX::LCS::BalancedCallbacks) + matches = DiffX::LCS.__lcs(seq1, seq2) + a_size = seq1.size + b_size = seq2.size + ai = bj = mb = 0 + ma = -1 + string = seq1.kind_of?(String) + + # Process all the lines in the match vector. + loop do + # Find next match indices +ma+ and +mb+ + loop do + ma += 1 + break unless ma < matches.size and matches[ma].nil? + end + + break if ma >= matches.size # end of matches? + mb = matches[ma] + + # Change(seq2) + while (ai < ma) or (bj < mb) + ax = string ? seq1[ai, 1] : seq1[ai] + bx = string ? seq2[bj, 1] : seq2[bj] + + case [(ai < ma), (bj < mb)] + when [true, true] + if callbacks.respond_to?(:change) + event = DiffX::LCS::ContextChange.new('!', ai, ax, bj, bx) + event = yield event if block_given? + callbacks.change(event) + ai += 1 + bj += 1 + else + event = DiffX::LCS::ContextChange.new('-', ai, ax, bj, bx) + event = yield event if block_given? + callbacks.discard_a(event) + ai += 1 + ax = string ? seq1[ai, 1] : seq1[ai] + event = DiffX::LCS::ContextChange.new('+', ai, ax, bj, bx) + event = yield event if block_given? + callbacks.discard_b(event) + bj += 1 + end + when [true, false] + event = DiffX::LCS::ContextChange.new('-', ai, ax, bj, bx) + event = yield event if block_given? + callbacks.discard_a(event) + ai += 1 + when [false, true] + event = DiffX::LCS::ContextChange.new('+', ai, ax, bj, bx) + event = yield event if block_given? + callbacks.discard_b(event) + bj += 1 + end + end + + # Match + ax = string ? seq1[ai, 1] : seq1[ai] + bx = string ? seq2[bj, 1] : seq2[bj] + event = DiffX::LCS::ContextChange.new('=', ai, ax, bj, bx) + event = yield event if block_given? + callbacks.match(event) + ai += 1 + bj += 1 + end + + while (ai < a_size) or (bj < b_size) + ax = string ? seq1[ai, 1] : seq1[ai] + bx = string ? seq2[bj, 1] : seq2[bj] + + case [(ai < a_size), (bj < b_size)] + when [true, true] + if callbacks.respond_to?(:change) + event = DiffX::LCS::ContextChange.new('!', ai, ax, bj, bx) + event = yield event if block_given? + callbacks.change(event) + ai += 1 + bj += 1 + else + event = DiffX::LCS::ContextChange.new('-', ai, ax, bj, bx) + event = yield event if block_given? + callbacks.discard_a(event) + ai += 1 + ax = string ? seq1[ai, 1] : seq1[ai] + event = DiffX::LCS::ContextChange.new('+', ai, ax, bj, bx) + event = yield event if block_given? + callbacks.discard_b(event) + bj += 1 + end + when [true, false] + event = DiffX::LCS::ContextChange.new('-', ai, ax, bj, bx) + event = yield event if block_given? + callbacks.discard_a(event) + ai += 1 + when [false, true] + event = DiffX::LCS::ContextChange.new('+', ai, ax, bj, bx) + event = yield event if block_given? + callbacks.discard_b(event) + bj += 1 + end + end + end + + PATCH_MAP = { #:nodoc: + :patch => { '+' => '+', '-' => '-', '!' => '!', '=' => '=' }, + :unpatch => { '+' => '-', '-' => '+', '!' => '!', '=' => '=' } + } + + # Given a patchset, convert the current version to the new + # version. If +direction+ is not specified (must be + # :patch or :unpatch), then discovery of the + # direction of the patch will be attempted. + def patch(src, patchset, direction = nil) + string = src.kind_of?(String) + # Start with a new empty type of the source's class + res = src.class.new + + # Normalize the patchset. + patchset = __normalize_patchset(patchset) + + direction ||= DiffX::LCS.__diff_direction(src, patchset) + direction ||= :patch + + ai = bj = 0 + + patchset.each do |change| + # Both Change and ContextChange support #action + action = PATCH_MAP[direction][change.action] + + case change + when DiffX::LCS::ContextChange + case direction + when :patch + el = change.new_element + op = change.old_position + np = change.new_position + when :unpatch + el = change.old_element + op = change.new_position + np = change.old_position + end + + case action + when '-' # Remove details from the old string + while ai < op + res << (string ? src[ai, 1] : src[ai]) + ai += 1 + bj += 1 + end + ai += 1 + when '+' + while bj < np + res << (string ? src[ai, 1] : src[ai]) + ai += 1 + bj += 1 + end + + res << el + bj += 1 + when '=' + # This only appears in sdiff output with the SDiff callback. + # Therefore, we only need to worry about dealing with a single + # element. + res << el + + ai += 1 + bj += 1 + when '!' + while ai < op + res << (string ? src[ai, 1] : src[ai]) + ai += 1 + bj += 1 + end + + bj += 1 + ai += 1 + + res << el + end + when DiffX::LCS::Change + case action + when '-' + while ai < change.position + res << (string ? src[ai, 1] : src[ai]) + ai += 1 + bj += 1 + end + ai += 1 + when '+' + while bj < change.position + res << (string ? src[ai, 1] : src[ai]) + ai += 1 + bj += 1 + end + + bj += 1 + + res << change.element + end + end + end + + while ai < src.size + res << (string ? src[ai, 1] : src[ai]) + ai += 1 + bj += 1 + end + + res + end + + # Given a set of patchset, convert the current version to the prior + # version. Does no auto-discovery. + def unpatch!(src, patchset) + DiffX::LCS.patch(src, patchset, :unpatch) + end + + # Given a set of patchset, convert the current version to the next + # version. Does no auto-discovery. + def patch!(src, patchset) + DiffX::LCS.patch(src, patchset, :patch) + end + +# private + # Compute the longest common subsequence between the sequenced Enumerables + # +a+ and +b+. The result is an array whose contents is such that + # + # result = Diff::LCS.__lcs(a, b) + # result.each_with_index do |e, ii| + # assert_equal(a[ii], b[e]) unless e.nil? + # end + def __lcs(a, b) + a_start = b_start = 0 + a_finish = a.size - 1 + b_finish = b.size - 1 + vector = [] + + # Prune off any common elements at the beginning... + while (a_start <= a_finish) and + (b_start <= b_finish) and + (a[a_start] == b[b_start]) + vector[a_start] = b_start + a_start += 1 + b_start += 1 + end + + # Now the end... + while (a_start <= a_finish) and + (b_start <= b_finish) and + (a[a_finish] == b[b_finish]) + vector[a_finish] = b_finish + a_finish -= 1 + b_finish -= 1 + end + + # Now, compute the equivalence classes of positions of elements. + b_matches = DiffX::LCS.__position_hash(b, b_start .. b_finish) + + thresh = [] + links = [] + + (a_start .. a_finish).each do |ii| + ai = a.kind_of?(String) ? a[ii, 1] : a[ii] + bm = b_matches[ai] + kk = nil + bm.reverse_each do |jj| + if kk and (thresh[kk] > jj) and (thresh[kk - 1] < jj) + thresh[kk] = jj + else + kk = DiffX::LCS.__replace_next_larger(thresh, jj, kk) + end + links[kk] = [ (kk > 0) ? links[kk - 1] : nil, ii, jj ] unless kk.nil? + end + end + + unless thresh.empty? + link = links[thresh.size - 1] + while not link.nil? + vector[link[1]] = link[2] + link = link[0] + end + end + + vector + end + + # Find the place at which +value+ would normally be inserted into the + # Enumerable. If that place is already occupied by +value+, do nothing + # and return +nil+. If the place does not exist (i.e., it is off the end + # of the Enumerable), add it to the end. Otherwise, replace the element + # at that point with +value+. It is assumed that the Enumerable's values + # are numeric. + # + # This operation preserves the sort order. + def __replace_next_larger(enum, value, last_index = nil) + # Off the end? + if enum.empty? or (value > enum[-1]) + enum << value + return enum.size - 1 + end + + # Binary search for the insertion point + last_index ||= enum.size + first_index = 0 + while (first_index <= last_index) + ii = (first_index + last_index) >> 1 + + found = enum[ii] + + if value == found + return nil + elsif value > found + first_index = ii + 1 + else + last_index = ii - 1 + end + end + + # The insertion point is in first_index; overwrite the next larger + # value. + enum[first_index] = value + return first_index + end + + # If +vector+ maps the matching elements of another collection onto this + # Enumerable, compute the inverse +vector+ that maps this Enumerable + # onto the collection. (Currently unused.) + def __inverse_vector(a, vector) + inverse = a.dup + (0 ... vector.size).each do |ii| + inverse[vector[ii]] = ii unless vector[ii].nil? + end + inverse + end + + # Returns a hash mapping each element of an Enumerable to the set of + # positions it occupies in the Enumerable, optionally restricted to the + # elements specified in the range of indexes specified by +interval+. + def __position_hash(enum, interval = 0 .. -1) + hash = Hash.new { |hh, kk| hh[kk] = [] } + interval.each do |ii| + kk = enum.kind_of?(String) ? enum[ii, 1] : enum[ii] + hash[kk] << ii + end + hash + end + + # Examine the patchset and the source to see in which direction the + # patch should be applied. + # + # WARNING: By default, this examines the whole patch, so this could take + # some time. This also works better with Diff::LCS::ContextChange or + # Diff::LCS::Change as its source, as an array will cause the creation + # of one of the above. + def __diff_direction(src, patchset, limit = nil) + count = left = left_miss = right = right_miss = 0 + string = src.kind_of?(String) + + patchset.each do |change| + count += 1 + + case change + when DiffX::LCS::Change + # With a simplistic change, we can't tell the difference between + # the left and right on '!' actions, so we ignore those. On '=' + # actions, if there's a miss, we miss both left and right. + element = string ? src[change.position, 1] : src[change.position] + + case change.action + when '-' + if element == change.element + left += 1 + else + left_miss += 1 + end + when '+' + if element == change.element + right += 1 + else + right_miss += 1 + end + when '=' + if element != change.element + left_miss += 1 + right_miss += 1 + end + end + when DiffX::LCS::ContextChange + case change.action + when '-' # Remove details from the old string + element = string ? src[change.old_position, 1] : src[change.old_position] + if element == change.old_element + left += 1 + else + left_miss += 1 + end + when '+' + element = string ? src[change.new_position, 1] : src[change.new_position] + if element == change.new_element + right += 1 + else + right_miss += 1 + end + when '=' + le = string ? src[change.old_position, 1] : src[change.old_position] + re = string ? src[change.new_position, 1] : src[change.new_position] + + left_miss += 1 if le != change.old_element + right_miss += 1 if re != change.new_element + when '!' + element = string ? src[change.old_position, 1] : src[change.old_position] + if element == change.old_element + left += 1 + else + element = string ? src[change.new_position, 1] : src[change.new_position] + if element == change.new_element + right += 1 + else + left_miss += 1 + right_miss += 1 + end + end + end + end + + break if not limit.nil? and count > limit + end + + no_left = (left == 0) and (left_miss >= 0) + no_right = (right == 0) and (right_miss >= 0) + + case [no_left, no_right] + when [false, true] + return :patch + when [true, false] + return :unpatch + else + raise "The provided patchset does not appear to apply to the provided value as either source or destination value." + end + end + + # Normalize the patchset. A patchset is always a sequence of changes, but + # how those changes are represented may vary, depending on how they were + # generated. In all cases we support, we also support the array + # representation of the changes. The formats are: + # + # [ # patchset <- Diff::LCS.diff(a, b) + # [ # one or more hunks + # Diff::LCS::Change # one or more changes + # ] ] + # + # [ # patchset, equivalent to the above + # [ # one or more hunks + # [ action, line, value ] # one or more changes + # ] ] + # + # [ # patchset <- Diff::LCS.diff(a, b, Diff::LCS::ContextDiffCallbacks) + # # OR <- Diff::LCS.sdiff(a, b, Diff::LCS::ContextDiffCallbacks) + # [ # one or more hunks + # Diff::LCS::ContextChange # one or more changes + # ] ] + # + # [ # patchset, equivalent to the above + # [ # one or more hunks + # [ action, [ old line, old value ], [ new line, new value ] ] + # # one or more changes + # ] ] + # + # [ # patchset <- Diff::LCS.sdiff(a, b) + # # OR <- Diff::LCS.diff(a, b, Diff::LCS::SDiffCallbacks) + # Diff::LCS::ContextChange # one or more changes + # ] + # + # [ # patchset, equivalent to the above + # [ action, [ old line, old value ], [ new line, new value ] ] + # # one or more changes + # ] + # + # The result of this will be either of the following. + # + # [ # patchset + # Diff::LCS::ContextChange # one or more changes + # ] + # + # [ # patchset + # Diff::LCS::Change # one or more changes + # ] + # + # If either of the above is provided, it will be returned as such. + # + def __normalize_patchset(patchset) + patchset.map do |hunk| + case hunk + when DiffX::LCS::ContextChange, DiffX::LCS::Change + hunk + when Array + if (not hunk[0].kind_of?(Array)) and hunk[1].kind_of?(Array) and hunk[2].kind_of?(Array) + DiffX::LCS::ContextChange.from_a(hunk) + else + hunk.map do |change| + case change + when DiffX::LCS::ContextChange, DiffX::LCS::Change + change + when Array + # change[1] will ONLY be an array in a ContextChange#to_a call. + # In Change#to_a, it represents the line (singular). + if change[1].kind_of?(Array) + DiffX::LCS::ContextChange.from_a(change) + else + DiffX::LCS::Change.from_a(change) + end + end + end + end + else + raise ArgumentError, "Cannot normalise a hunk of class #{hunk.class}." + end + end.flatten + end + end +end + +#blocks.rb +class DiffX::LCS::Block + attr_reader :changes, :insert, :remove + + def initialize(chunk) + @changes = [] + @insert = [] + @remove = [] + + chunk.each do |item| + @changes << item + @remove << item if item.deleting? + @insert << item if item.adding? + end + end + + def diff_size + @insert.size - @remove.size + end + + def op + case [@remove.empty?, @insert.empty?] + when [false, false] + '!' + when [false, true] + '-' + when [true, false] + '+' + else # [true, true] + '^' + end + end +end + +#hunk.rb +class DiffX::LCS::Hunk + # Create a hunk using references to both the old and new data, as well as + # the piece of data + def initialize(data_old, data_new, piece, context, file_length_difference) + # At first, a hunk will have just one Block in it + @blocks = [ DiffX::LCS::Block.new(piece) ] + @data_old = data_old + @data_new = data_new + + before = after = file_length_difference + after += @blocks[0].diff_size + @file_length_difference = after # The caller must get this manually + + # Save the start & end of each array. If the array doesn't exist + # (e.g., we're only adding items in this block), then figure out the + # line number based on the line number of the other file and the + # current difference in file lengths. + if @blocks[0].remove.empty? + a1 = a2 = nil + else + a1 = @blocks[0].remove[0].position + a2 = @blocks[0].remove[-1].position + end + + if @blocks[0].insert.empty? + b1 = b2 = nil + else + b1 = @blocks[0].insert[0].position + b2 = @blocks[0].insert[-1].position + end + + @start_old = a1 || (b1 - before) + @start_new = b1 || (a1 + before) + @end_old = a2 || (b2 - after) + @end_new = b2 || (a2 + after) + + self.flag_context = context + end + + attr_reader :blocks + attr_reader :start_old, :start_new + attr_reader :end_old, :end_new + attr_reader :file_length_difference + + # Change the "start" and "end" fields to note that context should be added + # to this hunk + attr_accessor :flag_context + def flag_context=(context) #:nodoc: + return if context.nil? or context.zero? + + add_start = (context > @start_old) ? @start_old : context + @start_old -= add_start + @start_new -= add_start + + if (@end_old + context) > @data_old.size + add_end = @data_old.size - @end_old + else + add_end = context + end + @end_old += add_end + @end_new += add_end + end + + def unshift(hunk) + @start_old = hunk.start_old + @start_new = hunk.start_new + blocks.unshift(*hunk.blocks) + end + + # Is there an overlap between hunk arg0 and old hunk arg1? Note: if end + # of old hunk is one less than beginning of second, they overlap + def overlaps?(hunk = nil) + return nil if hunk.nil? + + a = (@start_old - hunk.end_old) <= 1 + b = (@start_new - hunk.end_new) <= 1 + return (a or b) + end + + def diff(format) + case format + when :old + old_diff + when :unified + unified_diff + when :context + context_diff + when :ed + self + when :reverse_ed, :ed_finish + ed_diff(format) + else + raise "Unknown diff format #{format}." + end + end + + def each_old(block) + @data_old[@start_old .. @end_old].each { |e| yield e } + end + + private + # Note that an old diff can't have any context. Therefore, we know that + # there's only one block in the hunk. + def old_diff + warn "Expecting only one block in an old diff hunk!" if @blocks.size > 1 + op_act = { "+" => 'a', "-" => 'd', "!" => "c" } + + block = @blocks[0] + + # Calculate item number range. Old diff range is just like a context + # diff range, except the ranges are on one line with the action between + # them. + s = "#{context_range(:old)}#{op_act[block.op]}#{context_range(:new)}\n" + # If removing anything, just print out all the remove lines in the hunk + # which is just all the remove lines in the block. + @data_old[@start_old .. @end_old].each { |e| s << "< #{e}\n" } unless block.remove.empty? + s << "---\n" if block.op == "!" + @data_new[@start_new .. @end_new].each { |e| s << "> #{e}\n" } unless block.insert.empty? + s + end + + def unified_diff + # Calculate item number range. + s = "@@ -#{unified_range(:old)} +#{unified_range(:new)} @@\n" + + # Outlist starts containing the hunk of the old file. Removing an item + # just means putting a '-' in front of it. Inserting an item requires + # getting it from the new file and splicing it in. We splice in + # +num_added+ items. Remove blocks use +num_added+ because splicing + # changed the length of outlist. + # + # We remove +num_removed+ items. Insert blocks use +num_removed+ + # because their item numbers -- corresponding to positions in the NEW + # file -- don't take removed items into account. + lo, hi, num_added, num_removed = @start_old, @end_old, 0, 0 + + outlist = @data_old[lo .. hi].collect { |e| e.gsub(/^/, ' ') } + + @blocks.each do |block| + block.remove.each do |item| + op = item.action.to_s # - + offset = item.position - lo + num_added + outlist[offset].gsub!(/^ /, op.to_s) + num_removed += 1 + end + block.insert.each do |item| + op = item.action.to_s # + + offset = item.position - @start_new + num_removed + outlist[offset, 0] = "#{op}#{@data_new[item.position]}" + num_added += 1 + end + end + + s << outlist.join("\n") + end + + def context_diff + s = "***************\n" + s << "*** #{context_range(:old)} ****\n" + r = context_range(:new) + + # Print out file 1 part for each block in context diff format if there + # are any blocks that remove items + lo, hi = @start_old, @end_old + removes = @blocks.select { |e| not e.remove.empty? } + if removes + outlist = @data_old[lo .. hi].collect { |e| e.gsub(/^/, ' ') } + removes.each do |block| + block.remove.each do |item| + outlist[item.position - lo].gsub!(/^ /) { block.op } # - or ! + end + end + s << outlist.join("\n") + end + + s << "\n--- #{r} ----\n" + lo, hi = @start_new, @end_new + inserts = @blocks.select { |e| not e.insert.empty? } + if inserts + outlist = @data_new[lo .. hi].collect { |e| e.gsub(/^/, ' ') } + inserts.each do |block| + block.insert.each do |item| + outlist[item.position - lo].gsub!(/^ /) { block.op } # + or ! + end + end + s << outlist.join("\n") + end + s + end + + def ed_diff(format) + op_act = { "+" => 'a', "-" => 'd', "!" => "c" } + warn "Expecting only one block in an old diff hunk!" if @blocks.size > 1 + + if format == :reverse_ed + s = "#{op_act[@blocks[0].op]}#{context_range(:old)}\n" + else + s = "#{context_range(:old).gsub(/,/, ' ')}#{op_act[@blocks[0].op]}\n" + end + + unless @blocks[0].insert.empty? + @data_new[@start_new .. @end_new].each { |e| s << "#{e}\n" } + s << ".\n" + end + s + end + + # Generate a range of item numbers to print. Only print 1 number if the + # range has only one item in it. Otherwise, it's 'start,end' + def context_range(mode) + case mode + when :old + s, e = (@start_old + 1), (@end_old + 1) + when :new + s, e = (@start_new + 1), (@end_new + 1) + end + + (s < e) ? "#{s},#{e}" : "#{e}" + end + + # Generate a range of item numbers to print for unified diff. Print + # number where block starts, followed by number of lines in the block + # (don't print number of lines if it's 1) + def unified_range(mode) + case mode + when :old + s, e = (@start_old + 1), (@end_old + 1) + when :new + s, e = (@start_new + 1), (@end_new + 1) + end + + length = e - s + 1 + first = (length < 2) ? e : s # "strange, but correct" + (length == 1) ? "#{first}" : "#{first},#{length}" + end +end + +#change.rb + +module DiffX::LCS::ChangeTypeTests + def deleting? + @action == '-' + end + + def adding? + @action == '+' + end + + def unchanged? + @action == '=' + end + + def changed? + @changed == '!' + end + + def finished_a? + @changed == '>' + end + + def finished_b? + @changed == '<' + end +end + + # Represents a simplistic (non-contextual) change. Represents the removal or + # addition of an element from either the old or the new sequenced enumerable. +class DiffX::LCS::Change + # Returns the action this Change represents. Can be '+' (#adding?), '-' + # (#deleting?), '=' (#unchanged?), # or '!' (#changed?). When created by + # Diff::LCS#diff or Diff::LCS#sdiff, it may also be '>' (#finished_a?) or + # '<' (#finished_b?). + attr_reader :action + attr_reader :position + attr_reader :element + + include Comparable + def ==(other) + (self.action == other.action) and + (self.position == other.position) and + (self.element == other.element) + end + + def <=>(other) + r = self.action <=> other.action + r = self.position <=> other.position if r.zero? + r = self.element <=> other.element if r.zero? + r + end + + def initialize(action, position, element) + @action = action + @position = position + @element = element + end + + # Creates a Change from an array produced by Change#to_a. + def to_a + [@action, @position, @element] + end + + def self.from_a(arr) + DiffX::LCS::Change.new(arr[0], arr[1], arr[2]) + end + + include DiffX::LCS::ChangeTypeTests +end + + # Represents a contextual change. Contains the position and values of the + # elements in the old and the new sequenced enumerables as well as the action + # taken. +class DiffX::LCS::ContextChange + # Returns the action this Change represents. Can be '+' (#adding?), '-' + # (#deleting?), '=' (#unchanged?), # or '!' (#changed?). When + # created by Diff::LCS#diff or Diff::LCS#sdiff, it may also be '>' + # (#finished_a?) or '<' (#finished_b?). + attr_reader :action + attr_reader :old_position + attr_reader :old_element + attr_reader :new_position + attr_reader :new_element + + include Comparable + + def ==(other) + (@action == other.action) and + (@old_position == other.old_position) and + (@new_position == other.new_position) and + (@old_element == other.old_element) and + (@new_element == other.new_element) + end + + def inspect(*args) + %Q(#<#{self.class.name}:#{__id__} @action=#{action} positions=#{old_position},#{new_position} elements=#{old_element.inspect},#{new_element.inspect}>) + end + + def <=>(other) + r = @action <=> other.action + r = @old_position <=> other.old_position if r.zero? + r = @new_position <=> other.new_position if r.zero? + r = @old_element <=> other.old_element if r.zero? + r = @new_element <=> other.new_element if r.zero? + r + end + + def initialize(action, old_position, old_element, new_position, new_element) + @action = action + @old_position = old_position + @old_element = old_element + @new_position = new_position + @new_element = new_element + end + + def to_a + [@action, [@old_position, @old_element], [@new_position, @new_element]] + end + + # Creates a ContextChange from an array produced by ContextChange#to_a. + def self.from_a(arr) + if arr.size == 5 + DiffX::LCS::ContextChange.new(arr[0], arr[1], arr[2], arr[3], arr[4]) + else + DiffX::LCS::ContextChange.new(arr[0], arr[1][0], arr[1][1], arr[2][0], + arr[2][1]) + end + end + + # Simplifies a context change for use in some diff callbacks. '<' actions + # are converted to '-' and '>' actions are converted to '+'. + def self.simplify(event) + ea = event.to_a + + case ea[0] + when '-' + ea[2][1] = nil + when '<' + ea[0] = '-' + ea[2][1] = nil + when '+' + ea[1][1] = nil + when '>' + ea[0] = '+' + ea[1][1] = nil + end + + DiffX::LCS::ContextChange.from_a(ea) + end + + include DiffX::LCS::ChangeTypeTests +end + +#callbacks.rb + +module DiffX::LCS + # This callback object implements the default set of callback events, which + # only returns the event itself. Note that #finished_a and #finished_b are + # not implemented -- I haven't yet figured out where they would be useful. + # + # Note that this is intended to be called as is, e.g., + # + # Diff::LCS.LCS(seq1, seq2, Diff::LCS::DefaultCallbacks) + class DefaultCallbacks + class << self + # Called when two items match. + def match(event) + event + end + # Called when the old value is discarded in favour of the new value. + def discard_a(event) + event + end + # Called when the new value is discarded in favour of the old value. + def discard_b(event) + event + end + # Called when both the old and new values have changed. + def change(event) + event + end + + private :new + end + end + + # An alias for DefaultCallbacks that is used in Diff::LCS#traverse_sequences. + # + # Diff::LCS.LCS(seq1, seq2, Diff::LCS::SequenceCallbacks) + SequenceCallbacks = DefaultCallbacks + # An alias for DefaultCallbacks that is used in Diff::LCS#traverse_balanced. + # + # Diff::LCS.LCS(seq1, seq2, Diff::LCS::BalancedCallbacks) + BalancedCallbacks = DefaultCallbacks +end + + # This will produce a compound array of simple diff change objects. Each + # element in the #diffs array is a +hunk+ or +hunk+ array, where each + # element in each +hunk+ array is a single Change object representing the + # addition or removal of a single element from one of the two tested + # sequences. The +hunk+ provides the full context for the changes. + # + # diffs = Diff::LCS.diff(seq1, seq2) + # # This example shows a simplified array format. + # # [ [ [ '-', 0, 'a' ] ], # 1 + # # [ [ '+', 2, 'd' ] ], # 2 + # # [ [ '-', 4, 'h' ], # 3 + # # [ '+', 4, 'f' ] ], + # # [ [ '+', 6, 'k' ] ], # 4 + # # [ [ '-', 8, 'n' ], # 5 + # # [ '-', 9, 'p' ], + # # [ '+', 9, 'r' ], + # # [ '+', 10, 's' ], + # # [ '+', 11, 't' ] ] ] + # + # There are five hunks here. The first hunk says that the +a+ at position 0 + # of the first sequence should be deleted ('-'). The second hunk + # says that the +d+ at position 2 of the second sequence should be inserted + # ('+'). The third hunk says that the +h+ at position 4 of the + # first sequence should be removed and replaced with the +f+ from position 4 + # of the second sequence. The other two hunks are described similarly. + # + # === Use + # This callback object must be initialised and is used by the Diff::LCS#diff + # method. + # + # cbo = Diff::LCS::DiffCallbacks.new + # Diff::LCS.LCS(seq1, seq2, cbo) + # cbo.finish + # + # Note that the call to #finish is absolutely necessary, or the last set of + # changes will not be visible. Alternatively, can be used as: + # + # cbo = Diff::LCS::DiffCallbacks.new { |tcbo| Diff::LCS.LCS(seq1, seq2, tcbo) } + # + # The necessary #finish call will be made. + # + # === Simplified Array Format + # The simplified array format used in the example above can be obtained + # with: + # + # require 'pp' + # pp diffs.map { |e| e.map { |f| f.to_a } } +class DiffX::LCS::DiffCallbacks + # Returns the difference set collected during the diff process. + attr_reader :diffs + + def initialize # :yields self: + @hunk = [] + @diffs = [] + + if block_given? + begin + yield self + ensure + self.finish + end + end + end + + # Finalizes the diff process. If an unprocessed hunk still exists, then it + # is appended to the diff list. + def finish + add_nonempty_hunk + end + + def match(event) + add_nonempty_hunk + end + + def discard_a(event) + @hunk << DiffX::LCS::Change.new('-', event.old_position, event.old_element) + end + + def discard_b(event) + @hunk << DiffX::LCS::Change.new('+', event.new_position, event.new_element) + end + +private + def add_nonempty_hunk + @diffs << @hunk unless @hunk.empty? + @hunk = [] + end +end + + # This will produce a compound array of contextual diff change objects. Each + # element in the #diffs array is a "hunk" array, where each element in each + # "hunk" array is a single change. Each change is a Diff::LCS::ContextChange + # that contains both the old index and new index values for the change. The + # "hunk" provides the full context for the changes. Both old and new objects + # will be presented for changed objects. +nil+ will be substituted for a + # discarded object. + # + # seq1 = %w(a b c e h j l m n p) + # seq2 = %w(b c d e f j k l m r s t) + # + # diffs = Diff::LCS.diff(seq1, seq2, Diff::LCS::ContextDiffCallbacks) + # # This example shows a simplified array format. + # # [ [ [ '-', [ 0, 'a' ], [ 0, nil ] ] ], # 1 + # # [ [ '+', [ 3, nil ], [ 2, 'd' ] ] ], # 2 + # # [ [ '-', [ 4, 'h' ], [ 4, nil ] ], # 3 + # # [ '+', [ 5, nil ], [ 4, 'f' ] ] ], + # # [ [ '+', [ 6, nil ], [ 6, 'k' ] ] ], # 4 + # # [ [ '-', [ 8, 'n' ], [ 9, nil ] ], # 5 + # # [ '+', [ 9, nil ], [ 9, 'r' ] ], + # # [ '-', [ 9, 'p' ], [ 10, nil ] ], + # # [ '+', [ 10, nil ], [ 10, 's' ] ], + # # [ '+', [ 10, nil ], [ 11, 't' ] ] ] ] + # + # The five hunks shown are comprised of individual changes; if there is a + # related set of changes, they are still shown individually. + # + # This callback can also be used with Diff::LCS#sdiff, which will produce + # results like: + # + # diffs = Diff::LCS.sdiff(seq1, seq2, Diff::LCS::ContextCallbacks) + # # This example shows a simplified array format. + # # [ [ [ "-", [ 0, "a" ], [ 0, nil ] ] ], # 1 + # # [ [ "+", [ 3, nil ], [ 2, "d" ] ] ], # 2 + # # [ [ "!", [ 4, "h" ], [ 4, "f" ] ] ], # 3 + # # [ [ "+", [ 6, nil ], [ 6, "k" ] ] ], # 4 + # # [ [ "!", [ 8, "n" ], [ 9, "r" ] ], # 5 + # # [ "!", [ 9, "p" ], [ 10, "s" ] ], + # # [ "+", [ 10, nil ], [ 11, "t" ] ] ] ] + # + # The five hunks are still present, but are significantly shorter in total + # presentation, because changed items are shown as changes ("!") instead of + # potentially "mismatched" pairs of additions and deletions. + # + # The result of this operation is similar to that of + # Diff::LCS::SDiffCallbacks. They may be compared as: + # + # s = Diff::LCS.sdiff(seq1, seq2).reject { |e| e.action == "=" } + # c = Diff::LCS.sdiff(seq1, seq2, Diff::LCS::ContextDiffCallbacks).flatten + # + # s == c # -> true + # + # === Use + # This callback object must be initialised and can be used by the + # Diff::LCS#diff or Diff::LCS#sdiff methods. + # + # cbo = Diff::LCS::ContextDiffCallbacks.new + # Diff::LCS.LCS(seq1, seq2, cbo) + # cbo.finish + # + # Note that the call to #finish is absolutely necessary, or the last set of + # changes will not be visible. Alternatively, can be used as: + # + # cbo = Diff::LCS::ContextDiffCallbacks.new { |tcbo| Diff::LCS.LCS(seq1, seq2, tcbo) } + # + # The necessary #finish call will be made. + # + # === Simplified Array Format + # The simplified array format used in the example above can be obtained + # with: + # + # require 'pp' + # pp diffs.map { |e| e.map { |f| f.to_a } } +class DiffX::LCS::ContextDiffCallbacks < DiffX::LCS::DiffCallbacks + def discard_a(event) + @hunk << DiffX::LCS::ContextChange.simplify(event) + end + + def discard_b(event) + @hunk << DiffX::LCS::ContextChange.simplify(event) + end + + def change(event) + @hunk << DiffX::LCS::ContextChange.simplify(event) + end +end + + # This will produce a simple array of diff change objects. Each element in + # the #diffs array is a single ContextChange. In the set of #diffs provided + # by SDiffCallbacks, both old and new objects will be presented for both + # changed and unchanged objects. +nil+ will be substituted + # for a discarded object. + # + # The diffset produced by this callback, when provided to Diff::LCS#sdiff, + # will compute and display the necessary components to show two sequences + # and their minimized differences side by side, just like the Unix utility + # +sdiff+. + # + # same same + # before | after + # old < - + # - > new + # + # seq1 = %w(a b c e h j l m n p) + # seq2 = %w(b c d e f j k l m r s t) + # + # diffs = Diff::LCS.sdiff(seq1, seq2) + # # This example shows a simplified array format. + # # [ [ "-", [ 0, "a"], [ 0, nil ] ], + # # [ "=", [ 1, "b"], [ 0, "b" ] ], + # # [ "=", [ 2, "c"], [ 1, "c" ] ], + # # [ "+", [ 3, nil], [ 2, "d" ] ], + # # [ "=", [ 3, "e"], [ 3, "e" ] ], + # # [ "!", [ 4, "h"], [ 4, "f" ] ], + # # [ "=", [ 5, "j"], [ 5, "j" ] ], + # # [ "+", [ 6, nil], [ 6, "k" ] ], + # # [ "=", [ 6, "l"], [ 7, "l" ] ], + # # [ "=", [ 7, "m"], [ 8, "m" ] ], + # # [ "!", [ 8, "n"], [ 9, "r" ] ], + # # [ "!", [ 9, "p"], [ 10, "s" ] ], + # # [ "+", [ 10, nil], [ 11, "t" ] ] ] + # + # The result of this operation is similar to that of + # Diff::LCS::ContextDiffCallbacks. They may be compared as: + # + # s = Diff::LCS.sdiff(seq1, seq2).reject { |e| e.action == "=" } + # c = Diff::LCS.sdiff(seq1, seq2, Diff::LCS::ContextDiffCallbacks).flatten + # + # s == c # -> true + # + # === Use + # This callback object must be initialised and is used by the Diff::LCS#sdiff + # method. + # + # cbo = Diff::LCS::SDiffCallbacks.new + # Diff::LCS.LCS(seq1, seq2, cbo) + # + # As with the other initialisable callback objects, Diff::LCS::SDiffCallbacks + # can be initialised with a block. As there is no "fininishing" to be done, + # this has no effect on the state of the object. + # + # cbo = Diff::LCS::SDiffCallbacks.new { |tcbo| Diff::LCS.LCS(seq1, seq2, tcbo) } + # + # === Simplified Array Format + # The simplified array format used in the example above can be obtained + # with: + # + # require 'pp' + # pp diffs.map { |e| e.to_a } +class DiffX::LCS::SDiffCallbacks + # Returns the difference set collected during the diff process. + attr_reader :diffs + + def initialize #:yields self: + @diffs = [] + yield self if block_given? + end + + def match(event) + @diffs << DiffX::LCS::ContextChange.simplify(event) + end + + def discard_a(event) + @diffs << DiffX::LCS::ContextChange.simplify(event) + end + + def discard_b(event) + @diffs << DiffX::LCS::ContextChange.simplify(event) + end + + def change(event) + @diffs << DiffX::LCS::ContextChange.simplify(event) + end +end + +#============copy of diff-lcs, only change names. + + + +module Redmine + module Scm + module Adapters + class VisualSourceSafeAdapter < AbstractAdapter + + def callOle(obj, name, param, paramtype) + method = obj.ole_method(name) + return obj._invoke(method.dispid, param, paramtype) + end + + def callOleGet(obj, name, param, paramtype) + methods = obj.ole_get_methods + method = methods.find{|item| item.name == name} + return obj._getproperty(method.dispid, param, paramtype) + end + + # VSS OLE + module VSS_CONST + end + VSS_BIN = WIN32OLE.new('SourceSafe') + WIN32OLE.const_load(VSS_BIN, VSS_CONST) + + VSS_ACTION_ARRAY = [ + #Japanese -> English + [/^\xe8\xbf\xbd\xe5\x8a\xa0(.+)/, 'Added'] ,#TSUIKA + [/^\xe3\x83\x81\xe3\x82\xa7\xe3\x83\x83\xe3\x82\xaf\xe3\x82\xa4\xe3\x83\xb3(.+)/, 'Checked in'], #CHEKKU IN + [/^\xe4\xbd\x9c\xe6\x88\x90(.+)/, 'Created'] ,#SAKUSEI + [/^\xe3\x83\xa9\xe3\x83\x99\xe3\x83\xab\xe8\xa8\xad\xe5\xae\x9a(.+)/, 'Labeled'] ,#RABERU SETTEI + [/^\xe5\x89\x8a\xe9\x99\xa4(.+)/, 'Deleted'] ,#SAKUJYO + [/^\xe5\xbe\xa9\xe5\x85\x83(.+)/, 'Recovered'], #FUKUGEN + [/^\xe5\x90\x8d\xe5\x89\x8d\xe5\xa4\x89\xe6\x9b\xb4(.+)/, 'Renamed to'], #NAMAEHENKOU + [/^\xe5\x85\xb1\xe6\x9c\x89(.+)/, 'Shared'], #KYOUYUU + #need add any other languages, current(2007/11/9) using only 'Added','Created','Recovered','Deleted','Shared'. + ] + VSS_LANG_CODE = 'cp932' #assume Japanese, I hope make drop down list but I don't know how to do it in 'Project-Settings-Repository-Visual_Source_Safe'. + + # Get info about the VSS repository + def info + @ic ||= Iconv.new('UTF-8', VSS_LANG_CODE) + callOle(VSS_BIN, 'Open', + [@url, @login, @password], + [WIN32OLE::VARIANT::VT_BSTR, WIN32OLE::VARIANT::VT_BSTR, WIN32OLE::VARIANT::VT_BSTR]) + vssRootFolder = VSS_BIN.VSSItem("$/", false) + info = Info.new({ + :root_url => VSS_BIN.SrcSafeIni, + :lastrev => Revision.new({ + :identifier => Time.parse(vssRootFolder.VSSVersion.Date).localtime, + :time => Time.parse(vssRootFolder.VSSVersion.Date).localtime, + :author => @ic.iconv(vssRootFolder.VSSVersion.Username), + }), + }) + VSS_BIN.Close + return info + rescue Errno::ENOENT, WIN32OLERuntimeError => e + VSS_BIN.Close + return nil + end + + # Returns the entry identified by path and revision identifier + # or nil if entry doesn't exist in the repository + def entry(path=nil, identifier=nil) + e = entries(path, identifier) + e ? e.first : nil + end + + # Returns an Entries collection + # or nil if the given path doesn't exist in the repository + def entries(path=nil, identifier=nil) + @ic ||= Iconv.new('UTF-8', VSS_LANG_CODE) + @ic2 ||= Iconv.new(VSS_LANG_CODE, 'UTF-8') + path ||= '' + identifier = '0' unless identifier and identifier > 0 + entries = Entries.new + + if /^\// =~ path + path = path[1..(path.length)] + end + if path == '' + dir = "$/" + else + dir = path + end + #STDERR.puts "entries #{dir}, #{identifier}" + callOle(VSS_BIN, 'Open', + [@url, @login, @password], + [WIN32OLE::VARIANT::VT_BSTR, WIN32OLE::VARIANT::VT_BSTR, WIN32OLE::VARIANT::VT_BSTR]) + begin + vssFolder = VSS_BIN.VSSItem(dir, false) + rescue + vssFolder = VSS_BIN.VSSItem(@ic2.iconv(dir), false) + end + if vssFolder.Type == 0 + #is Project + vssItems = vssFolder.Version(identifier.to_s).Items + vssItems.each{|entry| + name = begin + @ic.iconv(entry.Name) + rescue + entry.Name + end + spec = begin + @ic.iconv(entry.Spec) + rescue + entry.Spec + end + username = begin + @ic.iconv(entry.VSSVersion.Username) + rescue + entry.VSSVersion.Username + end + tm = Time.parse(entry.VSSVersion.Date).localtime + isFile = entry.Type + entries << Entry.new({ + :name => name, + :path => spec, + :kind => (isFile == 0 ? 'dir' : 'file'), + :size => (isFile == 0 ? 0 : entry.Size), + :lastrev => Revision.new({ + :identifier => tm, + :time => tm, + :author => username, + }) + }) + } + else + #is File + if identifier == '0' + entry = vssFolder + else + entry = vssFolder.Version(identifier.to_s) + end + entry.Versions.each{|entry2| + if entry2.Label == '' + name = begin + @ic.iconv(entry2.VSSItem.Name) + rescue + entry2.VSSItem.Name + end + spec = begin + @ic.iconv(entry2.VSSItem.Spec) + rescue + entry2.VSSItem.Spec + end + username = begin + @ic.iconv(entry2.Username) + rescue + entry2.Username + end + tm = Time.parse(entry2.Date).localtime + entries << Entry.new({ + :name => name, + :path => spec, + :kind => 'file', + :size => entry2.VSSItem.Size, + :lastrev => Revision.new({ + :identifier => tm, + :time => tm, + :author => username, + }) + }) + else + #Label + end + } + + end + VSS_BIN.Close + entries.sort_by_name + rescue Errno::ENOENT, WIN32OLERuntimeError => e + VSS_BIN.Close + raise CommandFailed + end + +=begin #maybe not need + def revisions_merge(revisions) + STDERR.puts "revisions_merge (#{revisions.size}) #{Time.now}" + revisions.sort!{|a, b| a.time <=> b.time} + revisions2 = Revisions.new + if revisions.size > 0 + #revisions2 << revisions[0].dup + #STDERR.puts "create #{revisions[0].paths} @ #{revisions[0].time}" + + time_delta = 10.seconds + revisions.each{|revision| + if revisions2.size == 0 + revisions2 << revision.dup + else + revision2 = revisions2[-1] + if revision.author == revision2.author && revision.message == revision2.message && (revision.time > revision2.time - time_delta && revision.time < revision2.time + time_delta) + STDERR.puts "Merge #{revisions2[-1].paths} and #{revision.paths}" + revisions2[-1].paths += revision.paths.dup + #STDERR.puts "merge #{revision.paths}" + else + revisions2 << revision.dup + #STDERR.puts "create #{revision.paths} @ #{revision.time}" + end + end + } + end + STDERR.puts "revisions_merge end (#{revisions2.size}) #{Time.now}" + return revisions2 + end +=end + + def revision_for_one(identifier_from, identifier_to, vssItem) + @ic ||= Iconv.new('UTF-8', VSS_LANG_CODE) + #STDERR.puts "revision_for_one #{vssItem.Spec}, #{vssItem.VersionNumber}, #{vssItem.VSSVersion.Date}" + revisions_ = Revisions.new + if Time.parse(vssItem.VSSVersion.Date).localtime > identifier_from + #STDERR.puts "Time Check Pass! #{identifier_from}" + isFile = vssItem.Type + vssItem.Versions.each{|version| + tm = Time.parse(version.Date).localtime + if tm > identifier_from && tm <= identifier_to + #STDERR.puts "Rev Check Pass! #{tm} > #{identifier_from}" + begin + spec = begin + @ic.iconv(version.vssItem.Spec) + rescue + version.vssItem.Spec + end + act = begin + @ic.iconv(version.Action) + rescue + version.Action + end + comment = begin + @ic.iconv(version.Comment) + rescue + version.Comment + end + username = begin + @ic.iconv(version.Username) + rescue + version.Username + end + #JPN->ENG Conv, assume UTF-8 string never conflict + VSS_ACTION_ARRAY.each{|x| + if x[0] =~ act + act = x[1] + $1 + end + } + if isFile == 0 and (/^Deleted\s(.+)/ =~ act or /^Recovered\s(.+)/ =~ act) + #Deleted/Recovered is recorded at Folder. so change spec + s = $1 + unless /\/$/ =~ spec + spec += '/' + end + spec += s + end + STDERR.puts "Rev Information:isFile=#{isFile},act=#{act},spec=#{spec}" + if isFile == 0 and /^Added\s(.+)/ =~ act + #maybe this information include File's VSSVersion, so Skip + else + STDERR.puts "Add Revision:#{tm},#{username},#{act},#{comment}" + revisions_ << Revision.new({ + :identifier => tm, + :author => username, + :time => tm, + :message => comment, + :paths => [{ + :action => act, + :path => spec, + :from_path => spec, + :revision => tm, + }], + }) + end + rescue WIN32OLERuntimeError => e + logger.error("Error VSS revisions making: #{e.message}") + STDERR.puts("Error VSS revisions making: #{e.message}") + #STDERR.puts("version #{version.Comment}, #{version.Date}, #{version.Label}, #{version.VersionNumber}, #{version.Username}") + end + end + #escape for memory over... + #version.ole_free #Danger, not recommended + } + end + #STDERR.puts "for_one/#{vssItem.Spec} : #{revisions_}" + #GC.start + return revisions_ + end + + def revision_childs(path, identifier_from, identifier_to, vssFolder) + STDERR.puts "revision_childs #{path}" + revisions_ = Revisions.new + vssFolder.Items(false).each{|item| + if item.Type == 0 + #is Project + revisions_.concat(revision_childs(item.Spec, identifier_from, identifier_to, item)) + end + revisions_.concat(revision_for_one(identifier_from, identifier_to, item)) + #escape for memory over... + @revisions_loop_counter += 1 + #item.ole_free #Danger, not recommended + if @revisions_loop_counter > 1000 + GC.start + @revisions_loop_counter = 0 + end + } + #STDERR.puts "childs/#{vssFolder.Spec} : #{revisions_}" + #GC.start + return revisions_ + end + + def revisions(path=nil, identifier_from=nil, identifier_to=nil, options={}) + STDERR.puts "revisions #{path}, #{identifier_from}, #{identifier_to}, #{options}, @#{Time.now}, #{@url}" + @revisions_loop_counter = 0 + path ||= '' + identifier_from = Time.parse('2000/08/01 00:00:00').localtime unless identifier_from + identifier_to = Time.now.localtime + revisions_ = Revisions.new + + if /^\// =~ path + path = path[1..(path.length)] + end + if path == '' + dir = "$/" + else + dir = path + end + callOle(VSS_BIN, 'Open', + [@url, @login, @password], + [WIN32OLE::VARIANT::VT_BSTR, WIN32OLE::VARIANT::VT_BSTR, WIN32OLE::VARIANT::VT_BSTR]) + vssFolder = VSS_BIN.VSSItem(dir, false) + revisions_ += revision_childs(dir, identifier_from, identifier_to, vssFolder) + VSS_BIN.Close + return revisions_ + rescue Errno::ENOENT, WIN32OLERuntimeError => e + STDERR.puts "WIN32OLE Runtime Error for open:#{@url}, #{@login}, #{@password}" + VSS_BIN.Close + raise CommandFailed + end + + def diff(path, identifier_from, identifier_to=nil, type2="inline") + if /^\// =~ path + path = path[1..(path.length)] + end + #STDERR.puts " diff #{path}, #{identifier_from}, #{identifier_to}, #{type2}" + file1 = cat(path, identifier_from) + file2 = cat(path, identifier_to) + if file1 and file2 + file1x = file1.gsub("\r\n","\n").gsub("\r", "\n").split("\n") + file2x = file2.gsub("\r\n","\n").gsub("\r", "\n").split("\n") + #STDERR.puts " file1 and file2 found" + + df = DiffX::LCS.diff(file1x, file2x) + diff = ["Index: #{path}", + "===================================================================", + "--- #{path} (#{identifier_from})", + "+++ #{path} (#{identifier_to})", + ] + df.each{|x| + d = DiffX::LCS::Hunk.new(file1x, file2x, x, 3, 0) + diff << d.diff(:unified) + } + dtl = DiffTableList.new(diff.join("\n").split("\n"), type2) + return dtl + end + #STDERR.puts " ERROR! file1:#{file1}, file2:#{file2}" + return nil + rescue Errno::ENOENT => e + raise CommandFailed + end + + def cat(path, identifier=nil) + @ic2 ||= Iconv.new(VSS_LANG_CODE, 'UTF-8') + #STDERR.puts "cat #{path}, #{identifier}" + if identifier + identifier = Time.parse(identifier) + else + identifier = Time.now.localtime + end + if path == '' + dir = "$/" + else + dir = path + end + begin + callOle(VSS_BIN, 'Open', + [@url, @login, @password], + [WIN32OLE::VARIANT::VT_BSTR, WIN32OLE::VARIANT::VT_BSTR, WIN32OLE::VARIANT::VT_BSTR]) + rescue + VSS_BIN.Close + raise CommandFailed + end + begin + vssFolder = VSS_BIN.VSSItem(dir, false).version(identifier.strftime("%Y/%m/%d %H:%M:%S")) + rescue + vssFolder = VSS_BIN.VSSItem(@ic2.iconv(dir), false).version(identifier.strftime("%Y/%m/%d %H:%M:%S")) + end + + + if vssFolder.Type == 0 + return 'Target is Project.' + end + if vssFolder.Binary == true + #return 'Target is Binary.' + end + + file = Tempfile.new('redminevss') + filename = File.expand_path(file.path) + file.close(true) + + vssFolder.Get(filename, false) + data = '' + open(filename, 'rb'){|f| + data = f.read + } + VSS_BIN.Close + return data.dup + rescue Errno::ENOENT, WIN32OLERuntimeError => e + VSS_BIN.Close + raise CommandFailed + end + + def get_previous_revision(path, rev) + #STDERR.puts "get_previous_revision #{path}, #{rev}" + if /^\// =~ path + path = path[1..(path.length)] + end + if path == '' + dir = "$/" + else + dir = path + end + begin + callOle(VSS_BIN, 'Open', + [@url, @login, @password], + [WIN32OLE::VARIANT::VT_BSTR, WIN32OLE::VARIANT::VT_BSTR, WIN32OLE::VARIANT::VT_BSTR]) + rescue + VSS_BIN.Close + raise CommandFailed + end + vssFolder = VSS_BIN.VSSItem(dir, false) + vssVersions = vssFolder.Version(rev).Versions(VSS_CONST::VSSFLAG_RECURSYES) + prev = nil + vssVersions.each{|version| + if version.Date == rev + if prev + VSS_BIN.Close + return Time.parse(prev.Date).localtime.strftime("%Y/%m/%d %H:%M:%S") + else + VSS_BIN.Close + return rev + end + end + prev = version + } + VSS_BIN.Close + return rev + end + + end + end + end +end