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