2010-04-26 21:56:25 +00:00
|
|
|
<?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 ***** */
|
|
|
|
|
|
|
|
/**
|
2010-05-01 23:31:30 +00:00
|
|
|
* Monotone stdio class
|
2010-04-26 21:56:25 +00:00
|
|
|
*
|
2010-05-01 23:31:30 +00:00
|
|
|
* Connects to a monotone process and executes commands via its
|
|
|
|
* stdio interface
|
|
|
|
*
|
|
|
|
* @author Thomas Keller <me@thomaskeller.biz>
|
2010-04-26 21:56:25 +00:00
|
|
|
*/
|
2010-04-30 00:03:58 +00:00
|
|
|
class IDF_Scm_Monotone_Stdio
|
|
|
|
{
|
2010-05-01 23:31:30 +00:00
|
|
|
/** this is the most recent STDIO version. The number is output
|
|
|
|
at the protocol start. Older versions of monotone (prior 0.47)
|
|
|
|
do not output it and are therefor incompatible */
|
2010-04-30 00:03:58 +00:00
|
|
|
public static $SUPPORTED_STDIO_VERSION = 2;
|
|
|
|
|
2010-06-23 23:58:41 +00:00
|
|
|
private $project;
|
2010-04-30 00:03:58 +00:00
|
|
|
private $proc;
|
|
|
|
private $pipes;
|
|
|
|
private $oob;
|
|
|
|
private $cmdnum;
|
|
|
|
private $lastcmd;
|
|
|
|
|
2010-05-01 23:31:30 +00:00
|
|
|
/**
|
|
|
|
* Constructor - starts the stdio process
|
|
|
|
*
|
2010-06-23 23:58:41 +00:00
|
|
|
* @param IDF_Project
|
2010-05-01 23:31:30 +00:00
|
|
|
*/
|
2010-06-23 23:58:41 +00:00
|
|
|
public function __construct(IDF_Project $project)
|
2010-04-30 00:03:58 +00:00
|
|
|
{
|
2010-06-23 23:58:41 +00:00
|
|
|
$this->project = $project;
|
2010-04-30 00:03:58 +00:00
|
|
|
$this->start();
|
|
|
|
}
|
|
|
|
|
2010-05-01 23:31:30 +00:00
|
|
|
/**
|
|
|
|
* Destructor - stops the stdio process
|
|
|
|
*/
|
2010-04-30 00:03:58 +00:00
|
|
|
public function __destruct()
|
|
|
|
{
|
|
|
|
$this->stop();
|
|
|
|
}
|
|
|
|
|
2010-05-01 23:31:30 +00:00
|
|
|
/**
|
|
|
|
* Starts the stdio process and resets the command counter
|
|
|
|
*/
|
2010-04-30 00:03:58 +00:00
|
|
|
public function start()
|
|
|
|
{
|
|
|
|
if (is_resource($this->proc))
|
|
|
|
$this->stop();
|
|
|
|
|
2010-06-23 23:58:41 +00:00
|
|
|
$remote_db_access = Pluf::f('mtn_db_access', 'remote') == "remote";
|
|
|
|
|
|
|
|
$cmd = Pluf::f('idf_exec_cmd_prefix', '') .
|
|
|
|
Pluf::f('mtn_path', 'mtn') . ' ';
|
|
|
|
|
|
|
|
$opts = Pluf::f('mtn_opts', array());
|
|
|
|
foreach ($opts as $opt)
|
|
|
|
{
|
|
|
|
$cmd .= sprintf('%s ', escapeshellarg($opt));
|
|
|
|
}
|
|
|
|
|
|
|
|
// FIXME: we might want to add an option for anonymous / no key
|
|
|
|
// access, but upstream bug #30237 prevents that for now
|
|
|
|
if ($remote_db_access)
|
|
|
|
{
|
|
|
|
$host = sprintf(Pluf::f('mtn_remote_url'), $this->project->shortname);
|
|
|
|
$cmd .= sprintf('automate remote_stdio %s', escapeshellarg($host));
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
$repo = sprintf(Pluf::f('mtn_repositories'), $this->project->shortname);
|
|
|
|
if (!file_exists($repo))
|
|
|
|
{
|
|
|
|
throw new IDF_Scm_Exception(
|
|
|
|
"repository file '$repo' does not exist"
|
|
|
|
);
|
|
|
|
}
|
|
|
|
$cmd .= sprintf('--db %s automate stdio', escapeshellarg($repo));
|
|
|
|
}
|
2010-04-30 00:03:58 +00:00
|
|
|
|
|
|
|
$descriptors = array(
|
|
|
|
0 => array("pipe", "r"),
|
|
|
|
1 => array("pipe", "w"),
|
2010-06-21 21:24:47 +00:00
|
|
|
2 => array("pipe", "w"),
|
2010-04-30 00:03:58 +00:00
|
|
|
);
|
|
|
|
|
2010-06-23 22:10:41 +00:00
|
|
|
$env = array("LANG" => "en_US.UTF-8");
|
|
|
|
|
|
|
|
$this->proc = proc_open($cmd, $descriptors, $this->pipes,
|
|
|
|
null, $env);
|
2010-04-30 00:03:58 +00:00
|
|
|
|
|
|
|
if (!is_resource($this->proc))
|
|
|
|
{
|
|
|
|
throw new IDF_Scm_Exception("could not start stdio process");
|
|
|
|
}
|
|
|
|
|
|
|
|
$this->_checkVersion();
|
|
|
|
|
|
|
|
$this->cmdnum = -1;
|
|
|
|
}
|
|
|
|
|
2010-05-01 23:31:30 +00:00
|
|
|
/**
|
|
|
|
* Stops the stdio process and closes all pipes
|
|
|
|
*/
|
2010-04-30 00:03:58 +00:00
|
|
|
public function stop()
|
|
|
|
{
|
|
|
|
if (!is_resource($this->proc))
|
|
|
|
return;
|
|
|
|
|
|
|
|
fclose($this->pipes[0]);
|
|
|
|
fclose($this->pipes[1]);
|
2010-06-21 21:24:47 +00:00
|
|
|
fclose($this->pipes[2]);
|
2010-04-30 00:03:58 +00:00
|
|
|
|
|
|
|
proc_close($this->proc);
|
|
|
|
$this->proc = null;
|
|
|
|
}
|
|
|
|
|
2010-05-01 23:31:30 +00:00
|
|
|
/**
|
|
|
|
* select()'s on stdout and returns true as soon as we got new
|
|
|
|
* data to read, false if the select() timed out
|
|
|
|
*
|
|
|
|
* @return boolean
|
|
|
|
* @throws IDF_Scm_Exception
|
|
|
|
*/
|
2010-05-01 22:56:04 +00:00
|
|
|
private function _waitForReadyRead()
|
|
|
|
{
|
|
|
|
if (!is_resource($this->pipes[1]))
|
|
|
|
return false;
|
|
|
|
|
2010-06-21 21:24:47 +00:00
|
|
|
$read = array($this->pipes[1], $this->pipes[2]);
|
2010-05-01 22:56:04 +00:00
|
|
|
$streamsChanged = stream_select(
|
2010-06-21 21:24:47 +00:00
|
|
|
$read, $write = null, $except = null, 0, 20000
|
2010-05-01 22:56:04 +00:00
|
|
|
);
|
|
|
|
|
|
|
|
if ($streamsChanged === false)
|
|
|
|
{
|
|
|
|
throw new IDF_Scm_Exception(
|
|
|
|
"Could not select() on read pipe"
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
if ($streamsChanged == 0)
|
|
|
|
{
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
2010-05-01 23:31:30 +00:00
|
|
|
/**
|
|
|
|
* Checks the version of the used stdio protocol
|
|
|
|
*
|
|
|
|
* @throws IDF_Scm_Exception
|
|
|
|
*/
|
2010-04-30 00:03:58 +00:00
|
|
|
private function _checkVersion()
|
|
|
|
{
|
2010-05-01 22:56:04 +00:00
|
|
|
$this->_waitForReadyRead();
|
|
|
|
|
2010-04-30 00:03:58 +00:00
|
|
|
$version = fgets($this->pipes[1]);
|
2010-06-21 21:24:47 +00:00
|
|
|
if ($version === false)
|
|
|
|
{
|
|
|
|
throw new IDF_Scm_Exception(
|
2010-06-23 22:10:41 +00:00
|
|
|
"Could not determine stdio version, stderr is:\n".
|
|
|
|
$this->_readStderr()
|
2010-06-21 21:24:47 +00:00
|
|
|
);
|
|
|
|
}
|
|
|
|
|
2010-04-30 00:03:58 +00:00
|
|
|
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]."'"
|
|
|
|
);
|
|
|
|
}
|
2010-05-01 22:56:04 +00:00
|
|
|
|
2010-04-30 00:03:58 +00:00
|
|
|
fgets($this->pipes[1]);
|
|
|
|
}
|
|
|
|
|
2010-05-01 23:31:30 +00:00
|
|
|
/**
|
|
|
|
* Writes a command to stdio
|
|
|
|
*
|
|
|
|
* @param array
|
|
|
|
* @param array
|
|
|
|
* @throws IDF_Scm_Exception
|
|
|
|
*/
|
|
|
|
private function _write(array $args, array $options = array())
|
2010-04-30 00:03:58 +00:00
|
|
|
{
|
|
|
|
$cmd = "";
|
|
|
|
if (count($options) > 0)
|
|
|
|
{
|
|
|
|
$cmd = "o";
|
2010-04-30 23:05:54 +00:00
|
|
|
foreach ($options as $k => $vals)
|
2010-04-30 00:03:58 +00:00
|
|
|
{
|
2010-04-30 23:05:54 +00:00
|
|
|
if (!is_array($vals))
|
|
|
|
$vals = array($vals);
|
|
|
|
|
|
|
|
foreach ($vals as $v)
|
|
|
|
{
|
|
|
|
$cmd .= strlen((string)$k) . ":" . (string)$k;
|
|
|
|
$cmd .= strlen((string)$v) . ":" . (string)$v;
|
|
|
|
}
|
2010-04-30 00:03:58 +00:00
|
|
|
}
|
|
|
|
$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++;
|
|
|
|
}
|
|
|
|
|
2010-06-23 22:10:41 +00:00
|
|
|
/**
|
|
|
|
* Reads all output from stderr and returns it
|
|
|
|
*
|
|
|
|
* @return string
|
|
|
|
*/
|
|
|
|
private function _readStderr()
|
|
|
|
{
|
|
|
|
$err = "";
|
|
|
|
while (($line = fgets($this->pipes[2])) !== false)
|
|
|
|
{
|
|
|
|
$err .= $line;
|
|
|
|
}
|
|
|
|
return empty($err) ? "<empty>" : $err;
|
|
|
|
}
|
|
|
|
|
2010-05-01 23:31:30 +00:00
|
|
|
/**
|
|
|
|
* Reads the last output from the stdio process, parses and returns it
|
|
|
|
*
|
|
|
|
* @return string
|
|
|
|
* @throws IDF_Scm_Exception
|
|
|
|
*/
|
2010-06-23 22:10:41 +00:00
|
|
|
private function _readStdout()
|
2010-04-30 00:03:58 +00:00
|
|
|
{
|
|
|
|
$this->oob = array('w' => array(),
|
|
|
|
'p' => array(),
|
|
|
|
't' => array(),
|
|
|
|
'e' => array());
|
|
|
|
|
|
|
|
$output = "";
|
|
|
|
$errcode = 0;
|
|
|
|
|
|
|
|
while (true)
|
|
|
|
{
|
2010-05-01 22:56:04 +00:00
|
|
|
if (!$this->_waitForReadyRead())
|
2010-04-30 00:03:58 +00:00
|
|
|
continue;
|
|
|
|
|
|
|
|
$data = array(0,"",0);
|
|
|
|
$idx = 0;
|
|
|
|
while (true)
|
|
|
|
{
|
|
|
|
$c = fgetc($this->pipes[1]);
|
2010-06-21 21:24:47 +00:00
|
|
|
if ($c === false)
|
|
|
|
{
|
|
|
|
throw new IDF_Scm_Exception(
|
2010-06-23 22:10:41 +00:00
|
|
|
"No data on stdin, stderr is:\n".
|
|
|
|
$this->_readStderr()
|
|
|
|
);
|
2010-06-21 21:24:47 +00:00
|
|
|
}
|
|
|
|
|
2010-04-30 00:03:58 +00:00
|
|
|
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;
|
|
|
|
}
|
|
|
|
|
2010-05-01 23:31:30 +00:00
|
|
|
/**
|
|
|
|
* Executes a command over stdio and returns its result
|
|
|
|
*
|
|
|
|
* @param array Array of arguments
|
|
|
|
* @param array Array of options as key-value pairs. Multiple options
|
|
|
|
* can be defined in sub-arrays, like
|
|
|
|
* "r" => array("123...", "456...")
|
|
|
|
* @return string
|
|
|
|
*/
|
|
|
|
public function exec(array $args, array $options = array())
|
2010-04-30 00:03:58 +00:00
|
|
|
{
|
|
|
|
$this->_write($args, $options);
|
2010-06-23 22:10:41 +00:00
|
|
|
return $this->_readStdout();
|
2010-04-30 00:03:58 +00:00
|
|
|
}
|
|
|
|
|
2010-05-01 23:31:30 +00:00
|
|
|
/**
|
|
|
|
* Returns the last out-of-band output for a previously executed
|
|
|
|
* command as associative array with 'e' (error), 'w' (warning),
|
|
|
|
* 'p' (progress) and 't' (ticker, unparsed) as keys
|
|
|
|
*
|
|
|
|
* @return array
|
|
|
|
*/
|
|
|
|
public function getLastOutOfBandOutput()
|
2010-04-30 00:03:58 +00:00
|
|
|
{
|
2010-05-01 23:31:30 +00:00
|
|
|
return $this->oob;
|
2010-04-30 00:03:58 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2010-05-01 23:31:30 +00:00
|
|
|
/**
|
|
|
|
* Monotone scm class
|
|
|
|
*
|
|
|
|
* @author Thomas Keller <me@thomaskeller.biz>
|
|
|
|
*/
|
2010-04-26 21:56:25 +00:00
|
|
|
class IDF_Scm_Monotone extends IDF_Scm
|
|
|
|
{
|
2010-05-01 23:31:30 +00:00
|
|
|
/** the minimum supported interface version */
|
2010-04-28 23:44:34 +00:00
|
|
|
public static $MIN_INTERFACE_VERSION = 12.0;
|
2010-04-26 21:56:25 +00:00
|
|
|
|
2010-04-30 00:03:58 +00:00
|
|
|
private $stdio;
|
|
|
|
|
2010-06-24 00:27:38 +00:00
|
|
|
private static $instances = array();
|
|
|
|
|
2010-05-01 23:31:30 +00:00
|
|
|
/**
|
|
|
|
* @see IDF_Scm::__construct()
|
|
|
|
*/
|
2010-06-23 23:58:41 +00:00
|
|
|
public function __construct($project)
|
2010-04-26 21:56:25 +00:00
|
|
|
{
|
|
|
|
$this->project = $project;
|
2010-06-23 23:58:41 +00:00
|
|
|
$this->stdio = new IDF_Scm_Monotone_Stdio($project);
|
2010-04-26 21:56:25 +00:00
|
|
|
}
|
|
|
|
|
2010-05-01 23:31:30 +00:00
|
|
|
/**
|
|
|
|
* @see IDF_Scm::getRepositorySize()
|
|
|
|
*/
|
2010-04-26 21:56:25 +00:00
|
|
|
public function getRepositorySize()
|
|
|
|
{
|
2010-06-23 23:58:41 +00:00
|
|
|
// FIXME: this obviously won't work with remote databases - upstream
|
|
|
|
// needs to implement mtn db info in automate at first
|
|
|
|
$repo = sprintf(Pluf::f('mtn_repositories'), $this->project->shortname);
|
|
|
|
if (!file_exists($repo))
|
|
|
|
{
|
2010-04-26 21:56:25 +00:00
|
|
|
return 0;
|
|
|
|
}
|
2010-05-01 23:31:30 +00:00
|
|
|
|
2010-04-26 21:56:25 +00:00
|
|
|
$cmd = Pluf::f('idf_exec_cmd_prefix', '').'du -sk '
|
2010-06-23 23:58:41 +00:00
|
|
|
.escapeshellarg($repo);
|
2010-04-26 21:56:25 +00:00
|
|
|
$out = explode(' ',
|
|
|
|
self::shell_exec('IDF_Scm_Monotone::getRepositorySize', $cmd),
|
|
|
|
2);
|
|
|
|
return (int) $out[0]*1024;
|
|
|
|
}
|
|
|
|
|
2010-05-01 23:31:30 +00:00
|
|
|
/**
|
|
|
|
* @see IDF_Scm::isAvailable()
|
|
|
|
*/
|
2010-04-26 21:56:25 +00:00
|
|
|
public function isAvailable()
|
|
|
|
{
|
2010-04-30 00:03:58 +00:00
|
|
|
try
|
|
|
|
{
|
|
|
|
$out = $this->stdio->exec(array("interface_version"));
|
|
|
|
return floatval($out) >= self::$MIN_INTERFACE_VERSION;
|
2010-04-26 21:56:25 +00:00
|
|
|
}
|
2010-04-30 00:03:58 +00:00
|
|
|
catch (IDF_Scm_Exception $e) {}
|
2010-04-28 23:44:34 +00:00
|
|
|
|
2010-04-30 00:03:58 +00:00
|
|
|
return false;
|
2010-04-26 21:56:25 +00:00
|
|
|
}
|
|
|
|
|
2010-05-01 23:31:30 +00:00
|
|
|
/**
|
|
|
|
* @see IDF_Scm::getBranches()
|
|
|
|
*/
|
2010-04-26 21:56:25 +00:00
|
|
|
public function getBranches()
|
|
|
|
{
|
|
|
|
if (isset($this->cache['branches'])) {
|
|
|
|
return $this->cache['branches'];
|
|
|
|
}
|
2010-05-01 23:31:30 +00:00
|
|
|
// FIXME: we could / should introduce handling of suspended
|
|
|
|
// (i.e. dead) branches here by hiding them from the user's eye...
|
2010-04-30 00:03:58 +00:00
|
|
|
$out = $this->stdio->exec(array("branches"));
|
|
|
|
|
2010-05-01 23:31:30 +00:00
|
|
|
// note: we could expand each branch with one of its head revisions
|
2010-04-26 21:56:25 +00:00
|
|
|
// 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
|
2010-04-30 00:03:58 +00:00
|
|
|
$res = array();
|
|
|
|
foreach (preg_split("/\n/", $out, -1, PREG_SPLIT_NO_EMPTY) as $b)
|
|
|
|
{
|
2010-04-26 21:56:25 +00:00
|
|
|
$res["h:$b"] = $b;
|
|
|
|
}
|
2010-04-30 00:03:58 +00:00
|
|
|
|
2010-04-26 21:56:25 +00:00
|
|
|
$this->cache['branches'] = $res;
|
|
|
|
return $res;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* monotone has no concept of a "main" branch, so just return
|
2010-06-22 22:22:10 +00:00
|
|
|
* the configured one. Ensure however that we can select revisions
|
|
|
|
* with it at all.
|
2010-04-26 21:56:25 +00:00
|
|
|
*
|
2010-05-01 23:31:30 +00:00
|
|
|
* @see IDF_Scm::getMainBranch()
|
2010-04-26 21:56:25 +00:00
|
|
|
*/
|
|
|
|
public function getMainBranch()
|
|
|
|
{
|
2010-05-01 23:31:30 +00:00
|
|
|
$conf = $this->project->getConf();
|
|
|
|
if (false === ($branch = $conf->getVal('mtn_master_branch', false))
|
|
|
|
|| empty($branch)) {
|
|
|
|
$branch = "*";
|
|
|
|
}
|
2010-06-22 22:22:10 +00:00
|
|
|
|
|
|
|
if (count($this->_resolveSelector("h:$branch")) == 0)
|
|
|
|
{
|
|
|
|
throw new IDF_Scm_Exception(
|
|
|
|
"Branch $branch is empty"
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
2010-05-01 23:31:30 +00:00
|
|
|
return $branch;
|
2010-04-26 21:56:25 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* expands a selector or a partial revision id to zero, one or
|
|
|
|
* multiple 40 byte revision ids
|
|
|
|
*
|
|
|
|
* @param string $selector
|
|
|
|
* @return array
|
|
|
|
*/
|
2010-04-28 23:44:34 +00:00
|
|
|
private function _resolveSelector($selector)
|
2010-04-26 21:56:25 +00:00
|
|
|
{
|
2010-04-30 00:03:58 +00:00
|
|
|
$out = $this->stdio->exec(array("select", $selector));
|
|
|
|
return preg_split("/\n/", $out, -1, PREG_SPLIT_NO_EMPTY);
|
2010-04-26 21:56:25 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* 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;
|
|
|
|
}
|
|
|
|
}
|
2010-04-30 23:05:54 +00:00
|
|
|
|
|
|
|
for ($i = 0; $i <= $valCount; $i++)
|
|
|
|
{
|
|
|
|
$stanzaLine['values'][$i] = str_replace(
|
|
|
|
array("\\\\", "\\\""),
|
|
|
|
array("\\", "\""),
|
|
|
|
$stanzaLine['values'][$i]
|
|
|
|
);
|
|
|
|
}
|
2010-04-26 21:56:25 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
$stanza[] = $stanzaLine;
|
|
|
|
++$pos; // newline
|
|
|
|
}
|
|
|
|
$stanzas[] = $stanza;
|
|
|
|
++$pos; // newline
|
|
|
|
}
|
|
|
|
return $stanzas;
|
|
|
|
}
|
|
|
|
|
2010-05-01 23:31:30 +00:00
|
|
|
/**
|
|
|
|
* Queries the certs for a given revision and returns them in an
|
|
|
|
* associative array array("branch" => array("branch1", ...), ...)
|
|
|
|
*
|
|
|
|
* @param string
|
|
|
|
* @param array
|
|
|
|
*/
|
2010-04-28 23:44:34 +00:00
|
|
|
private function _getCerts($rev)
|
2010-04-26 21:56:25 +00:00
|
|
|
{
|
2010-04-28 23:44:34 +00:00
|
|
|
static $certCache = array();
|
|
|
|
|
|
|
|
if (!array_key_exists($rev, $certCache))
|
2010-04-26 21:56:25 +00:00
|
|
|
{
|
2010-04-30 00:03:58 +00:00
|
|
|
$out = $this->stdio->exec(array("certs", $rev));
|
2010-04-26 21:56:25 +00:00
|
|
|
|
2010-04-30 00:03:58 +00:00
|
|
|
$stanzas = self::_parseBasicIO($out);
|
2010-04-28 23:44:34 +00:00
|
|
|
$certs = array();
|
2010-04-26 21:56:25 +00:00
|
|
|
foreach ($stanzas as $stanza)
|
|
|
|
{
|
2010-04-28 23:44:34 +00:00
|
|
|
$certname = null;
|
2010-04-26 21:56:25 +00:00
|
|
|
foreach ($stanza as $stanzaline)
|
|
|
|
{
|
|
|
|
// luckily, name always comes before value
|
2010-04-28 23:44:34 +00:00
|
|
|
if ($stanzaline['key'] == "name")
|
2010-04-26 21:56:25 +00:00
|
|
|
{
|
2010-04-28 23:44:34 +00:00
|
|
|
$certname = $stanzaline['values'][0];
|
|
|
|
continue;
|
2010-04-26 21:56:25 +00:00
|
|
|
}
|
2010-04-28 23:44:34 +00:00
|
|
|
|
2010-04-26 21:56:25 +00:00
|
|
|
if ($stanzaline['key'] == "value")
|
|
|
|
{
|
2010-04-28 23:44:34 +00:00
|
|
|
if (!array_key_exists($certname, $certs))
|
|
|
|
{
|
|
|
|
$certs[$certname] = array();
|
|
|
|
}
|
|
|
|
|
|
|
|
$certs[$certname][] = $stanzaline['values'][0];
|
2010-04-26 21:56:25 +00:00
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2010-04-28 23:44:34 +00:00
|
|
|
$certCache[$rev] = $certs;
|
|
|
|
}
|
|
|
|
|
|
|
|
return $certCache[$rev];
|
|
|
|
}
|
|
|
|
|
2010-05-01 23:31:30 +00:00
|
|
|
/**
|
|
|
|
* Returns unique certificate values for the given revs and the specific
|
|
|
|
* cert name
|
|
|
|
*
|
|
|
|
* @param array
|
|
|
|
* @param string
|
|
|
|
* @return array
|
|
|
|
*/
|
2010-04-28 23:44:34 +00:00
|
|
|
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]);
|
2010-04-26 21:56:25 +00:00
|
|
|
}
|
|
|
|
return array_unique($certValues);
|
|
|
|
}
|
|
|
|
|
2010-05-01 23:31:30 +00:00
|
|
|
/**
|
|
|
|
* Returns the revision in which the file has been last changed,
|
|
|
|
* starting from the start rev
|
|
|
|
*
|
|
|
|
* @param string
|
|
|
|
* @param string
|
|
|
|
* @return string
|
|
|
|
*/
|
2010-04-28 23:44:34 +00:00
|
|
|
private function _getLastChangeFor($file, $startrev)
|
|
|
|
{
|
2010-04-30 00:03:58 +00:00
|
|
|
$out = $this->stdio->exec(array(
|
|
|
|
"get_content_changed", $startrev, $file
|
|
|
|
));
|
2010-04-28 23:44:34 +00:00
|
|
|
|
2010-04-30 00:03:58 +00:00
|
|
|
$stanzas = self::_parseBasicIO($out);
|
2010-04-28 23:44:34 +00:00
|
|
|
|
|
|
|
// FIXME: we only care about the first returned content mark
|
2010-05-01 23:31:30 +00:00
|
|
|
// everything else seem to be very, very rare cases
|
2010-04-28 23:44:34 +00:00
|
|
|
foreach ($stanzas as $stanza)
|
|
|
|
{
|
|
|
|
foreach ($stanza as $stanzaline)
|
|
|
|
{
|
|
|
|
if ($stanzaline['key'] == "content_mark")
|
|
|
|
{
|
|
|
|
return $stanzaline['hash'];
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return null;
|
|
|
|
}
|
|
|
|
|
2010-04-26 21:56:25 +00:00
|
|
|
/**
|
|
|
|
* @see IDF_Scm::inBranches()
|
2010-05-01 23:31:30 +00:00
|
|
|
*/
|
2010-04-26 21:56:25 +00:00
|
|
|
public function inBranches($commit, $path)
|
|
|
|
{
|
2010-04-28 23:44:34 +00:00
|
|
|
$revs = $this->_resolveSelector($commit);
|
2010-04-26 21:56:25 +00:00
|
|
|
if (count($revs) == 0) return array();
|
2010-04-28 23:44:34 +00:00
|
|
|
return $this->_getUniqueCertValuesFor($revs, "branch");
|
2010-04-26 21:56:25 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @see IDF_Scm::getTags()
|
2010-05-01 23:31:30 +00:00
|
|
|
*/
|
2010-04-26 21:56:25 +00:00
|
|
|
public function getTags()
|
|
|
|
{
|
2010-04-30 00:03:58 +00:00
|
|
|
if (isset($this->cache['tags']))
|
|
|
|
{
|
2010-04-26 21:56:25 +00:00
|
|
|
return $this->cache['tags'];
|
|
|
|
}
|
2010-04-30 00:03:58 +00:00
|
|
|
|
|
|
|
$out = $this->stdio->exec(array("tags"));
|
2010-04-26 21:56:25 +00:00
|
|
|
|
|
|
|
$tags = array();
|
2010-04-30 00:03:58 +00:00
|
|
|
$stanzas = self::_parseBasicIO($out);
|
2010-04-26 21:56:25 +00:00
|
|
|
foreach ($stanzas as $stanza)
|
|
|
|
{
|
2010-04-28 23:44:34 +00:00
|
|
|
$tagname = null;
|
2010-04-26 21:56:25 +00:00
|
|
|
foreach ($stanza as $stanzaline)
|
|
|
|
{
|
2010-04-28 23:44:34 +00:00
|
|
|
// revision comes directly after the tag stanza
|
2010-04-26 21:56:25 +00:00
|
|
|
if ($stanzaline['key'] == "tag")
|
|
|
|
{
|
2010-04-28 23:44:34 +00:00
|
|
|
$tagname = $stanzaline['values'][0];
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
if ($stanzaline['key'] == "revision")
|
|
|
|
{
|
|
|
|
$tags[$stanzaline['hash']] = $tagname;
|
2010-04-26 21:56:25 +00:00
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
$this->cache['tags'] = $tags;
|
|
|
|
return $tags;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @see IDF_Scm::inTags()
|
2010-05-01 23:31:30 +00:00
|
|
|
*/
|
2010-04-26 21:56:25 +00:00
|
|
|
public function inTags($commit, $path)
|
|
|
|
{
|
2010-04-28 23:44:34 +00:00
|
|
|
$revs = $this->_resolveSelector($commit);
|
2010-04-26 21:56:25 +00:00
|
|
|
if (count($revs) == 0) return array();
|
2010-04-28 23:44:34 +00:00
|
|
|
return $this->_getUniqueCertValuesFor($revs, "tag");
|
2010-04-26 21:56:25 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @see IDF_Scm::getTree()
|
|
|
|
*/
|
|
|
|
public function getTree($commit, $folder='/', $branch=null)
|
|
|
|
{
|
2010-04-28 23:44:34 +00:00
|
|
|
$revs = $this->_resolveSelector($commit);
|
|
|
|
if (count($revs) == 0)
|
2010-04-26 21:56:25 +00:00
|
|
|
{
|
2010-04-28 23:44:34 +00:00
|
|
|
return array();
|
2010-04-26 21:56:25 +00:00
|
|
|
}
|
|
|
|
|
2010-04-30 00:03:58 +00:00
|
|
|
$out = $this->stdio->exec(array(
|
|
|
|
"get_manifest_of", $revs[0]
|
|
|
|
));
|
2010-04-26 21:56:25 +00:00
|
|
|
|
|
|
|
$files = array();
|
2010-04-30 00:03:58 +00:00
|
|
|
$stanzas = self::_parseBasicIO($out);
|
2010-04-26 21:56:25 +00:00
|
|
|
$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")
|
2010-04-28 23:44:34 +00:00
|
|
|
{
|
|
|
|
$file['type'] = "tree";
|
|
|
|
$file['size'] = 0;
|
|
|
|
}
|
2010-04-26 21:56:25 +00:00
|
|
|
else
|
2010-04-28 23:44:34 +00:00
|
|
|
{
|
|
|
|
$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)
|
2010-06-28 06:47:39 +00:00
|
|
|
$dates[] = date('Y-m-d H:i:s', strtotime($date));
|
2010-04-28 23:44:34 +00:00
|
|
|
$file['date'] = implode(', ', $dates);
|
2010-04-29 21:38:28 +00:00
|
|
|
$file['log'] = implode("\n---\n", $certs['changelog']);
|
2010-04-26 21:56:25 +00:00
|
|
|
}
|
2010-04-28 23:44:34 +00:00
|
|
|
|
2010-04-26 21:56:25 +00:00
|
|
|
$files[] = (object) $file;
|
|
|
|
}
|
|
|
|
return $files;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
2010-05-01 23:31:30 +00:00
|
|
|
* @see IDF_Scm::findAuthor()
|
2010-04-26 21:56:25 +00:00
|
|
|
*/
|
|
|
|
public function findAuthor($author)
|
|
|
|
{
|
2010-04-28 23:44:34 +00:00
|
|
|
// We extract anything which looks like an email.
|
2010-04-26 21:56:25 +00:00
|
|
|
$match = array();
|
2010-04-28 23:44:34 +00:00
|
|
|
if (!preg_match('/([^ ]+@[^ ]+)/', $author, $match)) {
|
2010-04-26 21:56:25 +00:00
|
|
|
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;
|
|
|
|
}
|
|
|
|
|
2010-05-01 23:31:30 +00:00
|
|
|
/**
|
|
|
|
* @see IDF_Scm::getAnonymousAccessUrl()
|
|
|
|
*/
|
2010-05-01 22:56:04 +00:00
|
|
|
public static function getAnonymousAccessUrl($project, $commit = null)
|
2010-04-28 23:44:34 +00:00
|
|
|
{
|
2010-05-01 23:31:30 +00:00
|
|
|
$scm = IDF_Scm::get($project);
|
|
|
|
$branch = $scm->getMainBranch();
|
|
|
|
|
2010-05-01 22:56:04 +00:00
|
|
|
if (!empty($commit))
|
|
|
|
{
|
|
|
|
$revs = $scm->_resolveSelector($commit);
|
|
|
|
if (count($revs) > 0)
|
|
|
|
{
|
|
|
|
$certs = $scm->_getCerts($revs[0]);
|
|
|
|
// for the very seldom case that a revision
|
|
|
|
// has no branch certificate
|
|
|
|
if (count($certs['branch']) == 0)
|
|
|
|
{
|
|
|
|
$branch = "*";
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
$branch = $certs['branch'][0];
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2010-06-23 23:58:41 +00:00
|
|
|
$remote_url = Pluf::f('mtn_remote_url', '');
|
|
|
|
if (empty($remote_url))
|
2010-05-01 22:56:04 +00:00
|
|
|
{
|
2010-06-23 23:58:41 +00:00
|
|
|
return '';
|
|
|
|
}
|
|
|
|
|
|
|
|
return sprintf($remote_url, $project->shortname)."?".$branch;
|
2010-04-26 21:56:25 +00:00
|
|
|
}
|
|
|
|
|
2010-05-01 23:31:30 +00:00
|
|
|
/**
|
|
|
|
* @see IDF_Scm::getAuthAccessUrl()
|
|
|
|
*/
|
2010-05-01 22:56:04 +00:00
|
|
|
public static function getAuthAccessUrl($project, $user, $commit = null)
|
2010-04-26 21:56:25 +00:00
|
|
|
{
|
2010-06-23 23:58:41 +00:00
|
|
|
$url = self::getAnonymousAccessUrl($project, $commit);
|
|
|
|
return preg_replace("#^ssh://#", "ssh://$user@", $url);
|
2010-04-26 21:56:25 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Returns this object correctly initialized for the project.
|
|
|
|
*
|
|
|
|
* @param IDF_Project
|
|
|
|
* @return IDF_Scm_Monotone
|
|
|
|
*/
|
|
|
|
public static function factory($project)
|
|
|
|
{
|
2010-06-24 00:27:38 +00:00
|
|
|
if (!array_key_exists($project->shortname, self::$instances))
|
|
|
|
{
|
|
|
|
self::$instances[$project->shortname] =
|
|
|
|
new IDF_Scm_Monotone($project);
|
|
|
|
}
|
|
|
|
return self::$instances[$project->shortname];
|
2010-04-26 21:56:25 +00:00
|
|
|
}
|
|
|
|
|
2010-05-01 23:31:30 +00:00
|
|
|
/**
|
|
|
|
* @see IDF_Scm::isValidRevision()
|
|
|
|
*/
|
2010-04-26 21:56:25 +00:00
|
|
|
public function isValidRevision($commit)
|
|
|
|
{
|
2010-04-28 23:44:34 +00:00
|
|
|
$revs = $this->_resolveSelector($commit);
|
|
|
|
return count($revs) == 1;
|
2010-04-26 21:56:25 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
2010-05-01 23:31:30 +00:00
|
|
|
* @see IDF_Scm::getPathInfo()
|
2010-04-26 21:56:25 +00:00
|
|
|
*/
|
2010-04-28 23:44:34 +00:00
|
|
|
public function getPathInfo($file, $commit = null)
|
2010-04-26 21:56:25 +00:00
|
|
|
{
|
2010-04-28 23:44:34 +00:00
|
|
|
if ($commit === null) {
|
2010-05-01 23:31:30 +00:00
|
|
|
$commit = 'h:' . $this->getMainBranch();
|
2010-04-28 23:44:34 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
$revs = $this->_resolveSelector($commit);
|
|
|
|
if (count($revs) == 0)
|
|
|
|
return false;
|
|
|
|
|
2010-04-30 00:03:58 +00:00
|
|
|
$out = $this->stdio->exec(array(
|
|
|
|
"get_manifest_of", $revs[0]
|
|
|
|
));
|
2010-04-28 23:44:34 +00:00
|
|
|
|
|
|
|
$files = array();
|
2010-04-30 00:03:58 +00:00
|
|
|
$stanzas = self::_parseBasicIO($out);
|
2010-04-28 23:44:34 +00:00
|
|
|
|
|
|
|
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));
|
2010-04-26 21:56:25 +00:00
|
|
|
}
|
2010-04-28 23:44:34 +00:00
|
|
|
|
|
|
|
$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)
|
2010-06-28 06:47:39 +00:00
|
|
|
$dates[] = date('Y-m-d H:i:s', strtotime($date));
|
2010-04-28 23:44:34 +00:00
|
|
|
$file['date'] = implode(', ', $dates);
|
2010-04-29 21:38:28 +00:00
|
|
|
$file['log'] = implode("\n---\n", $certs['changelog']);
|
2010-04-28 23:44:34 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
return (object) $file;
|
2010-04-26 21:56:25 +00:00
|
|
|
}
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
2010-05-01 23:31:30 +00:00
|
|
|
/**
|
|
|
|
* @see IDF_Scm::getFile()
|
|
|
|
*/
|
2010-04-26 21:56:25 +00:00
|
|
|
public function getFile($def, $cmd_only=false)
|
|
|
|
{
|
2010-04-30 00:03:58 +00:00
|
|
|
// this won't work with remote databases
|
|
|
|
if ($cmd_only)
|
|
|
|
{
|
|
|
|
throw new Pluf_Exception_NotImplemented();
|
|
|
|
}
|
|
|
|
|
|
|
|
return $this->stdio->exec(array("get_file", $def->hash));
|
2010-04-26 21:56:25 +00:00
|
|
|
}
|
|
|
|
|
2010-05-01 23:31:30 +00:00
|
|
|
/**
|
|
|
|
* Returns the differences between two revisions as unified diff
|
|
|
|
*
|
|
|
|
* @param string The target of the diff
|
|
|
|
* @param string The source of the diff, if not given, the first
|
|
|
|
* parent of the target is used
|
|
|
|
* @return string
|
|
|
|
*/
|
2010-04-28 23:44:34 +00:00
|
|
|
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 "";
|
|
|
|
}
|
|
|
|
|
2010-04-30 00:03:58 +00:00
|
|
|
return $this->stdio->exec(
|
|
|
|
array("content_diff"),
|
2010-04-30 23:05:54 +00:00
|
|
|
array("r" => array($sources[0], $targets[0]))
|
2010-04-30 00:03:58 +00:00
|
|
|
);
|
2010-04-28 23:44:34 +00:00
|
|
|
}
|
|
|
|
|
2010-04-26 21:56:25 +00:00
|
|
|
/**
|
2010-05-01 23:31:30 +00:00
|
|
|
* @see IDF_Scm::getCommit()
|
2010-04-26 21:56:25 +00:00
|
|
|
*/
|
|
|
|
public function getCommit($commit, $getdiff=false)
|
|
|
|
{
|
2010-04-28 23:44:34 +00:00
|
|
|
$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)
|
2010-06-28 06:47:39 +00:00
|
|
|
$dates[] = date('Y-m-d H:i:s', strtotime($date));
|
2010-04-28 23:44:34 +00:00
|
|
|
$res['date'] = implode(', ', $dates);
|
|
|
|
|
2010-04-29 21:38:28 +00:00
|
|
|
$res['title'] = implode("\n---\n", $certs['changelog']);
|
2010-04-28 23:44:34 +00:00
|
|
|
|
|
|
|
$res['commit'] = $revs[0];
|
|
|
|
|
|
|
|
$res['changes'] = ($getdiff) ? $this->_getDiff($revs[0]) : '';
|
|
|
|
|
|
|
|
return (object) $res;
|
2010-04-26 21:56:25 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
2010-05-01 23:31:30 +00:00
|
|
|
* @see IDF_Scm::isCommitLarge()
|
2010-04-26 21:56:25 +00:00
|
|
|
*/
|
2010-04-28 23:44:34 +00:00
|
|
|
public function isCommitLarge($commit=null)
|
2010-04-26 21:56:25 +00:00
|
|
|
{
|
2010-04-28 23:44:34 +00:00
|
|
|
if (empty($commit))
|
|
|
|
{
|
2010-05-01 23:31:30 +00:00
|
|
|
$commit = "h:"+$this->getMainBranch();
|
2010-04-26 21:56:25 +00:00
|
|
|
}
|
2010-04-28 23:44:34 +00:00
|
|
|
|
|
|
|
$revs = $this->_resolveSelector($commit);
|
|
|
|
if (count($revs) == 0)
|
|
|
|
return false;
|
|
|
|
|
2010-04-30 00:03:58 +00:00
|
|
|
$out = $this->stdio->exec(array(
|
|
|
|
"get_revision", $revs[0]
|
|
|
|
));
|
2010-04-28 23:44:34 +00:00
|
|
|
|
|
|
|
$newAndPatchedFiles = 0;
|
2010-04-30 00:03:58 +00:00
|
|
|
$stanzas = self::_parseBasicIO($out);
|
2010-04-28 23:44:34 +00:00
|
|
|
|
|
|
|
foreach ($stanzas as $stanza)
|
|
|
|
{
|
|
|
|
if ($stanza[0]['key'] == "patch" || $stanza[0]['key'] == "add_file")
|
|
|
|
$newAndPatchedFiles++;
|
|
|
|
}
|
|
|
|
|
|
|
|
return $newAndPatchedFiles > 100;
|
2010-04-26 21:56:25 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
2010-05-01 23:31:30 +00:00
|
|
|
* @see IDF_Scm::getChangeLog()
|
2010-04-26 21:56:25 +00:00
|
|
|
*/
|
2010-04-30 00:03:58 +00:00
|
|
|
public function getChangeLog($commit=null, $n=10)
|
|
|
|
{
|
2010-04-30 23:05:54 +00:00
|
|
|
$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)
|
2010-06-28 06:47:39 +00:00
|
|
|
$dates[] = date('Y-m-d H:i:s', strtotime($date));
|
2010-04-30 23:05:54 +00:00
|
|
|
$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;
|
2010-04-26 21:56:25 +00:00
|
|
|
}
|
2010-04-28 23:44:34 +00:00
|
|
|
}
|
2010-05-01 23:31:30 +00:00
|
|
|
|