From 456ead984d3cda242fec35ead18d7437f6832bbb Mon Sep 17 00:00:00 2001 From: Robert Sesek Date: Fri, 13 Jan 2006 05:02:56 +0000 Subject: [PATCH] More refactoring. This time breaking up svnlib.php --- includes/svncommon.php | 955 +++++++++++++++++++++++++++++++++++++++++ includes/svnlib.php | 904 +------------------------------------- 2 files changed, 969 insertions(+), 890 deletions(-) create mode 100644 includes/svncommon.php diff --git a/includes/svncommon.php b/includes/svncommon.php new file mode 100644 index 0000000..7ce63a0 --- /dev/null +++ b/includes/svncommon.php @@ -0,0 +1,955 @@ +svnpath = $viewsvn->shell->cmd($svnpath); + + $this->common =& new SVNCommon(); + + $access = $viewsvn->shell->exec($this->svnpath . ' --version'); + + if (!$access) + { + $viewsvn->trigger->error($viewsvn->lang->string('The SVN binary could not be located')); + } + + if (!preg_match('#^svn, version (.*?)\)$#i', trim($access[0]))) + { + $viewsvn->trigger->error($viewsvn->lang->string('The SVN binary does not appear to be valid (it failed our tests)')); + } + } + + /** + * Prepares data for output + * + * @access public + * + * @param string Standard data + * + * @return string Output-ready data + */ + function format($string) + { + // convert entities + $string = htmlspecialchars($string); + + // tabs to 5 spaces + $string = str_replace("\t", ' ', $string); + + // spaces to nbsp + if (true) + { + $string = preg_replace('#( +)#e', '$this->format_spaces("\1")', $string); + } + // no word wrap + else + { + $string = str_replace(' ', ' ', $string); + } + + // convert advanced diff + $string = str_replace(array('{@+' . '+}', '{@-' . '-}'), array('', ''), $string); + $string = str_replace(array('{/@+' . '+}', '{/@-' . '-}'), '', $string); + + // nl2br + $string = nl2br($string); + + return $string; + } + + /** + * Formats a SVN log message + * + * @access public + * + * @param string Unformatted log message + * + * @return string Output-ready log message + */ + function format_log_message($message) + { + global $viewsvn; + + $message = $viewsvn->entity_encode($message); + + $message = preg_replace('#r([0-9]+)#e', '"path . "/" . $viewsvn->paths->out("diff.php" . $viewsvn->paths->fetch_rev_str(true, "\1", 0), $viewsvn->paths->fetch_repos($viewsvn->paths->parse()) . "/") . "\">r\1"', $message); + + $list = false; + $lines = explode("\n", $message); + $message = ''; + foreach ($lines AS $line) + { + if (preg_match('#^\s*?(\*|\-)\s?(.*)#', $line, $matches)) + { + if ($list) + { + $message .= '
  • ' . $matches[2] . '
  • '; + } + else + { + $message .= ''; + $message .= $line; + } + else + { + $message .= $line; + $message .= '
    '; + } + $list = false; + } + + $message .= "\n"; + } + + if ($list) + { + $message .= ''; + } + + $message = preg_replace('#(
    )*$#', '', $message); + + return $message; + } + + // ################################################################### + /** + * Parses a date from Xquery XML outut + * + * @access public + * + * @param string Date string + * + * @return string Formatted and readable date string + */ + function format_date_string($string) + { + // 2005-01-23T20:42:53.703057Z + return preg_replace('#(....)\-(..)\-(..)T(..):(..):(..).(.*)Z#e', 'gmdate("r", mktime(\4, \5, \6, \2, \3, \1))', $string); + } + + /** + * Counts the spaces and replaces two or more ones + * + * @access private + * + * @param string Spaced string + * + * @return string  'd string + */ + function format_spaces($thestring) + { + if (strlen($thestring) >= 2) + { + $thestring = str_replace(' ', ' ', $thestring); + } + + return $thestring; + } +} + +/** +* Commonly executed SVN commands that return data +* used in many parts of the system +* +* @package ViewSVN +* @version $Id$ +*/ +class SVNCommon +{ + /** + * Registry object + * @var object + */ + var $registry; + + /** + * List of revisions + * @var array + */ + var $revisions; + + /** + * List of logs + * @var array + */ + var $logs; + + /** + * Constructor: bind with registry + */ + function SVNCommon() + { + global $viewsvn; + + $this->registry =& $viewsvn; + } + + /** + * Checks to see if the given universal path is + * a directory + * + * @access public + * + * @param string Universal path + * + * @return bool Directory or not + */ + function isdir($path) + { + $output = $this->registry->svn->std('info', $this->registry->paths->fetch_repos($path), $this->registry->paths->fetch_path($path), $this->registry->paths->revnum); + + foreach ($output AS $line) + { + if (preg_match('#^Node Kind: (.*)#', $line, $matches)) + { + if (trim(strtolower($matches[1])) == 'directory') + { + return true; + } + } + } + + return false; + } + + /** + * Get a list of revisions for a path + * + * @access public + * + * @param string Universal path + * + * @return array Key revisions + */ + function fetch_revs($path) + { + if (!isset($this->revisions["$path"])) + { + $log = $this->fetch_logs($path); + + $revs = array_keys($log); + + $this->revisions["$path"] = array( + 'HEAD' => $revs[0], + 'START' => $revs[ count($revs) - 1 ], + 'revs' => $revs + ); + } + + return $this->revisions["$path"]; + } + + /** + * Gets the revision that is marked as HEAD + * + * @access public + * + * @param string Universal path + * + * @return integer Revision + */ + function fetch_head_rev($path) + { + $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)); + + foreach ($output AS $line) + { + if (preg_match('#^Last Changed Rev: (.*)#', $line, $matches)) + { + return $matches[1]; + } + } + + $revs = $this->fetch_revs($path); + return $revs['HEAD']; + } + + /** + * Returns the previous revision to the one given + * + * @access public + * + * @param string Universal path + * @param integer Arbitrary revision + * + * @return integer Previous revision (-1 if none) + */ + function fetch_prev_rev($path, $current) + { + global $viewsvn; + + $revs = $this->fetch_revs($path); + + if ($current == 'HEAD') + { + $current = $this->fetch_head_rev($path); + } + + $index = array_search($current, $revs['revs']); + if ($current === false) + { + $message->trigger->error(sprintf($viewsvn->lang->string('Revision r%1$s is not in path %2$s'), $current, $path)); + } + + if (isset($revs['revs'][ $index + 1 ])) + { + return $revs['revs'][ $index + 1 ]; + } + else + { + return -1; + } + } + + /** + * Get a list of logs + * + * @access public + * + * @param string Universal path + * @param bool Override the cache system? + * + * @return array Log data + */ + function fetch_logs($path) + { + if (!isset($this->logs["$path"])) + { + $log = new SVNLog($this->registry->paths->fetch_repos($path), $this->registry->paths->fetch_path($path), 0, $this->registry->paths->revnum); + + $this->logs["$path"] = $log->fetch(); + } + + return $this->logs["$path"]; + } + + /** + * Returns a given log entry for a path + * and revision + * + * @access public + * + * @param string Universal path + * @param integer Arbitrary revision + * + * @return array Log entry + */ + function fetch_log($path, $rev) + { + $logs = $this->fetch_logs($path); + + $rev = $this->registry->svn->rev($rev); + if ($rev == 'HEAD') + { + $rev = $this->fetch_head_rev($path); + } + + if (isset($logs["$rev"])) + { + return $logs["$rev"]; + } + else + { + $keys = array_keys($logs); + sort($keys); + + for ($i = 0; $i < count($keys); $i++) + { + if ($rev > $keys["$i"] AND $rev < $keys[ $i + 1 ]) + { + return $logs["$keys[$i]"]; + } + } + + return null; + } + } + + /** + * Prints the file changed list + * + * @access public + * + * @public array List of file changes + * @public string The repository + * @public integer Current revision + * + * @return string Processed HTML + */ + function construct_file_changes($changes, $repos, $revision) + { + global $viewsvn; + + $files = ''; + + foreach ($changes AS $file) + { + switch ($file['action']) + { + case 'A': + $class = 'file_add'; + $tooltip = $viewsvn->lang->string('Added'); + break; + case 'D': + $class = 'file_delete'; + $tooltip = $viewsvn->lang->string('Deleted'); + break; + case 'M': + $class = 'file_modify'; + $tooltip = $viewsvn->lang->string('Modified'); + break; + case 'R': + $class = 'file_replace'; + $tooltip = $viewsvn->lang->string('Replaced'); + break; + } + + $show['from'] = (bool)$file['from']; + + if ($file['from']) + { + $class = 'file_move'; + $tooltip = 'Moved/Copied'; + preg_match('#(.*):([0-9]+)#', $file['from'], $matches); + $link['from'] = $viewsvn->paths->out('view.php' . $viewsvn->paths->fetch_rev_str(false, $matches[2]), $repos . $matches[1]); + } + + $link['file'] = $viewsvn->paths->out('view.php' . $viewsvn->paths->fetch_rev_str(false, $revision), $repos . $file['file']); + + eval('$files .= "' . $viewsvn->template->fetch('file_change') . '";'); + } + + return $files; + } +} + +/** +* Annotation/blame system; constructs an array +* that is ready for output +* +* @package ViewSVN +* @version $Id$ +*/ +class SVNBlame +{ + /** + * Array of blame information + * @var array + */ + var $blame = array(); + + /** + * Raw "svn blame" output + * @var array + */ + var $rawoutput; + + /** + * Constructor: create blame and store data + * + * @param string Repository + * @param string Path + * @param integer Revision + */ + function SVNBlame($repos, $path, $revision) + { + global $viewsvn; + + $this->rawoutput = $viewsvn->svn->blame($repos, $path, $revision); + $this->process(); + } + + /** + * Returns blame for display + * + * @access public + * + * @return array Blame data + */ + function fetch() + { + return $this->blame; + } + + /** + * Parses the blame data + * + * @access private + */ + function process() + { + $lineno = 1; + + foreach ($this->rawoutput AS $line) + { + if (preg_match('#^\s+([0-9]+)\s+([\w\.\-_]+)\s(.*)$#', $line, $matches)) + { + $this->blame[] = array( + 'rev' => $matches[1], + 'author' => $matches[2], + 'line' => $matches[3], + 'lineno' => $lineno++ + ); + } + // a blank line + else if (preg_match('#^\s+([0-9]+)\s+([\w\.\-_]+)$#', $line, $matches)) + { + $this->blame[] = array( + 'rev' => $matches[1], + 'author' => $matches[2], + 'line' => '', + 'lineno' => $lineno++ + ); + } + } + } +} + +/** +* Log management system; creates a complex list +* of SVN log information +* +* @package ViewSVN +* @version $Id$ +*/ +class SVNLog +{ + /** + * Array of logs + * @var array + */ + var $logs = array(); + + /** + * Raw "svn log" output + * @var array + */ + var $rawoutput; + + /** + * Constructor: create log store for the given file + * + * @param string Repository + * @param string Path + * @param integer Lower revision + * @param integer Higher revision + */ + function SVNLog($repos, $path, $lorev, $hirev) + { + global $viewsvn; + + $this->rawoutput = $viewsvn->svn->log($repos, $path, $lorev, $hirev); + $this->process(); + } + + /** + * Returns logs for display + * + * @access public + * + * @return array Log data + */ + function fetch() + { + return $this->logs; + } + + /** + * Splits up the raw output into a usable log + * + * @access private + */ + function process() + { + $lastrev = 0; + + for ($i = 1; $i <= count($this->rawoutput) - 1; $i++) + { + $line = $this->rawoutput["$i"]; + + if (preg_match('#^r([0-9]*) \| (.*?) \| (....-..-.. ..:..:..) ([0-9\-]*) \((.*?)\) \| ([0-9]*) lines?$#', $line, $matches)) + { + if (isset($this->logs["$lastrev"])) + { + $this->logs["$lastrev"]['message'] = $this->strip_last_line($this->logs["$lastrev"]['message']); + } + + $this->logs["$matches[1]"] = array( + 'rev' => $matches[1], + 'author' => $matches[2], + 'date' => $matches[3], + 'timezone' => $matches[4], + 'lines' => $matches[6], + 'message' => '' + ); + + $lastrev = $matches[1]; + } + else if (preg_match('#^\s+([ADMR])\s(.*)#', $line, $matches)) + { + if (preg_match('#(.*) \(from (.*?)\)$#', $matches[2], $amatches)) + { + $matches[2] = $amatches[1]; + } + + $this->logs["$lastrev"]['files'][] = array( + 'action' => $matches[1], + 'file' => trim($matches[2]), + 'from' => (isset($amatches[2]) ? $amatches[2] : '') + ); + } + else + { + if (trim($line) != 'Changed paths:') + { + $this->logs["$lastrev"]['message'] .= $line . "\n"; + } + } + } + + if (isset($this->logs["$lastrev"])) + { + $this->logs["$lastrev"]['message'] = $this->strip_last_line($this->logs["$lastrev"]['message']); + } + } + + /** + * Trims the last dash line off a message + * + * @access private + * + * @param string Message with annoying-ass line + * + * @return string Clean string + */ + function strip_last_line($string) + { + return trim(preg_replace("#\n(.*?)\n$#", '', $string)); + } +} + +/** +* Diff system; constructs a diff array that +* is ready for output +* +* @package ViewSVN +*/ +class SVNDiff +{ + /** + * Array of diff information + * @var array + */ + var $diff = array(); + + /** + * Raw "svn diff" output + * @var array + */ + var $rawoutput; + + /** + * Constructor: create and store diff data + * + * @param string Repository + * @param string Path + * @param integer Lower revision + * @param integer Higher revision + */ + function SVNDiff($repos, $path, $lorev, $hirev) + { + global $viewsvn; + + $this->rawoutput = $viewsvn->svn->diff($repos, $path, $lorev, $hirev); + $this->process(); + } + + /** + * Returns diffs for display + * + * @access public + * + * @return array Diff data + */ + function fetch() + { + return $this->diff; + } + + /** + * Processes and prepares diff data + * + * @access private + */ + function process() + { + global $viewsvn; + + $chunk = 0; + $indexcounter = null; + $curprop = ''; + + $delstack = array(); + + foreach ($this->rawoutput AS $line) + { + if (preg_match('#^@@ \-([0-9]*),([0-9]*) \+([0-9]*),([0-9]*) @@$#', $line, $bits)) + { + $property = false; + $delstack = array(); + $this->diff["$index"][ ++$chunk ]['hunk'] = array('old' => array('line' => $bits[1], 'count' => $bits[2]), 'new' => array('line' => $bits[3], 'count' => $bits[4])); + $lines['old'] = $this->diff["$index"]["$chunk"]['hunk']['old']['line'] - 1; + $lines['new'] = $this->diff["$index"]["$chunk"]['hunk']['new']['line'] - 1; + continue; + } + else if (preg_match('#^Property changes on: (.*?)$#', $line, $bits)) + { + $property = true; + $index = $bits[1]; + $this->diff["$index"]['props'] = array(); + continue; + } + + if ($indexcounter <= 3 AND $indexcounter !== null) + { + $indexcounter++; + continue; + } + else if ($indexcounter == 3) + { + $indexcounter = null; + continue; + } + + if (preg_match('#^([\+\- ])(.*)#', $line, $matches) AND !$property) + { + $act = $matches[1]; + $content = $matches[2]; + + if ($act == ' ') + { + $this->diff["$index"]["$chunk"][] = array( + 'line' => $content, + 'act' => '', + 'oldlineno' => ++$lines['old'], + 'newlineno' => ++$lines['new'] + ); + + $delstack = array(); + } + else if ($act == '+') + { + // potential line delta + if (count($delstack) > 0) + { + $lastline = array_shift($delstack); + + if ($delta = @$this->fetch_diff_extent($lastline['line'], $content)) + { + if (strlen($lastline['line']) > ($delta['start'] - $delta['end'])) + { + $end = strlen($lastline['line']) + $delta['end']; + $viewsvn->debug("RM delta- = " . $end); + $change = '{@-' . '-}' . substr($lastline['line'], $delta['start'], $end - $delta['start']) . '{/@-' . '-}'; + $this->diff["$index"]["$chunk"]["$lastline[INDEX]"]['line'] = substr($lastline['line'], 0, $delta['start']) . $change . substr($lastline['line'], $end); + } + + if (strlen($content) > $delta['start'] - $delta['end']) + { + $end = strlen($content) + $delta['end']; + $viewsvn->debug("MK delta+ = " . $end); + $change = '{@+' . '+}' . substr($content, $delta['start'], $end - $delta['start']) . '{/@+' . '+}'; + $content = substr($content, 0, $delta['start']) . $change . substr($content, $end); + } + } + } + + $this->diff["$index"]["$chunk"][] = array( + 'line' => $content, + 'act' => '+', + 'oldlineno' => '', + 'newlineno' => ++$lines['new'] + ); + } + else if ($act == '-') + { + $this->diff["$index"]["$chunk"][] = $thearray = array( + 'line' => $content, + 'act' => '-', + 'oldlineno' => ++$lines['old'], + 'newlineno' => '' + ); + + $key = count($this->diff["$index"]["$chunk"]) - 2; + $thearray['INDEX'] = $key; + + array_push($delstack, $thearray); + } + } + // whitespace lines + else + { + if (preg_match('#^Index: (.*?)$#', $line, $matches)) + { + $index = $matches[1]; + $indexcounter = 1; + $chunk = 0; + continue; + } + + if ($property) + { + if (preg_match('#^__*_$#', trim($line))) + { + $viewsvn->debug("skipping: $line"); + continue; + } + + if (preg_match('#Name: (.*?)$#', $line, $matches)) + { + $curprop = $matches[1]; + $viewsvn->debug("prop: $curprop"); + continue; + } + else + { + if (preg_match('#^\s+?\+(.*)#', $line, $matches)) + { + $mode = 'add'; + $this->diff["$index"]['props']["$curprop"]['add'] .= $matches[1]; + } + else if (preg_match('#^\s+?\-(.*)#', $line, $matches)) + { + $mode = 'del'; + $this->diff["$index"]['props']["$curprop"]['del'] .= $matches[1]; + } + else if (!preg_match('#^\s+[\+\- ](.*)#', $line) AND trim($line) != '') + { + $this->diff["$index"]['props']["$curprop"]["$mode"] .= "\n" . $line; + } + continue; + } + } + + $this->diff["$index"]["$chunk"][] = array( + 'line' => '', + 'act' => '', + 'oldlineno' => ++$lines['old'], + 'newlineno' => ++$lines['new'] + ); + + $delstack = array(); + } + } + } + + /** + * Returns the amount of change that occured + * between two lines + * + * @access private + * + * @param string Old line + * @param string New line + * + * @return array Difference of positions + */ + function fetch_diff_extent($old, $new) + { + global $viewsvn; + + $start = 0; + $min = min(strlen($old), strlen($new)); + + $viewsvn->debug("min1 = $min"); + + while ($start < $min AND $old["$start"] == $new["$start"]) + { + $start++; + } + + $end = -1; + $min = $min - $start; + + $viewsvn->debug("min2 = $min"); + + $viewsvn->debug("checking: " . $old[ strlen($old) + $end ] . ' == ' . $new[ strlen($new) + $end ]); + + while (-$end <= $min AND $old[ strlen($old) + $end ] == $new[ strlen($new) + $end ]) + { + $end--; + } + + return array('start' => $start, 'end' => $end + 1); + } +} + +/*=====================================================================*\ +|| ################################################################### +|| # $HeadURL$ +|| # $Id$ +|| ################################################################### +\*=====================================================================*/ +?> \ No newline at end of file diff --git a/includes/svnlib.php b/includes/svnlib.php index 3fd86ca..c9790ee 100644 --- a/includes/svnlib.php +++ b/includes/svnlib.php @@ -41,173 +41,36 @@ class SVNLib var $svnpath; /** - * Common command system + * Controller * @var object + * @access private */ - var $common; + var $controller; /** * Constructor: validate SVN path * - * @param string Path to SVN binary + * @param object Controller */ - function SVNLib($svnpath) + function SVNLib(&$controller) { - global $viewsvn; + $this->controller =& $controller; - $this->svnpath = $viewsvn->shell->cmd($svnpath); + $this->svnpath = $this->controller->xquery->cmd($svnpath); - $this->common =& new SVNCommon(); - - $access = $viewsvn->shell->exec($this->svnpath . ' --version'); + $access = $this->controller->xquery->exec($this->svnpath . ' --version'); if (!$access) { - $viewsvn->trigger->error($viewsvn->lang->string('The SVN binary could not be located')); + $this->controller->registry->trigger->error($this->controller->registry->lang->string('The SVN binary could not be located')); } if (!preg_match('#^svn, version (.*?)\)$#i', trim($access[0]))) { - $viewsvn->trigger->error($viewsvn->lang->string('The SVN binary does not appear to be valid (it failed our tests)')); - } - } - - /** - * Prepares data for output - * - * @access public - * - * @param string Standard data - * - * @return string Output-ready data - */ - function format($string) - { - // convert entities - $string = htmlspecialchars($string); - - // tabs to 5 spaces - $string = str_replace("\t", ' ', $string); - - // spaces to nbsp - if (true) - { - $string = preg_replace('#( +)#e', '$this->format_spaces("\1")', $string); - } - // no word wrap - else - { - $string = str_replace(' ', ' ', $string); - } - - // convert advanced diff - $string = str_replace(array('{@+' . '+}', '{@-' . '-}'), array('', ''), $string); - $string = str_replace(array('{/@+' . '+}', '{/@-' . '-}'), '', $string); - - // nl2br - $string = nl2br($string); - - return $string; - } - - /** - * Formats a SVN log message - * - * @access public - * - * @param string Unformatted log message - * - * @return string Output-ready log message - */ - function format_log_message($message) - { - global $viewsvn; - - $message = $viewsvn->entity_encode($message); - - $message = preg_replace('#r([0-9]+)#e', '"path . "/" . $viewsvn->paths->out("diff.php" . $viewsvn->paths->fetch_rev_str(true, "\1", 0), $viewsvn->paths->fetch_repos($viewsvn->paths->parse()) . "/") . "\">r\1"', $message); - - $list = false; - $lines = explode("\n", $message); - $message = ''; - foreach ($lines AS $line) - { - if (preg_match('#^\s*?(\*|\-)\s?(.*)#', $line, $matches)) - { - if ($list) - { - $message .= '
  • ' . $matches[2] . '
  • '; - } - else - { - $message .= '
      '; - $message .= '
    • ' . $matches[2] . '
    • '; - } - $list = true; - } - else - { - if ($list) - { - $message .= '
    '; - $message .= $line; - } - else - { - $message .= $line; - $message .= '
    '; - } - $list = false; - } - - $message .= "\n"; - } - - if ($list) - { - $message .= ''; + $this->controller->registry->trigger->error($this->controller->registry->lang->string('The SVN binary does not appear to be valid (it failed our tests)')); } - - $message = preg_replace('#(
    )*$#', '', $message); - - return $message; } - - // ################################################################### - /** - * Parses a date from Xquery XML outut - * - * @access public - * - * @param string Date string - * - * @return string Formatted and readable date string - */ - function format_date_string($string) - { - // 2005-01-23T20:42:53.703057Z - return preg_replace('#(....)\-(..)\-(..)T(..):(..):(..).(.*)Z#e', 'gmdate("r", mktime(\4, \5, \6, \2, \3, \1))', $string); - } - - /** - * Counts the spaces and replaces two or more ones - * - * @access private - * - * @param string Spaced string - * - * @return string  'd string - */ - function format_spaces($thestring) - { - if (strlen($thestring) >= 2) - { - $thestring = str_replace(' ', ' ', $thestring); - } - - return $thestring; - } - + /** * Executes the SVN binary * @@ -219,9 +82,7 @@ class SVNLib */ function svn($command) { - global $viewsvn; - - $output = $viewsvn->shell->exec($this->svnpath . ' ' . $command . ' 2>&1'); + $output = $this->controller->xquery->exec($this->svnpath . ' ' . $command . ' 2>&1'); // make sure that we keep escaped chars //$output = str_replace(array('\t', '\n', '\r'), array('\\\t', '\\\n', '\\\r'), $output); @@ -231,7 +92,7 @@ class SVNLib $temp = implode("\n", $output); if (strpos($temp, '(apr' . '_err=') !== false) { - $viewsvn->trigger->error(nl2br($temp)); + $this->controller->registry->trigger->error(nl2br($temp)); } return $output; } @@ -405,747 +266,10 @@ class SVNLib } } -/** -* Commonly executed SVN commands that return data -* used in many parts of the system -* -* @package ViewSVN -* @version $Id$ -*/ -class SVNCommon -{ - /** - * Registry object - * @var object - */ - var $registry; - - /** - * List of revisions - * @var array - */ - var $revisions; - - /** - * List of logs - * @var array - */ - var $logs; - - /** - * Constructor: bind with registry - */ - function SVNCommon() - { - global $viewsvn; - - $this->registry =& $viewsvn; - } - - /** - * Checks to see if the given universal path is - * a directory - * - * @access public - * - * @param string Universal path - * - * @return bool Directory or not - */ - function isdir($path) - { - $output = $this->registry->svn->std('info', $this->registry->paths->fetch_repos($path), $this->registry->paths->fetch_path($path), $this->registry->paths->revnum); - - foreach ($output AS $line) - { - if (preg_match('#^Node Kind: (.*)#', $line, $matches)) - { - if (trim(strtolower($matches[1])) == 'directory') - { - return true; - } - } - } - - return false; - } - - /** - * Get a list of revisions for a path - * - * @access public - * - * @param string Universal path - * - * @return array Key revisions - */ - function fetch_revs($path) - { - if (!isset($this->revisions["$path"])) - { - $log = $this->fetch_logs($path); - - $revs = array_keys($log); - - $this->revisions["$path"] = array( - 'HEAD' => $revs[0], - 'START' => $revs[ count($revs) - 1 ], - 'revs' => $revs - ); - } - - return $this->revisions["$path"]; - } - - /** - * Gets the revision that is marked as HEAD - * - * @access public - * - * @param string Universal path - * - * @return integer Revision - */ - function fetch_head_rev($path) - { - $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)); - - foreach ($output AS $line) - { - if (preg_match('#^Last Changed Rev: (.*)#', $line, $matches)) - { - return $matches[1]; - } - } - - $revs = $this->fetch_revs($path); - return $revs['HEAD']; - } - - /** - * Returns the previous revision to the one given - * - * @access public - * - * @param string Universal path - * @param integer Arbitrary revision - * - * @return integer Previous revision (-1 if none) - */ - function fetch_prev_rev($path, $current) - { - global $viewsvn; - - $revs = $this->fetch_revs($path); - - if ($current == 'HEAD') - { - $current = $this->fetch_head_rev($path); - } - - $index = array_search($current, $revs['revs']); - if ($current === false) - { - $message->trigger->error(sprintf($viewsvn->lang->string('Revision r%1$s is not in path %2$s'), $current, $path)); - } - - if (isset($revs['revs'][ $index + 1 ])) - { - return $revs['revs'][ $index + 1 ]; - } - else - { - return -1; - } - } - - /** - * Get a list of logs - * - * @access public - * - * @param string Universal path - * @param bool Override the cache system? - * - * @return array Log data - */ - function fetch_logs($path) - { - if (!isset($this->logs["$path"])) - { - $log = new SVNLog($this->registry->paths->fetch_repos($path), $this->registry->paths->fetch_path($path), 0, $this->registry->paths->revnum); - - $this->logs["$path"] = $log->fetch(); - } - - return $this->logs["$path"]; - } - - /** - * Returns a given log entry for a path - * and revision - * - * @access public - * - * @param string Universal path - * @param integer Arbitrary revision - * - * @return array Log entry - */ - function fetch_log($path, $rev) - { - $logs = $this->fetch_logs($path); - - $rev = $this->registry->svn->rev($rev); - if ($rev == 'HEAD') - { - $rev = $this->fetch_head_rev($path); - } - - if (isset($logs["$rev"])) - { - return $logs["$rev"]; - } - else - { - $keys = array_keys($logs); - sort($keys); - - for ($i = 0; $i < count($keys); $i++) - { - if ($rev > $keys["$i"] AND $rev < $keys[ $i + 1 ]) - { - return $logs["$keys[$i]"]; - } - } - - return null; - } - } - - /** - * Prints the file changed list - * - * @access public - * - * @public array List of file changes - * @public string The repository - * @public integer Current revision - * - * @return string Processed HTML - */ - function construct_file_changes($changes, $repos, $revision) - { - global $viewsvn; - - $files = ''; - - foreach ($changes AS $file) - { - switch ($file['action']) - { - case 'A': - $class = 'file_add'; - $tooltip = $viewsvn->lang->string('Added'); - break; - case 'D': - $class = 'file_delete'; - $tooltip = $viewsvn->lang->string('Deleted'); - break; - case 'M': - $class = 'file_modify'; - $tooltip = $viewsvn->lang->string('Modified'); - break; - case 'R': - $class = 'file_replace'; - $tooltip = $viewsvn->lang->string('Replaced'); - break; - } - - $show['from'] = (bool)$file['from']; - - if ($file['from']) - { - $class = 'file_move'; - $tooltip = 'Moved/Copied'; - preg_match('#(.*):([0-9]+)#', $file['from'], $matches); - $link['from'] = $viewsvn->paths->out('view.php' . $viewsvn->paths->fetch_rev_str(false, $matches[2]), $repos . $matches[1]); - } - - $link['file'] = $viewsvn->paths->out('view.php' . $viewsvn->paths->fetch_rev_str(false, $revision), $repos . $file['file']); - - eval('$files .= "' . $viewsvn->template->fetch('file_change') . '";'); - } - - return $files; - } -} - -/** -* Annotation/blame system; constructs an array -* that is ready for output -* -* @package ViewSVN -* @version $Id$ -*/ -class SVNBlame -{ - /** - * Array of blame information - * @var array - */ - var $blame = array(); - - /** - * Raw "svn blame" output - * @var array - */ - var $rawoutput; - - /** - * Constructor: create blame and store data - * - * @param string Repository - * @param string Path - * @param integer Revision - */ - function SVNBlame($repos, $path, $revision) - { - global $viewsvn; - - $this->rawoutput = $viewsvn->svn->blame($repos, $path, $revision); - $this->process(); - } - - /** - * Returns blame for display - * - * @access public - * - * @return array Blame data - */ - function fetch() - { - return $this->blame; - } - - /** - * Parses the blame data - * - * @access private - */ - function process() - { - $lineno = 1; - - foreach ($this->rawoutput AS $line) - { - if (preg_match('#^\s+([0-9]+)\s+([\w\.\-_]+)\s(.*)$#', $line, $matches)) - { - $this->blame[] = array( - 'rev' => $matches[1], - 'author' => $matches[2], - 'line' => $matches[3], - 'lineno' => $lineno++ - ); - } - // a blank line - else if (preg_match('#^\s+([0-9]+)\s+([\w\.\-_]+)$#', $line, $matches)) - { - $this->blame[] = array( - 'rev' => $matches[1], - 'author' => $matches[2], - 'line' => '', - 'lineno' => $lineno++ - ); - } - } - } -} - -/** -* Log management system; creates a complex list -* of SVN log information -* -* @package ViewSVN -* @version $Id$ -*/ -class SVNLog -{ - /** - * Array of logs - * @var array - */ - var $logs = array(); - - /** - * Raw "svn log" output - * @var array - */ - var $rawoutput; - - /** - * Constructor: create log store for the given file - * - * @param string Repository - * @param string Path - * @param integer Lower revision - * @param integer Higher revision - */ - function SVNLog($repos, $path, $lorev, $hirev) - { - global $viewsvn; - - $this->rawoutput = $viewsvn->svn->log($repos, $path, $lorev, $hirev); - $this->process(); - } - - /** - * Returns logs for display - * - * @access public - * - * @return array Log data - */ - function fetch() - { - return $this->logs; - } - - /** - * Splits up the raw output into a usable log - * - * @access private - */ - function process() - { - $lastrev = 0; - - for ($i = 1; $i <= count($this->rawoutput) - 1; $i++) - { - $line = $this->rawoutput["$i"]; - - if (preg_match('#^r([0-9]*) \| (.*?) \| (....-..-.. ..:..:..) ([0-9\-]*) \((.*?)\) \| ([0-9]*) lines?$#', $line, $matches)) - { - if (isset($this->logs["$lastrev"])) - { - $this->logs["$lastrev"]['message'] = $this->strip_last_line($this->logs["$lastrev"]['message']); - } - - $this->logs["$matches[1]"] = array( - 'rev' => $matches[1], - 'author' => $matches[2], - 'date' => $matches[3], - 'timezone' => $matches[4], - 'lines' => $matches[6], - 'message' => '' - ); - - $lastrev = $matches[1]; - } - else if (preg_match('#^\s+([ADMR])\s(.*)#', $line, $matches)) - { - if (preg_match('#(.*) \(from (.*?)\)$#', $matches[2], $amatches)) - { - $matches[2] = $amatches[1]; - } - - $this->logs["$lastrev"]['files'][] = array( - 'action' => $matches[1], - 'file' => trim($matches[2]), - 'from' => (isset($amatches[2]) ? $amatches[2] : '') - ); - } - else - { - if (trim($line) != 'Changed paths:') - { - $this->logs["$lastrev"]['message'] .= $line . "\n"; - } - } - } - - if (isset($this->logs["$lastrev"])) - { - $this->logs["$lastrev"]['message'] = $this->strip_last_line($this->logs["$lastrev"]['message']); - } - } - - /** - * Trims the last dash line off a message - * - * @access private - * - * @param string Message with annoying-ass line - * - * @return string Clean string - */ - function strip_last_line($string) - { - return trim(preg_replace("#\n(.*?)\n$#", '', $string)); - } -} - -/** -* Diff system; constructs a diff array that -* is ready for output -* -* @package ViewSVN -*/ -class SVNDiff -{ - /** - * Array of diff information - * @var array - */ - var $diff = array(); - - /** - * Raw "svn diff" output - * @var array - */ - var $rawoutput; - - /** - * Constructor: create and store diff data - * - * @param string Repository - * @param string Path - * @param integer Lower revision - * @param integer Higher revision - */ - function SVNDiff($repos, $path, $lorev, $hirev) - { - global $viewsvn; - - $this->rawoutput = $viewsvn->svn->diff($repos, $path, $lorev, $hirev); - $this->process(); - } - - /** - * Returns diffs for display - * - * @access public - * - * @return array Diff data - */ - function fetch() - { - return $this->diff; - } - - /** - * Processes and prepares diff data - * - * @access private - */ - function process() - { - global $viewsvn; - - $chunk = 0; - $indexcounter = null; - $curprop = ''; - - $delstack = array(); - - foreach ($this->rawoutput AS $line) - { - if (preg_match('#^@@ \-([0-9]*),([0-9]*) \+([0-9]*),([0-9]*) @@$#', $line, $bits)) - { - $property = false; - $delstack = array(); - $this->diff["$index"][ ++$chunk ]['hunk'] = array('old' => array('line' => $bits[1], 'count' => $bits[2]), 'new' => array('line' => $bits[3], 'count' => $bits[4])); - $lines['old'] = $this->diff["$index"]["$chunk"]['hunk']['old']['line'] - 1; - $lines['new'] = $this->diff["$index"]["$chunk"]['hunk']['new']['line'] - 1; - continue; - } - else if (preg_match('#^Property changes on: (.*?)$#', $line, $bits)) - { - $property = true; - $index = $bits[1]; - $this->diff["$index"]['props'] = array(); - continue; - } - - if ($indexcounter <= 3 AND $indexcounter !== null) - { - $indexcounter++; - continue; - } - else if ($indexcounter == 3) - { - $indexcounter = null; - continue; - } - - if (preg_match('#^([\+\- ])(.*)#', $line, $matches) AND !$property) - { - $act = $matches[1]; - $content = $matches[2]; - - if ($act == ' ') - { - $this->diff["$index"]["$chunk"][] = array( - 'line' => $content, - 'act' => '', - 'oldlineno' => ++$lines['old'], - 'newlineno' => ++$lines['new'] - ); - - $delstack = array(); - } - else if ($act == '+') - { - // potential line delta - if (count($delstack) > 0) - { - $lastline = array_shift($delstack); - - if ($delta = @$this->fetch_diff_extent($lastline['line'], $content)) - { - if (strlen($lastline['line']) > ($delta['start'] - $delta['end'])) - { - $end = strlen($lastline['line']) + $delta['end']; - $viewsvn->debug("RM delta- = " . $end); - $change = '{@-' . '-}' . substr($lastline['line'], $delta['start'], $end - $delta['start']) . '{/@-' . '-}'; - $this->diff["$index"]["$chunk"]["$lastline[INDEX]"]['line'] = substr($lastline['line'], 0, $delta['start']) . $change . substr($lastline['line'], $end); - } - - if (strlen($content) > $delta['start'] - $delta['end']) - { - $end = strlen($content) + $delta['end']; - $viewsvn->debug("MK delta+ = " . $end); - $change = '{@+' . '+}' . substr($content, $delta['start'], $end - $delta['start']) . '{/@+' . '+}'; - $content = substr($content, 0, $delta['start']) . $change . substr($content, $end); - } - } - } - - $this->diff["$index"]["$chunk"][] = array( - 'line' => $content, - 'act' => '+', - 'oldlineno' => '', - 'newlineno' => ++$lines['new'] - ); - } - else if ($act == '-') - { - $this->diff["$index"]["$chunk"][] = $thearray = array( - 'line' => $content, - 'act' => '-', - 'oldlineno' => ++$lines['old'], - 'newlineno' => '' - ); - - $key = count($this->diff["$index"]["$chunk"]) - 2; - $thearray['INDEX'] = $key; - - array_push($delstack, $thearray); - } - } - // whitespace lines - else - { - if (preg_match('#^Index: (.*?)$#', $line, $matches)) - { - $index = $matches[1]; - $indexcounter = 1; - $chunk = 0; - continue; - } - - if ($property) - { - if (preg_match('#^__*_$#', trim($line))) - { - $viewsvn->debug("skipping: $line"); - continue; - } - - if (preg_match('#Name: (.*?)$#', $line, $matches)) - { - $curprop = $matches[1]; - $viewsvn->debug("prop: $curprop"); - continue; - } - else - { - if (preg_match('#^\s+?\+(.*)#', $line, $matches)) - { - $mode = 'add'; - $this->diff["$index"]['props']["$curprop"]['add'] .= $matches[1]; - } - else if (preg_match('#^\s+?\-(.*)#', $line, $matches)) - { - $mode = 'del'; - $this->diff["$index"]['props']["$curprop"]['del'] .= $matches[1]; - } - else if (!preg_match('#^\s+[\+\- ](.*)#', $line) AND trim($line) != '') - { - $this->diff["$index"]['props']["$curprop"]["$mode"] .= "\n" . $line; - } - continue; - } - } - - $this->diff["$index"]["$chunk"][] = array( - 'line' => '', - 'act' => '', - 'oldlineno' => ++$lines['old'], - 'newlineno' => ++$lines['new'] - ); - - $delstack = array(); - } - } - } - - /** - * Returns the amount of change that occured - * between two lines - * - * @access private - * - * @param string Old line - * @param string New line - * - * @return array Difference of positions - */ - function fetch_diff_extent($old, $new) - { - global $viewsvn; - - $start = 0; - $min = min(strlen($old), strlen($new)); - - $viewsvn->debug("min1 = $min"); - - while ($start < $min AND $old["$start"] == $new["$start"]) - { - $start++; - } - - $end = -1; - $min = $min - $start; - - $viewsvn->debug("min2 = $min"); - - $viewsvn->debug("checking: " . $old[ strlen($old) + $end ] . ' == ' . $new[ strlen($new) + $end ]); - - while (-$end <= $min AND $old[ strlen($old) + $end ] == $new[ strlen($new) + $end ]) - { - $end--; - } - - return array('start' => $start, 'end' => $end + 1); - } -} - /*=====================================================================*\ || ################################################################### || # $HeadURL$ || # $Id$ || ################################################################### \*=====================================================================*/ -?> +?> \ No newline at end of file -- 2.22.5