project); $scm = IDF_Scm::get($request->project); $scmConf = $request->conf->getVal('scm', 'git'); $params = array( 'page_title' => $title, 'title' => $title, 'scm' => $scmConf, ); return Pluf_Shortcuts_RenderToResponse('idf/source/'.$scmConf.'/help.html', $params, $request); } /** * Is displayed in case an invalid revision is requested */ public $invalidRevision_precond = array('IDF_Precondition::accessSource'); public function invalidRevision($request, $match) { $title = sprintf(__('%s Invalid Revision'), (string) $request->project); $scm = IDF_Scm::get($request->project); $branches = $scm->getBranches(); $commit = $match[2]; $params = array( 'page_title' => $title, 'title' => $title, 'commit' => $commit, 'branches' => $branches, ); $scmConf = $request->conf->getVal('scm', 'git'); return Pluf_Shortcuts_RenderToResponse('idf/source/'.$scmConf.'/invalid_revision.html', $params, $request); } /** * Is displayed in case a revision identifier cannot be uniquely resolved * to one single revision */ public $disambiguateRevision_precond = array('IDF_Precondition::accessSource', 'IDF_Views_Source_Precondition::scmAvailable'); public function disambiguateRevision($request, $match) { $title = sprintf(__('%s Ambiguous Revision'), (string) $request->project); $commit = $match[2]; $redirect = $match[3]; $scm = IDF_Scm::get($request->project); $revisions = $scm->disambiguateRevision($commit); $params = array( 'page_title' => $title, 'title' => $title, 'commit' => $commit, 'revisions' => $revisions, 'redirect' => $redirect, ); return Pluf_Shortcuts_RenderToResponse('idf/source/disambiguate_revision.html', $params, $request); } public $changeLog_precond = array('IDF_Precondition::accessSource', 'IDF_Views_Source_Precondition::scmAvailable', 'IDF_Views_Source_Precondition::revisionValid'); public function changeLog($request, $match) { $scm = IDF_Scm::get($request->project); $branches = $scm->getBranches(); $commit = $match[2]; $title = sprintf(__('%1$s %2$s Change Log'), (string) $request->project, $this->getScmType($request)); $changes = $scm->getChangeLog($commit, 25); $rchanges = array(); // Sync with the database foreach ($changes as $change) { $rchanges[] = IDF_Commit::getOrAdd($change, $request->project); } $rchanges = new Pluf_Template_ContextVars($rchanges); $scmConf = $request->conf->getVal('scm', 'git'); $in_branches = $scm->inBranches($commit, ''); $tags = $scm->getTags(); $in_tags = $scm->inTags($commit, ''); return Pluf_Shortcuts_RenderToResponse('idf/source/'.$scmConf.'/changelog.html', array( 'page_title' => $title, 'title' => $title, 'changes' => $rchanges, 'commit' => $commit, 'branches' => $branches, 'tree_in' => $in_branches, 'tags' => $tags, 'tags_in' => $in_tags, 'scm' => $scmConf, ), $request); } public $treeBase_precond = array('IDF_Precondition::accessSource', 'IDF_Views_Source_Precondition::scmAvailable', 'IDF_Views_Source_Precondition::revisionValid'); public function treeBase($request, $match) { $scm = IDF_Scm::get($request->project); $commit = $match[2]; $cobject = $scm->getCommit($commit); if (!$cobject) { throw new Exception('could not retrieve commit object for '. $commit); } $title = sprintf(__('%1$s %2$s Source Tree'), $request->project, $this->getScmType($request)); $branches = $scm->getBranches(); $in_branches = $scm->inBranches($commit, ''); $tags = $scm->getTags(); $in_tags = $scm->inTags($commit, ''); $cache = Pluf_Cache::factory(); $key = sprintf('Project:%s::IDF_Views_Source::treeBase:%s::', $request->project->id, $commit); if (null === ($res=$cache->get($key))) { $res = new Pluf_Template_ContextVars($scm->getTree($commit)); $cache->set($key, $res); } $scmConf = $request->conf->getVal('scm', 'git'); $props = $scm->getProperties($commit); $res->uasort(array('IDF_Views_Source', 'treeSort')); return Pluf_Shortcuts_RenderToResponse('idf/source/'.$scmConf.'/tree.html', array( 'page_title' => $title, 'title' => $title, 'files' => $res, 'cobject' => $cobject, 'commit' => $commit, 'tree_in' => $in_branches, 'branches' => $branches, 'tags' => $tags, 'tags_in' => $in_tags, 'props' => $props, ), $request); } public $tree_precond = array('IDF_Precondition::accessSource', 'IDF_Views_Source_Precondition::scmAvailable', 'IDF_Views_Source_Precondition::revisionValid'); public function tree($request, $match) { $scm = IDF_Scm::get($request->project); $commit = $match[2]; $request_file = $match[3]; if (substr($request_file, -1) == '/') { $request_file = substr($request_file, 0, -1); $url = Pluf_HTTP_URL_urlForView('IDF_Views_Source::tree', array($match[1], $match[2], $request_file)); return new Pluf_HTTP_Response_Redirect($url, 301); } $request_file_info = $scm->getPathInfo($request_file, $commit); if (!$request_file_info) { // Redirect to the main branch $fburl = Pluf_HTTP_URL_urlForView('IDF_Views_Source::treeBase', array($request->project->shortname, $scm->getMainBranch())); return new Pluf_HTTP_Response_Redirect($fburl); } $branches = $scm->getBranches(); $tags = $scm->getTags(); if ($request_file_info->type != 'tree') { $info = self::getRequestedFileMimeType($request_file_info, $commit, $scm); if (!IDF_FileUtil::isText($info)) { $rep = new Pluf_HTTP_Response($scm->getFile($request_file_info), $info[0]); $rep->headers['Content-Disposition'] = 'attachment; filename="'.$info[1].'"'; return $rep; } else { // We want to display the content of the file as text $extra = array('branches' => $branches, 'tags' => $tags, 'commit' => $commit, 'request_file' => $request_file, 'request_file_info' => $request_file_info, 'mime' => $info, ); return $this->viewFile($request, $match, $extra); } } $bc = self::makeBreadCrumb($request->project, $commit, $request_file_info->fullpath); $title = sprintf(__('%1$s %2$s Source Tree'), $request->project, $this->getScmType($request)); $page_title = $bc.' - '.$title; $cobject = $scm->getCommit($commit); if (!$cobject) { // Redirect to the first branch $url = Pluf_HTTP_URL_urlForView('IDF_Views_Source::treeBase', array($request->project->shortname, $scm->getMainBranch())); return new Pluf_HTTP_Response_Redirect($url); } $in_branches = $scm->inBranches($commit, $request_file); $in_tags = $scm->inTags($commit, $request_file); $cache = Pluf_Cache::factory(); $key = sprintf('Project:%s::IDF_Views_Source::tree:%s::%s', $request->project->id, $commit, $request_file); if (null === ($res=$cache->get($key))) { $res = new Pluf_Template_ContextVars($scm->getTree($commit, $request_file)); $cache->set($key, $res); } // try to find the previous level if it exists. $prev = explode('/', $request_file); $l = array_pop($prev); $previous = substr($request_file, 0, -strlen($l.' ')); $scmConf = $request->conf->getVal('scm', 'git'); $props = $scm->getProperties($commit, $request_file); $res->uasort(array('IDF_Views_Source', 'treeSort')); return Pluf_Shortcuts_RenderToResponse('idf/source/'.$scmConf.'/tree.html', array( 'page_title' => $page_title, 'title' => $title, 'breadcrumb' => $bc, 'files' => $res, 'commit' => $commit, 'cobject' => $cobject, 'base' => $request_file_info->file, 'prev' => $previous, 'tree_in' => $in_branches, 'branches' => $branches, 'tags' => $tags, 'tags_in' => $in_tags, 'props' => $props, ), $request); } public static function makeBreadCrumb($project, $commit, $file, $sep='/') { $elts = explode('/', $file); $out = array(); $stack = ''; $i = 0; foreach ($elts as $elt) { $stack .= ($i==0) ? rawurlencode($elt) : '/'.rawurlencode($elt); $url = Pluf_HTTP_URL_urlForView('IDF_Views_Source::tree', array($project->shortname, $commit, $stack)); $out[] = ''.Pluf_esc($elt).''; $i++; } return ''.implode(''.$sep.'', $out).''; } public $commit_precond = array('IDF_Precondition::accessSource', 'IDF_Views_Source_Precondition::scmAvailable', 'IDF_Views_Source_Precondition::revisionValid'); public function commit($request, $match) { $scm = IDF_Scm::get($request->project); $commit = $match[2]; $large = $scm->isCommitLarge($commit); $cobject = $scm->getCommit($commit, !$large); if (!$cobject) { throw new Exception('could not retrieve commit object for '. $commit); } $title = sprintf(__('%s Commit Details'), (string) $request->project); $page_title = sprintf(__('%s Commit Details - %s'), (string) $request->project, $commit); $rcommit = IDF_Commit::getOrAdd($cobject, $request->project); $diff = new IDF_Diff($cobject->diff, $scm->getDiffPathStripLevel()); $cobject->diff = null; $diff->parse(); $scmConf = $request->conf->getVal('scm', 'git'); $changes = $scm->getChanges($commit); $branches = $scm->getBranches(); $in_branches = $scm->inBranches($cobject->commit, ''); $tags = $scm->getTags(); $in_tags = $scm->inTags($cobject->commit, ''); return Pluf_Shortcuts_RenderToResponse('idf/source/'.$scmConf.'/commit.html', array( 'page_title' => $page_title, 'title' => $title, 'diff' => $diff, 'cobject' => $cobject, 'commit' => $commit, 'changes' => $changes, 'branches' => $branches, 'tree_in' => $in_branches, 'tags' => $tags, 'tags_in' => $in_tags, 'scm' => $scmConf, 'rcommit' => $rcommit, 'large_commit' => $large, ), $request); } public $downloadDiff_precond = array('IDF_Precondition::accessSource', 'IDF_Views_Source_Precondition::scmAvailable', 'IDF_Views_Source_Precondition::revisionValid'); public function downloadDiff($request, $match) { $scm = IDF_Scm::get($request->project); $commit = $match[2]; $cobject = $scm->getCommit($commit, true); if (!$cobject) { throw new Exception('could not retrieve commit object for '. $commit); } $rep = new Pluf_HTTP_Response($cobject->diff, 'text/plain'); $rep->headers['Content-Disposition'] = 'attachment; filename="'.$commit.'.diff"'; return $rep; } /** * Should only be called through self::tree */ public function viewFile($request, $match, $extra) { $title = sprintf(__('%1$s %2$s Source Tree'), (string) $request->project, $this->getScmType($request)); $scm = IDF_Scm::get($request->project); $branches = $extra['branches']; $tags = $extra['tags']; $commit = $extra['commit']; $request_file = $extra['request_file']; $request_file_info = $extra['request_file_info']; $bc = self::makeBreadCrumb($request->project, $commit, $request_file_info->fullpath); $page_title = $bc.' - '.$title; $cobject = $scm->getCommit($commit); $in_branches = $scm->inBranches($commit, $request_file); $in_tags = $scm->inTags($commit, ''); // try to find the previous level if it exists. $prev = explode('/', $request_file); $l = array_pop($prev); $previous = substr($request_file, 0, -strlen($l.' ')); $scmConf = $request->conf->getVal('scm', 'git'); $props = $scm->getProperties($commit, $request_file); $content = IDF_FileUtil::highLight($extra['mime'], $scm->getFile($request_file_info)); return Pluf_Shortcuts_RenderToResponse('idf/source/'.$scmConf.'/file.html', array( 'page_title' => $page_title, 'title' => $title, 'breadcrumb' => $bc, 'file' => $content, 'commit' => $commit, 'cobject' => $cobject, 'fullpath' => $request_file, 'efullpath' => IDF_Scm::smartEncode($request_file), 'base' => $request_file_info->file, 'prev' => $previous, 'tree_in' => $in_branches, 'branches' => $branches, 'tags' => $tags, 'tags_in' => $in_tags, 'props' => $props, ), $request); } /** * Get a given file at a given commit. * */ public $getFile_precond = array('IDF_Precondition::accessSource', 'IDF_Views_Source_Precondition::scmAvailable', 'IDF_Views_Source_Precondition::revisionValid'); public function getFile($request, $match) { $scm = IDF_Scm::get($request->project); $commit = $match[2]; $request_file = $match[3]; $request_file_info = $scm->getPathInfo($request_file, $commit); if (!$request_file_info or $request_file_info->type == 'tree') { // Redirect to the first branch $url = Pluf_HTTP_URL_urlForView('IDF_Views_Source::treeBase', array($request->project->shortname, $scm->getMainBranch())); return new Pluf_HTTP_Response_Redirect($url); } $info = self::getRequestedFileMimeType($request_file_info, $commit, $scm); $rep = new Pluf_HTTP_Response($scm->getFile($request_file_info), $info[0]); $rep->headers['Content-Disposition'] = 'attachment; filename="'.$info[1].'"'; return $rep; } /** * Get a zip archive of the current commit. * */ public $download_precond = array('IDF_Precondition::accessSource', 'IDF_Views_Source_Precondition::scmAvailable', 'IDF_Views_Source_Precondition::revisionValid'); public function download($request, $match) { $commit = trim($match[2]); $scm = IDF_Scm::get($request->project); $base = $request->project->shortname.'-'.$commit; $rep = $scm->getArchiveStream($commit, $base.'/'); $rep->headers['Content-Transfer-Encoding'] = 'binary'; $rep->headers['Content-Disposition'] = 'attachment; filename="'.$base.'.zip"'; return $rep; } /** * Find the mime type of a requested file. * * @param stdClass Request file info * @param string Commit at which we want the file * @param IDF_Scm SCM object * @param array Mime type found or 'application/octet-stream', basename, extension */ public static function getRequestedFileMimeType($file_info, $commit, $scm) { $mime = IDF_FileUtil::getMimeType($file_info->file); if ('application/octet-stream' != $mime[0]) { return $mime; } return IDF_FileUtil::getMimeTypeFromContent($file_info->file, $scm->getFile($file_info)); } /** * Callback function to sort tree entries */ public static function treeSort($a, $b) { // compare two nodes of the same type if ($a->type === $b->type) { if (mb_convert_case($a->file, MB_CASE_LOWER) < mb_convert_case ($b->file, MB_CASE_LOWER)) { return -1; } return 1; } // compare two nodes of different types, directories ("tree") // should come before files ("blob") if ($a->type > $b->type) { return -1; } return 1; } /** * Get the scm type for page title * * @return String */ private function getScmType($request) { return mb_convert_case($request->conf->getVal('scm', 'git'), MB_CASE_TITLE, 'UTF-8'); } } function IDF_Views_Source_PrettySize($size) { return Pluf_Template::markSafe(str_replace(' ', ' ', Pluf_Utils::prettySize($size))); } function IDF_Views_Source_PrettySizeSimple($size) { return Pluf_Utils::prettySize($size); } function IDF_Views_Source_ShortenString($string, $length) { $ellipse = "..."; $length = max(strlen($ellipse) + 2, $length); $preflen = ceil($length / 10); if (mb_strlen($string) < $length) return $string; return substr($string, 0, $preflen).$ellipse. substr($string, -($length - $preflen - mb_strlen($ellipse))); }