diff --git a/src/IDF/Diff.php b/src/IDF/Diff.php index 2f86fd0..58bab6b 100644 --- a/src/IDF/Diff.php +++ b/src/IDF/Diff.php @@ -56,6 +56,14 @@ class IDF_Diff $current_chunk = 0; continue; } + if (0 === strpos($line, 'Index: ')) { + $current_file = self::getSvnFile($line); + $files[$current_file] = array(); + $files[$current_file]['chunks'] = array(); + $files[$current_file]['chunks_def'] = array(); + $current_chunk = 0; + continue; + } if (0 === strpos($line, '@@ ')) { $files[$current_file]['chunks_def'][] = self::getChunk($line); $files[$current_file]['chunks'][] = array(); @@ -95,6 +103,11 @@ class IDF_Diff return trim(substr($line, 3, $n-3)); } + public static function getSvnFile($line) + { + return substr(trim($line), 7); + } + /** * Return the html version of a parsed diff. */ diff --git a/src/IDF/Git.php b/src/IDF/Git.php index 0fb373a..316f928 100644 --- a/src/IDF/Git.php +++ b/src/IDF/Git.php @@ -40,9 +40,10 @@ class IDF_Git * Test a given object hash. * * @param string Object hash. + * @param null to be svn client compatible * @return mixed false if not valid or 'blob', 'tree', 'commit' */ - public function testHash($hash) + public function testHash($hash, $dummy=null) { $cmd = sprintf('GIT_DIR=%s git cat-file -t %s', escapeshellarg($this->repo), @@ -297,6 +298,7 @@ class IDF_Git } } $c['full_message'] = trim($c['full_message']); + $res[] = (object) $c; return $res; } diff --git a/src/IDF/Project.php b/src/IDF/Project.php index 26b7889..17887d9 100644 --- a/src/IDF/Project.php +++ b/src/IDF/Project.php @@ -335,6 +335,34 @@ class IDF_Project extends Pluf_Model return $gitrep.'/'.$this->shortname.'.git'; } + /** + * Get the path to the git repository. + * + * @return string Path to the git repository + */ + public function getSvnDaemonUrl() + { + $conf = new IDF_Conf(); + $conf->setProject($this); + + return $conf->getVal('svn_daemon_url'); + } + + + /** + * Get the root name of the project scm + * + * @return string SCM root + */ + public function getScmRoot() + { + $roots = array('git' => 'master', 'svn' => 'HEAD'); + $conf = new IDF_Conf(); + $conf->setProject($this); + $scm = $conf->getVal('scm', 'git'); + return $roots[$scm]; + } + /** * Check that the object belongs to the project or rise a 404 * error. diff --git a/src/IDF/ScmFactory.php b/src/IDF/ScmFactory.php new file mode 100644 index 0000000..d8ae0a1 --- /dev/null +++ b/src/IDF/ScmFactory.php @@ -0,0 +1,53 @@ +conf->getVal('scm', 'git'); + + // CASE: git + if ($scm === 'git') { + return new IDF_Git($request->project->getGitRepository()); + } + + // CASE: svn + if ($scm === 'svn') { + return new IDF_Svn($request->conf->getVal('svn_repository'), + $request->conf->getVal('svn_username'), + $request->conf->getVal('svn_password')); + } + } +} + diff --git a/src/IDF/Svn.php b/src/IDF/Svn.php new file mode 100644 index 0000000..697417b --- /dev/null +++ b/src/IDF/Svn.php @@ -0,0 +1,384 @@ + 'tree', + 'file' => 'blob'); + + + public function __construct($repo, $username='', $password='') + { + $this->repo = $repo; + $this->username = $username; + $this->password = $password; + } + + + /** + * Test a given object hash. + * + * @param string Object hash. + * @return mixed false if not valid or 'blob', 'tree', 'commit' + */ + public function testHash($rev, $path='') + { + // OK if HEAD on / + if ($rev === 'HEAD' && $path === '') { + return 'commit'; + } + + // Else, test the path on revision + $cmd = sprintf('svn info --xml --username=%s --password=%s %s@%s', + escapeshellarg($this->username), + escapeshellarg($this->password), + escapeshellarg($this->repo.'/'.$path), + escapeshellarg($rev)); + $xmlInfo = shell_exec($cmd); + + // If exception is thrown, return false + try { + $xml = simplexml_load_string($xmlInfo); + } + catch (Exception $e) { + return false; + } + + // If the entry node does exists, params are wrong + if (!isset($xml->entry)) { + return false; + } + + // Else, enjoy it :) + return 'commit'; + } + + + /** + * Given a commit hash returns an array of files in it. + * + * A file is a class with the following properties: + * + * 'perm', 'type', 'size', 'hash', 'file' + * + * @param string Commit ('HEAD') + * @param string Base folder ('') + * @return array + */ + public function filesAtCommit($rev='HEAD', $folder='') + { + $cmd = sprintf('svn ls --xml --username=%s --password=%s %s@%s', + escapeshellarg($this->username), + escapeshellarg($this->password), + escapeshellarg($this->repo.'/'.$folder), + escapeshellarg($rev)); + $xmlLs = shell_exec($cmd); + $xml = simplexml_load_string($xmlLs); + + $res = array(); + foreach ($xml->list->entry as $entry) { + $file = array(); + $file['type'] = $this->assoc[(String) $entry['kind']]; + $file['file'] = (String) $entry->name; + $file['fullpath'] = $folder.'/'.((String) $entry->name); + $file['date'] = gmdate('Y-m-d H:i:s', + strtotime((String) $entry->commit->date)); + $file['rev'] = (String) $entry->commit['revision']; + + // Get commit message + $currentReposFile = $this->repo.'/'.$folder.'/'.$file['file']; + $file['log'] = $this->getCommitMessage($currentReposFile, $rev); + + // Get the size if the type is blob + if ($file['type'] == 'blob') { + $file['size'] = (String) $entry->size; + } + + $file['perm'] = ''; + + $res[] = (Object) $file; + } + + return $res; + } + + + /** + * Get a commit message for given file and revision. + * + * @param string File + * @param string Commit ('HEAD') + * + * @return String commit message + */ + private function getCommitMessage($file, $rev='HEAD') + { + $cmd = sprintf('svn log --xml --limit 1 --username=%s --password=%s %s@%s', + escapeshellarg($this->username), + escapeshellarg($this->password), + escapeshellarg($file), + escapeshellarg($rev)); + $xmlLog = shell_exec($cmd); + $xml = simplexml_load_string($xmlLog); + return (String) $xml->logentry->msg; + } + + + /** + * Get the file info. + * + * @param string File + * @param string Commit ('HEAD') + * @return false Information + */ + public function getFileInfo($totest, $rev='HEAD') + { + $cmd = sprintf('svn info --xml --username=%s --password=%s %s@%s', + escapeshellarg($this->username), + escapeshellarg($this->password), + escapeshellarg($this->repo.'/'.$totest), + escapeshellarg($rev)); + $xmlInfo = shell_exec($cmd); + $xml = simplexml_load_string($xmlInfo); + $entry = $xml->entry; + + $file = array(); + $file['fullpath'] = $totest; + $file['hash'] = (String) $entry->repository->uuid; + $file['type'] = $this->assoc[(String) $entry['kind']]; + $file['file'] = $totest; + $file['rev'] = (String) $entry->commit['revision']; + $file['author'] = (String) $entry->author; + $file['date'] = gmdate('Y-m-d H:i:s', strtotime((String) $entry->commit->date)); + $file['size'] = (String) $entry->size; + $file['log'] = ''; + + return (Object) $file; + } + + + /** + * Get a blob. + * + * @param string Blob hash + * @return string Raw blob + */ + public function getBlob($path, $rev) + { + $cmd = sprintf('svn cat --username=%s --password=%s %s@%s', + escapeshellarg($this->username), + escapeshellarg($this->password), + escapeshellarg($this->repo.'/'.$path), + escapeshellarg($rev)); + return shell_exec($cmd); + } + + + /** + * Get the branches. + * + * @return array Branches. + */ + public function getBranches() + { + $res = array('HEAD'); + return $res; + } + + + /** + * Get commit details. + * + * @param string Commit ('HEAD'). + * @return array Changes. + */ + public function getCommit($rev='HEAD') + { + $res = array(); + $cmd = sprintf('svn log --xml -v --username=%s --password=%s %s@%s', + escapeshellarg($this->username), + escapeshellarg($this->password), + escapeshellarg($this->repo), + escapeshellarg($rev)); + $xmlRes = shell_exec($cmd); + $xml = simplexml_load_string($xmlRes); + + $res['author'] = (String) $xml->logentry->author; + $res['date'] = gmdate('Y-m-d H:i:s', strtotime((String) $xml->logentry->date)); + $res['title'] = (String) $xml->logentry->msg; + $res['commit'] = (String) $xml->logentry['revision']; + $res['changes'] = $this->getDiff($rev); + $res['tree'] = ''; + + + return (Object) $res; + } + + private function getDiff($rev='HEAD') + { + $res = array(); + $cmd = sprintf('svn diff -c %s --username=%s --password=%s %s', + escapeshellarg($rev), + escapeshellarg($this->username), + escapeshellarg($this->password), + escapeshellarg($this->repo)); + return shell_exec($cmd); + } + + + /** + * Get latest changes. + * + * @param string Commit ('HEAD'). + * @param int Number of changes (10). + * + * @return array Changes. + */ + public function getChangeLog($rev='HEAD', $n=10) + { + $res = array(); + $cmd = sprintf('svn log --xml -v --limit %s --username=%s --password=%s %s@%s', + escapeshellarg($n), + escapeshellarg($this->username), + escapeshellarg($this->password), + escapeshellarg($this->repo), + escapeshellarg($rev)); + $xmlRes = shell_exec($cmd); + $xml = simplexml_load_string($xmlRes); + + $res = array(); + foreach ($xml->logentry as $entry) { + $log = array(); + $log['author'] = (String) $entry->author; + $log['date'] = gmdate('Y-m-d H:i:s', strtotime((String) $entry->date)); + $log['title'] = (String) $entry->msg; + $log['commit'] = (String) $entry['revision']; + $log['full_message'] = ''; + + $res[] = (Object) $log; + } + + return $res; + } + + + /** + * Generate the command to create a zip archive at a given commit. + * Unsupported feature in subversion + * + * @param string dummy + * @param string dummy + * @return Exception + */ + public function getArchiveCommand($commit, $prefix='git-repo-dump/') + { + throw new Exception(('Unsupported feature.')); + } + + + /** + * Get additionnals properties on path and revision + * + * @param string File + * @param string Commit ('HEAD') + * @return array + */ + public function getProperties($rev, $path='') + { + $res = array(); + $cmd = sprintf('svn proplist --xml --username=%s --password=%s %s@%s', + escapeshellarg($this->username), + escapeshellarg($this->password), + escapeshellarg($this->repo.'/'.$path), + escapeshellarg($rev)); + $xmlProps = shell_exec($cmd); + $props = simplexml_load_string($xmlProps); + + // No properties, returns an empty array + if (!isset($props->target)) { + return $res; + } + + // Get the value of each property + foreach ($props->target->property as $prop) { + $key = (String) $prop['name']; + $res[$key] = $this->getProperty($key, $rev, $path); + } + + return $res; + } + + + /** + * Get a specific additionnal property on path and revision + * + * @param string Property + * @param string File + * @param string Commit ('HEAD') + * @return string the property value + */ + private function getProperty($property, $rev, $path='') + { + $res = array(); + $cmd = sprintf('svn propget --xml %s --username=%s --password=%s %s@%s', + escapeshellarg($property), + escapeshellarg($this->username), + escapeshellarg($this->password), + escapeshellarg($this->repo.'/'.$path), + escapeshellarg($rev)); + $xmlProp = shell_exec($cmd); + $prop = simplexml_load_string($xmlProp); + + return (String) $prop->target->property; + } + + + /** + * Get the number of the last commit in the repository. + * + * @param string Commit ('HEAD'). + * + * @return String last number commit + */ + public function getLastCommit($rev='HEAD') + { + $xmlInfo = ''; + $cmd = sprintf('svn info --xml --username=%s --password=%s %s@%s', + escapeshellarg($this->username), + escapeshellarg($this->password), + escapeshellarg($this->repo), + escapeshellarg($rev)); + $xmlInfo = shell_exec($cmd); + + $xml = simplexml_load_string($xmlInfo); + return (String) $xml->entry->commit['revision']; + } +} + diff --git a/src/IDF/Views/Source.php b/src/IDF/Views/Source.php index 4180dca..9fe5503 100644 --- a/src/IDF/Views/Source.php +++ b/src/IDF/Views/Source.php @@ -34,11 +34,13 @@ class IDF_Views_Source public $changeLog_precond = array('IDF_Precondition::accessSource'); public function changeLog($request, $match) { - $title = sprintf(__('%s Git Change Log'), (string) $request->project); - $git = new IDF_Git($request->project->getGitRepository()); - $branches = $git->getBranches(); + $title = sprintf(__('%s %s Change Log'), (string) $request->project, + $this->getScmType($request)); + $scm = IDF_ScmFactory::getScm($request); + $branches = $scm->getBranches(); $commit = $match[2]; - $res = $git->getChangeLog($commit, 25); + $res = $scm->getChangeLog($commit, 25); + $scmConf = $request->conf->getVal('scm', 'git'); return Pluf_Shortcuts_RenderToResponse('source/changelog.html', array( 'page_title' => $title, @@ -46,6 +48,7 @@ class IDF_Views_Source 'changes' => $res, 'commit' => $commit, 'branches' => $branches, + 'scm' => $scmConf, ), $request); } @@ -53,21 +56,27 @@ class IDF_Views_Source public $treeBase_precond = array('IDF_Precondition::accessSource'); public function treeBase($request, $match) { - $title = sprintf(__('%s Git Source Tree'), (string) $request->project); - $git = new IDF_Git($request->project->getGitRepository()); + $title = sprintf(__('%s %s Source Tree'), (string) $request->project, + $this->getScmType($request)); + $scm = IDF_ScmFactory::getScm($request); $commit = $match[2]; - $branches = $git->getBranches(); - if ('commit' != $git->testHash($commit)) { + $branches = $scm->getBranches(); + if ('commit' != $scm->testHash($commit)) { // Redirect to the first branch $url = Pluf_HTTP_URL_urlForView('IDF_Views_Source::treeBase', array($request->project->shortname, $branches[0])); return new Pluf_HTTP_Response_Redirect($url); } - $res = $git->filesAtCommit($commit); - $cobject = $git->getCommit($commit); + $res = $scm->filesAtCommit($commit); + $cobject = $scm->getCommit($commit); $tree_in = in_array($commit, $branches); - return Pluf_Shortcuts_RenderToResponse('source/tree.html', + $scmConf = $request->conf->getVal('scm', 'git'); + $props = null; + if ($scmConf === 'svn') { + $props = $scm->getProperties($commit); + } + return Pluf_Shortcuts_RenderToResponse('source/'.$scmConf.'/tree.html', array( 'page_title' => $title, 'title' => $title, @@ -76,6 +85,7 @@ class IDF_Views_Source 'commit' => $commit, 'tree_in' => $tree_in, 'branches' => $branches, + 'props' => $props, ), $request); } @@ -83,19 +93,20 @@ class IDF_Views_Source public $tree_precond = array('IDF_Precondition::accessSource'); public function tree($request, $match) { - $title = sprintf(__('%s Git Source Tree'), (string) $request->project); - $git = new IDF_Git($request->project->getGitRepository()); - $branches = $git->getBranches(); + $title = sprintf(__('%s %s Source Tree'), (string) $request->project, + $this->getScmType($request)); + $scm = IDF_ScmFactory::getScm($request); + $branches = $scm->getBranches(); $commit = $match[2]; - if ('commit' != $git->testHash($commit)) { + $request_file = $match[3]; + if ('commit' != $scm->testHash($commit, $request_file)) { // Redirect to the first branch $url = Pluf_HTTP_URL_urlForView('IDF_Views_Source::treeBase', array($request->project->shortname, $branches[0])); return new Pluf_HTTP_Response_Redirect($url); } - $request_file = $match[3]; - $request_file_info = $git->getFileInfo($request_file, $commit); + $request_file_info = $scm->getFileInfo($request_file, $commit); if (!$request_file_info) { // Redirect to the first branch $url = Pluf_HTTP_URL_urlForView('IDF_Views_Source::treeBase', @@ -105,21 +116,32 @@ class IDF_Views_Source } if ($request_file_info->type != 'tree') { $info = self::getMimeType($request_file_info->file); - $rep = new Pluf_HTTP_Response($git->getBlob($request_file_info->hash), - $info[0]); + if (Pluf::f('src') == 'git') { + $rep = new Pluf_HTTP_Response($scm->getBlob($request_file_info->hash), + $info[0]); + } + else { + $rep = new Pluf_HTTP_Response($scm->getBlob($request_file_info->fullpath, $commit), + $info[0]); + } $rep->headers['Content-Disposition'] = 'attachment; filename="'.$info[1].'"'; return $rep; } $bc = self::makeBreadCrumb($request->project, $commit, $request_file_info->file); $page_title = $bc.' - '.$title; - $cobject = $git->getCommit($commit); + $cobject = $scm->getCommit($commit); $tree_in = in_array($commit, $branches); - $res = $git->filesAtCommit($commit, $request_file); + $res = $scm->filesAtCommit($commit, $request_file); // 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', + $scmConf = $request->conf->getVal('scm', 'git'); + $props = null; + if ($scmConf === 'svn') { + $props = $scm->getProperties($commit, $request_file); + } + return Pluf_Shortcuts_RenderToResponse('source/'.$scmConf.'/tree.html', array( 'page_title' => $page_title, 'title' => $title, @@ -131,6 +153,7 @@ class IDF_Views_Source 'prev' => $previous, 'tree_in' => $tree_in, 'branches' => $branches, + 'props' => $props, ), $request); } @@ -155,10 +178,10 @@ class IDF_Views_Source public $commit_precond = array('IDF_Precondition::accessSource'); public function commit($request, $match) { - $git = new IDF_Git($request->project->getGitRepository()); + $scm = IDF_ScmFactory::getScm($request); $commit = $match[2]; - $branches = $git->getBranches(); - if ('commit' != $git->testHash($commit)) { + $branches = $scm->getBranches(); + if ('commit' != $scm->testHash($commit)) { // Redirect to the first branch $url = Pluf_HTTP_URL_urlForView('IDF_Views_Source::treeBase', array($request->project->shortname, @@ -167,9 +190,10 @@ class IDF_Views_Source } $title = sprintf(__('%s Commit Details'), (string) $request->project); $page_title = sprintf(__('%s Commit Details - %s'), (string) $request->project, $commit); - $cobject = $git->getCommit($commit); + $cobject = $scm->getCommit($commit); $diff = new IDF_Diff($cobject->changes); $diff->parse(); + $scmConf = $request->conf->getVal('scm', 'git'); return Pluf_Shortcuts_RenderToResponse('source/commit.html', array( 'page_title' => $page_title, @@ -178,6 +202,7 @@ class IDF_Views_Source 'cobject' => $cobject, 'commit' => $commit, 'branches' => $branches, + 'scm' => $scmConf, ), $request); } @@ -190,9 +215,9 @@ class IDF_Views_Source public function download($request, $match) { $commit = trim($match[2]); - $git = new IDF_Git($request->project->getGitRepository()); - $branches = $git->getBranches(); - if ('commit' != $git->testHash($commit)) { + $scm = IDF_ScmFactory::getScm($request); + $branches = $scm->getBranches(); + if ('commit' != $scm->testHash($commit)) { // Redirect to the first branch $url = Pluf_HTTP_URL_urlForView('IDF_Views_Source::treeBase', array($request->project->shortname, @@ -200,13 +225,76 @@ class IDF_Views_Source return new Pluf_HTTP_Response_Redirect($url); } $base = $request->project->shortname.'-'.$commit; - $cmd = $git->getArchiveCommand($commit, $base.'/'); + $cmd = $scm->getArchiveCommand($commit, $base.'/'); $rep = new Pluf_HTTP_Response_CommandPassThru($cmd, 'application/x-zip'); $rep->headers['Content-Transfer-Encoding'] = 'binary'; $rep->headers['Content-Disposition'] = 'attachment; filename="'.$base.'.zip"'; return $rep; } + /** + * Display tree of a specific SVN revision + * + */ + public function treeRev($request, $match) + { + $prj = $request->project; + + // Redirect to tree base if not svn + if ($request->conf->getVal('scm', 'git') != 'svn') { + $url = Pluf_HTTP_URL_urlForView('IDF_Views_Source::treeBase', + array($prj->shortname, $prj->getScmRoot())); + + return new Pluf_HTTP_Response_Redirect($url); + } + + // Get revision value + if (!isset($request->REQUEST['rev']) or trim($request->REQUEST['rev']) == '') { + $scmRoot = $prj->getScmRoot(); + } + else { + $scmRoot = $request->REQUEST['rev']; + } + + // Get source if not / + if (isset($request->REQUEST['sourcefile']) and trim($request->REQUEST['sourcefile']) != '') { + $scmRoot .= '/'.$request->REQUEST['sourcefile']; + } + + // Redirect + $url = Pluf_HTTP_URL_urlForView('IDF_Views_Source::treeBase', + array($prj->shortname, $scmRoot)); + return new Pluf_HTTP_Response_Redirect($url); + } + + /** + * Display SVN changelog from specific revision + * + */ + public function changelogRev($request, $match) + { + $prj = $request->project; + + // Redirect to tree base if not svn + if ($request->conf->getVal('scm', 'git') != 'svn') { + $scmRoot = $prj->getScmRoot(); + } + // Get revision value if svn + else { + if (!isset($request->REQUEST['rev']) or trim($request->REQUEST['rev']) == '') { + $scmRoot = $prj->getScmRoot(); + } + else { + $scmRoot = $request->REQUEST['rev']; + } + } + + // Redirect + $url = Pluf_HTTP_URL_urlForView('IDF_Views_Source::changeLog', + array($prj->shortname, $scmRoot)); + return new Pluf_HTTP_Response_Redirect($url); + } + /** * Find the mime type of a file. * @@ -238,6 +326,16 @@ class IDF_Views_Source } return array('application/octet-stream', $info['basename']); } + + /** + * Get the scm type for page title + * + * @return String + */ + private function getScmType($request) + { + return ucfirst($scm = $request->conf->getVal('scm', 'git')); + } } function IDF_Views_Source_PrettySize($size) diff --git a/src/IDF/conf/views.php b/src/IDF/conf/views.php index 5bb68a2..01ecac1 100644 --- a/src/IDF/conf/views.php +++ b/src/IDF/conf/views.php @@ -133,7 +133,7 @@ $ctl[] = array('regex' => '#^/p/(\w+)/issues/my/(\w+)/$#', 'model' => 'IDF_Views_Issue', 'method' => 'myIssues'); -// ---------- GIT ---------------------------------------- +// ---------- SCM ---------------------------------------- $ctl[] = array('regex' => '#^/p/(\w+)/source/$#', 'base' => $base, @@ -171,6 +171,18 @@ $ctl[] = array('regex' => '#^/p/(\w+)/source/download/(\w+)/$#', 'model' => 'IDF_Views_Source', 'method' => 'download'); +$ctl[] = array('regex' => '#^/p/(\w+)/source/treerev/$#', + 'base' => $base, + 'priority' => 4, + 'model' => 'IDF_Views_Source', + 'method' => 'treeRev'); + +$ctl[] = array('regex' => '#^/p/(\w+)/source/changesrev/$#', + 'base' => $base, + 'priority' => 4, + 'model' => 'IDF_Views_Source', + 'method' => 'changelogRev'); + // ---------- Downloads ------------------------------------ $ctl[] = array('regex' => '#^/p/(\w+)/downloads/$#', diff --git a/src/IDF/templates/base.html b/src/IDF/templates/base.html index 74de62c..5c49e5f 100644 --- a/src/IDF/templates/base.html +++ b/src/IDF/templates/base.html @@ -44,7 +44,7 @@ {trans 'Project Home'} {if $hasIssuesAccess} {trans 'Issues'}{/if} {if $hasDownloadsAccess} {trans 'Downloads'}{/if} -{if $hasSourceAccess} {trans 'Source'}{/if} +{if $hasSourceAccess} {trans 'Source'}{/if} {if $isOwner} {trans 'Administer'}{/if}{/if} diff --git a/src/IDF/templates/source/base.html b/src/IDF/templates/source/base.html index fec2832..166e767 100644 --- a/src/IDF/templates/source/base.html +++ b/src/IDF/templates/source/base.html @@ -2,8 +2,8 @@ {block tabsource} class="active"{/block} {block subtabs}
{trans 'Branches:'}
{foreach $branches as $branch}
{aurl 'url', 'IDF_Views_Source::changeLog', array($project.shortname, $branch)}
{$branch}
{/foreach}
{trans 'Revison:'} {$commit}
+
{trans 'Branches:'}
{foreach $branches as $branch}
{aurl 'url', 'IDF_Views_Source::commit', array($project.shortname, $branch)}
{$branch}
{/foreach}
{trans 'File'} | +{trans 'Age'} | +{trans 'Rev'} | +{trans 'Message'} | +{trans 'Size'} | +||
---|---|---|---|---|---|---|
+
| ||||||
{blocktrans}Source at commit {$commit} created {$cobject.date|dateago}.{/blocktrans} + {blocktrans}By {$cobject.author|strip_tags|trim}, {$cobject.title}{/blocktrans} + | ||||||
+ | + .. | ++ | ||||
+ + | {$file.file} | +{$file.date|dateago:"wihtout"} | +{$file.rev} | +{$file.log|nl2br} | + {if $file.type == 'blob'} +{$file.size|size} | + {/if} + +
svn checkout {$project.getSvnDaemonUrl()}@{$commit}
+ + +{/block} +{block context} +{trans 'Revison:'} {$commit}
+