From 876e206742179ba640667430b5cad6b45c651250 Mon Sep 17 00:00:00 2001 From: Loic d'Anterroches Date: Sat, 26 Jul 2008 18:42:41 +0200 Subject: [PATCH] First work on the git browser. --- src/IDF/Git.php | 137 +++++++++++++++++++++++++++++ src/IDF/Views/Source.php | 111 +++++++++++++++++++++++ src/IDF/conf/views.php | 27 +++++- src/IDF/templates/base.html | 2 + src/IDF/templates/source/base.html | 8 ++ src/IDF/templates/source/tree.html | 44 +++++++++ www/media/idf/css/style.css | 29 ++++++ www/media/idf/img/blob.png | Bin 0 -> 333 bytes www/media/idf/img/home.png | Bin 0 -> 528 bytes www/media/idf/img/tree.png | Bin 0 -> 498 bytes 10 files changed, 356 insertions(+), 2 deletions(-) create mode 100644 src/IDF/Git.php create mode 100644 src/IDF/Views/Source.php create mode 100644 src/IDF/templates/source/base.html create mode 100644 src/IDF/templates/source/tree.html create mode 100644 www/media/idf/img/blob.png create mode 100644 www/media/idf/img/home.png create mode 100644 www/media/idf/img/tree.png diff --git a/src/IDF/Git.php b/src/IDF/Git.php new file mode 100644 index 0000000..28d6971 --- /dev/null +++ b/src/IDF/Git.php @@ -0,0 +1,137 @@ +repo = $repo; + } + + /** + * Given a commit hash (or a branch) returns an array of files in + * it. + * + * A file is a class with the following properties: + * + * 'perm', 'type', 'size', 'hash', 'file' + * + * @param string Commit/Branch ('HEAD') + * @param string Base folder ('') + * @return array + */ + public function filesInTree($commit='HEAD', $basefolder='') + { + if (is_object($basefolder)) { + $base = $basefolder; + } else if ($basefolder != '' + and + ( + (false === ($base=$this->getFileInfo($basefolder, $commit))) + or + ($base->type != 'tree') + )) { + throw new Exception(sprintf('Base folder "%s" not found.', $basefolder)); + } else { + // no base + $base = (object) array('file' => '', + 'hash' => $commit); + } + + $res = array(); + $out = array(); + $cmd = sprintf('GIT_DIR=%s git-ls-tree -t -l %s', $this->repo, $base->hash); + exec($cmd, &$out); + $current_dir = getcwd(); + chdir(substr($this->repo, 0, -5)); + foreach ($out as $line) { + list($perm, $type, $hash, $size, $file) = preg_split('/ |\t/', $line, 5, PREG_SPLIT_NO_EMPTY); + $cm = array(); + $cmd = sprintf('GIT_DIR=%s git log -1 --pretty=format:\'%%H %%at %%s\' %s -- %s', $this->repo, $commit, ($base->file) ? $base->file.'/'.$file : $file); + exec($cmd, &$cm); + list($h, $time, $log) = explode(' ', $cm[0], 3); + $res[] = (object) array('perm' => $perm, 'type' => $type, + 'size' => $size, 'hash' => $hash, + 'fullpath' => ($base->file) ? $base->file.'/'.$file : $file, + 'log' => $log, 'time' => $time, + 'file' => $file); + } + chdir($current_dir); + return $res; + } + + /** + * Get the file info. + * + * @param string Tree to test + * @param string Commit/Branch ('HEAD') + * @return false or Tree information + */ + public function getFileInfo($totest, $commit='HEAD') + { + $cmd_tmpl = 'GIT_DIR=%s git-ls-tree -r -t -l %s'; + $cmd = sprintf($cmd_tmpl, $this->repo, $commit); + $out = array(); + exec($cmd, &$out); + foreach ($out as $line) { + list($perm, $type, $hash, $size, $file) = preg_split('/ |\t/', $line, 5, PREG_SPLIT_NO_EMPTY); + if ($totest == $file) { + return (object) array('perm' => $perm, 'type' => $type, + 'size' => $size, 'hash' => $hash, + 'file' => $file); + } + } + return false; + } + + /** + * Get a blob. + * + * @param string Blob hash + * @return string Raw blob + */ + public function getBlob($hash) + { + return shell_exec(sprintf('GIT_DIR=%s git-cat-file blob %s', + $this->repo, $hash)); + } + + /** + * Get the branches. + */ + public function getBranches() + { + $out = array(); + exec(sprintf('GIT_DIR=%s git branch', $this->repo), &$out); + $res = array(); + foreach ($out as $b) { + $res[] = substr($b, 2); + } + return $res; + } +} \ No newline at end of file diff --git a/src/IDF/Views/Source.php b/src/IDF/Views/Source.php new file mode 100644 index 0000000..a90599b --- /dev/null +++ b/src/IDF/Views/Source.php @@ -0,0 +1,111 @@ +project); + $git = new IDF_Git(Pluf::f('git_repository')); + $branches = $git->getBranches(); + $res = $git->filesInTree($match[2]); + $commit = $match[2]; + return Pluf_Shortcuts_RenderToResponse('source/tree.html', + array( + 'page_title' => $title, + 'files' => $res, + 'commit' => $commit, + 'branches' => $branches, + ), + $request); + } + + public function tree($request, $match) + { + $title = sprintf('%s Git Source Tree', (string) $request->project); + $git = new IDF_Git(Pluf::f('git_repository')); + $branches = $git->getBranches(); + $commit = $match[2]; + $request_file = $match[3]; + $request_file_info = $git->getFileInfo($request_file); + if (!$request_file_info) throw new Pluf_HTTP_Error404(); + $bc = self::makeBreadCrumb($request->project, $commit, $request_file_info->file); + $page_title = $bc.' - '.$title; + if ($request_file_info->type != 'tree') { + return new Pluf_HTTP_Response($git->getBlob($request_file_info->hash), + 'application/octet-stream'); + } + $res = $git->filesInTree($commit, $request_file_info); + // try to find the previous level if it exists. + $prev = split('/', $request_file); + $l = array_pop($prev); + $previous = substr($request_file, 0, -strlen($l.' ')); + return Pluf_Shortcuts_RenderToResponse('source/tree.html', + array( + 'page_title' => $page_title, + 'title' => $title, + 'breadcrumb' => $bc, + 'files' => $res, + 'commit' => $commit, + 'base' => $request_file_info->file, + 'prev' => $previous, + 'branches' => $branches, + ), + $request); + } + + public static function makeBreadCrumb($project, $commit, $file, $sep='/') + { + $elts = split('/', $file); + $out = array(); + $stack = ''; + $i = 0; + foreach ($elts as $elt) { + $stack .= ($i==0) ? $elt : '/'.$elt; + $url = Pluf_HTTP_URL_urlForView('IDF_Views_Source::tree', + array($project->shortname, + $commit, $stack)); + $out[] = ''.Pluf_esc($elt).''; + $i++; + } + return ''.implode(''.$sep.'', $out).''; + } +} + +function IDF_Views_Source_PrettySize($size) +{ + return Pluf_Template::markSafe(Pluf_Utils::prettySize($size)); +} + diff --git a/src/IDF/conf/views.php b/src/IDF/conf/views.php index be94528..a5274c8 100644 --- a/src/IDF/conf/views.php +++ b/src/IDF/conf/views.php @@ -51,8 +51,8 @@ $ctl[] = array('regex' => '#^/help/$#', $ctl[] = array('regex' => '#^/p/(\w+)/$#', 'base' => $base, 'priority' => 4, - 'model' => 'IDF_Views', - 'method' => 'projectHome'); + 'model' => 'IDF_Views_Project', + 'method' => 'home'); $ctl[] = array('regex' => '#^/p/(\w+)/issues/$#', 'base' => $base, @@ -90,6 +90,29 @@ $ctl[] = array('regex' => '#^/p/(\w+)/issues/my/(\w+)/$#', 'model' => 'IDF_Views_Issue', 'method' => 'myIssues'); +// ---------- GIT ---------------------------------------- + +$ctl[] = array('regex' => '#^/p/(\w+)/source/$#', + 'base' => $base, + 'priority' => 4, + 'model' => 'IDF_Views_Source', + 'method' => 'index'); + +$ctl[] = array('regex' => '#^/p/(\w+)/source/tree/(\w+)/$#', + 'base' => $base, + 'priority' => 4, + 'model' => 'IDF_Views_Source', + 'method' => 'treeBase'); + +$ctl[] = array('regex' => '#^/p/(\w+)/source/tree/(\w+)/(.*)$#', + 'base' => $base, + 'priority' => 4, + 'model' => 'IDF_Views_Source', + 'method' => 'tree'); + + +// ---------- ADMIN -------------------------------------- + $ctl[] = array('regex' => '#^/p/(\w+)/admin/$#', 'base' => $base, 'priority' => 4, diff --git a/src/IDF/templates/base.html b/src/IDF/templates/base.html index a94edf3..57cf351 100644 --- a/src/IDF/templates/base.html +++ b/src/IDF/templates/base.html @@ -41,7 +41,9 @@