2 /*=====================================================================*\
3 || ###################################################################
4 || # ViewSVN [#]version[#]
5 || # Copyright ©2002-[#]year[#] Iris Studios, Inc.
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.
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
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 \*=====================================================================*/
23 * Common functions that aren't Xquery-related and advanced query systems
29 * Interacts with the command line subsystem to
30 * access SVN information
38 * Path to the SVN binary
44 * Common command system
50 * Constructor: validate SVN path
52 * @param string Path to SVN binary
54 function SVNLib($svnpath)
58 $this->svnpath
= $viewsvn->shell
->cmd($svnpath);
60 $this->common
=& new SVNCommon();
62 $access = $viewsvn->shell
->exec($this->svnpath
. ' --version');
66 $viewsvn->trigger
->error($viewsvn->lang
->string('The SVN binary could not be located'));
69 if (!preg_match('#^svn, version (.*?)\)$#i', trim($access[0])))
71 $viewsvn->trigger
->error($viewsvn->lang
->string('The SVN binary does not appear to be valid (it failed our tests)'));
76 * Prepares data for output
80 * @param string Standard data
82 * @return string Output-ready data
84 function format($string)
87 $string = htmlspecialchars($string);
90 $string = str_replace("\t", ' ', $string);
95 $string = preg_replace('#( +)#e', '$this->format_spaces("\1")', $string);
100 $string = str_replace(' ', ' ', $string);
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);
108 $string = nl2br($string);
114 * Formats a SVN log message
118 * @param string Unformatted log message
120 * @return string Output-ready log message
122 function format_log_message($message)
126 $message = $viewsvn->entity_encode($message);
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);
131 $lines = explode("\n", $message);
133 foreach ($lines AS $line)
135 if (preg_match('#^\s*?(\*|\-)\s?(.*)#', $line, $matches))
139 $message .= '<li>' . $matches[2] . '</li>';
143 $message .= '<ul class="list">';
144 $message .= '<li>' . $matches[2] . '</li>';
158 $message .= '<br />';
171 $message = preg_replace('#(<br />)*$#', '', $message);
176 // ###################################################################
178 * Parses a date from Xquery XML outut
182 * @param string Date string
184 * @return string Formatted and readable date string
186 function format_date_string($string)
188 // 2005-01-23T20:42:53.703057Z
189 return preg_replace('#(....)\-(..)\-(..)T(..):(..):(..).(.*)Z#e', 'gmdate("r", mktime(\4, \5, \6, \2, \3, \1))', $string);
193 * Counts the spaces and replaces two or more ones
197 * @param string Spaced string
199 * @return string 'd string
201 function format_spaces($thestring)
203 if (strlen($thestring) >= 2)
205 $thestring = str_replace(' ', ' ', $thestring);
213 * Commonly executed SVN commands that return data
214 * used in many parts of the system
240 * Constructor: bind with registry
246 $this->registry
=& $viewsvn;
250 * Checks to see if the given universal path is
255 * @param string Universal path
257 * @return bool Directory or not
259 function isdir($path)
261 $output = $this->registry
->svn
->std('info', $this->registry
->paths
->fetch_repos($path), $this->registry
->paths
->fetch_path($path), $this->registry
->paths
->revnum
);
263 foreach ($output AS $line)
265 if (preg_match('#^Node Kind: (.*)#', $line, $matches))
267 if (trim(strtolower($matches[1])) == 'directory')
278 * Get a list of revisions for a path
282 * @param string Universal path
284 * @return array Key revisions
286 function fetch_revs($path)
288 if (!isset($this->revisions
["$path"]))
290 $log = $this->fetch_logs($path);
292 $revs = array_keys($log);
294 $this->revisions
["$path"] = array(
296 'START' => $revs[ count($revs) - 1 ],
301 return $this->revisions
["$path"];
305 * Gets the revision that is marked as HEAD
309 * @param string Universal path
311 * @return integer Revision
313 function fetch_head_rev($path)
315 $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));
317 foreach ($output AS $line)
319 if (preg_match('#^Last Changed Rev: (.*)#', $line, $matches))
325 $revs = $this->fetch_revs($path);
326 return $revs['HEAD'];
330 * Returns the previous revision to the one given
334 * @param string Universal path
335 * @param integer Arbitrary revision
337 * @return integer Previous revision (-1 if none)
339 function fetch_prev_rev($path, $current)
343 $revs = $this->fetch_revs($path);
345 if ($current == 'HEAD')
347 $current = $this->fetch_head_rev($path);
350 $index = array_search($current, $revs['revs']);
351 if ($current === false
)
353 $message->trigger
->error(sprintf($viewsvn->lang
->string('Revision r%1$s is not in path %2$s'), $current, $path));
356 if (isset($revs['revs'][ $index +
1 ]))
358 return $revs['revs'][ $index +
1 ];
371 * @param string Universal path
372 * @param bool Override the cache system?
374 * @return array Log data
376 function fetch_logs($path)
378 if (!isset($this->logs
["$path"]))
380 $log = new SVNLog($this->registry
->paths
->fetch_repos($path), $this->registry
->paths
->fetch_path($path), 0, $this->registry
->paths
->revnum
);
382 $this->logs
["$path"] = $log->fetch();
385 return $this->logs
["$path"];
389 * Returns a given log entry for a path
394 * @param string Universal path
395 * @param integer Arbitrary revision
397 * @return array Log entry
399 function fetch_log($path, $rev)
401 $logs = $this->fetch_logs($path);
403 $rev = $this->registry
->svn
->rev($rev);
406 $rev = $this->fetch_head_rev($path);
409 if (isset($logs["$rev"]))
411 return $logs["$rev"];
415 $keys = array_keys($logs);
418 for ($i = 0; $i < count($keys); $i++
)
420 if ($rev > $keys["$i"] AND $rev < $keys[ $i +
1 ])
422 return $logs["$keys[$i]"];
431 * Prints the file changed list
435 * @public array List of file changes
436 * @public string The repository
437 * @public integer Current revision
439 * @return string Processed HTML
441 function construct_file_changes($changes, $repos, $revision)
447 foreach ($changes AS $file)
449 switch ($file['action'])
453 $tooltip = $viewsvn->lang
->string('Added');
456 $class = 'file_delete';
457 $tooltip = $viewsvn->lang
->string('Deleted');
460 $class = 'file_modify';
461 $tooltip = $viewsvn->lang
->string('Modified');
464 $class = 'file_replace';
465 $tooltip = $viewsvn->lang
->string('Replaced');
469 $show['from'] = (bool
)$file['from'];
473 $class = 'file_move';
474 $tooltip = 'Moved/Copied';
475 preg_match('#(.*):([0-9]+)#', $file['from'], $matches);
476 $link['from'] = $viewsvn->paths
->out('view.php' . $viewsvn->paths
->fetch_rev_str(false
, $matches[2]), $repos . $matches[1]);
479 $link['file'] = $viewsvn->paths
->out('view.php' . $viewsvn->paths
->fetch_rev_str(false
, $revision), $repos . $file['file']);
481 eval('$files .= "' . $viewsvn->template
->fetch('file_change') . '";');
489 * Annotation/blame system; constructs an array
490 * that is ready for output
498 * Array of blame information
501 var $blame = array();
504 * Raw "svn blame" output
510 * Constructor: create blame and store data
512 * @param string Repository
514 * @param integer Revision
516 function SVNBlame($repos, $path, $revision)
520 $this->rawoutput
= $viewsvn->svn
->blame($repos, $path, $revision);
525 * Returns blame for display
529 * @return array Blame data
537 * Parses the blame data
545 foreach ($this->rawoutput
AS $line)
547 if (preg_match('#^\s+([0-9]+)\s+([\w\.\-_]+)\s(.*)$#', $line, $matches))
549 $this->blame
[] = array(
550 'rev' => $matches[1],
551 'author' => $matches[2],
552 'line' => $matches[3],
553 'lineno' => $lineno++
557 else if (preg_match('#^\s+([0-9]+)\s+([\w\.\-_]+)$#', $line, $matches))
559 $this->blame
[] = array(
560 'rev' => $matches[1],
561 'author' => $matches[2],
563 'lineno' => $lineno++
571 * Log management system; creates a complex list
572 * of SVN log information
586 * Raw "svn log" output
592 * Constructor: create log store for the given file
594 * @param string Repository
596 * @param integer Lower revision
597 * @param integer Higher revision
599 function SVNLog($repos, $path, $lorev, $hirev)
603 $this->rawoutput
= $viewsvn->svn
->log($repos, $path, $lorev, $hirev);
608 * Returns logs for display
612 * @return array Log data
620 * Splits up the raw output into a usable log
628 for ($i = 1; $i <= count($this->rawoutput
) - 1; $i++
)
630 $line = $this->rawoutput
["$i"];
632 if (preg_match('#^r([0-9]*) \| (.*?) \| (....-..-.. ..:..:..) ([0-9\-]*) \((.*?)\) \| ([0-9]*) lines?$#', $line, $matches))
634 if (isset($this->logs
["$lastrev"]))
636 $this->logs
["$lastrev"]['message'] = $this->strip_last_line($this->logs
["$lastrev"]['message']);
639 $this->logs
["$matches[1]"] = array(
640 'rev' => $matches[1],
641 'author' => $matches[2],
642 'date' => $matches[3],
643 'timezone' => $matches[4],
644 'lines' => $matches[6],
648 $lastrev = $matches[1];
650 else if (preg_match('#^\s+([ADMR])\s(.*)#', $line, $matches))
652 if (preg_match('#(.*) \(from (.*?)\)$#', $matches[2], $amatches))
654 $matches[2] = $amatches[1];
657 $this->logs
["$lastrev"]['files'][] = array(
658 'action' => $matches[1],
659 'file' => trim($matches[2]),
660 'from' => (isset($amatches[2]) ?
$amatches[2] : '')
665 if (trim($line) != 'Changed paths:')
667 $this->logs
["$lastrev"]['message'] .= $line . "\n";
672 if (isset($this->logs
["$lastrev"]))
674 $this->logs
["$lastrev"]['message'] = $this->strip_last_line($this->logs
["$lastrev"]['message']);
679 * Trims the last dash line off a message
683 * @param string Message with annoying-ass line
685 * @return string Clean string
687 function strip_last_line($string)
689 return trim(preg_replace("#\n(.*?)\n$#", '', $string));
694 * Diff system; constructs a diff array that
695 * is ready for output
702 * Array of diff information
708 * Raw "svn diff" output
714 * Constructor: create and store diff data
716 * @param string Repository
718 * @param integer Lower revision
719 * @param integer Higher revision
721 function SVNDiff($repos, $path, $lorev, $hirev)
725 $this->rawoutput
= $viewsvn->svn
->diff($repos, $path, $lorev, $hirev);
730 * Returns diffs for display
734 * @return array Diff data
742 * Processes and prepares diff data
751 $indexcounter = null
;
756 foreach ($this->rawoutput
AS $line)
758 if (preg_match('#^@@ \-([0-9]*),([0-9]*) \+([0-9]*),([0-9]*) @@$#', $line, $bits))
762 $this->diff
["$index"][ ++
$chunk ]['hunk'] = array('old' => array('line' => $bits[1], 'count' => $bits[2]), 'new' => array('line' => $bits[3], 'count' => $bits[4]));
763 $lines['old'] = $this->diff
["$index"]["$chunk"]['hunk']['old']['line'] - 1;
764 $lines['new'] = $this->diff
["$index"]["$chunk"]['hunk']['new']['line'] - 1;
767 else if (preg_match('#^Property changes on: (.*?)$#', $line, $bits))
771 $this->diff
["$index"]['props'] = array();
775 if ($indexcounter <= 3 AND $indexcounter !== null
)
780 else if ($indexcounter == 3)
782 $indexcounter = null
;
786 if (preg_match('#^([\+\- ])(.*)#', $line, $matches) AND !$property)
789 $content = $matches[2];
793 $this->diff
["$index"]["$chunk"][] = array(
796 'oldlineno' => ++
$lines['old'],
797 'newlineno' => ++
$lines['new']
802 else if ($act == '+')
804 // potential line delta
805 if (count($delstack) > 0)
807 $lastline = array_shift($delstack);
809 if ($delta = @$this->fetch_diff_extent($lastline['line'], $content))
811 if (strlen($lastline['line']) > ($delta['start'] - $delta['end']))
813 $end = strlen($lastline['line']) +
$delta['end'];
814 $viewsvn->debug("RM delta- = " . $end);
815 $change = '{@-' . '-}' . substr($lastline['line'], $delta['start'], $end - $delta['start']) . '{/@-' . '-}';
816 $this->diff
["$index"]["$chunk"]["$lastline[INDEX]"]['line'] = substr($lastline['line'], 0, $delta['start']) . $change . substr($lastline['line'], $end);
819 if (strlen($content) > $delta['start'] - $delta['end'])
821 $end = strlen($content) +
$delta['end'];
822 $viewsvn->debug("MK delta+ = " . $end);
823 $change = '{@+' . '+}' . substr($content, $delta['start'], $end - $delta['start']) . '{/@+' . '+}';
824 $content = substr($content, 0, $delta['start']) . $change . substr($content, $end);
829 $this->diff
["$index"]["$chunk"][] = array(
833 'newlineno' => ++
$lines['new']
836 else if ($act == '-')
838 $this->diff
["$index"]["$chunk"][] = $thearray = array(
841 'oldlineno' => ++
$lines['old'],
845 $key = count($this->diff
["$index"]["$chunk"]) - 2;
846 $thearray['INDEX'] = $key;
848 array_push($delstack, $thearray);
854 if (preg_match('#^Index: (.*?)$#', $line, $matches))
856 $index = $matches[1];
864 if (preg_match('#^__*_$#', trim($line)))
866 $viewsvn->debug("skipping: $line");
870 if (preg_match('#Name: (.*?)$#', $line, $matches))
872 $curprop = $matches[1];
873 $viewsvn->debug("prop: $curprop");
878 if (preg_match('#^\s+?\+(.*)#', $line, $matches))
881 $this->diff
["$index"]['props']["$curprop"]['add'] .= $matches[1];
883 else if (preg_match('#^\s+?\-(.*)#', $line, $matches))
886 $this->diff
["$index"]['props']["$curprop"]['del'] .= $matches[1];
888 else if (!preg_match('#^\s+[\+\- ](.*)#', $line) AND trim($line) != '')
890 $this->diff
["$index"]['props']["$curprop"]["$mode"] .= "\n" . $line;
896 $this->diff
["$index"]["$chunk"][] = array(
899 'oldlineno' => ++
$lines['old'],
900 'newlineno' => ++
$lines['new']
909 * Returns the amount of change that occured
914 * @param string Old line
915 * @param string New line
917 * @return array Difference of positions
919 function fetch_diff_extent($old, $new)
924 $min = min(strlen($old), strlen($new));
926 $viewsvn->debug("min1 = $min");
928 while ($start < $min AND $old["$start"] == $new["$start"])
934 $min = $min - $start;
936 $viewsvn->debug("min2 = $min");
938 $viewsvn->debug("checking: " . $old[ strlen($old) +
$end ] . ' == ' . $new[ strlen($new) +
$end ]);
940 while (-$end <= $min AND $old[ strlen($old) +
$end ] == $new[ strlen($new) +
$end ])
945 return array('start' => $start, 'end' => $end +
1);
949 /*=====================================================================*\
950 || ###################################################################
953 || ###################################################################
954 \*=====================================================================*/