<?php
/**
* BSVN Parsers
* @copyright (c) 2010 Kamil Kamiński
* @license GPL v3 (http://gplv3.fsf.org/)
* @author Kamil Kamiński
* @package bsvn
*/
/**
* Parser for svn info output
* @package bsvn
*/
class BSVN_InfoParser {
public $url;
public $root;
public $uuid;
public $entry;
/**
* Creates new parser instance for provided XML
* @param string $xmlstring
*/
public function __construct($xmlstring) {
$dom = new DomDocument();
$dom->loadXml($xmlstring);
$xpath = new DomXPath($dom);
$this->uuid = self::getstring( $xpath->evaluate('//uuid') );
$this->root = self::getstring( $xpath->evaluate('//root') );
$this->url = self::getstring( $xpath->evaluate('//url') );
$this->entry = new BSVN_Entry( $xpath->evaluate('/info/entry')->item(0));
}
private static function getstring(DOMNodeList $nodes) {
foreach($nodes as $n) {
//var_dump($n);
//printf("type: %s\n", $n->nodeType);
//printf("value: %s\n", $n->nodeValue);
if ($n->nodeType == XML_ELEMENT_NODE)
return trim($n->nodeValue);
}
}
}
/**
* XML "entry" node
*/
class BSVN_Entry {
public $kind;
public $name;
public $path;
public $revision;
/**
* @var BSVN_Commit
*/
public $commit;
function __construct(DOMNode $item = null) {
if ($item !== null)
$this->parse($item);
}
/**
* Parses provided DOMNode
* @param DOMNode $item
*/
public function parse(DOMNode $item) {
if ($item->hasAttributes()) {
$this->kind = $item->getAttribute('kind');
$this->path = $item->getAttribute('path');
$this->revision = $item->getAttribute('revision');
}
foreach ($item->childNodes as $node) {
if ($node->nodeType != XML_ELEMENT_NODE)
continue;
switch ($node->nodeName) {
case 'commit': $this->commit = new BSVN_Commit($node); continue;
case 'name': $this->name = $node->nodeValue; continue;
default: continue;//bsvn_verbose(sprintf("Unknown node: %s (%s)\n", $node->nodeName, trim($node->nodeValue)));
}
}
}
}
/**
* XML "commit" node
*/
class BSVN_Commit {
public $revision;
public $author;
public $date;
function __construct(DOMNode $item=null) {
if ($item !== null) {
$this->parse($item);
}
}
/**
* Parses provided DOMNode
* @param DOMNode $item
*/
public function parse(DOMNode $item) {
if ($item->hasAttributes())
$this->revision = $item->getAttribute('revision');
foreach($item->childNodes as $node) {
if ($node->nodeType != XML_ELEMENT_NODE)
continue;
switch ($node->nodeName) {
case 'author': $this->author = $node->nodeValue; continue;
case 'date': $this->date = new DateTime($node->nodeValue); continue;
default: bsvn_verbose(sprintf("Unknown node: %s (%s)\n", $node->nodeName, trim($node->nodeValue)));
}
}
}
}
/**
* Parser for svn list output
*/
class BSVN_ListParser {
public $path;
/**
* Listed entries
* @var BSVN_Entry array
*/
public $entries = null;
function __construct($xmlstring='') {
if ($xmlstring != '') {
$dom = new DomDocument();
$dom->loadXml($xmlstring);
$xpath = new DomXPath($dom);
$this->entries = array();
$this->parse($xpath->evaluate( '/lists/list')->item(0));
}
}
/**
* Parses provided DOMNode
* @param DOMNode $item
*/
public function parse(DOMNode $item) {
if ($item->hasAttributes())
$this->path = $item->getAttribute('path');
foreach ($item->childNodes as $node) {
if ($node->nodeType != XML_ELEMENT_NODE)
continue;
if ($node->nodeName == 'entry') {
$e = new BSVN_Entry($node);
$this->entries[$e->name] = $e;
}
}
}
}
/**
* Parser for svn log output
*/
class BSVN_LogParser {
function __construct($xmlstream, $isFile=false) {
if ($xmlstream != '') {
BSVN_Database::get()->beginTransaction();
try {
$dom = new DomDocument();
if ($isFile)
$load = $dom->load($xmlstream);
else $load = $dom->loadXml($xmlstream);
if (!$load)
throw new Exception('Cannot load XML!');
unset($load);
foreach ($dom->childNodes as $masternode) {
if ($masternode->nodeType != XML_ELEMENT_NODE)
continue;
if ($masternode->nodeName == 'log') {
foreach ($masternode->childNodes as $node) {
if ($node->nodeType != XML_ELEMENT_NODE)
continue;
if ($node->nodeName == 'logentry') {
$logentry = new BSVN_LogEntry($node);
$logentry->save();
$sql = BSVN_Database::prepare('UPDATE authors SET active=?,commits=commits+1, a=a+?, m=m+?, d=d+?, r=r+? WHERE id=?');
$sql->bindValue(1, $logentry->date->format('U'), PDO::PARAM_STR);
$sql->bindValue(2, $logentry->a, PDO::PARAM_INT);
$sql->bindValue(3, $logentry->m, PDO::PARAM_INT);
$sql->bindValue(4, $logentry->d, PDO::PARAM_INT);
$sql->bindValue(5, $logentry->r, PDO::PARAM_INT);
$sql->bindValue(6, $logentry->author, PDO::PARAM_INT);
$sql->execute();
}
}
}
}
} catch (Exception $ex) {
@file_put_contents('php://stderr', $ex->getMessage());
if (BSVN_Commandline::hasSwitch('verbose'))
@file_put_contents('php://stderr', $ex->getTraceAsString ());
}
BSVN_Database::get()->commit();
}
}
public function __destruct() {
$this->repo = null;
}
}
/**
* Single log entry instannce
*/
class BSVN_LogEntry {
/**
* Revision number
* @var int
*/
public $revision;
/**
* Revision author's id
* @var id
*/
public $author;
/**
* Commit date
* @var DateTime
*/
public $date=null;
/**
* Revision message
* @var string
*/
public $msg;
/**
* Files added in this commit
* @var int
*/
public $a;
/**
* Files modified in this commit
* @var int
*/
public $m;
/**
* Fies deleted in this commit
* @var int
*/
public $d;
/**
*
* @var int
*/
public $r;
/**
*
* @var int
*/
public $branch = null;
function __construct(DOMNode $node=null) {
if ($node !== null) {
$this->parse($node);
}
else {
if ($this->date !== null) // this->date mogło już być DateTime, ze względu na wywołanie parse
$this->date = new DateTime('@'.$this->date);
}
}
/**
* Parses provided node
* @param DOMNode $node
*/
public function parse(DOMNode &$node) {
$this->m = 0;
$this->d = 0;
$this->r = 0;
$this->a = 0;
if ($node->hasAttributes())
$this->revision = $node->getAttribute('revision');
foreach ($node->childNodes as $cn) {
if ($cn->nodeType != XML_ELEMENT_NODE)
continue;
switch ($cn->nodeName) {
case 'author':
$this->author = BSVN_Repository::get()->requestAuthorId(trim($cn->nodeValue));
continue;
case 'date': $this->date = new DateTime($cn->nodeValue); $this->date->setTimezone( BSVN_Repository::get()->getTimezone() ); continue;
case 'msg': $this->msg = trim($cn->nodeValue); continue;
case 'paths':
foreach($cn->childNodes as $pathnode) {
if ($pathnode->nodeType != XML_ELEMENT_NODE)
continue;
if ($pathnode->nodeName == 'path') {
$o = new BSVN_LogEntryPath($pathnode);
$o->revision = $this->revision;
$o->save();
switch($o->action) {
case 'm': $this->m++; break;
case 'd': $this->d++; break;
case 'r': $this->r++; break;
case 'a': $this->a++; break;
default: file_put_contents('php://stderr', 'Unknown action: '.$o->action."\n");
}
if ($this->branch === null)
$this->detectBranch($o->path);
}
}
}
}
}
private function detectBranch($path) {
$m = array();
if (preg_match('/'.BSVN_Repository::get()->branchesDir.'\/([^\0\/]+)/i', $path, $m)) {
$this->branch = BSVN_Repository::get()->getBranchId($m[1]);
}
}
/**
* Saves revision onto database
*/
public function save() {
$branchid = $this->branch===null?0:$this->branch;
$sql = BSVN_Database::prepare('REPLACE INTO revisions (revision, branch, author, msg, date, a, m, d, r) VALUES(?,?,?,?,?,?,?,?,?)');
$sql->bindValue(1, $this->revision, PDO::PARAM_INT);
$sql->bindValue(2, $branchid, PDO::PARAM_INT);
$sql->bindValue(3, $this->author, PDO::PARAM_INT);
$sql->bindValue(4, $this->msg, PDO::PARAM_STR);
$sql->bindValue(5, $this->date->format('U'), PDO::PARAM_STR);
$sql->bindValue(6, $this->a, PDO::PARAM_INT);
$sql->bindValue(7, $this->m, PDO::PARAM_INT);
$sql->bindValue(8, $this->d, PDO::PARAM_INT);
$sql->bindValue(9, $this->r, PDO::PARAM_INT);
$sql->execute();
}
}
class BSVN_LogEntryPath {
public $id = null;
public $kind;
public $action;
public $path;
public $revision = null;
public function __construct(DOMNode $pathnode=null) {
if ($pathnode !== null) {
$this->kind = $pathnode->getAttribute('kind');
$this->action = strtolower($pathnode->getAttribute('action'));
$this->path = trim($pathnode->nodeValue);
}
}
public function save() {
$dir = '';
$fname = '';
if ($this->revision === null)
throw new Exception('Cannot store unversioned file into database! Set $revision first!');
if ($this->kind != '') {
// The easy way :]
if ($this->kind == 'dir') {
$dir = $this->path;
} else {
$dir = pathinfo($this->path, PATHINFO_DIRNAME);
$fname = pathinfo($this->path, PATHINFO_BASENAME);
}
} else {
// WARNING: Fuzzy autodetection
$dir = pathinfo($this->path, PATHINFO_DIRNAME);
$fname = pathinfo($this->path, PATHINFO_BASENAME);
if (strpos($fname, '.')===false) {
// No dot in filename, maybe this is directory??
$dir .= $fname;
$fname = '';
} else {
// UpperCase extensions are uncommon, try to filter out .Net's directory names
$ext = pathinfo($fname, PATHINFO_EXTENSION);
if ($ext == ucfirst($ext)) {
$dir .= $fname;
$fname = '';
}
}
bsvn_verbose('Fuzzy kind detection: '.$this->path.' : '.($fname==''?'dir':'file'));
}
// update database entry, if it exists, set modCount++
$sql = BSVN_Database::prepare('UPDATE files SET revision=?, modCount=modCount+1 WHERE dir=? AND fname=?');
$sql->bindValue(1, $this->revision, PDO::PARAM_INT);
$sql->bindValue(2, $dir, PDO::PARAM_STR);
$sql->bindValue(3, $fname, PDO::PARAM_STR);
$sql->execute();
if (!$sql->rowCount()) {
$sql = BSVN_Database::prepare('INSERT INTO files (dir, fname, type, revision, modCount, createdRev) VALUES(?,?,?,?,0,?)');
$sql->bindValue(1, $dir, PDO::PARAM_STR);
$sql->bindValue(2, $fname, PDO::PARAM_STR);
$sql->bindValue(3, self::getType($fname), PDO::PARAM_STR);
$sql->bindValue(4, $this->revision, PDO::PARAM_INT);
$sql->bindValue(5, $this->revision, PDO::PARAM_INT);
$sql->execute();
$this->id = BSVN_Database::get()->lastInsertId();
}
if ($this->action == 'd') {
$sql = BSVN_Database::prepare('UPDATE files SET deletedRev=? WHERE dir=? AND fname=?');
$sql->bindValue(1, $this->revision, PDO::PARAM_INT);
$sql->bindValue(2, $dir, PDO::PARAM_STR);
$sql->bindValue(3, $fname, PDO::PARAM_STR);
$sql->execute();
}
}
public static function getType($fname) {
if ($fname == '')
return 'dir';
return pathinfo($fname, PATHINFO_EXTENSION);
}
}
class BSVN_DiffParser {
private $file = '';
public function __construct($file) {
$this->file = $file;
}
/**
* Parses diff file
* @return BSVN_DiffFileStats Associative array of file-stats
*/
public function parse() {
$out = array();
$fh = fopen($this->file, 'rt');
if ($fh === false)
throw new Exception('Cannot open diff for reading.');
$obj = null;
$section = null;
while (!feof($fh)) {
$line = trim(fgets($fh, 4096));
if (empty($line))
continue;
// Find "Index: "
if (strcasecmp(substr($line, 0, 7), 'Index: ') == 0) {
if ($obj != null)
$out[$obj->fpath] = $obj;
$obj = new BSVN_DiffFileStats();
$obj->fpath = substr($line, 8);
continue;
}
if ($obj == null)
continue;
// Skip '='
switch (trim($line[0])) {
case '' :
case '=' : continue;
case '@' :
if ($section !== null)
$obj->sections[] = $section;
$obj->diffsections++;
$section = new BSVN_DiffSection();
$section->id = $line;
// codebase changed
$matches = array();
if (!preg_match('/@@\s*(?<range11>[\-\+0-9]+)(,(?<range12>[\-\+0-9]+))?\s*(?<range21>[\-\+0-9]+)(,(?<range22>[\-\+0-9]+))?\s*@@/', $line, $matches)) {
bsvn_verbose('WARNING: Cannot parse section data: '.$line);
$section = null; //skip section
continue;
}
$r12 = 1;
$r22 = 1;
if (isset($matches['range22']))
$r22 = intval($matches['range22']);
if (isset($matches['range12']))
$r12 = intval($matches['range12']);
$section->codebaseChange = $r22-$r12;
break;
case '\\': continue; // Comment
case '+' : if ($section !==null) $section->linesAdded++; break;
case '-' : if ($section !== null) $section->linesDeleted++; break;
}
}
if ($obj != null && $section !== null)
$obj->sections[] = $section;
if ($obj != null)
$out[$obj->fpath] = $obj;
fclose($fh);
return $out;
}
}
class BSVN_DiffFileStats {
public $fpath = '';
public $revision = 0;
public $author = '';
public $diffsections = 0;
public $sections = array();
public function getCodebaseChange() {
$sum = 0;
foreach ($this->sections as $section) {
$sum += $section->codebaseChange;
}
return $sum;
}
public function getLinesChanged() {
$sum = 0;
foreach($this->sections as $section) {
$sum += $section->getLinesChanged();
}
return $sum;
}
}
class BSVN_DiffSection {
public $id = '';
public $codebaseChange = 0;
public $linesAdded = 0;
public $linesDeleted = 0;
public function getLinesChanged() {
return max($this->linesAdded, $this->linesDeleted);
}
}
?>