Start on monotone support for indefero. The SCM backend is about 30% done, interesting pieces like getTree() are not finished yet.
This commit is contained in:
parent
d7843a55bd
commit
9fd4334dec
651
src/IDF/Scm/Monotone.php
Normal file
651
src/IDF/Scm/Monotone.php
Normal file
@ -0,0 +1,651 @@
|
|||||||
|
<?php
|
||||||
|
/* -*- tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
|
||||||
|
/*
|
||||||
|
# ***** BEGIN LICENSE BLOCK *****
|
||||||
|
# This file is part of InDefero, an open source project management application.
|
||||||
|
# Copyright (C) 2008 Céondo Ltd and contributors.
|
||||||
|
#
|
||||||
|
# InDefero is free software; you can redistribute it and/or modify
|
||||||
|
# it under the terms of the GNU General Public License as published by
|
||||||
|
# the Free Software Foundation; either version 2 of the License, or
|
||||||
|
# (at your option) any later version.
|
||||||
|
#
|
||||||
|
# InDefero is distributed in the hope that it will be useful,
|
||||||
|
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
# GNU General Public License for more details.
|
||||||
|
#
|
||||||
|
# You should have received a copy of the GNU General Public License
|
||||||
|
# along with this program; if not, write to the Free Software
|
||||||
|
# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
|
||||||
|
#
|
||||||
|
# ***** END LICENSE BLOCK ***** */
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Monotone utils.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
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';
|
||||||
|
|
||||||
|
/* ============================================== *
|
||||||
|
* *
|
||||||
|
* 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));
|
||||||
|
}
|
||||||
|
}
|
17
src/IDF/templates/idf/source/mtn/changelog.html
Normal file
17
src/IDF/templates/idf/source/mtn/changelog.html
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
{extends "idf/source/changelog.html"}
|
||||||
|
{block context}
|
||||||
|
<p><strong>{trans 'Branches:'}</strong><br/>
|
||||||
|
{foreach $branches as $branch => $path}
|
||||||
|
{aurl 'url', 'IDF_Views_Source::changeLog', array($project.shortname, $branch)}
|
||||||
|
<span class="label{if in_array($branch, $tree_in)} active{/if}"><a href="{$url}" class="label">{if $path}{$path}{else}{$branch}{/if}</a></span><br/>
|
||||||
|
{/foreach}
|
||||||
|
</p>
|
||||||
|
{if $tags}
|
||||||
|
<p><strong>{trans 'Tags:'}</strong><br/>
|
||||||
|
{foreach $tags as $tag => $path}
|
||||||
|
{aurl 'url', 'IDF_Views_Source::changeLog', array($project.shortname, $tag)}
|
||||||
|
<span class="label{if in_array($tag, $tags_in)} active{/if}"><a href="{$url}" class="label">{if $path}{$path}{else}{$tag}{/if}</a></span><br/>
|
||||||
|
{/foreach}
|
||||||
|
</p>
|
||||||
|
{/if}
|
||||||
|
{/block}
|
44
src/IDF/templates/idf/source/mtn/file.html
Normal file
44
src/IDF/templates/idf/source/mtn/file.html
Normal file
@ -0,0 +1,44 @@
|
|||||||
|
{extends "idf/source/base.html"}
|
||||||
|
{block extraheader}<link rel="stylesheet" type="text/css" href="{media '/idf/css/prettify.css'}" />{/block}
|
||||||
|
{block docclass}yui-t1{assign $inSourceTree=true}{/block}
|
||||||
|
{block body}
|
||||||
|
<h2 class="top"><a href="{url 'IDF_Views_Source::treeBase', array($project.shortname, $commit)}">{trans 'Root'}</a><span class="sep">/</span>{if $breadcrumb}{$breadcrumb|safe}{/if}</h2>
|
||||||
|
|
||||||
|
<table class="code" summary=" ">
|
||||||
|
{if !$tree_in and !$tags_in}
|
||||||
|
{aurl 'url', 'IDF_Views_Source::commit', array($project.shortname, $commit)}
|
||||||
|
<tfoot>
|
||||||
|
<tr><th colspan="2">{blocktrans}Source at commit <a class="mono" href="{$url}">{$commit}</a> created {$cobject.date|dateago}.{/blocktrans}<br/>
|
||||||
|
<span class="smaller">{blocktrans}By {$cobject.author|strip_tags|trim}, {$cobject.title}{/blocktrans}</span>
|
||||||
|
</th></tr>
|
||||||
|
</tfoot>
|
||||||
|
{/if}
|
||||||
|
<tbody>
|
||||||
|
{$file}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
{aurl 'url', 'IDF_Views_Source::getFile', array($project.shortname, $commit, $fullpath)}
|
||||||
|
<p class="right soft"><a href="{$url}"><img style="vertical-align: text-bottom;" src="{media '/idf/img/package-grey.png'}" alt="{trans 'Archive'}" align="bottom" /></a> <a href="{$url}">{trans 'Download this file'}</a></p>
|
||||||
|
|
||||||
|
{/block}
|
||||||
|
{block context}
|
||||||
|
<p><strong>{trans 'Branches:'}</strong><br/>
|
||||||
|
{foreach $branches as $branch => $path}
|
||||||
|
{aurl 'url', 'IDF_Views_Source::treeBase', array($project.shortname, $branch)}
|
||||||
|
<span class="label{if in_array($branch, $tree_in)} active{/if}"><a href="{$url}" class="label">{if $path}{$path}{else}{$branch}{/if}</a></span><br/>
|
||||||
|
{/foreach}
|
||||||
|
</p>
|
||||||
|
{if $tags}
|
||||||
|
<p><strong>{trans 'Tags:'}</strong><br/>
|
||||||
|
{foreach $tags as $tag => $path}
|
||||||
|
{aurl 'url', 'IDF_Views_Source::treeBase', array($project.shortname, $tag)}
|
||||||
|
<span class="label{if in_array($tag, $tags_in)} active{/if}"><a href="{$url}" class="label">{if $path}{$path}{else}{$tag}{/if}</a></span><br/>
|
||||||
|
{/foreach}
|
||||||
|
</p>
|
||||||
|
{/if}
|
||||||
|
{/block}
|
||||||
|
|
||||||
|
{block javascript}
|
||||||
|
<script type="text/javascript" src="{media '/idf/js/prettify.js'}"></script>
|
||||||
|
<script type="text/javascript">prettyPrint();</script>
|
||||||
|
{/block}
|
38
src/IDF/templates/idf/source/mtn/help.html
Normal file
38
src/IDF/templates/idf/source/mtn/help.html
Normal file
@ -0,0 +1,38 @@
|
|||||||
|
{extends "idf/source/base.html"}
|
||||||
|
{block docclass}yui-t2{assign $inHelp=true}{/block}
|
||||||
|
{block body}
|
||||||
|
|
||||||
|
<p>{blocktrans}The team behind {$project} is using
|
||||||
|
the <strong>git</strong> software to manage the source
|
||||||
|
code.{/blocktrans}</p>
|
||||||
|
|
||||||
|
<h3>{trans 'Command-Line Access'}</h3>
|
||||||
|
|
||||||
|
<p><kbd>git clone {$project.getSourceAccessUrl($user)}</kbd></p>
|
||||||
|
|
||||||
|
{aurl 'url', 'IDF_Views_User::myAccount'}
|
||||||
|
<p>{blocktrans}You may need to <a href="{$url}">provide your SSH key</a>. The synchronization of your SSH key can take a couple of minutes. You can learn more about <a href="http://www.google.com/search?q=public+ssh+key+authentication">SSH key authentification</a>.{/blocktrans}</p>
|
||||||
|
|
||||||
|
{if $isOwner or $isMember}
|
||||||
|
<h3>{trans 'First Commit'}</h3>
|
||||||
|
|
||||||
|
<p>{blocktrans}To make a first commit in the repository, perform the following steps:{/blocktrans}</p>
|
||||||
|
|
||||||
|
<pre>
|
||||||
|
git init
|
||||||
|
git add .
|
||||||
|
git commit -m "initial import"
|
||||||
|
git remote add origin {$project.getWriteRemoteAccessUrl($url)}
|
||||||
|
git push origin master
|
||||||
|
</pre>
|
||||||
|
|
||||||
|
{/if}
|
||||||
|
|
||||||
|
{/block}
|
||||||
|
{block context}
|
||||||
|
<div class="issue-submit-info">
|
||||||
|
<p>{blocktrans}Find here more details on how to access {$project} source code.{/blocktrans}</p>
|
||||||
|
</div>
|
||||||
|
{/block}
|
||||||
|
|
||||||
|
|
69
src/IDF/templates/idf/source/mtn/tree.html
Normal file
69
src/IDF/templates/idf/source/mtn/tree.html
Normal file
@ -0,0 +1,69 @@
|
|||||||
|
{extends "idf/source/base.html"}
|
||||||
|
{block docclass}yui-t1{assign $inSourceTree=true}{/block}
|
||||||
|
{block body}
|
||||||
|
<h2 class="top"><a href="{url 'IDF_Views_Source::treeBase', array($project.shortname, $commit)}">{trans 'Root'}</a><span class="sep">/</span>{if $breadcrumb}{$breadcrumb|safe}{/if}</h2>
|
||||||
|
|
||||||
|
<table summary="" class="tree-list">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th colspan="2">{trans 'File'}</th>
|
||||||
|
<th>{trans 'Age'}</th>
|
||||||
|
<th>{trans 'Message'}</th>
|
||||||
|
<th>{trans 'Size'}</th>
|
||||||
|
</tr>
|
||||||
|
</thead>{if !$tree_in and !$tags_in}
|
||||||
|
{aurl 'url', 'IDF_Views_Source::commit', array($project.shortname, $commit)}
|
||||||
|
<tfoot>
|
||||||
|
<tr><th colspan="5">{blocktrans}Source at commit <a class="mono" href="{$url}">{$commit}</a> created {$cobject.date|dateago}.{/blocktrans}<br/>
|
||||||
|
<span class="smaller">{blocktrans}By {$cobject.author|strip_tags|trim}, {$cobject.title}{/blocktrans}</span>
|
||||||
|
</th></tr>
|
||||||
|
</tfoot>
|
||||||
|
{/if}<tbody>
|
||||||
|
{if $base}
|
||||||
|
<tr>
|
||||||
|
<td> </td>
|
||||||
|
<td>
|
||||||
|
<a href="{url 'IDF_Views_Source::tree', array($project.shortname, $commit, $prev)}">..</a></td>
|
||||||
|
<td colspan="3"></td>
|
||||||
|
</tr>
|
||||||
|
{/if}
|
||||||
|
{foreach $files as $file}
|
||||||
|
{aurl 'url', 'IDF_Views_Source::tree', array($project.shortname, $commit, $file.efullpath)}
|
||||||
|
<tr>
|
||||||
|
<td class="fileicon"><img src="{media '/idf/img/'~$file.type~'.png'}" alt="{$file.type}" /></td>
|
||||||
|
{if $file.type != 'extern'}
|
||||||
|
<td{if $file.type == 'tree'} colspan="4"{/if}><a href="{$url}">{$file.file}</a></td>{else}<td><a href="#" title="{$file.hash}">{$file.file}</a></td>{/if}
|
||||||
|
{if $file.type == 'blob'}
|
||||||
|
{if isset($file.date) and $file.log != '----'}
|
||||||
|
<td><span class="smaller">{$file.date|dateago:"wihtout"}</span></td>
|
||||||
|
<td><span class="smaller">{$file.author|strip_tags|trim}{trans ':'} {issuetext $file.log, $request, true, false}</span></td>
|
||||||
|
{else}<td colspan="2"></td>{/if}
|
||||||
|
<td>{$file.size|size}</td>{/if}
|
||||||
|
{if $file.type == 'extern'}
|
||||||
|
<td colspan="3">{$file.extern}</td>
|
||||||
|
{/if}
|
||||||
|
</tr>
|
||||||
|
{/foreach}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
{aurl 'url', 'IDF_Views_Source::download', array($project.shortname, $commit)}
|
||||||
|
<p class="right soft"><a href="{$url}"><img style="vertical-align: text-bottom;" src="{media '/idf/img/package-grey.png'}" alt="{trans 'Archive'}" align="bottom" /></a> <a href="{$url}">{trans 'Download this version'}</a> {trans 'or'} <kbd>git clone {$project.getSourceAccessUrl($user)}</kbd> <a href="{url 'IDF_Views_Source::help', array($project.shortname)}"><img style="vertical-align: text-bottom;" src="{media '/idf/img/help.png'}" alt="{trans 'Help'}" /></a></p>
|
||||||
|
|
||||||
|
|
||||||
|
{/block}
|
||||||
|
{block context}
|
||||||
|
<p><strong>{trans 'Branches:'}</strong><br/>
|
||||||
|
{foreach $branches as $branch => $path}
|
||||||
|
{aurl 'url', 'IDF_Views_Source::treeBase', array($project.shortname, $branch)}
|
||||||
|
<span class="label{if in_array($branch, $tree_in)} active{/if}"><a href="{$url}" class="label">{if $path}{$path}{else}{$branch}{/if}</a></span><br/>
|
||||||
|
{/foreach}
|
||||||
|
</p>
|
||||||
|
{if $tags}
|
||||||
|
<p><strong>{trans 'Tags:'}</strong><br/>
|
||||||
|
{foreach $tags as $tag => $path}
|
||||||
|
{aurl 'url', 'IDF_Views_Source::treeBase', array($project.shortname, $tag)}
|
||||||
|
<span class="label{if in_array($tag, $tags_in)} active{/if}"><a href="{$url}" class="label">{if $path}{$path}{else}{$tag}{/if}</a></span><br/>
|
||||||
|
{/foreach}
|
||||||
|
</p>
|
||||||
|
{/if}
|
||||||
|
{/block}
|
Loading…
Reference in New Issue
Block a user