Removing the stuff in SVNCommon2 (except for 1 function) as it's all part of Node_Con...
[viewsvn.git] / includes / svncommon.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 * Common functions that aren't Xquery-related and advanced query systems
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 SVNCommon
36 {
37 /**
38 * Path to the SVN binary
39 * @var string
40 */
41 var $svnpath;
42
43 /**
44 * Constructor: validate SVN path
45 *
46 * @param string Path to SVN binary
47 */
48 function SVNLib($svnpath)
49 {
50 global $viewsvn;
51
52 $this->svnpath = $viewsvn->shell->cmd($svnpath);
53
54 $access = $viewsvn->shell->exec($this->svnpath . ' --version');
55
56 if (!$access)
57 {
58 $viewsvn->trigger->error($viewsvn->lang->string('The SVN binary could not be located'));
59 }
60
61 if (!preg_match('#^svn, version (.*?)\)$#i', trim($access[0])))
62 {
63 $viewsvn->trigger->error($viewsvn->lang->string('The SVN binary does not appear to be valid (it failed our tests)'));
64 }
65 }
66
67 /**
68 * Prepares data for output
69 *
70 * @access public
71 *
72 * @param string Standard data
73 *
74 * @return string Output-ready data
75 */
76 function format($string)
77 {
78 // convert entities
79 $string = htmlspecialchars($string);
80
81 // tabs to 5 spaces
82 $string = str_replace("\t", ' ', $string);
83
84 // spaces to nbsp
85 if (true)
86 {
87 $string = preg_replace('#( +)#e', '$this->format_spaces("\1")', $string);
88 }
89 // no word wrap
90 else
91 {
92 $string = str_replace(' ', '&nbsp;', $string);
93 }
94
95 // convert advanced diff
96 $string = str_replace(array('{@+' . '+}', '{@-' . '-}'), array('<span class="diff_add_bit">', '<span class="diff_del_bit">'), $string);
97 $string = str_replace(array('{/@+' . '+}', '{/@-' . '-}'), '</span>', $string);
98
99 // nl2br
100 $string = nl2br($string);
101
102 return $string;
103 }
104
105 /**
106 * Formats a SVN log message
107 *
108 * @access public
109 *
110 * @param string Unformatted log message
111 *
112 * @return string Output-ready log message
113 */
114 function format_log_message($message)
115 {
116 global $viewsvn;
117
118 $message = $viewsvn->entity_encode($message);
119
120 $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);
121
122 $list = false;
123 $lines = explode("\n", $message);
124 $message = '';
125 foreach ($lines AS $line)
126 {
127 if (preg_match('#^\s*?(\*|\-)\s?(.*)#', $line, $matches))
128 {
129 if ($list)
130 {
131 $message .= '<li>' . $matches[2] . '</li>';
132 }
133 else
134 {
135 $message .= '<ul class="list">';
136 $message .= '<li>' . $matches[2] . '</li>';
137 }
138 $list = true;
139 }
140 else
141 {
142 if ($list)
143 {
144 $message .= '</ul>';
145 $message .= $line;
146 }
147 else
148 {
149 $message .= $line;
150 $message .= '<br />';
151 }
152 $list = false;
153 }
154
155 $message .= "\n";
156 }
157
158 if ($list)
159 {
160 $message .= '</ul>';
161 }
162
163 $message = preg_replace('#(<br />)*$#', '', $message);
164
165 return $message;
166 }
167
168 // ###################################################################
169 /**
170 * Parses a date from Xquery XML outut
171 *
172 * @access public
173 *
174 * @param string Date string
175 *
176 * @return string Formatted and readable date string
177 */
178 function format_date_string($string)
179 {
180 // 2005-01-23T20:42:53.703057Z
181 return preg_replace('#(....)\-(..)\-(..)T(..):(..):(..).(.*)Z#e', 'gmdate("r", mktime(\4, \5, \6, \2, \3, \1))', $string);
182 }
183
184 /**
185 * Counts the spaces and replaces two or more ones
186 *
187 * @access private
188 *
189 * @param string Spaced string
190 *
191 * @return string &nbsp;'d string
192 */
193 function format_spaces($thestring)
194 {
195 if (strlen($thestring) >= 2)
196 {
197 $thestring = str_replace(' ', '&nbsp;', $thestring);
198 }
199
200 return $thestring;
201 }
202
203 /**
204 * Prints the file changed list
205 *
206 * @access public
207 *
208 * @public array List of file changes
209 * @public string The repository
210 * @public integer Current revision
211 *
212 * @return string Processed HTML
213 */
214 function construct_file_changes($changes, $repos, $revision)
215 {
216 global $viewsvn;
217
218 $files = '';
219
220 foreach ($changes AS $file)
221 {
222 switch ($file['action'])
223 {
224 case 'A':
225 $class = 'file_add';
226 $tooltip = $viewsvn->lang->string('Added');
227 break;
228 case 'D':
229 $class = 'file_delete';
230 $tooltip = $viewsvn->lang->string('Deleted');
231 break;
232 case 'M':
233 $class = 'file_modify';
234 $tooltip = $viewsvn->lang->string('Modified');
235 break;
236 case 'R':
237 $class = 'file_replace';
238 $tooltip = $viewsvn->lang->string('Replaced');
239 break;
240 }
241
242 $show['from'] = (bool)$file['from'];
243
244 if ($file['from'])
245 {
246 $class = 'file_move';
247 $tooltip = 'Moved/Copied';
248 preg_match('#(.*):([0-9]+)#', $file['from'], $matches);
249 $link['from'] = $viewsvn->paths->out('view.php' . $viewsvn->paths->fetch_rev_str(false, $matches[2]), $repos . $matches[1]);
250 }
251
252 $link['file'] = $viewsvn->paths->out('view.php' . $viewsvn->paths->fetch_rev_str(false, $revision), $repos . $file['file']);
253
254 eval('$files .= "' . $viewsvn->template->fetch('file_change') . '";');
255 }
256
257 return $files;
258 }
259 }
260
261 /**
262 * Annotation/blame system; constructs an array
263 * that is ready for output
264 *
265 * @package ViewSVN
266 * @version $Id$
267 */
268 class SVNBlame
269 {
270 /**
271 * Array of blame information
272 * @var array
273 */
274 var $blame = array();
275
276 /**
277 * Raw "svn blame" output
278 * @var array
279 */
280 var $rawoutput;
281
282 /**
283 * Constructor: create blame and store data
284 *
285 * @param string Repository
286 * @param string Path
287 * @param integer Revision
288 */
289 function SVNBlame($repos, $path, $revision)
290 {
291 global $viewsvn;
292
293 $this->rawoutput = $viewsvn->svn->blame($repos, $path, $revision);
294 $this->process();
295 }
296
297 /**
298 * Returns blame for display
299 *
300 * @access public
301 *
302 * @return array Blame data
303 */
304 function fetch()
305 {
306 return $this->blame;
307 }
308
309 /**
310 * Parses the blame data
311 *
312 * @access private
313 */
314 function process()
315 {
316 $lineno = 1;
317
318 foreach ($this->rawoutput AS $line)
319 {
320 if (preg_match('#^\s+([0-9]+)\s+([\w\.\-_]+)\s(.*)$#', $line, $matches))
321 {
322 $this->blame[] = array(
323 'rev' => $matches[1],
324 'author' => $matches[2],
325 'line' => $matches[3],
326 'lineno' => $lineno++
327 );
328 }
329 // a blank line
330 else if (preg_match('#^\s+([0-9]+)\s+([\w\.\-_]+)$#', $line, $matches))
331 {
332 $this->blame[] = array(
333 'rev' => $matches[1],
334 'author' => $matches[2],
335 'line' => '',
336 'lineno' => $lineno++
337 );
338 }
339 }
340 }
341 }
342
343 /**
344 * Log management system; creates a complex list
345 * of SVN log information
346 *
347 * @package ViewSVN
348 * @version $Id$
349 */
350 class SVNLog
351 {
352 /**
353 * Array of logs
354 * @var array
355 */
356 var $logs = array();
357
358 /**
359 * Raw "svn log" output
360 * @var array
361 */
362 var $rawoutput;
363
364 /**
365 * Constructor: create log store for the given file
366 *
367 * @param string Repository
368 * @param string Path
369 * @param integer Lower revision
370 * @param integer Higher revision
371 */
372 function SVNLog($repos, $path, $lorev, $hirev)
373 {
374 global $viewsvn;
375
376 $this->rawoutput = $viewsvn->svn->log($repos, $path, $lorev, $hirev);
377 $this->process();
378 }
379
380 /**
381 * Returns logs for display
382 *
383 * @access public
384 *
385 * @return array Log data
386 */
387 function fetch()
388 {
389 return $this->logs;
390 }
391
392 /**
393 * Splits up the raw output into a usable log
394 *
395 * @access private
396 */
397 function process()
398 {
399 $lastrev = 0;
400
401 for ($i = 1; $i <= count($this->rawoutput) - 1; $i++)
402 {
403 $line = $this->rawoutput["$i"];
404
405 if (preg_match('#^r([0-9]*) \| (.*?) \| (....-..-.. ..:..:..) ([0-9\-]*) \((.*?)\) \| ([0-9]*) lines?$#', $line, $matches))
406 {
407 if (isset($this->logs["$lastrev"]))
408 {
409 $this->logs["$lastrev"]['message'] = $this->strip_last_line($this->logs["$lastrev"]['message']);
410 }
411
412 $this->logs["$matches[1]"] = array(
413 'rev' => $matches[1],
414 'author' => $matches[2],
415 'date' => $matches[3],
416 'timezone' => $matches[4],
417 'lines' => $matches[6],
418 'message' => ''
419 );
420
421 $lastrev = $matches[1];
422 }
423 else if (preg_match('#^\s+([ADMR])\s(.*)#', $line, $matches))
424 {
425 if (preg_match('#(.*) \(from (.*?)\)$#', $matches[2], $amatches))
426 {
427 $matches[2] = $amatches[1];
428 }
429
430 $this->logs["$lastrev"]['files'][] = array(
431 'action' => $matches[1],
432 'file' => trim($matches[2]),
433 'from' => (isset($amatches[2]) ? $amatches[2] : '')
434 );
435 }
436 else
437 {
438 if (trim($line) != 'Changed paths:')
439 {
440 $this->logs["$lastrev"]['message'] .= $line . "\n";
441 }
442 }
443 }
444
445 if (isset($this->logs["$lastrev"]))
446 {
447 $this->logs["$lastrev"]['message'] = $this->strip_last_line($this->logs["$lastrev"]['message']);
448 }
449 }
450
451 /**
452 * Trims the last dash line off a message
453 *
454 * @access private
455 *
456 * @param string Message with annoying-ass line
457 *
458 * @return string Clean string
459 */
460 function strip_last_line($string)
461 {
462 return trim(preg_replace("#\n(.*?)\n$#", '', $string));
463 }
464 }
465
466 /**
467 * Diff system; constructs a diff array that
468 * is ready for output
469 *
470 * @package ViewSVN
471 */
472 class SVNDiff
473 {
474 /**
475 * Array of diff information
476 * @var array
477 */
478 var $diff = array();
479
480 /**
481 * Raw "svn diff" output
482 * @var array
483 */
484 var $rawoutput;
485
486 /**
487 * Constructor: create and store diff data
488 *
489 * @param string Repository
490 * @param string Path
491 * @param integer Lower revision
492 * @param integer Higher revision
493 */
494 function SVNDiff($repos, $path, $lorev, $hirev)
495 {
496 global $viewsvn;
497
498 $this->rawoutput = $viewsvn->svn->diff($repos, $path, $lorev, $hirev);
499 $this->process();
500 }
501
502 /**
503 * Returns diffs for display
504 *
505 * @access public
506 *
507 * @return array Diff data
508 */
509 function fetch()
510 {
511 return $this->diff;
512 }
513
514 /**
515 * Processes and prepares diff data
516 *
517 * @access private
518 */
519 function process()
520 {
521 global $viewsvn;
522
523 $chunk = 0;
524 $indexcounter = null;
525 $curprop = '';
526
527 $delstack = array();
528
529 foreach ($this->rawoutput AS $line)
530 {
531 if (preg_match('#^@@ \-([0-9]*),([0-9]*) \+([0-9]*),([0-9]*) @@$#', $line, $bits))
532 {
533 $property = false;
534 $delstack = array();
535 $this->diff["$index"][ ++$chunk ]['hunk'] = array('old' => array('line' => $bits[1], 'count' => $bits[2]), 'new' => array('line' => $bits[3], 'count' => $bits[4]));
536 $lines['old'] = $this->diff["$index"]["$chunk"]['hunk']['old']['line'] - 1;
537 $lines['new'] = $this->diff["$index"]["$chunk"]['hunk']['new']['line'] - 1;
538 continue;
539 }
540 else if (preg_match('#^Property changes on: (.*?)$#', $line, $bits))
541 {
542 $property = true;
543 $index = $bits[1];
544 $this->diff["$index"]['props'] = array();
545 continue;
546 }
547
548 if ($indexcounter <= 3 AND $indexcounter !== null)
549 {
550 $indexcounter++;
551 continue;
552 }
553 else if ($indexcounter == 3)
554 {
555 $indexcounter = null;
556 continue;
557 }
558
559 if (preg_match('#^([\+\- ])(.*)#', $line, $matches) AND !$property)
560 {
561 $act = $matches[1];
562 $content = $matches[2];
563
564 if ($act == ' ')
565 {
566 $this->diff["$index"]["$chunk"][] = array(
567 'line' => $content,
568 'act' => '',
569 'oldlineno' => ++$lines['old'],
570 'newlineno' => ++$lines['new']
571 );
572
573 $delstack = array();
574 }
575 else if ($act == '+')
576 {
577 // potential line delta
578 if (count($delstack) > 0)
579 {
580 $lastline = array_shift($delstack);
581
582 if ($delta = @$this->fetch_diff_extent($lastline['line'], $content))
583 {
584 if (strlen($lastline['line']) > ($delta['start'] - $delta['end']))
585 {
586 $end = strlen($lastline['line']) + $delta['end'];
587 $viewsvn->debug("RM delta- = " . $end);
588 $change = '{@-' . '-}' . substr($lastline['line'], $delta['start'], $end - $delta['start']) . '{/@-' . '-}';
589 $this->diff["$index"]["$chunk"]["$lastline[INDEX]"]['line'] = substr($lastline['line'], 0, $delta['start']) . $change . substr($lastline['line'], $end);
590 }
591
592 if (strlen($content) > $delta['start'] - $delta['end'])
593 {
594 $end = strlen($content) + $delta['end'];
595 $viewsvn->debug("MK delta+ = " . $end);
596 $change = '{@+' . '+}' . substr($content, $delta['start'], $end - $delta['start']) . '{/@+' . '+}';
597 $content = substr($content, 0, $delta['start']) . $change . substr($content, $end);
598 }
599 }
600 }
601
602 $this->diff["$index"]["$chunk"][] = array(
603 'line' => $content,
604 'act' => '+',
605 'oldlineno' => '',
606 'newlineno' => ++$lines['new']
607 );
608 }
609 else if ($act == '-')
610 {
611 $this->diff["$index"]["$chunk"][] = $thearray = array(
612 'line' => $content,
613 'act' => '-',
614 'oldlineno' => ++$lines['old'],
615 'newlineno' => ''
616 );
617
618 $key = count($this->diff["$index"]["$chunk"]) - 2;
619 $thearray['INDEX'] = $key;
620
621 array_push($delstack, $thearray);
622 }
623 }
624 // whitespace lines
625 else
626 {
627 if (preg_match('#^Index: (.*?)$#', $line, $matches))
628 {
629 $index = $matches[1];
630 $indexcounter = 1;
631 $chunk = 0;
632 continue;
633 }
634
635 if ($property)
636 {
637 if (preg_match('#^__*_$#', trim($line)))
638 {
639 $viewsvn->debug("skipping: $line");
640 continue;
641 }
642
643 if (preg_match('#Name: (.*?)$#', $line, $matches))
644 {
645 $curprop = $matches[1];
646 $viewsvn->debug("prop: $curprop");
647 continue;
648 }
649 else
650 {
651 if (preg_match('#^\s+?\+(.*)#', $line, $matches))
652 {
653 $mode = 'add';
654 $this->diff["$index"]['props']["$curprop"]['add'] .= $matches[1];
655 }
656 else if (preg_match('#^\s+?\-(.*)#', $line, $matches))
657 {
658 $mode = 'del';
659 $this->diff["$index"]['props']["$curprop"]['del'] .= $matches[1];
660 }
661 else if (!preg_match('#^\s+[\+\- ](.*)#', $line) AND trim($line) != '')
662 {
663 $this->diff["$index"]['props']["$curprop"]["$mode"] .= "\n" . $line;
664 }
665 continue;
666 }
667 }
668
669 $this->diff["$index"]["$chunk"][] = array(
670 'line' => '',
671 'act' => '',
672 'oldlineno' => ++$lines['old'],
673 'newlineno' => ++$lines['new']
674 );
675
676 $delstack = array();
677 }
678 }
679 }
680
681 /**
682 * Returns the amount of change that occured
683 * between two lines
684 *
685 * @access private
686 *
687 * @param string Old line
688 * @param string New line
689 *
690 * @return array Difference of positions
691 */
692 function fetch_diff_extent($old, $new)
693 {
694 global $viewsvn;
695
696 $start = 0;
697 $min = min(strlen($old), strlen($new));
698
699 $viewsvn->debug("min1 = $min");
700
701 while ($start < $min AND $old["$start"] == $new["$start"])
702 {
703 $start++;
704 }
705
706 $end = -1;
707 $min = $min - $start;
708
709 $viewsvn->debug("min2 = $min");
710
711 $viewsvn->debug("checking: " . $old[ strlen($old) + $end ] . ' == ' . $new[ strlen($new) + $end ]);
712
713 while (-$end <= $min AND $old[ strlen($old) + $end ] == $new[ strlen($new) + $end ])
714 {
715 $end--;
716 }
717
718 return array('start' => $start, 'end' => $end + 1);
719 }
720 }
721
722 /*=====================================================================*\
723 || ###################################################################
724 || # $HeadURL$
725 || # $Id$
726 || ###################################################################
727 \*=====================================================================*/
728 ?>