Project

General

Profile

Patch #228 » visual_source_safe_adapter.rb

Aruo Miura, 2008-02-03 11:01

 
1
# redMine - project management software
2
# Copyright (C) 2006-2007  Jean-Philippe Lang
3
# Visual Source Safe modules: Aruo Miura
4
#
5
# This program is free software; you can redistribute it and/or
6
# modify it under the terms of the GNU General Public License
7
# as published by the Free Software Foundation; either version 2
8
# of the License, or (at your option) any later version.
9
# 
10
# This program is distributed in the hope that it will be useful,
11
# but WITHOUT ANY WARRANTY; without even the implied warranty of
12
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13
# GNU General Public License for more details.
14
# 
15
# You should have received a copy of the GNU General Public License
16
# along with this program; if not, write to the Free Software
17
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
18

    
19
require 'redmine/scm/adapters/abstract_adapter'
20
require 'rexml/document'
21
require 'win32ole'
22
require 'tempfile'
23
#require	'nkf'
24

    
25
#============copy of diff-lcs, only change names. Diff -> DiffX
26

    
27
#lcs.rb
28

    
29
module DiffX
30
    # = Diff::LCS 1.1.2
31
    # Computes "intelligent" differences between two sequenced Enumerables.
32
    # This is an implementation of the McIlroy-Hunt "diff" algorithm for
33
    # Enumerable objects that include Diffable.
34
    #
35
    # Based on Mario I. Wolczko's <mario@wolczko.com> Smalltalk version
36
    # (1.2, 1993) and Ned Konz's <perl@bike-nomad.com> Perl version
37
    # (Algorithm::Diff).
38
    #
39
    # == Synopsis
40
    #   require 'diff/lcs'
41
    #
42
    #   seq1 = %w(a b c e h j l m n p)
43
    #   seq2 = %w(b c d e f j k l m r s t)
44
    #
45
    #   lcs = Diff::LCS.LCS(seq1, seq2)
46
    #   diffs = Diff::LCS.diff(seq1, seq2)
47
    #   sdiff = Diff::LCS.sdiff(seq1, seq2)
48
    #   seq = Diff::LCS.traverse_sequences(seq1, seq2, callback_obj)
49
    #   bal = Diff::LCS.traverse_balanced(seq1, seq2, callback_obj)
50
    #   seq2 == Diff::LCS.patch(seq1, diffs)
51
    #   seq2 == Diff::LCS.patch!(seq1, diffs)
52
    #   seq1 == Diff::LCS.unpatch(seq2, diffs)
53
    #   seq1 == Diff::LCS.unpatch!(seq2, diffs)
54
    #   seq2 == Diff::LCS.patch(seq1, sdiff)
55
    #   seq2 == Diff::LCS.patch!(seq1, sdiff)
56
    #   seq1 == Diff::LCS.unpatch(seq2, sdiff)
57
    #   seq1 == Diff::LCS.unpatch!(seq2, sdiff)
58
    #
59
    # Alternatively, objects can be extended with Diff::LCS:
60
    #
61
    #   seq1.extend(Diff::LCS)
62
    #   lcs = seq1.lcs(seq2)
63
    #   diffs = seq1.diff(seq2)
64
    #   sdiff = seq1.sdiff(seq2)
65
    #   seq = seq1.traverse_sequences(seq2, callback_obj)
66
    #   bal = seq1.traverse_balanced(seq2, callback_obj)
67
    #   seq2 == seq1.patch(diffs)
68
    #   seq2 == seq1.patch!(diffs)
69
    #   seq1 == seq2.unpatch(diffs)
70
    #   seq1 == seq2.unpatch!(diffs)
71
    #   seq2 == seq1.patch(sdiff)
72
    #   seq2 == seq1.patch!(sdiff)
73
    #   seq1 == seq2.unpatch(sdiff)
74
    #   seq1 == seq2.unpatch!(sdiff)
75
    # 
76
    # Default extensions are provided for Array and String objects through
77
    # the use of 'diff/lcs/array' and 'diff/lcs/string'.
78
    #
79
    # == Introduction (by Mark-Jason Dominus)
80
    # 
81
    # <em>The following text is from the Perl documentation. The only
82
    # changes have been to make the text appear better in Rdoc</em>.
83
    #
84
    # I once read an article written by the authors of +diff+; they said
85
    # that they hard worked very hard on the algorithm until they found the
86
    # right one.
87
    #
88
    # I think what they ended up using (and I hope someone will correct me,
89
    # because I am not very confident about this) was the `longest common
90
    # subsequence' method. In the LCS problem, you have two sequences of
91
    # items:
92
    #
93
    #    a b c d f g h j q z
94
    #    a b c d e f g i j k r x y z
95
    #
96
    # and you want to find the longest sequence of items that is present in
97
    # both original sequences in the same order. That is, you want to find a
98
    # new sequence *S* which can be obtained from the first sequence by
99
    # deleting some items, and from the second sequence by deleting other
100
    # items. You also want *S* to be as long as possible. In this case *S*
101
    # is:
102
    # 
103
    #    a b c d f g j z
104
    #
105
    # From there it's only a small step to get diff-like output:
106
    #
107
    #    e   h i   k   q r x y
108
    #    +   - +   +   - + + +
109
    #
110
    # This module solves the LCS problem. It also includes a canned function
111
    # to generate +diff+-like output.
112
    #
113
    # It might seem from the example above that the LCS of two sequences is
114
    # always pretty obvious, but that's not always the case, especially when
115
    # the two sequences have many repeated elements. For example, consider
116
    #
117
    #    a x b y c z p d q
118
    #    a b c a x b y c z
119
    #
120
    # A naive approach might start by matching up the +a+ and +b+ that
121
    # appear at the beginning of each sequence, like this:
122
    # 
123
    #    a x b y c         z p d q
124
    #    a   b   c a b y c z
125
    #
126
    # This finds the common subsequence +a b c z+. But actually, the LCS is
127
    # +a x b y c z+:
128
    #
129
    #          a x b y c z p d q
130
    #    a b c a x b y c z
131
    #
132
    # == Author
133
    # This version is by Austin Ziegler <diff-lcs@halostatue.ca>.
134
    #
135
    # It is based on the Perl Algorithm::Diff by Ned Konz
136
    # <perl@bike-nomad.com>, copyright &copy; 2000 - 2002 and the Smalltalk
137
    # diff version by Mario I. Wolczko <mario@wolczko.com>, copyright &copy;
138
    # 1993. Documentation includes work by Mark-Jason Dominus.
139
    #
140
    # == Licence
141
    # Copyright &copy; 2004 Austin Ziegler
142
    # This program is free software; you can redistribute it and/or modify it
143
    # under the same terms as Ruby, or alternatively under the Perl Artistic
144
    # licence.
145
    #
146
    # == Credits
147
    # Much of the documentation is taken directly from the Perl
148
    # Algorithm::Diff implementation and was written originally by Mark-Jason
149
    # Dominus <mjd-perl-diff@plover.com> and later by Ned Konz. The basic Ruby
150
    # implementation was re-ported from the Smalltalk implementation, available
151
    # at ftp://st.cs.uiuc.edu/pub/Smalltalk/MANCHESTER/manchester/4.0/diff.st
152
    #
153
    # #sdiff and #traverse_balanced were written for the Perl version by Mike
154
    # Schilli <m@perlmeister.com>.
155
    #
156
    # "The algorithm is described in <em>A Fast Algorithm for Computing Longest
157
    # Common Subsequences</em>, CACM, vol.20, no.5, pp.350-353, May 1977, with
158
    # a few minor improvements to improve the speed."
159
  module LCS
160
    VERSION = '1.1.2'
161
  end
162
end
163

    
164
module DiffX::LCS
165
    # Returns an Array containing the longest common subsequence(s) between
166
    # +self+ and +other+. See Diff::LCS#LCS.
167
    #
168
    #   lcs = seq1.lcs(seq2)
169
  def lcs(other, &block) #:yields self[ii] if there are matched subsequences:
170
    DiffX::LCS.LCS(self, other, &block)
171
  end
172

    
173
    # Returns the difference set between +self+ and +other+. See
174
    # Diff::LCS#diff.
175
  def diff(other, callbacks = nil, &block)
176
    DiffX::LCS::diff(self, other, callbacks, &block)
177
  end
178

    
179
    # Returns the balanced ("side-by-side") difference set between +self+ and
180
    # +other+. See Diff::LCS#sdiff.
181
  def sdiff(other, callbacks = nil, &block)
182
    DiffX::LCS::sdiff(self, other, callbacks, &block)
183
  end
184

    
185
    # Traverses the discovered longest common subsequences between +self+ and
186
    # +other+. See Diff::LCS#traverse_sequences.
187
  def traverse_sequences(other, callbacks = nil, &block)
188
    traverse_sequences(self, other, callbacks || DiffX::LCS::YieldingCallbacks,
189
                       &block)
190
  end
191

    
192
    # Traverses the discovered longest common subsequences between +self+ and
193
    # +other+ using the alternate, balanced algorithm. See
194
    # Diff::LCS#traverse_balanced.
195
  def traverse_balanced(other, callbacks = nil, &block)
196
    traverse_balanced(self, other, callbacks || DiffX::LCS::YieldingCallbacks,
197
                      &block)
198
  end
199

    
200
    # Attempts to patch a copy of +self+ with the provided +patchset+. See
201
    # Diff::LCS#patch.
202
  def patch(patchset)
203
    DiffX::LCS::patch(self.dup, patchset)
204
  end
205

    
206
    # Attempts to unpatch a copy of +self+ with the provided +patchset+.
207
    # See Diff::LCS#patch.
208
  def unpatch(patchset)
209
    DiffX::LCS::unpatch(self.dup, patchset)
210
  end
211

    
212
    # Attempts to patch +self+ with the provided +patchset+. See
213
    # Diff::LCS#patch!. Does no autodiscovery.
214
  def patch!(patchset)
215
    DiffX::LCS::patch!(self, patchset)
216
  end
217

    
218
    # Attempts to unpatch +self+ with the provided +patchset+. See
219
    # Diff::LCS#unpatch. Does no autodiscovery.
220
  def unpatch!(patchset)
221
    DiffX::LCS::unpatch!(self, patchset)
222
  end
223
end
224

    
225
module DiffX::LCS
226
  class << self
227
      # Given two sequenced Enumerables, LCS returns an Array containing their
228
      # longest common subsequences.
229
      #
230
      #   lcs = Diff::LCS.LCS(seq1, seq2)
231
      #
232
      # This array whose contents is such that:
233
      #
234
      #   lcs.each_with_index do |ee, ii|
235
      #     assert(ee.nil? || (seq1[ii] == seq2[ee]))
236
      #   end
237
      #
238
      # If a block is provided, the matching subsequences will be yielded from
239
      # +seq1+ in turn and may be modified before they are placed into the
240
      # returned Array of subsequences.
241
    def LCS(seq1, seq2, &block) #:yields seq1[ii] for each matched:
242
      matches = DiffX::LCS.__lcs(seq1, seq2)
243
      ret = []
244
      matches.each_with_index do |ee, ii|
245
        unless matches[ii].nil?
246
          if block_given?
247
            ret << (yield seq1[ii])
248
          else
249
            ret << seq1[ii]
250
          end
251
        end
252
      end
253
      ret
254
    end
255

    
256
      # Diff::LCS.diff computes the smallest set of additions and deletions
257
      # necessary to turn the first sequence into the second, and returns a
258
      # description of these changes.
259
      # 
260
      # See Diff::LCS::DiffCallbacks for the default behaviour. An alternate
261
      # behaviour may be implemented with Diff::LCS::ContextDiffCallbacks.
262
      # If a Class argument is provided for +callbacks+, #diff will attempt
263
      # to initialise it. If the +callbacks+ object (possibly initialised)
264
      # responds to #finish, it will be called.
265
    def diff(seq1, seq2, callbacks = nil, &block) # :yields diff changes:
266
      callbacks ||= DiffX::LCS::DiffCallbacks
267
      if callbacks.kind_of?(Class)
268
        cb = callbacks.new rescue callbacks
269
        callbacks = cb
270
      end
271
      traverse_sequences(seq1, seq2, callbacks)
272
      callbacks.finish if callbacks.respond_to?(:finish)
273

    
274
      if block_given?
275
        res = callbacks.diffs.map do |hunk|
276
          if hunk.kind_of?(Array)
277
            hunk = hunk.map { |block| yield block }
278
          else
279
            yield hunk
280
          end
281
        end
282
        res
283
      else
284
        callbacks.diffs
285
      end
286
    end
287

    
288
      # Diff::LCS.sdiff computes all necessary components to show two sequences
289
      # and their minimized differences side by side, just like the Unix
290
      # utility <em>sdiff</em> does:
291
      #
292
      #     old        <     -
293
      #     same             same
294
      #     before     |     after
295
      #     -          >     new
296
      #
297
      # See Diff::LCS::SDiffCallbacks for the default behaviour. An alternate
298
      # behaviour may be implemented with Diff::LCS::ContextDiffCallbacks. If
299
      # a Class argument is provided for +callbacks+, #diff will attempt to
300
      # initialise it. If the +callbacks+ object (possibly initialised)
301
      # responds to #finish, it will be called.
302
    def sdiff(seq1, seq2, callbacks = nil, &block) #:yields diff changes:
303
      callbacks ||= DiffX::LCS::SDiffCallbacks
304
      if callbacks.kind_of?(Class)
305
        cb = callbacks.new rescue callbacks
306
        callbacks = cb
307
      end
308
      traverse_balanced(seq1, seq2, callbacks)
309
      callbacks.finish if callbacks.respond_to?(:finish)
310

    
311
      if block_given?
312
        res = callbacks.diffs.map do |hunk|
313
          if hunk.kind_of?(Array)
314
            hunk = hunk.map { |block| yield block }
315
          else
316
            yield hunk
317
          end
318
        end
319
        res
320
      else
321
        callbacks.diffs
322
      end
323
    end
324

    
325
      # Diff::LCS.traverse_sequences is the most general facility provided by this
326
      # module; +diff+ and +LCS+ are implemented as calls to it.
327
      #
328
      # The arguments to #traverse_sequences are the two sequences to
329
      # traverse, and a callback object, like this:
330
      #
331
      #   traverse_sequences(seq1, seq2, Diff::LCS::ContextDiffCallbacks.new)
332
      #
333
      # #diff is implemented with #traverse_sequences.
334
      #
335
      # == Callback Methods
336
      # Optional callback methods are <em>emphasized</em>.
337
      #
338
      # callbacks#match::               Called when +a+ and +b+ are pointing
339
      #                                 to common elements in +A+ and +B+.
340
      # callbacks#discard_a::           Called when +a+ is pointing to an
341
      #                                 element not in +B+.
342
      # callbacks#discard_b::           Called when +b+ is pointing to an
343
      #                                 element not in +A+.
344
      # <em>callbacks#finished_a</em>:: Called when +a+ has reached the end of
345
      #                                 sequence +A+.
346
      # <em>callbacks#finished_b</em>:: Called when +b+ has reached the end of
347
      #                                 sequence +B+.
348
      #
349
      # == Algorithm
350
      #       a---+
351
      #           v
352
      #       A = a b c e h j l m n p
353
      #       B = b c d e f j k l m r s t
354
      #           ^
355
      #       b---+
356
      #
357
      # If there are two arrows (+a+ and +b+) pointing to elements of
358
      # sequences +A+ and +B+, the arrows will initially point to the first
359
      # elements of their respective sequences. #traverse_sequences will
360
      # advance the arrows through the sequences one element at a time,
361
      # calling a method on the user-specified callback object before each
362
      # advance. It will advance the arrows in such a way that if there are
363
      # elements <tt>A[ii]</tt> and <tt>B[jj]</tt> which are both equal and
364
      # part of the longest common subsequence, there will be some moment
365
      # during the execution of #traverse_sequences when arrow +a+ is pointing
366
      # to <tt>A[ii]</tt> and arrow +b+ is pointing to <tt>B[jj]</tt>. When
367
      # this happens, #traverse_sequences will call <tt>callbacks#match</tt>
368
      # and then it will advance both arrows.
369
      #
370
      # Otherwise, one of the arrows is pointing to an element of its sequence
371
      # that is not part of the longest common subsequence.
372
      # #traverse_sequences will advance that arrow and will call
373
      # <tt>callbacks#discard_a</tt> or <tt>callbacks#discard_b</tt>, depending
374
      # on which arrow it advanced. If both arrows point to elements that are
375
      # not part of the longest common subsequence, then #traverse_sequences
376
      # will advance one of them and call the appropriate callback, but it is
377
      # not specified which it will call.
378
      #
379
      # The methods for <tt>callbacks#match</tt>, <tt>callbacks#discard_a</tt>,
380
      # and <tt>callbacks#discard_b</tt> are invoked with an event comprising
381
      # the action ("=", "+", or "-", respectively), the indicies +ii+ and
382
      # +jj+, and the elements <tt>A[ii]</tt> and <tt>B[jj]</tt>. Return
383
      # values are discarded by #traverse_sequences.
384
      #
385
      # === End of Sequences
386
      # If arrow +a+ reaches the end of its sequence before arrow +b+ does,
387
      # #traverse_sequence try to call <tt>callbacks#finished_a</tt> with the
388
      # last index and element of +A+ (<tt>A[-1]</tt>) and the current index
389
      # and element of +B+ (<tt>B[jj]</tt>). If <tt>callbacks#finished_a</tt>
390
      # does not exist, then <tt>callbacks#discard_b</tt> will be called on
391
      # each element of +B+ until the end of the sequence is reached (the call
392
      # will be done with <tt>A[-1]</tt> and <tt>B[jj]</tt> for each element).
393
      #
394
      # If +b+ reaches the end of +B+ before +a+ reaches the end of +A+,
395
      # <tt>callbacks#finished_b</tt> will be called with the current index
396
      # and element of +A+ (<tt>A[ii]</tt>) and the last index and element of
397
      # +B+ (<tt>A[-1]</tt>). Again, if <tt>callbacks#finished_b</tt> does not
398
      # exist on the callback object, then <tt>callbacks#discard_a</tt> will
399
      # be called on each element of +A+ until the end of the sequence is
400
      # reached (<tt>A[ii]</tt> and <tt>B[-1]</tt>).
401
      #
402
      # There is a chance that one additional <tt>callbacks#discard_a</tt> or
403
      # <tt>callbacks#discard_b</tt> will be called after the end of the
404
      # sequence is reached, if +a+ has not yet reached the end of +A+ or +b+
405
      # has not yet reached the end of +B+.
406
    def traverse_sequences(seq1, seq2, callbacks = DiffX::LCS::SequenceCallbacks, &block) #:yields change events:
407
      matches = DiffX::LCS.__lcs(seq1, seq2)
408

    
409
      run_finished_a = run_finished_b = false
410
      string = seq1.kind_of?(String)
411

    
412
      a_size = seq1.size
413
      b_size = seq2.size
414
      ai = bj = 0
415

    
416
      (0 .. matches.size).each do |ii|
417
        b_line = matches[ii]
418

    
419
        ax = string ? seq1[ii, 1] : seq1[ii]
420
        bx = string ? seq2[bj, 1] : seq2[bj]
421

    
422
        if b_line.nil?
423
          unless ax.nil?
424
            event = DiffX::LCS::ContextChange.new('-', ii, ax, bj, bx)
425
            event = yield event if block_given?
426
            callbacks.discard_a(event)
427
          end
428
        else
429
          loop do
430
            break unless bj < b_line
431
            bx = string ? seq2[bj, 1] : seq2[bj]
432
            event = DiffX::LCS::ContextChange.new('+', ii, ax, bj, bx)
433
            event = yield event if block_given?
434
            callbacks.discard_b(event)
435
            bj += 1
436
          end
437
          bx = string ? seq2[bj, 1] : seq2[bj]
438
          event = DiffX::LCS::ContextChange.new('=', ii, ax, bj, bx)
439
          event = yield event if block_given?
440
          callbacks.match(event)
441
          bj += 1
442
        end
443
        ai = ii
444
      end
445
      ai += 1
446

    
447
        # The last entry (if any) processed was a match. +ai+ and +bj+ point
448
        # just past the last matching lines in their sequences.
449
      while (ai < a_size) or (bj < b_size)
450
          # last A?
451
        if ai == a_size and bj < b_size
452
          if callbacks.respond_to?(:finished_a) and not run_finished_a
453
            ax = string ? seq1[-1, 1] : seq1[-1]
454
            bx = string ? seq2[bj, 1] : seq2[bj]
455
            event = DiffX::LCS::ContextChange.new('>', (a_size - 1), ax, bj, bx)
456
            event = yield event if block_given?
457
            callbacks.finished_a(event)
458
            run_finished_a = true
459
          else
460
            ax = string ? seq1[ai, 1] : seq1[ai]
461
            loop do
462
              bx = string ? seq2[bj, 1] : seq2[bj]
463
              event = DiffX::LCS::ContextChange.new('+', ai, ax, bj, bx)
464
              event = yield event if block_given?
465
              callbacks.discard_b(event)
466
              bj += 1
467
              break unless bj < b_size
468
            end
469
          end
470
        end
471

    
472
          # last B?
473
        if bj == b_size and ai < a_size
474
          if callbacks.respond_to?(:finished_b) and not run_finished_b
475
            ax = string ? seq1[ai, 1] : seq1[ai]
476
            bx = string ? seq2[-1, 1] : seq2[-1]
477
            event = DiffX::LCS::ContextChange.new('<', ai, ax, (b_size - 1), bx)
478
            event = yield event if block_given?
479
            callbacks.finished_b(event)
480
            run_finished_b = true
481
          else
482
            bx = string ? seq2[bj, 1] : seq2[bj]
483
            loop do
484
              ax = string ? seq1[ai, 1] : seq1[ai]
485
              event = DiffX::LCS::ContextChange.new('-', ai, ax, bj, bx)
486
              event = yield event if block_given?
487
              callbacks.discard_a(event)
488
              ai += 1
489
              break unless bj < b_size
490
            end
491
          end
492
        end
493

    
494
        if ai < a_size
495
          ax = string ? seq1[ai, 1] : seq1[ai]
496
          bx = string ? seq2[bj, 1] : seq2[bj]
497
          event = DiffX::LCS::ContextChange.new('-', ai, ax, bj, bx)
498
          event = yield event if block_given?
499
          callbacks.discard_a(event)
500
          ai += 1
501
        end
502

    
503
        if bj < b_size
504
          ax = string ? seq1[ai, 1] : seq1[ai]
505
          bx = string ? seq2[bj, 1] : seq2[bj]
506
          event = DiffX::LCS::ContextChange.new('+', ai, ax, bj, bx)
507
          event = yield event if block_given?
508
          callbacks.discard_b(event)
509
          bj += 1
510
        end
511
      end
512
    end
513

    
514
      # #traverse_balanced is an alternative to #traverse_sequences. It
515
      # uses a different algorithm to iterate through the entries in the
516
      # computed longest common subsequence. Instead of viewing the changes as
517
      # insertions or deletions from one of the sequences, #traverse_balanced
518
      # will report <em>changes</em> between the sequences. To represent a
519
      #
520
      # The arguments to #traverse_balanced are the two sequences to traverse
521
      # and a callback object, like this:
522
      #
523
      #   traverse_balanced(seq1, seq2, Diff::LCS::ContextDiffCallbacks.new)
524
      #
525
      # #sdiff is implemented with #traverse_balanced.
526
      #
527
      # == Callback Methods
528
      # Optional callback methods are <em>emphasized</em>.
529
      #
530
      # callbacks#match::               Called when +a+ and +b+ are pointing
531
      #                                 to common elements in +A+ and +B+.
532
      # callbacks#discard_a::           Called when +a+ is pointing to an
533
      #                                 element not in +B+.
534
      # callbacks#discard_b::           Called when +b+ is pointing to an
535
      #                                 element not in +A+.
536
      # <em>callbacks#change</em>::     Called when +a+ and +b+ are pointing
537
      #                                 to the same relative position, but
538
      #                                 <tt>A[a]</tt> and <tt>B[b]</tt> are
539
      #                                 not the same; a <em>change</em> has
540
      #                                 occurred.
541
      #
542
      # #traverse_balanced might be a bit slower than #traverse_sequences,
543
      # noticable only while processing huge amounts of data.
544
      #
545
      # The +sdiff+ function of this module is implemented as call to
546
      # #traverse_balanced.
547
      #
548
      # == Algorithm
549
      #       a---+
550
      #           v
551
      #       A = a b c e h j l m n p
552
      #       B = b c d e f j k l m r s t
553
      #           ^
554
      #       b---+
555
      #
556
      # === Matches
557
      # If there are two arrows (+a+ and +b+) pointing to elements of
558
      # sequences +A+ and +B+, the arrows will initially point to the first
559
      # elements of their respective sequences. #traverse_sequences will
560
      # advance the arrows through the sequences one element at a time,
561
      # calling a method on the user-specified callback object before each
562
      # advance. It will advance the arrows in such a way that if there are
563
      # elements <tt>A[ii]</tt> and <tt>B[jj]</tt> which are both equal and
564
      # part of the longest common subsequence, there will be some moment
565
      # during the execution of #traverse_sequences when arrow +a+ is pointing
566
      # to <tt>A[ii]</tt> and arrow +b+ is pointing to <tt>B[jj]</tt>. When
567
      # this happens, #traverse_sequences will call <tt>callbacks#match</tt>
568
      # and then it will advance both arrows.
569
      #
570
      # === Discards
571
      # Otherwise, one of the arrows is pointing to an element of its sequence
572
      # that is not part of the longest common subsequence.
573
      # #traverse_sequences will advance that arrow and will call
574
      # <tt>callbacks#discard_a</tt> or <tt>callbacks#discard_b</tt>,
575
      # depending on which arrow it advanced.
576
      #
577
      # === Changes
578
      # If both +a+ and +b+ point to elements that are not part of the longest
579
      # common subsequence, then #traverse_sequences will try to call
580
      # <tt>callbacks#change</tt> and advance both arrows. If
581
      # <tt>callbacks#change</tt> is not implemented, then
582
      # <tt>callbacks#discard_a</tt> and <tt>callbacks#discard_b</tt> will be
583
      # called in turn.
584
      #
585
      # The methods for <tt>callbacks#match</tt>, <tt>callbacks#discard_a</tt>,
586
      # <tt>callbacks#discard_b</tt>, and <tt>callbacks#change</tt> are
587
      # invoked with an event comprising the action ("=", "+", "-", or "!",
588
      # respectively), the indicies +ii+ and +jj+, and the elements
589
      # <tt>A[ii]</tt> and <tt>B[jj]</tt>. Return values are discarded by
590
      # #traverse_balanced.
591
      #
592
      # === Context
593
      # Note that +ii+ and +jj+ may not be the same index position, even if
594
      # +a+ and +b+ are considered to be pointing to matching or changed
595
      # elements.
596
    def traverse_balanced(seq1, seq2, callbacks = DiffX::LCS::BalancedCallbacks)
597
      matches = DiffX::LCS.__lcs(seq1, seq2)
598
      a_size = seq1.size
599
      b_size = seq2.size
600
      ai = bj = mb = 0
601
      ma = -1
602
      string = seq1.kind_of?(String)
603

    
604
        # Process all the lines in the match vector.
605
      loop do
606
          # Find next match indices +ma+ and +mb+
607
        loop do
608
          ma += 1
609
          break unless ma < matches.size and matches[ma].nil?
610
        end
611

    
612
        break if ma >= matches.size # end of matches?
613
        mb = matches[ma]
614

    
615
          # Change(seq2)
616
        while (ai < ma) or (bj < mb)
617
          ax = string ? seq1[ai, 1] : seq1[ai]
618
          bx = string ? seq2[bj, 1] : seq2[bj]
619

    
620
          case [(ai < ma), (bj < mb)]
621
          when [true, true]
622
            if callbacks.respond_to?(:change)
623
              event = DiffX::LCS::ContextChange.new('!', ai, ax, bj, bx)
624
              event = yield event if block_given?
625
              callbacks.change(event)
626
              ai += 1
627
              bj += 1
628
            else
629
              event = DiffX::LCS::ContextChange.new('-', ai, ax, bj, bx)
630
              event = yield event if block_given?
631
              callbacks.discard_a(event)
632
              ai += 1
633
              ax = string ? seq1[ai, 1] : seq1[ai]
634
              event = DiffX::LCS::ContextChange.new('+', ai, ax, bj, bx)
635
              event = yield event if block_given?
636
              callbacks.discard_b(event)
637
              bj += 1
638
            end
639
          when [true, false]
640
            event = DiffX::LCS::ContextChange.new('-', ai, ax, bj, bx)
641
            event = yield event if block_given?
642
            callbacks.discard_a(event)
643
            ai += 1
644
          when [false, true]
645
            event = DiffX::LCS::ContextChange.new('+', ai, ax, bj, bx)
646
            event = yield event if block_given?
647
            callbacks.discard_b(event)
648
            bj += 1
649
          end
650
        end
651

    
652
          # Match
653
        ax = string ? seq1[ai, 1] : seq1[ai]
654
        bx = string ? seq2[bj, 1] : seq2[bj]
655
        event = DiffX::LCS::ContextChange.new('=', ai, ax, bj, bx)
656
        event = yield event if block_given?
657
        callbacks.match(event)
658
        ai += 1
659
        bj += 1
660
      end
661

    
662
      while (ai < a_size) or (bj < b_size)
663
        ax = string ? seq1[ai, 1] : seq1[ai]
664
        bx = string ? seq2[bj, 1] : seq2[bj]
665

    
666
        case [(ai < a_size), (bj < b_size)]
667
        when [true, true]
668
          if callbacks.respond_to?(:change)
669
            event = DiffX::LCS::ContextChange.new('!', ai, ax, bj, bx)
670
            event = yield event if block_given?
671
            callbacks.change(event)
672
            ai += 1
673
            bj += 1
674
          else
675
            event = DiffX::LCS::ContextChange.new('-', ai, ax, bj, bx)
676
            event = yield event if block_given?
677
            callbacks.discard_a(event)
678
            ai += 1
679
            ax = string ? seq1[ai, 1] : seq1[ai]
680
            event = DiffX::LCS::ContextChange.new('+', ai, ax, bj, bx)
681
            event = yield event if block_given?
682
            callbacks.discard_b(event)
683
            bj += 1
684
          end
685
        when [true, false]
686
          event = DiffX::LCS::ContextChange.new('-', ai, ax, bj, bx)
687
          event = yield event if block_given?
688
          callbacks.discard_a(event)
689
          ai += 1
690
        when [false, true]
691
          event = DiffX::LCS::ContextChange.new('+', ai, ax, bj, bx)
692
          event = yield event if block_given?
693
          callbacks.discard_b(event)
694
          bj += 1
695
        end
696
      end
697
    end
698

    
699
    PATCH_MAP = { #:nodoc:
700
      :patch => { '+' => '+', '-' => '-', '!' => '!', '=' => '=' },
701
      :unpatch => { '+' => '-', '-' => '+', '!' => '!', '=' => '=' }
702
    }
703

    
704
      # Given a patchset, convert the current version to the new
705
      # version. If +direction+ is not specified (must be
706
      # <tt>:patch</tt> or <tt>:unpatch</tt>), then discovery of the
707
      # direction of the patch will be attempted.
708
    def patch(src, patchset, direction = nil)
709
      string = src.kind_of?(String)
710
        # Start with a new empty type of the source's class
711
      res = src.class.new
712

    
713
        # Normalize the patchset.
714
      patchset = __normalize_patchset(patchset)
715

    
716
      direction ||= DiffX::LCS.__diff_direction(src, patchset)
717
      direction ||= :patch
718

    
719
      ai = bj = 0
720

    
721
      patchset.each do |change|
722
          # Both Change and ContextChange support #action
723
        action = PATCH_MAP[direction][change.action]
724

    
725
        case change
726
        when DiffX::LCS::ContextChange
727
          case direction
728
          when :patch
729
            el = change.new_element
730
            op = change.old_position
731
            np = change.new_position
732
          when :unpatch
733
            el = change.old_element
734
            op = change.new_position
735
            np = change.old_position
736
          end
737

    
738
          case action
739
          when '-' # Remove details from the old string
740
            while ai < op
741
              res << (string ? src[ai, 1] : src[ai])
742
              ai += 1
743
              bj += 1
744
            end
745
            ai += 1
746
          when '+'
747
            while bj < np
748
              res << (string ? src[ai, 1] : src[ai])
749
              ai += 1
750
              bj += 1
751
            end
752

    
753
            res << el
754
            bj += 1
755
          when '='
756
              # This only appears in sdiff output with the SDiff callback.
757
              # Therefore, we only need to worry about dealing with a single
758
              # element.
759
            res << el
760

    
761
            ai += 1
762
            bj += 1
763
          when '!'
764
            while ai < op
765
              res << (string ? src[ai, 1] : src[ai])
766
              ai += 1
767
              bj += 1
768
            end
769

    
770
            bj += 1
771
            ai += 1
772

    
773
            res << el
774
          end
775
        when DiffX::LCS::Change
776
          case action
777
          when '-'
778
            while ai < change.position
779
              res << (string ? src[ai, 1] : src[ai])
780
              ai += 1
781
              bj += 1
782
            end
783
            ai += 1
784
          when '+'
785
            while bj < change.position
786
              res << (string ? src[ai, 1] : src[ai])
787
              ai += 1
788
              bj += 1
789
            end
790

    
791
            bj += 1
792

    
793
            res << change.element
794
          end
795
        end
796
      end
797

    
798
      while ai < src.size
799
        res << (string ? src[ai, 1] : src[ai])
800
        ai += 1
801
        bj += 1
802
      end
803

    
804
      res
805
    end
806

    
807
      # Given a set of patchset, convert the current version to the prior
808
      # version. Does no auto-discovery.
809
    def unpatch!(src, patchset)
810
      DiffX::LCS.patch(src, patchset, :unpatch)
811
    end
812

    
813
      # Given a set of patchset, convert the current version to the next
814
      # version. Does no auto-discovery.
815
    def patch!(src, patchset)
816
      DiffX::LCS.patch(src, patchset, :patch)
817
    end
818

    
819
# private
820
      # Compute the longest common subsequence between the sequenced Enumerables
821
      # +a+ and +b+. The result is an array whose contents is such that
822
      #
823
      #     result = Diff::LCS.__lcs(a, b)
824
      #     result.each_with_index do |e, ii|
825
      #       assert_equal(a[ii], b[e]) unless e.nil?
826
      #     end
827
    def __lcs(a, b)
828
      a_start = b_start = 0
829
      a_finish = a.size - 1
830
      b_finish = b.size - 1
831
      vector = []
832

    
833
        # Prune off any common elements at the beginning...
834
      while (a_start <= a_finish) and
835
            (b_start <= b_finish) and
836
            (a[a_start] == b[b_start])
837
        vector[a_start] = b_start
838
        a_start += 1
839
        b_start += 1
840
      end
841

    
842
        # Now the end...
843
      while (a_start <= a_finish) and
844
            (b_start <= b_finish) and
845
            (a[a_finish] == b[b_finish])
846
        vector[a_finish] = b_finish
847
        a_finish -= 1
848
        b_finish -= 1
849
      end
850

    
851
        # Now, compute the equivalence classes of positions of elements.
852
      b_matches = DiffX::LCS.__position_hash(b, b_start .. b_finish)
853

    
854
      thresh = []
855
      links = []
856

    
857
      (a_start .. a_finish).each do |ii|
858
        ai = a.kind_of?(String) ? a[ii, 1] : a[ii]
859
        bm = b_matches[ai]
860
        kk = nil
861
        bm.reverse_each do |jj|
862
          if kk and (thresh[kk] > jj) and (thresh[kk - 1] < jj)
863
            thresh[kk] = jj
864
          else
865
            kk = DiffX::LCS.__replace_next_larger(thresh, jj, kk)
866
          end
867
          links[kk] = [ (kk > 0) ? links[kk - 1] : nil, ii, jj ] unless kk.nil?
868
        end
869
      end
870

    
871
      unless thresh.empty?
872
        link = links[thresh.size - 1]
873
        while not link.nil?
874
          vector[link[1]] = link[2]
875
          link = link[0]
876
        end
877
      end
878

    
879
      vector
880
    end
881

    
882
      # Find the place at which +value+ would normally be inserted into the
883
      # Enumerable. If that place is already occupied by +value+, do nothing
884
      # and return +nil+. If the place does not exist (i.e., it is off the end
885
      # of the Enumerable), add it to the end. Otherwise, replace the element
886
      # at that point with +value+. It is assumed that the Enumerable's values
887
      # are numeric.
888
      #
889
      # This operation preserves the sort order.
890
    def __replace_next_larger(enum, value, last_index = nil)
891
        # Off the end?
892
      if enum.empty? or (value > enum[-1])
893
        enum << value
894
        return enum.size - 1
895
      end
896

    
897
        # Binary search for the insertion point
898
      last_index ||= enum.size
899
      first_index = 0
900
      while (first_index <= last_index)
901
        ii = (first_index + last_index) >> 1
902

    
903
        found = enum[ii]
904

    
905
        if value == found
906
          return nil
907
        elsif value > found
908
          first_index = ii + 1
909
        else
910
          last_index = ii - 1
911
        end
912
      end
913

    
914
        # The insertion point is in first_index; overwrite the next larger
915
        # value.
916
      enum[first_index] = value
917
      return first_index
918
    end
919

    
920
      # If +vector+ maps the matching elements of another collection onto this
921
      # Enumerable, compute the inverse +vector+ that maps this Enumerable
922
      # onto the collection. (Currently unused.)
923
    def __inverse_vector(a, vector)
924
      inverse = a.dup
925
      (0 ... vector.size).each do |ii|
926
        inverse[vector[ii]] = ii unless vector[ii].nil?
927
      end
928
      inverse
929
    end
930

    
931
      # Returns a hash mapping each element of an Enumerable to the set of
932
      # positions it occupies in the Enumerable, optionally restricted to the
933
      # elements specified in the range of indexes specified by +interval+.
934
    def __position_hash(enum, interval = 0 .. -1)
935
      hash = Hash.new { |hh, kk| hh[kk] = [] }
936
      interval.each do |ii|
937
        kk = enum.kind_of?(String) ? enum[ii, 1] : enum[ii]
938
        hash[kk] << ii
939
      end
940
      hash
941
    end
942

    
943
      # Examine the patchset and the source to see in which direction the
944
      # patch should be applied.
945
      #
946
      # WARNING: By default, this examines the whole patch, so this could take
947
      # some time. This also works better with Diff::LCS::ContextChange or
948
      # Diff::LCS::Change as its source, as an array will cause the creation
949
      # of one of the above.
950
    def __diff_direction(src, patchset, limit = nil)
951
      count = left = left_miss = right = right_miss = 0
952
      string = src.kind_of?(String)
953

    
954
      patchset.each do |change|
955
        count += 1
956

    
957
        case change
958
        when DiffX::LCS::Change
959
            # With a simplistic change, we can't tell the difference between
960
            # the left and right on '!' actions, so we ignore those. On '='
961
            # actions, if there's a miss, we miss both left and right.
962
          element = string ? src[change.position, 1] : src[change.position]
963

    
964
          case change.action
965
          when '-'
966
            if element == change.element
967
              left += 1
968
            else
969
              left_miss += 1
970
            end
971
          when '+'
972
            if element == change.element
973
              right += 1
974
            else
975
              right_miss += 1
976
            end
977
          when '='
978
            if element != change.element
979
              left_miss += 1
980
              right_miss += 1
981
            end
982
          end
983
        when DiffX::LCS::ContextChange
984
          case change.action
985
          when '-' # Remove details from the old string
986
            element = string ? src[change.old_position, 1] : src[change.old_position]
987
            if element == change.old_element
988
              left += 1
989
            else
990
              left_miss += 1
991
            end
992
          when '+'
993
            element = string ? src[change.new_position, 1] : src[change.new_position]
994
            if element == change.new_element
995
              right += 1
996
            else
997
              right_miss += 1
998
            end
999
          when '='
1000
            le = string ? src[change.old_position, 1] : src[change.old_position]
1001
            re = string ? src[change.new_position, 1] : src[change.new_position]
1002

    
1003
            left_miss += 1 if le != change.old_element
1004
            right_miss += 1 if re != change.new_element
1005
          when '!'
1006
            element = string ? src[change.old_position, 1] : src[change.old_position]
1007
            if element == change.old_element
1008
              left += 1
1009
            else
1010
              element = string ? src[change.new_position, 1] : src[change.new_position]
1011
              if element == change.new_element
1012
                right += 1
1013
              else
1014
                left_miss += 1
1015
                right_miss += 1
1016
              end
1017
            end
1018
          end
1019
        end
1020

    
1021
        break if not limit.nil? and count > limit
1022
      end
1023

    
1024
      no_left = (left == 0) and (left_miss >= 0)
1025
      no_right = (right == 0) and (right_miss >= 0)
1026

    
1027
      case [no_left, no_right]
1028
      when [false, true]
1029
        return :patch
1030
      when [true, false]
1031
        return :unpatch
1032
      else
1033
        raise "The provided patchset does not appear to apply to the provided value as either source or destination value."
1034
      end
1035
    end
1036

    
1037
      # Normalize the patchset. A patchset is always a sequence of changes, but
1038
      # how those changes are represented may vary, depending on how they were
1039
      # generated. In all cases we support, we also support the array
1040
      # representation of the changes. The formats are:
1041
      #
1042
      #   [ # patchset <- Diff::LCS.diff(a, b)
1043
      #     [ # one or more hunks
1044
      #       Diff::LCS::Change # one or more changes
1045
      #     ] ]
1046
      #
1047
      #   [ # patchset, equivalent to the above
1048
      #     [ # one or more hunks
1049
      #       [ action, line, value ] # one or more changes
1050
      #     ] ]
1051
      #
1052
      #   [ # patchset <- Diff::LCS.diff(a, b, Diff::LCS::ContextDiffCallbacks)
1053
      #     #       OR <- Diff::LCS.sdiff(a, b, Diff::LCS::ContextDiffCallbacks)
1054
      #     [ # one or more hunks
1055
      #       Diff::LCS::ContextChange # one or more changes
1056
      #     ] ]
1057
      #
1058
      #   [ # patchset, equivalent to the above
1059
      #     [ # one or more hunks
1060
      #       [ action, [ old line, old value ], [ new line, new value ] ]
1061
      #         # one or more changes
1062
      #     ] ]
1063
      #
1064
      #   [ # patchset <- Diff::LCS.sdiff(a, b)
1065
      #     #       OR <- Diff::LCS.diff(a, b, Diff::LCS::SDiffCallbacks)
1066
      #     Diff::LCS::ContextChange # one or more changes
1067
      #   ]
1068
      #
1069
      #   [ # patchset, equivalent to the above
1070
      #     [ action, [ old line, old value ], [ new line, new value ] ]
1071
      #       # one or more changes
1072
      #   ]
1073
      #
1074
      # The result of this will be either of the following.
1075
      #
1076
      #   [ # patchset
1077
      #     Diff::LCS::ContextChange # one or more changes
1078
      #   ]
1079
      #
1080
      #   [ # patchset
1081
      #     Diff::LCS::Change # one or more changes
1082
      #   ]
1083
      #
1084
      # If either of the above is provided, it will be returned as such.
1085
      #
1086
    def __normalize_patchset(patchset)
1087
      patchset.map do |hunk|
1088
        case hunk
1089
        when DiffX::LCS::ContextChange, DiffX::LCS::Change
1090
          hunk
1091
        when Array
1092
          if (not hunk[0].kind_of?(Array)) and hunk[1].kind_of?(Array) and hunk[2].kind_of?(Array)
1093
            DiffX::LCS::ContextChange.from_a(hunk)
1094
          else
1095
            hunk.map do |change|
1096
              case change
1097
              when DiffX::LCS::ContextChange, DiffX::LCS::Change
1098
                change
1099
              when Array
1100
                  # change[1] will ONLY be an array in a ContextChange#to_a call.
1101
                  # In Change#to_a, it represents the line (singular).
1102
                if change[1].kind_of?(Array)
1103
                  DiffX::LCS::ContextChange.from_a(change)
1104
                else
1105
                  DiffX::LCS::Change.from_a(change)
1106
                end
1107
              end
1108
            end
1109
          end
1110
        else
1111
          raise ArgumentError, "Cannot normalise a hunk of class #{hunk.class}."
1112
        end
1113
      end.flatten
1114
    end
1115
  end
1116
end
1117

    
1118
#blocks.rb
1119
class DiffX::LCS::Block
1120
  attr_reader :changes, :insert, :remove
1121

    
1122
  def initialize(chunk)
1123
    @changes = []
1124
    @insert = []
1125
    @remove = []
1126

    
1127
    chunk.each do |item|
1128
      @changes << item
1129
      @remove << item if item.deleting?
1130
      @insert << item if item.adding?
1131
    end
1132
  end
1133

    
1134
  def diff_size
1135
    @insert.size - @remove.size
1136
  end
1137

    
1138
  def op
1139
    case [@remove.empty?, @insert.empty?]
1140
    when [false, false]
1141
      '!'
1142
    when [false, true]
1143
      '-'
1144
    when [true, false]
1145
      '+'
1146
    else # [true, true]
1147
      '^'
1148
    end
1149
  end
1150
end
1151

    
1152
#hunk.rb
1153
class DiffX::LCS::Hunk
1154
    # Create a hunk using references to both the old and new data, as well as
1155
    # the piece of data
1156
  def initialize(data_old, data_new, piece, context, file_length_difference)
1157
      # At first, a hunk will have just one Block in it
1158
    @blocks = [ DiffX::LCS::Block.new(piece) ]
1159
    @data_old = data_old
1160
    @data_new = data_new
1161

    
1162
    before = after = file_length_difference
1163
    after += @blocks[0].diff_size
1164
    @file_length_difference = after # The caller must get this manually
1165

    
1166
      # Save the start & end of each array. If the array doesn't exist
1167
      # (e.g., we're only adding items in this block), then figure out the
1168
      # line number based on the line number of the other file and the
1169
      # current difference in file lengths.
1170
    if @blocks[0].remove.empty?
1171
      a1 = a2 = nil
1172
    else
1173
      a1 = @blocks[0].remove[0].position
1174
      a2 = @blocks[0].remove[-1].position
1175
    end
1176

    
1177
    if @blocks[0].insert.empty?
1178
      b1 = b2 = nil
1179
    else
1180
      b1 = @blocks[0].insert[0].position
1181
      b2 = @blocks[0].insert[-1].position
1182
    end
1183

    
1184
    @start_old = a1 || (b1 - before)
1185
    @start_new = b1 || (a1 + before)
1186
    @end_old   = a2 || (b2 - after)
1187
    @end_new   = b2 || (a2 + after)
1188

    
1189
    self.flag_context = context
1190
  end
1191

    
1192
  attr_reader :blocks
1193
  attr_reader :start_old, :start_new
1194
  attr_reader :end_old, :end_new
1195
  attr_reader :file_length_difference
1196

    
1197
    # Change the "start" and "end" fields to note that context should be added
1198
    # to this hunk
1199
  attr_accessor :flag_context
1200
  def flag_context=(context) #:nodoc:
1201
    return if context.nil? or context.zero?
1202

    
1203
    add_start = (context > @start_old) ? @start_old : context
1204
    @start_old -= add_start
1205
    @start_new -= add_start
1206

    
1207
    if (@end_old + context) > @data_old.size
1208
      add_end = @data_old.size - @end_old
1209
    else
1210
      add_end = context
1211
    end
1212
    @end_old += add_end
1213
    @end_new += add_end
1214
  end
1215

    
1216
  def unshift(hunk)
1217
    @start_old = hunk.start_old
1218
    @start_new = hunk.start_new
1219
    blocks.unshift(*hunk.blocks)
1220
  end
1221

    
1222
    # Is there an overlap between hunk arg0 and old hunk arg1? Note: if end
1223
    # of old hunk is one less than beginning of second, they overlap
1224
  def overlaps?(hunk = nil)
1225
    return nil if hunk.nil?
1226

    
1227
    a = (@start_old - hunk.end_old) <= 1
1228
    b = (@start_new - hunk.end_new) <= 1
1229
    return (a or b)
1230
  end
1231

    
1232
  def diff(format)
1233
    case format
1234
    when :old
1235
      old_diff
1236
    when :unified
1237
      unified_diff
1238
    when :context
1239
      context_diff
1240
    when :ed
1241
      self
1242
    when :reverse_ed, :ed_finish
1243
      ed_diff(format)
1244
    else
1245
      raise "Unknown diff format #{format}."
1246
    end
1247
  end
1248

    
1249
  def each_old(block)
1250
    @data_old[@start_old .. @end_old].each { |e| yield e }
1251
  end
1252

    
1253
  private
1254
    # Note that an old diff can't have any context. Therefore, we know that
1255
    # there's only one block in the hunk.
1256
  def old_diff
1257
    warn "Expecting only one block in an old diff hunk!" if @blocks.size > 1
1258
    op_act = { "+" => 'a', "-" => 'd', "!" => "c" }
1259

    
1260
    block = @blocks[0]
1261

    
1262
      # Calculate item number range. Old diff range is just like a context
1263
      # diff range, except the ranges are on one line with the action between
1264
      # them.
1265
    s = "#{context_range(:old)}#{op_act[block.op]}#{context_range(:new)}\n"
1266
      # If removing anything, just print out all the remove lines in the hunk
1267
      # which is just all the remove lines in the block.
1268
    @data_old[@start_old .. @end_old].each { |e| s << "< #{e}\n" } unless block.remove.empty?
1269
    s << "---\n" if block.op == "!"
1270
    @data_new[@start_new .. @end_new].each { |e| s << "> #{e}\n" } unless block.insert.empty?
1271
    s
1272
  end
1273

    
1274
  def unified_diff
1275
      # Calculate item number range.
1276
    s = "@@ -#{unified_range(:old)} +#{unified_range(:new)} @@\n"
1277

    
1278
      # Outlist starts containing the hunk of the old file. Removing an item
1279
      # just means putting a '-' in front of it. Inserting an item requires
1280
      # getting it from the new file and splicing it in. We splice in
1281
      # +num_added+ items. Remove blocks use +num_added+ because splicing
1282
      # changed the length of outlist.
1283
      #
1284
      # We remove +num_removed+ items. Insert blocks use +num_removed+
1285
      # because their item numbers -- corresponding to positions in the NEW
1286
      # file -- don't take removed items into account.
1287
    lo, hi, num_added, num_removed = @start_old, @end_old, 0, 0
1288

    
1289
    outlist = @data_old[lo .. hi].collect { |e| e.gsub(/^/, ' ') }
1290

    
1291
    @blocks.each do |block|
1292
      block.remove.each do |item|
1293
        op = item.action.to_s # -
1294
        offset = item.position - lo + num_added
1295
        outlist[offset].gsub!(/^ /, op.to_s)
1296
        num_removed += 1
1297
      end
1298
      block.insert.each do |item|
1299
        op = item.action.to_s # +
1300
        offset = item.position - @start_new + num_removed
1301
        outlist[offset, 0] = "#{op}#{@data_new[item.position]}"
1302
        num_added += 1
1303
      end
1304
    end
1305

    
1306
    s << outlist.join("\n")
1307
  end
1308

    
1309
  def context_diff
1310
    s = "***************\n"
1311
    s << "*** #{context_range(:old)} ****\n"
1312
    r = context_range(:new)
1313

    
1314
      # Print out file 1 part for each block in context diff format if there
1315
      # are any blocks that remove items
1316
    lo, hi = @start_old, @end_old
1317
    removes = @blocks.select { |e| not e.remove.empty? }
1318
    if removes
1319
      outlist = @data_old[lo .. hi].collect { |e| e.gsub(/^/, '  ') }
1320
      removes.each do |block|
1321
        block.remove.each do |item|
1322
          outlist[item.position - lo].gsub!(/^ /) { block.op } # - or !
1323
        end
1324
      end
1325
      s << outlist.join("\n")
1326
    end
1327

    
1328
    s << "\n--- #{r} ----\n"
1329
    lo, hi = @start_new, @end_new
1330
    inserts = @blocks.select { |e| not e.insert.empty? }
1331
    if inserts
1332
      outlist = @data_new[lo .. hi].collect { |e| e.gsub(/^/, '  ') }
1333
      inserts.each do |block|
1334
        block.insert.each do |item|
1335
          outlist[item.position - lo].gsub!(/^ /) { block.op } # + or !
1336
        end
1337
      end
1338
      s << outlist.join("\n")
1339
    end
1340
    s
1341
  end
1342

    
1343
  def ed_diff(format)
1344
    op_act = { "+" => 'a', "-" => 'd', "!" => "c" }
1345
    warn "Expecting only one block in an old diff hunk!" if @blocks.size > 1
1346

    
1347
    if format == :reverse_ed
1348
      s = "#{op_act[@blocks[0].op]}#{context_range(:old)}\n"
1349
    else
1350
      s = "#{context_range(:old).gsub(/,/, ' ')}#{op_act[@blocks[0].op]}\n"
1351
    end
1352

    
1353
    unless @blocks[0].insert.empty?
1354
      @data_new[@start_new .. @end_new].each { |e| s << "#{e}\n" }
1355
      s << ".\n"
1356
    end
1357
    s
1358
  end
1359

    
1360
    # Generate a range of item numbers to print. Only print 1 number if the
1361
    # range has only one item in it. Otherwise, it's 'start,end'
1362
  def context_range(mode)
1363
    case mode
1364
    when :old
1365
      s, e = (@start_old + 1), (@end_old + 1)
1366
    when :new
1367
      s, e = (@start_new + 1), (@end_new + 1)
1368
    end
1369

    
1370
    (s < e) ? "#{s},#{e}" : "#{e}"
1371
  end
1372

    
1373
    # Generate a range of item numbers to print for unified diff. Print
1374
    # number where block starts, followed by number of lines in the block
1375
    # (don't print number of lines if it's 1)
1376
  def unified_range(mode)
1377
    case mode
1378
    when :old
1379
      s, e = (@start_old + 1), (@end_old + 1)
1380
    when :new
1381
      s, e = (@start_new + 1), (@end_new + 1)
1382
    end
1383

    
1384
    length = e - s + 1
1385
    first = (length < 2) ? e : s # "strange, but correct"
1386
    (length == 1) ? "#{first}" : "#{first},#{length}"
1387
  end
1388
end
1389

    
1390
#change.rb
1391

    
1392
module DiffX::LCS::ChangeTypeTests
1393
  def deleting?
1394
    @action == '-'
1395
  end
1396

    
1397
  def adding?
1398
    @action == '+'
1399
  end
1400

    
1401
  def unchanged?
1402
    @action == '='
1403
  end
1404

    
1405
  def changed?
1406
    @changed == '!'
1407
  end
1408

    
1409
  def finished_a?
1410
    @changed == '>'
1411
  end
1412

    
1413
  def finished_b?
1414
    @changed == '<'
1415
  end
1416
end
1417

    
1418
  # Represents a simplistic (non-contextual) change. Represents the removal or
1419
  # addition of an element from either the old or the new sequenced enumerable.
1420
class DiffX::LCS::Change
1421
    # Returns the action this Change represents. Can be '+' (#adding?), '-'
1422
    # (#deleting?), '=' (#unchanged?), # or '!' (#changed?). When created by
1423
    # Diff::LCS#diff or Diff::LCS#sdiff, it may also be '>' (#finished_a?) or
1424
    # '<' (#finished_b?).
1425
  attr_reader :action
1426
  attr_reader :position
1427
  attr_reader :element
1428

    
1429
  include Comparable
1430
  def ==(other)
1431
    (self.action == other.action) and
1432
    (self.position == other.position) and
1433
    (self.element == other.element)
1434
  end
1435

    
1436
  def <=>(other)
1437
    r = self.action <=> other.action
1438
    r = self.position <=> other.position if r.zero?
1439
    r = self.element <=> other.element if r.zero?
1440
    r
1441
  end
1442

    
1443
  def initialize(action, position, element)
1444
    @action = action
1445
    @position = position
1446
    @element = element
1447
  end
1448

    
1449
    # Creates a Change from an array produced by Change#to_a.
1450
  def to_a
1451
    [@action, @position, @element]
1452
  end
1453

    
1454
  def self.from_a(arr)
1455
    DiffX::LCS::Change.new(arr[0], arr[1], arr[2])
1456
  end
1457

    
1458
  include DiffX::LCS::ChangeTypeTests
1459
end
1460

    
1461
  # Represents a contextual change. Contains the position and values of the
1462
  # elements in the old and the new sequenced enumerables as well as the action
1463
  # taken.
1464
class DiffX::LCS::ContextChange
1465
    # Returns the action this Change represents. Can be '+' (#adding?), '-'
1466
    # (#deleting?), '=' (#unchanged?), # or '!' (#changed?). When
1467
    # created by Diff::LCS#diff or Diff::LCS#sdiff, it may also be '>'
1468
    # (#finished_a?) or '<' (#finished_b?).
1469
  attr_reader :action
1470
  attr_reader :old_position
1471
  attr_reader :old_element
1472
  attr_reader :new_position
1473
  attr_reader :new_element
1474

    
1475
  include Comparable
1476

    
1477
  def ==(other)
1478
    (@action == other.action) and
1479
    (@old_position == other.old_position) and
1480
    (@new_position == other.new_position) and
1481
    (@old_element == other.old_element) and
1482
    (@new_element == other.new_element)
1483
  end
1484

    
1485
  def inspect(*args)
1486
    %Q(#<#{self.class.name}:#{__id__} @action=#{action} positions=#{old_position},#{new_position} elements=#{old_element.inspect},#{new_element.inspect}>)
1487
  end
1488

    
1489
  def <=>(other)
1490
    r = @action <=> other.action
1491
    r = @old_position <=> other.old_position if r.zero?
1492
    r = @new_position <=> other.new_position if r.zero?
1493
    r = @old_element <=> other.old_element if r.zero?
1494
    r = @new_element <=> other.new_element if r.zero?
1495
    r
1496
  end
1497

    
1498
  def initialize(action, old_position, old_element, new_position, new_element)
1499
    @action = action
1500
    @old_position = old_position
1501
    @old_element = old_element
1502
    @new_position = new_position
1503
    @new_element = new_element
1504
  end
1505

    
1506
  def to_a
1507
    [@action, [@old_position, @old_element], [@new_position, @new_element]]
1508
  end
1509

    
1510
    # Creates a ContextChange from an array produced by ContextChange#to_a.
1511
  def self.from_a(arr)
1512
    if arr.size == 5
1513
      DiffX::LCS::ContextChange.new(arr[0], arr[1], arr[2], arr[3], arr[4])
1514
    else
1515
      DiffX::LCS::ContextChange.new(arr[0], arr[1][0], arr[1][1], arr[2][0],
1516
                                   arr[2][1])
1517
    end
1518
  end
1519

    
1520
    # Simplifies a context change for use in some diff callbacks. '<' actions
1521
    # are converted to '-' and '>' actions are converted to '+'. 
1522
  def self.simplify(event)
1523
    ea = event.to_a
1524

    
1525
    case ea[0]
1526
    when '-'
1527
      ea[2][1] = nil
1528
    when '<'
1529
      ea[0] = '-'
1530
      ea[2][1] = nil
1531
    when '+'
1532
      ea[1][1] = nil
1533
    when '>'
1534
      ea[0] = '+'
1535
      ea[1][1] = nil
1536
    end
1537

    
1538
    DiffX::LCS::ContextChange.from_a(ea)
1539
  end
1540

    
1541
  include DiffX::LCS::ChangeTypeTests
1542
end
1543

    
1544
#callbacks.rb
1545

    
1546
module DiffX::LCS
1547
    # This callback object implements the default set of callback events, which
1548
    # only returns the event itself. Note that #finished_a and #finished_b are
1549
    # not implemented -- I haven't yet figured out where they would be useful.
1550
    #
1551
    # Note that this is intended to be called as is, e.g.,
1552
    #
1553
    #     Diff::LCS.LCS(seq1, seq2, Diff::LCS::DefaultCallbacks)
1554
  class DefaultCallbacks
1555
    class << self
1556
        # Called when two items match.
1557
      def match(event)
1558
        event
1559
      end
1560
        # Called when the old value is discarded in favour of the new value.
1561
      def discard_a(event)
1562
        event
1563
      end
1564
        # Called when the new value is discarded in favour of the old value.
1565
      def discard_b(event)
1566
        event
1567
      end
1568
        # Called when both the old and new values have changed.
1569
      def change(event)
1570
        event
1571
      end
1572

    
1573
      private :new
1574
    end
1575
  end
1576

    
1577
    # An alias for DefaultCallbacks that is used in Diff::LCS#traverse_sequences.
1578
    #
1579
    #     Diff::LCS.LCS(seq1, seq2, Diff::LCS::SequenceCallbacks)
1580
  SequenceCallbacks = DefaultCallbacks
1581
    # An alias for DefaultCallbacks that is used in Diff::LCS#traverse_balanced.
1582
    #
1583
    #     Diff::LCS.LCS(seq1, seq2, Diff::LCS::BalancedCallbacks)
1584
  BalancedCallbacks = DefaultCallbacks
1585
end
1586

    
1587
  # This will produce a compound array of simple diff change objects. Each
1588
  # element in the #diffs array is a +hunk+ or +hunk+ array, where each
1589
  # element in each +hunk+ array is a single Change object representing the
1590
  # addition or removal of a single element from one of the two tested
1591
  # sequences. The +hunk+ provides the full context for the changes.
1592
  #
1593
  #     diffs = Diff::LCS.diff(seq1, seq2)
1594
  #       # This example shows a simplified array format.
1595
  #       # [ [ [ '-',  0, 'a' ] ],   # 1
1596
  #       #   [ [ '+',  2, 'd' ] ],   # 2
1597
  #       #   [ [ '-',  4, 'h' ],     # 3
1598
  #       #     [ '+',  4, 'f' ] ],
1599
  #       #   [ [ '+',  6, 'k' ] ],   # 4
1600
  #       #   [ [ '-',  8, 'n' ],     # 5
1601
  #       #     [ '-',  9, 'p' ],
1602
  #       #     [ '+',  9, 'r' ],
1603
  #       #     [ '+', 10, 's' ],
1604
  #       #     [ '+', 11, 't' ] ] ]
1605
  #
1606
  # There are five hunks here. The first hunk says that the +a+ at position 0
1607
  # of the first sequence should be deleted (<tt>'-'</tt>). The second hunk
1608
  # says that the +d+ at position 2 of the second sequence should be inserted
1609
  # (<tt>'+'</tt>). The third hunk says that the +h+ at position 4 of the
1610
  # first sequence should be removed and replaced with the +f+ from position 4
1611
  # of the second sequence. The other two hunks are described similarly.
1612
  #
1613
  # === Use
1614
  # This callback object must be initialised and is used by the Diff::LCS#diff
1615
  # method.
1616
  #
1617
  #     cbo = Diff::LCS::DiffCallbacks.new
1618
  #     Diff::LCS.LCS(seq1, seq2, cbo)
1619
  #     cbo.finish
1620
  #
1621
  # Note that the call to #finish is absolutely necessary, or the last set of
1622
  # changes will not be visible. Alternatively, can be used as:
1623
  #
1624
  #     cbo = Diff::LCS::DiffCallbacks.new { |tcbo| Diff::LCS.LCS(seq1, seq2, tcbo) }
1625
  #
1626
  # The necessary #finish call will be made.
1627
  #
1628
  # === Simplified Array Format
1629
  # The simplified array format used in the example above can be obtained
1630
  # with:
1631
  #
1632
  #     require 'pp'
1633
  #     pp diffs.map { |e| e.map { |f| f.to_a } }
1634
class DiffX::LCS::DiffCallbacks
1635
    # Returns the difference set collected during the diff process.
1636
  attr_reader :diffs
1637

    
1638
  def initialize # :yields self:
1639
    @hunk = []
1640
    @diffs = []
1641

    
1642
    if block_given?
1643
      begin
1644
        yield self
1645
      ensure
1646
        self.finish
1647
      end
1648
    end
1649
  end
1650

    
1651
    # Finalizes the diff process. If an unprocessed hunk still exists, then it
1652
    # is appended to the diff list.
1653
  def finish
1654
    add_nonempty_hunk
1655
  end
1656

    
1657
  def match(event)
1658
    add_nonempty_hunk
1659
  end
1660

    
1661
  def discard_a(event)
1662
    @hunk << DiffX::LCS::Change.new('-', event.old_position, event.old_element)
1663
  end
1664

    
1665
  def discard_b(event)
1666
    @hunk << DiffX::LCS::Change.new('+', event.new_position, event.new_element)
1667
  end
1668

    
1669
private
1670
  def add_nonempty_hunk
1671
    @diffs << @hunk unless @hunk.empty?
1672
    @hunk = []
1673
  end
1674
end
1675

    
1676
  # This will produce a compound array of contextual diff change objects. Each
1677
  # element in the #diffs array is a "hunk" array, where each element in each
1678
  # "hunk" array is a single change. Each change is a Diff::LCS::ContextChange
1679
  # that contains both the old index and new index values for the change. The
1680
  # "hunk" provides the full context for the changes. Both old and new objects
1681
  # will be presented for changed objects. +nil+ will be substituted for a
1682
  # discarded object.
1683
  #
1684
  #     seq1 = %w(a b c e h j l m n p)
1685
  #     seq2 = %w(b c d e f j k l m r s t)
1686
  #
1687
  #     diffs = Diff::LCS.diff(seq1, seq2, Diff::LCS::ContextDiffCallbacks)
1688
  #       # This example shows a simplified array format.
1689
  #       # [ [ [ '-', [  0, 'a' ], [  0, nil ] ] ],   # 1
1690
  #       #   [ [ '+', [  3, nil ], [  2, 'd' ] ] ],   # 2
1691
  #       #   [ [ '-', [  4, 'h' ], [  4, nil ] ],     # 3
1692
  #       #     [ '+', [  5, nil ], [  4, 'f' ] ] ],
1693
  #       #   [ [ '+', [  6, nil ], [  6, 'k' ] ] ],   # 4
1694
  #       #   [ [ '-', [  8, 'n' ], [  9, nil ] ],     # 5
1695
  #       #     [ '+', [  9, nil ], [  9, 'r' ] ],
1696
  #       #     [ '-', [  9, 'p' ], [ 10, nil ] ],
1697
  #       #     [ '+', [ 10, nil ], [ 10, 's' ] ],
1698
  #       #     [ '+', [ 10, nil ], [ 11, 't' ] ] ] ]
1699
  #
1700
  # The five hunks shown are comprised of individual changes; if there is a
1701
  # related set of changes, they are still shown individually.
1702
  #
1703
  # This callback can also be used with Diff::LCS#sdiff, which will produce
1704
  # results like:
1705
  #
1706
  #     diffs = Diff::LCS.sdiff(seq1, seq2, Diff::LCS::ContextCallbacks)
1707
  #       # This example shows a simplified array format.
1708
  #       # [ [ [ "-", [  0, "a" ], [  0, nil ] ] ],  # 1
1709
  #       #   [ [ "+", [  3, nil ], [  2, "d" ] ] ],  # 2
1710
  #       #   [ [ "!", [  4, "h" ], [  4, "f" ] ] ],  # 3
1711
  #       #   [ [ "+", [  6, nil ], [  6, "k" ] ] ],  # 4
1712
  #       #   [ [ "!", [  8, "n" ], [  9, "r" ] ],    # 5
1713
  #       #     [ "!", [  9, "p" ], [ 10, "s" ] ],
1714
  #       #     [ "+", [ 10, nil ], [ 11, "t" ] ] ] ]
1715
  #
1716
  # The five hunks are still present, but are significantly shorter in total
1717
  # presentation, because changed items are shown as changes ("!") instead of
1718
  # potentially "mismatched" pairs of additions and deletions.
1719
  #
1720
  # The result of this operation is similar to that of
1721
  # Diff::LCS::SDiffCallbacks. They may be compared as:
1722
  #
1723
  #     s = Diff::LCS.sdiff(seq1, seq2).reject { |e| e.action == "=" }
1724
  #     c = Diff::LCS.sdiff(seq1, seq2, Diff::LCS::ContextDiffCallbacks).flatten
1725
  #
1726
  #     s == c # -> true
1727
  #
1728
  # === Use
1729
  # This callback object must be initialised and can be used by the
1730
  # Diff::LCS#diff or Diff::LCS#sdiff methods.
1731
  #
1732
  #     cbo = Diff::LCS::ContextDiffCallbacks.new
1733
  #     Diff::LCS.LCS(seq1, seq2, cbo)
1734
  #     cbo.finish
1735
  #
1736
  # Note that the call to #finish is absolutely necessary, or the last set of
1737
  # changes will not be visible. Alternatively, can be used as:
1738
  #
1739
  #     cbo = Diff::LCS::ContextDiffCallbacks.new { |tcbo| Diff::LCS.LCS(seq1, seq2, tcbo) }
1740
  #
1741
  # The necessary #finish call will be made.
1742
  #
1743
  # === Simplified Array Format
1744
  # The simplified array format used in the example above can be obtained
1745
  # with:
1746
  #
1747
  #     require 'pp'
1748
  #     pp diffs.map { |e| e.map { |f| f.to_a } }
1749
class DiffX::LCS::ContextDiffCallbacks < DiffX::LCS::DiffCallbacks
1750
  def discard_a(event)
1751
    @hunk << DiffX::LCS::ContextChange.simplify(event)
1752
  end
1753

    
1754
  def discard_b(event)
1755
    @hunk << DiffX::LCS::ContextChange.simplify(event)
1756
  end
1757

    
1758
  def change(event)
1759
    @hunk << DiffX::LCS::ContextChange.simplify(event)
1760
  end
1761
end
1762

    
1763
  # This will produce a simple array of diff change objects. Each element in
1764
  # the #diffs array is a single ContextChange. In the set of #diffs provided
1765
  # by SDiffCallbacks, both old and new objects will be presented for both
1766
  # changed <strong>and unchanged</strong> objects. +nil+ will be substituted
1767
  # for a discarded object.
1768
  #
1769
  # The diffset produced by this callback, when provided to Diff::LCS#sdiff,
1770
  # will compute and display the necessary components to show two sequences
1771
  # and their minimized differences side by side, just like the Unix utility
1772
  # +sdiff+.
1773
  # 
1774
  #     same             same
1775
  #     before     |     after
1776
  #     old        <     -
1777
  #     -          >     new
1778
  #
1779
  #     seq1 = %w(a b c e h j l m n p)
1780
  #     seq2 = %w(b c d e f j k l m r s t)
1781
  #
1782
  #     diffs = Diff::LCS.sdiff(seq1, seq2)
1783
  #       # This example shows a simplified array format.
1784
  #       # [ [ "-", [  0, "a"], [  0, nil ] ],
1785
  #       #   [ "=", [  1, "b"], [  0, "b" ] ],
1786
  #       #   [ "=", [  2, "c"], [  1, "c" ] ],
1787
  #       #   [ "+", [  3, nil], [  2, "d" ] ],
1788
  #       #   [ "=", [  3, "e"], [  3, "e" ] ],
1789
  #       #   [ "!", [  4, "h"], [  4, "f" ] ],
1790
  #       #   [ "=", [  5, "j"], [  5, "j" ] ],
1791
  #       #   [ "+", [  6, nil], [  6, "k" ] ],
1792
  #       #   [ "=", [  6, "l"], [  7, "l" ] ],
1793
  #       #   [ "=", [  7, "m"], [  8, "m" ] ],
1794
  #       #   [ "!", [  8, "n"], [  9, "r" ] ],
1795
  #       #   [ "!", [  9, "p"], [ 10, "s" ] ],
1796
  #       #   [ "+", [ 10, nil], [ 11, "t" ] ] ]
1797
  #
1798
  # The result of this operation is similar to that of
1799
  # Diff::LCS::ContextDiffCallbacks. They may be compared as:
1800
  #
1801
  #     s = Diff::LCS.sdiff(seq1, seq2).reject { |e| e.action == "=" }
1802
  #     c = Diff::LCS.sdiff(seq1, seq2, Diff::LCS::ContextDiffCallbacks).flatten
1803
  #
1804
  #     s == c # -> true
1805
  #
1806
  # === Use
1807
  # This callback object must be initialised and is used by the Diff::LCS#sdiff
1808
  # method.
1809
  #
1810
  #     cbo = Diff::LCS::SDiffCallbacks.new
1811
  #     Diff::LCS.LCS(seq1, seq2, cbo)
1812
  #
1813
  # As with the other initialisable callback objects, Diff::LCS::SDiffCallbacks
1814
  # can be initialised with a block. As there is no "fininishing" to be done,
1815
  # this has no effect on the state of the object.
1816
  #
1817
  #     cbo = Diff::LCS::SDiffCallbacks.new { |tcbo| Diff::LCS.LCS(seq1, seq2, tcbo) }
1818
  #
1819
  # === Simplified Array Format
1820
  # The simplified array format used in the example above can be obtained
1821
  # with:
1822
  #
1823
  #     require 'pp'
1824
  #     pp diffs.map { |e| e.to_a }
1825
class DiffX::LCS::SDiffCallbacks
1826
    # Returns the difference set collected during the diff process.
1827
  attr_reader :diffs
1828

    
1829
  def initialize #:yields self:
1830
    @diffs = []
1831
    yield self if block_given?
1832
  end
1833

    
1834
  def match(event)
1835
    @diffs << DiffX::LCS::ContextChange.simplify(event)
1836
  end
1837

    
1838
  def discard_a(event)
1839
    @diffs << DiffX::LCS::ContextChange.simplify(event)
1840
  end
1841

    
1842
  def discard_b(event)
1843
    @diffs << DiffX::LCS::ContextChange.simplify(event)
1844
  end
1845

    
1846
  def change(event)
1847
    @diffs << DiffX::LCS::ContextChange.simplify(event)
1848
  end
1849
end
1850

    
1851
#============copy of diff-lcs, only change names.
1852

    
1853

    
1854

    
1855
module Redmine
1856
  module Scm
1857
    module Adapters    
1858
      class VisualSourceSafeAdapter < AbstractAdapter
1859
      
1860
		def	callOle(obj, name, param, paramtype)
1861
			method = obj.ole_method(name)
1862
			return obj._invoke(method.dispid, param, paramtype)
1863
		end
1864

    
1865
		def	callOleGet(obj, name, param, paramtype)
1866
			methods = obj.ole_get_methods
1867
			method = methods.find{|item| item.name == name}
1868
			return obj._getproperty(method.dispid, param, paramtype)
1869
		end
1870

    
1871
        # VSS OLE
1872
        module VSS_CONST
1873
        end
1874
		VSS_BIN = WIN32OLE.new('SourceSafe')
1875
		WIN32OLE.const_load(VSS_BIN, VSS_CONST)
1876
        
1877
	    VSS_ACTION_ARRAY = [
1878
	        #Japanese -> English
1879
	    	[/^\xe8\xbf\xbd\xe5\x8a\xa0(.+)/, 'Added'] ,#TSUIKA
1880
	    	[/^\xe3\x83\x81\xe3\x82\xa7\xe3\x83\x83\xe3\x82\xaf\xe3\x82\xa4\xe3\x83\xb3(.+)/, 'Checked in'], #CHEKKU IN
1881
	    	[/^\xe4\xbd\x9c\xe6\x88\x90(.+)/, 'Created'] ,#SAKUSEI
1882
	    	[/^\xe3\x83\xa9\xe3\x83\x99\xe3\x83\xab\xe8\xa8\xad\xe5\xae\x9a(.+)/, 'Labeled'] ,#RABERU SETTEI
1883
	    	[/^\xe5\x89\x8a\xe9\x99\xa4(.+)/, 'Deleted'] ,#SAKUJYO
1884
	    	[/^\xe5\xbe\xa9\xe5\x85\x83(.+)/, 'Recovered'], #FUKUGEN
1885
	    	[/^\xe5\x90\x8d\xe5\x89\x8d\xe5\xa4\x89\xe6\x9b\xb4(.+)/, 'Renamed to'], #NAMAEHENKOU
1886
	    	[/^\xe5\x85\xb1\xe6\x9c\x89(.+)/, 'Shared'], #KYOUYUU
1887
	    	#need add any other languages, current(2007/11/9) using only 'Added','Created','Recovered','Deleted','Shared'.
1888
	    ]
1889
	    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'.
1890

    
1891
        # Get info about the VSS repository
1892
        def info
1893
	        @ic ||= Iconv.new('UTF-8', VSS_LANG_CODE)
1894
  			callOle(VSS_BIN, 'Open', 
1895
					[@url, @login, @password],
1896
					[WIN32OLE::VARIANT::VT_BSTR, WIN32OLE::VARIANT::VT_BSTR, WIN32OLE::VARIANT::VT_BSTR])
1897
			vssRootFolder = VSS_BIN.VSSItem("$/", false)
1898
			info = Info.new({
1899
						:root_url => VSS_BIN.SrcSafeIni,
1900
						:lastrev => Revision.new({
1901
							:identifier => Time.parse(vssRootFolder.VSSVersion.Date).localtime,
1902
							:time => Time.parse(vssRootFolder.VSSVersion.Date).localtime,
1903
							:author => @ic.iconv(vssRootFolder.VSSVersion.Username),
1904
						}),
1905
					})
1906
            VSS_BIN.Close
1907
			return info
1908
        rescue Errno::ENOENT, WIN32OLERuntimeError => e
1909
          VSS_BIN.Close
1910
          return nil
1911
        end
1912
        
1913
        # Returns the entry identified by path and revision identifier
1914
        # or nil if entry doesn't exist in the repository
1915
        def entry(path=nil, identifier=nil)
1916
          e = entries(path, identifier)
1917
          e ? e.first : nil
1918
        end
1919
        
1920
        # Returns an Entries collection
1921
        # or nil if the given path doesn't exist in the repository
1922
        def entries(path=nil, identifier=nil)
1923
          @ic ||= Iconv.new('UTF-8', VSS_LANG_CODE)
1924
          @ic2 ||= Iconv.new(VSS_LANG_CODE, 'UTF-8')
1925
          path ||= ''
1926
          identifier = '0' unless identifier and identifier > 0
1927
          entries = Entries.new
1928

    
1929
          if /^\// =~ path
1930
            path = path[1..(path.length)]
1931
          end
1932
          if path == ''
1933
            dir = "$/"
1934
          else
1935
            dir = path
1936
          end
1937
		  #STDERR.puts "entries #{dir}, #{identifier}"
1938
		  callOle(VSS_BIN, 'Open', 
1939
					[@url, @login, @password],
1940
					[WIN32OLE::VARIANT::VT_BSTR, WIN32OLE::VARIANT::VT_BSTR, WIN32OLE::VARIANT::VT_BSTR])
1941
		  begin
1942
	          vssFolder = VSS_BIN.VSSItem(dir, false)
1943
          rescue
1944
	          vssFolder = VSS_BIN.VSSItem(@ic2.iconv(dir), false)
1945
          end
1946
          if vssFolder.Type == 0
1947
       		#is Project
1948
			  vssItems = vssFolder.Version(identifier.to_s).Items
1949
			  vssItems.each{|entry|
1950
			    name = begin
1951
			      @ic.iconv(entry.Name)
1952
			    rescue
1953
		          entry.Name
1954
			    end
1955
			    spec = begin
1956
			      @ic.iconv(entry.Spec)
1957
			    rescue
1958
			      entry.Spec
1959
			    end
1960
			    username = begin
1961
			      @ic.iconv(entry.VSSVersion.Username)
1962
			    rescue
1963
			      entry.VSSVersion.Username
1964
			    end
1965
			    tm = Time.parse(entry.VSSVersion.Date).localtime
1966
			    isFile = entry.Type
1967
			  	entries << Entry.new({
1968
			  		:name => name,
1969
			  		:path => spec,
1970
			  		:kind => (isFile == 0 ? 'dir' : 'file'),
1971
			  		:size => (isFile == 0 ? 0 : entry.Size),
1972
			  		:lastrev => Revision.new({
1973
			  			:identifier => tm,
1974
			  			:time => tm,
1975
			  			:author => username,
1976
			  		})
1977
			  	})
1978
			  }
1979
		  else
1980
      		#is File
1981
		    if identifier == '0'
1982
			  	entry = vssFolder
1983
		    else
1984
			  	entry = vssFolder.Version(identifier.to_s)
1985
		  	end
1986
			entry.Versions.each{|entry2|
1987
			    if entry2.Label == ''
1988
			      name = begin
1989
			        @ic.iconv(entry2.VSSItem.Name)
1990
			      rescue
1991
			        entry2.VSSItem.Name
1992
			      end
1993
			      spec = begin
1994
			        @ic.iconv(entry2.VSSItem.Spec)
1995
			      rescue
1996
			        entry2.VSSItem.Spec
1997
			      end
1998
			      username = begin
1999
			        @ic.iconv(entry2.Username)
2000
			      rescue
2001
			        entry2.Username
2002
			      end
2003
			      tm = Time.parse(entry2.Date).localtime
2004
				  entries << Entry.new({
2005
				  		:name => name,
2006
				  		:path => spec,
2007
				  		:kind => 'file',
2008
				  		:size => entry2.VSSItem.Size,
2009
				  		:lastrev => Revision.new({
2010
				  			:identifier => tm,
2011
				  			:time => tm,
2012
				  			:author => username,
2013
				  		})
2014
				  	})
2015
				else
2016
				#Label
2017
			  	end
2018
			}
2019

    
2020
		  end
2021
          VSS_BIN.Close
2022
          entries.sort_by_name
2023
        rescue Errno::ENOENT, WIN32OLERuntimeError => e
2024
          VSS_BIN.Close
2025
          raise CommandFailed
2026
        end
2027

    
2028
=begin	#maybe not need
2029
		def revisions_merge(revisions)
2030
		  STDERR.puts "revisions_merge (#{revisions.size}) #{Time.now}"
2031
          revisions.sort!{|a, b| a.time <=> b.time}
2032
          revisions2 = Revisions.new
2033
          if revisions.size > 0
2034
            #revisions2 << revisions[0].dup
2035
     	    #STDERR.puts "create #{revisions[0].paths} @ #{revisions[0].time}"
2036
          
2037
            time_delta = 10.seconds
2038
            revisions.each{|revision|
2039
              if revisions2.size == 0
2040
            	revisions2 << revision.dup
2041
              else
2042
                revision2 = revisions2[-1]
2043
            	if revision.author == revision2.author && revision.message == revision2.message && (revision.time > revision2.time - time_delta && revision.time < revision2.time + time_delta)
2044
					STDERR.puts "Merge #{revisions2[-1].paths} and #{revision.paths}"
2045
            		revisions2[-1].paths += revision.paths.dup
2046
            		#STDERR.puts "merge #{revision.paths}"
2047
            	else
2048
            		revisions2 << revision.dup
2049
       	          #STDERR.puts "create #{revision.paths} @ #{revision.time}"
2050
            	end
2051
           	  end
2052
            }
2053
          end
2054
		  STDERR.puts "revisions_merge end (#{revisions2.size}) #{Time.now}"
2055
          return revisions2
2056
		end
2057
=end
2058

    
2059
		def revision_for_one(identifier_from, identifier_to, vssItem)
2060
	      @ic ||= Iconv.new('UTF-8', VSS_LANG_CODE)
2061
		  #STDERR.puts "revision_for_one #{vssItem.Spec}, #{vssItem.VersionNumber}, #{vssItem.VSSVersion.Date}"
2062
          revisions = Revisions.new
2063
          if Time.parse(vssItem.VSSVersion.Date).localtime > identifier_from
2064
              #STDERR.puts "Time Check Pass! #{identifier_from}"
2065
              isFile = vssItem.Type
2066
	          vssItem.Versions.each{|version|
2067
	          	tm = Time.parse(version.Date).localtime
2068
				if tm > identifier_from && tm <= identifier_to
2069
                    #STDERR.puts "Rev Check Pass! #{tm} > #{identifier_from}"
2070
					begin
2071
			            spec = begin
2072
				            @ic.iconv(version.vssItem.Spec)
2073
			            rescue
2074
			            	version.vssItem.Spec
2075
			            end
2076
			            act = begin
2077
			              @ic.iconv(version.Action)
2078
			            rescue
2079
	  		              version.Action
2080
			            end
2081
			        	comment = begin
2082
			        	  @ic.iconv(version.Comment)
2083
			        	rescue
2084
			        	  version.Comment
2085
			        	end
2086
					    username = begin
2087
					      @ic.iconv(version.Username)
2088
					    rescue
2089
					      version.Username
2090
					    end
2091
				        #JPN->ENG Conv, assume UTF-8 string never conflict
2092
						VSS_ACTION_ARRAY.each{|x|
2093
							if x[0] =~ act
2094
							  act = x[1] + $1
2095
							end
2096
						}
2097
					    if isFile == 0 and (/^Deleted\s(.+)/ =~ act or /^Recovered\s(.+)/ =~ act)
2098
					    #Deleted/Recovered is recorded at Folder. so change spec
2099
					    	s = $1
2100
					    	unless /\/$/ =~ spec
2101
					    	  spec += '/'
2102
					    	end
2103
					    	spec += s
2104
						end
2105
					    if isFile == 0 and /^Added\s(.+)/ =~ act
2106
					    #maybe this information include File's VSSVersion, so Skip
2107
					    else
2108
				          	revisions << Revision.new({
2109
				          			:identifier => tm,
2110
				          			:author => username,
2111
				          			:time => tm,
2112
				          			:message => comment,
2113
				          			:paths => [{
2114
						        		:action => act,
2115
						        		:path => spec,
2116
						        		:from_path => spec,
2117
						          		:revision => tm,
2118
				          			  }],
2119
				          		})
2120
					    end
2121
		          	rescue WIN32OLERuntimeError => e
2122
		              logger.error("Error VSS revisions making: #{e.message}")
2123
		              #STDERR.puts("Error VSS revisions making: #{e.message}")
2124
		              #STDERR.puts("version #{version.Comment}, #{version.Date}, #{version.Label}, #{version.VersionNumber}, #{version.Username}")
2125
		          	end
2126
	          	end
2127
            	#escape for memory over...
2128
            	#version.ole_free	#Danger, not recommended
2129
	          }
2130
          end
2131
          #STDERR.puts "for_one/#{vssItem.Spec} : #{revisions}"
2132
          #GC.start
2133
          return revisions
2134
		end
2135

    
2136
		def	revision_childs(path, identifier_from, identifier_to, vssFolder)
2137
		  #STDERR.puts "revision_childs #{path}"
2138
          revisions = Revisions.new
2139
          vssFolder.Items(false).each{|item|
2140
          	if item.Type == 0
2141
          		#is Project
2142
          		revisions.concat(revision_childs(item.Spec, identifier_from, identifier_to, item))
2143
          	end
2144
          	revisions.concat(revision_for_one(identifier_from, identifier_to, item))
2145
          	#escape for memory over...
2146
          	@revisions_loop_counter += 1
2147
          	#item.ole_free	#Danger, not recommended
2148
          	if @revisions_loop_counter > 1000
2149
          	  GC.start
2150
          	  @revisions_loop_counter = 0
2151
          	end
2152
          }
2153
          #STDERR.puts "childs/#{vssFolder.Spec} : #{revisions}"
2154
          #GC.start
2155
          return revisions
2156
		end
2157

    
2158
        def revisions(path=nil, identifier_from=nil, identifier_to=nil, options={})
2159
          STDERR.puts "revisions #{path}, #{identifier_from}, #{identifier_to}, #{options}"
2160
		  @revisions_loop_counter = 0
2161
          path ||= ''
2162
          identifier_from = Time.parse('2000/08/01 00:00:00').localtime unless identifier_from
2163
          identifier_to = Time.now.localtime
2164
          revisions = Revisions.new
2165
          
2166
          if /^\// =~ path
2167
            path = path[1..(path.length)]
2168
          end
2169
          if path == ''
2170
            dir = "$/"
2171
          else
2172
            dir = path
2173
          end
2174
  		  callOle(VSS_BIN, 'Open', 
2175
					[@url, @login, @password],
2176
					[WIN32OLE::VARIANT::VT_BSTR, WIN32OLE::VARIANT::VT_BSTR, WIN32OLE::VARIANT::VT_BSTR])
2177
          vssFolder = VSS_BIN.VSSItem(dir, false)
2178
          revisions += revision_childs(dir, identifier_from, identifier_to, vssFolder)
2179
          VSS_BIN.Close
2180
          revisions
2181
        rescue Errno::ENOENT, WIN32OLERuntimeError => e
2182
          VSS_BIN.Close
2183
          raise CommandFailed
2184
        end
2185
        
2186
        def diff(path, identifier_from, identifier_to=nil, type2="inline")
2187
          if /^\// =~ path
2188
            path = path[1..(path.length)]
2189
          end
2190
		  #STDERR.puts "<VSS> diff #{path}, #{identifier_from}, #{identifier_to}, #{type2}"
2191
		  file1 = cat(path, identifier_from)
2192
		  file2 = cat(path, identifier_to)
2193
		  if file1 and file2
2194
			file1x = file1.gsub("\r\n","\n").gsub("\r", "\n").split("\n")
2195
			file2x = file2.gsub("\r\n","\n").gsub("\r", "\n").split("\n")
2196
		  	#STDERR.puts "<VSS diff> file1 and file2 found"
2197

    
2198
		  	df = DiffX::LCS.diff(file1x, file2x)
2199
		  	diff = ["Index: #{path}",
2200
		  			"===================================================================",
2201
		  			"--- #{path} (#{identifier_from})",
2202
		  			"+++ #{path} (#{identifier_to})",
2203
		  		]
2204
		  	df.each{|x|
2205
		  		d = DiffX::LCS::Hunk.new(file1x, file2x, x, 3, 0)
2206
		  		diff << d.diff(:unified)
2207
		  	}
2208
		  	dtl = DiffTableList.new(diff.join("\n").split("\n"), type2)
2209
		  	return	dtl
2210
		  end
2211
	  	  #STDERR.puts "<VSS diff> ERROR! file1:#{file1}, file2:#{file2}"
2212
		  return nil
2213
        rescue Errno::ENOENT => e
2214
          raise CommandFailed    
2215
        end
2216
        
2217
        def cat(path, identifier=nil)
2218
          @ic2 ||= Iconv.new(VSS_LANG_CODE, 'UTF-8')
2219
		  #STDERR.puts "cat #{path}, #{identifier}"
2220
          if identifier
2221
			  identifier = Time.parse(identifier)
2222
          else
2223
	          identifier = Time.now.localtime
2224
		  end
2225
          if path == ''
2226
            dir = "$/"
2227
          else
2228
            dir = path
2229
          end
2230
          begin
2231
		    callOle(VSS_BIN, 'Open', 
2232
					[@url, @login, @password],
2233
					[WIN32OLE::VARIANT::VT_BSTR, WIN32OLE::VARIANT::VT_BSTR, WIN32OLE::VARIANT::VT_BSTR])
2234
		  rescue
2235
		    VSS_BIN.Close
2236
            raise CommandFailed
2237
		  end
2238
		  begin
2239
            vssFolder = VSS_BIN.VSSItem(dir, false).version(identifier.strftime("%Y/%m/%d %H:%M:%S"))
2240
          rescue
2241
            vssFolder = VSS_BIN.VSSItem(@ic2.iconv(dir), false).version(identifier.strftime("%Y/%m/%d %H:%M:%S"))
2242
          end
2243

    
2244

    
2245
          if vssFolder.Type == 0
2246
          	return 'Target is Project.'
2247
          end
2248
          if vssFolder.Binary == true
2249
          	#return 'Target is Binary.'
2250
          end
2251

    
2252
          file = Tempfile.new('redminevss')
2253
          filename = File.expand_path(file.path)
2254
          file.close(true)
2255

    
2256
          vssFolder.Get(filename, false)
2257
          data = ''
2258
		  open(filename, 'rb'){|f|
2259
		    data = f.read
2260
		  }
2261
          VSS_BIN.Close
2262
		  return data.dup
2263
        rescue Errno::ENOENT, WIN32OLERuntimeError => e
2264
          VSS_BIN.Close
2265
          raise CommandFailed    
2266
        end
2267
		
2268
		def get_previous_revision(path, rev)
2269
		  #STDERR.puts "get_previous_revision #{path}, #{rev}"
2270
          if /^\// =~ path
2271
            path = path[1..(path.length)]
2272
          end
2273
          if path == ''
2274
            dir = "$/"
2275
          else
2276
            dir = path
2277
          end
2278
          begin
2279
		    callOle(VSS_BIN, 'Open', 
2280
					[@url, @login, @password],
2281
					[WIN32OLE::VARIANT::VT_BSTR, WIN32OLE::VARIANT::VT_BSTR, WIN32OLE::VARIANT::VT_BSTR])
2282
		  rescue
2283
		    VSS_BIN.Close
2284
            raise CommandFailed
2285
		  end
2286
          vssFolder = VSS_BIN.VSSItem(dir, false)
2287
          vssVersions = vssFolder.Version(rev).Versions(VSS_CONST::VSSFLAG_RECURSYES)
2288
          prev = nil
2289
          vssVersions.each{|version|
2290
			if version.Date == rev
2291
				if prev
2292
			        VSS_BIN.Close
2293
					return	Time.parse(prev.Date).localtime.strftime("%Y/%m/%d %H:%M:%S")
2294
				else
2295
			        VSS_BIN.Close
2296
					return	rev
2297
				end
2298
          	end
2299
          	prev = version
2300
          }
2301
          VSS_BIN.Close
2302
          return	rev
2303
		end
2304

    
2305
      end
2306
    end
2307
  end
2308
end
(3-3/8)