File Coverage

blib/lib/Text/Diff3.pm
Criterion Covered Total %
statement 207 238 86.9
branch 48 54 88.8
condition 26 36 72.2
subroutine 12 12 100.0
pod 3 3 100.0
total 296 343 86.3


line stmt bran cond sub pod time code
1             package Text::Diff3;
2 10     10   658556 use 5.006;
  10         63  
  10         376  
3 10     10   55 use strict;
  10         19  
  10         268  
4 10     10   46 use warnings;
  10         23  
  10         235  
5              
6 10     10   8927 use version; our $VERSION = '0.10';
  10         28992  
  10         61  
7              
8             our @EXPORT_OK = qw(diff3 merge diff);
9              
10             # Two way diff procedure for function style diff3.
11             # Change this as your like.
12             our $DIFF_PROC = \&_diff_heckel;
13              
14 94     94 1 21649 sub diff { return $DIFF_PROC->(@_) }
15              
16             sub import {
17 10     10   107 my($class, @arg) = @_;
18 10         29 my $pkg = caller;
19 10         24 my %opt = map { $_ => 1 } @arg;
  6         31  
20 10         26 my %export;
21 10 100       56 if (exists $opt{':factory'}) {
22             # compatibility for old version's component style interface.
23 6         683 warn "Text::Diff3 ':factory' is deprecated.\n";
24 6         4647 require Text::Diff3::Factory;
25 6         3994 return;
26             }
27 4 50       16 if (exists $opt{':all'}) {
28 0         0 %export = map { $_ => 1 } @EXPORT_OK;
  0         0  
29             }
30             else {
31 4         10 for my $func (@EXPORT_OK) {
32 12 50       33 if (exists $opt{$func}) {
33 0         0 $export{$func} = 1;
34             }
35             }
36             }
37 4         45 for my $func (keys %export) {
38 10     10   3357 no strict 'refs';
  10         21  
  10         29098  
39 0         0 *{"${pkg}::${func}"} = \&{"${class}::${func}"};
  0         0  
  0         0  
40             }
41 4         5568 return;
42             }
43              
44             # the three-way diff based on the GNU diff3.c by R. Smith.
45             sub diff3 {
46 42     42 1 65257 my($text0, $text2, $text1) = @_;
47             # diff result => [[$cmd, $loA, $hiA, $loB, $hiB], ...]
48 42         101 my @diff2 = (
49             diff($text2, $text0),
50             diff($text2, $text1),
51             );
52 42         66 my $diff3 = [];
53 42         95 my $range3 = [undef, 0, 0, 0, 0, 0, 0];
54 42   100     46 while (@{$diff2[0]} || @{$diff2[1]}) {
  108         265  
  62         188  
55             # find a continual range in text2 $lo2..$hi2
56             # changed by text0 or by text1.
57             #
58             # diff2[0] 222 222222222
59             # text2 ...L!!!!!!!!!!!!!!!!!!!!H...
60             # diff2[1] 222222 22 2222222
61 66         127 my @range2 = ([], []);
62 66         128 my $i =
63 46         140 ! @{$diff2[0]} ? 1
64 66 100       71 : ! @{$diff2[1]} ? 0
    100          
    100          
65             : $diff2[0][0][1] <= $diff2[1][0][1] ? 0
66             : 1;
67 66         106 my $j = $i;
68 66         78 my $k = $i ^ 1;
69 66         86 my $hi = $diff2[$j][0][2];
70 66         66 push @{$range2[$j]}, shift @{$diff2[$j]};
  66         77  
  66         108  
71 66   100     75 while (@{$diff2[$k]} && $diff2[$k][0][1] <= $hi + 1) {
  86         299  
72 20         30 my $hi_k = $diff2[$k][0][2];
73 20         20 push @{$range2[$k]}, shift @{$diff2[$k]};
  20         25  
  20         36  
74 20 50       43 if ($hi < $hi_k) {
75 0         0 $hi = $hi_k;
76 0         0 $j = $k;
77 0         0 $k = $k ^ 1;
78             }
79             }
80 66         104 my $lo2 = $range2[$i][ 0][1];
81 66         85 my $hi2 = $range2[$j][-1][2];
82             # take the corresponding ranges in text0 $lo0..$hi0
83             # and in text1 $lo1..$hi1.
84             #
85             # text0 ..L!!!!!!!!!!!!!!!!!!!!!!!!!!!!H...
86             # diff2[0] 222 222222222
87             # text2 ...00!1111!000!!00!111111...
88             # diff2[1] 222222 22 2222222
89             # text1 ...L!!!!!!!!!!!!!!!!H...
90 66         68 my($lo0, $hi0);
91 66 100       62 if (@{$range2[0]}) {
  66         111  
92 42         78 $lo0 = $range2[0][ 0][3] - $range2[0][ 0][1] + $lo2;
93 42         73 $hi0 = $range2[0][-1][4] - $range2[0][-1][2] + $hi2;
94             }
95             else {
96 24         38 $lo0 = $range3->[2] - $range3->[6] + $lo2;
97 24         38 $hi0 = $range3->[2] - $range3->[6] + $hi2;
98             }
99 66         71 my($lo1, $hi1);
100 66 100       62 if (@{$range2[1]}) {
  66         120  
101 44         84 $lo1 = $range2[1][ 0][3] - $range2[1][ 0][1] + $lo2;
102 44         73 $hi1 = $range2[1][-1][4] - $range2[1][-1][2] + $hi2;
103             }
104             else {
105 22         31 $lo1 = $range3->[4] - $range3->[6] + $lo2;
106 22         33 $hi1 = $range3->[4] - $range3->[6] + $hi2;
107             }
108 66         146 $range3 = [undef, $lo0, $hi0, $lo1, $hi1, $lo2, $hi2];
109             # detect type of changes.
110 66 100       86 if (! @{$range2[0]}) {
  66 100       113  
  42 100       95  
111 24         40 $range3->[0] = '1';
112             }
113             elsif (! @{$range2[1]}) {
114 22         35 $range3->[0] = '0';
115             }
116             elsif ($hi0 - $lo0 != $hi1 - $lo1) {
117 4         9 $range3->[0] = 'A';
118             }
119             else {
120 16         24 $range3->[0] = '2';
121 16         30 for my $d (0 .. $hi0 - $lo0) {
122 26         47 my($i0, $i1) = ($lo0 + $d - 1, $lo1 + $d - 1);
123 26   33     56 my $ok0 = 0 <= $i0 && $i0 <= $#{$text0};
124 26   33     58 my $ok1 = 0 <= $i1 && $i1 <= $#{$text1};
125 26 100 66     170 if ($ok0 ^ $ok1 || ($ok0 && $text0->[$i0] ne $text1->[$i1])) {
      33        
126 4         11 $range3->[0] = 'A';
127 4         5 last;
128             }
129             }
130             }
131 66         107 push @{$diff3}, $range3;
  66         200  
132             }
133 42         140 return $diff3;
134             }
135              
136             sub merge {
137 21     21 1 48340 my($mytext, $origtext, $yourtext) = @_;
138 21         43 my $text3 = [$mytext, $yourtext, $origtext];
139 21         57 my $res = {conflict => 0, body => []};
140 21         27 my $diff3 = diff3(@{$text3}[0, 2, 1]);
  21         59  
141 21         26 my $i2 = 1;
142 21         23 for my $r3 (@{$diff3}) {
  21         35  
143 33         54 for my $lineno ($i2 .. $r3->[5] - 1) {
144 50         43 push @{$res->{body}}, $text3->[2][$lineno - 1];
  50         113  
145             }
146 33 100       85 if ($r3->[0] eq '0') {
    100          
147 11         21 for my $lineno ($r3->[1] .. $r3->[2]) {
148 15         12 push @{$res->{body}}, $text3->[0][$lineno - 1];
  15         33  
149             }
150             }
151             elsif ($r3->[0] ne 'A') {
152 18         36 for my $lineno ($r3->[3] .. $r3->[4]) {
153 26         22 push @{$res->{body}}, $text3->[1][$lineno - 1];
  26         64  
154             }
155             }
156             else {
157 4         10 _conflict_range($text3, $r3, $res);
158             }
159 33         68 $i2 = $r3->[6] + 1;
160             }
161 21         32 for my $lineno ($i2 .. $#{$text3->[2]} + 1) {
  21         37  
162 42         35 push @{$res->{body}}, $text3->[2][$lineno - 1];
  42         101  
163             }
164 21         98 return $res;
165             }
166              
167             sub _conflict_range {
168 4     4   6 my($text3, $r3, $res) = @_;
169 10         26 my $text2 = [
170 10         22 [map { $text3->[1][$_ - 1] } $r3->[3] .. $r3->[4]], # yourtext
171 4         10 [map { $text3->[0][$_ - 1] } $r3->[1] .. $r3->[2]], # mytext
172             ];
173 4         6 my $diff = diff(@{$text2});
  4         9  
174 4 100 66     11 if (_assoc_range($diff, 'c') && $r3->[5] <= $r3->[6]) {
175 3         4 $res->{conflict}++;
176 3         4 push @{$res->{body}}, '<<<<<<<';
  3         6  
177 3         8 for my $lineno ($r3->[1] .. $r3->[2]) {
178 10         7 push @{$res->{body}}, $text3->[0][$lineno - 1];
  10         23  
179             }
180 3         4 push @{$res->{body}}, '|||||||';
  3         5  
181 3         7 for my $lineno ($r3->[5] .. $r3->[6]) {
182 10         9 push @{$res->{body}}, $text3->[2][$lineno - 1];
  10         41  
183             }
184 3         36 push @{$res->{body}}, '=======';
  3         10  
185 3         6 for my $lineno ($r3->[3] .. $r3->[4]) {
186 9         10 push @{$res->{body}}, $text3->[1][$lineno - 1];
  9         20  
187             }
188 3         4 push @{$res->{body}}, '>>>>>>>';
  3         4  
189 3         9 return;
190             }
191 1         2 my $ia = 1;
192 1         2 for my $r2 (@{$diff}) {
  1         2  
193 1         3 for my $lineno ($ia .. $r2->[1] - 1) {
194 0         0 push @{$res->{body}}, $text2->[0][$lineno - 1];
  0         0  
195             }
196 1 50       7 if ($r2->[0] eq 'c') {
    50          
197 0         0 $res->{conflict}++;
198 0         0 push @{$res->{body}}, '<<<<<<<';
  0         0  
199 0         0 for my $lineno ($r2->[3] .. $r2->[4]) {
200 0         0 push @{$res->{body}}, $text2->[1][$lineno - 1];
  0         0  
201             }
202 0         0 push @{$res->{body}}, '=======';
  0         0  
203 0         0 for my $lineno ($r2->[1] .. $r2->[2]) {
204 0         0 push @{$res->{body}}, $text2->[0][$lineno - 1];
  0         0  
205             }
206 0         0 push @{$res->{body}}, '>>>>>>>';
  0         0  
207             }
208             elsif ($r2->[0] eq 'a') {
209 0         0 for my $lineno ($r2->[3] .. $r2->[4]) {
210 0         0 push @{$res->{body}}, $text2->[1][$lineno - 1];
  0         0  
211             }
212             }
213 1         2 $ia = $r2->[2] + 1;
214             }
215 1         2 for my $lineno ($ia .. $#{$text2->[0]} + 1) {
  1         3  
216 0         0 push @{$res->{body}}, $text2->[0][$lineno - 1];
  0         0  
217             }
218 1         3 return;
219             }
220              
221             sub _assoc_range {
222 4     4   5 my($diff, $type) = @_;
223 4         5 for my $r (@{$diff}) {
  4         7  
224 4 100       34 return $r if $r->[0] eq $type;
225             }
226 1         5 return;
227             }
228              
229             # the two-way diff based on the algorithm by P. Heckel.
230             sub _diff_heckel {
231 94     94   130 my($text_a, $text_b) = @_;
232 94         124 my $diff = [];
233 94         107 my @uniq = ([$#{$text_a} + 1, $#{$text_b} + 1]);
  94         153  
  94         224  
234 94         130 my(%freq, %ap, %bp);
235 94         93 for my $i (0 .. $#{$text_a}) {
  94         203  
236 565         601 my $s = $text_a->[$i];
237 565         728 $freq{$s} += 2;
238 565         862 $ap{$s} = $i;
239             }
240 94         141 for my $i (0 .. $#{$text_b}) {
  94         171  
241 591         591 my $s = $text_b->[$i];
242 591         667 $freq{$s} += 3;
243 591         877 $bp{$s} = $i;
244             }
245 94         332 while (my($s, $x) = each %freq) {
246 700 100       1484 next if $x != 5;
247 434         1554 push @uniq, [$ap{$s}, $bp{$s}];
248             }
249 94         215 %freq = (); %ap = (); %bp = ();
  94         154  
  94         180  
250 94         230 @uniq = sort { $a->[0] <=> $b->[0] } @uniq;
  875         1050  
251 94         124 my($a1, $b1) = (0, 0);
252 94   100     103 while ($a1 <= $#{$text_a} && $b1 <= $#{$text_b}) {
  363         779  
  326         958  
253 320 100       696 last if $text_a->[$a1] ne $text_b->[$b1];
254 269         220 ++$a1;
255 269         254 ++$b1;
256             }
257 94         164 for (@uniq) {
258 528         462 my($a_uniq, $b_uniq) = @{$_};
  528         638  
259 528 100 66     2648 next if $a_uniq < $a1 || $b_uniq < $b1;
260 172         178 my($a0, $b0) = ($a1, $b1);
261 172         219 ($a1, $b1) = ($a_uniq - 1, $b_uniq - 1);
262 172   100     465 while ($a0 <= $a1 && $b0 <= $b1) {
263 34 50       75 last if $text_a->[$a1] ne $text_b->[$b1];
264 0         0 --$a1;
265 0         0 --$b1;
266             }
267 172 100 100     606 if ($a0 <= $a1 && $b0 <= $b1) {
    100          
    100          
268 34         31 push @{$diff}, ['c', $a0 + 1, $a1 + 1, $b0 + 1, $b1 + 1];
  34         96  
269             }
270             elsif ($a0 <= $a1) {
271 27         56 push @{$diff}, ['d', $a0 + 1, $a1 + 1, $b0 + 1, $b0];
  27         84  
272             }
273             elsif ($b0 <= $b1) {
274 37         41 push @{$diff}, ['a', $a0 + 1, $a0, $b0 + 1, $b1 + 1];
  37         112  
275             }
276 172         267 ($a1, $b1) = ($a_uniq + 1, $b_uniq + 1);
277 172   66     166 while ($a1 <= $#{$text_a} && $b1 <= $#{$text_b}) {
  259         884  
  117         326  
278 117 100       260 last if $text_a->[$a1] ne $text_b->[$b1];
279 87         78 ++$a1;
280 87         82 ++$b1;
281             }
282             }
283 94         462 return $diff;
284             }
285              
286             1;
287              
288             __END__