Changing empty fetch_rev_(str|num)() calls to the cached version in Paths
[viewsvn.git] / includes / svnlib.php
1 <?php
2 /*=====================================================================*\
3 || ###################################################################
4 || # ViewSVN [#]version[#]
5 || # Copyright ©2002-[#]year[#] Iris Studios, Inc.
6 || #
7 || # This program is free software; you can redistribute it and/or modify
8 || # it under the terms of the GNU General Public License as published by
9 || # the Free Software Foundation; version [#]gpl[#] of the License.
10 || #
11 || # This program is distributed in the hope that it will be useful, but
12 || # WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
13 || # or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
14 || # more details.
15 || #
16 || # You should have received a copy of the GNU General Public License along
17 || # with this program; if not, write to the Free Software Foundation, Inc.,
18 || # 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
19 || ###################################################################
20 \*=====================================================================*/
21
22 /**
23 * Command line interface with the SVN commands
24 *
25 * @package ViewSVN
26 */
27
28 /**
29 * Interacts with the command line subsystem to
30 * access SVN information
31 *
32 * @package ViewSVN
33 * @version $Id$
34 */
35 class SVNLib
36 {
37 /**
38 * Path to the SVN binary
39 * @var string
40 */
41 var $svnpath;
42
43 /**
44 * Common command system
45 * @var object
46 */
47 var $common;
48
49 /**
50 * Constructor: validate SVN path
51 *
52 * @param string Path to SVN binary
53 */
54 function SVNLib($svnpath)
55 {
56 global $viewsvn;
57
58 $this->svnpath = $viewsvn->shell->cmd($svnpath);
59
60 $this->common =& new SVNCommon();
61
62 $access = $viewsvn->shell->exec($this->svnpath . ' --version');
63
64 if (!$access)
65 {
66 $viewsvn->trigger->error($viewsvn->lang->string('The SVN binary could not be located'));
67 }
68
69 if (!preg_match('#^svn, version (.*?)\)$#i', trim($access[0])))
70 {
71 $viewsvn->trigger->error($viewsvn->lang->string('The SVN binary does not appear to be valid (it failed our tests)'));
72 }
73 }
74
75 /**
76 * Prepares data for output
77 *
78 * @access public
79 *
80 * @param string Standard data
81 *
82 * @return string Output-ready data
83 */
84 function format($string)
85 {
86 // convert entities
87 $string = htmlspecialchars($string);
88
89 // tabs to 5 spaces
90 $string = str_replace("\t", ' ', $string);
91
92 // spaces to nbsp
93 if (true)
94 {
95 $string = preg_replace('#( +)#e', '$this->format_spaces("\1")', $string);
96 }
97 // no word wrap
98 else
99 {
100 $string = str_replace(' ', '&nbsp;', $string);
101 }
102
103 // convert advanced diff
104 $string = str_replace(array('{@+' . '+}', '{@-' . '-}'), array('<span class="diff_add_bit">', '<span class="diff_del_bit">'), $string);
105 $string = str_replace(array('{/@+' . '+}', '{/@-' . '-}'), '</span>', $string);
106
107 // nl2br
108 $string = nl2br($string);
109
110 return $string;
111 }
112
113 /**
114 * Formats a SVN log message
115 *
116 * @access public
117 *
118 * @param string Unformatted log message
119 *
120 * @return string Output-ready log message
121 */
122 function format_log_message($message)
123 {
124 global $viewsvn;
125
126 $message = $viewsvn->entity_encode($message);
127
128 $message = preg_replace('#r([0-9]+)#e', '"<a href=\"" . $viewsvn->path . "/" . $viewsvn->paths->out("diff.php" . $viewsvn->paths->fetch_rev_str(true, "\1", 0), $viewsvn->paths->fetch_repos($viewsvn->paths->parse()) . "/") . "\">r\1</a>"', $message);
129
130 $list = false;
131 $lines = explode("\n", $message);
132 $message = '';
133 foreach ($lines AS $line)
134 {
135 if (preg_match('#^\s*?(\*|\-)\s?(.*)#', $line, $matches))
136 {
137 if ($list)
138 {
139 $message .= '<li>' . $matches[2] . '</li>';
140 }
141 else
142 {
143 $message .= '<ul class="list">';
144 $message .= '<li>' . $matches[2] . '</li>';
145 }
146 $list = true;
147 }
148 else
149 {
150 if ($list)
151 {
152 $message .= '</ul>';
153 $message .= $line;
154 }
155 else
156 {
157 $message .= $line;
158 $message .= '<br />';
159 }
160 $list = false;
161 }
162
163 $message .= "\n";
164 }
165
166 if ($list)
167 {
168 $message .= '</ul>';
169 }
170
171 $message = preg_replace('#(<br />)*$#', '', $message);
172
173 return $message;
174 }
175
176 /**
177 * Counts the spaces and replaces two or more ones
178 *
179 * @access private
180 *
181 * @param string Spaced string
182 *
183 * @return string &nbsp;'d string
184 */
185 function format_spaces($thestring)
186 {
187 if (strlen($thestring) >= 2)
188 {
189 $thestring = str_replace(' ', '&nbsp;', $thestring);
190 }
191
192 return $thestring;
193 }
194
195 /**
196 * Executes the SVN binary
197 *
198 * @access private
199 *
200 * @param string Command
201 *
202 * @return array Output
203 */
204 function svn($command)
205 {
206 global $viewsvn;
207
208 $output = $viewsvn->shell->exec($this->svnpath . ' ' . $command . ' 2>&1');
209
210 // make sure that we keep escaped chars
211 //$output = str_replace(array('\t', '\n', '\r'), array('\\\t', '\\\n', '\\\r'), $output);
212 //$output = preg_replace('#\\\(.)#', '\\\\\\\\' . '\1', $output);
213 //$output = str_replace('\\', '\\\\', $output);
214
215 $temp = implode("\n", $output);
216 if (strpos($temp, '(apr' . '_err=') !== false)
217 {
218 $viewsvn->trigger->error(nl2br($temp));
219 }
220 return $output;
221 }
222
223 /**
224 * SVN Wrapper: standard command system
225 *
226 * @access private
227 *
228 * @param string SVN command
229 * @param string Repository
230 * @param string Path
231 * @param integer Revision
232 *
233 * @return array Lines of output
234 */
235 function std($command, $repos, $path, $revision)
236 {
237 global $viewsvn;
238
239 $revision = $this->rev($revision);
240 $repospath = $viewsvn->repos->fetch_path($repos, false);
241
242 return $this->svn($command . ' ' . $repospath . $path . '@' . $revision);
243 }
244
245 /**
246 * SVN Wrapper: blame
247 *
248 * @access protected
249 *
250 * @param string Repository
251 * @param string Path
252 * @param integer Revision
253 *
254 * @return array Lines of blame output
255 */
256 function blame($repos, $path, $revision)
257 {
258 return $this->std('blame', $repos, $path, $revision);
259 }
260
261 /**
262 * SVN Wrapper: cat
263 *
264 * @access protected
265 *
266 * @param string Repository
267 * @param string Path
268 * @param integer Revision
269 *
270 * @return array Lines of cat output
271 */
272 function cat($repos, $path, $revision)
273 {
274 return $this->std('cat', $repos, $path, $revision);
275 }
276
277 /**
278 * SVN Wrapper: diff
279 *
280 * @access protected
281 *
282 * @param string Repository
283 * @param string Path
284 * @param integer Lower revision
285 * @param integer Higher revision
286 *
287 * @return array Lines of diff output
288 */
289 function diff($repos, $path, $lorev, $hirev)
290 {
291 global $viewsvn;
292
293 $hirev = $this->rev($hirev);
294 $lorev = $this->rev($lorev);
295 if ($lorev == 'HEAD')
296 {
297 $lorev = 1;
298 }
299
300 if (is_integer($hirev) AND is_integer($lorev))
301 {
302 if ($lorev > $hirev)
303 {
304 $lorev = $hirev - 1;
305 }
306 if ($lorev == $hirev)
307 {
308 $lorev = 0;
309 }
310 }
311
312 $repospath = $viewsvn->repos->fetch_path($repos, false);
313
314 return $this->svn('diff -r' . $lorev . ':' . $hirev . ' ' . $repospath . $path);
315 }
316
317 /**
318 * SVN Wrapper: log
319 *
320 * @access protected
321 *
322 * @param string Repository
323 * @param string Path
324 * @param integer Lower revision
325 * @param integer Higher revision
326 *
327 * @return array Lines of log output
328 */
329 function log($repos, $path, $lorev, $hirev)
330 {
331 global $viewsvn;
332
333 $hirev = $this->rev($hirev);
334 $lorev = $this->rev($hirev);
335 if ($lorev == 'HEAD')
336 {
337 $lorev = 0;
338 }
339
340 if (is_integer($hirev) AND is_integer($lorev))
341 {
342 if ($lorev > $hirev)
343 {
344 $lorev = $hirev - 1;
345 }
346 if ($lorev == $hirev)
347 {
348 $lorev = 0;
349 }
350 }
351
352 $repospath = $viewsvn->repos->fetch_path($repos, false);
353
354 return $this->svn('log -v -r' . $hirev . ':' . $lorev . ' ' . $repospath . $path);
355 }
356
357 /**
358 * SVN Wrapper: ls (list)
359 *
360 * @access protected
361 *
362 * @param string Repository
363 * @param string Path
364 * @param integer Revision
365 *
366 * @return array Lines of list output
367 */
368 function ls($repos, $path, $revision)
369 {
370 return $this->std('list', $repos, $path, $revision);
371 }
372
373 /**
374 * Generates a clean revision number
375 *
376 * @access public
377 *
378 * @param integer Revision number
379 *
380 * @return mixed Cleaned revision or HEAD
381 */
382 function rev($revision)
383 {
384 if (($revision = intval($revision)) < 1)
385 {
386 $revision = 'HEAD';
387 }
388 return $revision;
389 }
390 }
391
392 /**
393 * Commonly executed SVN commands that return data
394 * used in many parts of the system
395 *
396 * @package ViewSVN
397 * @version $Id$
398 */
399 class SVNCommon
400 {
401 /**
402 * Registry object
403 * @var object
404 */
405 var $registry;
406
407 /**
408 * List of revisions
409 * @var array
410 */
411 var $revisions;
412
413 /**
414 * List of logs
415 * @var array
416 */
417 var $logs;
418
419 /**
420 * Constructor: bind with registry
421 */
422 function SVNCommon()
423 {
424 global $viewsvn;
425
426 $this->registry =& $viewsvn;
427 }
428
429 /**
430 * Checks to see if the given universal path is
431 * a directory
432 *
433 * @access public
434 *
435 * @param string Universal path
436 *
437 * @return bool Directory or not
438 */
439 function isdir($path)
440 {
441 $output = $this->registry->svn->std('info', $this->registry->paths->fetch_repos($path), $this->registry->paths->fetch_path($path), $this->registry->paths->revnum);
442
443 foreach ($output AS $line)
444 {
445 if (preg_match('#^Node Kind: (.*)#', $line, $matches))
446 {
447 if (trim(strtolower($matches[1])) == 'directory')
448 {
449 return true;
450 }
451 }
452 }
453
454 return false;
455 }
456
457 /**
458 * Get a list of revisions for a path
459 *
460 * @access public
461 *
462 * @param string Universal path
463 *
464 * @return array Key revisions
465 */
466 function fetch_revs($path)
467 {
468 if (!isset($this->revisions["$path"]))
469 {
470 $log = $this->fetch_logs($path);
471
472 $revs = array_keys($log);
473
474 $this->revisions["$path"] = array(
475 'HEAD' => $revs[0],
476 'START' => $revs[ count($revs) - 1 ],
477 'revs' => $revs
478 );
479 }
480
481 return $this->revisions["$path"];
482 }
483
484 /**
485 * Gets the revision that is marked as HEAD
486 *
487 * @access public
488 *
489 * @param string Universal path
490 *
491 * @return integer Revision
492 */
493 function fetch_head_rev($path)
494 {
495 $output = $this->registry->shell->exec($this->registry->svn->svnpath . ' info ' . $this->registry->repos->fetch_path($this->registry->paths->fetch_repos($path), false) . $this->registry->paths->fetch_path($path));
496
497 foreach ($output AS $line)
498 {
499 if (preg_match('#^Last Changed Rev: (.*)#', $line, $matches))
500 {
501 return $matches[1];
502 }
503 }
504
505 $revs = $this->fetch_revs($path);
506 return $revs['HEAD'];
507 }
508
509 /**
510 * Returns the previous revision to the one given
511 *
512 * @access public
513 *
514 * @param string Universal path
515 * @param integer Arbitrary revision
516 *
517 * @return integer Previous revision (-1 if none)
518 */
519 function fetch_prev_rev($path, $current)
520 {
521 global $viewsvn;
522
523 $revs = $this->fetch_revs($path);
524
525 if ($current == 'HEAD')
526 {
527 $current = $this->fetch_head_rev($path);
528 }
529
530 $index = array_search($current, $revs['revs']);
531 if ($current === false)
532 {
533 $message->trigger->error(sprintf($viewsvn->lang->string('Revision r%1$s is not in path %2$s'), $current, $path));
534 }
535
536 if (isset($revs['revs'][ $index + 1 ]))
537 {
538 return $revs['revs'][ $index + 1 ];
539 }
540 else
541 {
542 return -1;
543 }
544 }
545
546 /**
547 * Get a list of logs
548 *
549 * @access public
550 *
551 * @param string Universal path
552 * @param bool Override the cache system?
553 *
554 * @return array Log data
555 */
556 function fetch_logs($path)
557 {
558 if (!isset($this->logs["$path"]))
559 {
560 $log = new SVNLog($this->registry->paths->fetch_repos($path), $this->registry->paths->fetch_path($path), 0, $this->registry->paths->revnum);
561
562 $this->logs["$path"] = $log->fetch();
563 }
564
565 return $this->logs["$path"];
566 }
567
568 /**
569 * Returns a given log entry for a path
570 * and revision
571 *
572 * @access public
573 *
574 * @param string Universal path
575 * @param integer Arbitrary revision
576 *
577 * @return array Log entry
578 */
579 function fetch_log($path, $rev)
580 {
581 $logs = $this->fetch_logs($path);
582
583 $rev = $this->registry->svn->rev($rev);
584 if ($rev == 'HEAD')
585 {
586 $rev = $this->fetch_head_rev($path);
587 }
588
589 if (isset($logs["$rev"]))
590 {
591 return $logs["$rev"];
592 }
593 else
594 {
595 $keys = array_keys($logs);
596 sort($keys);
597
598 for ($i = 0; $i < count($keys); $i++)
599 {
600 if ($rev > $keys["$i"] AND $rev < $keys[ $i + 1 ])
601 {
602 return $logs["$keys[$i]"];
603 }
604 }
605
606 return null;
607 }
608 }
609
610 /**
611 * Prints the file changed list
612 *
613 * @access public
614 *
615 * @public array List of file changes
616 * @public string The repository
617 * @public integer Current revision
618 *
619 * @return string Processed HTML
620 */
621 function construct_file_changes($changes, $repos, $revision)
622 {
623 global $viewsvn;
624
625 $files = '';
626
627 foreach ($changes AS $file)
628 {
629 switch ($file['action'])
630 {
631 case 'A':
632 $class = 'file_add';
633 $tooltip = $viewsvn->lang->string('Added');
634 break;
635 case 'D':
636 $class = 'file_delete';
637 $tooltip = $viewsvn->lang->string('Deleted');
638 break;
639 case 'M':
640 $class = 'file_modify';
641 $tooltip = $viewsvn->lang->string('Modified');
642 break;
643 case 'R':
644 $class = 'file_replace';
645 $tooltip = $viewsvn->lang->string('Replaced');
646 break;
647 }
648
649 $show['from'] = (bool)$file['from'];
650
651 if ($file['from'])
652 {
653 $class = 'file_move';
654 $tooltip = 'Moved/Copied';
655 preg_match('#(.*):([0-9]+)#', $file['from'], $matches);
656 $link['from'] = $viewsvn->paths->out('view.php' . $viewsvn->paths->fetch_rev_str(false, $matches[2]), $repos . $matches[1]);
657 }
658
659 $link['file'] = $viewsvn->paths->out('view.php' . $viewsvn->paths->fetch_rev_str(false, $revision), $repos . $file['file']);
660
661 eval('$files .= "' . $viewsvn->template->fetch('file_change') . '";');
662 }
663
664 return $files;
665 }
666 }
667
668 /**
669 * Annotation/blame system; constructs an array
670 * that is ready for output
671 *
672 * @package ViewSVN
673 * @version $Id$
674 */
675 class SVNBlame
676 {
677 /**
678 * Array of blame information
679 * @var array
680 */
681 var $blame = array();
682
683 /**
684 * Raw "svn blame" output
685 * @var array
686 */
687 var $rawoutput;
688
689 /**
690 * Constructor: create blame and store data
691 *
692 * @param string Repository
693 * @param string Path
694 * @param integer Revision
695 */
696 function SVNBlame($repos, $path, $revision)
697 {
698 global $viewsvn;
699
700 $this->rawoutput = $viewsvn->svn->blame($repos, $path, $revision);
701 $this->process();
702 }
703
704 /**
705 * Returns blame for display
706 *
707 * @access public
708 *
709 * @return array Blame data
710 */
711 function fetch()
712 {
713 return $this->blame;
714 }
715
716 /**
717 * Parses the blame data
718 *
719 * @access private
720 */
721 function process()
722 {
723 $lineno = 1;
724
725 foreach ($this->rawoutput AS $line)
726 {
727 if (preg_match('#^\s+([0-9]+)\s+([\w\.\-_]+)\s(.*)$#', $line, $matches))
728 {
729 $this->blame[] = array(
730 'rev' => $matches[1],
731 'author' => $matches[2],
732 'line' => $matches[3],
733 'lineno' => $lineno++
734 );
735 }
736 // a blank line
737 else if (preg_match('#^\s+([0-9]+)\s+([\w\.\-_]+)$#', $line, $matches))
738 {
739 $this->blame[] = array(
740 'rev' => $matches[1],
741 'author' => $matches[2],
742 'line' => '',
743 'lineno' => $lineno++
744 );
745 }
746 }
747 }
748 }
749
750 /**
751 * Log management system; creates a complex list
752 * of SVN log information
753 *
754 * @package ViewSVN
755 * @version $Id$
756 */
757 class SVNLog
758 {
759 /**
760 * Array of logs
761 * @var array
762 */
763 var $logs = array();
764
765 /**
766 * Raw "svn log" output
767 * @var array
768 */
769 var $rawoutput;
770
771 /**
772 * Constructor: create log store for the given file
773 *
774 * @param string Repository
775 * @param string Path
776 * @param integer Lower revision
777 * @param integer Higher revision
778 */
779 function SVNLog($repos, $path, $lorev, $hirev)
780 {
781 global $viewsvn;
782
783 $this->rawoutput = $viewsvn->svn->log($repos, $path, $lorev, $hirev);
784 $this->process();
785 }
786
787 /**
788 * Returns logs for display
789 *
790 * @access public
791 *
792 * @return array Log data
793 */
794 function fetch()
795 {
796 return $this->logs;
797 }
798
799 /**
800 * Splits up the raw output into a usable log
801 *
802 * @access private
803 */
804 function process()
805 {
806 $lastrev = 0;
807
808 for ($i = 1; $i <= count($this->rawoutput) - 1; $i++)
809 {
810 $line = $this->rawoutput["$i"];
811
812 if (preg_match('#^r([0-9]*) \| (.*?) \| (....-..-.. ..:..:..) ([0-9\-]*) \((.*?)\) \| ([0-9]*) lines?$#', $line, $matches))
813 {
814 if (isset($this->logs["$lastrev"]))
815 {
816 $this->logs["$lastrev"]['message'] = $this->strip_last_line($this->logs["$lastrev"]['message']);
817 }
818
819 $this->logs["$matches[1]"] = array(
820 'rev' => $matches[1],
821 'author' => $matches[2],
822 'date' => $matches[3],
823 'timezone' => $matches[4],
824 'lines' => $matches[6],
825 'message' => ''
826 );
827
828 $lastrev = $matches[1];
829 }
830 else if (preg_match('#^\s+([ADMR])\s(.*)#', $line, $matches))
831 {
832 if (preg_match('#(.*) \(from (.*?)\)$#', $matches[2], $amatches))
833 {
834 $matches[2] = $amatches[1];
835 }
836
837 $this->logs["$lastrev"]['files'][] = array(
838 'action' => $matches[1],
839 'file' => trim($matches[2]),
840 'from' => (isset($amatches[2]) ? $amatches[2] : '')
841 );
842 }
843 else
844 {
845 if (trim($line) != 'Changed paths:')
846 {
847 $this->logs["$lastrev"]['message'] .= $line . "\n";
848 }
849 }
850 }
851
852 if (isset($this->logs["$lastrev"]))
853 {
854 $this->logs["$lastrev"]['message'] = $this->strip_last_line($this->logs["$lastrev"]['message']);
855 }
856 }
857
858 /**
859 * Trims the last dash line off a message
860 *
861 * @access private
862 *
863 * @param string Message with annoying-ass line
864 *
865 * @return string Clean string
866 */
867 function strip_last_line($string)
868 {
869 return trim(preg_replace("#\n(.*?)\n$#", '', $string));
870 }
871 }
872
873 /**
874 * Diff system; constructs a diff array that
875 * is ready for output
876 *
877 * @package ViewSVN
878 */
879 class SVNDiff
880 {
881 /**
882 * Array of diff information
883 * @var array
884 */
885 var $diff = array();
886
887 /**
888 * Raw "svn diff" output
889 * @var array
890 */
891 var $rawoutput;
892
893 /**
894 * Constructor: create and store diff data
895 *
896 * @param string Repository
897 * @param string Path
898 * @param integer Lower revision
899 * @param integer Higher revision
900 */
901 function SVNDiff($repos, $path, $lorev, $hirev)
902 {
903 global $viewsvn;
904
905 $this->rawoutput = $viewsvn->svn->diff($repos, $path, $lorev, $hirev);
906 $this->process();
907 }
908
909 /**
910 * Returns diffs for display
911 *
912 * @access public
913 *
914 * @return array Diff data
915 */
916 function fetch()
917 {
918 return $this->diff;
919 }
920
921 /**
922 * Processes and prepares diff data
923 *
924 * @access private
925 */
926 function process()
927 {
928 global $viewsvn;
929
930 $chunk = 0;
931 $indexcounter = null;
932 $curprop = '';
933
934 $delstack = array();
935
936 foreach ($this->rawoutput AS $line)
937 {
938 if (preg_match('#^@@ \-([0-9]*),([0-9]*) \+([0-9]*),([0-9]*) @@$#', $line, $bits))
939 {
940 $property = false;
941 $delstack = array();
942 $this->diff["$index"][ ++$chunk ]['hunk'] = array('old' => array('line' => $bits[1], 'count' => $bits[2]), 'new' => array('line' => $bits[3], 'count' => $bits[4]));
943 $lines['old'] = $this->diff["$index"]["$chunk"]['hunk']['old']['line'] - 1;
944 $lines['new'] = $this->diff["$index"]["$chunk"]['hunk']['new']['line'] - 1;
945 continue;
946 }
947 else if (preg_match('#^Property changes on: (.*?)$#', $line, $bits))
948 {
949 $property = true;
950 $index = $bits[1];
951 $this->diff["$index"]['props'] = array();
952 continue;
953 }
954
955 if ($indexcounter <= 3 AND $indexcounter !== null)
956 {
957 $indexcounter++;
958 continue;
959 }
960 else if ($indexcounter == 3)
961 {
962 $indexcounter = null;
963 continue;
964 }
965
966 if (preg_match('#^([\+\- ])(.*)#', $line, $matches) AND !$property)
967 {
968 $act = $matches[1];
969 $content = $matches[2];
970
971 if ($act == ' ')
972 {
973 $this->diff["$index"]["$chunk"][] = array(
974 'line' => $content,
975 'act' => '',
976 'oldlineno' => ++$lines['old'],
977 'newlineno' => ++$lines['new']
978 );
979
980 $delstack = array();
981 }
982 else if ($act == '+')
983 {
984 // potential line delta
985 if (count($delstack) > 0)
986 {
987 $lastline = array_shift($delstack);
988
989 if ($delta = @$this->fetch_diff_extent($lastline['line'], $content))
990 {
991 if (strlen($lastline['line']) > ($delta['start'] - $delta['end']))
992 {
993 $end = strlen($lastline['line']) + $delta['end'];
994 $viewsvn->debug("RM delta- = " . $end);
995 $change = '{@-' . '-}' . substr($lastline['line'], $delta['start'], $end - $delta['start']) . '{/@-' . '-}';
996 $this->diff["$index"]["$chunk"]["$lastline[INDEX]"]['line'] = substr($lastline['line'], 0, $delta['start']) . $change . substr($lastline['line'], $end);
997 }
998
999 if (strlen($content) > $delta['start'] - $delta['end'])
1000 {
1001 $end = strlen($content) + $delta['end'];
1002 $viewsvn->debug("MK delta+ = " . $end);
1003 $change = '{@+' . '+}' . substr($content, $delta['start'], $end - $delta['start']) . '{/@+' . '+}';
1004 $content = substr($content, 0, $delta['start']) . $change . substr($content, $end);
1005 }
1006 }
1007 }
1008
1009 $this->diff["$index"]["$chunk"][] = array(
1010 'line' => $content,
1011 'act' => '+',
1012 'oldlineno' => '',
1013 'newlineno' => ++$lines['new']
1014 );
1015 }
1016 else if ($act == '-')
1017 {
1018 $this->diff["$index"]["$chunk"][] = $thearray = array(
1019 'line' => $content,
1020 'act' => '-',
1021 'oldlineno' => ++$lines['old'],
1022 'newlineno' => ''
1023 );
1024
1025 $key = count($this->diff["$index"]["$chunk"]) - 2;
1026 $thearray['INDEX'] = $key;
1027
1028 array_push($delstack, $thearray);
1029 }
1030 }
1031 // whitespace lines
1032 else
1033 {
1034 if (preg_match('#^Index: (.*?)$#', $line, $matches))
1035 {
1036 $index = $matches[1];
1037 $indexcounter = 1;
1038 $chunk = 0;
1039 continue;
1040 }
1041
1042 if ($property)
1043 {
1044 if (preg_match('#^__*_$#', trim($line)))
1045 {
1046 $viewsvn->debug("skipping: $line");
1047 continue;
1048 }
1049
1050 if (preg_match('#Name: (.*?)$#', $line, $matches))
1051 {
1052 $curprop = $matches[1];
1053 $viewsvn->debug("prop: $curprop");
1054 continue;
1055 }
1056 else
1057 {
1058 if (preg_match('#^\s+?\+(.*)#', $line, $matches))
1059 {
1060 $mode = 'add';
1061 $this->diff["$index"]['props']["$curprop"]['add'] .= $matches[1];
1062 }
1063 else if (preg_match('#^\s+?\-(.*)#', $line, $matches))
1064 {
1065 $mode = 'del';
1066 $this->diff["$index"]['props']["$curprop"]['del'] .= $matches[1];
1067 }
1068 else if (!preg_match('#^\s+[\+\- ](.*)#', $line) AND trim($line) != '')
1069 {
1070 $this->diff["$index"]['props']["$curprop"]["$mode"] .= "\n" . $line;
1071 }
1072 continue;
1073 }
1074 }
1075
1076 $this->diff["$index"]["$chunk"][] = array(
1077 'line' => '',
1078 'act' => '',
1079 'oldlineno' => ++$lines['old'],
1080 'newlineno' => ++$lines['new']
1081 );
1082
1083 $delstack = array();
1084 }
1085 }
1086 }
1087
1088 /**
1089 * Returns the amount of change that occured
1090 * between two lines
1091 *
1092 * @access private
1093 *
1094 * @param string Old line
1095 * @param string New line
1096 *
1097 * @return array Difference of positions
1098 */
1099 function fetch_diff_extent($old, $new)
1100 {
1101 global $viewsvn;
1102
1103 $start = 0;
1104 $min = min(strlen($old), strlen($new));
1105
1106 $viewsvn->debug("min1 = $min");
1107
1108 while ($start < $min AND $old["$start"] == $new["$start"])
1109 {
1110 $start++;
1111 }
1112
1113 $end = -1;
1114 $min = $min - $start;
1115
1116 $viewsvn->debug("min2 = $min");
1117
1118 $viewsvn->debug("checking: " . $old[ strlen($old) + $end ] . ' == ' . $new[ strlen($new) + $end ]);
1119
1120 while (-$end <= $min AND $old[ strlen($old) + $end ] == $new[ strlen($new) + $end ])
1121 {
1122 $end--;
1123 }
1124
1125 return array('start' => $start, 'end' => $end + 1);
1126 }
1127 }
1128
1129 /*=====================================================================*\
1130 || ###################################################################
1131 || # $HeadURL$
1132 || # $Id$
1133 || ###################################################################
1134 \*=====================================================================*/
1135 ?>