Project

General

Profile

Patch #1260 » VisualSourceSafe.diff

Add SCM:Microsoft Visual SourceSafe - Aruo Miura, 2008-05-20 03:24

View differences:

app/helpers/repositories_helper.rb (working copy)
75 75
                                           :onchange => "this.name='repository[password]';"))
76 76
  end
77 77

  
78
  def visual_source_safe_field_tags(form, repository)
79
      content_tag('p', form.text_field(:url, :size => 60, :required => true, :disabled => (repository && !repository.root_url.blank?)) +
80
                       '<br />(*/srcsafe.ini)') +
81
      content_tag('p', form.text_field(:login, :size => 30)) +
82
      content_tag('p', form.password_field(:password, :size => 30))
83
      #content_tag('p', select_tag('vss_lang', "<option>en</option><option>ja</option>"))
84
  end
85

  
78 86
  def darcs_field_tags(form, repository)
79 87
      content_tag('p', form.text_field(:url, :label => 'Root directory', :size => 60, :required => true, :disabled => (repository && !repository.new_record?)))
80 88
  end
app/models/repository/visual_source_safe.rb (revision 0)
1
# redMine - project management software
2
# Copyright (C) 2006-2007  Jean-Philippe Lang
3
#
4
# This program is free software; you can redistribute it and/or
5
# modify it under the terms of the GNU General Public License
6
# as published by the Free Software Foundation; either version 2
7
# of the License, or (at your option) any later version.
8
# 
9
# This program is distributed in the hope that it will be useful,
10
# but WITHOUT ANY WARRANTY; without even the implied warranty of
11
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12
# GNU General Public License for more details.
13
# 
14
# You should have received a copy of the GNU General Public License
15
# along with this program; if not, write to the Free Software
16
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
17

  
18
require 'redmine/scm/adapters/visual_source_safe_adapter'
19
require 'redmine/scm/adapters/abstract_adapter'
20

  
21
class Repository::VisualSourceSafe < Repository
22
  attr_protected :root_url
23
  validates_presence_of :url
24
  validates_format_of :url, :with => /^.*srcsafe\.ini/i
25

  
26
  def scm_adapter
27
    Redmine::Scm::Adapters::VisualSourceSafeAdapter
28
  end
29
  
30
  def self.scm_name
31
    'VisualSourceSafe'
32
  end
33

  
34
  def fetch_changesets
35
	#use like CVS
36

  
37
	STDERR.puts "fetch_changeset start!"
38

  
39
    #not the preferred way with CVS. maybe we should introduce always a cron-job for this
40
    last_commit = changesets.maximum(:committed_on)
41
    
42
    # some nifty bits to introduce a commit-id with cvs
43
    # natively cvs doesn't provide any kind of changesets, there is only a revision per file.
44
    # we now take a guess using the author, the commitlog and the commit-date.
45
    
46
    # last one is the next step to take. the commit-date is not equal for all 
47
    # commits in one changeset. cvs update the commit-date when the *,v file was touched. so
48
    # we use a small delta here, to merge all changes belonging to _one_ changeset
49
    #time_delta=10.seconds
50
    
51
    time_delta=10.seconds
52
    fetch_since = latest_changeset ? latest_changeset.committed_on : nil
53
    revisions = nil
54
    transaction do
55
      revisions = scm.revisions('', fetch_since, nil, :with_paths => true)
56
      STDERR.puts "fetch_changeset revisions.size = #{revisions.size}"
57
      STDERR.puts "changesets.size = #{changesets.size}"
58
      tmp_rev_num = changesets.size + 1
59
      revisions.each do |revision|
60
        # only add the change to the database, if it doen't exists. the cvs log
61
        # is not exclusive at all. 
62
        cs1 = changes.find_by_path_and_revision(scm.with_leading_slash(revision.paths[0][:path]), revision.paths[0][:revision])
63
        unless cs1
64
          revision
65
          cs=Changeset.find(:first, :conditions=>{
66
            :committed_on=>revision.time-time_delta..revision.time+time_delta,
67
            :committer=>revision.author,
68
            :comments=>revision.message
69
          })
70
        
71
          # create a new changeset.... 
72
          unless cs 
73
            # we use a temporaray revision number here (just for inserting)
74
            # later on, we calculate a continous positive number
75
            latest = changesets.find(:first, :order => 'id DESC')
76
            cs = Changeset.create(:repository => self,
77
                                  :revision => "_#{tmp_rev_num}", 
78
                                  :committer => revision.author, 
79
                                  :committed_on => revision.time,
80
                                  :comments => revision.message)
81
            tmp_rev_num += 1
82
            STDERR.puts "Create ChangeSet"
83
          end
84
        
85
          #convert CVS-File-States to internal Action-abbrevations
86
          #default action is (M)odified
87
          action="M"
88
          act = revision.paths[0][:action]
89
          #need check only 'Added' or 'Created' or 'Deleted'
90
		  if /^Added/ =~ act or /^Created/ =~ act or /^Recovered/ =~ act or /^Shared/ =~ act
91
		    action="A"
92
		  elsif /^Deleted/ =~ act
93
		    action="D"
94
		  end
95
         Change.create(:changeset => cs,
96
          :action => action,
97
          :path => scm.with_leading_slash(revision.paths[0][:path]),
98
          :revision => revision.paths[0][:revision],
99
          :branch => revision.paths[0][:branch]
100
          )
101
        end
102
      end
103
      
104
      c = changesets.find(:first, :order => 'committed_on DESC, id DESC', :conditions => "revision NOT LIKE '_%'")
105
      next_rev = c.nil? ? 1 : (c.revision.to_i + 1)
106
      changesets.find(:all, :order => 'committed_on ASC, id ASC', :conditions => "revision LIKE '_%'").each do |changeset|
107
        changeset.update_attribute :revision, next_rev
108
        next_rev += 1
109
      end
110
    end
111
	STDERR.puts "fetch_changeset end!"
112
  end
113

  
114
  def diff(path, rev, rev_to, type2)
115
    #convert rev to revision. CVS can't handle changesets here
116
    diffx = Redmine::Scm::Adapters::DiffTableList.new([], type2)
117
    changeset_from=changesets.find_by_revision(rev)
118
    if rev_to.to_i > 0 
119
      changeset_to=changesets.find_by_revision(rev_to)
120
    end
121
    changeset_from.changes.each() do |change_from|
122
      
123
      revision_from=nil
124
      revision_to=nil      
125
      
126
      revision_from=change_from.revision if path.nil? || (change_from.path.starts_with? scm.with_leading_slash(path))
127
      
128
      if revision_from
129
        if changeset_to
130
          changeset_to.changes.each() do |change_to|
131
            revision_to=change_to.revision if change_to.path==change_from.path 
132
          end
133
        end
134
        unless revision_to
135
          revision_to=scm.get_previous_revision(change_from.path, revision_from)
136
        end
137
        diff2 = scm.diff(change_from.path, revision_from, revision_to, type2)
138
        diffx=diff2 if diff2
139
      end
140
    end
141
    return diffx
142
  end
143

  
144
end
db/migrate/055_add_repositories_type.rb (working copy)
3 3
    add_column :repositories, :type, :string    
4 4
    # Set class name for existing SVN repositories
5 5
    Repository.update_all "type = 'Subversion'"
6
    Repository.update_all "type = 'VisualSourceSafe'"
6 7
  end
7 8

  
8 9
  def self.down
lib/redmine.rb (working copy)
11 11
  # RMagick is not available
12 12
end
13 13

  
14
REDMINE_SUPPORTED_SCM = %w( Subversion Darcs Mercurial Cvs Bazaar Git )
14
REDMINE_SUPPORTED_SCM = %w( Subversion Darcs Mercurial Cvs Bazaar Git VisualSourceSafe)
15 15

  
16 16
# Permissions
17 17
Redmine::AccessControl.map do |map|
lib/redmine/scm/adapters/visual_source_safe_adapter.rb (revision 0)
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
... This diff was truncated because it exceeds the maximum size that can be displayed.
(1-1/2)