phpBB
Statistics
| Revision:

root / trunk / phpBB / includes / diff / diff.php

History | View | Annotate | Download (23.7 kB)

1
<?php
2
/**
3
*
4
* @package diff
5
* @copyright (c) 2006 phpBB Group
6
* @license http://opensource.org/licenses/gpl-2.0.php GNU General Public License v2
7
*
8
*/
9
10
/**
11
* @ignore
12
*/
13
if (!defined('IN_PHPBB'))
14
{
15
        exit;
16
}
17
18
/**
19
* Code from pear.php.net, Text_Diff-1.1.0 package
20
* http://pear.php.net/package/Text_Diff/
21
*
22
* Modified by phpBB Group to meet our coding standards
23
* and being able to integrate into phpBB
24
*
25
* General API for generating and formatting diffs - the differences between
26
* two sequences of strings.
27
*
28
* Copyright 2004 Geoffrey T. Dairiki <dairiki@dairiki.org>
29
* Copyright 2004-2008 The Horde Project (http://www.horde.org/)
30
*
31
* @package diff
32
* @author  Geoffrey T. Dairiki <dairiki@dairiki.org>
33
*/
34
class diff
35
{
36
        /**
37
        * Array of changes.
38
        * @var array
39
        */
40
        var $_edits;
41
42
        /**
43
        * Computes diffs between sequences of strings.
44
        *
45
        * @param array $from_lines  An array of strings. Typically these are lines from a file.
46
        * @param array $to_lines    An array of strings.
47
        */
48
        function diff(&$from_content, &$to_content, $preserve_cr = true)
49
        {
50
                $diff_engine = new diff_engine();
51
                $this->_edits = $diff_engine->diff($from_content, $to_content, $preserve_cr);
52
        }
53
54
        /**
55
        * Returns the array of differences.
56
        */
57
        function get_diff()
58
        {
59
                return $this->_edits;
60
        }
61
62
        /**
63
        * returns the number of new (added) lines in a given diff.
64
        *
65
        * @since Text_Diff 1.1.0
66
        *
67
        * @return integer The number of new lines
68
        */
69
        function count_added_lines()
70
        {
71
                $count = 0;
72
73
                for ($i = 0, $size = sizeof($this->_edits); $i < $size; $i++)
74
                {
75
                        $edit = $this->_edits[$i];
76
77
                        if (is_a($edit, 'diff_op_add') || is_a($edit, 'diff_op_change'))
78
                        {
79
                                $count += $edit->nfinal();
80
                        }
81
                }
82
                return $count;
83
        }
84
85
        /**
86
        * Returns the number of deleted (removed) lines in a given diff.
87
        *
88
        * @since Text_Diff 1.1.0
89
        *
90
        * @return integer The number of deleted lines
91
        */
92
        function count_deleted_lines()
93
        {
94
                $count = 0;
95
96
                for ($i = 0, $size = sizeof($this->_edits); $i < $size; $i++)
97
                {
98
                        $edit = $this->_edits[$i];
99
100
                        if (is_a($edit, 'diff_op_delete') || is_a($edit, 'diff_op_change'))
101
                        {
102
                                $count += $edit->norig();
103
                        }
104
                }
105
                return $count;
106
        }
107
108
        /**
109
        * Computes a reversed diff.
110
        *
111
        * Example:
112
        * <code>
113
        * $diff = new diff($lines1, $lines2);
114
        * $rev = $diff->reverse();
115
        * </code>
116
        *
117
        * @return diff  A Diff object representing the inverse of the original diff.
118
        *               Note that we purposely don't return a reference here, since
119
        *               this essentially is a clone() method.
120
        */
121
        function reverse()
122
        {
123
                if (version_compare(zend_version(), '2', '>'))
124
                {
125
                        $rev = clone($this);
126
                }
127
                else
128
                {
129
                        $rev = $this;
130
                }
131
132
                $rev->_edits = array();
133
134
                for ($i = 0, $size = sizeof($this->_edits); $i < $size; $i++)
135
                {
136
                        $edit = $this->_edits[$i];
137
                        $rev->_edits[] = $edit->reverse();
138
                }
139
140
                return $rev;
141
        }
142
143
        /**
144
        * Checks for an empty diff.
145
        *
146
        * @return boolean  True if two sequences were identical.
147
        */
148
        function is_empty()
149
        {
150
                for ($i = 0, $size = sizeof($this->_edits); $i < $size; $i++)
151
                {
152
                        $edit = $this->_edits[$i];
153
154
                        // skip diff_op_copy
155
                        if (is_a($edit, 'diff_op_copy'))
156
                        {
157
                                continue;
158
                        }
159
160
                        if (is_a($edit, 'diff_op_delete') || is_a($edit, 'diff_op_add'))
161
                        {
162
                                $orig = $edit->orig;
163
                                $final = $edit->final;
164
165
                                // We can simplify one case where the array is usually supposed to be empty...
166
                                if (sizeof($orig) == 1 && trim($orig[0]) === '') $orig = array();
167
                                if (sizeof($final) == 1 && trim($final[0]) === '') $final = array();
168
169
                                if (!$orig && !$final)
170
                                {
171
                                        continue;
172
                                }
173
174
                                return false;
175
                        }
176
177
                        return false;
178
                }
179
180
                return true;
181
        }
182
183
        /**
184
        * Computes the length of the Longest Common Subsequence (LCS).
185
        *
186
        * This is mostly for diagnostic purposes.
187
        *
188
        * @return integer  The length of the LCS.
189
        */
190
        function lcs()
191
        {
192
                $lcs = 0;
193
194
                for ($i = 0, $size = sizeof($this->_edits); $i < $size; $i++)
195
                {
196
                        $edit = $this->_edits[$i];
197
198
                        if (is_a($edit, 'diff_op_copy'))
199
                        {
200
                                $lcs += sizeof($edit->orig);
201
                        }
202
                }
203
                return $lcs;
204
        }
205
206
        /**
207
        * Gets the original set of lines.
208
        *
209
        * This reconstructs the $from_lines parameter passed to the constructor.
210
        *
211
        * @return array  The original sequence of strings.
212
        */
213
        function get_original()
214
        {
215
                $lines = array();
216
217
                for ($i = 0, $size = sizeof($this->_edits); $i < $size; $i++)
218
                {
219
                        $edit = $this->_edits[$i];
220
221
                        if ($edit->orig)
222
                        {
223
                                array_splice($lines, sizeof($lines), 0, $edit->orig);
224
                        }
225
                }
226
                return $lines;
227
        }
228
229
        /**
230
        * Gets the final set of lines.
231
        *
232
        * This reconstructs the $to_lines parameter passed to the constructor.
233
        *
234
        * @return array  The sequence of strings.
235
        */
236
        function get_final()
237
        {
238
                $lines = array();
239
240
                for ($i = 0, $size = sizeof($this->_edits); $i < $size; $i++)
241
                {
242
                        $edit = $this->_edits[$i];
243
244
                        if ($edit->final)
245
                        {
246
                                array_splice($lines, sizeof($lines), 0, $edit->final);
247
                        }
248
                }
249
                return $lines;
250
        }
251
252
        /**
253
        * Removes trailing newlines from a line of text. This is meant to be used with array_walk().
254
        *
255
        * @param string &$line  The line to trim.
256
        * @param integer $key  The index of the line in the array. Not used.
257
        */
258
        function trim_newlines(&$line, $key)
259
        {
260
                $line = str_replace(array("\n", "\r"), '', $line);
261
        }
262
263
        /**
264
        * Checks a diff for validity.
265
        *
266
        * This is here only for debugging purposes.
267
        */
268
        function _check($from_lines, $to_lines)
269
        {
270
                if (serialize($from_lines) != serialize($this->get_original()))
271
                {
272
                        trigger_error("[diff] Reconstructed original doesn't match", E_USER_ERROR);
273
                }
274
275
                if (serialize($to_lines) != serialize($this->get_final()))
276
                {
277
                        trigger_error("[diff] Reconstructed final doesn't match", E_USER_ERROR);
278
                }
279
280
                $rev = $this->reverse();
281
282
                if (serialize($to_lines) != serialize($rev->get_original()))
283
                {
284
                        trigger_error("[diff] Reversed original doesn't match", E_USER_ERROR);
285
                }
286
287
                if (serialize($from_lines) != serialize($rev->get_final()))
288
                {
289
                        trigger_error("[diff] Reversed final doesn't match", E_USER_ERROR);
290
                }
291
292
                $prevtype = null;
293
294
                for ($i = 0, $size = sizeof($this->_edits); $i < $size; $i++)
295
                {
296
                        $edit = $this->_edits[$i];
297
298
                        if ($prevtype == get_class($edit))
299
                        {
300
                                trigger_error("[diff] Edit sequence is non-optimal", E_USER_ERROR);
301
                        }
302
                        $prevtype = get_class($edit);
303
                }
304
305
                return true;
306
        }
307
}
308
309
/**
310
* @package diff
311
* @author  Geoffrey T. Dairiki <dairiki@dairiki.org>
312
*/
313
class mapped_diff extends diff
314
{
315
        /**
316
        * Computes a diff between sequences of strings.
317
        *
318
        * This can be used to compute things like case-insensitve diffs, or diffs
319
        * which ignore changes in white-space.
320
        *
321
        * @param array $from_lines         An array of strings.
322
        * @param array $to_lines           An array of strings.
323
        * @param array $mapped_from_lines  This array should have the same size number of elements as $from_lines.
324
        *                                  The elements in $mapped_from_lines and $mapped_to_lines are what is actually
325
        *                                  compared when computing the diff.
326
        * @param array $mapped_to_lines    This array should have the same number of elements as $to_lines.
327
        */
328
        function mapped_diff(&$from_lines, &$to_lines, &$mapped_from_lines, &$mapped_to_lines)
329
        {
330
                if (sizeof($from_lines) != sizeof($mapped_from_lines) || sizeof($to_lines) != sizeof($mapped_to_lines))
331
                {
332
                        return false;
333
                }
334
335
                parent::diff($mapped_from_lines, $mapped_to_lines);
336
337
                $xi = $yi = 0;
338
                for ($i = 0; $i < sizeof($this->_edits); $i++)
339
                {
340
                        $orig = &$this->_edits[$i]->orig;
341
                        if (is_array($orig))
342
                        {
343
                                $orig = array_slice($from_lines, $xi, sizeof($orig));
344
                                $xi += sizeof($orig);
345
                        }
346
347
                        $final = &$this->_edits[$i]->final;
348
                        if (is_array($final))
349
                        {
350
                                $final = array_slice($to_lines, $yi, sizeof($final));
351
                                $yi += sizeof($final);
352
                        }
353
                }
354
        }
355
}
356
357
/**
358
* @package diff
359
* @author  Geoffrey T. Dairiki <dairiki@dairiki.org>
360
*
361
* @access private
362
*/
363
class diff_op
364
{
365
        var $orig;
366
        var $final;
367
368
        function &reverse()
369
        {
370
                trigger_error('[diff] Abstract method', E_USER_ERROR);
371
        }
372
373
        function norig()
374
        {
375
                return ($this->orig) ? sizeof($this->orig) : 0;
376
        }
377
378
        function nfinal()
379
        {
380
                return ($this->final) ? sizeof($this->final) : 0;
381
        }
382
}
383
384
/**
385
* @package diff
386
* @author  Geoffrey T. Dairiki <dairiki@dairiki.org>
387
*
388
* @access private
389
*/
390
class diff_op_copy extends diff_op
391
{
392
        function diff_op_copy($orig, $final = false)
393
        {
394
                if (!is_array($final))
395
                {
396
                        $final = $orig;
397
                }
398
                $this->orig = $orig;
399
                $this->final = $final;
400
        }
401
402
        function &reverse()
403
        {
404
                $reverse = new diff_op_copy($this->final, $this->orig);
405
                return $reverse;
406
        }
407
}
408
409
/**
410
* @package diff
411
* @author  Geoffrey T. Dairiki <dairiki@dairiki.org>
412
*
413
* @access private
414
*/
415
class diff_op_delete extends diff_op
416
{
417
        function diff_op_delete($lines)
418
        {
419
                $this->orig = $lines;
420
                $this->final = false;
421
        }
422
423
        function &reverse()
424
        {
425
                $reverse = new diff_op_add($this->orig);
426
                return $reverse;
427
        }
428
}
429
430
/**
431
* @package diff
432
* @author  Geoffrey T. Dairiki <dairiki@dairiki.org>
433
*
434
* @access private
435
*/
436
class diff_op_add extends diff_op
437
{
438
        function diff_op_add($lines)
439
        {
440
                $this->final = $lines;
441
                $this->orig = false;
442
        }
443
444
        function &reverse()
445
        {
446
                $reverse = new diff_op_delete($this->final);
447
                return $reverse;
448
        }
449
}
450
451
/**
452
* @package diff
453
* @author  Geoffrey T. Dairiki <dairiki@dairiki.org>
454
*
455
* @access private
456
*/
457
class diff_op_change extends diff_op
458
{
459
        function diff_op_change($orig, $final)
460
        {
461
                $this->orig = $orig;
462
                $this->final = $final;
463
        }
464
465
        function &reverse()
466
        {
467
                $reverse = new diff_op_change($this->final, $this->orig);
468
                return $reverse;
469
        }
470
}
471
472
473
/**
474
* A class for computing three way diffs.
475
*
476
* @package diff
477
* @author  Geoffrey T. Dairiki <dairiki@dairiki.org>
478
*/
479
class diff3 extends diff
480
{
481
        /**
482
        * Conflict counter.
483
        * @var integer
484
        */
485
        var $_conflicting_blocks = 0;
486
487
        /**
488
        * Computes diff between 3 sequences of strings.
489
        *
490
        * @param array $orig    The original lines to use.
491
        * @param array $final1  The first version to compare to.
492
        * @param array $final2  The second version to compare to.
493
        */
494
        function diff3(&$orig, &$final1, &$final2, $preserve_cr = true)
495
        {
496
                $diff_engine = new diff_engine();
497
498
                $diff_1 = $diff_engine->diff($orig, $final1, $preserve_cr);
499
                $diff_2 = $diff_engine->diff($orig, $final2, $preserve_cr);
500
501
                unset($diff_engine);
502
503
                $this->_edits = $this->_diff3($diff_1, $diff_2);
504
        }
505
506
        /**
507
        * Return number of conflicts
508
        */
509
        function get_num_conflicts()
510
        {
511
                $conflicts = 0;
512
513
                for ($i = 0, $size = sizeof($this->_edits); $i < $size; $i++)
514
                {
515
                        $edit = $this->_edits[$i];
516
517
                        if ($edit->is_conflict())
518
                        {
519
                                $conflicts++;
520
                        }
521
                }
522
523
                return $conflicts;
524
        }
525
526
        /**
527
        * Get conflicts content for download. This is generally a merged file, but preserving conflicts and adding explanations to it.
528
        * A user could then go through this file, search for the conflicts and changes the code accordingly.
529
        *
530
        * @param string $label1 the cvs file version/label from the original set of lines
531
        * @param string $label2 the cvs file version/label from the new set of lines
532
        * @param string $label_sep the explanation between label1 and label2 - more of a helper for the user
533
        *
534
        * @return mixed the merged output
535
        */
536
        function get_conflicts_content($label1 = 'CURRENT_FILE', $label2 = 'NEW_FILE', $label_sep = 'DIFF_SEP_EXPLAIN')
537
        {
538
                global $user;
539
540
                $label1 = (!empty($user->lang[$label1])) ? $user->lang[$label1] : $label1;
541
                $label2 = (!empty($user->lang[$label2])) ? $user->lang[$label2] : $label2;
542
                $label_sep = (!empty($user->lang[$label_sep])) ? $user->lang[$label_sep] : $label_sep;
543
544
                $lines = array();
545
546
                for ($i = 0, $size = sizeof($this->_edits); $i < $size; $i++)
547
                {
548
                        $edit = $this->_edits[$i];
549
550
                        if ($edit->is_conflict())
551
                        {
552
                                // Start conflict label
553
                                $label_start        = array('<<<<<<< ' . $label1);
554
                                $label_mid                = array('======= ' . $label_sep);
555
                                $label_end                = array('>>>>>>> ' . $label2);
556
557
                                $lines = array_merge($lines, $label_start, $edit->final1, $label_mid, $edit->final2, $label_end);
558
                                $this->_conflicting_blocks++;
559
                        }
560
                        else
561
                        {
562
                                $lines = array_merge($lines, $edit->merged());
563
                        }
564
                }
565
566
                return $lines;
567
        }
568
569
        /**
570
        * Return merged output (used by the renderer)
571
        *
572
        * @return mixed the merged output
573
        */
574
        function merged_output()
575
        {
576
                return $this->get_conflicts_content();
577
        }
578
579
        /**
580
        * Merge the output and use the new file code for conflicts
581
        */
582
        function merged_new_output()
583
        {
584
                $lines = array();
585
586
                for ($i = 0, $size = sizeof($this->_edits); $i < $size; $i++)
587
                {
588
                        $edit = $this->_edits[$i];
589
590
                        if ($edit->is_conflict())
591
                        {
592
                                $lines = array_merge($lines, $edit->final2);
593
                        }
594
                        else
595
                        {
596
                                $lines = array_merge($lines, $edit->merged());
597
                        }
598
                }
599
600
                return $lines;
601
        }
602
603
        /**
604
        * Merge the output and use the original file code for conflicts
605
        */
606
        function merged_orig_output()
607
        {
608
                $lines = array();
609
610
                for ($i = 0, $size = sizeof($this->_edits); $i < $size; $i++)
611
                {
612
                        $edit = $this->_edits[$i];
613
614
                        if ($edit->is_conflict())
615
                        {
616
                                $lines = array_merge($lines, $edit->final1);
617
                        }
618
                        else
619
                        {
620
                                $lines = array_merge($lines, $edit->merged());
621
                        }
622
                }
623
624
                return $lines;
625
        }
626
627
        /**
628
        * Get conflicting block(s)
629
        */
630
        function get_conflicts()
631
        {
632
                $conflicts = array();
633
634
                for ($i = 0, $size = sizeof($this->_edits); $i < $size; $i++)
635
                {
636
                        $edit = $this->_edits[$i];
637
638
                        if ($edit->is_conflict())
639
                        {
640
                                $conflicts[] = array($edit->final1, $edit->final2);
641
                        }
642
                }
643
644
                return $conflicts;
645
        }
646
647
        /**
648
        * @access private
649
        */
650
        function _diff3(&$edits1, &$edits2)
651
        {
652
                $edits = array();
653
                $bb = new diff3_block_builder();
654
655
                $e1 = current($edits1);
656
                $e2 = current($edits2);
657
658
                while ($e1 || $e2)
659
                {
660
                        if ($e1 && $e2 && is_a($e1, 'diff_op_copy') && is_a($e2, 'diff_op_copy'))
661
                        {
662
                                // We have copy blocks from both diffs. This is the (only) time we want to emit a diff3 copy block.
663
                                // Flush current diff3 diff block, if any.
664
                                if ($edit = $bb->finish())
665
                                {
666
                                        $edits[] = $edit;
667
                                }
668
669
                                $ncopy = min($e1->norig(), $e2->norig());
670
                                $edits[] = new diff3_op_copy(array_slice($e1->orig, 0, $ncopy));
671
672
                                if ($e1->norig() > $ncopy)
673
                                {
674
                                        array_splice($e1->orig, 0, $ncopy);
675
                                        array_splice($e1->final, 0, $ncopy);
676
                                }
677
                                else
678
                                {
679
                                        $e1 = next($edits1);
680
                                }
681
682
                                if ($e2->norig() > $ncopy)
683
                                {
684
                                        array_splice($e2->orig, 0, $ncopy);
685
                                        array_splice($e2->final, 0, $ncopy);
686
                                }
687
                                else
688
                                {
689
                                        $e2 = next($edits2);
690
                                }
691
                        }
692
                        else
693
                        {
694
                                if ($e1 && $e2)
695
                                {
696
                                        if ($e1->orig && $e2->orig)
697
                                        {
698
                                                $norig = min($e1->norig(), $e2->norig());
699
                                                $orig = array_splice($e1->orig, 0, $norig);
700
                                                array_splice($e2->orig, 0, $norig);
701
                                                $bb->input($orig);
702
                                        }
703
                                        else
704
                                        {
705
                                                $norig = 0;
706
                                        }
707
708
                                        if (is_a($e1, 'diff_op_copy'))
709
                                        {
710
                                                $bb->out1(array_splice($e1->final, 0, $norig));
711
                                        }
712
713
                                        if (is_a($e2, 'diff_op_copy'))
714
                                        {
715
                                                $bb->out2(array_splice($e2->final, 0, $norig));
716
                                        }
717
                                }
718
719
                                if ($e1 && ! $e1->orig)
720
                                {
721
                                        $bb->out1($e1->final);
722
                                        $e1 = next($edits1);
723
                                }
724
725
                                if ($e2 && ! $e2->orig)
726
                                {
727
                                        $bb->out2($e2->final);
728
                                        $e2 = next($edits2);
729
                                }
730
                        }
731
                }
732
733
                if ($edit = $bb->finish())
734
                {
735
                        $edits[] = $edit;
736
                }
737
738
                return $edits;
739
        }
740
}
741
742
/**
743
* @package diff
744
* @author  Geoffrey T. Dairiki <dairiki@dairiki.org>
745
*
746
* @access private
747
*/
748
class diff3_op
749
{
750
        function diff3_op($orig = false, $final1 = false, $final2 = false)
751
        {
752
                $this->orig = $orig ? $orig : array();
753
                $this->final1 = $final1 ? $final1 : array();
754
                $this->final2 = $final2 ? $final2 : array();
755
        }
756
757
        function merged()
758
        {
759
                if (!isset($this->_merged))
760
                {
761
                        // Prepare the arrays before we compare them. ;)
762
                        $this->solve_prepare();
763
764
                        if ($this->final1 === $this->final2)
765
                        {
766
                                $this->_merged = &$this->final1;
767
                        }
768
                        else if ($this->final1 === $this->orig)
769
                        {
770
                                $this->_merged = &$this->final2;
771
                        }
772
                        else if ($this->final2 === $this->orig)
773
                        {
774
                                $this->_merged = &$this->final1;
775
                        }
776
                        else
777
                        {
778
                                // The following tries to aggressively solve conflicts...
779
                                $this->_merged = false;
780
                                $this->solve_conflict();
781
                        }
782
                }
783
784
                return $this->_merged;
785
        }
786
787
        function is_conflict()
788
        {
789
                return ($this->merged() === false) ? true : false;
790
        }
791
792
        /**
793
        * Function to prepare the arrays for comparing - we want to skip over newline changes
794
        * @author acydburn
795
        */
796
        function solve_prepare()
797
        {
798
                // We can simplify one case where the array is usually supposed to be empty...
799
                if (sizeof($this->orig) == 1 && trim($this->orig[0]) === '') $this->orig = array();
800
                if (sizeof($this->final1) == 1 && trim($this->final1[0]) === '') $this->final1 = array();
801
                if (sizeof($this->final2) == 1 && trim($this->final2[0]) === '') $this->final2 = array();
802
803
                // Now we only can have the case where the only difference between arrays are newlines, so compare all cases
804
805
                // First, some strings we can compare...
806
                $orig = $final1 = $final2 = '';
807
808
                foreach ($this->orig as $null => $line) $orig .= trim($line);
809
                foreach ($this->final1 as $null => $line) $final1 .= trim($line);
810
                foreach ($this->final2 as $null => $line) $final2 .= trim($line);
811
812
                // final1 === final2
813
                if ($final1 === $final2)
814
                {
815
                        // We preserve the part which will be used in the merge later
816
                        $this->final2 = $this->final1;
817
                }
818
                // final1 === orig
819
                else if ($final1 === $orig)
820
                {
821
                        // Here it does not really matter what we choose, but we will use the new code
822
                        $this->orig = $this->final1;
823
                }
824
                // final2 === orig
825
                else if ($final2 === $orig)
826
                {
827
                        // Here it does not really matter too (final1 will be used), but we will use the new code
828
                        $this->orig = $this->final2;
829
                }
830
        }
831
832
        /**
833
        * Find code portions from $orig in $final1 and use $final2 as merged instance if provided
834
        * @author acydburn
835
        */
836
        function _compare_conflict_seq($orig, $final1, $final2 = false)
837
        {
838
                $result = array('merge_found' => false, 'merge' => array());
839
840
                $_orig = &$this->$orig;
841
                $_final1 = &$this->$final1;
842
843
                // Ok, we basically search for $orig in $final1
844
                $compare_seq = sizeof($_orig);
845
846
                // Go through the conflict code
847
                for ($i = 0, $j = 0, $size = sizeof($_final1); $i < $size; $i++, $j = $i)
848
                {
849
                        $line = $_final1[$i];
850
                        $skip = 0;
851
852
                        for ($x = 0; $x < $compare_seq; $x++)
853
                        {
854
                                // Try to skip all matching lines
855
                                if (trim($line) === trim($_orig[$x]))
856
                                {
857
                                        $line = (++$j < $size) ? $_final1[$j] : $line;
858
                                        $skip++;
859
                                }
860
                        }
861
862
                        if ($skip === $compare_seq)
863
                        {
864
                                $result['merge_found'] = true;
865
866
                                if ($final2 !== false)
867
                                {
868
                                        $result['merge'] = array_merge($result['merge'], $this->$final2);
869
                                }
870
                                $i += ($skip - 1);
871
                        }
872
                        else if ($final2 !== false)
873
                        {
874
                                $result['merge'][] = $line;
875
                        }
876
                }
877
878
                return $result;
879
        }
880
881
        /**
882
        * Tries to solve conflicts aggressively based on typical "assumptions"
883
        * @author acydburn
884
        */
885
        function solve_conflict()
886
        {
887
                $this->_merged = false;
888
889
                // CASE ONE: orig changed into final2, but modified/unknown code in final1.
890
                // IF orig is found "as is" in final1 we replace the code directly in final1 and populate this as final2/merge
891
                if (sizeof($this->orig) && sizeof($this->final2))
892
                {
893
                        $result = $this->_compare_conflict_seq('orig', 'final1', 'final2');
894
895
                        if ($result['merge_found'])
896
                        {
897
                                $this->final2 = $result['merge'];
898
                                $this->_merged = &$this->final2;
899
                                return;
900
                        }
901
902
                        $result = $this->_compare_conflict_seq('final2', 'final1');
903
904
                        if ($result['merge_found'])
905
                        {
906
                                $this->_merged = &$this->final1;
907
                                return;
908
                        }
909
910
                        // Try to solve $Id: diff.php 11653 2012-01-02 18:00:19Z git-gate $ issues. ;)
911
                        if (sizeof($this->orig) == 1 && sizeof($this->final1) == 1 && sizeof($this->final2) == 1)
912
                        {
913
                                $match = '#^' . preg_quote('* @version $Id: ', '#') . '[a-z\._\- ]+[0-9]+ [0-9]{4}-[0-9]{2}-[0-9]{2} [0-9\:Z]+ [a-z0-9_\- ]+\$$#';
914
915
                                if (preg_match($match, $this->orig[0]) && preg_match($match, $this->final1[0]) && preg_match($match, $this->final2[0]))
916
                                {
917
                                        $this->_merged = &$this->final2;
918
                                        return;
919
                                }
920
                        }
921
922
                        $second_run = false;
923
924
                        // Try to solve issues where the only reason why the above did not work is a newline being removed in the final1 code but exist in the orig/final2 code
925
                        if (trim($this->orig[0]) === '' && trim($this->final2[0]) === '')
926
                        {
927
                                unset($this->orig[0], $this->final2[0]);
928
                                $this->orig = array_values($this->orig);
929
                                $this->final2 = array_values($this->final2);
930
931
                                $second_run = true;
932
                        }
933
934
                        // The same is true for a line at the end. ;)
935
                        if (sizeof($this->orig) && sizeof($this->final2) && sizeof($this->orig) === sizeof($this->final2) && trim($this->orig[sizeof($this->orig)-1]) === '' && trim($this->final2[sizeof($this->final2)-1]) === '')
936
                        {
937
                                unset($this->orig[sizeof($this->orig)-1], $this->final2[sizeof($this->final2)-1]);
938
                                $this->orig = array_values($this->orig);
939
                                $this->final2 = array_values($this->final2);
940
941
                                $second_run = true;
942
                        }
943
944
                        if ($second_run)
945
                        {
946
                                $result = $this->_compare_conflict_seq('orig', 'final1', 'final2');
947
948
                                if ($result['merge_found'])
949
                                {
950
                                        $this->final2 = $result['merge'];
951
                                        $this->_merged = &$this->final2;
952
                                        return;
953
                                }
954
955
                                $result = $this->_compare_conflict_seq('final2', 'final1');
956
957
                                if ($result['merge_found'])
958
                                {
959
                                        $this->_merged = &$this->final1;
960
                                        return;
961
                                }
962
                        }
963
964
                        return;
965
                }
966
967
                // CASE TWO: Added lines from orig to final2 but final1 had added lines too. Just merge them.
968
                if (!sizeof($this->orig) && $this->final1 !== $this->final2 && sizeof($this->final1) && sizeof($this->final2))
969
                {
970
                        $result = $this->_compare_conflict_seq('final2', 'final1');
971
972
                        if ($result['merge_found'])
973
                        {
974
                                $this->final2 = $this->final1;
975
                                $this->_merged = &$this->final1;
976
                        }
977
                        else
978
                        {
979
                                $result = $this->_compare_conflict_seq('final1', 'final2');
980
981
                                if (!$result['merge_found'])
982
                                {
983
                                        $this->final2 = array_merge($this->final1, $this->final2);
984
                                        $this->_merged = &$this->final2;
985
                                }
986
                                else
987
                                {
988
                                        $this->final2 = $this->final1;
989
                                        $this->_merged = &$this->final1;
990
                                }
991
                        }
992
993
                        return;
994
                }
995
996
                // CASE THREE: Removed lines (orig has the to-remove line(s), but final1 has additional lines which does not need to be removed). Just remove orig from final1 and then use final1 as final2/merge
997
                if (!sizeof($this->final2) && sizeof($this->orig) && sizeof($this->final1) && $this->orig !== $this->final1)
998
                {
999
                        $result = $this->_compare_conflict_seq('orig', 'final1');
1000
1001
                        if (!$result['merge_found'])
1002
                        {
1003
                                return;
1004
                        }
1005
1006
                        // First of all, try to find the code in orig in final1. ;)
1007
                        $compare_seq = sizeof($this->orig);
1008
                        $begin = $end = -1;
1009
                        $j = 0;
1010
1011
                        for ($i = 0, $size = sizeof($this->final1); $i < $size; $i++)
1012
                        {
1013
                                $line = $this->final1[$i];
1014
1015
                                if (trim($line) === trim($this->orig[$j]))
1016
                                {
1017
                                        // Mark begin
1018
                                        if ($begin === -1)
1019
                                        {
1020
                                                $begin = $i;
1021
                                        }
1022
1023
                                        // End is always $i, the last found line
1024
                                        $end = $i;
1025
1026
                                        if (isset($this->orig[$j+1]))
1027
                                        {
1028
                                                $j++;
1029
                                        }
1030
                                }
1031
                        }
1032
1033
                        if ($begin !== -1 && $begin + ($compare_seq - 1) == $end)
1034
                        {
1035
                                foreach ($this->final1 as $i => $line)
1036
                                {
1037
                                        if ($i < $begin || $i > $end)
1038
                                        {
1039
                                                $merged[] = $line;
1040
                                        }
1041
                                }
1042
1043
                                $this->final2 = $merged;
1044
                                $this->_merged = &$this->final2;
1045
                        }
1046
1047
                        return;
1048
                }
1049
1050
                return;
1051
        }
1052
}
1053
1054
/**
1055
* @package diff
1056
* @author  Geoffrey T. Dairiki <dairiki@dairiki.org>
1057
*
1058
* @access private
1059
*/
1060
class diff3_op_copy extends diff3_op
1061
{
1062
        function diff3_op_copy($lines = false)
1063
        {
1064
                $this->orig = $lines ? $lines : array();
1065
                $this->final1 = &$this->orig;
1066
                $this->final2 = &$this->orig;
1067
        }
1068
1069
        function merged()
1070
        {
1071
                return $this->orig;
1072
        }
1073
1074
        function is_conflict()
1075
        {
1076
                return false;
1077
        }
1078
}
1079
1080
/**
1081
* @package diff
1082
* @author  Geoffrey T. Dairiki <dairiki@dairiki.org>
1083
*
1084
* @access private
1085
*/
1086
class diff3_block_builder
1087
{
1088
        function diff3_block_builder()
1089
        {
1090
                $this->_init();
1091
        }
1092
1093
        function input($lines)
1094
        {
1095
                if ($lines)
1096
                {
1097
                        $this->_append($this->orig, $lines);
1098
                }
1099
        }
1100
1101
        function out1($lines)
1102
        {
1103
                if ($lines)
1104
                {
1105
                        $this->_append($this->final1, $lines);
1106
                }
1107
        }
1108
1109
        function out2($lines)
1110
        {
1111
                if ($lines)
1112
                {
1113
                        $this->_append($this->final2, $lines);
1114
                }
1115
        }
1116
1117
        function is_empty()
1118
        {
1119
                return !$this->orig && !$this->final1 && !$this->final2;
1120
        }
1121
1122
        function finish()
1123
        {
1124
                if ($this->is_empty())
1125
                {
1126
                        return false;
1127
                }
1128
                else
1129
                {
1130
                        $edit = new diff3_op($this->orig, $this->final1, $this->final2);
1131
                        $this->_init();
1132
                        return $edit;
1133
                }
1134
        }
1135
1136
        function _init()
1137
        {
1138
                $this->orig = $this->final1 = $this->final2 = array();
1139
        }
1140
1141
        function _append(&$array, $lines)
1142
        {
1143
                array_splice($array, sizeof($array), 0, $lines);
1144
        }
1145
}