Improving performance of fetching git objects by using git cat-file --batch through a pipe. Also improved debug output and added destructor-based auto-timers
--- a/include/AutoLoader.class.php
+++ b/include/AutoLoader.class.php
@@ -84,6 +84,7 @@
} else if (in_array($classname, array(
'Config',
'DebugLog',
+ 'DebugAutoLog',
'Resource',
'Util'
))) {
--- /dev/null
+++ b/include/DebugAutoLog.class.php
@@ -1,1 +1,31 @@
+<?php
+/**
+ * Debug auto logging class (destructor-based)
+ *
+ * @author Yuriy Nasretdinov <nasretdinov@gmail.com>
+ * @copyright Copyright (c) 2013 Christopher Han
+ * @package GitPHP
+ */
+class GitPHP_DebugAutoLog
+{
+ private $name;
+ public function __construct($name = null)
+ {
+ if (is_null($name)) {
+ $trace = debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS);
+ if (!isset($trace[1]['class']) || !isset($trace[1]['function'])) {
+ throw new InvalidArgumentException("You need to specify name when not in method context");
+ }
+ $name = $trace[1]['class'] . '::' . $trace[1]['function'];
+ }
+ $this->name = $name;
+ GitPHP_DebugLog::GetInstance()->TimerStart();
+ }
+
+ public function __destruct()
+ {
+ GitPHP_DebugLog::GetInstance()->TimerStop($this->name);
+ }
+}
+
--- a/include/DebugLog.class.php
+++ b/include/DebugLog.class.php
@@ -44,12 +44,36 @@
protected $entries = array();
/**
+ * Stores the timers
+ *
+ * @var float[]
+ */
+ protected $timers = array();
+
+ /**
+ * @return GitPHP_DebugLog
+ */
+ public static function GetInstance()
+ {
+ static $instance;
+ if (!$instance) $instance = new self();
+ return $instance;
+ }
+
+ /**
+ * You must use GetInstance()
+ */
+ private function __construct()
+ {
+ }
+
+ /**
* Constructor
*
* @param boolean $enabled whether log should be enabled
* @param boolean $benchmark whether benchmarking should be enabled
*/
- public function __construct($enabled = false, $benchmark = false)
+ public function init($enabled = false, $benchmark = false)
{
$this->startTime = microtime(true);
$this->startMem = memory_get_usage();
@@ -79,23 +103,59 @@
}
/**
+ * Shortcut to start timer
+ */
+ public function TimerStart()
+ {
+ if (!$this->benchmark) return;
+ $this->Log('', '', 'start');
+ }
+
+ /**
+ * Shortcut to stop timer
+ *
+ * @param $msg
+ * @param $msg_data
+ */
+ public function TimerStop($msg, $msg_data = '')
+ {
+ if (!$this->benchmark) return;
+ $this->Log($msg, $msg_data, 'stop');
+ }
+
+ /**
* Log an entry
*
- * @param string $message message to log
- */
- public function Log($message)
+ * @param string $msg message to log
+ */
+ public function Log($msg, $msg_data = '', $type = 'ts')
{
if (!$this->enabled)
return;
$entry = array();
-
- if ($this->benchmark) {
- $entry['time'] = microtime(true);
- $entry['mem'] = memory_get_usage();
+
+ if ($type == 'start') {
+ array_push($this->timers, microtime(true));
+ return;
+ } else if ($type == 'stop') {
+ $timer = array_pop($this->timers);
+ $entry['time'] = $duration = microtime(true) - $timer;
+ foreach ($this->timers as &$item) $item += $duration;
+ } else {
+ if ($this->benchmark) {
+ $entry['time'] = (microtime(true) - $this->startTime);
+ $entry['reltime'] = true;
+ $entry['mem'] = memory_get_usage();
+ }
}
- $entry['msg'] = $message;
+ $entry['name'] = $msg;
+ $entry['value'] = $msg_data;
+ $bt = explode("\n", new Exception());
+ array_shift($bt);
+ array_shift($bt);
+ $entry['bt'] = implode("\n", $bt);
$this->entries[] = $entry;
}
@@ -137,46 +197,6 @@
public function SetBenchmark($bench)
{
$this->benchmark = $bench;
- }
-
- /**
- * Gets log entries
- *
- * @return string[] log entries
- */
- public function GetEntries()
- {
- $data = array();
-
- if ($this->enabled) {
-
- if ($this->benchmark) {
- $endTime = microtime(true);
- $endMem = memory_get_usage();
-
- $lastTime = $this->startTime;
- $lastMem = $this->startMem;
-
- $data[] = 'DEBUG: [' . $this->startTime . '] [' . $this->startMem . ' bytes] Start';
-
- }
-
- foreach ($this->entries as $entry) {
- if ($this->benchmark) {
- $data[] = 'DEBUG: [' . $entry['time'] . '] [' . ($entry['time'] - $this->startTime) . ' sec since start] [' . ($entry['time'] - $lastTime) . ' sec since last] [' . $entry['mem'] . ' bytes] [' . ($entry['mem'] - $this->startMem) . ' bytes since start] [' . ($entry['mem'] - $lastMem) . ' bytes since last] ' . $entry['msg'];
- $lastTime = $entry['time'];
- $lastMem = $entry['mem'];
- } else {
- $data[] = 'DEBUG: ' . $entry['msg'];
- }
- }
-
- if ($this->benchmark) {
- $data[] = 'DEBUG: [' . $endTime . '] [' . ($endTime - $this->startTime) . ' sec since start] [' . ($endTime - $lastTime) . ' sec since last] [' . $endMem . ' bytes] [' . ($endMem - $this->startMem) . ' bytes since start] [' . ($endMem - $lastMem) . ' bytes since last] End';
- }
- }
-
- return $data;
}
/**
@@ -206,9 +226,88 @@
return;
$msg = $args[0];
-
- $this->Log($msg);
- }
-
+ $msg_data = isset($args[1]) ? $args[1] : '';
+ $type = isset($args[2]) ? $args[2] : 'ts';
+
+ $this->Log($msg, $msg_data, $type);
+ }
+
+ public function PrintHtml()
+ {
+ if (!$this->enabled) return;
+
+ foreach ($this->entries as $i => $e) {
+ $bt_id = 'bt_' . $i;
+ if (strlen($e['value']) > 512) {
+ $contents = htmlspecialchars(substr($e['value'], 0, 512) . "...");
+ $contents .= "\n\n<i>" . (strlen($e['value']) - 512) . " bytes more in output</i>";
+ } else {
+ $contents = htmlspecialchars($e['value']);
+ }
+ echo "<tr>
+ <td class='debug_key'>$e[name]</td>
+ <td class='debug_value'>
+ " . nl2br($contents) . ($contents != "" ? "<br§ />" : "") . "
+ <span class='debug_toggle' onclick='bt_toggle(\"$bt_id\");'>trace</span>
+ <div style='display: none;' class='debug_bt' id='$bt_id'>$e[bt]</div>
+ </td>
+ <td class='debug_time'>
+ " . ($e['time'] ? sprintf("%.1f", $e['time'] * 1000) : '') . "
+ " . ($e['time'] ? (!empty($e['reltime']) ? " ms from start" : " ms") : '') . "
+ </td>
+ </tr>";
+ }
+ }
+
+ public function PrintHtmlHeader()
+ {
+ if (!$this->enabled) return;
+
+ echo
+<<<HEREDOC
+ <script type="text/javascript">
+ function bt_toggle(id) {
+ var el = document.getElementById(id);
+ el.style.display = ((el.style.display == 'none') ? 'block' : 'none');
+ }
+ </script>
+ <style type="text/css">
+ .debug {
+ border: 0;
+ border-spacing: 0;
+ width: 100%;
+ }
+ .debug_toggle {
+ color: #88a; border-bottom: 1px dashed blue;
+ display: inline-block;
+ margin: 3px;
+ cursor: pointer;
+ }
+ .debug_key {
+ background: #ccf; border-bottom: 1px solid #888;
+ max-width: 100px;
+ word-wrap: break-word;
+ }
+ .debug_value {
+ background: #ccc; border-bottom: 1px solid #888;
+ max-width: 900px;
+ word-wrap: break-word;
+ }
+ .debug_bt {
+ white-space: pre;
+ }
+ .debug_time {
+ background: #cff; border-bottom: 1px solid #888;
+ }
+ </style>
+ <table class="debug"><tbody>
+HEREDOC;
+ }
+
+ public function PrintHtmlFooter()
+ {
+ if (!$this->enabled) return;
+ echo '</tbody></table>';
+ }
}
--- a/include/Util.class.php
+++ b/include/Util.class.php
@@ -42,6 +42,11 @@
public static function IsWindows()
{
return (strtoupper(substr(PHP_OS, 0, 3)) === 'WIN');
+ }
+
+ public static function NullFile()
+ {
+ return self::IsWindows() ? 'NUL' : '/dev/null';
}
/**
--- a/include/controller/ControllerBase.class.php
+++ b/include/controller/ControllerBase.class.php
@@ -348,7 +348,8 @@
$debug = $this->config->GetValue('debug');
if ($debug) {
- $this->log = new GitPHP_DebugLog($debug, $this->config->GetValue('benchmark'));
+ $this->log = GitPHP_DebugLog::GetInstance();
+ $this->log->init($debug, $this->config->GetValue('benchmark'));
$this->log->SetStartTime(GITPHP_START_TIME);
$this->log->SetStartMemory(GITPHP_START_MEM);
if ($this->exe)
@@ -421,11 +422,11 @@
if ($this->project && $projectKeys) {
$cacheKeyPrefix .= '|' . sha1($this->project);
}
-
+
return $cacheKeyPrefix;
}
- /**
+ /**
* Get the full cache key
*
* @return string full cache key
@@ -549,7 +550,7 @@
if ($querypos !== false)
$requesturl = substr($requesturl, 0, $querypos);
$this->tpl->assign('requesturl', $requesturl);
-
+
if ($this->router) {
$this->router->SetCleanUrl($this->config->GetValue('cleanurl') ? true : false);
$this->router->SetAbbreviate($this->config->GetValue('abbreviateurl') ? true : false);
@@ -621,30 +622,32 @@
if (($this->config->GetValue('cache') == true) && ($this->config->GetValue('cacheexpire') === true))
$this->CacheExpire();
+ $log = GitPHP_DebugLog::GetInstance();
+
if (!$this->tpl->isCached($this->GetTemplate(), $this->GetFullCacheKey())) {
$this->tpl->clearAllAssign();
- if ($this->log && $this->log->GetBenchmark())
- $this->log->Log("Data load begin");
+
+ $log->TimerStart();
$this->LoadCommonData();
+ $log->TimerStop('Common data');
+
+ $log->TimerStart();
$this->LoadData();
- if ($this->log && $this->log->GetBenchmark())
- $this->log->Log("Data load end");
+ $log->TimerStop('Data');
}
if (!$this->preserveWhitespace) {
//$this->tpl->loadFilter('output', 'trimwhitespace');
}
- if ($this->log && $this->log->GetBenchmark())
- $this->log->Log("Smarty render begin");
+ $log->TimerStart();
$this->tpl->display($this->GetTemplate(), $this->GetFullCacheKey());
- if ($this->log && $this->log->GetBenchmark())
- $this->log->Log("Smarty render end");
+ $log->TimerStop('Render');
$this->tpl->clearAllAssign();
- if ($this->log && $this->projectList)
- $this->log->Log('MemoryCache count: ' . $this->projectList->GetMemoryCache()->GetCount());
+ if ($this->projectList)
+ $log->Log('MemoryCache count: ' . $this->projectList->GetMemoryCache()->GetCount());
}
/**
--- a/include/controller/Controller_Blob.class.php
+++ b/include/controller/Controller_Blob.class.php
@@ -90,7 +90,7 @@
$blob->SetPath($this->params['file']);
$mimeReader = new GitPHP_FileMimeTypeReader($blob, $this->GetMimeStrategy());
- $mime = $mimeReader->GetMimeType();
+ $mime = trim($mimeReader->GetMimeType());
}
if ($mime)
--- a/include/controller/Controller_Project.class.php
+++ b/include/controller/Controller_Project.class.php
@@ -49,10 +49,14 @@
*/
protected function LoadData()
{
+ $log = GitPHP_DebugLog::GetInstance();
+
+ $log->TimerStart();
$head = $this->GetProject()->GetHeadCommit();
$this->tpl->assign('head', $head);
if (!$head)
$this->tpl->assign('enablesearch', false);
+ $log->TimerStop('GetHeadCommit');
//$compat = $this->GetProject()->GetCompat();
$strategy = null;
@@ -69,7 +73,9 @@
}
$this->tpl->assign('revlist', $revlist);
+ $log->TimerStart();
$taglist = $this->GetProject()->GetTagList()->GetOrderedTags('-creatordate', 17);
+ $log->TimerStop('GetTagList');
if ($taglist) {
if (count($taglist) > 16) {
$this->tpl->assign('hasmoretags', true);
@@ -78,7 +84,9 @@
$this->tpl->assign('taglist', $taglist);
}
+ $log->TimerStart();
$headlist = $this->GetProject()->GetHeadList()->GetOrderedHeads('-committerdate', 17);
+ $log->TimerStop('GetHeadList');
if ($headlist) {
if (count($headlist) > 17) {
$this->tpl->assign('hasmoreheads', true);
--- a/include/git/GitExe.class.php
+++ b/include/git/GitExe.class.php
@@ -72,7 +72,7 @@
* @var string
*/
protected $binary;
-
+
/**
* The binary version
*
@@ -114,6 +114,27 @@
* @var null|boolean
*/
protected $popenAllowed = null;
+
+ /**
+ * Whether the proc_open function is allowed by the install
+ *
+ * @var null|boolean
+ */
+ protected $procOpenAllowed = null;
+
+ /**
+ * Whether or not caching function GetProcess is initialized
+ *
+ * @var null|boolean
+ */
+ protected $getProcessInitialized = false;
+
+ /**
+ * Processes spawned for batch object fetching
+ *
+ * @var array
+ */
+ protected static $processes = array();
/**
* Constructor
@@ -147,14 +168,114 @@
$fullCommand = $this->CreateCommand($projectPath, $command, $args);
- $this->Log('Begin executing "' . $fullCommand . '"');
+ $this->Log('Execute', '', 'start');
$ret = shell_exec($fullCommand);
- $this->Log('Finish executing "' . $fullCommand . '"' .
- "\nwith result: " . $ret);
+ $this->Log('Execute', $fullCommand . "\n\n" . $ret, 'stop');
return $ret;
+ }
+
+ protected function GetProcess($projectPath)
+ {
+ if (!$this->getProcessInitialized) {
+ register_shutdown_function(array($this, 'DestroyAllProcesses'));
+ $this->getProcessInitialized = true;
+ }
+
+ if (!isset(self::$processes[$projectPath])) {
+ GitPHP_DebugLog::GetInstance()->TimerStart();
+
+ $process = proc_open(
+ $cmd = $this->CreateCommand($projectPath, GIT_CAT_FILE, array('--batch')),
+ array(
+ 0 => array('pipe', 'r'),
+ 1 => array('pipe', 'w'),
+ 2 => array('file', GitPHP_Util::NullFile(), 'w'),
+ ),
+ $pipes
+ );
+
+ self::$processes[$projectPath] = array(
+ 'process' => $process,
+ 'pipes' => $pipes,
+ );
+
+ GitPHP_DebugLog::GetInstance()->TimerStop('proc_open', $cmd);
+ }
+
+ return self::$processes[$projectPath];
+ }
+
+ public function DestroyAllProcesses()
+ {
+ foreach (self::$processes as $projectPath => $process) {
+ $this->DestroyProcess($projectPath);
+ }
+ }
+
+ protected function DestroyProcess($projectPath)
+ {
+ proc_terminate(self::$processes[$projectPath]);
+ proc_close(self::$processes[$projectPath]);
+ unset(self::$processes[$projectPath]);
+ }
+
+ public function GetObjectData($projectPath, $hash)
+ {
+ if ($this->procOpenAllowed === null) {
+ $this->procOpenAllowed = GitPHP_Util::FunctionAllowed('proc_open');
+ if (!$this->procOpenAllowed) {
+ throw new GitPHP_DisabledFunctionException('proc_open');
+ }
+ }
+
+ $process = $this->GetProcess($projectPath);
+ $pipes = $process['pipes'];
+
+ $data = $hash . "\n";
+ if (fwrite($pipes[0], $data) !== mb_orig_strlen($data)) {
+ $this->DestroyProcess($projectPath);
+ return false;
+ }
+ fflush($pipes[0]);
+
+ $ln = rtrim(fgets($pipes[1]));
+ if (!$ln) {
+ $this->DestroyProcess($projectPath);
+ return false;
+ }
+
+ $parts = explode(" ", rtrim($ln));
+ if (count($parts) == 2 && $parts[1] == 'missing') {
+ return false;
+ } else if (count($parts) != 3) {
+ $this->DestroyProcess($projectPath);
+ return false;
+ }
+
+ list($hash, $type, $n) = $parts;
+
+ $contents = '';
+ while (mb_orig_strlen($contents) < $n) {
+ $buf = fread($pipes[1], min(4096, $n - mb_orig_strlen($contents)));
+ if ($buf === false) {
+ $this->DestroyProcess($projectPath);
+ return false;
+ }
+ $contents .= $buf;
+ }
+
+ if (fgetc($pipes[1]) != "\n") {
+ $this->DestroyProcess($projectPath);
+ return false;
+ }
+
+ return array(
+ 'contents' => $contents,
+ 'type' => $type,
+ );
}
/**
@@ -194,7 +315,7 @@
if (!empty($projectPath)) {
$gitDir = '--git-dir=' . escapeshellarg($projectPath);
}
-
+
return $this->binary . ' ' . $gitDir . ' ' . $command . ' ' . implode(' ', $args);
}
@@ -371,14 +492,16 @@
* Log an execution
*
* @param string $message message
- */
- private function Log($message)
+ * @param string $msg_data message
+ * @param string $type message
+ */
+ private function Log($message, $msg_data, $type)
{
if (empty($message))
return;
foreach ($this->observers as $observer) {
- $observer->ObjectChanged($this, GitPHP_Observer_Interface::LoggableChange, array($message));
+ $observer->ObjectChanged($this, GitPHP_Observer_Interface::LoggableChange, array($message, $msg_data, $type));
}
}
--- a/include/git/GitObjectLoader.class.php
+++ b/include/git/GitObjectLoader.class.php
@@ -66,6 +66,8 @@
return false;
}
+ $autolog = new GitPHP_DebugAutoLog();
+
// first check if it's unpacked
$path = $this->project->GetPath() . '/objects/' . substr($hash, 0, 2) . '/' . substr($hash, 2);
if (file_exists($path)) {
--- a/include/git/blob/BlobLoad_Git.class.php
+++ b/include/git/blob/BlobLoad_Git.class.php
@@ -20,11 +20,8 @@
if (!$blob)
return;
- $args = array();
- $args[] = 'blob';
- $args[] = $blob->GetHash();
-
- return $this->exe->Execute($blob->GetProject()->GetPath(), GIT_CAT_FILE, $args);
+ $result = $this->exe->GetObjectData($blob->GetProject()->GetPath(), $blob->GetHash());
+ return $result['contents'];
}
/**
--- a/include/git/commit/CommitLoad_Git.class.php
+++ b/include/git/commit/CommitLoad_Git.class.php
@@ -32,31 +32,15 @@
$title = null;
$comment = array();
+ $ret = $this->exe->GetObjectData($commit->GetProject()->GetPath(), $commit->GetHash());
- /* get data from git_rev_list */
- $args = array();
- $args[] = '--header';
- $args[] = '--parents';
- $args[] = '--max-count=1';
- $args[] = '--abbrev-commit';
- $args[] = $commit->GetHash();
- $ret = $this->exe->Execute($commit->GetProject()->GetPath(), GIT_REV_LIST, $args);
-
- $lines = explode("\n", $ret);
+ $lines = explode("\n", $ret['contents']);
if (!isset($lines[0]))
return;
- /* In case we returned something unexpected */
- $tok = strtok($lines[0], ' ');
- if ((strlen($tok) == 0) || (substr_compare($commit->GetHash(), $tok, 0, strlen($tok)) !== 0)) {
- return;
- }
- $abbreviatedHash = $tok;
-
- array_shift($lines);
-
-
+ /* Sadly, git cat-file does not support abbreviation of hashes in batch mode so we have to substr :( */
+ $abbreviatedHash = substr($commit->GetHash(), 0, 7);
$linecount = count($lines);
$i = 0;
$encoding = null;
--- a/include/git/headlist/HeadList.class.php
+++ b/include/git/headlist/HeadList.class.php
@@ -80,12 +80,12 @@
if (!$this->dataLoaded)
$this->LoadData();
+ if (!isset($this->invertedRefs[$commitHash])) return array();
+ $headNames = $this->invertedRefs[$commitHash];
$heads = array();
- foreach ($this->refs as $head => $hash) {
- if ($commitHash == $hash) {
- $heads[] = $this->project->GetObjectManager()->GetHead($head, $hash);
- }
+ foreach ($headNames as $head) {
+ $heads[] = $this->project->GetObjectManager()->GetHead($head, $commitHash);
}
return $heads;
@@ -99,6 +99,7 @@
$this->dataLoaded = true;
$this->refs = $this->strategy->Load($this);
+ foreach ($this->refs as $ref => $hash) $this->invertedRefs[$hash][] = $ref;
}
/**
--- a/include/git/headlist/HeadListLoad_Raw.class.php
+++ b/include/git/headlist/HeadListLoad_Raw.class.php
@@ -36,6 +36,8 @@
if (empty($order))
return;
+ $autotimer = new GitPHP_DebugAutoLog();
+
$heads = $headList->GetHeads();
/* TODO add different orders */
--- a/include/git/pack/PackData.class.php
+++ b/include/git/pack/PackData.class.php
@@ -162,7 +162,7 @@
/*
* next read the compressed delta data
*/
- $delta = gzuncompress(substr($buf, $pos), $size);
+ $delta = gzuncompress(mb_orig_substr($buf, $pos), $size);
$baseOffset = $offset - $off;
if ($baseOffset > 0) {
@@ -230,9 +230,9 @@
if ($opcode & 0x20) $len |= ord($delta{$pos++}) << 8;
if ($opcode & 0x40) $len |= ord($delta{$pos++}) << 16;
if ($len == 0) $len = 0x10000;
- $data .= substr($base, $off, $len);
+ $data .= mb_orig_substr($base, $off, $len);
} else if ($opcode > 0) {
- $data .= substr($delta, $pos, $opcode);
+ $data .= mb_orig_substr($delta, $pos, $opcode);
$pos += $opcode;
}
}
--- a/include/git/reflist/RefList.class.php
+++ b/include/git/reflist/RefList.class.php
@@ -22,6 +22,14 @@
* @var array
*/
protected $refs = array();
+
+ /**
+ * The inverted refs
+ *
+ * @var array
+ */
+ protected $invertedRefs = array();
+
/**
* Whether data has been loaded
--- a/include/git/reflist/RefListLoad_Raw.class.php
+++ b/include/git/reflist/RefListLoad_Raw.class.php
@@ -24,6 +24,8 @@
if (empty($type))
return;
+ $autotimer = new GitPHP_DebugAutoLog();
+
$refs = array();
$prefix = 'refs/' . $type;
@@ -34,7 +36,7 @@
$refFiles = GitPHP_Util::ListDir($fullPath);
for ($i = 0; $i < count($refFiles); ++$i) {
$ref = substr($refFiles[$i], $fullPathLen);
-
+
if (empty($ref) || isset($refs[$ref]))
continue;
--- a/include/git/tag/TagLoad_Git.class.php
+++ b/include/git/tag/TagLoad_Git.class.php
@@ -48,13 +48,9 @@
$taggerTimezone = null;
$comment = array();
+ $result = $this->exe->GetObjectData($tag->GetProject()->GetPath(), $tag->GetHash());
- $args = array();
- $args[] = '-t';
- $args[] = $tag->GetHash();
- $ret = trim($this->exe->Execute($tag->GetProject()->GetPath(), GIT_CAT_FILE, $args));
-
- if ($ret === 'commit') {
+ if ($result['type'] === 'commit') {
/* light tag */
$object = $tag->GetHash();
$commitHash = $tag->GetHash();
@@ -71,12 +67,9 @@
}
/* get data from tag object */
- $args = array();
- $args[] = 'tag';
- $args[] = $tag->GetName();
- $ret = $this->exe->Execute($tag->GetProject()->GetPath(), GIT_CAT_FILE, $args);
+ $result = $this->exe->GetObjectData($tag->GetProject()->GetPath(), $tag->GetName());
- $lines = explode("\n", $ret);
+ $lines = explode("\n", $result['contents']);
if (!isset($lines[0]))
return;
--- a/include/git/taglist/TagList.class.php
+++ b/include/git/taglist/TagList.class.php
@@ -90,16 +90,18 @@
if (!$this->dataLoaded)
$this->LoadData();
+ if (!isset($this->invertedRefs[$commitHash])) return array();
+ $tagNames = $this->invertedRefs[$commitHash];
$tags = array();
- foreach ($this->refs as $tag => $hash) {
+ foreach ($tagNames as $tag) {
if (isset($this->commits[$tag])) {
if ($this->commits[$tag] == $commitHash) {
- $tagObj = $this->project->GetObjectManager()->GetTag($tag, $hash);
+ $tagObj = $this->project->GetObjectManager()->GetTag($tag, $commitHash);
$tagObj->SetCommitHash($this->commits[$tag]);
$tags[] = $tagObj;
}
} else {
- $tagObj = $this->project->GetObjectManager()->GetTag($tag, $hash);
+ $tagObj = $this->project->GetObjectManager()->GetTag($tag, $commitHash);
$tagCommitHash = $tagObj->GetCommitHash();
if (!empty($tagCommitHash)) {
$this->commits[$tag] = $tagCommitHash;
@@ -120,6 +122,7 @@
$this->dataLoaded = true;
list($this->refs, $this->commits) = $this->strategy->Load($this);
+ foreach ($this->refs as $ref => $hash) $this->invertedRefs[$hash][] = $ref;
}
/**
--- a/include/router/Router.class.php
+++ b/include/router/Router.class.php
@@ -352,7 +352,7 @@
/**
* Gets a controller for an action
*
- * @return mixed controller object
+ * @return GitPHP_ControllerBase
*/
public function GetController()
{
@@ -483,7 +483,7 @@
$controller->SetParam('opml', true);
break;
-
+
case 'login':
$controller = new GitPHP_Controller_Login();
if (!empty($_POST['username']))
--- a/index.php
+++ b/index.php
@@ -39,6 +39,21 @@
mb_internal_encoding("UTF-8");
}
date_default_timezone_set('UTC');
+
+/* strlen() can be overloaded in mbstring extension, so always using mb_orig_strlen for binary data */
+if (!function_exists('mb_orig_strlen')) {
+ function mb_orig_strlen($str)
+ {
+ return strlen($str);
+ }
+}
+
+if (!function_exists('mb_orig_substr')) {
+ function mb_orig_substr($str, $offset, $len = null)
+ {
+ return isset($len) ? substr($str, $offset, $len) : substr($str, $offset);
+ }
+}
/**
* Version header
@@ -88,12 +103,9 @@
if (isset($controller)) {
$log = $controller->GetLog();
if ($log && $log->GetEnabled()) {
- $entries = $log->GetEntries();
- foreach ($entries as $logline) {
- echo "<br />\n" . htmlspecialchars($logline, ENT_QUOTES, 'UTF-8', true);
- }
- unset($logline);
- unset($entries);
+ $log->PrintHtmlHeader();
+ $log->PrintHtml();
+ $log->PrintHtmlFooter();
}
unset($controller);
}