From 9fd4334dec58e292e9a3c4e3c6ab776a6ee8d07c Mon Sep 17 00:00:00 2001 From: Thomas Keller Date: Mon, 26 Apr 2010 23:56:25 +0200 Subject: [PATCH 01/40] Start on monotone support for indefero. The SCM backend is about 30% done, interesting pieces like getTree() are not finished yet. --- src/IDF/Scm/Monotone.php | 651 ++++++++++++++++++ .../templates/idf/source/mtn/changelog.html | 17 + src/IDF/templates/idf/source/mtn/file.html | 44 ++ src/IDF/templates/idf/source/mtn/help.html | 38 + src/IDF/templates/idf/source/mtn/tree.html | 69 ++ 5 files changed, 819 insertions(+) create mode 100644 src/IDF/Scm/Monotone.php create mode 100644 src/IDF/templates/idf/source/mtn/changelog.html create mode 100644 src/IDF/templates/idf/source/mtn/file.html create mode 100644 src/IDF/templates/idf/source/mtn/help.html create mode 100644 src/IDF/templates/idf/source/mtn/tree.html diff --git a/src/IDF/Scm/Monotone.php b/src/IDF/Scm/Monotone.php new file mode 100644 index 0000000..8c8b83f --- /dev/null +++ b/src/IDF/Scm/Monotone.php @@ -0,0 +1,651 @@ +%nTree: %T%nDate: %ai%n%n%s%n%n%b'; + + /* ============================================== * + * * + * Common Methods Implemented By All The SCMs * + * * + * ============================================== */ + + public function __construct($repo, $project=null) + { + $this->repo = $repo; + $this->project = $project; + } + + public function getRepositorySize() + { + if (!file_exists($this->repo)) { + return 0; + } + $cmd = Pluf::f('idf_exec_cmd_prefix', '').'du -sk ' + .escapeshellarg($this->repo); + $out = explode(' ', + self::shell_exec('IDF_Scm_Monotone::getRepositorySize', $cmd), + 2); + return (int) $out[0]*1024; + } + + public function isAvailable() + { + try { + $branches = $this->getBranches(); + } catch (IDF_Scm_Exception $e) { + return false; + } + return (count($branches) > 0); + } + + public function getBranches() + { + if (isset($this->cache['branches'])) { + return $this->cache['branches']; + } + // FIXME: introduce handling of suspended branches + $cmd = Pluf::f('idf_exec_cmd_prefix', '') + .sprintf("%s -d %s automate branches", + Pluf::f('mtn_path', 'mtn'), + escapeshellarg($this->repo)); + self::exec('IDF_Scm_Monotone::getBranches', + $cmd, $out, $return); + if ($return != 0) { + throw new IDF_Scm_Exception(sprintf($this->error_tpl, + $cmd, $return, + implode("\n", $out))); + } + $res = array(); + // FIXME: we could expand each branch with one of its head revisions + // here, but these would soon become bogus anyway and we cannot + // map multiple head revisions here either, so we just use the + // selector as placeholder + foreach ($out as $b) { + $res["h:$b"] = $b; + } + $this->cache['branches'] = $res; + return $res; + } + + /** + * monotone has no concept of a "main" branch, so just return + * the first one (the branch list is already sorted) + * + * @return string + */ + public function getMainBranch() + { + $branches = $this->getBranches(); + return key($branches); + } + + /** + * expands a selector or a partial revision id to zero, one or + * multiple 40 byte revision ids + * + * @param string $selector + * @return array + */ + private static function _resolveSelector($selector) + { + $cmd = Pluf::f('idf_exec_cmd_prefix', '') + .sprintf("%s -d %s automate select %s", + Pluf::f('mtn_path', 'mtn'), + escapeshellarg($this->repo), + escapeshellarg($selector)); + self::exec('IDF_Scm_Monotone::_resolveSelector', + $cmd, $out, $return); + return $out; + } + + /** + * Parses monotone's basic_io format + * + * @param string $in + * @return array of arrays + */ + private static function _parseBasicIO($in) + { + $pos = 0; + $stanzas = array(); + + while ($pos < strlen($in)) + { + $stanza = array(); + while ($pos < strlen($in)) + { + if ($in[$pos] == "\n") break; + + $stanzaLine = array("key" => "", "values" => array(), "hash" => null); + while ($pos < strlen($in)) + { + $ch = $in[$pos]; + if ($ch == '"' || $ch == '[') break; + ++$pos; + if ($ch == ' ') continue; + $stanzaLine['key'] .= $ch; + } + + if ($in[$pos] == '[') + { + ++$pos; // opening square bracket + $stanzaLine['hash'] = substr($in, $pos, 40); + $pos += 40; + ++$pos; // closing square bracket + } + else + { + $valCount = 0; + while ($in[$pos] == '"') + { + ++$pos; // opening quote + $stanzaLine['values'][$valCount] = ""; + while ($pos < strlen($in)) + { + $ch = $in[$pos]; $pr = $in[$pos-1]; + if ($ch == '"' && $pr != '\\') break; + ++$pos; + $stanzaLine['values'][$valCount] .= $ch; + } + ++$pos; // closing quote + + if ($in[$pos] == ' ') + { + ++$pos; // space + ++$valCount; + } + } + } + + $stanza[] = $stanzaLine; + ++$pos; // newline + } + $stanzas[] = $stanza; + ++$pos; // newline + } + return $stanzas; + } + + private static function _getUniqueCertValuesFor($revs, $certName) + { + $certValues = array(); + foreach ($revs as $rev) + { + $cmd = Pluf::f('idf_exec_cmd_prefix', '') + .sprintf("%s -d %s automate certs %s", + Pluf::f('mtn_path', 'mtn'), + escapeshellarg($this->repo), + escapeshellarg($rev)); + self::exec('IDF_Scm_Monotone::inBranches', + $cmd, $out, $return); + + $stanzas = self::_parseBasicIO(implode('\n', $out)); + foreach ($stanzas as $stanza) + { + foreach ($stanza as $stanzaline) + { + // luckily, name always comes before value + if ($stanzaline['key'] == "name" && + $stanzaline['values'][0] != $certName) + { + break; + } + if ($stanzaline['key'] == "value") + { + $certValues[] = $stanzaline['values'][0]; + break; + } + } + } + } + return array_unique($certValues); + } + + /** + * @see IDF_Scm::inBranches() + **/ + public function inBranches($commit, $path) + { + $revs = self::_resolveSelector($commit); + if (count($revs) == 0) return array(); + return self::_getUniqueCertValuesFor($revs, "branch"); + } + + /** + * @see IDF_Scm::getTags() + **/ + public function getTags() + { + if (isset($this->cache['tags'])) { + return $this->cache['tags']; + } + $cmd = Pluf::f('idf_exec_cmd_prefix', '') + .sprintf("%s -d %s automate tags", + Pluf::f('mtn_path', 'mtn'), + escapeshellarg($this->repo)); + self::exec('IDF_Scm_Monotone::getTags', $cmd, $out, $return); + + $tags = array(); + $stanzas = self::parseBasicIO(implode('\n', $out)); + foreach ($stanzas as $stanza) + { + foreach ($stanza as $stanzaline) + { + if ($stanzaline['key'] == "tag") + { + $tags[] = $stanzaline['values'][0]; + break; + } + } + } + + $this->cache['tags'] = $tags; + return $tags; + } + + /** + * @see IDF_Scm::inTags() + **/ + public function inTags($commit, $path) + { + $revs = self::_resolveSelector($commit); + if (count($revs) == 0) return array(); + return self::_getUniqueCertValuesFor($revs, "tag"); + } + + /** + * @see IDF_Scm::getTree() + */ + public function getTree($commit, $folder='/', $branch=null) + { + $revs = self::_resolveSelector($commit); + if ($revs != 1) + { + throw new Exception(sprintf( + __('Commit %1$s does not (uniquely) identify a revision.'), + $commit + )); + } + + $cmd = Pluf::f('idf_exec_cmd_prefix', '') + .sprintf("%s -d %s automate get_manifest_of %s", + Pluf::f('mtn_path', 'mtn'), + escapeshellarg($this->repo), + escapeshellarg($revs[0])); + self::exec('IDF_Scm_Monotone::getTree', $cmd, $out, $return); + + $files = array(); + $stanzas = self::parseBasicIO(implode('\n', $out)); + $folder = $folder == '/' || empty($folder) ? '' : $folder.'/'; + + foreach ($stanzas as $stanza) + { + if ($stanza[0]['key'] == "format_version") + continue; + + $path = $stanza[0]['values'][0]; + if (!preg_match('#^'.$folder.'([^/]+)$#', $path, $m)) + continue; + + $file = array(); + $file['file'] = $m[1]; + $file['fullpath'] = $path; + $file['efullpath'] = self::smartEncode($path); + + if ($stanza[0]['key'] == "dir") + $file['type'] == "tree"; + else + $file['type'] == "blob"; + + /* + $file['date'] = gmdate('Y-m-d H:i:s', + strtotime((string) $entry->commit->date)); + $file['rev'] = (string) $entry->commit['revision']; + $file['log'] = $this->getCommitMessage($file['rev']); + // Get the size if the type is blob + if ($file['type'] == 'blob') { + $file['size'] = (string) $entry->size; + } + $file['author'] = (string) $entry->commit->author; + */ + $file['perm'] = ''; + $files[] = (object) $file; + } + return $files; + } + + /** + * Given the string describing the author from the log find the + * author in the database. + * + * @param string Author + * @return mixed Pluf_User or null + */ + public function findAuthor($author) + { + // We extract the email. + $match = array(); + if (!preg_match('/<(.*)>/', $author, $match)) { + return null; + } + foreach (array('email', 'login') as $what) { + $sql = new Pluf_SQL($what.'=%s', array($match[1])); + $users = Pluf::factory('Pluf_User')->getList(array('filter'=>$sql->gen())); + if ($users->count() > 0) { + return $users[0]; + } + } + return null; + } + + public static function getAnonymousAccessUrl($project) + { + return sprintf( + Pluf::f('mtn_remote_url'), + $project->shortname, + Pluf::f('mtn_branch_prefix'), + $project->shortname + ); + } + + public static function getAuthAccessUrl($project, $user) + { + return self::getAnonymousAccessUrl($project); + } + + /** + * Returns this object correctly initialized for the project. + * + * @param IDF_Project + * @return IDF_Scm_Monotone + */ + public static function factory($project) + { + $rep = sprintf(Pluf::f('git_repositories'), $project->shortname); + return new IDF_Scm_Monotone($rep, $project); + } + + public function isValidRevision($commit) + { + $type = $this->testHash($commit); + return ('commit' == $type || 'tag' == $type); + } + + /** + * Test a given object hash. + * + * @param string Object hash. + * @return mixed false if not valid or 'blob', 'tree', 'commit', 'tag' + */ + public function testHash($hash) + { + $cmd = sprintf('GIT_DIR=%s '.Pluf::f('git_path', 'git').' cat-file -t %s', + escapeshellarg($this->repo), + escapeshellarg($hash)); + $ret = 0; $out = array(); + $cmd = Pluf::f('idf_exec_cmd_prefix', '').$cmd; + self::exec('IDF_Scm_Monotone::testHash', $cmd, $out, $ret); + if ($ret != 0) return false; + return trim($out[0]); + } + + /** + * Get the tree info. + * + * @param string Tree hash + * @param bool Do we recurse in subtrees (true) + * @param string Folder in which we want to get the info ('') + * @return array Array of file information. + */ + public function getTreeInfo($tree, $folder='') + { + if (!in_array($this->testHash($tree), array('tree', 'commit', 'tag'))) { + throw new Exception(sprintf(__('Not a valid tree: %s.'), $tree)); + } + $cmd_tmpl = 'GIT_DIR=%s '.Pluf::f('git_path', 'git').' ls-tree -l %s %s'; + $cmd = Pluf::f('idf_exec_cmd_prefix', '') + .sprintf($cmd_tmpl, escapeshellarg($this->repo), + escapeshellarg($tree), escapeshellarg($folder)); + $out = array(); + $res = array(); + self::exec('IDF_Scm_Monotone::getTreeInfo', $cmd, $out); + foreach ($out as $line) { + list($perm, $type, $hash, $size, $file) = preg_split('/ |\t/', $line, 5, PREG_SPLIT_NO_EMPTY); + $res[] = (object) array('perm' => $perm, 'type' => $type, + 'size' => $size, 'hash' => $hash, + 'file' => $file); + } + return $res; + } + + /** + * Get the file info. + * + * @param string File + * @param string Commit ('HEAD') + * @return false Information + */ + public function getPathInfo($totest, $commit='HEAD') + { + $cmd_tmpl = 'GIT_DIR=%s '.Pluf::f('git_path', 'git').' ls-tree -r -t -l %s'; + $cmd = sprintf($cmd_tmpl, + escapeshellarg($this->repo), + escapeshellarg($commit)); + $out = array(); + $cmd = Pluf::f('idf_exec_cmd_prefix', '').$cmd; + self::exec('IDF_Scm_Monotone::getPathInfo', $cmd, $out); + foreach ($out as $line) { + list($perm, $type, $hash, $size, $file) = preg_split('/ |\t/', $line, 5, PREG_SPLIT_NO_EMPTY); + if ($totest == $file) { + $pathinfo = pathinfo($file); + return (object) array('perm' => $perm, 'type' => $type, + 'size' => $size, 'hash' => $hash, + 'fullpath' => $file, + 'file' => $pathinfo['basename']); + } + } + return false; + } + + public function getFile($def, $cmd_only=false) + { + $cmd = sprintf(Pluf::f('idf_exec_cmd_prefix', ''). + 'GIT_DIR=%s '.Pluf::f('git_path', 'git').' cat-file blob %s', + escapeshellarg($this->repo), + escapeshellarg($def->hash)); + return ($cmd_only) + ? $cmd : self::shell_exec('IDF_Scm_Monotone::getFile', $cmd); + } + + /** + * Get commit details. + * + * @param string Commit + * @param bool Get commit diff (false) + * @return array Changes + */ + public function getCommit($commit, $getdiff=false) + { + if ($getdiff) { + $cmd = sprintf('GIT_DIR=%s '.Pluf::f('git_path', 'git').' show --date=iso --pretty=format:%s %s', + escapeshellarg($this->repo), + "'".$this->mediumtree_fmt."'", + escapeshellarg($commit)); + } else { + $cmd = sprintf('GIT_DIR=%s '.Pluf::f('git_path', 'git').' log -1 --date=iso --pretty=format:%s %s', + escapeshellarg($this->repo), + "'".$this->mediumtree_fmt."'", + escapeshellarg($commit)); + } + $out = array(); + $cmd = Pluf::f('idf_exec_cmd_prefix', '').$cmd; + self::exec('IDF_Scm_Monotone::getCommit', $cmd, $out, $ret); + if ($ret != 0 or count($out) == 0) { + return false; + } + if ($getdiff) { + $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 = implode("\n", $change); + } else { + $out = self::parseLog($out); + $out[0]->changes = ''; + } + return $out[0]; + } + + /** + * Check if a commit is big. + * + * @param string Commit ('HEAD') + * @return bool The commit is big + */ + public function isCommitLarge($commit='HEAD') + { + $cmd = sprintf('GIT_DIR=%s '.Pluf::f('git_path', 'git').' log --numstat -1 --pretty=format:%s %s', + escapeshellarg($this->repo), + "'commit %H%n'", + escapeshellarg($commit)); + $out = array(); + $cmd = Pluf::f('idf_exec_cmd_prefix', '').$cmd; + self::exec('IDF_Scm_Monotone::isCommitLarge', $cmd, $out); + $affected = count($out) - 2; + $added = 0; + $removed = 0; + $c=0; + foreach ($out as $line) { + $c++; + if ($c < 3) { + continue; + } + list($a, $r, $f) = preg_split("/[\s]+/", $line, 3, PREG_SPLIT_NO_EMPTY); + $added+=$a; + $removed+=$r; + } + return ($affected > 100 or ($added + $removed) > 20000); + } + + /** + * Get latest changes. + * + * @param string Commit ('HEAD'). + * @param int Number of changes (10). + * @return array Changes. + */ + public function getChangeLog($commit='HEAD', $n=10) + { + if ($n === null) $n = ''; + else $n = ' -'.$n; + $cmd = sprintf('GIT_DIR=%s '.Pluf::f('git_path', 'git').' log%s --date=iso --pretty=format:\'%s\' %s', + escapeshellarg($this->repo), $n, $this->mediumtree_fmt, + escapeshellarg($commit)); + $out = array(); + $cmd = Pluf::f('idf_exec_cmd_prefix', '').$cmd; + self::exec('IDF_Scm_Monotone::getChangeLog', $cmd, $out); + return self::parseLog($out); + } + + /** + * Parse the log lines of a --pretty=medium log output. + * + * @param array Lines. + * @return array Change log. + */ + public static function parseLog($lines) + { + $res = array(); + $c = array(); + $inheads = true; + $next_is_title = false; + foreach ($lines as $line) { + if (preg_match('/^commit (\w{40})$/', $line)) { + if (count($c) > 0) { + $c['full_message'] = trim($c['full_message']); + $c['full_message'] = IDF_Commit::toUTF8($c['full_message']); + $c['title'] = IDF_Commit::toUTF8($c['title']); + $res[] = (object) $c; + } + $c = array(); + $c['commit'] = trim(substr($line, 7, 40)); + $c['full_message'] = ''; + $inheads = true; + $next_is_title = false; + continue; + } + if ($next_is_title) { + $c['title'] = trim($line); + $next_is_title = false; + continue; + } + $match = array(); + if ($inheads and 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 ($inheads and !$next_is_title and $line == '') { + $next_is_title = true; + $inheads = false; + } + if (!$inheads) { + $c['full_message'] .= trim($line)."\n"; + continue; + } + } + $c['full_message'] = !empty($c['full_message']) ? trim($c['full_message']) : ''; + $c['full_message'] = IDF_Commit::toUTF8($c['full_message']); + $c['title'] = IDF_Commit::toUTF8($c['title']); + $res[] = (object) $c; + return $res; + } + + public function getArchiveCommand($commit, $prefix='repository/') + { + return sprintf(Pluf::f('idf_exec_cmd_prefix', ''). + 'GIT_DIR=%s '.Pluf::f('git_path', 'git').' archive --format=zip --prefix=%s %s', + escapeshellarg($this->repo), + escapeshellarg($prefix), + escapeshellarg($commit)); + } +} \ No newline at end of file diff --git a/src/IDF/templates/idf/source/mtn/changelog.html b/src/IDF/templates/idf/source/mtn/changelog.html new file mode 100644 index 0000000..fa92f24 --- /dev/null +++ b/src/IDF/templates/idf/source/mtn/changelog.html @@ -0,0 +1,17 @@ +{extends "idf/source/changelog.html"} +{block context} +

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

+{if $tags} +

{trans 'Tags:'}
+{foreach $tags as $tag => $path} +{aurl 'url', 'IDF_Views_Source::changeLog', array($project.shortname, $tag)} +{if $path}{$path}{else}{$tag}{/if}
+{/foreach} +

+{/if} +{/block} diff --git a/src/IDF/templates/idf/source/mtn/file.html b/src/IDF/templates/idf/source/mtn/file.html new file mode 100644 index 0000000..77b33d8 --- /dev/null +++ b/src/IDF/templates/idf/source/mtn/file.html @@ -0,0 +1,44 @@ +{extends "idf/source/base.html"} +{block extraheader}{/block} +{block docclass}yui-t1{assign $inSourceTree=true}{/block} +{block body} +

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

+ + +{if !$tree_in and !$tags_in} +{aurl 'url', 'IDF_Views_Source::commit', array($project.shortname, $commit)} + + + +{/if} + +{$file} + +
{blocktrans}Source at commit {$commit} created {$cobject.date|dateago}.{/blocktrans}
+{blocktrans}By {$cobject.author|strip_tags|trim}, {$cobject.title}{/blocktrans} +
+{aurl 'url', 'IDF_Views_Source::getFile', array($project.shortname, $commit, $fullpath)} +

{trans 'Archive'} {trans 'Download this file'}

+ +{/block} +{block context} +

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

+{if $tags} +

{trans 'Tags:'}
+{foreach $tags as $tag => $path} +{aurl 'url', 'IDF_Views_Source::treeBase', array($project.shortname, $tag)} +{if $path}{$path}{else}{$tag}{/if}
+{/foreach} +

+{/if} +{/block} + +{block javascript} + + +{/block} diff --git a/src/IDF/templates/idf/source/mtn/help.html b/src/IDF/templates/idf/source/mtn/help.html new file mode 100644 index 0000000..9e72ba9 --- /dev/null +++ b/src/IDF/templates/idf/source/mtn/help.html @@ -0,0 +1,38 @@ +{extends "idf/source/base.html"} +{block docclass}yui-t2{assign $inHelp=true}{/block} +{block body} + +

{blocktrans}The team behind {$project} is using +the git software to manage the source +code.{/blocktrans}

+ +

{trans 'Command-Line Access'}

+ +

git clone {$project.getSourceAccessUrl($user)}

+ +{aurl 'url', 'IDF_Views_User::myAccount'} +

{blocktrans}You may need to provide your SSH key. The synchronization of your SSH key can take a couple of minutes. You can learn more about SSH key authentification.{/blocktrans}

+ +{if $isOwner or $isMember} +

{trans 'First Commit'}

+ +

{blocktrans}To make a first commit in the repository, perform the following steps:{/blocktrans}

+ +
+git init
+git add .
+git commit -m "initial import"
+git remote add origin {$project.getWriteRemoteAccessUrl($url)}
+git push origin master
+
+ +{/if} + +{/block} +{block context} +
+

{blocktrans}Find here more details on how to access {$project} source code.{/blocktrans}

+
+{/block} + + diff --git a/src/IDF/templates/idf/source/mtn/tree.html b/src/IDF/templates/idf/source/mtn/tree.html new file mode 100644 index 0000000..859a249 --- /dev/null +++ b/src/IDF/templates/idf/source/mtn/tree.html @@ -0,0 +1,69 @@ +{extends "idf/source/base.html"} +{block docclass}yui-t1{assign $inSourceTree=true}{/block} +{block body} +

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

+ + + + + + + + + +{if !$tree_in and !$tags_in} +{aurl 'url', 'IDF_Views_Source::commit', array($project.shortname, $commit)} + + + +{/if} +{if $base} + + + + + +{/if} +{foreach $files as $file} +{aurl 'url', 'IDF_Views_Source::tree', array($project.shortname, $commit, $file.efullpath)} + + +{if $file.type != 'extern'} +{$file.file}{else}{/if} +{if $file.type == 'blob'} +{if isset($file.date) and $file.log != '----'} + + +{else}{/if} +{/if} +{if $file.type == 'extern'} + +{/if} + +{/foreach} + +
{trans 'File'}{trans 'Age'}{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.type}{$file.file}{$file.date|dateago:"wihtout"}{$file.author|strip_tags|trim}{trans ':'} {issuetext $file.log, $request, true, false}{$file.size|size}{$file.extern}
+{aurl 'url', 'IDF_Views_Source::download', array($project.shortname, $commit)} +

{trans 'Archive'} {trans 'Download this version'} {trans 'or'} git clone {$project.getSourceAccessUrl($user)} {trans 'Help'}

+ + +{/block} +{block context} +

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

+{if $tags} +

{trans 'Tags:'}
+{foreach $tags as $tag => $path} +{aurl 'url', 'IDF_Views_Source::treeBase', array($project.shortname, $tag)} +{if $path}{$path}{else}{$tag}{/if}
+{/foreach} +

+{/if} +{/block} From 18ba8d0ac55be7dbb09b0ac61d08cffce22cff6b Mon Sep 17 00:00:00 2001 From: Thomas Keller Date: Tue, 27 Apr 2010 00:02:47 +0200 Subject: [PATCH 02/40] Some more files from the initial work --- src/IDF/Form/Admin/ProjectCreate.php | 37 +++++++++-- src/IDF/Project.php | 29 ++++----- src/IDF/Views/Project.php | 21 ++++--- src/IDF/conf/idf.php-dist | 61 +++++++++++-------- .../templates/idf/gadmin/projects/create.html | 19 +++++- src/IDF/templates/idf/source/mtn/help.html | 15 ++--- 6 files changed, 116 insertions(+), 66 deletions(-) diff --git a/src/IDF/Form/Admin/ProjectCreate.php b/src/IDF/Form/Admin/ProjectCreate.php index 182c737..7f767e5 100644 --- a/src/IDF/Form/Admin/ProjectCreate.php +++ b/src/IDF/Form/Admin/ProjectCreate.php @@ -38,6 +38,7 @@ class IDF_Form_Admin_ProjectCreate extends Pluf_Form 'git' => __('git'), 'svn' => __('Subversion'), 'mercurial' => __('mercurial'), + 'mtn' => __('monotone'), ); foreach (Pluf::f('allowed_scm', array()) as $key => $class) { $choices[$options[$key]] = $key; @@ -92,6 +93,14 @@ class IDF_Form_Admin_ProjectCreate extends Pluf_Form 'widget' => 'Pluf_Form_Widget_PasswordInput', )); + $this->fields['mtn_master_branch'] = new Pluf_Form_Field_Varchar( + array('required' => false, + 'label' => __('Master branch'), + 'initial' => '', + 'widget' => 'Pluf_Form_Widget_Input', + 'help_text' => __('This should be a world-wide unique identifier for your project. A reverse DNS notation like "com.my-domain.my-project" is a good idea.'), + )); + $this->fields['owners'] = new Pluf_Form_Field_Varchar( array('required' => false, 'label' => __('Project owners'), @@ -156,6 +165,21 @@ class IDF_Form_Admin_ProjectCreate extends Pluf_Form return $url; } + public function clean_mtn_master_branch() + { + $mtn_master_branch = mb_strtolower($this->cleaned_data['mtn_master_branch']); + if (!preg_match('/^([\w\d]+([-][\w\d]+)*)(\.[\w\d]+([-][\w\d]+)*)*$/', $mtn_master_branch)) { + throw new Pluf_Form_Invalid(__('This master branch contains illegal characters, please use only letters, digits, dashs and dots as separators.')); + } + + $sql = new Pluf_SQL('vkey=%s AND vdesc=%s', array("mtn_master_branch", $mtn_master_branch)); + $l = Pluf::factory('IDF_Conf')->getList(array('filter'=>$sql->gen())); + if ($l->count() > 0) { + throw new Pluf_Form_Invalid(__('This master branch is already used. Please select another one.')); + } + return $mtn_master_branch; + } + public function clean_shortname() { $shortname = mb_strtolower($this->cleaned_data['shortname']); @@ -184,6 +208,11 @@ class IDF_Form_Admin_ProjectCreate extends Pluf_Form $this->cleaned_data[$key] = ''; } } + + if ($this->cleaned_data['scm'] != 'mtn') { + $this->cleaned_data['mtn_master_branch'] = ''; + } + /** * [signal] * @@ -222,15 +251,15 @@ class IDF_Form_Admin_ProjectCreate extends Pluf_Form $project->create(); $conf = new IDF_Conf(); $conf->setProject($project); - $keys = array('scm', 'svn_remote_url', - 'svn_username', 'svn_password'); + $keys = array('scm', 'svn_remote_url', 'svn_username', + 'svn_password', 'mtn_master_branch'); foreach ($keys as $key) { - $this->cleaned_data[$key] = (!empty($this->cleaned_data[$key])) ? + $this->cleaned_data[$key] = (!empty($this->cleaned_data[$key])) ? $this->cleaned_data[$key] : ''; $conf->setVal($key, $this->cleaned_data[$key]); } $project->created(); - IDF_Form_MembersConf::updateMemberships($project, + IDF_Form_MembersConf::updateMemberships($project, $this->cleaned_data); $project->membershipsUpdated(); return $project; diff --git a/src/IDF/Project.php b/src/IDF/Project.php index d9895db..762bbde 100644 --- a/src/IDF/Project.php +++ b/src/IDF/Project.php @@ -39,7 +39,7 @@ class IDF_Project extends Pluf_Model * * @see self::isRestricted */ - protected $_isRestricted = null; + protected $_isRestricted = null; function init() { @@ -52,7 +52,7 @@ class IDF_Project extends Pluf_Model 'id' => array( 'type' => 'Pluf_DB_Field_Sequence', - 'blank' => true, + 'blank' => true, ), 'name' => array( @@ -113,7 +113,7 @@ class IDF_Project extends Pluf_Model return ''; } - + function preSave($create=false) { if ($this->id == '') { @@ -181,7 +181,7 @@ class IDF_Project extends Pluf_Model */ public function getTagIdsByStatus($status='open', $cache_refresh=false) { - if (!$cache_refresh + if (!$cache_refresh and isset($this->_extra_cache['getTagIdsByStatus-'.$status])) { return $this->_extra_cache['getTagIdsByStatus-'.$status]; } @@ -197,7 +197,7 @@ class IDF_Project extends Pluf_Model break; } $tags = array(); - foreach ($this->getTagsFromConfig($key, $default, 'Status') as $tag) { + foreach ($this->getTagsFromConfig($key, $default, 'Status') as $tag) { $tags[] = (int) $tag->id; } $this->_extra_cache['getTagIdsByStatus-'.$status] = $tags; @@ -289,9 +289,9 @@ class IDF_Project extends Pluf_Model if ($fmt == 'objects') { return new Pluf_Template_ContextVars(array('members' => $members, 'owners' => $owners, 'authorized' => $authorized)); } else { - return array('members' => implode("\n", (array) $members), + return array('members' => implode("\n", (array) $members), 'owners' => implode("\n", (array) $owners), - 'authorized' => implode("\n", (array) $authorized), + 'authorized' => implode("\n", (array) $authorized), ); } } @@ -385,7 +385,7 @@ class IDF_Project extends Pluf_Model public function getSourceAccessUrl($user=null) { $right = $this->getConf()->getVal('source_access_rights', 'all'); - if (($user == null or $user->isAnonymous()) + if (($user == null or $user->isAnonymous()) and $right == 'all' and !$this->private) { return $this->getRemoteAccessUrl(); } @@ -433,9 +433,10 @@ class IDF_Project extends Pluf_Model { $conf = $this->getConf(); $roots = array( - 'git' => 'master', - 'svn' => 'HEAD', - 'mercurial' => 'tip' + 'git' => 'master', + 'svn' => 'HEAD', + 'mercurial' => 'tip', + 'mtn' => 'h:', ); $scm = $conf->getVal('scm', 'git'); return $roots[$scm]; @@ -448,7 +449,7 @@ class IDF_Project extends Pluf_Model * By convention, all the objects belonging to a project have the * 'project' property set, so this is easy to check. * - * @param Pluf_Model + * @param Pluf_Model */ public function inOr404($obj) { @@ -505,7 +506,7 @@ class IDF_Project extends Pluf_Model * * [description] * - * This signal allows an application to update the statistics + * This signal allows an application to update the statistics * array of a project. For example to add the on disk size * of the repository if available. * @@ -649,7 +650,7 @@ class IDF_Project extends Pluf_Model ); $conf = $this->getConf(); foreach ($tabs as $tab) { - if (!in_array($conf->getVal($tab, 'all'), + if (!in_array($conf->getVal($tab, 'all'), array('all', 'none'))) { $this->_isRestricted = true; return true; diff --git a/src/IDF/Views/Project.php b/src/IDF/Views/Project.php index d66e0ea..edee410 100644 --- a/src/IDF/Views/Project.php +++ b/src/IDF/Views/Project.php @@ -44,12 +44,12 @@ class IDF_Views_Project if ($request->rights['hasDownloadsAccess']) { $tags = IDF_Views_Download::getDownloadTags($prj); // the first tag is the featured, the last is the deprecated. - $downloads = $tags[0]->get_idf_upload_list(); + $downloads = $tags[0]->get_idf_upload_list(); } $pages = array(); if ($request->rights['hasWikiAccess']) { $tags = IDF_Views_Wiki::getWikiTags($prj); - $pages = $tags[0]->get_idf_wikipage_list(); + $pages = $tags[0]->get_idf_wikipage_list(); } return Pluf_Shortcuts_RenderToResponse('idf/project/home.html', array( @@ -100,7 +100,7 @@ class IDF_Views_Project $rights[] = '\'IDF_Dummy\''; } $sql = sprintf('model_class IN (%s)', implode(', ', $rights)); - $pag->forced_where = new Pluf_SQL('project=%s AND '.$sql, + $pag->forced_where = new Pluf_SQL('project=%s AND '.$sql, array($prj->id)); $pag->sort_order = array('creation_dtime', 'ASC'); $pag->sort_reverse_order = array('creation_dtime'); @@ -117,16 +117,16 @@ class IDF_Views_Project if ($request->rights['hasDownloadsAccess']) { $tags = IDF_Views_Download::getDownloadTags($prj); // the first tag is the featured, the last is the deprecated. - $downloads = $tags[0]->get_idf_upload_list(); + $downloads = $tags[0]->get_idf_upload_list(); } $pages = array(); if ($request->rights['hasWikiAccess']) { $tags = IDF_Views_Wiki::getWikiTags($prj); - $pages = $tags[0]->get_idf_wikipage_list(); + $pages = $tags[0]->get_idf_wikipage_list(); } if (!$request->user->isAnonymous() and $prj->isRestricted()) { $feedurl = Pluf_HTTP_URL_urlForView('idf_project_timeline_feed_auth', - array($prj->shortname, + array($prj->shortname, IDF_Precondition::genFeedToken($prj, $request->user))); } else { $feedurl = Pluf_HTTP_URL_urlForView('idf_project_timeline_feed', @@ -188,7 +188,7 @@ class IDF_Views_Project 'nb' => 20, ); $items = Pluf::factory('IDF_Timeline')->getList($params); - $set = new Pluf_Model_Set($items, + $set = new Pluf_Model_Set($items, array('public_dtime' => 'public_dtime')); $out = array(); foreach ($set as $item) { @@ -207,7 +207,7 @@ class IDF_Views_Project $feedurl = Pluf::f('url_base').Pluf::f('idf_base').$request->query; $viewurl = Pluf_HTTP_URL_urlForView('IDF_Views_Project::timeline', array($prj->shortname)); - $context = new Pluf_Template_Context_Request($request, + $context = new Pluf_Template_Context_Request($request, array('body' => $out, 'date' => $date, 'title' => $title, @@ -235,7 +235,7 @@ class IDF_Views_Project if ($form->isValid()) { $prj = $form->save(); $request->user->setMessage(__('The project has been updated.')); - $url = Pluf_HTTP_URL_urlForView('IDF_Views_Project::admin', + $url = Pluf_HTTP_URL_urlForView('IDF_Views_Project::admin', array($prj->shortname)); return new Pluf_HTTP_Response_Redirect($url); } @@ -445,7 +445,7 @@ class IDF_Views_Project } else { $params = array(); $keys = array('downloads_access_rights', 'source_access_rights', - 'issues_access_rights', 'review_access_rights', + 'issues_access_rights', 'review_access_rights', 'wiki_access_rights', 'downloads_notification_email', 'review_notification_email', @@ -519,6 +519,7 @@ class IDF_Views_Project 'git' => __('git'), 'svn' => __('Subversion'), 'mercurial' => __('mercurial'), + 'mtn' => __('monotone'), ); $repository_type = $options[$scm]; return Pluf_Shortcuts_RenderToResponse('idf/admin/source.html', diff --git a/src/IDF/conf/idf.php-dist b/src/IDF/conf/idf.php-dist index 36d69b2..412826d 100644 --- a/src/IDF/conf/idf.php-dist +++ b/src/IDF/conf/idf.php-dist @@ -27,14 +27,14 @@ $cfg = array(); # You must set them to false once everything is running ok. # $cfg['debug'] = true; -# It will help you catch errors at beginning when configuring your +# It will help you catch errors at beginning when configuring your # SCM backend. It must be turned off in production. -$cfg['debug_scm'] = false; +$cfg['debug_scm'] = false; # -# Note: By default, InDefero will not manage the repositories for -# you, you can enable the repositories management with the -# built-in plugins. The documentation of the plugins is available +# Note: By default, InDefero will not manage the repositories for +# you, you can enable the repositories management with the +# built-in plugins. The documentation of the plugins is available # in the `doc/` folder. # @@ -44,9 +44,9 @@ $cfg['debug_scm'] = false; # For example: '/path/to/my/project/.git' # # If you have multiple repositories, you need to put %s where you -# want the shortname of the project to be replaced. +# want the shortname of the project to be replaced. # For example: -# - You have many projects on your local computer and want to use +# - You have many projects on your local computer and want to use # InDefero to see them. Put: '/home/yourlogin/Projects/%s/.git' # - You have many projects on a remote server with only "bare" git # repositories. Put: '/home/git/repositories/%s.git' @@ -64,7 +64,7 @@ $cfg['git_remote_url'] = 'git://localhost/%s.git'; $cfg['git_write_remote_url'] = 'git@localhost:%s.git'; # Same as for git, you can have multiple repositories, one for each -# project or a single one for all the projects. +# project or a single one for all the projects. # # In the case of subversion, the admin of a project can also select a # remote repository from the web interface. From the web interface @@ -73,6 +73,12 @@ $cfg['git_write_remote_url'] = 'git@localhost:%s.git'; $cfg['svn_repositories'] = 'file:///home/svn/repositories/%s'; $cfg['svn_remote_url'] = 'http://localhost/svn/%s'; +# Same as for git, you can have multiple repositories, one for each +# project or a single one for all the projects. +$cfg['mtn_repositories'] = '/home/mtn/repositories/%s.mtn'; +$cfg['mtn_branch_prefix'] = 'com.indefero.projects.'; +$cfg['mtn_remote_url'] = 'mtn://localhost/~%s/%s%s'; + # Mercurial repositories path #$cfg['mercurial_repositories'] = '/home/mercurial/repositories/%s'; #$cfg['mercurial_remote_url'] = 'http://projects.ceondo.com/hg/%s'; @@ -90,15 +96,15 @@ $cfg['mail_host'] = 'localhost'; $cfg['mail_port'] = 25; # Paths/Url configuration. -# +# # Examples: -# You have: +# You have: # http://www.mydomain.com/myfolder/index.php # Put: # $cfg['idf_base'] = '/myfolder/index.php'; # $cfg['url_base'] = 'http://www.mydomain.com'; # -# You have mod_rewrite: +# You have mod_rewrite: # http://www.mydomain.com/ # Put: # $cfg['idf_base'] = ''; @@ -109,7 +115,7 @@ $cfg['mail_port'] = 25; $cfg['idf_base'] = '/index.php'; $cfg['url_base'] = 'http://localhost'; -# Url to access the media folder which is in the www folder +# Url to access the media folder which is in the www folder # of the archive $cfg['url_media'] = 'http://localhost/media'; @@ -120,9 +126,9 @@ $cfg['url_upload'] = 'http://localhost/media/upload'; $cfg['upload_path'] = '/home/www/indefero/www/media/upload'; # -# The following path *MUST NOT* be accessible through a web browser -# as user will be able to upload .html, .php files and this can -# create *TERRIBLE* security issues. In this folder, the attachments +# The following path *MUST NOT* be accessible through a web browser +# as user will be able to upload .html, .php files and this can +# create *TERRIBLE* security issues. In this folder, the attachments # to the issues will be uploaded and we do not restrict the content type. # $cfg['upload_issue_path'] = '/home/www/indefero/attachments'; @@ -130,10 +136,10 @@ $cfg['upload_issue_path'] = '/home/www/indefero/attachments'; # # write here a long random string unique for this installation. This # is critical to put a long string, with at least 40 characters. -$cfg['secret_key'] = ''; +$cfg['secret_key'] = ''; # the sender of all the emails. -$cfg['from_email'] = 'sender@example.com'; +$cfg['from_email'] = 'sender@example.com'; # Email address for the bounced messages. $cfg['bounce_email'] = 'no-reply@example.com'; @@ -150,22 +156,22 @@ $cfg['db_password'] = ''; $cfg['db_server'] = ''; $cfg['db_version'] = '5.1'; # Only needed for MySQL # If you want to have different installations with the same DB -$cfg['db_table_prefix'] = 'indefero_'; -# ** DO NOT USE SQLITE IN PRODUCTION ** +$cfg['db_table_prefix'] = 'indefero_'; +# ** DO NOT USE SQLITE IN PRODUCTION ** # This is not because of problems with the quality of the SQLite # driver or with SQLite itself, this is due to the lack of migration # support in Pluf for SQLite, this means we cannot modify the DB # easily once it is loaded with data. $cfg['db_engine'] = 'PostgreSQL'; # SQLite is also well tested or MySQL $cfg['db_database'] = 'website'; # put absolute path to the db if you - # are using SQLite. + # are using SQLite. # # The extension of the downloads are limited. You can add extra # extensions here. The list must start with a space. # $cfg['idf_extra_upload_ext'] = ' ext1 ext2'; # # By default, the size of the downloads is limited to 2MB. -# The php.ini upload_max_filesize configuration setting will +# The php.ini upload_max_filesize configuration setting will # always have precedence. # $cfg['max_upload_size'] = 2097152; // Size in bytes @@ -203,12 +209,13 @@ $cfg['template_context_processors'] = array('IDF_Middleware_ContextPreProcessor' $cfg['idf_views'] = dirname(__FILE__).'/urls.php'; # available languages -$cfg['languages'] = array('en', 'fr'); +$cfg['languages'] = array('en', 'fr'); # SCM base configuration $cfg['allowed_scm'] = array('git' => 'IDF_Scm_Git', 'svn' => 'IDF_Scm_Svn', 'mercurial' => 'IDF_Scm_Mercurial', + 'mtn' => 'IDF_Scm_Monotone', ); # If you want to use another memtypes database @@ -218,8 +225,8 @@ $cfg['allowed_scm'] = array('git' => 'IDF_Scm_Git', # $cfg['idf_extra_text_ext'] = 'ext1 ext2 ext3'; # If you can execute the shell commands executed to get info -# from the scm with the user of your PHP process but it is -# not working from within PHP, this can be due to the environment +# from the scm with the user of your PHP process but it is +# not working from within PHP, this can be due to the environment # variables not being set correctly. Note the trailing space. # $cfg['idf_exec_cmd_prefix'] = '/usr/bin/env -i '; @@ -229,10 +236,10 @@ $cfg['allowed_scm'] = array('git' => 'IDF_Scm_Git', # To know which path you need to provide, just run: # $ which git # from the command line. This will give you the path to git. -# $cfg['svn_path'] = 'svn'; -# $cfg['svnlook_path'] = 'svnlook'; +# $cfg['svn_path'] = 'svn'; +# $cfg['svnlook_path'] = 'svnlook'; # $cfg['svnadmin_path'] = 'svnadmin'; # $cfg['hg_path'] = 'hg'; -# $cfg['git_path'] = 'git'; +# $cfg['git_path'] = 'git'; return $cfg; diff --git a/src/IDF/templates/idf/gadmin/projects/create.html b/src/IDF/templates/idf/gadmin/projects/create.html index 1affd7d..d542e7e 100644 --- a/src/IDF/templates/idf/gadmin/projects/create.html +++ b/src/IDF/templates/idf/gadmin/projects/create.html @@ -52,6 +52,13 @@ {$form.f.svn_password|unsafe} + +{$form.f.mtn_master_branch.labelTag}: +{if $form.f.mtn_master_branch.errors}{$form.f.mtn_master_branch.fieldErrors}{/if} +{$form.f.mtn_master_branch|unsafe}
+{$form.f.mtn_master_branch.help_text} + + {$form.f.owners.labelTag}: @@ -76,7 +83,7 @@   - + @@ -112,12 +119,22 @@ $(document).ready(function() { if ($("#id_scm option:selected").val() != "svn") { $(".svn-form").hide(); } + // Hide if not mtn + if ($("#id_scm option:selected").val() != "mtn") { + $(".mtn-form").hide(); + } $("#id_scm").change(function () { if ($("#id_scm option:selected").val() == "svn") { $(".svn-form").show(); } else { $(".svn-form").hide(); } + if ($("#id_scm option:selected").val() == "mtn") { + $(".mtn-form").show(); + } else { + $(".mtn-form").hide(); + } + }); }); diff --git a/src/IDF/templates/idf/source/mtn/help.html b/src/IDF/templates/idf/source/mtn/help.html index 9e72ba9..34d0e83 100644 --- a/src/IDF/templates/idf/source/mtn/help.html +++ b/src/IDF/templates/idf/source/mtn/help.html @@ -3,15 +3,12 @@ {block body}

{blocktrans}The team behind {$project} is using -the git software to manage the source +the monotone software to manage the source code.{/blocktrans}

{trans 'Command-Line Access'}

-

git clone {$project.getSourceAccessUrl($user)}

- -{aurl 'url', 'IDF_Views_User::myAccount'} -

{blocktrans}You may need to provide your SSH key. The synchronization of your SSH key can take a couple of minutes. You can learn more about SSH key authentification.{/blocktrans}

+

mtn clone {$project.getSourceAccessUrl()}

{if $isOwner or $isMember}

{trans 'First Commit'}

@@ -19,11 +16,9 @@ code.{/blocktrans}

{blocktrans}To make a first commit in the repository, perform the following steps:{/blocktrans}

-git init
-git add .
-git commit -m "initial import"
-git remote add origin {$project.getWriteRemoteAccessUrl($url)}
-git push origin master
+mtn add -R .
+mtn commit -m "initial import"
+mtn push {$project.getSourceAccessUrl()}
 
{/if} From 94a5464155aa03030712a95ca25fd8e50e8b810a Mon Sep 17 00:00:00 2001 From: Thomas Keller Date: Tue, 27 Apr 2010 23:28:52 +0200 Subject: [PATCH 03/40] * idf.php-dist: no need to configure a branch prefix any longer now that the project owner can define the master branch name * Monotone.php: change accordingly to use the configured master branch name and fallback to all branches ("*") if noone is found --- src/IDF/Scm/Monotone.php | 8 ++++++-- src/IDF/conf/idf.php-dist | 3 +-- 2 files changed, 7 insertions(+), 4 deletions(-) diff --git a/src/IDF/Scm/Monotone.php b/src/IDF/Scm/Monotone.php index 8c8b83f..160bf75 100644 --- a/src/IDF/Scm/Monotone.php +++ b/src/IDF/Scm/Monotone.php @@ -366,11 +366,15 @@ class IDF_Scm_Monotone extends IDF_Scm public static function getAnonymousAccessUrl($project) { + $conf = $project->getConf(); + if (false === ($branch = $conf->getVal('mtn_master_branch', false)) + || empty($branch)) { + $branch = "*"; + } return sprintf( Pluf::f('mtn_remote_url'), $project->shortname, - Pluf::f('mtn_branch_prefix'), - $project->shortname + $branch ); } diff --git a/src/IDF/conf/idf.php-dist b/src/IDF/conf/idf.php-dist index 412826d..6f46f17 100644 --- a/src/IDF/conf/idf.php-dist +++ b/src/IDF/conf/idf.php-dist @@ -76,8 +76,7 @@ $cfg['svn_remote_url'] = 'http://localhost/svn/%s'; # Same as for git, you can have multiple repositories, one for each # project or a single one for all the projects. $cfg['mtn_repositories'] = '/home/mtn/repositories/%s.mtn'; -$cfg['mtn_branch_prefix'] = 'com.indefero.projects.'; -$cfg['mtn_remote_url'] = 'mtn://localhost/~%s/%s%s'; +$cfg['mtn_remote_url'] = 'mtn://localhost/~%s/%s'; # Mercurial repositories path #$cfg['mercurial_repositories'] = '/home/mercurial/repositories/%s'; From 5ef6e6c08f6d6d43a0f5f7f87e5f940ba4cf7334 Mon Sep 17 00:00:00 2001 From: Thomas Keller Date: Wed, 28 Apr 2010 00:13:42 +0200 Subject: [PATCH 04/40] mtn still needs a local db and project setup if there is nothing beside an empty database remotely --- src/IDF/templates/idf/source/mtn/help.html | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/IDF/templates/idf/source/mtn/help.html b/src/IDF/templates/idf/source/mtn/help.html index 34d0e83..e915cb5 100644 --- a/src/IDF/templates/idf/source/mtn/help.html +++ b/src/IDF/templates/idf/source/mtn/help.html @@ -16,6 +16,8 @@ code.{/blocktrans}

{blocktrans}To make a first commit in the repository, perform the following steps:{/blocktrans}

+mtn db init -d project.mtn
+mtn setup -d project.mtn -b {$project.getConf().getVal('mtn_master_branch', 'your-branch')} .
 mtn add -R .
 mtn commit -m "initial import"
 mtn push {$project.getSourceAccessUrl()}

From 5954cd0ad115397804ab48b9a9553c12266e500d Mon Sep 17 00:00:00 2001
From: Thomas Keller 
Date: Wed, 28 Apr 2010 00:14:19 +0200
Subject: [PATCH 05/40] default to the master branch for the head / tip / main
 revision

---
 src/IDF/Project.php | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/src/IDF/Project.php b/src/IDF/Project.php
index 762bbde..b1dfb76 100644
--- a/src/IDF/Project.php
+++ b/src/IDF/Project.php
@@ -436,7 +436,7 @@ class IDF_Project extends Pluf_Model
                        'git' => 'master',
                        'svn' => 'HEAD',
                        'mercurial' => 'tip',
-                       'mtn' => 'h:',
+                       'mtn' => 'h:'.$conf->getVal('mtn_master_branch', '*'),
                        );
         $scm = $conf->getVal('scm', 'git');
         return $roots[$scm];

From f8012c37d1d8b105d4cd91ad741b0ba86082a137 Mon Sep 17 00:00:00 2001
From: Thomas Keller 
Date: Wed, 28 Apr 2010 01:12:29 +0200
Subject: [PATCH 06/40] from monotone 0.48 onwards the setup command creates
 its own internal database

---
 src/IDF/templates/idf/source/mtn/help.html | 3 +--
 1 file changed, 1 insertion(+), 2 deletions(-)

diff --git a/src/IDF/templates/idf/source/mtn/help.html b/src/IDF/templates/idf/source/mtn/help.html
index e915cb5..6b2fd0c 100644
--- a/src/IDF/templates/idf/source/mtn/help.html
+++ b/src/IDF/templates/idf/source/mtn/help.html
@@ -16,8 +16,7 @@ code.{/blocktrans}

{blocktrans}To make a first commit in the repository, perform the following steps:{/blocktrans}

-mtn db init -d project.mtn
-mtn setup -d project.mtn -b {$project.getConf().getVal('mtn_master_branch', 'your-branch')} .
+mtn setup -b {$project.getConf().getVal('mtn_master_branch', 'your-branch')} .
 mtn add -R .
 mtn commit -m "initial import"
 mtn push {$project.getSourceAccessUrl()}

From af4f5aaeb09abff77df8dd671bd74cefcbdcb109 Mon Sep 17 00:00:00 2001
From: Thomas Keller 
Date: Thu, 29 Apr 2010 01:37:28 +0200
Subject: [PATCH 07/40] make the path to the monotone executable configurable

---
 src/IDF/conf/idf.php-dist | 1 +
 1 file changed, 1 insertion(+)

diff --git a/src/IDF/conf/idf.php-dist b/src/IDF/conf/idf.php-dist
index 6f46f17..49f7e2d 100644
--- a/src/IDF/conf/idf.php-dist
+++ b/src/IDF/conf/idf.php-dist
@@ -75,6 +75,7 @@ $cfg['svn_remote_url'] = 'http://localhost/svn/%s';
 
 # Same as for git, you can have multiple repositories, one for each
 # project or a single one for all the projects.
+$cfg['mtn_path'] = 'mtn';
 $cfg['mtn_repositories'] = '/home/mtn/repositories/%s.mtn';
 $cfg['mtn_remote_url'] = 'mtn://localhost/~%s/%s';
 

From 02603fd8fd8b951ee7b6296b29cf877f5e28919d Mon Sep 17 00:00:00 2001
From: Thomas Keller 
Date: Thu, 29 Apr 2010 01:42:50 +0200
Subject: [PATCH 08/40] disable archive generation for now, this is not
 possible with monotone as it is implemented now in IDF

---
 src/IDF/templates/idf/source/mtn/tree.html | 9 ++++++---
 1 file changed, 6 insertions(+), 3 deletions(-)

diff --git a/src/IDF/templates/idf/source/mtn/tree.html b/src/IDF/templates/idf/source/mtn/tree.html
index 859a249..8dfdb18 100644
--- a/src/IDF/templates/idf/source/mtn/tree.html
+++ b/src/IDF/templates/idf/source/mtn/tree.html
@@ -12,7 +12,7 @@
 {trans 'Size'}
 
 {if !$tree_in and !$tags_in}
-{aurl 'url', 'IDF_Views_Source::commit', array($project.shortname, $commit)} 
+{aurl 'url', 'IDF_Views_Source::commit', array($project.shortname, $commit)}
 
 {blocktrans}Source at commit {$commit} created {$cobject.date|dateago}.{/blocktrans}
{blocktrans}By {$cobject.author|strip_tags|trim}, {$cobject.title}{/blocktrans} @@ -35,7 +35,7 @@ {$file.file}{else}{$file.file}{/if} {if $file.type == 'blob'} {if isset($file.date) and $file.log != '----'} -{$file.date|dateago:"wihtout"} +{$file.date|dateago:"wihtout"} {$file.author|strip_tags|trim}{trans ':'} {issuetext $file.log, $request, true, false} {else}{/if} {$file.size|size}{/if} @@ -47,7 +47,10 @@ {aurl 'url', 'IDF_Views_Source::download', array($project.shortname, $commit)} -

{trans 'Archive'} {trans 'Download this version'} {trans 'or'} git clone {$project.getSourceAccessUrl($user)} {trans 'Help'}

+

+{* {trans 'Archive'} {trans 'Download this version'} {trans 'or'} *} +mtn clone {$project.getSourceAccessUrl($user)} {trans 'Help'} +

{/block} From cf22909722bb54602bc8bca2e71737f17351ee5e Mon Sep 17 00:00:00 2001 From: Thomas Keller Date: Thu, 29 Apr 2010 01:44:34 +0200 Subject: [PATCH 09/40] * isAvailable(): check monotone's interface version and mark the interface as available if it matches (we might see later on if this alone is actually a good idea especially if we browse an empty database...) * _getCerts(): implement a cert cache and make multiple cert values easily available * getCommit(), getCommitLarge(), getFile(), getPathInfo(), testHash(): implement * getTags(): save the first found revision id for a tag as key in the associative array to make tags actually browsable --- src/IDF/Scm/Monotone.php | 524 +++++++++++++++++++++------------------ 1 file changed, 283 insertions(+), 241 deletions(-) diff --git a/src/IDF/Scm/Monotone.php b/src/IDF/Scm/Monotone.php index 160bf75..d93bce1 100644 --- a/src/IDF/Scm/Monotone.php +++ b/src/IDF/Scm/Monotone.php @@ -27,7 +27,7 @@ */ class IDF_Scm_Monotone extends IDF_Scm { - public $mediumtree_fmt = 'commit %H%nAuthor: %an <%ae>%nTree: %T%nDate: %ai%n%n%s%n%n%b'; + public static $MIN_INTERFACE_VERSION = 12.0; /* ============================================== * * * @@ -56,12 +56,19 @@ class IDF_Scm_Monotone extends IDF_Scm public function isAvailable() { + $out = array(); try { - $branches = $this->getBranches(); + $cmd = Pluf::f('idf_exec_cmd_prefix', '') + .sprintf("%s -d %s automate interface_version", + Pluf::f('mtn_path', 'mtn'), + escapeshellarg($this->repo)); + self::exec('IDF_Scm_Monotone::isAvailable', + $cmd, $out, $return); } catch (IDF_Scm_Exception $e) { return false; } - return (count($branches) > 0); + + return count($out) > 0 && floatval($out[0]) >= self::$MIN_INTERFACE_VERSION; } public function getBranches() @@ -112,7 +119,7 @@ class IDF_Scm_Monotone extends IDF_Scm * @param string $selector * @return array */ - private static function _resolveSelector($selector) + private function _resolveSelector($selector) { $cmd = Pluf::f('idf_exec_cmd_prefix', '') .sprintf("%s -d %s automate select %s", @@ -132,6 +139,9 @@ class IDF_Scm_Monotone extends IDF_Scm */ private static function _parseBasicIO($in) { + if (substr($in, -1) != "\n") + $in .= "\n"; + $pos = 0; $stanzas = array(); @@ -192,49 +202,102 @@ class IDF_Scm_Monotone extends IDF_Scm return $stanzas; } - private static function _getUniqueCertValuesFor($revs, $certName) + private function _getCerts($rev) { - $certValues = array(); - foreach ($revs as $rev) + static $certCache = array(); + + if (!array_key_exists($rev, $certCache)) { $cmd = Pluf::f('idf_exec_cmd_prefix', '') .sprintf("%s -d %s automate certs %s", Pluf::f('mtn_path', 'mtn'), escapeshellarg($this->repo), escapeshellarg($rev)); - self::exec('IDF_Scm_Monotone::inBranches', + self::exec('IDF_Scm_Monotone::_getCerts', $cmd, $out, $return); - $stanzas = self::_parseBasicIO(implode('\n', $out)); + $stanzas = self::_parseBasicIO(implode("\n", $out)); + $certs = array(); foreach ($stanzas as $stanza) { + $certname = null; foreach ($stanza as $stanzaline) { // luckily, name always comes before value - if ($stanzaline['key'] == "name" && - $stanzaline['values'][0] != $certName) + if ($stanzaline['key'] == "name") { - break; + $certname = $stanzaline['values'][0]; + continue; } + if ($stanzaline['key'] == "value") { - $certValues[] = $stanzaline['values'][0]; + if (!array_key_exists($certname, $certs)) + { + $certs[$certname] = array(); + } + + $certs[$certname][] = $stanzaline['values'][0]; break; } } } + $certCache[$rev] = $certs; + } + + return $certCache[$rev]; + } + + private function _getUniqueCertValuesFor($revs, $certName) + { + $certValues = array(); + foreach ($revs as $rev) + { + $certs = $this->_getCerts($rev); + if (!array_key_exists($certName, $certs)) + continue; + + $certValues = array_merge($certValues, $certs[$certName]); } return array_unique($certValues); } + private function _getLastChangeFor($file, $startrev) + { + $cmd = Pluf::f('idf_exec_cmd_prefix', '') + .sprintf("%s -d %s automate get_content_changed %s %s", + Pluf::f('mtn_path', 'mtn'), + escapeshellarg($this->repo), + escapeshellarg($startrev), + escapeshellarg($file)); + self::exec('IDF_Scm_Monotone::_getLastChangeFor', + $cmd, $out, $return); + + $stanzas = self::_parseBasicIO(implode("\n", $out)); + + // FIXME: we only care about the first returned content mark + // everything else seem to be very rare cases + foreach ($stanzas as $stanza) + { + foreach ($stanza as $stanzaline) + { + if ($stanzaline['key'] == "content_mark") + { + return $stanzaline['hash']; + } + } + } + return null; + } + /** * @see IDF_Scm::inBranches() **/ public function inBranches($commit, $path) { - $revs = self::_resolveSelector($commit); + $revs = $this->_resolveSelector($commit); if (count($revs) == 0) return array(); - return self::_getUniqueCertValuesFor($revs, "branch"); + return $this->_getUniqueCertValuesFor($revs, "branch"); } /** @@ -252,14 +315,21 @@ class IDF_Scm_Monotone extends IDF_Scm self::exec('IDF_Scm_Monotone::getTags', $cmd, $out, $return); $tags = array(); - $stanzas = self::parseBasicIO(implode('\n', $out)); + $stanzas = self::_parseBasicIO(implode("\n", $out)); foreach ($stanzas as $stanza) { + $tagname = null; foreach ($stanza as $stanzaline) { + // revision comes directly after the tag stanza if ($stanzaline['key'] == "tag") { - $tags[] = $stanzaline['values'][0]; + $tagname = $stanzaline['values'][0]; + continue; + } + if ($stanzaline['key'] == "revision") + { + $tags[$stanzaline['hash']] = $tagname; break; } } @@ -274,9 +344,9 @@ class IDF_Scm_Monotone extends IDF_Scm **/ public function inTags($commit, $path) { - $revs = self::_resolveSelector($commit); + $revs = $this->_resolveSelector($commit); if (count($revs) == 0) return array(); - return self::_getUniqueCertValuesFor($revs, "tag"); + return $this->_getUniqueCertValuesFor($revs, "tag"); } /** @@ -284,13 +354,10 @@ class IDF_Scm_Monotone extends IDF_Scm */ public function getTree($commit, $folder='/', $branch=null) { - $revs = self::_resolveSelector($commit); - if ($revs != 1) + $revs = $this->_resolveSelector($commit); + if (count($revs) == 0) { - throw new Exception(sprintf( - __('Commit %1$s does not (uniquely) identify a revision.'), - $commit - )); + return array(); } $cmd = Pluf::f('idf_exec_cmd_prefix', '') @@ -301,7 +368,7 @@ class IDF_Scm_Monotone extends IDF_Scm self::exec('IDF_Scm_Monotone::getTree', $cmd, $out, $return); $files = array(); - $stanzas = self::parseBasicIO(implode('\n', $out)); + $stanzas = self::_parseBasicIO(implode("\n", $out)); $folder = $folder == '/' || empty($folder) ? '' : $folder.'/'; foreach ($stanzas as $stanza) @@ -319,22 +386,33 @@ class IDF_Scm_Monotone extends IDF_Scm $file['efullpath'] = self::smartEncode($path); if ($stanza[0]['key'] == "dir") - $file['type'] == "tree"; - else - $file['type'] == "blob"; - - /* - $file['date'] = gmdate('Y-m-d H:i:s', - strtotime((string) $entry->commit->date)); - $file['rev'] = (string) $entry->commit['revision']; - $file['log'] = $this->getCommitMessage($file['rev']); - // Get the size if the type is blob - if ($file['type'] == 'blob') { - $file['size'] = (string) $entry->size; + { + $file['type'] = "tree"; + $file['size'] = 0; } - $file['author'] = (string) $entry->commit->author; - */ - $file['perm'] = ''; + else + { + $file['type'] = "blob"; + $file['hash'] = $stanza[1]['hash']; + $file['size'] = strlen($this->getFile((object)$file)); + } + + $rev = $this->_getLastChangeFor($file['fullpath'], $revs[0]); + if ($rev !== null) + { + $file['rev'] = $rev; + $certs = $this->_getCerts($rev); + + // FIXME: this assumes that author, date and changelog are always given + $file['author'] = implode(", ", $certs['author']); + + $dates = array(); + foreach ($certs['date'] as $date) + $dates[] = gmdate('Y-m-d H:i:s', strtotime($date)); + $file['date'] = implode(', ', $dates); + $file['log'] = substr(implode("; ", $certs['changelog']), 0, 80); + } + $files[] = (object) $file; } return $files; @@ -349,9 +427,9 @@ class IDF_Scm_Monotone extends IDF_Scm */ public function findAuthor($author) { - // We extract the email. + // We extract anything which looks like an email. $match = array(); - if (!preg_match('/<(.*)>/', $author, $match)) { + if (!preg_match('/([^ ]+@[^ ]+)/', $author, $match)) { return null; } foreach (array('email', 'login') as $what) { @@ -364,17 +442,22 @@ class IDF_Scm_Monotone extends IDF_Scm return null; } - public static function getAnonymousAccessUrl($project) + private static function _getMasterBranch($project) { $conf = $project->getConf(); if (false === ($branch = $conf->getVal('mtn_master_branch', false)) || empty($branch)) { $branch = "*"; } + return $branch; + } + + public static function getAnonymousAccessUrl($project) + { return sprintf( Pluf::f('mtn_remote_url'), $project->shortname, - $branch + self::_getMasterBranch($project) ); } @@ -391,61 +474,14 @@ class IDF_Scm_Monotone extends IDF_Scm */ public static function factory($project) { - $rep = sprintf(Pluf::f('git_repositories'), $project->shortname); + $rep = sprintf(Pluf::f('mtn_repositories'), $project->shortname); return new IDF_Scm_Monotone($rep, $project); } public function isValidRevision($commit) { - $type = $this->testHash($commit); - return ('commit' == $type || 'tag' == $type); - } - - /** - * Test a given object hash. - * - * @param string Object hash. - * @return mixed false if not valid or 'blob', 'tree', 'commit', 'tag' - */ - public function testHash($hash) - { - $cmd = sprintf('GIT_DIR=%s '.Pluf::f('git_path', 'git').' cat-file -t %s', - escapeshellarg($this->repo), - escapeshellarg($hash)); - $ret = 0; $out = array(); - $cmd = Pluf::f('idf_exec_cmd_prefix', '').$cmd; - self::exec('IDF_Scm_Monotone::testHash', $cmd, $out, $ret); - if ($ret != 0) return false; - return trim($out[0]); - } - - /** - * Get the tree info. - * - * @param string Tree hash - * @param bool Do we recurse in subtrees (true) - * @param string Folder in which we want to get the info ('') - * @return array Array of file information. - */ - public function getTreeInfo($tree, $folder='') - { - if (!in_array($this->testHash($tree), array('tree', 'commit', 'tag'))) { - throw new Exception(sprintf(__('Not a valid tree: %s.'), $tree)); - } - $cmd_tmpl = 'GIT_DIR=%s '.Pluf::f('git_path', 'git').' ls-tree -l %s %s'; - $cmd = Pluf::f('idf_exec_cmd_prefix', '') - .sprintf($cmd_tmpl, escapeshellarg($this->repo), - escapeshellarg($tree), escapeshellarg($folder)); - $out = array(); - $res = array(); - self::exec('IDF_Scm_Monotone::getTreeInfo', $cmd, $out); - foreach ($out as $line) { - list($perm, $type, $hash, $size, $file) = preg_split('/ |\t/', $line, 5, PREG_SPLIT_NO_EMPTY); - $res[] = (object) array('perm' => $perm, 'type' => $type, - 'size' => $size, 'hash' => $hash, - 'file' => $file); - } - return $res; + $revs = $this->_resolveSelector($commit); + return count($revs) == 1; } /** @@ -455,38 +491,121 @@ class IDF_Scm_Monotone extends IDF_Scm * @param string Commit ('HEAD') * @return false Information */ - public function getPathInfo($totest, $commit='HEAD') + public function getPathInfo($file, $commit = null) { - $cmd_tmpl = 'GIT_DIR=%s '.Pluf::f('git_path', 'git').' ls-tree -r -t -l %s'; - $cmd = sprintf($cmd_tmpl, - escapeshellarg($this->repo), - escapeshellarg($commit)); - $out = array(); - $cmd = Pluf::f('idf_exec_cmd_prefix', '').$cmd; - self::exec('IDF_Scm_Monotone::getPathInfo', $cmd, $out); - foreach ($out as $line) { - list($perm, $type, $hash, $size, $file) = preg_split('/ |\t/', $line, 5, PREG_SPLIT_NO_EMPTY); - if ($totest == $file) { - $pathinfo = pathinfo($file); - return (object) array('perm' => $perm, 'type' => $type, - 'size' => $size, 'hash' => $hash, - 'fullpath' => $file, - 'file' => $pathinfo['basename']); + if ($commit === null) { + $commit = 'h:' . self::_getMasterBranch($this->project); + } + + $revs = $this->_resolveSelector($commit); + if (count($revs) == 0) + return false; + + $cmd = Pluf::f('idf_exec_cmd_prefix', '') + .sprintf("%s -d %s automate get_manifest_of %s", + Pluf::f('mtn_path', 'mtn'), + escapeshellarg($this->repo), + escapeshellarg($revs[0])); + self::exec('IDF_Scm_Monotone::getPathInfo', $cmd, $out, $return); + + $files = array(); + $stanzas = self::_parseBasicIO(implode("\n", $out)); + + foreach ($stanzas as $stanza) + { + if ($stanza[0]['key'] == "format_version") + continue; + + $path = $stanza[0]['values'][0]; + if (!preg_match('#^'.$file.'$#', $path, $m)) + continue; + + $file = array(); + $file['fullpath'] = $path; + + if ($stanza[0]['key'] == "dir") + { + $file['type'] = "tree"; + $file['hash'] = null; + $file['size'] = 0; } + else + { + $file['type'] = "blob"; + $file['hash'] = $stanza[1]['hash']; + $file['size'] = strlen($this->getFile((object)$file)); + } + + $pathinfo = pathinfo($file['fullpath']); + $file['file'] = $pathinfo['basename']; + + $rev = $this->_getLastChangeFor($file['fullpath'], $revs[0]); + if ($rev !== null) + { + $file['rev'] = $rev; + $certs = $this->_getCerts($rev); + + // FIXME: this assumes that author, date and changelog are always given + $file['author'] = implode(", ", $certs['author']); + + $dates = array(); + foreach ($certs['date'] as $date) + $dates[] = gmdate('Y-m-d H:i:s', strtotime($date)); + $file['date'] = implode(', ', $dates); + $file['log'] = substr(implode("; ", $certs['changelog']), 0, 80); + } + + return (object) $file; } return false; } public function getFile($def, $cmd_only=false) { - $cmd = sprintf(Pluf::f('idf_exec_cmd_prefix', ''). - 'GIT_DIR=%s '.Pluf::f('git_path', 'git').' cat-file blob %s', - escapeshellarg($this->repo), - escapeshellarg($def->hash)); + $cmd = Pluf::f('idf_exec_cmd_prefix', '') + .sprintf("%s -d %s automate get_file %s", + Pluf::f('mtn_path', 'mtn'), + escapeshellarg($this->repo), + escapeshellarg($def->hash)); return ($cmd_only) ? $cmd : self::shell_exec('IDF_Scm_Monotone::getFile', $cmd); } + private function _getDiff($target, $source = null) + { + if (empty($source)) + { + $source = "p:$target"; + } + + // FIXME: add real support for merge revisions here which have + // two distinct diff sets + $targets = $this->_resolveSelector($target); + $sources = $this->_resolveSelector($source); + + if (count($targets) == 0 || count($sources) == 0) + { + return ""; + } + + // if target contains a root revision, we cannot produce a diff + if (empty($sources[0])) + { + return ""; + } + + $cmd = Pluf::f('idf_exec_cmd_prefix', '') + .sprintf("%s -d %s automate content_diff -r %s -r %s", + Pluf::f('mtn_path', 'mtn'), + escapeshellarg($this->repo), + escapeshellarg($sources[0]), + escapeshellarg($targets[0])); + self::exec('IDF_Scm_Monotone::_getDiff', + $cmd, $out, $return); + + return implode("\n", $out); + } + /** * Get commit details. * @@ -496,44 +615,27 @@ class IDF_Scm_Monotone extends IDF_Scm */ public function getCommit($commit, $getdiff=false) { - if ($getdiff) { - $cmd = sprintf('GIT_DIR=%s '.Pluf::f('git_path', 'git').' show --date=iso --pretty=format:%s %s', - escapeshellarg($this->repo), - "'".$this->mediumtree_fmt."'", - escapeshellarg($commit)); - } else { - $cmd = sprintf('GIT_DIR=%s '.Pluf::f('git_path', 'git').' log -1 --date=iso --pretty=format:%s %s', - escapeshellarg($this->repo), - "'".$this->mediumtree_fmt."'", - escapeshellarg($commit)); - } - $out = array(); - $cmd = Pluf::f('idf_exec_cmd_prefix', '').$cmd; - self::exec('IDF_Scm_Monotone::getCommit', $cmd, $out, $ret); - if ($ret != 0 or count($out) == 0) { - return false; - } - if ($getdiff) { - $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 = implode("\n", $change); - } else { - $out = self::parseLog($out); - $out[0]->changes = ''; - } - return $out[0]; + $revs = $this->_resolveSelector($commit); + if (count($revs) == 0) + return array(); + + $certs = $this->_getCerts($revs[0]); + + // FIXME: this assumes that author, date and changelog are always given + $res['author'] = implode(", ", $certs['author']); + + $dates = array(); + foreach ($certs['date'] as $date) + $dates[] = gmdate('Y-m-d H:i:s', strtotime($date)); + $res['date'] = implode(', ', $dates); + + $res['title'] = implode("\n---\n, ", $certs['changelog']); + + $res['commit'] = $revs[0]; + + $res['changes'] = ($getdiff) ? $this->_getDiff($revs[0]) : ''; + + return (object) $res; } /** @@ -542,29 +644,35 @@ class IDF_Scm_Monotone extends IDF_Scm * @param string Commit ('HEAD') * @return bool The commit is big */ - public function isCommitLarge($commit='HEAD') + public function isCommitLarge($commit=null) { - $cmd = sprintf('GIT_DIR=%s '.Pluf::f('git_path', 'git').' log --numstat -1 --pretty=format:%s %s', - escapeshellarg($this->repo), - "'commit %H%n'", - escapeshellarg($commit)); - $out = array(); - $cmd = Pluf::f('idf_exec_cmd_prefix', '').$cmd; - self::exec('IDF_Scm_Monotone::isCommitLarge', $cmd, $out); - $affected = count($out) - 2; - $added = 0; - $removed = 0; - $c=0; - foreach ($out as $line) { - $c++; - if ($c < 3) { - continue; - } - list($a, $r, $f) = preg_split("/[\s]+/", $line, 3, PREG_SPLIT_NO_EMPTY); - $added+=$a; - $removed+=$r; + if (empty($commit)) + { + $commit = "h:"+self::_getMasterBranch($this->project); } - return ($affected > 100 or ($added + $removed) > 20000); + + $revs = $this->_resolveSelector($commit); + if (count($revs) == 0) + return false; + + $cmd = Pluf::f('idf_exec_cmd_prefix', '') + .sprintf("%s -d %s automate get_revision %s", + Pluf::f('mtn_path', 'mtn'), + escapeshellarg($this->repo), + escapeshellarg($revs[0])); + self::exec('IDF_Scm_Monotone::isCommitLarge', + $cmd, $out, $return); + + $newAndPatchedFiles = 0; + $stanzas = self::_parseBasicIO(implode("\n", $out)); + + foreach ($stanzas as $stanza) + { + if ($stanza[0]['key'] == "patch" || $stanza[0]['key'] == "add_file") + $newAndPatchedFiles++; + } + + return $newAndPatchedFiles > 100; } /** @@ -586,70 +694,4 @@ class IDF_Scm_Monotone extends IDF_Scm self::exec('IDF_Scm_Monotone::getChangeLog', $cmd, $out); return self::parseLog($out); } - - /** - * Parse the log lines of a --pretty=medium log output. - * - * @param array Lines. - * @return array Change log. - */ - public static function parseLog($lines) - { - $res = array(); - $c = array(); - $inheads = true; - $next_is_title = false; - foreach ($lines as $line) { - if (preg_match('/^commit (\w{40})$/', $line)) { - if (count($c) > 0) { - $c['full_message'] = trim($c['full_message']); - $c['full_message'] = IDF_Commit::toUTF8($c['full_message']); - $c['title'] = IDF_Commit::toUTF8($c['title']); - $res[] = (object) $c; - } - $c = array(); - $c['commit'] = trim(substr($line, 7, 40)); - $c['full_message'] = ''; - $inheads = true; - $next_is_title = false; - continue; - } - if ($next_is_title) { - $c['title'] = trim($line); - $next_is_title = false; - continue; - } - $match = array(); - if ($inheads and 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 ($inheads and !$next_is_title and $line == '') { - $next_is_title = true; - $inheads = false; - } - if (!$inheads) { - $c['full_message'] .= trim($line)."\n"; - continue; - } - } - $c['full_message'] = !empty($c['full_message']) ? trim($c['full_message']) : ''; - $c['full_message'] = IDF_Commit::toUTF8($c['full_message']); - $c['title'] = IDF_Commit::toUTF8($c['title']); - $res[] = (object) $c; - return $res; - } - - public function getArchiveCommand($commit, $prefix='repository/') - { - return sprintf(Pluf::f('idf_exec_cmd_prefix', ''). - 'GIT_DIR=%s '.Pluf::f('git_path', 'git').' archive --format=zip --prefix=%s %s', - escapeshellarg($this->repo), - escapeshellarg($prefix), - escapeshellarg($commit)); - } -} \ No newline at end of file +} From 995f1a13c3b17aac73aa7f0dcdbe5ddb021b404f Mon Sep 17 00:00:00 2001 From: Thomas Keller Date: Thu, 29 Apr 2010 23:35:57 +0200 Subject: [PATCH 10/40] Add a new view modifier which allows the shortening of long strings such as branch or tag names. Use that in the tree view and display the full name in a title tag. --- src/IDF/Middleware.php | 5 +++-- src/IDF/Views/Source.php | 25 ++++++++++++++++------ src/IDF/templates/idf/source/mtn/tree.html | 8 +++++-- 3 files changed, 28 insertions(+), 10 deletions(-) diff --git a/src/IDF/Middleware.php b/src/IDF/Middleware.php index a4060af..e0436b6 100644 --- a/src/IDF/Middleware.php +++ b/src/IDF/Middleware.php @@ -92,6 +92,7 @@ class IDF_Middleware array( 'size' => 'IDF_Views_Source_PrettySize', 'ssize' => 'IDF_Views_Source_PrettySizeSimple', + 'shorten' => 'IDF_Views_Source_ShortenString', )); } } @@ -104,9 +105,9 @@ function IDF_Middleware_ContextPreProcessor($request) $c['isAdmin'] = ($request->user->administrator or $request->user->staff); if (isset($request->project)) { $c['project'] = $request->project; - $c['isOwner'] = $request->user->hasPerm('IDF.project-owner', + $c['isOwner'] = $request->user->hasPerm('IDF.project-owner', $request->project); - $c['isMember'] = $request->user->hasPerm('IDF.project-member', + $c['isMember'] = $request->user->hasPerm('IDF.project-member', $request->project); $c = array_merge($c, $request->rights); } diff --git a/src/IDF/Views/Source.php b/src/IDF/Views/Source.php index e979084..9a4791f 100644 --- a/src/IDF/Views/Source.php +++ b/src/IDF/Views/Source.php @@ -35,10 +35,10 @@ class IDF_Views_Source * Extension supported by the syntax highlighter. */ public static $supportedExtenstions = array( - 'ascx', 'ashx', 'asmx', 'aspx', 'browser', 'bsh', 'c', 'cc', - 'config', 'cpp', 'cs', 'csh', 'csproj', 'css', 'cv', 'cyc', - 'html', 'html', 'java', 'js', 'master', 'perl', 'php', 'pl', - 'pm', 'py', 'rb', 'sh', 'sitemap', 'skin', 'sln', 'svc', 'vala', + 'ascx', 'ashx', 'asmx', 'aspx', 'browser', 'bsh', 'c', 'cc', + 'config', 'cpp', 'cs', 'csh', 'csproj', 'css', 'cv', 'cyc', + 'html', 'html', 'java', 'js', 'master', 'perl', 'php', 'pl', + 'pm', 'py', 'rb', 'sh', 'sitemap', 'skin', 'sln', 'svc', 'vala', 'vb', 'vbproj', 'wsdl', 'xhtml', 'xml', 'xsd', 'xsl', 'xslt'); /** @@ -415,7 +415,7 @@ class IDF_Views_Source $scm->getMainBranch())); return new Pluf_HTTP_Response_Redirect($url); } - $info = self::getRequestedFileMimeType($request_file_info, + $info = self::getRequestedFileMimeType($request_file_info, $commit, $scm); $rep = new Pluf_HTTP_Response($scm->getFile($request_file_info), $info[0]); @@ -476,7 +476,7 @@ class IDF_Views_Source public static function getMimeTypeFromContent($file, $filedata) { $info = pathinfo($file); - $res = array('application/octet-stream', + $res = array('application/octet-stream', $info['basename'], isset($info['extension']) ? $info['extension'] : 'bin'); if (function_exists('finfo_open')) { @@ -597,3 +597,16 @@ 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))); +} + diff --git a/src/IDF/templates/idf/source/mtn/tree.html b/src/IDF/templates/idf/source/mtn/tree.html index 8dfdb18..933f7b0 100644 --- a/src/IDF/templates/idf/source/mtn/tree.html +++ b/src/IDF/templates/idf/source/mtn/tree.html @@ -58,14 +58,18 @@

{trans 'Branches:'}
{foreach $branches as $branch => $path} {aurl 'url', 'IDF_Views_Source::treeBase', array($project.shortname, $branch)} -{if $path}{$path}{else}{$branch}{/if}
+ + {if $path}{$path|shorten:25}{else}{$branch|shorten:25}{/if} +
{/foreach}

{if $tags}

{trans 'Tags:'}
{foreach $tags as $tag => $path} {aurl 'url', 'IDF_Views_Source::treeBase', array($project.shortname, $tag)} -{if $path}{$path}{else}{$tag}{/if}
+ + {if $path}{$path|shorten:25}{else}{$tag|shorten:25}{/if} +
{/foreach}

{/if} From b7ced5fa69a838280a2bb49c2be8b821de982a84 Mon Sep 17 00:00:00 2001 From: Thomas Keller Date: Thu, 29 Apr 2010 23:38:28 +0200 Subject: [PATCH 11/40] Do not shorten the changelog in the SCM model - thats a task for the view. --- src/IDF/Scm/Monotone.php | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/IDF/Scm/Monotone.php b/src/IDF/Scm/Monotone.php index d93bce1..5d71efe 100644 --- a/src/IDF/Scm/Monotone.php +++ b/src/IDF/Scm/Monotone.php @@ -410,7 +410,7 @@ class IDF_Scm_Monotone extends IDF_Scm foreach ($certs['date'] as $date) $dates[] = gmdate('Y-m-d H:i:s', strtotime($date)); $file['date'] = implode(', ', $dates); - $file['log'] = substr(implode("; ", $certs['changelog']), 0, 80); + $file['log'] = implode("\n---\n", $certs['changelog']); } $files[] = (object) $file; @@ -552,7 +552,7 @@ class IDF_Scm_Monotone extends IDF_Scm foreach ($certs['date'] as $date) $dates[] = gmdate('Y-m-d H:i:s', strtotime($date)); $file['date'] = implode(', ', $dates); - $file['log'] = substr(implode("; ", $certs['changelog']), 0, 80); + $file['log'] = implode("\n---\n", $certs['changelog']); } return (object) $file; @@ -629,7 +629,7 @@ class IDF_Scm_Monotone extends IDF_Scm $dates[] = gmdate('Y-m-d H:i:s', strtotime($date)); $res['date'] = implode(', ', $dates); - $res['title'] = implode("\n---\n, ", $certs['changelog']); + $res['title'] = implode("\n---\n", $certs['changelog']); $res['commit'] = $revs[0]; From 445c90fefe75c4e8a601f0e038309d3057b881f4 Mon Sep 17 00:00:00 2001 From: Thomas Keller Date: Fri, 30 Apr 2010 02:03:58 +0200 Subject: [PATCH 12/40] Create a separate class which handles command streaming over mtn automate stdio. Use that everywhere instead of the direct system calls. --- src/IDF/Scm/Monotone.php | 397 ++++++++++++++++++++++++++++----------- 1 file changed, 289 insertions(+), 108 deletions(-) diff --git a/src/IDF/Scm/Monotone.php b/src/IDF/Scm/Monotone.php index 5d71efe..02287d2 100644 --- a/src/IDF/Scm/Monotone.php +++ b/src/IDF/Scm/Monotone.php @@ -25,10 +25,248 @@ * Monotone utils. * */ + +class IDF_Scm_Monotone_Stdio +{ + public static $SUPPORTED_STDIO_VERSION = 2; + + private $repo; + private $proc; + private $pipes; + private $oob; + private $cmdnum; + private $lastcmd; + + public function __construct($repo) + { + $this->repo = $repo; + $this->start(); + } + + public function __destruct() + { + $this->stop(); + } + + public function start() + { + if (is_resource($this->proc)) + $this->stop(); + + $cmd = Pluf::f('idf_exec_cmd_prefix', '') + .sprintf("%s -d %s automate stdio --no-workspace --norc", + Pluf::f('mtn_path', 'mtn'), + escapeshellarg($this->repo)); + + $descriptors = array( + 0 => array("pipe", "r"), + 1 => array("pipe", "w"), + 2 => array("pipe", "r") + ); + + $this->proc = proc_open($cmd, $descriptors, $this->pipes); + + if (!is_resource($this->proc)) + { + throw new IDF_Scm_Exception("could not start stdio process"); + } + + $this->_checkVersion(); + + $this->cmdnum = -1; + } + + public function stop() + { + if (!is_resource($this->proc)) + return; + + fclose($this->pipes[0]); + fclose($this->pipes[1]); + fclose($this->pipes[2]); + + proc_close($this->proc); + $this->proc = null; + } + + private function _checkVersion() + { + $version = fgets($this->pipes[1]); + if (!preg_match('/^format-version: (\d+)$/', $version, $m) || + $m[1] != self::$SUPPORTED_STDIO_VERSION) + { + throw new IDF_Scm_Exception( + "stdio format version mismatch, expected '". + self::$SUPPORTED_STDIO_VERSION."', got '".@$m[1]."'" + ); + } + fgets($this->pipes[1]); + } + + private function _write($args, $options = array()) + { + $cmd = ""; + if (count($options) > 0) + { + $cmd = "o"; + foreach ($options as $k => $v) + { + $cmd .= strlen((string)$k) . ":" . (string)$k; + $cmd .= strlen((string)$v) . ":" . (string)$v; + } + $cmd .= "e "; + } + + $cmd .= "l"; + foreach ($args as $arg) + { + $cmd .= strlen((string)$arg) . ":" . (string)$arg; + } + $cmd .= "e\n"; + + if (!fwrite($this->pipes[0], $cmd)) + { + throw new IDF_Scm_Exception("could not write '$cmd' to process"); + } + + $this->lastcmd = $cmd; + $this->cmdnum++; + } + + private function _read() + { + $this->oob = array('w' => array(), + 'p' => array(), + 't' => array(), + 'e' => array()); + + $output = ""; + $errcode = 0; + + while (true) + { + $read = array($this->pipes[1]); + $write = null; + $except = null; + + $streamsChanged = stream_select( + $read, $write, $except, 0, 20000 + ); + + if ($streamsChanged === false) + { + throw new IDF_Scm_Exception( + "Could not select() on read pipe" + ); + } + + if ($streamsChanged == 0) + { + continue; + } + + $data = array(0,"",0); + $idx = 0; + while (true) + { + $c = fgetc($this->pipes[1]); + if ($c == ':') + { + if ($idx == 2) + break; + + ++$idx; + continue; + } + + if (is_numeric($c)) + $data[$idx] = $data[$idx] * 10 + $c; + else + $data[$idx] .= $c; + } + + // sanity + if ($this->cmdnum != $data[0]) + { + throw new IDF_Scm_Exception( + "command numbers out of sync; ". + "expected {$this->cmdnum}, got {$data[0]}" + ); + } + + $toRead = $data[2]; + $buffer = ""; + while ($toRead > 0) + { + $buffer .= fread($this->pipes[1], $toRead); + $toRead = $data[2] - strlen($buffer); + } + + switch ($data[1]) + { + case 'w': + case 'p': + case 't': + case 'e': + $this->oob[$data[1]][] = $buffer; + continue; + case 'm': + $output .= $buffer; + continue; + case 'l': + $errcode = $buffer; + break 2; + } + } + + if ($errcode != 0) + { + throw new IDF_Scm_Exception( + "command '{$this->lastcmd}' returned error code $errcode: ". + implode(" ", $this->oob['e']) + ); + } + + return $output; + } + + public function exec($args, $options = array()) + { + $this->_write($args, $options); + return $this->_read(); + } + + public function getLastWarnings() + { + return array_key_exists('w', $this->oob) ? + $this->oob['w'] : array(); + } + + public function getLastProgress() + { + return array_key_exists('p', $this->oob) ? + $this->oob['p'] : array(); + } + + public function getLastTickers() + { + return array_key_exists('t', $this->oob) ? + $this->oob['t'] : array(); + } + + public function getLastErrors() + { + return array_key_exists('e', $this->oob) ? + $this->oob['e'] : array(); + } +} + class IDF_Scm_Monotone extends IDF_Scm { public static $MIN_INTERFACE_VERSION = 12.0; + private $stdio; + /* ============================================== * * * * Common Methods Implemented By All The SCMs * @@ -39,6 +277,7 @@ class IDF_Scm_Monotone extends IDF_Scm { $this->repo = $repo; $this->project = $project; + $this->stdio = new IDF_Scm_Monotone_Stdio($repo); } public function getRepositorySize() @@ -56,19 +295,14 @@ class IDF_Scm_Monotone extends IDF_Scm public function isAvailable() { - $out = array(); - try { - $cmd = Pluf::f('idf_exec_cmd_prefix', '') - .sprintf("%s -d %s automate interface_version", - Pluf::f('mtn_path', 'mtn'), - escapeshellarg($this->repo)); - self::exec('IDF_Scm_Monotone::isAvailable', - $cmd, $out, $return); - } catch (IDF_Scm_Exception $e) { - return false; + try + { + $out = $this->stdio->exec(array("interface_version")); + return floatval($out) >= self::$MIN_INTERFACE_VERSION; } + catch (IDF_Scm_Exception $e) {} - return count($out) > 0 && floatval($out[0]) >= self::$MIN_INTERFACE_VERSION; + return false; } public function getBranches() @@ -77,25 +311,18 @@ class IDF_Scm_Monotone extends IDF_Scm return $this->cache['branches']; } // FIXME: introduce handling of suspended branches - $cmd = Pluf::f('idf_exec_cmd_prefix', '') - .sprintf("%s -d %s automate branches", - Pluf::f('mtn_path', 'mtn'), - escapeshellarg($this->repo)); - self::exec('IDF_Scm_Monotone::getBranches', - $cmd, $out, $return); - if ($return != 0) { - throw new IDF_Scm_Exception(sprintf($this->error_tpl, - $cmd, $return, - implode("\n", $out))); - } - $res = array(); + $out = $this->stdio->exec(array("branches")); + // FIXME: we could expand each branch with one of its head revisions // here, but these would soon become bogus anyway and we cannot // map multiple head revisions here either, so we just use the // selector as placeholder - foreach ($out as $b) { + $res = array(); + foreach (preg_split("/\n/", $out, -1, PREG_SPLIT_NO_EMPTY) as $b) + { $res["h:$b"] = $b; } + $this->cache['branches'] = $res; return $res; } @@ -121,14 +348,8 @@ class IDF_Scm_Monotone extends IDF_Scm */ private function _resolveSelector($selector) { - $cmd = Pluf::f('idf_exec_cmd_prefix', '') - .sprintf("%s -d %s automate select %s", - Pluf::f('mtn_path', 'mtn'), - escapeshellarg($this->repo), - escapeshellarg($selector)); - self::exec('IDF_Scm_Monotone::_resolveSelector', - $cmd, $out, $return); - return $out; + $out = $this->stdio->exec(array("select", $selector)); + return preg_split("/\n/", $out, -1, PREG_SPLIT_NO_EMPTY); } /** @@ -139,9 +360,6 @@ class IDF_Scm_Monotone extends IDF_Scm */ private static function _parseBasicIO($in) { - if (substr($in, -1) != "\n") - $in .= "\n"; - $pos = 0; $stanzas = array(); @@ -208,15 +426,9 @@ class IDF_Scm_Monotone extends IDF_Scm if (!array_key_exists($rev, $certCache)) { - $cmd = Pluf::f('idf_exec_cmd_prefix', '') - .sprintf("%s -d %s automate certs %s", - Pluf::f('mtn_path', 'mtn'), - escapeshellarg($this->repo), - escapeshellarg($rev)); - self::exec('IDF_Scm_Monotone::_getCerts', - $cmd, $out, $return); + $out = $this->stdio->exec(array("certs", $rev)); - $stanzas = self::_parseBasicIO(implode("\n", $out)); + $stanzas = self::_parseBasicIO($out); $certs = array(); foreach ($stanzas as $stanza) { @@ -264,16 +476,11 @@ class IDF_Scm_Monotone extends IDF_Scm private function _getLastChangeFor($file, $startrev) { - $cmd = Pluf::f('idf_exec_cmd_prefix', '') - .sprintf("%s -d %s automate get_content_changed %s %s", - Pluf::f('mtn_path', 'mtn'), - escapeshellarg($this->repo), - escapeshellarg($startrev), - escapeshellarg($file)); - self::exec('IDF_Scm_Monotone::_getLastChangeFor', - $cmd, $out, $return); + $out = $this->stdio->exec(array( + "get_content_changed", $startrev, $file + )); - $stanzas = self::_parseBasicIO(implode("\n", $out)); + $stanzas = self::_parseBasicIO($out); // FIXME: we only care about the first returned content mark // everything else seem to be very rare cases @@ -305,17 +512,15 @@ class IDF_Scm_Monotone extends IDF_Scm **/ public function getTags() { - if (isset($this->cache['tags'])) { + if (isset($this->cache['tags'])) + { return $this->cache['tags']; } - $cmd = Pluf::f('idf_exec_cmd_prefix', '') - .sprintf("%s -d %s automate tags", - Pluf::f('mtn_path', 'mtn'), - escapeshellarg($this->repo)); - self::exec('IDF_Scm_Monotone::getTags', $cmd, $out, $return); + + $out = $this->stdio->exec(array("tags")); $tags = array(); - $stanzas = self::_parseBasicIO(implode("\n", $out)); + $stanzas = self::_parseBasicIO($out); foreach ($stanzas as $stanza) { $tagname = null; @@ -360,15 +565,12 @@ class IDF_Scm_Monotone extends IDF_Scm return array(); } - $cmd = Pluf::f('idf_exec_cmd_prefix', '') - .sprintf("%s -d %s automate get_manifest_of %s", - Pluf::f('mtn_path', 'mtn'), - escapeshellarg($this->repo), - escapeshellarg($revs[0])); - self::exec('IDF_Scm_Monotone::getTree', $cmd, $out, $return); + $out = $this->stdio->exec(array( + "get_manifest_of", $revs[0] + )); $files = array(); - $stanzas = self::_parseBasicIO(implode("\n", $out)); + $stanzas = self::_parseBasicIO($out); $folder = $folder == '/' || empty($folder) ? '' : $folder.'/'; foreach ($stanzas as $stanza) @@ -501,15 +703,12 @@ class IDF_Scm_Monotone extends IDF_Scm if (count($revs) == 0) return false; - $cmd = Pluf::f('idf_exec_cmd_prefix', '') - .sprintf("%s -d %s automate get_manifest_of %s", - Pluf::f('mtn_path', 'mtn'), - escapeshellarg($this->repo), - escapeshellarg($revs[0])); - self::exec('IDF_Scm_Monotone::getPathInfo', $cmd, $out, $return); + $out = $this->stdio->exec(array( + "get_manifest_of", $revs[0] + )); $files = array(); - $stanzas = self::_parseBasicIO(implode("\n", $out)); + $stanzas = self::_parseBasicIO($out); foreach ($stanzas as $stanza) { @@ -562,13 +761,13 @@ class IDF_Scm_Monotone extends IDF_Scm public function getFile($def, $cmd_only=false) { - $cmd = Pluf::f('idf_exec_cmd_prefix', '') - .sprintf("%s -d %s automate get_file %s", - Pluf::f('mtn_path', 'mtn'), - escapeshellarg($this->repo), - escapeshellarg($def->hash)); - return ($cmd_only) - ? $cmd : self::shell_exec('IDF_Scm_Monotone::getFile', $cmd); + // this won't work with remote databases + if ($cmd_only) + { + throw new Pluf_Exception_NotImplemented(); + } + + return $this->stdio->exec(array("get_file", $def->hash)); } private function _getDiff($target, $source = null) @@ -594,16 +793,10 @@ class IDF_Scm_Monotone extends IDF_Scm return ""; } - $cmd = Pluf::f('idf_exec_cmd_prefix', '') - .sprintf("%s -d %s automate content_diff -r %s -r %s", - Pluf::f('mtn_path', 'mtn'), - escapeshellarg($this->repo), - escapeshellarg($sources[0]), - escapeshellarg($targets[0])); - self::exec('IDF_Scm_Monotone::_getDiff', - $cmd, $out, $return); - - return implode("\n", $out); + return $this->stdio->exec( + array("content_diff"), + array("r" => $sources[0], "r" => $targets[0]) + ); } /** @@ -655,16 +848,12 @@ class IDF_Scm_Monotone extends IDF_Scm if (count($revs) == 0) return false; - $cmd = Pluf::f('idf_exec_cmd_prefix', '') - .sprintf("%s -d %s automate get_revision %s", - Pluf::f('mtn_path', 'mtn'), - escapeshellarg($this->repo), - escapeshellarg($revs[0])); - self::exec('IDF_Scm_Monotone::isCommitLarge', - $cmd, $out, $return); + $out = $this->stdio->exec(array( + "get_revision", $revs[0] + )); $newAndPatchedFiles = 0; - $stanzas = self::_parseBasicIO(implode("\n", $out)); + $stanzas = self::_parseBasicIO($out); foreach ($stanzas as $stanza) { @@ -682,16 +871,8 @@ class IDF_Scm_Monotone extends IDF_Scm * @param int Number of changes (10). * @return array Changes. */ - public function getChangeLog($commit='HEAD', $n=10) + public function getChangeLog($commit=null, $n=10) { - if ($n === null) $n = ''; - else $n = ' -'.$n; - $cmd = sprintf('GIT_DIR=%s '.Pluf::f('git_path', 'git').' log%s --date=iso --pretty=format:\'%s\' %s', - escapeshellarg($this->repo), $n, $this->mediumtree_fmt, - escapeshellarg($commit)); - $out = array(); - $cmd = Pluf::f('idf_exec_cmd_prefix', '').$cmd; - self::exec('IDF_Scm_Monotone::getChangeLog', $cmd, $out); - return self::parseLog($out); + throw new Pluf_Exception_NotImplemented(); } } From 601e894935f66257cd7c0969c141f898d99a1e23 Mon Sep 17 00:00:00 2001 From: Thomas Keller Date: Fri, 30 Apr 2010 02:11:40 +0200 Subject: [PATCH 13/40] Use the branch / tag name shortener in two other templates as well. --- src/IDF/templates/idf/source/mtn/changelog.html | 12 ++++++++---- src/IDF/templates/idf/source/mtn/file.html | 11 +++++++---- 2 files changed, 15 insertions(+), 8 deletions(-) diff --git a/src/IDF/templates/idf/source/mtn/changelog.html b/src/IDF/templates/idf/source/mtn/changelog.html index fa92f24..bb3dbc5 100644 --- a/src/IDF/templates/idf/source/mtn/changelog.html +++ b/src/IDF/templates/idf/source/mtn/changelog.html @@ -2,15 +2,19 @@ {block context}

{trans 'Branches:'}
{foreach $branches as $branch => $path} -{aurl 'url', 'IDF_Views_Source::changeLog', array($project.shortname, $branch)} -{if $path}{$path}{else}{$branch}{/if}
+{aurl 'url', 'IDF_Views_Source::treeBase', array($project.shortname, $branch)} + + {if $path}{$path|shorten:25}{else}{$branch|shorten:25}{/if} +
{/foreach}

{if $tags}

{trans 'Tags:'}
{foreach $tags as $tag => $path} -{aurl 'url', 'IDF_Views_Source::changeLog', array($project.shortname, $tag)} -{if $path}{$path}{else}{$tag}{/if}
+{aurl 'url', 'IDF_Views_Source::treeBase', array($project.shortname, $tag)} + + {if $path}{$path|shorten:25}{else}{$tag|shorten:25}{/if} +
{/foreach}

{/if} diff --git a/src/IDF/templates/idf/source/mtn/file.html b/src/IDF/templates/idf/source/mtn/file.html index 77b33d8..a8f4e43 100644 --- a/src/IDF/templates/idf/source/mtn/file.html +++ b/src/IDF/templates/idf/source/mtn/file.html @@ -6,7 +6,7 @@ {if !$tree_in and !$tags_in} -{aurl 'url', 'IDF_Views_Source::commit', array($project.shortname, $commit)} +{aurl 'url', 'IDF_Views_Source::commit', array($project.shortname, $commit)}
{blocktrans}Source at commit {$commit} created {$cobject.date|dateago}.{/blocktrans}
{blocktrans}By {$cobject.author|strip_tags|trim}, {$cobject.title}{/blocktrans} @@ -25,19 +25,22 @@

{trans 'Branches:'}
{foreach $branches as $branch => $path} {aurl 'url', 'IDF_Views_Source::treeBase', array($project.shortname, $branch)} -{if $path}{$path}{else}{$branch}{/if}
+ + {if $path}{$path|shorten:25}{else}{$branch|shorten:25}{/if} +
{/foreach}

{if $tags}

{trans 'Tags:'}
{foreach $tags as $tag => $path} {aurl 'url', 'IDF_Views_Source::treeBase', array($project.shortname, $tag)} -{if $path}{$path}{else}{$tag}{/if}
+ + {if $path}{$path|shorten:25}{else}{$tag|shorten:25}{/if} +
{/foreach}

{/if} {/block} - {block javascript} From c49a8204e00ca385032098120000e2b1647fbaef Mon Sep 17 00:00:00 2001 From: Thomas Keller Date: Fri, 30 Apr 2010 02:38:45 +0200 Subject: [PATCH 14/40] Properly activate the correct branches / tags for the currently viewed revision --- .../templates/idf/source/mtn/changelog.html | 24 +++++++++++-------- src/IDF/templates/idf/source/mtn/file.html | 24 +++++++++++-------- src/IDF/templates/idf/source/mtn/tree.html | 24 +++++++++++-------- 3 files changed, 42 insertions(+), 30 deletions(-) diff --git a/src/IDF/templates/idf/source/mtn/changelog.html b/src/IDF/templates/idf/source/mtn/changelog.html index bb3dbc5..5a6fc23 100644 --- a/src/IDF/templates/idf/source/mtn/changelog.html +++ b/src/IDF/templates/idf/source/mtn/changelog.html @@ -1,20 +1,24 @@ {extends "idf/source/changelog.html"} {block context}

{trans 'Branches:'}
-{foreach $branches as $branch => $path} -{aurl 'url', 'IDF_Views_Source::treeBase', array($project.shortname, $branch)} - - {if $path}{$path|shorten:25}{else}{$branch|shorten:25}{/if} -
+{foreach $branches as $selector => $branch} +{aurl 'url', 'IDF_Views_Source::treeBase', array($project.shortname, $selector)} + + + {$branch|shorten:25} + +
{/foreach}

{if $tags}

{trans 'Tags:'}
-{foreach $tags as $tag => $path} -{aurl 'url', 'IDF_Views_Source::treeBase', array($project.shortname, $tag)} - - {if $path}{$path|shorten:25}{else}{$tag|shorten:25}{/if} -
+{foreach $tags as $selector => $tag} +{aurl 'url', 'IDF_Views_Source::treeBase', array($project.shortname, $selector)} + + + {$tag|shorten:25} + +
{/foreach}

{/if} diff --git a/src/IDF/templates/idf/source/mtn/file.html b/src/IDF/templates/idf/source/mtn/file.html index a8f4e43..6190557 100644 --- a/src/IDF/templates/idf/source/mtn/file.html +++ b/src/IDF/templates/idf/source/mtn/file.html @@ -23,20 +23,24 @@ {/block} {block context}

{trans 'Branches:'}
-{foreach $branches as $branch => $path} -{aurl 'url', 'IDF_Views_Source::treeBase', array($project.shortname, $branch)} - - {if $path}{$path|shorten:25}{else}{$branch|shorten:25}{/if} -
+{foreach $branches as $selector => $branch} +{aurl 'url', 'IDF_Views_Source::treeBase', array($project.shortname, $selector)} + + + {$branch|shorten:25} + +
{/foreach}

{if $tags}

{trans 'Tags:'}
-{foreach $tags as $tag => $path} -{aurl 'url', 'IDF_Views_Source::treeBase', array($project.shortname, $tag)} - - {if $path}{$path|shorten:25}{else}{$tag|shorten:25}{/if} -
+{foreach $tags as $selector => $tag} +{aurl 'url', 'IDF_Views_Source::treeBase', array($project.shortname, $selector)} + + + {$tag|shorten:25} + +
{/foreach}

{/if} diff --git a/src/IDF/templates/idf/source/mtn/tree.html b/src/IDF/templates/idf/source/mtn/tree.html index 933f7b0..0250b01 100644 --- a/src/IDF/templates/idf/source/mtn/tree.html +++ b/src/IDF/templates/idf/source/mtn/tree.html @@ -56,20 +56,24 @@ {/block} {block context}

{trans 'Branches:'}
-{foreach $branches as $branch => $path} -{aurl 'url', 'IDF_Views_Source::treeBase', array($project.shortname, $branch)} - - {if $path}{$path|shorten:25}{else}{$branch|shorten:25}{/if} -
+{foreach $branches as $selector => $branch} +{aurl 'url', 'IDF_Views_Source::treeBase', array($project.shortname, $selector)} + + + {$branch|shorten:25} + +
{/foreach}

{if $tags}

{trans 'Tags:'}
-{foreach $tags as $tag => $path} -{aurl 'url', 'IDF_Views_Source::treeBase', array($project.shortname, $tag)} - - {if $path}{$path|shorten:25}{else}{$tag|shorten:25}{/if} -
+{foreach $tags as $selector => $tag} +{aurl 'url', 'IDF_Views_Source::treeBase', array($project.shortname, $selector)} + + + {$tag|shorten:25} + +
{/foreach}

{/if} From 15a2bd90b35a629e7f3551fe120efe1c02bbf20d Mon Sep 17 00:00:00 2001 From: Thomas Keller Date: Sat, 1 May 2010 00:56:48 +0200 Subject: [PATCH 15/40] Add support for monotone's diff header --- src/IDF/Diff.php | 28 +++++++++++++++++++++------- 1 file changed, 21 insertions(+), 7 deletions(-) diff --git a/src/IDF/Diff.php b/src/IDF/Diff.php index 817885d..71e8019 100644 --- a/src/IDF/Diff.php +++ b/src/IDF/Diff.php @@ -51,7 +51,7 @@ class IDF_Diff $i = 0; // Used to skip the end of a git patch with --\nversion number foreach ($this->lines as $line) { $i++; - if (0 === strpos($line, '--') and isset($this->lines[$i]) + if (0 === strpos($line, '--') and isset($this->lines[$i]) and preg_match('/^\d+\.\d+\.\d+\.\d+$/', $this->lines[$i])) { break; } @@ -71,6 +71,14 @@ class IDF_Diff $current_chunk = 0; $indiff = true; continue; + } else if (0 === strpos($line, '=========')) { + $current_file = self::getMtnFile($this->lines[$i+1]); + $files[$current_file] = array(); + $files[$current_file]['chunks'] = array(); + $files[$current_file]['chunks_def'] = array(); + $current_chunk = 0; + $indiff = true; + continue; } else if (0 === strpos($line, 'Index: ')) { $current_file = self::getSvnFile($line); $files[$current_file] = array(); @@ -133,6 +141,12 @@ class IDF_Diff return substr(trim($line), 7); } + public static function getMtnFile($line) + { + preg_match("/^[+-]{3} ([^\t]+)/", $line, $m); + return $m[1]; + } + /** * Return the html version of a parsed diff. */ @@ -215,14 +229,14 @@ class IDF_Diff * @param int Number of lines before/after the chunk to be displayed (10) * @return Pluf_Template_SafeString The table body */ - public function fileCompare($orig, $chunks, $filename, $context=10) + public function fileCompare($orig, $chunks, $filename, $context=10) { $orig_lines = preg_split("/\015\012|\015|\012/", $orig); $new_chunks = $this->mergeChunks($orig_lines, $chunks, $context); return $this->renderCompared($new_chunks, $filename); } - public function mergeChunks($orig_lines, $chunks, $context=10) + public function mergeChunks($orig_lines, $chunks, $context=10) { $spans = array(); $new_chunks = array(); @@ -250,7 +264,7 @@ class IDF_Diff for ($lc=$spans[$i][0];$lc<$chunk[0][0];$lc++) { $exists = false; foreach ($chunk_lines as $line) { - if ($lc == $line[0] + if ($lc == $line[0] or ($chunk[0][1]-$chunk[0][0]+$lc) == $line[1]) { $exists = true; break; @@ -259,7 +273,7 @@ class IDF_Diff if (!$exists) { $orig = isset($orig_lines[$lc-1]) ? $orig_lines[$lc-1] : ''; $n_chunk[] = array( - $lc, + $lc, $chunk[0][1]-$chunk[0][0]+$lc, $orig ); @@ -283,7 +297,7 @@ class IDF_Diff } if (!$exists) { $n_chunk[] = array( - $lc, + $lc, $lline[1]-$lline[0]+$lc, $orig_lines[$lc-1] ); @@ -305,7 +319,7 @@ class IDF_Diff foreach ($chunk as $line) { if ($line[0] > $lline[0] or empty($line[0])) { $nnew_chunks[$i-1][] = $line; - } + } } } else { $nnew_chunks[] = $chunk; From 3b53ceedcd60fe116e3f2844b0b5b7fd3b0eaa60 Mon Sep 17 00:00:00 2001 From: Thomas Keller Date: Sat, 1 May 2010 01:05:54 +0200 Subject: [PATCH 16/40] * Monotone.php (IDF_Scm_Monotone): basic_io values need to be unescaped; implement getChangeLog() * Monotone.php (IDF_Scm_Monotone_Stdio): add support for multiple, equally named options * Source.php, commit.html: split-off the global commit template (which had some separate code already for SVN) and adapt the left blocks for mtn to shorten branch and tag names just like we do everywhere else --- src/IDF/Scm/Monotone.php | 73 +++++++++++++++++-- src/IDF/Views/Source.php | 2 +- src/IDF/templates/idf/source/commit.html | 27 ------- src/IDF/templates/idf/source/git/commit.html | 18 +++++ .../idf/source/mercurial/commit.html | 18 +++++ src/IDF/templates/idf/source/mtn/commit.html | 26 +++++++ src/IDF/templates/idf/source/svn/commit.html | 11 +++ 7 files changed, 142 insertions(+), 33 deletions(-) create mode 100644 src/IDF/templates/idf/source/git/commit.html create mode 100644 src/IDF/templates/idf/source/mercurial/commit.html create mode 100644 src/IDF/templates/idf/source/mtn/commit.html create mode 100644 src/IDF/templates/idf/source/svn/commit.html diff --git a/src/IDF/Scm/Monotone.php b/src/IDF/Scm/Monotone.php index 02287d2..b35c6b4 100644 --- a/src/IDF/Scm/Monotone.php +++ b/src/IDF/Scm/Monotone.php @@ -109,10 +109,16 @@ class IDF_Scm_Monotone_Stdio if (count($options) > 0) { $cmd = "o"; - foreach ($options as $k => $v) + foreach ($options as $k => $vals) { - $cmd .= strlen((string)$k) . ":" . (string)$k; - $cmd .= strlen((string)$v) . ":" . (string)$v; + if (!is_array($vals)) + $vals = array($vals); + + foreach ($vals as $v) + { + $cmd .= strlen((string)$k) . ":" . (string)$k; + $cmd .= strlen((string)$v) . ":" . (string)$v; + } } $cmd .= "e "; } @@ -409,6 +415,15 @@ class IDF_Scm_Monotone extends IDF_Scm ++$valCount; } } + + for ($i = 0; $i <= $valCount; $i++) + { + $stanzaLine['values'][$i] = str_replace( + array("\\\\", "\\\""), + array("\\", "\""), + $stanzaLine['values'][$i] + ); + } } $stanza[] = $stanzaLine; @@ -795,7 +810,7 @@ class IDF_Scm_Monotone extends IDF_Scm return $this->stdio->exec( array("content_diff"), - array("r" => $sources[0], "r" => $targets[0]) + array("r" => array($sources[0], $targets[0])) ); } @@ -873,6 +888,54 @@ class IDF_Scm_Monotone extends IDF_Scm */ public function getChangeLog($commit=null, $n=10) { - throw new Pluf_Exception_NotImplemented(); + $horizont = $this->_resolveSelector($commit); + $initialBranches = array(); + $logs = array(); + + while (!empty($horizont) && $n > 0) + { + if (count($horizont) > 1) + { + $out = $this->stdio->exec(array("toposort") + $horizont); + $horizont = preg_split("/\n/", $out, -1, PREG_SPLIT_NO_EMPTY); + } + + $rev = array_shift($horizont); + $certs = $this->_getCerts($rev); + + // read in the initial branches we should follow + if (count($initialBranches) == 0) + { + $initialBranches = $certs['branch']; + } + + // only add it to our log if it is on one of the initial branches + if (count(array_intersect($initialBranches, $certs['branch'])) > 0) + { + --$n; + + $log = array(); + $log['author'] = implode(", ", $certs['author']); + + $dates = array(); + foreach ($certs['date'] as $date) + $dates[] = gmdate('Y-m-d H:i:s', strtotime($date)); + $log['date'] = implode(', ', $dates); + + $combinedChangelog = implode("\n---\n", $certs['changelog']); + $split = preg_split("/[\n\r]/", $combinedChangelog, 2); + $log['title'] = $split[0]; + $log['full_message'] = (isset($split[1])) ? trim($split[1]) : ''; + + $log['commit'] = $rev; + + $logs[] = (object)$log; + } + + $out = $this->stdio->exec(array("parents", $rev)); + $horizont += preg_split("/\n/", $out, -1, PREG_SPLIT_NO_EMPTY); + } + + return $logs; } } diff --git a/src/IDF/Views/Source.php b/src/IDF/Views/Source.php index 9a4791f..37d9ca9 100644 --- a/src/IDF/Views/Source.php +++ b/src/IDF/Views/Source.php @@ -308,7 +308,7 @@ class IDF_Views_Source $in_branches = $scm->inBranches($cobject->commit, ''); $tags = $scm->getTags(); $in_tags = $scm->inTags($cobject->commit, ''); - return Pluf_Shortcuts_RenderToResponse('idf/source/commit.html', + return Pluf_Shortcuts_RenderToResponse('idf/source/'.$scmConf.'/commit.html', array( 'page_title' => $page_title, 'title' => $title, diff --git a/src/IDF/templates/idf/source/commit.html b/src/IDF/templates/idf/source/commit.html index a3c0f51..c9c1051 100644 --- a/src/IDF/templates/idf/source/commit.html +++ b/src/IDF/templates/idf/source/commit.html @@ -37,33 +37,6 @@ {/if} {/block} -{block context} -{if $scm != 'svn'} -

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

-{if $tags} -

{trans 'Tags:'}
-{foreach $tags as $tag => $path} -{aurl 'url', 'IDF_Views_Source::treeBase', array($project.shortname, $tag)} -{if $path}{$path}{else}{$tag}{/if}
-{/foreach} -

-{/if} -{else} -
-

{trans 'Revision:'} {$commit}

-

- - -

-
-{/if} -{/block} - {block javascript}