/websvn-2.3.3/include/svnlook.php
PHP | 1325 lines | 966 code | 224 blank | 135 comment | 267 complexity | a14bff59bb07a24c2424612145652ccb MD5 | raw file
Possible License(s): AGPL-1.0
- <?php
- // WebSVN - Subversion repository viewing via the web using PHP
- // Copyright (C) 2004-2006 Tim Armes
- //
- // This program is free software; you can redistribute it and/or modify
- // it under the terms of the GNU General Public License as published by
- // the Free Software Foundation; either version 2 of the License, or
- // (at your option) any later version.
- //
- // This program is distributed in the hope that it will be useful,
- // but WITHOUT ANY WARRANTY; without even the implied warranty of
- // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- // GNU General Public License for more details.
- //
- // You should have received a copy of the GNU General Public License
- // along with this program; if not, write to the Free Software
- // Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
- //
- // --
- //
- // svn-look.php
- //
- // Svn bindings
- //
- // These binding currently use the svn command line to achieve their goal. Once a proper
- // SWIG binding has been produced for PHP, there'll be an option to use that instead.
- require_once 'include/utils.php';
- // {{{ Classes for retaining log information ---
- $debugxml = false;
- class SVNInfoEntry {
- var $rev = 1;
- var $path = '';
- var $isdir = false;
- }
- class SVNMod {
- var $action = '';
- var $copyfrom = '';
- var $copyrev = '';
- var $path = '';
- var $isdir = false;
- }
- class SVNListEntry {
- var $rev = 1;
- var $author = '';
- var $date = '';
- var $committime;
- var $age = '';
- var $file = '';
- var $isdir = false;
- }
- class SVNList {
- var $entries; // Array of entries
- var $curEntry; // Current entry
- var $path = ''; // The path of the list
- }
- class SVNLogEntry {
- var $rev = 1;
- var $author = '';
- var $date = '';
- var $committime;
- var $age = '';
- var $msg = '';
- var $path = '';
- var $precisePath = '';
- var $mods;
- var $curMod;
- }
- function SVNLogEntry_compare($a, $b) {
- return strnatcasecmp($a->path, $b->path);
- }
- class SVNLog {
- var $entries; // Array of entries
- var $curEntry; // Current entry
- var $path = ''; // Temporary variable used to trace path history
- // findEntry
- //
- // Return the entry for a given revision
- function findEntry($rev) {
- foreach ($this->entries as $index => $entry) {
- if ($entry->rev == $rev) {
- return $index;
- }
- }
- }
- }
- // }}}
- // {{{ XML parsing functions---
- $curTag = '';
- $curInfo = 0;
- // {{{ infoStartElement
- function infoStartElement($parser, $name, $attrs) {
- global $curInfo, $curTag, $debugxml;
- switch ($name) {
- case 'INFO':
- if ($debugxml) print 'Starting info'."\n";
- break;
- case 'ENTRY':
- if ($debugxml) print 'Creating info entry'."\n";
- if (count($attrs)) {
- while (list($k, $v) = each($attrs)) {
- switch ($k) {
- case 'KIND':
- if ($debugxml) print 'Kind '.$v."\n";
- $curInfo->isdir = ($v == 'dir');
- break;
- case 'REVISION':
- if ($debugxml) print 'Revision '.$v."\n";
- $curInfo->rev = $v;
- break;
- }
- }
- }
- break;
- default:
- $curTag = $name;
- break;
- }
- }
- // }}}
- // {{{ infoEndElement
- function infoEndElement($parser, $name) {
- global $curInfo, $debugxml, $curTag;
- switch ($name) {
- case 'ENTRY':
- if ($debugxml) print 'Ending info entry'."\n";
- if ($curInfo->isdir) {
- $curInfo->path .= '/';
- }
- break;
- }
- $curTag = '';
- }
- // }}}
- // {{{ infoCharacterData
- function infoCharacterData($parser, $data) {
- global $curInfo, $curTag, $debugxml;
- switch ($curTag) {
- case 'URL':
- if ($debugxml) print 'Url: '.$data."\n";
- $curInfo->path = $data;
- break;
- case 'ROOT':
- if ($debugxml) print 'Root: '.$data."\n";
- $curInfo->path = urldecode(substr($curInfo->path, strlen($data)));
- break;
- }
- }
- // }}}
- $curList = 0;
- // {{{ listStartElement
- function listStartElement($parser, $name, $attrs) {
- global $curList, $curTag, $debugxml;
- switch ($name) {
- case 'LIST':
- if ($debugxml) print 'Starting list'."\n";
- if (count($attrs)) {
- while (list($k, $v) = each($attrs)) {
- switch ($k) {
- case 'PATH':
- if ($debugxml) print 'Path '.$v."\n";
- $curList->path = $v;
- break;
- }
- }
- }
- break;
- case 'ENTRY':
- if ($debugxml) print 'Creating new entry'."\n";
- $curList->curEntry = new SVNListEntry;
- if (count($attrs)) {
- while (list($k, $v) = each($attrs)) {
- switch ($k) {
- case 'KIND':
- if ($debugxml) print 'Kind '.$v."\n";
- $curList->curEntry->isdir = ($v == 'dir');
- break;
- }
- }
- }
- break;
- case 'COMMIT':
- if ($debugxml) print 'Commit'."\n";
- if (count($attrs)) {
- while (list($k, $v) = each($attrs)) {
- switch ($k) {
- case 'REVISION':
- if ($debugxml) print 'Revision '.$v."\n";
- $curList->curEntry->rev = $v;
- break;
- }
- }
- }
- break;
- default:
- $curTag = $name;
- break;
- }
- }
- // }}}
- // {{{ listEndElement
- function listEndElement($parser, $name) {
- global $curList, $debugxml, $curTag;
- switch ($name) {
- case 'ENTRY':
- if ($debugxml) print 'Ending new list entry'."\n";
- if ($curList->curEntry->isdir) {
- $curList->curEntry->file .= '/';
- }
- $curList->entries[] = $curList->curEntry;
- $curList->curEntry = null;
- break;
- }
- $curTag = '';
- }
- // }}}
- // {{{ listCharacterData
- function listCharacterData($parser, $data) {
- global $curList, $curTag, $debugxml;
- switch ($curTag) {
- case 'NAME':
- if ($debugxml) print 'Name: '.$data."\n";
- if ($data === false || $data === '') return;
- $curList->curEntry->file .= $data;
- break;
- case 'AUTHOR':
- if ($debugxml) print 'Author: '.$data."\n";
- if ($data === false || $data === '') return;
- if (function_exists('mb_detect_encoding') && function_exists('mb_convert_encoding'))
- $data = mb_convert_encoding($data, 'UTF-8', mb_detect_encoding($data));
- $curList->curEntry->author .= $data;
- break;
- case 'DATE':
- if ($debugxml) print 'Date: '.$data."\n";
- $data = trim($data);
- if ($data === false || $data === '') return;
- $committime = parseSvnTimestamp($data);
- $curList->curEntry->committime = $committime;
- $curList->curEntry->date = strftime('%Y-%m-%d %H:%M:%S', $committime);
- $curList->curEntry->age = datetimeFormatDuration(max(time() - $committime, 0), true, true);
- break;
- }
- }
- // }}}
- $curLog = 0;
- // {{{ logStartElement
- function logStartElement($parser, $name, $attrs) {
- global $curLog, $curTag, $debugxml;
- switch ($name) {
- case 'LOGENTRY':
- if ($debugxml) print 'Creating new log entry'."\n";
- $curLog->curEntry = new SVNLogEntry;
- $curLog->curEntry->mods = array();
- $curLog->curEntry->path = $curLog->path;
- if (count($attrs)) {
- while (list($k, $v) = each($attrs)) {
- switch ($k) {
- case 'REVISION':
- if ($debugxml) print 'Revision '.$v."\n";
- $curLog->curEntry->rev = $v;
- break;
- }
- }
- }
- break;
- case 'PATH':
- if ($debugxml) print 'Creating new path'."\n";
- $curLog->curEntry->curMod = new SVNMod;
- if (count($attrs)) {
- while (list($k, $v) = each($attrs)) {
- switch ($k) {
- case 'ACTION':
- if ($debugxml) print 'Action '.$v."\n";
- $curLog->curEntry->curMod->action = $v;
- break;
- case 'COPYFROM-PATH':
- if ($debugxml) print 'Copy from: '.$v."\n";
- $curLog->curEntry->curMod->copyfrom = $v;
- break;
- case 'COPYFROM-REV':
- $curLog->curEntry->curMod->copyrev = $v;
- break;
- case 'KIND':
- if ($debugxml) print 'Kind '.$v."\n";
- $curLog->curEntry->curMod->isdir = ($v == 'dir');
- break;
- }
- }
- }
- $curTag = $name;
- break;
- default:
- $curTag = $name;
- break;
- }
- }
- // }}}
- // {{{ logEndElement
- function logEndElement($parser, $name) {
- global $curLog, $debugxml, $curTag;
- switch ($name) {
- case 'LOGENTRY':
- if ($debugxml) print 'Ending new log entry'."\n";
- $curLog->entries[] = $curLog->curEntry;
- break;
- case 'PATH':
- if ($debugxml) print 'Ending path'."\n";
- $curLog->curEntry->mods[] = $curLog->curEntry->curMod;
- break;
- case 'MSG':
- $curLog->curEntry->msg = trim($curLog->curEntry->msg);
- if ($debugxml) print 'Completed msg = "'.$curLog->curEntry->msg.'"'."\n";
- break;
- }
- $curTag = '';
- }
- // }}}
- // {{{ logCharacterData
- function logCharacterData($parser, $data) {
- global $curLog, $curTag, $debugxml;
- switch ($curTag) {
- case 'AUTHOR':
- if ($debugxml) print 'Author: '.$data."\n";
- if ($data === false || $data === '') return;
- if (function_exists('mb_detect_encoding') && function_exists('mb_convert_encoding'))
- $data = mb_convert_encoding($data, 'UTF-8', mb_detect_encoding($data));
- $curLog->curEntry->author .= $data;
- break;
- case 'DATE':
- if ($debugxml) print 'Date: '.$data."\n";
- $data = trim($data);
- if ($data === false || $data === '') return;
- $committime = parseSvnTimestamp($data);
- $curLog->curEntry->committime = $committime;
- $curLog->curEntry->date = strftime('%Y-%m-%d %H:%M:%S', $committime);
- $curLog->curEntry->age = datetimeFormatDuration(max(time() - $committime, 0), true, true);
- break;
- case 'MSG':
- if ($debugxml) print 'Msg: '.$data."\n";
- if (function_exists('mb_detect_encoding') && function_exists('mb_convert_encoding'))
- $data = mb_convert_encoding($data, 'UTF-8', mb_detect_encoding($data));
- $curLog->curEntry->msg .= $data;
- break;
- case 'PATH':
- if ($debugxml) print 'Path name: '.$data."\n";
- $data = trim($data);
- if ($data === false || $data === '') return;
- $curLog->curEntry->curMod->path .= $data;
- // The XML returned when a file is renamed/branched in inconsistent.
- // In the case of a branch, the path doesn't include the leafname.
- // In the case of a rename, it does. Ludicrous.
- if (!empty($curLog->path)) {
- $pos = strrpos($curLog->path, '/');
- $curpath = substr($curLog->path, 0, $pos);
- $leafname = substr($curLog->path, $pos + 1);
- } else {
- $curpath = '';
- $leafname = '';
- }
- $curMod = $curLog->curEntry->curMod;
- if ($curMod->action == 'A') {
- if ($debugxml) print 'Examining added path "'.$curMod->copyfrom.'" - Current path = "'.$curpath.'", leafname = "'.$leafname.'"'."\n";
- if ($data == $curLog->path) {
- // For directories and renames
- $curLog->path = $curMod->copyfrom;
- } else if ($data == $curpath || $data == $curpath.'/') {
- // Logs of files that have moved due to branching
- $curLog->path = $curMod->copyfrom.'/'.$leafname;
- } else {
- $curLog->path = str_replace($curMod->path, $curMod->copyfrom, $curLog->path);
- }
- if ($debugxml) print 'New path for comparison: "'.$curLog->path.'"'."\n";
- }
- break;
- }
- }
- // }}}
- // }}}
- // {{{ internal functions (_topLevel and _listSort)
- // Function returns true if the give entry in a directory tree is at the top level
- function _topLevel($entry) {
- // To be at top level, there must be one space before the entry
- return (strlen($entry) > 1 && $entry{0} == ' ' && $entry{1} != ' ');
- }
- // Function to sort two given directory entries.
- // Directories go at the top if config option alphabetic is not set
- function _listSort($e1, $e2) {
- global $config;
- $file1 = $e1->file;
- $file2 = $e2->file;
- $isDir1 = ($file1{strlen($file1) - 1} == '/');
- $isDir2 = ($file2{strlen($file2) - 1} == '/');
- if (!$config->isAlphabeticOrder()) {
- if ($isDir1 && !$isDir2) return -1;
- if ($isDir2 && !$isDir1) return 1;
- }
- if ($isDir1) $file1 = substr($file1, 0, -1);
- if ($isDir2) $file2 = substr($file2, 0, -1);
- return strnatcasecmp($file1, $file2);
- }
- // }}}
- // {{{ encodePath
- // Function to encode a URL without encoding the /'s
- function encodePath($uri) {
- global $config;
- $uri = str_replace(DIRECTORY_SEPARATOR, '/', $uri);
- if (function_exists('mb_detect_encoding') && function_exists('mb_convert_encoding')) {
- $uri = mb_convert_encoding($uri, 'UTF-8', mb_detect_encoding($uri));
- }
- $parts = explode('/', $uri);
- $partscount = count($parts);
- for ($i = 0; $i < $partscount; $i++) {
- // do not urlencode the 'svn+ssh://' part!
- if ($i != 0 || $parts[$i] != 'svn+ssh:') {
- $parts[$i] = rawurlencode($parts[$i]);
- }
- }
- $uri = implode('/', $parts);
- // Quick hack. Subversion seems to have a bug surrounding the use of %3A instead of :
- $uri = str_replace('%3A', ':', $uri);
- // Correct for Window share names
- if ($config->serverIsWindows) {
- if (substr($uri, 0, 2) == '//') {
- $uri = '\\'.substr($uri, 2, strlen($uri));
- }
- if (substr($uri, 0, 10) == 'file://///' ) {
- $uri = 'file:///\\'.substr($uri, 10, strlen($uri));
- }
- }
- return $uri;
- }
- // }}}
- function _equalPart($str1, $str2) {
- $len1 = strlen($str1);
- $len2 = strlen($str2);
- $i = 0;
- while ($i < $len1 && $i < $len2) {
- if (strcmp($str1{$i}, $str2{$i}) != 0) {
- break;
- }
- $i++;
- }
- if ($i == 0) {
- return '';
- }
- return substr($str1, 0, $i);
- }
- // The SVNRepository class
- class SVNRepository {
- var $repConfig;
- var $geshi = null;
- function SVNRepository($repConfig) {
- $this->repConfig = $repConfig;
- }
- // {{{ highlightLine
- //
- // Distill line-spanning syntax highlighting so that each line can stand alone
- // (when invoking on the first line, $attributes should be an empty array)
- // Invoked to make sure all open syntax highlighting tags (<font>, <i>, <b>, etc.)
- // are closed at the end of each line and re-opened on the next line
- function highlightLine($line, &$attributes) {
- $hline = '';
- // Apply any highlighting in effect from the previous line
- foreach ($attributes as $attr) {
- $hline .= $attr['text'];
- }
- // append the new line
- $hline .= $line;
- // update attributes
- for ($line = strstr($line, '<'); $line; $line = strstr(substr($line, 1), '<')) {
- if (substr($line, 1, 1) == '/') {
- // if this closes a tag, remove most recent corresponding opener
- $tagNamLen = strcspn($line, '> '."\t", 2);
- $tagNam = substr($line, 2, $tagNamLen);
- foreach (array_reverse(array_keys($attributes)) as $k) {
- if ($attributes[$k]['tag'] == $tagNam) {
- unset($attributes[$k]);
- break;
- }
- }
- } else {
- // if this opens a tag, add it to the list
- $tagNamLen = strcspn($line, '> '."\t", 1);
- $tagNam = substr($line, 1, $tagNamLen);
- $tagLen = strcspn($line, '>') + 1;
- $attributes[] = array('tag' => $tagNam, 'text' => substr($line, 0, $tagLen));
- }
- }
- // close any still-open tags
- foreach (array_reverse($attributes) as $attr) {
- $hline .= '</'.$attr['tag'].'>';
- }
- // XXX: this just simply replaces [ and ] with their entities to prevent
- // it from being parsed by the template parser; maybe something more
- // elegant is in order?
- $hline = str_replace('[', '[', str_replace(']', ']', $hline) );
- return $hline;
- }
- // }}}
- // Private function to simplify creation of common SVN command string text.
- function svnCommandString($command, $path, $rev, $peg) {
- global $config;
- return $config->getSvnCommand().$this->repConfig->svnCredentials().' '.$command.' '.($rev ? '-r '.$rev.' ' : '').quote(encodePath($this->getSvnPath($path)).'@'.($peg ? $peg : ''));
- }
- // Private function to simplify creation of enscript command string text.
- function enscriptCommandString($path) {
- global $config, $extEnscript;
- $filename = basename($path);
- $ext = strrchr($path, '.');
-
- $lang = false;
- if (array_key_exists($filename, $extEnscript)) {
- $lang = $extEnscript[$filename];
- } else if (array_key_exists($ext, $extEnscript)) {
- $lang = $extEnscript[$ext];
- }
- $cmd = $config->enscript.' --language=html';
- if ($lang !== false) {
- $cmd .= ' --color --'.(!$config->getUseEnscriptBefore_1_6_3() ? 'highlight' : 'pretty-print').'='.$lang;
- }
- $cmd .= ' -o -';
- return $cmd;
- }
- // {{{ getFileContents
- //
- // Dump the content of a file to the given filename
- function getFileContents($path, $filename, $rev = 0, $peg = '', $pipe = '', $highlight = 'file') {
- global $config;
- assert ($highlight == 'file' || $highlight == 'no' || $highlight == 'line');
- $highlighted = false;
- // If there's no filename, just deliver the contents as-is to the user
- if ($filename == '') {
- $cmd = $this->svnCommandString('cat', $path, $rev, $peg);
- passthruCommand($cmd.' '.$pipe);
- return $highlighted;
- }
- // Get the file contents info
- $tempname = $filename;
- if ($highlight == 'line') {
- $tempname = tempnamWithCheck($config->getTempDir(), '');
- }
- $highlighted = true;
- if ($highlight != 'no' && $config->useGeshi && $geshiLang = $this->highlightLanguageUsingGeshi($path)) {
- $this->applyGeshi($path, $tempname, $geshiLang, $rev, $peg);
- } else if ($highlight != 'no' && $config->useEnscript) {
- // Get the files, feed it through enscript, then remove the enscript headers using sed
- // Note that the sed command returns only the part of the file between <PRE> and </PRE>.
- // It's complicated because it's designed not to return those lines themselves.
- $cmd = $this->svnCommandString('cat', $path, $rev, $peg);
- $cmd = quoteCommand($cmd.' | '.$this->enscriptCommandString($path).' | '.
- $config->sed.' -n '.$config->quote.'1,/^<PRE.$/!{/^<\\/PRE.$/,/^<PRE.$/!p;}'.$config->quote.' > '.$tempname);
- } else {
- $highlighted = false;
- $cmd = $this->svnCommandString('cat', $path, $rev, $peg);
- $cmd = quoteCommand($cmd.' > '.quote($filename));
- }
- if (isset($cmd)) {
- $descriptorspec = array(2 => array('pipe', 'w')); // stderr
- $resource = proc_open($cmd, $descriptorspec, $pipes);
- $error = '';
- while (!feof($pipes[2])) {
- $error .= fgets($pipes[2]);
- }
- $error = trim($error);
- fclose($pipes[2]);
- proc_close($resource);
- if (!empty($error)) {
- global $lang;
- error_log($lang['BADCMD'].': '.$cmd);
- error_log($error);
- global $vars;
- $vars['warning'] = nl2br(escape(toOutputEncoding($error)));
- }
- }
- if ($highlighted && $highlight == 'line') {
- // If we need each line independently highlighted (e.g. for diff or blame)
- // then we'll need to filter the output of the highlighter
- // to make sure tags like <font>, <i> or <b> don't span lines
- $dst = fopen($filename, 'w');
- if ($dst) {
- $content = file_get_contents($tempname);
- $content = explode('<br />', $content);
- // $attributes is used to remember what highlighting attributes
- // are in effect from one line to the next
- $attributes = array(); // start with no attributes in effect
- foreach ($content as $line) {
- fputs($dst, $this->highlightLine(trim($line), $attributes)."\n");
- }
- fclose($dst);
- }
- }
- if ($tempname != $filename) {
- @unlink($tempname);
- }
- return $highlighted;
- }
- // }}}
- // {{{ highlightLanguageUsingGeshi
- //
- // check if geshi can highlight the given extension and return the language
- function highlightLanguageUsingGeshi($path) {
- global $extGeshi;
- $filename = basename($path);
- $ext = strrchr($path, '.');
- if (substr($ext, 0, 1) == '.') $ext = substr($ext, 1);
- foreach ($extGeshi as $language => $extensions) {
- if (in_array($filename, $extensions) || in_array($ext, $extensions)) {
- if ($this->geshi === null) {
- require_once 'geshi.php';
- $this->geshi = new GeSHi();
- } else {
- $this->geshi->error = false;
- }
- $this->geshi->set_language($language);
- if ($this->geshi->error() === false) {
- return $language;
- }
- }
- }
- return '';
- }
- // }}}
- // {{{ applyGeshi
- //
- // perform syntax highlighting using geshi
- function applyGeshi($path, $filename, $language, $rev, $peg = '', $return = false) {
- // Output the file to the filename
- $cmd = quoteCommand($this->svnCommandString('cat', $path, $rev, $peg).' > '.quote($filename));
- $descriptorspec = array(2 => array('pipe', 'w')); // stderr
- $resource = proc_open($cmd, $descriptorspec, $pipes);
- $error = '';
- while (!feof($pipes[2])) {
- $error .= fgets($pipes[2]);
- }
- $error = trim($error);
- fclose($pipes[2]);
- proc_close($resource);
- if (!empty($error)) {
- global $lang;
- error_log($lang['BADCMD'].': '.$cmd);
- error_log($error);
- global $vars;
- $vars['warning'] = 'Unable to cat file: '.nl2br(escape(toOutputEncoding($error)));
- return;
- }
- $source = file_get_contents($filename);
- if ($this->geshi === null) {
- require_once 'geshi.php';
- $this->geshi = new GeSHi();
- }
- $this->geshi->set_source($source);
- $this->geshi->set_language($language);
- $this->geshi->set_header_type(GESHI_HEADER_NONE);
- $this->geshi->set_overall_class('geshi');
- $this->geshi->set_tab_width($this->repConfig->getExpandTabsBy());
- if ($return) {
- return $this->geshi->parse_code();
- } else {
- $f = @fopen($filename, 'w');
- fwrite($f, $this->geshi->parse_code());
- fclose($f);
- }
- }
- // }}}
- // {{{ listFileContents
- //
- // Print the contents of a file without filling up Apache's memory
- function listFileContents($path, $rev = 0, $peg = '') {
- global $config;
- if ($config->useGeshi && $geshiLang = $this->highlightLanguageUsingGeshi($path)) {
- $tempname = tempnamWithCheck($config->getTempDir(), 'wsvn');
- if ($tempname !== false) {
- print toOutputEncoding($this->applyGeshi($path, $tempname, $geshiLang, $rev, $peg, true));
- @unlink($tempname);
- }
- } else {
- $pre = false;
- $cmd = $this->svnCommandString('cat', $path, $rev, $peg);
- if ($config->useEnscript) {
- $cmd .= ' | '.$this->enscriptCommandString($path).' | '.
- $config->sed.' -n '.$config->quote.'/^<PRE.$/,/^<\\/PRE.$/p'.$config->quote;
- } else {
- $pre = true;
- }
- if ($result = popenCommand($cmd, 'r')) {
- if ($pre)
- echo '<pre>';
- while (!feof($result)) {
- $line = fgets($result, 1024);
- $line = toOutputEncoding($line);
- if ($pre) {
- $line = escape($line);
- }
- print hardspace($line);
- }
- if ($pre)
- echo '</pre>';
- pclose($result);
- }
- }
- }
- // }}}
- // {{{ getBlameDetails
- //
- // Dump the blame content of a file to the given filename
- function getBlameDetails($path, $filename, $rev = 0, $peg = '') {
- $cmd = quoteCommand($this->svnCommandString('blame', $path, $rev, $peg).' > '.quote($filename));
- $descriptorspec = array(2 => array('pipe', 'w')); // stderr
- $resource = proc_open($cmd, $descriptorspec, $pipes);
- $error = '';
- while (!feof($pipes[2])) {
- $error .= fgets($pipes[2]);
- }
- $error = trim($error);
- fclose($pipes[2]);
- proc_close($resource);
- if (!empty($error)) {
- global $lang;
- error_log($lang['BADCMD'].': '.$cmd);
- error_log($error);
- global $vars;
- $vars['warning'] = 'No blame info: '.nl2br(escape(toOutputEncoding($error)));
- }
- }
- // }}}
- function getProperties($path, $rev = 0, $peg = '') {
- $cmd = $this->svnCommandString('proplist', $path, $rev, $peg);
- $ret = runCommand($cmd, true);
- $properties = array();
- if (is_array($ret)) {
- foreach ($ret as $line) {
- if (substr($line, 0, 1) == ' ') {
- $properties[] = ltrim($line);
- }
- }
- }
- return $properties;
- }
- // {{{ getProperty
- function getProperty($path, $property, $rev = 0, $peg = '') {
- $cmd = $this->svnCommandString('propget '.$property, $path, $rev, $peg);
- $ret = runCommand($cmd, true);
- // Remove the surplus newline
- if (count($ret)) {
- unset($ret[count($ret) - 1]);
- }
- return implode("\n", $ret);
- }
- // }}}
- // {{{ exportDirectory
- //
- // Exports the directory to the given location
- function exportRepositoryPath($path, $filename, $rev = 0, $peg = '') {
- $cmd = $this->svnCommandString('export', $path, $rev, $peg).' '.quote($filename);
- $retcode = 0;
- execCommand($cmd, $retcode);
- if ($retcode != 0) {
- global $lang;
- error_log($lang['BADCMD'].': '.$cmd);
- }
- return $retcode;
- }
- // }}}
- // {{{ getInfo
- function getInfo($path, $rev = 0, $peg = '') {
- global $config, $curInfo;
- $xml_parser = xml_parser_create('UTF-8');
- xml_parser_set_option($xml_parser, XML_OPTION_CASE_FOLDING, true);
- xml_set_element_handler($xml_parser, 'infoStartElement', 'infoEndElement');
- xml_set_character_data_handler($xml_parser, 'infoCharacterData');
- // Since directories returned by svn log don't have trailing slashes (:-(), we need to remove
- // the trailing slash from the path for comparison purposes
- if ($path{strlen($path) - 1} == '/' && $path != '/') {
- $path = substr($path, 0, -1);
- }
- $curInfo = new SVNInfoEntry;
- // Get the svn info
- if ($rev == 0) {
- $headlog = $this->getLog('/', '', '', true, 1);
- if ($headlog && isset($headlog->entries[0]))
- $rev = $headlog->entries[0]->rev;
- }
- $cmd = quoteCommand($this->svnCommandString('info --xml', $path, $rev, $peg));
- $descriptorspec = array(0 => array('pipe', 'r'), 1 => array('pipe', 'w'), 2 => array('pipe', 'w'));
- $resource = proc_open($cmd, $descriptorspec, $pipes);
- if (!is_resource($resource)) {
- global $lang;
- echo $lang['BADCMD'].': <code>'.escape(stripCredentialsFromCommand($cmd)).'</code>';
- exit;
- }
- $handle = $pipes[1];
- $firstline = true;
- while (!feof($handle)) {
- $line = fgets($handle);
- if (!xml_parse($xml_parser, $line, feof($handle))) {
- $errorMsg = sprintf('XML error: %s (%d) at line %d column %d byte %d'."\n".'cmd: %s',
- xml_error_string(xml_get_error_code($xml_parser)),
- xml_get_error_code($xml_parser),
- xml_get_current_line_number($xml_parser),
- xml_get_current_column_number($xml_parser),
- xml_get_current_byte_index($xml_parser),
- $cmd);
- if (xml_get_error_code($xml_parser) != 5) {
- // errors can contain sensitive info! don't echo this ~J
- error_log($errorMsg);
- exit;
- } else {
- break;
- }
- }
- }
- $error = '';
- while (!feof($pipes[2])) {
- $error .= fgets($pipes[2]);
- }
- $error = toOutputEncoding(trim($error));
- fclose($pipes[0]);
- fclose($pipes[1]);
- fclose($pipes[2]);
- proc_close($resource);
- xml_parser_free($xml_parser);
- if (!empty($error)) {
- $error = toOutputEncoding(nl2br(str_replace('svn: ', '', $error)));
- global $lang;
- error_log($lang['BADCMD'].': '.$cmd);
- error_log($error);
- global $vars;
- if (strstr($error, 'found format')) {
- $vars['error'] = 'Repository uses a newer format than Subversion '.$config->getSubversionVersion().' can read. ("'.nl2br(escape(toOutputEncoding(substr($error, strrpos($error, 'Expected'))))).'.")';
- } else if (strstr($error, 'No such revision')) {
- $vars['warning'] = 'Revision '.escape($rev).' of this resource does not exist.';
- } else {
- $vars['error'] = $lang['BADCMD'].': <code>'.escape(stripCredentialsFromCommand($cmd)).'</code><br />'.nl2br(escape(toOutputEncoding($error)));
- }
- return null;
- }
- if ($this->repConfig->subpath !== null) {
- if (substr($curInfo->path, 0, strlen($this->repConfig->subpath) + 1) === '/'. $this->repConfig->subpath) {
- $curInfo->path = substr($curInfo->path, strlen($this->repConfig->subpath) + 1);
- } else {
- global $vars;
- $vars['error'] = 'Info entry does not start with subpath for repository with subpath';
- return null;
- }
- }
- return $curInfo;
- }
- // }}}
- // {{{ getList
- function getList($path, $rev = 0, $peg = '') {
- global $config, $curList;
- $xml_parser = xml_parser_create('UTF-8');
- xml_parser_set_option($xml_parser, XML_OPTION_CASE_FOLDING, true);
- xml_set_element_handler($xml_parser, 'listStartElement', 'listEndElement');
- xml_set_character_data_handler($xml_parser, 'listCharacterData');
- // Since directories returned by svn log don't have trailing slashes (:-(), we need to remove
- // the trailing slash from the path for comparison purposes
- if ($path{strlen($path) - 1} == '/' && $path != '/') {
- $path = substr($path, 0, -1);
- }
- $curList = new SVNList;
- $curList->entries = array();
- $curList->path = $path;
- // Get the list info
- if ($rev == 0) {
- $headlog = $this->getLog('/', '', '', true, 1);
- if ($headlog && isset($headlog->entries[0]))
- $rev = $headlog->entries[0]->rev;
- }
- $cmd = quoteCommand($this->svnCommandString('list --xml', $path, $rev, $peg));
- $descriptorspec = array(0 => array('pipe', 'r'), 1 => array('pipe', 'w'), 2 => array('pipe', 'w'));
- $resource = proc_open($cmd, $descriptorspec, $pipes);
- if (!is_resource($resource)) {
- global $lang;
- echo $lang['BADCMD'].': <code>'.escape(stripCredentialsFromCommand($cmd)).'</code>';
- exit;
- }
- $handle = $pipes[1];
- $firstline = true;
- while (!feof($handle)) {
- $line = fgets($handle);
- if (!xml_parse($xml_parser, $line, feof($handle))) {
- $errorMsg = sprintf('XML error: %s (%d) at line %d column %d byte %d'."\n".'cmd: %s',
- xml_error_string(xml_get_error_code($xml_parser)),
- xml_get_error_code($xml_parser),
- xml_get_current_line_number($xml_parser),
- xml_get_current_column_number($xml_parser),
- xml_get_current_byte_index($xml_parser),
- $cmd);
- if (xml_get_error_code($xml_parser) != 5) {
- // errors can contain sensitive info! don't echo this ~J
- error_log($errorMsg);
- exit;
- } else {
- break;
- }
- }
- }
- $error = '';
- while (!feof($pipes[2])) {
- $error .= fgets($pipes[2]);
- }
- $error = toOutputEncoding(trim($error));
- fclose($pipes[0]);
- fclose($pipes[1]);
- fclose($pipes[2]);
- proc_close($resource);
- xml_parser_free($xml_parser);
- if (!empty($error)) {
- $error = toOutputEncoding(nl2br(str_replace('svn: ', '', $error)));
- global $lang;
- error_log($lang['BADCMD'].': '.$cmd);
- error_log($error);
- global $vars;
- if (strstr($error, 'found format')) {
- $vars['error'] = 'Repository uses a newer format than Subversion '.$config->getSubversionVersion().' can read. ("'.nl2br(escape(toOutputEncoding(substr($error, strrpos($error, 'Expected'))))).'.")';
- } else if (strstr($error, 'No such revision')) {
- $vars['warning'] = 'Revision '.escape($rev).' of this resource does not exist.';
- } else {
- $vars['error'] = $lang['BADCMD'].': <code>'.escape(stripCredentialsFromCommand($cmd)).'</code><br />'.nl2br(escape(toOutputEncoding($error)));
- }
- return null;
- }
- // Sort the entries into alphabetical order
- usort($curList->entries, '_listSort');
- return $curList;
- }
- // }}}
- // {{{ getLog
- function getLog($path, $brev = '', $erev = 1, $quiet = false, $limit = 2, $peg = '') {
- global $config, $curLog;
- $xml_parser = xml_parser_create('UTF-8');
- xml_parser_set_option($xml_parser, XML_OPTION_CASE_FOLDING, true);
- xml_set_element_handler($xml_parser, 'logStartElement', 'logEndElement');
- xml_set_character_data_handler($xml_parser, 'logCharacterData');
- // Since directories returned by svn log don't have trailing slashes (:-(),
- // we must remove the trailing slash from the path for comparison purposes.
- if ($path != '/' && $path{strlen($path) - 1} == '/') {
- $path = substr($path, 0, -1);
- }
- $curLog = new SVNLog;
- $curLog->entries = array();
- $curLog->path = $path;
- // Get the log info
- $effectiveRev = ($brev && $erev ? $brev.':'.$erev : ($brev ? $brev.':1' : ''));
- $effectivePeg = ($peg ? $peg : ($brev ? $brev : ''));
- $cmd = quoteCommand($this->svnCommandString('log --xml '.($quiet ? '--quiet' : '--verbose'), $path, $effectiveRev, $effectivePeg));
- if (($config->subversionMajorVersion > 1 || $config->subversionMinorVersion >= 2) && $limit != 0) {
- $cmd .= ' --limit '.$limit;
- }
- $descriptorspec = array(0 => array('pipe', 'r'), 1 => array('pipe', 'w'), 2 => array('pipe', 'w'));
- $resource = proc_open($cmd, $descriptorspec, $pipes);
- if (!is_resource($resource)) {
- global $lang;
- echo $lang['BADCMD'].': <code>'.escape(stripCredentialsFromCommand($cmd)).'</code>';
- exit;
- }
- $handle = $pipes[1];
- $firstline = true;
- while (!feof($handle)) {
- $line = fgets($handle);
- if (!xml_parse($xml_parser, $line, feof($handle))) {
- $errorMsg = sprintf('XML error: %s (%d) at line %d column %d byte %d'."\n".'cmd: %s',
- xml_error_string(xml_get_error_code($xml_parser)),
- xml_get_error_code($xml_parser),
- xml_get_current_line_number($xml_parser),
- xml_get_current_column_number($xml_parser),
- xml_get_current_byte_index($xml_parser),
- $cmd);
- if (xml_get_error_code($xml_parser) != 5) {
- // errors can contain sensitive info! don't echo this ~J
- error_log($errorMsg);
- exit;
- } else {
- break;
- }
- }
- }
- $error = '';
- while (!feof($pipes[2])) {
- $error .= fgets($pipes[2]);
- }
- $error = trim($error);
- fclose($pipes[0]);
- fclose($pipes[1]);
- fclose($pipes[2]);
- proc_close($resource);
- if (!empty($error)) {
- global $lang;
- error_log($lang['BADCMD'].': '.$cmd);
- error_log($error);
- global $vars;
- if (strstr($error, 'found format')) {
- $vars['error'] = 'Repository uses a newer format than Subversion '.$config->getSubversionVersion().' can read. ("'.nl2br(escape(toOutputEncoding(substr($error, strrpos($error, 'Expected'))))).'.")';
- } else if (strstr($error, 'No such revision')) {
- $vars['warning'] = 'Revision '.escape($brev).' of this resource does not exist.';
- } else {
- $vars['error'] = $lang['BADCMD'].': <code>'.escape(stripCredentialsFromCommand($cmd)).'</code><br />'.nl2br(escape(toOutputEncoding($error)));
- }
- return null;
- }
- xml_parser_free($xml_parser);
- foreach ($curLog->entries as $entryKey => $entry) {
- $fullModAccess = true;
- $anyModAccess = (count($entry->mods) == 0);
- $precisePath = null;
- foreach ($entry->mods as $modKey => $mod) {
- $access = $this->repConfig->hasReadAccess($mod->path);
- if ($access) {
- $anyModAccess = true;
- // find path which is parent of all modification but more precise than $curLogEntry->path
- $modpath = $mod->path;
- if (!$mod->isdir || $mod->action == 'D') {
- $pos = strrpos($modpath, '/');
- $modpath = substr($modpath, 0, $pos + 1);
- }
- if (strlen($modpath) == 0 || substr($modpath, -1) !== '/') {
- $modpath .= '/';
- }
- //compare with current precise path
- if ($precisePath === null) {
- $precisePath = $modpath;
- } else {
- $equalPart = _equalPart($precisePath, $modpath);
- if (substr($equalPart, -1) !== '/') {
- $pos = strrpos($equalPart, '/');
- $equalPart = substr($equalPart, 0, $pos + 1);
- }
- $precisePath = $equalPart;
- }
- } else {
- // hide modified entry when access is prohibited
- unset($curLog->entries[$entryKey]->mods[$modKey]);
- $fullModAccess = false;
- }
- // fix paths if command was for a subpath repository
- if ($this->repConfig->subpath !== null) {
- if (substr($mod->path, 0, strlen($this->repConfig->subpath) + 1) === '/'. $this->repConfig->subpath) {
- $curLog->entries[$entryKey]->mods[$modKey]->path = substr($mod->path, strlen($this->repConfig->subpath) + 1);
- } else {
- $vars['error'] = 'Log entries do not start with subpath for repository with subpath';
- return null;
- }
- }
- }
- if (!$fullModAccess) {
- // hide commit message when access to any of the entries is prohibited
- $curLog->entries[$entryKey]->msg = '';
- }
- if (!$anyModAccess) {
- // hide author and date when access to all of the entries is prohibited
- $curLog->entries[$entryKey]->author = '';
- $curLog->entries[$entryKey]->date = '';
- $curLog->entries[$entryKey]->committime = '';
- $curLog->entries[$entryKey]->age = '';
- }
- if ($precisePath !== null) {
- $curLog->entries[$entryKey]->precisePath = $precisePath;
- } else {
- $curLog->entries[$entryKey]->precisePath = $curLog->entries[$entryKey]->path;
- }
- }
- return $curLog;
- }
- // }}}
- function isFile($path, $rev = 0, $peg = '') {
- $cmd = $this->svnCommandString('info --xml', $path, $rev, $peg);
- return strpos(implode(' ', runCommand($cmd, true)), 'kind="file"') !== false;
- }
- // {{{ getSvnPath
- function getSvnPath($path) {
- if ($this->repConfig->subpath === null) {
- return $this->repConfig->path.$path;
- } else {
- return $this->repConfig->path.'/'.$this->repConfig->subpath.$path;
- }
- }
- // }}}
- }
- // Initialize SVN version information by parsing from command-line output.
- $cmd = $config->getSvnCommand();
- $cmd = str_replace(array('--non-interactive', '--trust-server-cert'), array('', ''), $cmd);
- $cmd .= ' --version';
- $ret = runCommand($cmd, false);
- if (preg_match('~([0-9]+)\.([0-9]+)\.([0-9]+)~', $ret[0], $matches)) {
- $config->setSubversionVersion($matches[0]);
- $config->setSubversionMajorVersion($matches[1]);
- $config->setSubversionMinorVersion($matches[2]);
- }