diff --git a/src/IDF/Git.php b/src/IDF/Git.php index 28d6971..00f1272 100644 --- a/src/IDF/Git.php +++ b/src/IDF/Git.php @@ -41,18 +41,19 @@ class IDF_Git * * 'perm', 'type', 'size', 'hash', 'file' * - * @param string Commit/Branch ('HEAD') + * @param string Tree ('HEAD') * @param string Base folder ('') * @return array */ - public function filesInTree($commit='HEAD', $basefolder='') + public function filesInTree($tree='HEAD', $basefolder='') { if (is_object($basefolder)) { $base = $basefolder; - } else if ($basefolder != '' - and + } else if ( + $basefolder != '' + and ( - (false === ($base=$this->getFileInfo($basefolder, $commit))) + (false === ($base=$this->getFileInfo($basefolder, $tree))) or ($base->type != 'tree') )) { @@ -60,25 +61,35 @@ class IDF_Git } else { // no base $base = (object) array('file' => '', - 'hash' => $commit); + 'hash' => $tree); } $res = array(); $out = array(); $cmd = sprintf('GIT_DIR=%s git-ls-tree -t -l %s', $this->repo, $base->hash); exec($cmd, &$out); + $rawlog = array(); + $cmd = sprintf('GIT_DIR=%s git log --raw --abbrev=40 --pretty=oneline', + $this->repo); + exec($cmd, &$rawlog); + $rawlog = implode("\n", array_reverse($rawlog)); $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); + $matches = array(); + $date = '1970-01-01 12:00:00'; + $log = ''; + if ($type == 'blob' and preg_match('/^\:\d{6} \d{6} [0-9a-f]{40} '.$hash.' .*^([0-9a-f]{40})/msU', + $rawlog, &$matches)) { + $_c = $this->getCommit($matches[1]); + $date = $_c->date; + $log = $_c->title; + } $res[] = (object) array('perm' => $perm, 'type' => $type, 'size' => $size, 'hash' => $hash, 'fullpath' => ($base->file) ? $base->file.'/'.$file : $file, - 'log' => $log, 'time' => $time, + 'log' => $log, 'date' => $date, 'file' => $file); } chdir($current_dir); @@ -88,14 +99,14 @@ class IDF_Git /** * Get the file info. * - * @param string Tree to test - * @param string Commit/Branch ('HEAD') - * @return false or Tree information + * @param string File + * @param string Tree ('HEAD') + * @return false Information */ - public function getFileInfo($totest, $commit='HEAD') + public function getFileInfo($totest, $tree='HEAD') { $cmd_tmpl = 'GIT_DIR=%s git-ls-tree -r -t -l %s'; - $cmd = sprintf($cmd_tmpl, $this->repo, $commit); + $cmd = sprintf($cmd_tmpl, $this->repo, $tree); $out = array(); exec($cmd, &$out); foreach ($out as $line) { @@ -134,4 +145,104 @@ class IDF_Git } return $res; } + + /** + * Get commit details. + * + * @param string Commit ('HEAD'). + * @return array Changes. + */ + public function getCommit($commit='HEAD') + { + $cmd = sprintf('GIT_DIR=%s git show --date=iso --pretty=medium %s', + escapeshellarg($this->repo), $commit); + $out = array(); + exec($cmd, &$out); + $log = array(); + $change = array(); + $inchange = false; + foreach ($out as $line) { + if (!$inchange and 0 === strpos($line, 'diff --git a')) { + $inchange = true; + } + if ($inchange) { + $change[] = $line; + } else { + $log[] = $line; + } + } + $out = self::parseLog($log); + $out[0]->changes = $change; + return $out[0]; + } + + + /** + * Get latest changes. + * + * @param string Tree ('HEAD'). + * @param int Number of changes (10). + * @return array Changes. + */ + public function getChangeLog($tree='HEAD', $n=10) + { + $format = 'commit %H%nAuthor: %an <%ae>%nTree: %T%nDate: %ai%n%n%s%n%n%b'; + if ($n === null) $n = ''; + else $n = ' -'.$n; + $cmd = sprintf('GIT_DIR=%s git log%s --date=iso --pretty=format:\'%s\' %s', + escapeshellarg($this->repo), $n, $format, $tree); + $out = array(); + exec($cmd, &$out); + return self::parseLog($out, 4); + } + + /** + * Parse the log lines of a --pretty=medium log output. + * + * @param array Lines. + * @param int Number of lines in the headers (3) + * @return array Change log. + */ + public static function parseLog($lines, $hdrs=3) + { + $res = array(); + $c = array(); + $i = 0; + $hdrs += 2; + foreach ($lines as $line) { + $i++; + if (0 === strpos($line, 'commit')) { + if (count($c) > 0) { + $c['full_message'] = trim($c['full_message']); + $res[] = (object) $c; + } + $c = array(); + $c['commit'] = trim(substr($line, 7)); + $c['full_message'] = ''; + $i=1; + continue; + } + if ($i == $hdrs) { + $c['title'] = trim($line); + continue; + } + $match = array(); + if (preg_match('/(\S+)\s*:\s*(.*)/', $line, $match)) { + $match[1] = strtolower($match[1]); + $c[$match[1]] = trim($match[2]); + if ($match[1] == 'date') { + $c['date'] = gmdate('Y-m-d H:i:s', strtotime($match[2])); + } + continue; + } + if ($i > ($hdrs+1)) { + $c['full_message'] .= trim($line)."\n"; + continue; + } + } + $c['full_message'] = trim($c['full_message']); + $res[] = (object) $c; + return $res; + } + } \ No newline at end of file diff --git a/src/IDF/Views/Source.php b/src/IDF/Views/Source.php index c5851c9..fa89830 100644 --- a/src/IDF/Views/Source.php +++ b/src/IDF/Views/Source.php @@ -31,8 +31,22 @@ Pluf::loadFunction('Pluf_Shortcuts_GetFormForModel'); */ class IDF_Views_Source { - public function index($request, $match) + public function changeLog($request, $match) { + $title = sprintf('%s Git Change Log', (string) $request->project); + $git = new IDF_Git(Pluf::f('git_repository')); + $branches = $git->getBranches(); + $commit = $match[2]; + $res = $git->getChangeLog($commit, 50); + return Pluf_Shortcuts_RenderToResponse('source/changelog.html', + array( + 'page_title' => $title, + 'title' => $title, + 'changes' => $res, + 'commit' => $commit, + 'branches' => $branches, + ), + $request); } public function treeBase($request, $match) @@ -41,13 +55,22 @@ class IDF_Views_Source $git = new IDF_Git(Pluf::f('git_repository')); $branches = $git->getBranches(); $res = $git->filesInTree($match[2]); - $commit = $match[2]; + $tree = $match[2]; + $cobject = ''; + $tree_in = in_array($tree, $branches); + foreach ($git->getChangeLog('', null) as $change) { + if ($change->tree == $tree) { + $cobject = $change; + } + } return Pluf_Shortcuts_RenderToResponse('source/tree.html', array( 'page_title' => $title, 'title' => $title, 'files' => $res, - 'commit' => $commit, + 'cobject' => $cobject, + 'tree' => $tree, + 'tree_in' => $tree_in, 'branches' => $branches, ), $request); @@ -57,37 +80,46 @@ class IDF_Views_Source { $title = sprintf('%s Git Source Tree', (string) $request->project); $git = new IDF_Git(Pluf::f('git_repository')); - $branches = $git->getBranches(); - $commit = $match[2]; + $tree = $match[2]; $request_file = $match[3]; - $request_file_info = $git->getFileInfo($request_file); + $request_file_info = $git->getFileInfo($request_file, $tree); 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); + $bc = self::makeBreadCrumb($request->project, $tree, $request_file_info->file); + $page_title = $bc.' - '.$title; + $branches = $git->getBranches(); + $cobject = ''; + $tree_in = in_array($tree, $branches); + $res = $git->filesInTree($tree, $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.' ')); + foreach ($git->getChangeLog('', null) as $change) { + if ($change->tree == $tree) { + $cobject = $change; //$git->getCommit($tree); + } + } return Pluf_Shortcuts_RenderToResponse('source/tree.html', array( 'page_title' => $page_title, 'title' => $title, 'breadcrumb' => $bc, 'files' => $res, - 'commit' => $commit, + 'tree' => $tree, + 'cobject' => $cobject, 'base' => $request_file_info->file, 'prev' => $previous, + 'tree_in' => $tree_in, 'branches' => $branches, ), $request); } - public static function makeBreadCrumb($project, $commit, $file, $sep='/') + public static function makeBreadCrumb($project, $tree, $file, $sep='/') { $elts = split('/', $file); $out = array(); @@ -97,7 +129,7 @@ class IDF_Views_Source $stack .= ($i==0) ? $elt : '/'.$elt; $url = Pluf_HTTP_URL_urlForView('IDF_Views_Source::tree', array($project->shortname, - $commit, $stack)); + $tree, $stack)); $out[] = ''.Pluf_esc($elt).''; $i++; } diff --git a/src/IDF/conf/views.php b/src/IDF/conf/views.php index a5274c8..d9a372a 100644 --- a/src/IDF/conf/views.php +++ b/src/IDF/conf/views.php @@ -110,6 +110,12 @@ $ctl[] = array('regex' => '#^/p/(\w+)/source/tree/(\w+)/(.*)$#', 'model' => 'IDF_Views_Source', 'method' => 'tree'); +$ctl[] = array('regex' => '#^/p/(\w+)/source/changes/(\w+)/$#', + 'base' => $base, + 'priority' => 4, + 'model' => 'IDF_Views_Source', + 'method' => 'changeLog'); + // ---------- ADMIN -------------------------------------- diff --git a/src/IDF/templates/base.html b/src/IDF/templates/base.html index 57cf351..1277a29 100644 --- a/src/IDF/templates/base.html +++ b/src/IDF/templates/base.html @@ -43,7 +43,7 @@ {if $project} {* {trans 'Project Home'} *} {trans 'Issues'} - {trans 'Source'} + {trans 'Source'} {if $isOwner} {trans 'Administer'}{/if}{/if} diff --git a/src/IDF/templates/source/base.html b/src/IDF/templates/source/base.html index 9b5191b..a9fd256 100644 --- a/src/IDF/templates/source/base.html +++ b/src/IDF/templates/source/base.html @@ -2,7 +2,8 @@ {block tabsource} class="active"{/block} {block subtabs}
-{trans 'Source Tree'} +{trans 'Source Tree'} | +{trans 'Change Log'}
{/block} {block title}{$title}{/block} diff --git a/src/IDF/templates/source/changelog.html b/src/IDF/templates/source/changelog.html new file mode 100644 index 0000000..7e8a707 --- /dev/null +++ b/src/IDF/templates/source/changelog.html @@ -0,0 +1,34 @@ +{extends "source/base.html"} +{block docclass}yui-t1{/block} +{block body} + + + + + + + + + +{foreach $changes as $change} +{aurl 'url', 'IDF_Views_Source::treeBase', array($project.shortname, $change.tree)} + + + + + +{/foreach} + +
{trans 'Age'}{trans 'Message'}{trans 'Details'}
{$change.date|dateago:"wihtout"}{$change.title}{if $change.full_message}
{$change.full_message}{/if}
{trans 'Tree:'} {$change.tree}
+{trans 'By:'} {$change.author|strip_tags} {* this remove the email address *} +
+{/block} +{block context} +

{trans 'Branches:'}
+{foreach $branches as $branch} +{aurl 'url', 'IDF_Views_Source::changeLog', array($project.shortname, $branch)} +{$branch}
+{/foreach} +

+{/block} + diff --git a/src/IDF/templates/source/tree.html b/src/IDF/templates/source/tree.html index eb5ab05..357db18 100644 --- a/src/IDF/templates/source/tree.html +++ b/src/IDF/templates/source/tree.html @@ -1,7 +1,7 @@ {extends "source/base.html"} {block docclass}yui-t1{/block} {block body} -

{trans 'Root'}/{if $breadcrumb}{$breadcrumb|safe}{/if}

+

{trans 'Root'}/{if $breadcrumb}{$breadcrumb|safe}{/if}

@@ -10,23 +10,27 @@ - - +{if !$tree_in} +{aurl 'url', 'IDF_Views_Source::treeBase', array($project.shortname, $tree)} + + + +{/if} {if $base} +.. {/if} {foreach $files as $file} -{aurl 'url', 'IDF_Views_Source::tree', array($project.shortname, $commit, $file.fullpath)} +{aurl 'url', 'IDF_Views_Source::tree', array($project.shortname, $tree, $file.fullpath)} - - -{$file.log} +{$file.file} {if $file.type == 'blob'} + +{/if} {/foreach} @@ -38,7 +42,7 @@

{trans 'Branches:'}
{foreach $branches as $branch} {aurl 'url', 'IDF_Views_Source::treeBase', array($project.shortname, $branch)} -{$branch}
+{$branch}
{/foreach}

{/block} diff --git a/www/media/idf/css/style.css b/www/media/idf/css/style.css index 627849a..8183dec 100644 --- a/www/media/idf/css/style.css +++ b/www/media/idf/css/style.css @@ -273,6 +273,16 @@ table.tree-list td { vertical-align: top; } +table.tree-list tfoot th { + text-align: right; + font-weight: normal; +} + +table.recent-issues tfoot th a { + color: #000; + font-weight: normal; +} + /**
{trans 'Message'} {trans 'Size'}
{blocktrans}In tree {$tree} created {$cobject.date|dateago}.{/blocktrans}
  -..
{$file.type}{$file.file}{$file.time|timeago:"wihtout"}{$file.date|dateago:"wihtout"}{$file.log} {$file.size|size}