array(), 'nodes' => array()); // ################################################################### /** * Constructor: initialies the registry * * @param object Controller */ function cacheV(&$controller) { $this->controller =& $controller; $this->set_hash(); } // ################################################################### /** * Sets the hash so we know what table we're dealing with * * @access public */ function set_hash() { $this->hash = md5($this->controller->repospath); $this->controller->registry->debug("hash: $this->hash"); } // ################################################################### /** * Returns a node string that has the beginning and ending slashes * removed to allow it to match to the _nodes cacheV table * * @access public * * @param string Original string * * @return string Matchable string */ function fetch_node_string($node) { trigger_error('You shouldn\'t be calling fetch_node_string. It\'s evil.', E_USER_WARNING); return preg_replace('#(^/|/$)#', '', $node); } // ################################################################### /** * Returns a specific log entry * * @access public * * @param integer Revision number * * @return array Complete revision/commit entry */ function fetch_revision($revision) { $revision = $this->controller->registry->clean($revision, TYPE_UINT); if (!isset($this->memcache['revs']["$revision"])) { $this->memcache['revs']["$revision"] = $this->controller->registry->db->query_first("SELECT * FROM c{$this->hash}_revs " . ($revision == 0 ? " ORDER BY revision DESC LIMIT 1" : "WHERE revision = $revision")); $this->memcache['revs']["$revision"]['files'] = unserialize($this->memcache['revs']["$revision"]['files']); } return $this->memcache['revs']["$revision"]; } // ################################################################### /** * Returns the revision entry before the specified one * * @access public * * @param integer Revision number * * @return array Complete revision/commit entry */ function fetch_prev_revision($revision) { $data = $this->fetch_node(); $data = $data['history']; if (sizeof($data) < 1) { return $this->fetch_revision(0); } $list = array_keys($data); $key = array_search($revision, $list); if ($revision == 'HEAD') { $key = 0; } $key++; // go to the next earliest revision if (!isset($list["$key"])) { return -1; } return $this->fetch_revision($list["$key"]); } // ################################################################### /** * Returns the latest revision that the file is at * * @access public * * @return integer HEAD revision */ function fetch_head_revision() { $data = $this->fetch_node(); $data = $data['history']; if (is_array($data)) { return max(array_keys($data)); } else { return 0; } } // ################################################################### /** * Returns the revision entry that it's in context with the node. For * instance, if the version 50 was passed and this node only has 48 as * it's max, 48 will be returned. * * @access public * * @param integer Target revision * * @return integer Contextual revision */ function fetch_revision_context($target) { $data = $this->fetch_node(); $data = $data['history']; if ($target == 'HEAD') { $target = $this->fetch_head_revision(); } if (isset($data["$target"])) { return $this->fetch_revision($target); } if (is_array($data)) { $keys = array_keys($data); } else { return 0; } $prev = 0; foreach ($keys AS $id => $revnum) { if ($target > $revnum) { $prev = $revnum; if ($prev > $rev) { $rev = $prev; } } else if ($target < $revnum) { $rev = $prev; } else { $rev = $keys[0]; } } return $this->fetch_revision($rev); } // ################################################################### /** * Fetches the latest revision for a given path * * @access public * * @return integer Latest revision; FALSE if none (not in HEAD) */ function fetch_node() { $node = $this->controller->path; if (!isset($this->memcache['nodes']["$node"])) { $result = $this->controller->registry->db->query_first("SELECT * FROM c{$this->hash}_nodes WHERE name = '" . $this->controller->registry->escape($node) . "'"); if ($result == false) { return false; } $this->memcache['nodes']["$node"] = $result; $this->memcache['nodes']["$node"]['history'] = unserialize($this->memcache['nodes']["$node"]['history']); $this->memcache['nodes']["$node"]['properties'] = unserialize($this->memcache['nodes']["$node"]['properties']); } return $this->memcache['nodes']["$node"]; } // ################################################################### /** * Checks to see if a given node is a directory. Returns TRUE if so. * * @access public * * @return bool TRUE if directory, FALSE if not */ function isdir() { $node = $this->fetch_node(); if ($node['node'] == 'dir') { return true; } return false; } // ################################################################### /** * Checks to see if it's necessary to rebuild the cacheV table for the * current repository. This is done by making sure $count > 0. If not, * then rebuild() is run. This also checks against the cacheV table * to make sure that it's up-to-date against the root repository. * * @access public * * @return bool Whether or not any part of the cache was (re-)built */ function exec_build() { $result = $this->controller->registry->db->query_first("SELECT MAX(revision) AS max FROM c{$this->hash}_revs"); $this->count = $result['max']; // time to go from the start if ($this->count == 0) { $this->build(null); } else { // send an Xquery to SVN to see if we need to update $query = $this->controller->library->svn('info --xml ' . $this->controller->repospath); $query = implode("\n", $query); $tree = $this->controller->registry->xml->parse($query); if ($tree['info']['entry']['revision'] != $this->count) { $this->build($this->count); return true; } } return false; } // ################################################################### /** * Builds the cacheV table. This can be used to build only part of the * cache or the entire thing, if the revision is set to NULL. * * @access public * * @param integer Lower (current) revision * @param bool Use separate and individual queries for inserting? */ function build($revision, $seps = false) { $start = microtime(); // get _revs $output = $this->controller->library->svn('log --xml -v ' . ($revision !== null ? '-r' . $revision . ':HEAD ' : '') . $this->controller->repospath); $output = implode("\n", $output); $tree = $this->controller->registry->xml->parse($output); // construct _revs inserts and the list of add revisions foreach ($tree['log']['logentry'] AS $log) { XML::unify_node($log['paths']['path']); $inserts['revs'][] = "($log[revision], '{$log['author']['value']}', '{$log['date']['value']}', '" . $this->controller->registry->escape($log['msg']['value']) . "', '" . $this->controller->registry->escape(serialize($log['paths']['path'])) . "')"; foreach ($log['paths']['path'] AS $path) { if (trim($path['action']) == 'A') { $path['value'] = preg_replace('#^/#', '', $path['value']); $addlist["$path[value]"] = $log['revision']; } $filelist[] = $this->controller->repospath . $path['value'] . '@' . $log['revision']; } } $newfilelist = array(); foreach ($filelist AS $item) { } // get _nodes $output = $this->controller->library->svn('info --xml ' . implode(' ', $filelist)); $output = implode("\n", $output); $infolist = $this->controller->registry->xml->parse($output); // other part of _nodes: properties $output = $this->controller->library->svn('proplist -v ' . implode(' ', $filelist)); $init = false; foreach ($output AS $line) { if (preg_match('#^Properties on \'(.*?)\':$#', $line, $bits)) { if ($init == true) { $proplist["$index"]["$curprop"] = trim($proplist["$index"]["$curprop"]); } $init = true; $index = str_replace($this->controller->repospath, '', $bits[1]); $index = ($index == '' ? '/' : $index); $capture = false; } else { if (preg_match('#^\s+(.*)\s:\s(.*)#', $line, $matches)) { $curprop = $matches[1]; $proplist["$index"]["$curprop"] = $matches[2] . "\n"; $capture = true; } else if ($capture == true) { $proplist["$index"]["$curprop"] .= $line . "\n"; } } } XML::unify_node($infolist['info']['entry']); $nodeindex = 0; $nodesat = array(); // construct list of HEAD nodes for _nodes foreach ($infolist['info']['entry'] AS $node) { $history = $this->controller->library->svn('log --xml ' . $node['url']['value']); $history = implode("\n", $history); $history = $this->controller->registry->xml->parse($history); $loglist = array(); $latestrev = -1; foreach ($history['log']['logentry'] AS $log) { // WHY THE HELL DOES THIS GET HIT ON REBUILDS? if (!is_array($log)) { /*print_r($node); var_dump($log);*/ continue; } $loglist["$log[revision]"] = array( 'revision' => $log['revision'], 'author' => $log['author']['value'], // why does PHP5 hate this? 'date' => $log['date']['value'], 'message' => $log['msg']['value'] ); } $path = str_replace($this->controller->repospath, '', $node['url']['value']); $path = ($path == '' ? '/' : $path); $nodesat["$path"][ $node['commit']['revision'] ] = $nodeindex; $max = max(array_keys($nodesat["$path"])); if ($max < $node['commit']['revision']) { continue; } else if ($max >= $node['commit']['revision']) { foreach ($nodesat["$path"] AS $rev => $ind) { if ($rev != $node['commit']['revision']) { unset($inserts['nodes'][ $nodesat["$path"]["$rev"] ]); } } } $inserts['nodes']["$nodeindex"] = "('$path', '" . $node['kind'] . "', " . $node['commit']['revision'] . ", '" . $this->controller->registry->escape(serialize($loglist)) . "', '" . $this->controller->registry->escape(serialize($proplist["$path"])) . "')"; $nodeindex++; } if ($seps == false) { // insert _revs var_dump(" REPLACE INTO c{$this->hash}_revs (revision, author, dateline, message, files) VALUES " . implode(",\n", $inserts['revs']) ); // insert _nodes var_dump(" REPLACE INTO c{$this->hash}_nodes (name, node, revision, history, properties) VALUES " . implode(",\n", $inserts['nodes']) ); } else { // _revs foreach ($inserts['revs'] AS $insert) { $this->controller->registry->db->query(" REPLACE INTO c{$this->hash}_revs (revision, author, dateline, message, files) VALUES $insert" ); } // _nodes foreach ($inserts['nodes'] AS $insert) { $this->controller->registry->db->query(" REPLACE INTO c{$this->hash}_nodes (name, node, revision, history, properties) VALUES $insert" ); } } $this->controller->registry->debug("TIME TO (RE)BUILD: " . $this->controller->registry->funct->fetch_microtime_diff($start)); } } /*=====================================================================*\ || ################################################################### || # $HeadURL$ || # $Id$ || ################################################################### \*=====================================================================*/ ?>