Merge branch 'mnt-support'
This commit is contained in:
commit
14d07a22e2
63
doc/readme-monotone.mdtext
Normal file
63
doc/readme-monotone.mdtext
Normal file
@ -0,0 +1,63 @@
|
|||||||
|
# monotone implementation notes
|
||||||
|
|
||||||
|
## general
|
||||||
|
|
||||||
|
This version of indefero contains an implementation of the monotone
|
||||||
|
automation interface. It needs at least monotone version 0.47
|
||||||
|
(interface version 12.0) or newer, but as development continues, its
|
||||||
|
likely that this dependency has to be raised.
|
||||||
|
|
||||||
|
To set up a new IDF project with monotone quickly, all you need to do
|
||||||
|
is to create a new monotone database with
|
||||||
|
|
||||||
|
$ mtn db init -d project.mtn
|
||||||
|
|
||||||
|
in the configured repository path `$cfg['mtn_repositories']` and
|
||||||
|
configure `$cfg['mtn_db_access']` to "local".
|
||||||
|
|
||||||
|
To have a really workable setup, this database needs an initial commit
|
||||||
|
on the configured master branch of the project. This can be done easily
|
||||||
|
with
|
||||||
|
|
||||||
|
$ mkdir tmp && touch tmp/remove_me
|
||||||
|
$ mtn import -d project.mtn -b master.branch.name \
|
||||||
|
-m "initial commit" tmp
|
||||||
|
$ rm -rf tmp
|
||||||
|
|
||||||
|
Its expected that more scripts arrive soon to automate this and other
|
||||||
|
tasks in the future for (multi)forge setups.
|
||||||
|
|
||||||
|
## current state / internals
|
||||||
|
|
||||||
|
The implementation should be fairly stable and fast, though some
|
||||||
|
information, such as individual file sizes or last change information,
|
||||||
|
won't scale well with the tree size. Its expected that the mtn
|
||||||
|
automation interface improves in this area in the future and that
|
||||||
|
these parts can then be rewritten with speed in mind.
|
||||||
|
|
||||||
|
As the idf.conf-dist explains more in detail, different access patterns
|
||||||
|
are possible to retrieve changeset data from monotone. Please refer
|
||||||
|
to the documentation there for more information.
|
||||||
|
|
||||||
|
## indefero critique:
|
||||||
|
|
||||||
|
It was not always 100% clear what some of the abstract SCM API method
|
||||||
|
wanted in return. While it helped a lot to have prior art in form of the
|
||||||
|
SVN and git implementation, the documentation of the abstract IDF_Scm
|
||||||
|
should probably still be improved.
|
||||||
|
|
||||||
|
Since branch and tag names can be of arbitrary size, it was not possible
|
||||||
|
to display them completely in the default layout. This might be a problem
|
||||||
|
in other SCMs as well, in particular for the monotone implementation I
|
||||||
|
introduced a special filter, called "IDF_Views_Source_ShortenString".
|
||||||
|
|
||||||
|
The API methods getPathInfo() and getTree() return similar VCS "objects"
|
||||||
|
which unfortunately do not have a well-defined structure - this should
|
||||||
|
probably addressed in future indefero releases.
|
||||||
|
|
||||||
|
While the returned objects from getTree() contain all the needed
|
||||||
|
information, indefero doesn't seem to use them to sort the output
|
||||||
|
f.e. alphabetically or in such a way that directories are outputted
|
||||||
|
before files. It was unclear if the SCM implementor should do this
|
||||||
|
task or not and what the admired default sorting should be.
|
||||||
|
|
@ -71,6 +71,25 @@ class IDF_Diff
|
|||||||
$current_chunk = 0;
|
$current_chunk = 0;
|
||||||
$indiff = true;
|
$indiff = true;
|
||||||
continue;
|
continue;
|
||||||
|
} else if (0 === strpos($line, '=========')) {
|
||||||
|
// by default always use the new name of a possibly renamed file
|
||||||
|
$current_file = self::getMtnFile($this->lines[$i+1]);
|
||||||
|
// mtn 0.48 and newer set /dev/null as file path for dropped files
|
||||||
|
// so we display the old name here
|
||||||
|
if ($current_file == "/dev/null") {
|
||||||
|
$current_file = self::getMtnFile($this->lines[$i]);
|
||||||
|
}
|
||||||
|
if ($current_file == "/dev/null") {
|
||||||
|
throw new Exception(
|
||||||
|
"could not determine path from diff"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
$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: ')) {
|
} else if (0 === strpos($line, 'Index: ')) {
|
||||||
$current_file = self::getSvnFile($line);
|
$current_file = self::getSvnFile($line);
|
||||||
$files[$current_file] = array();
|
$files[$current_file] = array();
|
||||||
@ -133,6 +152,12 @@ class IDF_Diff
|
|||||||
return substr(trim($line), 7);
|
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.
|
* Return the html version of a parsed diff.
|
||||||
*/
|
*/
|
||||||
|
@ -38,6 +38,7 @@ class IDF_Form_Admin_ProjectCreate extends Pluf_Form
|
|||||||
'git' => __('git'),
|
'git' => __('git'),
|
||||||
'svn' => __('Subversion'),
|
'svn' => __('Subversion'),
|
||||||
'mercurial' => __('mercurial'),
|
'mercurial' => __('mercurial'),
|
||||||
|
'mtn' => __('monotone'),
|
||||||
);
|
);
|
||||||
foreach (Pluf::f('allowed_scm', array()) as $key => $class) {
|
foreach (Pluf::f('allowed_scm', array()) as $key => $class) {
|
||||||
$choices[$options[$key]] = $key;
|
$choices[$options[$key]] = $key;
|
||||||
@ -92,6 +93,13 @@ class IDF_Form_Admin_ProjectCreate extends Pluf_Form
|
|||||||
'widget' => 'Pluf_Form_Widget_PasswordInput',
|
'widget' => 'Pluf_Form_Widget_PasswordInput',
|
||||||
));
|
));
|
||||||
|
|
||||||
|
$this->fields['mtn_master_branch'] = new Pluf_Form_Field_Varchar(
|
||||||
|
array('required' => false,
|
||||||
|
'label' => __('Master branch'),
|
||||||
|
'initial' => '',
|
||||||
|
'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(
|
$this->fields['owners'] = new Pluf_Form_Field_Varchar(
|
||||||
array('required' => false,
|
array('required' => false,
|
||||||
'label' => __('Project owners'),
|
'label' => __('Project owners'),
|
||||||
@ -170,6 +178,34 @@ class IDF_Form_Admin_ProjectCreate extends Pluf_Form
|
|||||||
return $url;
|
return $url;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function clean_mtn_master_branch()
|
||||||
|
{
|
||||||
|
// do not validate, but empty the field if a different
|
||||||
|
// SCM should be used
|
||||||
|
if ($this->cleaned_data['scm'] != 'mtn')
|
||||||
|
return '';
|
||||||
|
|
||||||
|
$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(__(
|
||||||
|
'The master branch is empty or 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()
|
public function clean_shortname()
|
||||||
{
|
{
|
||||||
$shortname = mb_strtolower($this->cleaned_data['shortname']);
|
$shortname = mb_strtolower($this->cleaned_data['shortname']);
|
||||||
@ -198,6 +234,11 @@ class IDF_Form_Admin_ProjectCreate extends Pluf_Form
|
|||||||
$this->cleaned_data[$key] = '';
|
$this->cleaned_data[$key] = '';
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if ($this->cleaned_data['scm'] != 'mtn') {
|
||||||
|
$this->cleaned_data['mtn_master_branch'] = '';
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* [signal]
|
* [signal]
|
||||||
*
|
*
|
||||||
@ -246,8 +287,8 @@ class IDF_Form_Admin_ProjectCreate extends Pluf_Form
|
|||||||
$project->create();
|
$project->create();
|
||||||
$conf = new IDF_Conf();
|
$conf = new IDF_Conf();
|
||||||
$conf->setProject($project);
|
$conf->setProject($project);
|
||||||
$keys = array('scm', 'svn_remote_url',
|
$keys = array('scm', 'svn_remote_url', 'svn_username',
|
||||||
'svn_username', 'svn_password');
|
'svn_password', 'mtn_master_branch');
|
||||||
foreach ($keys as $key) {
|
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] : '';
|
$this->cleaned_data[$key] : '';
|
||||||
@ -284,6 +325,7 @@ class IDF_Form_Admin_ProjectCreate extends Pluf_Form
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
$project->created();
|
$project->created();
|
||||||
|
|
||||||
if ($this->cleaned_data['template'] == '--') {
|
if ($this->cleaned_data['template'] == '--') {
|
||||||
IDF_Form_MembersConf::updateMemberships($project,
|
IDF_Form_MembersConf::updateMemberships($project,
|
||||||
$this->cleaned_data);
|
$this->cleaned_data);
|
||||||
|
@ -82,17 +82,15 @@ class IDF_Form_Admin_UserCreate extends Pluf_Form
|
|||||||
),
|
),
|
||||||
));
|
));
|
||||||
|
|
||||||
$this->fields['ssh_key'] = new Pluf_Form_Field_Varchar(
|
$this->fields['public_key'] = new Pluf_Form_Field_Varchar(
|
||||||
array('required' => false,
|
array('required' => false,
|
||||||
'label' => __('Add a public SSH key'),
|
'label' => __('Add a public key'),
|
||||||
'initial' => '',
|
'initial' => '',
|
||||||
'widget_attrs' => array('rows' => 3,
|
'widget_attrs' => array('rows' => 3,
|
||||||
'cols' => 40),
|
'cols' => 40),
|
||||||
'widget' => 'Pluf_Form_Widget_TextareaInput',
|
'widget' => 'Pluf_Form_Widget_TextareaInput',
|
||||||
'help_text' => __('Be careful to provide the public key and not the private key!')
|
'help_text' => __('Paste a SSH or monotone public key. Be careful to not provide your private key here!')
|
||||||
));
|
));
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -137,11 +135,11 @@ class IDF_Form_Admin_UserCreate extends Pluf_Form
|
|||||||
$params = array('user' => $user);
|
$params = array('user' => $user);
|
||||||
Pluf_Signal::send('Pluf_User::passwordUpdated',
|
Pluf_Signal::send('Pluf_User::passwordUpdated',
|
||||||
'IDF_Form_Admin_UserCreate', $params);
|
'IDF_Form_Admin_UserCreate', $params);
|
||||||
// Create the ssh key as needed
|
// Create the public key as needed
|
||||||
if ('' !== $this->cleaned_data['ssh_key']) {
|
if ('' !== $this->cleaned_data['public_key']) {
|
||||||
$key = new IDF_Key();
|
$key = new IDF_Key();
|
||||||
$key->user = $user;
|
$key->user = $user;
|
||||||
$key->content = $this->cleaned_data['ssh_key'];
|
$key->content = $this->cleaned_data['public_key'];
|
||||||
$key->create();
|
$key->create();
|
||||||
}
|
}
|
||||||
// Send an email to the user with the password
|
// Send an email to the user with the password
|
||||||
@ -162,11 +160,6 @@ class IDF_Form_Admin_UserCreate extends Pluf_Form
|
|||||||
return $user;
|
return $user;
|
||||||
}
|
}
|
||||||
|
|
||||||
function clean_ssh_key()
|
|
||||||
{
|
|
||||||
return IDF_Form_UserAccount::checkSshKey($this->cleaned_data['ssh_key']);
|
|
||||||
}
|
|
||||||
|
|
||||||
function clean_last_name()
|
function clean_last_name()
|
||||||
{
|
{
|
||||||
$last_name = trim($this->cleaned_data['last_name']);
|
$last_name = trim($this->cleaned_data['last_name']);
|
||||||
@ -211,4 +204,12 @@ class IDF_Form_Admin_UserCreate extends Pluf_Form
|
|||||||
}
|
}
|
||||||
return $this->cleaned_data['login'];
|
return $this->cleaned_data['login'];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function clean_public_key()
|
||||||
|
{
|
||||||
|
$this->cleaned_data['public_key'] =
|
||||||
|
IDF_Form_UserAccount::checkPublicKey($this->cleaned_data['public_key']);
|
||||||
|
|
||||||
|
return $this->cleaned_data['public_key'];
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -92,17 +92,15 @@ class IDF_Form_UserAccount extends Pluf_Form
|
|||||||
),
|
),
|
||||||
));
|
));
|
||||||
|
|
||||||
$this->fields['ssh_key'] = new Pluf_Form_Field_Varchar(
|
$this->fields['public_key'] = new Pluf_Form_Field_Varchar(
|
||||||
array('required' => false,
|
array('required' => false,
|
||||||
'label' => __('Add a public SSH key'),
|
'label' => __('Add a public key'),
|
||||||
'initial' => '',
|
'initial' => '',
|
||||||
'widget_attrs' => array('rows' => 3,
|
'widget_attrs' => array('rows' => 3,
|
||||||
'cols' => 40),
|
'cols' => 40),
|
||||||
'widget' => 'Pluf_Form_Widget_TextareaInput',
|
'widget' => 'Pluf_Form_Widget_TextareaInput',
|
||||||
'help_text' => __('Be careful to provide your public key and not your private key!')
|
'help_text' => __('Paste a SSH or monotone public key. Be careful to not provide your private key here!')
|
||||||
));
|
));
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -151,10 +149,10 @@ class IDF_Form_UserAccount extends Pluf_Form
|
|||||||
}
|
}
|
||||||
$this->user->setFromFormData($this->cleaned_data);
|
$this->user->setFromFormData($this->cleaned_data);
|
||||||
// Add key as needed.
|
// Add key as needed.
|
||||||
if ('' !== $this->cleaned_data['ssh_key']) {
|
if ('' !== $this->cleaned_data['public_key']) {
|
||||||
$key = new IDF_Key();
|
$key = new IDF_Key();
|
||||||
$key->user = $this->user;
|
$key->user = $this->user;
|
||||||
$key->content = $this->cleaned_data['ssh_key'];
|
$key->content = $this->cleaned_data['public_key'];
|
||||||
if ($commit) {
|
if ($commit) {
|
||||||
$key->create();
|
$key->create();
|
||||||
}
|
}
|
||||||
@ -190,7 +188,7 @@ class IDF_Form_UserAccount extends Pluf_Form
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Check an ssh key.
|
* Check arbitrary public keys.
|
||||||
*
|
*
|
||||||
* It will throw a Pluf_Form_Invalid exception if it cannot
|
* It will throw a Pluf_Form_Invalid exception if it cannot
|
||||||
* validate the key.
|
* validate the key.
|
||||||
@ -199,27 +197,59 @@ class IDF_Form_UserAccount extends Pluf_Form
|
|||||||
* @param $user int The user id of the user of the key (0)
|
* @param $user int The user id of the user of the key (0)
|
||||||
* @return string The clean key
|
* @return string The clean key
|
||||||
*/
|
*/
|
||||||
public static function checkSshKey($key, $user=0)
|
public static function checkPublicKey($key, $user=0)
|
||||||
{
|
{
|
||||||
$key = trim($key);
|
$key = trim($key);
|
||||||
if (strlen($key) == 0) {
|
if (strlen($key) == 0) {
|
||||||
return '';
|
return '';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (preg_match('#^ssh\-[a-z]{3}\s\S+\s\S+$#', $key)) {
|
||||||
$key = str_replace(array("\n", "\r"), '', $key);
|
$key = str_replace(array("\n", "\r"), '', $key);
|
||||||
if (!preg_match('#^ssh\-[a-z]{3}\s(\S+)\s\S+$#', $key, $matches)) {
|
|
||||||
throw new Pluf_Form_Invalid(__('The format of the key is not valid. It must start with ssh-dss or ssh-rsa, a long string on a single line and at the end a comment.'));
|
|
||||||
}
|
|
||||||
if (Pluf::f('idf_strong_key_check', false)) {
|
if (Pluf::f('idf_strong_key_check', false)) {
|
||||||
|
|
||||||
$tmpfile = Pluf::f('tmp_folder', '/tmp').'/'.$user.'-key';
|
$tmpfile = Pluf::f('tmp_folder', '/tmp').'/'.$user.'-key';
|
||||||
file_put_contents($tmpfile, $key, LOCK_EX);
|
file_put_contents($tmpfile, $key, LOCK_EX);
|
||||||
$cmd = Pluf::f('idf_exec_cmd_prefix', '').
|
$cmd = Pluf::f('idf_exec_cmd_prefix', '').
|
||||||
'ssh-keygen -l -f '.escapeshellarg($tmpfile).' > /dev/null 2>&1';
|
'ssh-keygen -l -f '.escapeshellarg($tmpfile).' > /dev/null 2>&1';
|
||||||
exec($cmd, $out, $return);
|
exec($cmd, $out, $return);
|
||||||
unlink($tmpfile);
|
unlink($tmpfile);
|
||||||
|
|
||||||
if ($return != 0) {
|
if ($return != 0) {
|
||||||
throw new Pluf_Form_Invalid(__('Please check the key as it does not appears to be a valid key.'));
|
throw new Pluf_Form_Invalid(
|
||||||
|
__('Please check the key as it does not appear '.
|
||||||
|
'to be a valid SSH public key.')
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
else if (preg_match('#^\[pubkey [^\]]+\]\s*\S+\s*\[end\]$#', $key)) {
|
||||||
|
if (Pluf::f('idf_strong_key_check', false)) {
|
||||||
|
|
||||||
|
// if monotone can read it, it should be valid
|
||||||
|
$mtn_opts = implode(' ', Pluf::f('mtn_opts', array()));
|
||||||
|
$cmd = Pluf::f('idf_exec_cmd_prefix', '').
|
||||||
|
sprintf('%s %s -d :memory: read >/tmp/php-out 2>&1',
|
||||||
|
Pluf::f('mtn_path', 'mtn'), $mtn_opts);
|
||||||
|
$fp = popen($cmd, 'w');
|
||||||
|
fwrite($fp, $key);
|
||||||
|
$return = pclose($fp);
|
||||||
|
|
||||||
|
if ($return != 0) {
|
||||||
|
throw new Pluf_Form_Invalid(
|
||||||
|
__('Please check the key as it does not appear '.
|
||||||
|
'to be a valid monotone public key.')
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
throw new Pluf_Form_Invalid(
|
||||||
|
__('Public key looks neither like a SSH '.
|
||||||
|
'nor monotone public key.'));
|
||||||
|
}
|
||||||
|
|
||||||
// If $user, then check if not the same key stored
|
// If $user, then check if not the same key stored
|
||||||
if ($user) {
|
if ($user) {
|
||||||
$ruser = Pluf::factory('Pluf_User', $user);
|
$ruser = Pluf::factory('Pluf_User', $user);
|
||||||
@ -227,19 +257,15 @@ class IDF_Form_UserAccount extends Pluf_Form
|
|||||||
$sql = new Pluf_SQL('content=%s', array($key));
|
$sql = new Pluf_SQL('content=%s', array($key));
|
||||||
$keys = Pluf::factory('IDF_Key')->getList(array('filter' => $sql->gen()));
|
$keys = Pluf::factory('IDF_Key')->getList(array('filter' => $sql->gen()));
|
||||||
if (count($keys) > 0) {
|
if (count($keys) > 0) {
|
||||||
throw new Pluf_Form_Invalid(__('You already have uploaded this SSH key.'));
|
throw new Pluf_Form_Invalid(
|
||||||
|
__('You already have uploaded this key.')
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return $key;
|
return $key;
|
||||||
}
|
}
|
||||||
|
|
||||||
function clean_ssh_key()
|
|
||||||
{
|
|
||||||
return self::checkSshKey($this->cleaned_data['ssh_key'],
|
|
||||||
$this->user->id);
|
|
||||||
}
|
|
||||||
|
|
||||||
function clean_last_name()
|
function clean_last_name()
|
||||||
{
|
{
|
||||||
$last_name = trim($this->cleaned_data['last_name']);
|
$last_name = trim($this->cleaned_data['last_name']);
|
||||||
@ -272,8 +298,16 @@ class IDF_Form_UserAccount extends Pluf_Form
|
|||||||
return $this->cleaned_data['email'];
|
return $this->cleaned_data['email'];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function clean_public_key()
|
||||||
|
{
|
||||||
|
$this->cleaned_data['public_key'] =
|
||||||
|
self::checkPublicKey($this->cleaned_data['public_key'],
|
||||||
|
$this->user->id);
|
||||||
|
return $this->cleaned_data['public_key'];
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Check to see if the 2 passwords are the same.
|
* Check to see if the 2 passwords are the same
|
||||||
*/
|
*/
|
||||||
public function clean()
|
public function clean()
|
||||||
{
|
{
|
||||||
@ -285,6 +319,7 @@ class IDF_Form_UserAccount extends Pluf_Form
|
|||||||
throw new Pluf_Form_Invalid(__('The passwords do not match. Please give them again.'));
|
throw new Pluf_Form_Invalid(__('The passwords do not match. Please give them again.'));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return $this->cleaned_data;
|
return $this->cleaned_data;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -22,7 +22,7 @@
|
|||||||
# ***** END LICENSE BLOCK ***** */
|
# ***** END LICENSE BLOCK ***** */
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Storage of the SSH keys.
|
* Storage of the public keys (ssh or monotone).
|
||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
class IDF_Key extends Pluf_Model
|
class IDF_Key extends Pluf_Model
|
||||||
@ -52,7 +52,7 @@ class IDF_Key extends Pluf_Model
|
|||||||
array(
|
array(
|
||||||
'type' => 'Pluf_DB_Field_Text',
|
'type' => 'Pluf_DB_Field_Text',
|
||||||
'blank' => false,
|
'blank' => false,
|
||||||
'verbose' => __('ssh key'),
|
'verbose' => __('public key'),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
// WARNING: Not using getSqlTable on the Pluf_User object to
|
// WARNING: Not using getSqlTable on the Pluf_User object to
|
||||||
@ -75,6 +75,58 @@ class IDF_Key extends Pluf_Model
|
|||||||
return Pluf_Template::markSafe(Pluf_esc(substr($this->content, 0, 25)).' [...] '.Pluf_esc(substr($this->content, -55)));
|
return Pluf_Template::markSafe(Pluf_esc(substr($this->content, 0, 25)).' [...] '.Pluf_esc(substr($this->content, -55)));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private function parseContent()
|
||||||
|
{
|
||||||
|
if (preg_match('#^\[pubkey ([^\]]+)\]\s*(\S+)\s*\[end\]$#', $this->content, $m)) {
|
||||||
|
return array('mtn', $m[1], $m[2]);
|
||||||
|
}
|
||||||
|
else if (preg_match('#^ssh\-[a-z]{3}\s(\S+)\s(\S+)$#', $this->content, $m)) {
|
||||||
|
return array('ssh', $m[2], $m[1]);
|
||||||
|
}
|
||||||
|
|
||||||
|
throw new IDF_Exception('invalid or unknown key data detected');
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the type of the public key
|
||||||
|
*
|
||||||
|
* @return string 'ssh' or 'mtn'
|
||||||
|
*/
|
||||||
|
function getType()
|
||||||
|
{
|
||||||
|
list($type, , ) = $this->parseContent();
|
||||||
|
return $type;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the key name of the key
|
||||||
|
*
|
||||||
|
* @return string
|
||||||
|
*/
|
||||||
|
function getName()
|
||||||
|
{
|
||||||
|
list(, $keyName, ) = $this->parseContent();
|
||||||
|
return $keyName;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This function should be used to calculate the key id from the
|
||||||
|
* public key hash for authentication purposes. This avoids clashes
|
||||||
|
* in case the key name is not unique across the project
|
||||||
|
*
|
||||||
|
* And yes, this is actually how monotone itself calculates the key
|
||||||
|
* id...
|
||||||
|
*
|
||||||
|
* @return string
|
||||||
|
*/
|
||||||
|
function getMtnId()
|
||||||
|
{
|
||||||
|
list($type, $keyName, $keyData) = $this->parseContent();
|
||||||
|
if ($type != 'mtn')
|
||||||
|
throw new IDF_Exception('key is not a monotone public key');
|
||||||
|
return sha1($keyName.":".$keyData);
|
||||||
|
}
|
||||||
|
|
||||||
function postSave($create=false)
|
function postSave($create=false)
|
||||||
{
|
{
|
||||||
/**
|
/**
|
||||||
@ -89,7 +141,7 @@ class IDF_Key extends Pluf_Model
|
|||||||
* [description]
|
* [description]
|
||||||
*
|
*
|
||||||
* This signal allows an application to perform special
|
* This signal allows an application to perform special
|
||||||
* operations after the saving of a SSH Key.
|
* operations after the saving of a public Key.
|
||||||
*
|
*
|
||||||
* [parameters]
|
* [parameters]
|
||||||
*
|
*
|
||||||
@ -127,5 +179,4 @@ class IDF_Key extends Pluf_Model
|
|||||||
Pluf_Signal::send('IDF_Key::preDelete',
|
Pluf_Signal::send('IDF_Key::preDelete',
|
||||||
'IDF_Key', $params);
|
'IDF_Key', $params);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -92,6 +92,7 @@ class IDF_Middleware
|
|||||||
array(
|
array(
|
||||||
'size' => 'IDF_Views_Source_PrettySize',
|
'size' => 'IDF_Views_Source_PrettySize',
|
||||||
'ssize' => 'IDF_Views_Source_PrettySizeSimple',
|
'ssize' => 'IDF_Views_Source_PrettySizeSimple',
|
||||||
|
'shorten' => 'IDF_Views_Source_ShortenString',
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -110,6 +111,7 @@ function IDF_Middleware_ContextPreProcessor($request)
|
|||||||
$request->project);
|
$request->project);
|
||||||
$c = array_merge($c, $request->rights);
|
$c = array_merge($c, $request->rights);
|
||||||
}
|
}
|
||||||
|
$c['usherConfigured'] = Pluf::f("mtn_usher", null) !== null;
|
||||||
return $c;
|
return $c;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -48,8 +48,7 @@ class IDF_Plugin_SyncGit_Cron
|
|||||||
$out = '';
|
$out = '';
|
||||||
$keys = Pluf::factory('IDF_Key')->getList(array('view'=>'join_user'));
|
$keys = Pluf::factory('IDF_Key')->getList(array('view'=>'join_user'));
|
||||||
foreach ($keys as $key) {
|
foreach ($keys as $key) {
|
||||||
if (strlen($key->content) > 40 // minimal check
|
if ($key->getType() == 'ssh' and preg_match('/^[a-zA-Z][a-zA-Z0-9_.-]*(@[a-zA-Z][a-zA-Z0-9.-]*)?$/', $key->login)) {
|
||||||
and preg_match('/^[a-zA-Z][a-zA-Z0-9_.-]*(@[a-zA-Z][a-zA-Z0-9.-]*)?$/', $key->login)) {
|
|
||||||
$content = trim(str_replace(array("\n", "\r"), '', $key->content));
|
$content = trim(str_replace(array("\n", "\r"), '', $key->content));
|
||||||
$out .= sprintf($template, $cmd, $key->login, $content)."\n";
|
$out .= sprintf($template, $cmd, $key->login, $content)."\n";
|
||||||
}
|
}
|
||||||
|
@ -382,15 +382,16 @@ class IDF_Project extends Pluf_Model
|
|||||||
* This will return the right url based on the user.
|
* This will return the right url based on the user.
|
||||||
*
|
*
|
||||||
* @param Pluf_User The user (null)
|
* @param Pluf_User The user (null)
|
||||||
|
* @param string A specific commit to access
|
||||||
*/
|
*/
|
||||||
public function getSourceAccessUrl($user=null)
|
public function getSourceAccessUrl($user=null, $commit=null)
|
||||||
{
|
{
|
||||||
$right = $this->getConf()->getVal('source_access_rights', 'all');
|
$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) {
|
and $right == 'all' and !$this->private) {
|
||||||
return $this->getRemoteAccessUrl();
|
return $this->getRemoteAccessUrl($commit);
|
||||||
}
|
}
|
||||||
return $this->getWriteRemoteAccessUrl($user);
|
return $this->getWriteRemoteAccessUrl($user, $commit);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@ -398,15 +399,17 @@ class IDF_Project extends Pluf_Model
|
|||||||
* Get the remote access url to the repository.
|
* Get the remote access url to the repository.
|
||||||
*
|
*
|
||||||
* This will always return the anonymous access url.
|
* This will always return the anonymous access url.
|
||||||
|
*
|
||||||
|
* @param string A specific commit to access
|
||||||
*/
|
*/
|
||||||
public function getRemoteAccessUrl()
|
public function getRemoteAccessUrl($commit=null)
|
||||||
{
|
{
|
||||||
$conf = $this->getConf();
|
$conf = $this->getConf();
|
||||||
$scm = $conf->getVal('scm', 'git');
|
$scm = $conf->getVal('scm', 'git');
|
||||||
$scms = Pluf::f('allowed_scm');
|
$scms = Pluf::f('allowed_scm');
|
||||||
Pluf::loadClass($scms[$scm]);
|
Pluf::loadClass($scms[$scm]);
|
||||||
return call_user_func(array($scms[$scm], 'getAnonymousAccessUrl'),
|
return call_user_func(array($scms[$scm], 'getAnonymousAccessUrl'),
|
||||||
$this);
|
$this, $commit);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -415,14 +418,16 @@ class IDF_Project extends Pluf_Model
|
|||||||
* Some SCM have a remote access URL to write which is not the
|
* Some SCM have a remote access URL to write which is not the
|
||||||
* same as the one to read. For example, you do a checkout with
|
* same as the one to read. For example, you do a checkout with
|
||||||
* git-daemon and push with SSH.
|
* git-daemon and push with SSH.
|
||||||
|
*
|
||||||
|
* @param string A specific commit to access
|
||||||
*/
|
*/
|
||||||
public function getWriteRemoteAccessUrl($user)
|
public function getWriteRemoteAccessUrl($user,$commit=null)
|
||||||
{
|
{
|
||||||
$conf = $this->getConf();
|
$conf = $this->getConf();
|
||||||
$scm = $conf->getVal('scm', 'git');
|
$scm = $conf->getVal('scm', 'git');
|
||||||
$scms = Pluf::f('allowed_scm');
|
$scms = Pluf::f('allowed_scm');
|
||||||
return call_user_func(array($scms[$scm], 'getAuthAccessUrl'),
|
return call_user_func(array($scms[$scm], 'getAuthAccessUrl'),
|
||||||
$this, $user);
|
$this, $user, $commit);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -447,7 +452,8 @@ class IDF_Project extends Pluf_Model
|
|||||||
$roots = array(
|
$roots = array(
|
||||||
'git' => 'master',
|
'git' => 'master',
|
||||||
'svn' => 'HEAD',
|
'svn' => 'HEAD',
|
||||||
'mercurial' => 'tip'
|
'mercurial' => 'tip',
|
||||||
|
'mtn' => 'h:'.$conf->getVal('mtn_master_branch', '*'),
|
||||||
);
|
);
|
||||||
$scm = $conf->getVal('scm', 'git');
|
$scm = $conf->getVal('scm', 'git');
|
||||||
return $roots[$scm];
|
return $roots[$scm];
|
||||||
|
@ -273,12 +273,12 @@ class IDF_Scm_Git extends IDF_Scm
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static function getAnonymousAccessUrl($project)
|
public static function getAnonymousAccessUrl($project, $commit=null)
|
||||||
{
|
{
|
||||||
return sprintf(Pluf::f('git_remote_url'), $project->shortname);
|
return sprintf(Pluf::f('git_remote_url'), $project->shortname);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static function getAuthAccessUrl($project, $user)
|
public static function getAuthAccessUrl($project, $user, $commit=null)
|
||||||
{
|
{
|
||||||
return sprintf(Pluf::f('git_write_remote_url'), $project->shortname);
|
return sprintf(Pluf::f('git_write_remote_url'), $project->shortname);
|
||||||
}
|
}
|
||||||
|
@ -77,12 +77,12 @@ class IDF_Scm_Mercurial extends IDF_Scm
|
|||||||
return 'tip';
|
return 'tip';
|
||||||
}
|
}
|
||||||
|
|
||||||
public static function getAnonymousAccessUrl($project)
|
public static function getAnonymousAccessUrl($project, $commit=null)
|
||||||
{
|
{
|
||||||
return sprintf(Pluf::f('mercurial_remote_url'), $project->shortname);
|
return sprintf(Pluf::f('mercurial_remote_url'), $project->shortname);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static function getAuthAccessUrl($project, $user)
|
public static function getAuthAccessUrl($project, $user, $commit=null)
|
||||||
{
|
{
|
||||||
return sprintf(Pluf::f('mercurial_remote_url'), $project->shortname);
|
return sprintf(Pluf::f('mercurial_remote_url'), $project->shortname);
|
||||||
}
|
}
|
||||||
|
731
src/IDF/Scm/Monotone.php
Normal file
731
src/IDF/Scm/Monotone.php
Normal file
@ -0,0 +1,731 @@
|
|||||||
|
<?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) 2010 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 ***** */
|
||||||
|
|
||||||
|
require_once(dirname(__FILE__) . "/Monotone/Stdio.php");
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Monotone scm class
|
||||||
|
*
|
||||||
|
* @author Thomas Keller <me@thomaskeller.biz>
|
||||||
|
*/
|
||||||
|
class IDF_Scm_Monotone extends IDF_Scm
|
||||||
|
{
|
||||||
|
/** the minimum supported interface version */
|
||||||
|
public static $MIN_INTERFACE_VERSION = 12.0;
|
||||||
|
|
||||||
|
private $stdio;
|
||||||
|
|
||||||
|
private static $instances = array();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @see IDF_Scm::__construct()
|
||||||
|
*/
|
||||||
|
public function __construct($project)
|
||||||
|
{
|
||||||
|
$this->project = $project;
|
||||||
|
$this->stdio = new IDF_Scm_Monotone_Stdio($project);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @see IDF_Scm::getRepositorySize()
|
||||||
|
*/
|
||||||
|
public function getRepositorySize()
|
||||||
|
{
|
||||||
|
// 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)) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
$cmd = Pluf::f('idf_exec_cmd_prefix', '').'du -sk '
|
||||||
|
.escapeshellarg($repo);
|
||||||
|
$out = explode(' ',
|
||||||
|
self::shell_exec('IDF_Scm_Monotone::getRepositorySize', $cmd),
|
||||||
|
2);
|
||||||
|
return (int) $out[0]*1024;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @see IDF_Scm::isAvailable()
|
||||||
|
*/
|
||||||
|
public function isAvailable()
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
$out = $this->stdio->exec(array('interface_version'));
|
||||||
|
return floatval($out) >= self::$MIN_INTERFACE_VERSION;
|
||||||
|
}
|
||||||
|
catch (IDF_Scm_Exception $e) {}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @see IDF_Scm::getBranches()
|
||||||
|
*/
|
||||||
|
public function getBranches()
|
||||||
|
{
|
||||||
|
if (isset($this->cache['branches'])) {
|
||||||
|
return $this->cache['branches'];
|
||||||
|
}
|
||||||
|
// FIXME: we could / should introduce handling of suspended
|
||||||
|
// (i.e. dead) branches here by hiding them from the user's eye...
|
||||||
|
$out = $this->stdio->exec(array('branches'));
|
||||||
|
|
||||||
|
// note: 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
|
||||||
|
$res = array();
|
||||||
|
foreach (preg_split("/\n/", $out, -1, PREG_SPLIT_NO_EMPTY) as $b) {
|
||||||
|
$res["h:$b"] = $b;
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->cache['branches'] = $res;
|
||||||
|
return $res;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* monotone has no concept of a "main" branch, so just return
|
||||||
|
* the configured one. Ensure however that we can select revisions
|
||||||
|
* with it at all.
|
||||||
|
*
|
||||||
|
* @see IDF_Scm::getMainBranch()
|
||||||
|
*/
|
||||||
|
public function getMainBranch()
|
||||||
|
{
|
||||||
|
$conf = $this->project->getConf();
|
||||||
|
if (false === ($branch = $conf->getVal('mtn_master_branch', false))
|
||||||
|
|| empty($branch)) {
|
||||||
|
$branch = "*";
|
||||||
|
}
|
||||||
|
|
||||||
|
if (count($this->_resolveSelector("h:$branch")) == 0) {
|
||||||
|
throw new IDF_Scm_Exception(
|
||||||
|
"Branch $branch is empty"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return $branch;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* expands a selector or a partial revision id to zero, one or
|
||||||
|
* multiple 40 byte revision ids
|
||||||
|
*
|
||||||
|
* @param string $selector
|
||||||
|
* @return array
|
||||||
|
*/
|
||||||
|
private function _resolveSelector($selector)
|
||||||
|
{
|
||||||
|
$out = $this->stdio->exec(array('select', $selector));
|
||||||
|
return preg_split("/\n/", $out, -1, PREG_SPLIT_NO_EMPTY);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for ($i = 0; $i <= $valCount; $i++) {
|
||||||
|
$stanzaLine['values'][$i] = str_replace(
|
||||||
|
array("\\\\", "\\\""),
|
||||||
|
array("\\", "\""),
|
||||||
|
$stanzaLine['values'][$i]
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$stanza[] = $stanzaLine;
|
||||||
|
++$pos; // newline
|
||||||
|
}
|
||||||
|
$stanzas[] = $stanza;
|
||||||
|
++$pos; // newline
|
||||||
|
}
|
||||||
|
return $stanzas;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Queries the certs for a given revision and returns them in an
|
||||||
|
* associative array array("branch" => array("branch1", ...), ...)
|
||||||
|
*
|
||||||
|
* @param string
|
||||||
|
* @param array
|
||||||
|
*/
|
||||||
|
private function _getCerts($rev)
|
||||||
|
{
|
||||||
|
static $certCache = array();
|
||||||
|
|
||||||
|
if (!array_key_exists($rev, $certCache)) {
|
||||||
|
$out = $this->stdio->exec(array('certs', $rev));
|
||||||
|
|
||||||
|
$stanzas = self::_parseBasicIO($out);
|
||||||
|
$certs = array();
|
||||||
|
foreach ($stanzas as $stanza) {
|
||||||
|
$certname = null;
|
||||||
|
foreach ($stanza as $stanzaline) {
|
||||||
|
// luckily, name always comes before value
|
||||||
|
if ($stanzaline['key'] == 'name') {
|
||||||
|
$certname = $stanzaline['values'][0];
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($stanzaline['key'] == 'value') {
|
||||||
|
if (!array_key_exists($certname, $certs)) {
|
||||||
|
$certs[$certname] = array();
|
||||||
|
}
|
||||||
|
|
||||||
|
$certs[$certname][] = $stanzaline['values'][0];
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
$certCache[$rev] = $certs;
|
||||||
|
}
|
||||||
|
|
||||||
|
return $certCache[$rev];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns unique certificate values for the given revs and the specific
|
||||||
|
* cert name, optionally prefixed with $prefix
|
||||||
|
*
|
||||||
|
* @param array
|
||||||
|
* @param string
|
||||||
|
* @param string
|
||||||
|
* @return array
|
||||||
|
*/
|
||||||
|
private function _getUniqueCertValuesFor($revs, $certName, $prefix)
|
||||||
|
{
|
||||||
|
$certValues = array();
|
||||||
|
foreach ($revs as $rev) {
|
||||||
|
$certs = $this->_getCerts($rev);
|
||||||
|
if (!array_key_exists($certName, $certs))
|
||||||
|
continue;
|
||||||
|
foreach ($certs[$certName] as $certValue) {
|
||||||
|
$certValues[] = "$prefix$certValue";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return array_unique($certValues);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the revision in which the file has been last changed,
|
||||||
|
* starting from the start rev
|
||||||
|
*
|
||||||
|
* @param string
|
||||||
|
* @param string
|
||||||
|
* @return string
|
||||||
|
*/
|
||||||
|
private function _getLastChangeFor($file, $startrev)
|
||||||
|
{
|
||||||
|
$out = $this->stdio->exec(array(
|
||||||
|
'get_content_changed', $startrev, $file
|
||||||
|
));
|
||||||
|
|
||||||
|
$stanzas = self::_parseBasicIO($out);
|
||||||
|
|
||||||
|
// FIXME: we only care about the first returned content mark
|
||||||
|
// everything else seem to be very, 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 = $this->_resolveSelector($commit);
|
||||||
|
if (count($revs) == 0) return array();
|
||||||
|
return $this->_getUniqueCertValuesFor($revs, 'branch', 'h:');
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @see IDF_Scm::getTags()
|
||||||
|
*/
|
||||||
|
public function getTags()
|
||||||
|
{
|
||||||
|
if (isset($this->cache['tags'])) {
|
||||||
|
return $this->cache['tags'];
|
||||||
|
}
|
||||||
|
|
||||||
|
$out = $this->stdio->exec(array('tags'));
|
||||||
|
|
||||||
|
$tags = array();
|
||||||
|
$stanzas = self::_parseBasicIO($out);
|
||||||
|
foreach ($stanzas as $stanza) {
|
||||||
|
$tagname = null;
|
||||||
|
foreach ($stanza as $stanzaline) {
|
||||||
|
// revision comes directly after the tag stanza
|
||||||
|
if ($stanzaline['key'] == 'tag') {
|
||||||
|
$tagname = $stanzaline['values'][0];
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if ($stanzaline['key'] == 'revision') {
|
||||||
|
// FIXME: warn if multiple revisions have
|
||||||
|
// equally named tags
|
||||||
|
if (!array_key_exists("t:$tagname", $tags)) {
|
||||||
|
$tags["t:$tagname"] = $tagname;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->cache['tags'] = $tags;
|
||||||
|
return $tags;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @see IDF_Scm::inTags()
|
||||||
|
*/
|
||||||
|
public function inTags($commit, $path)
|
||||||
|
{
|
||||||
|
$revs = $this->_resolveSelector($commit);
|
||||||
|
if (count($revs) == 0) return array();
|
||||||
|
return $this->_getUniqueCertValuesFor($revs, 'tag', 't:');
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @see IDF_Scm::getTree()
|
||||||
|
*/
|
||||||
|
public function getTree($commit, $folder='/', $branch=null)
|
||||||
|
{
|
||||||
|
$revs = $this->_resolveSelector($commit);
|
||||||
|
if (count($revs) == 0) {
|
||||||
|
return array();
|
||||||
|
}
|
||||||
|
|
||||||
|
$out = $this->stdio->exec(array(
|
||||||
|
'get_manifest_of', $revs[0]
|
||||||
|
));
|
||||||
|
|
||||||
|
$files = array();
|
||||||
|
$stanzas = self::_parseBasicIO($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';
|
||||||
|
$file['size'] = 0;
|
||||||
|
}
|
||||||
|
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[] = date('Y-m-d H:i:s', strtotime($date));
|
||||||
|
$file['date'] = implode(', ', $dates);
|
||||||
|
$file['log'] = implode("\n---\n", $certs['changelog']);
|
||||||
|
}
|
||||||
|
|
||||||
|
$files[] = (object) $file;
|
||||||
|
}
|
||||||
|
return $files;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @see IDF_Scm::findAuthor()
|
||||||
|
*/
|
||||||
|
public function findAuthor($author)
|
||||||
|
{
|
||||||
|
// We extract anything which looks like an 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;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @see IDF_Scm::getAnonymousAccessUrl()
|
||||||
|
*/
|
||||||
|
public static function getAnonymousAccessUrl($project, $commit = null)
|
||||||
|
{
|
||||||
|
$scm = IDF_Scm::get($project);
|
||||||
|
$branch = $scm->getMainBranch();
|
||||||
|
|
||||||
|
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];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$remote_url = Pluf::f('mtn_remote_url', '');
|
||||||
|
if (empty($remote_url)) {
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
|
||||||
|
return sprintf($remote_url, $project->shortname).'?'.$branch;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @see IDF_Scm::getAuthAccessUrl()
|
||||||
|
*/
|
||||||
|
public static function getAuthAccessUrl($project, $user, $commit = null)
|
||||||
|
{
|
||||||
|
$url = self::getAnonymousAccessUrl($project, $commit);
|
||||||
|
return preg_replace("#^ssh://#", "ssh://$user@", $url);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns this object correctly initialized for the project.
|
||||||
|
*
|
||||||
|
* @param IDF_Project
|
||||||
|
* @return IDF_Scm_Monotone
|
||||||
|
*/
|
||||||
|
public static function factory($project)
|
||||||
|
{
|
||||||
|
if (!array_key_exists($project->shortname, self::$instances)) {
|
||||||
|
self::$instances[$project->shortname] =
|
||||||
|
new IDF_Scm_Monotone($project);
|
||||||
|
}
|
||||||
|
return self::$instances[$project->shortname];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @see IDF_Scm::isValidRevision()
|
||||||
|
*/
|
||||||
|
public function isValidRevision($commit)
|
||||||
|
{
|
||||||
|
$revs = $this->_resolveSelector($commit);
|
||||||
|
return count($revs) == 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @see IDF_Scm::getPathInfo()
|
||||||
|
*/
|
||||||
|
public function getPathInfo($file, $commit = null)
|
||||||
|
{
|
||||||
|
if ($commit === null) {
|
||||||
|
$commit = 'h:' . $this->getMainBranch();
|
||||||
|
}
|
||||||
|
|
||||||
|
$revs = $this->_resolveSelector($commit);
|
||||||
|
if (count($revs) == 0)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
$out = $this->stdio->exec(array(
|
||||||
|
'get_manifest_of', $revs[0]
|
||||||
|
));
|
||||||
|
|
||||||
|
$files = array();
|
||||||
|
$stanzas = self::_parseBasicIO($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[] = date('Y-m-d H:i:s', strtotime($date));
|
||||||
|
$file['date'] = implode(', ', $dates);
|
||||||
|
$file['log'] = implode("\n---\n", $certs['changelog']);
|
||||||
|
}
|
||||||
|
|
||||||
|
return (object) $file;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @see IDF_Scm::getFile()
|
||||||
|
*/
|
||||||
|
public function getFile($def, $cmd_only=false)
|
||||||
|
{
|
||||||
|
// this won't work with remote databases
|
||||||
|
if ($cmd_only) {
|
||||||
|
throw new Pluf_Exception_NotImplemented();
|
||||||
|
}
|
||||||
|
|
||||||
|
return $this->stdio->exec(array('get_file', $def->hash));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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
|
||||||
|
*/
|
||||||
|
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 '';
|
||||||
|
}
|
||||||
|
|
||||||
|
return $this->stdio->exec(
|
||||||
|
array('content_diff'),
|
||||||
|
array('r' => array($sources[0], $targets[0]))
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @see IDF_Scm::getCommit()
|
||||||
|
*/
|
||||||
|
public function getCommit($commit, $getdiff=false)
|
||||||
|
{
|
||||||
|
$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[] = date('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;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @see IDF_Scm::isCommitLarge()
|
||||||
|
*/
|
||||||
|
public function isCommitLarge($commit=null)
|
||||||
|
{
|
||||||
|
if (empty($commit)) {
|
||||||
|
$commit = 'h:'.$this->getMainBranch();
|
||||||
|
}
|
||||||
|
|
||||||
|
$revs = $this->_resolveSelector($commit);
|
||||||
|
if (count($revs) == 0)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
$out = $this->stdio->exec(array(
|
||||||
|
'get_revision', $revs[0]
|
||||||
|
));
|
||||||
|
|
||||||
|
$newAndPatchedFiles = 0;
|
||||||
|
$stanzas = self::_parseBasicIO($out);
|
||||||
|
|
||||||
|
foreach ($stanzas as $stanza) {
|
||||||
|
if ($stanza[0]['key'] == 'patch' || $stanza[0]['key'] == 'add_file')
|
||||||
|
$newAndPatchedFiles++;
|
||||||
|
}
|
||||||
|
|
||||||
|
return $newAndPatchedFiles > 100;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @see IDF_Scm::getChangeLog()
|
||||||
|
*/
|
||||||
|
public function getChangeLog($commit=null, $n=10)
|
||||||
|
{
|
||||||
|
$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[] = date('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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
360
src/IDF/Scm/Monotone/Stdio.php
Normal file
360
src/IDF/Scm/Monotone/Stdio.php
Normal file
@ -0,0 +1,360 @@
|
|||||||
|
<?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) 2010 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 stdio class
|
||||||
|
*
|
||||||
|
* Connects to a monotone process and executes commands via its
|
||||||
|
* stdio interface
|
||||||
|
*
|
||||||
|
* @author Thomas Keller <me@thomaskeller.biz>
|
||||||
|
*/
|
||||||
|
class IDF_Scm_Monotone_Stdio
|
||||||
|
{
|
||||||
|
/** 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 */
|
||||||
|
public static $SUPPORTED_STDIO_VERSION = 2;
|
||||||
|
|
||||||
|
private $project;
|
||||||
|
private $proc;
|
||||||
|
private $pipes;
|
||||||
|
private $oob;
|
||||||
|
private $cmdnum;
|
||||||
|
private $lastcmd;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructor - starts the stdio process
|
||||||
|
*
|
||||||
|
* @param IDF_Project
|
||||||
|
*/
|
||||||
|
public function __construct(IDF_Project $project)
|
||||||
|
{
|
||||||
|
$this->project = $project;
|
||||||
|
$this->start();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Destructor - stops the stdio process
|
||||||
|
*/
|
||||||
|
public function __destruct()
|
||||||
|
{
|
||||||
|
$this->stop();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Starts the stdio process and resets the command counter
|
||||||
|
*/
|
||||||
|
public function start()
|
||||||
|
{
|
||||||
|
if (is_resource($this->proc))
|
||||||
|
$this->stop();
|
||||||
|
|
||||||
|
$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));
|
||||||
|
}
|
||||||
|
|
||||||
|
$descriptors = array(
|
||||||
|
0 => array('pipe', 'r'),
|
||||||
|
1 => array('pipe', 'w'),
|
||||||
|
2 => array('pipe', 'w'),
|
||||||
|
);
|
||||||
|
|
||||||
|
$env = array('LANG' => 'en_US.UTF-8');
|
||||||
|
|
||||||
|
$this->proc = proc_open($cmd, $descriptors, $this->pipes,
|
||||||
|
null, $env);
|
||||||
|
|
||||||
|
if (!is_resource($this->proc)) {
|
||||||
|
throw new IDF_Scm_Exception('could not start stdio process');
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->_checkVersion();
|
||||||
|
|
||||||
|
$this->cmdnum = -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Stops the stdio process and closes all pipes
|
||||||
|
*/
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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
|
||||||
|
*/
|
||||||
|
private function _waitForReadyRead()
|
||||||
|
{
|
||||||
|
if (!is_resource($this->pipes[1]))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
$read = array($this->pipes[1], $this->pipes[2]);
|
||||||
|
$streamsChanged = stream_select(
|
||||||
|
$read, $write = null, $except = null, 0, 20000
|
||||||
|
);
|
||||||
|
|
||||||
|
if ($streamsChanged === false) {
|
||||||
|
throw new IDF_Scm_Exception(
|
||||||
|
'Could not select() on read pipe'
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($streamsChanged == 0) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Checks the version of the used stdio protocol
|
||||||
|
*
|
||||||
|
* @throws IDF_Scm_Exception
|
||||||
|
*/
|
||||||
|
private function _checkVersion()
|
||||||
|
{
|
||||||
|
$this->_waitForReadyRead();
|
||||||
|
|
||||||
|
$version = fgets($this->pipes[1]);
|
||||||
|
if ($version === false) {
|
||||||
|
throw new IDF_Scm_Exception(
|
||||||
|
"Could not determine stdio version, stderr is:\n".
|
||||||
|
$this->_readStderr()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
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]);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Writes a command to stdio
|
||||||
|
*
|
||||||
|
* @param array
|
||||||
|
* @param array
|
||||||
|
* @throws IDF_Scm_Exception
|
||||||
|
*/
|
||||||
|
private function _write(array $args, array $options = array())
|
||||||
|
{
|
||||||
|
$cmd = '';
|
||||||
|
if (count($options) > 0) {
|
||||||
|
$cmd = 'o';
|
||||||
|
foreach ($options as $k => $vals) {
|
||||||
|
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 ';
|
||||||
|
}
|
||||||
|
|
||||||
|
$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++;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reads the last output from the stdio process, parses and returns it
|
||||||
|
*
|
||||||
|
* @return string
|
||||||
|
* @throws IDF_Scm_Exception
|
||||||
|
*/
|
||||||
|
private function _readStdout()
|
||||||
|
{
|
||||||
|
$this->oob = array('w' => array(),
|
||||||
|
'p' => array(),
|
||||||
|
't' => array(),
|
||||||
|
'e' => array());
|
||||||
|
|
||||||
|
$output = "";
|
||||||
|
$errcode = 0;
|
||||||
|
|
||||||
|
while (true) {
|
||||||
|
if (!$this->_waitForReadyRead())
|
||||||
|
continue;
|
||||||
|
|
||||||
|
$data = array(0,"",0);
|
||||||
|
$idx = 0;
|
||||||
|
while (true) {
|
||||||
|
$c = fgetc($this->pipes[1]);
|
||||||
|
if ($c === false) {
|
||||||
|
throw new IDF_Scm_Exception(
|
||||||
|
"No data on stdin, stderr is:\n".
|
||||||
|
$this->_readStderr()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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())
|
||||||
|
{
|
||||||
|
$this->_write($args, $options);
|
||||||
|
return $this->_readStdout();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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()
|
||||||
|
{
|
||||||
|
return $this->oob;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
241
src/IDF/Scm/Monotone/Usher.php
Normal file
241
src/IDF/Scm/Monotone/Usher.php
Normal file
@ -0,0 +1,241 @@
|
|||||||
|
<?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) 2010 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 ***** */
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Connects with the admininistrative interface of usher,
|
||||||
|
* the monotone proxy. This class contains only static methods because
|
||||||
|
* there is really no state to keep between each invocation, as usher
|
||||||
|
* closes the connection after every command.
|
||||||
|
*
|
||||||
|
* @author Thomas Keller <me@thomaskeller.biz>
|
||||||
|
*/
|
||||||
|
class IDF_Scm_Monotone_Usher
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Without giving a specific state, returns an array of all servers.
|
||||||
|
* When a state is given, the array contains only servers which are
|
||||||
|
* in the given state.
|
||||||
|
*
|
||||||
|
* @param string $state One of REMOTE, ACTIVE, WAITING, SLEEPING,
|
||||||
|
* STOPPING, STOPPED, SHUTTINGDOWN or SHUTDOWN
|
||||||
|
* @return array
|
||||||
|
*/
|
||||||
|
public static function getServerList($state = null)
|
||||||
|
{
|
||||||
|
$conn = self::_triggerCommand('LIST '.$state);
|
||||||
|
if ($conn == 'none')
|
||||||
|
return array();
|
||||||
|
|
||||||
|
return preg_split('/[ ]/', $conn);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns an array of all open connections to the given server, or to
|
||||||
|
* any server if no server is specified.
|
||||||
|
* If there are no connections to list, an empty array is returned.
|
||||||
|
*
|
||||||
|
* Example:
|
||||||
|
* array("server1" => array(
|
||||||
|
* array("address" => "192.168.1.0", "port" => "13456"),
|
||||||
|
* ...
|
||||||
|
* ),
|
||||||
|
* "server2" => ...
|
||||||
|
* )
|
||||||
|
*
|
||||||
|
* @param string $server
|
||||||
|
* @return array
|
||||||
|
*/
|
||||||
|
public static function getConnectionList($server = null)
|
||||||
|
{
|
||||||
|
$conn = self::_triggerCommand('LISTCONNECTIONS '.$server);
|
||||||
|
if ($conn == 'none')
|
||||||
|
return array();
|
||||||
|
|
||||||
|
$single_conns = preg_split('/[ ]/', $conn);
|
||||||
|
$ret = array();
|
||||||
|
foreach ($single_conns as $conn) {
|
||||||
|
preg_match('/\(\w+\)([^:]):(\d+)/', $conn, $matches);
|
||||||
|
$ret[$matches[1]][] = (object)array(
|
||||||
|
'server' => $matches[1],
|
||||||
|
'address' => $matches[2],
|
||||||
|
'port' => $matches[3],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return $ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the status of a particular server, or of the usher as a whole if
|
||||||
|
* no server is specified.
|
||||||
|
*
|
||||||
|
* @param string $server
|
||||||
|
* @return One of REMOTE, SLEEPING, STOPPING, STOPPED for servers or
|
||||||
|
* ACTIVE, WAITING, SHUTTINGDOWN or SHUTDOWN for usher itself
|
||||||
|
*/
|
||||||
|
public static function getStatus($server = null)
|
||||||
|
{
|
||||||
|
return self::_triggerCommand('STATUS '.$server);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Looks up the name of the server that would be used for an incoming
|
||||||
|
* connection having the given host and pattern.
|
||||||
|
*
|
||||||
|
* @param string $host Host
|
||||||
|
* @param string $pattern Branch pattern
|
||||||
|
* @return server name
|
||||||
|
* @throws IDF_Scm_Exception
|
||||||
|
*/
|
||||||
|
public static function matchServer($host, $pattern)
|
||||||
|
{
|
||||||
|
$ret = self::_triggerCommand('MATCH '.$host.' '.$pattern);
|
||||||
|
if (preg_match('/^OK: (.+)/', $ret, $m))
|
||||||
|
return $m[1];
|
||||||
|
preg_match('/^ERROR: (.+)/', $ret, $m);
|
||||||
|
throw new IDF_Scm_Exception('could not match server: '.$m[1]);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Prevent the given local server from receiving further connections,
|
||||||
|
* and stop it once all connections are closed. The return value will
|
||||||
|
* be the new status of that server: ACTIVE local servers will become
|
||||||
|
* STOPPING, and WAITING and SLEEPING serveres become STOPPED.
|
||||||
|
* Servers in other states are not affected.
|
||||||
|
*
|
||||||
|
* @param string $server
|
||||||
|
* @return string State of the server after the command
|
||||||
|
*/
|
||||||
|
public static function stopServer($server)
|
||||||
|
{
|
||||||
|
return self::_triggerCommand("STOP $server");
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Allow a STOPPED or STOPPING server to receive connections again.
|
||||||
|
* The return value is the new status of that server: STOPPING servers
|
||||||
|
* become ACTIVE, and STOPPED servers become SLEEPING. Servers in other
|
||||||
|
* states are not affected.
|
||||||
|
*
|
||||||
|
* @param string $server
|
||||||
|
* @return string State of the server after the command
|
||||||
|
*/
|
||||||
|
public static function startServer($server)
|
||||||
|
{
|
||||||
|
return self::_triggerCommand('START '.$server);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Immediately kill the given local server, dropping any open connections,
|
||||||
|
* and prevent is from receiving new connections and restarting. The named
|
||||||
|
* server will immediately change to state STOPPED.
|
||||||
|
*
|
||||||
|
* @param string $server
|
||||||
|
* @return bool True if successful
|
||||||
|
*/
|
||||||
|
public static function killServer($server)
|
||||||
|
{
|
||||||
|
return self::_triggerCommand('KILL_NOW '.$server) == 'ok';
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Do not accept new connections for any servers, local or remote.
|
||||||
|
*
|
||||||
|
* @return bool True if successful
|
||||||
|
*/
|
||||||
|
public static function shutDown()
|
||||||
|
{
|
||||||
|
return self::_triggerCommand('SHUTDOWN') == 'ok';
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Begin accepting connections after a SHUTDOWN.
|
||||||
|
*
|
||||||
|
* @return bool True if successful
|
||||||
|
*/
|
||||||
|
public static function startUp()
|
||||||
|
{
|
||||||
|
return self::_triggerCommand('STARTUP') == 'ok';
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reload the config file, the same as sending SIGHUP.
|
||||||
|
*
|
||||||
|
* @return bool True if successful (after the configuration was reloaded)
|
||||||
|
*/
|
||||||
|
public static function reload()
|
||||||
|
{
|
||||||
|
return self::_triggerCommand('RELOAD') == 'ok';
|
||||||
|
}
|
||||||
|
|
||||||
|
private static function _triggerCommand($cmd)
|
||||||
|
{
|
||||||
|
$uc = Pluf::f('mtn_usher');
|
||||||
|
if (empty($uc['host'])) {
|
||||||
|
throw new IDF_Scm_Exception('usher host is empty');
|
||||||
|
}
|
||||||
|
if (!preg_match('/^\d+$/', $uc['port']) ||
|
||||||
|
$uc['port'] == 0)
|
||||||
|
{
|
||||||
|
throw new IDF_Scm_Exception('usher port is invalid');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (empty($uc['user'])) {
|
||||||
|
throw new IDF_Scm_Exception('usher user is empty');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (empty($uc['pass'])) {
|
||||||
|
throw new IDF_Scm_Exception('usher pass is empty');
|
||||||
|
}
|
||||||
|
|
||||||
|
$sock = @fsockopen($uc['host'], $uc['port'], $errno, $errstr);
|
||||||
|
if (!$sock) {
|
||||||
|
throw new IDF_Scm_Exception(
|
||||||
|
"could not connect to usher: $errstr ($errno)"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
fwrite($sock, 'USERPASS '.$uc['user'].' '.$uc['pass'].'\n');
|
||||||
|
if (feof($sock)) {
|
||||||
|
throw new IDF_Scm_Exception(
|
||||||
|
'usher closed the connection - probably wrong admin '.
|
||||||
|
'username or password'
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
fwrite($sock, $cmd.'\n');
|
||||||
|
$out = '';
|
||||||
|
while (!feof($sock)) {
|
||||||
|
$out .= fgets($sock);
|
||||||
|
}
|
||||||
|
fclose($sock);
|
||||||
|
$out = rtrim($out);
|
||||||
|
|
||||||
|
if ($out == 'unknown command') {
|
||||||
|
throw new IDF_Scm_Exception("unknown command: $cmd");
|
||||||
|
}
|
||||||
|
|
||||||
|
return $out;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -80,9 +80,10 @@ class IDF_Scm_Svn extends IDF_Scm
|
|||||||
* Returns the URL of the subversion repository.
|
* Returns the URL of the subversion repository.
|
||||||
*
|
*
|
||||||
* @param IDF_Project
|
* @param IDF_Project
|
||||||
|
* @param string
|
||||||
* @return string URL
|
* @return string URL
|
||||||
*/
|
*/
|
||||||
public static function getAnonymousAccessUrl($project)
|
public static function getAnonymousAccessUrl($project,$commit=null)
|
||||||
{
|
{
|
||||||
$conf = $project->getConf();
|
$conf = $project->getConf();
|
||||||
if (false !== ($url=$conf->getVal('svn_remote_url', false))
|
if (false !== ($url=$conf->getVal('svn_remote_url', false))
|
||||||
@ -97,9 +98,10 @@ class IDF_Scm_Svn extends IDF_Scm
|
|||||||
* Returns the URL of the subversion repository.
|
* Returns the URL of the subversion repository.
|
||||||
*
|
*
|
||||||
* @param IDF_Project
|
* @param IDF_Project
|
||||||
|
* @param string
|
||||||
* @return string URL
|
* @return string URL
|
||||||
*/
|
*/
|
||||||
public static function getAuthAccessUrl($project, $user)
|
public static function getAuthAccessUrl($project, $user, $commit=null)
|
||||||
{
|
{
|
||||||
$conf = $project->getConf();
|
$conf = $project->getConf();
|
||||||
if (false !== ($url=$conf->getVal('svn_remote_url', false))
|
if (false !== ($url=$conf->getVal('svn_remote_url', false))
|
||||||
|
239
src/IDF/Tests/TestMonotone.php
Normal file
239
src/IDF/Tests/TestMonotone.php
Normal file
@ -0,0 +1,239 @@
|
|||||||
|
<?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) 2010 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 ***** */
|
||||||
|
|
||||||
|
require_once("simpletest/autorun.php");
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Test the monotone class.
|
||||||
|
*/
|
||||||
|
class IDF_Tests_TestMonotone extends UnitTestCase
|
||||||
|
{
|
||||||
|
private $tmpdir, $dbfile, $mtnInstance;
|
||||||
|
|
||||||
|
private function mtnCall($args, $stdin = null, $dir = null)
|
||||||
|
{
|
||||||
|
// if you have an SSH agent running for key caching,
|
||||||
|
// please disable it
|
||||||
|
$cmdline = array("mtn",
|
||||||
|
"--confdir", $this->tmpdir,
|
||||||
|
"--db", $this->dbfile,
|
||||||
|
"--norc",
|
||||||
|
"--timestamps");
|
||||||
|
|
||||||
|
$cmdline = array_merge($cmdline, $args);
|
||||||
|
|
||||||
|
$descriptorspec = array(
|
||||||
|
0 => array("pipe", "r"),
|
||||||
|
1 => array("pipe", "w"),
|
||||||
|
2 => array("file", "{$this->tmpdir}/mtn-errors", "a")
|
||||||
|
);
|
||||||
|
|
||||||
|
$pipes = array();
|
||||||
|
$dir = !empty($dir) ? $dir : $this->tmpdir;
|
||||||
|
$process = proc_open(implode(" ", $cmdline),
|
||||||
|
$descriptorspec,
|
||||||
|
$pipes,
|
||||||
|
$dir);
|
||||||
|
|
||||||
|
if (!is_resource($process)) {
|
||||||
|
throw new Exception("could not create process");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!empty($stdin)) {
|
||||||
|
fwrite($pipes[0], $stdin);
|
||||||
|
fclose($pipes[0]);
|
||||||
|
}
|
||||||
|
|
||||||
|
$stdout = stream_get_contents($pipes[1]);
|
||||||
|
fclose($pipes[1]);
|
||||||
|
|
||||||
|
$ret = proc_close($process);
|
||||||
|
if ($ret != 0) {
|
||||||
|
throw new Exception(
|
||||||
|
"call ended with a non-zero error code (complete cmdline was: ".
|
||||||
|
implode(" ", $cmdline).")"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return $stdout;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function __construct()
|
||||||
|
{
|
||||||
|
parent::__construct("Test the monotone class.");
|
||||||
|
|
||||||
|
$this->tmpdir = sys_get_temp_dir() . "/mtn-test";
|
||||||
|
$this->dbfile = "{$this->tmpdir}/test.mtn";
|
||||||
|
|
||||||
|
set_include_path(get_include_path() . ":../../../pluf-master/src");
|
||||||
|
require_once("Pluf.php");
|
||||||
|
|
||||||
|
Pluf::start(dirname(__FILE__)."/../conf/idf.php");
|
||||||
|
|
||||||
|
// Pluf::f() mocking
|
||||||
|
$GLOBALS['_PX_config']['mtn_repositories'] = "{$this->tmpdir}/%s.mtn";
|
||||||
|
}
|
||||||
|
|
||||||
|
private static function deleteRecursive($dirname)
|
||||||
|
{
|
||||||
|
if (is_dir($dirname))
|
||||||
|
$dir_handle=opendir($dirname);
|
||||||
|
|
||||||
|
while ($file = readdir($dir_handle)) {
|
||||||
|
if ($file!="." && $file!="..") {
|
||||||
|
if (!is_dir($dirname."/".$file)) {
|
||||||
|
unlink ($dirname."/".$file);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
self::deleteRecursive($dirname."/".$file);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
closedir($dir_handle);
|
||||||
|
rmdir($dirname);
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function setUp()
|
||||||
|
{
|
||||||
|
if (is_dir($this->tmpdir)) {
|
||||||
|
self::deleteRecursive($this->tmpdir);
|
||||||
|
}
|
||||||
|
|
||||||
|
mkdir($this->tmpdir);
|
||||||
|
|
||||||
|
$this->mtnCall(array("db", "init"));
|
||||||
|
|
||||||
|
$this->mtnCall(array("genkey", "test@test.de"), "\n\n");
|
||||||
|
|
||||||
|
$workspaceRoot = "{$this->tmpdir}/test-workspace";
|
||||||
|
mkdir($workspaceRoot);
|
||||||
|
|
||||||
|
$this->mtnCall(array("setup", "-b", "testbranch", "."), null, $workspaceRoot);
|
||||||
|
|
||||||
|
file_put_contents("$workspaceRoot/foo", "blubber");
|
||||||
|
$this->mtnCall(array("add", "foo"), null, $workspaceRoot);
|
||||||
|
|
||||||
|
$this->mtnCall(array("commit", "-m", "initial"), null, $workspaceRoot);
|
||||||
|
|
||||||
|
file_put_contents("$workspaceRoot/bar", "blafoo");
|
||||||
|
mkdir("$workspaceRoot/subdir");
|
||||||
|
file_put_contents("$workspaceRoot/subdir/bla", "blabla");
|
||||||
|
$this->mtnCall(array("add", "-R", "--unknown"), null, $workspaceRoot);
|
||||||
|
|
||||||
|
$this->mtnCall(array("commit", "-m", "second"), null, $workspaceRoot);
|
||||||
|
|
||||||
|
$rev = $this->mtnCall(array("au", "get_base_revision_id"), null, $workspaceRoot);
|
||||||
|
$this->mtnCall(array("tag", rtrim($rev), "release-1.0"));
|
||||||
|
|
||||||
|
$project = new IDF_Project();
|
||||||
|
$project->shortname = "test";
|
||||||
|
$this->mtnInstance = new IDF_Scm_Monotone($project);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testIsAvailable()
|
||||||
|
{
|
||||||
|
$this->assertTrue($this->mtnInstance->isAvailable());
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testGetBranches()
|
||||||
|
{
|
||||||
|
$branches = $this->mtnInstance->getBranches();
|
||||||
|
$this->assertEqual(1, count($branches));
|
||||||
|
list($key, $value) = each($branches);
|
||||||
|
$this->assertEqual("h:testbranch", $key);
|
||||||
|
$this->assertEqual("testbranch", $value);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testGetTags()
|
||||||
|
{
|
||||||
|
$tags = $this->mtnInstance->getTags();
|
||||||
|
$this->assertEqual(1, count($tags));
|
||||||
|
list($key, $value) = each($tags);
|
||||||
|
$this->assertEqual("t:release-1.0", $key);
|
||||||
|
$this->assertEqual("release-1.0", $value);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testInBranches()
|
||||||
|
{
|
||||||
|
$revOut = $this->mtnCall(array("au", "select", "b:testbranch"));
|
||||||
|
$revs = preg_split('/\n/', $revOut, -1, PREG_SPLIT_NO_EMPTY);
|
||||||
|
|
||||||
|
$branches = $this->mtnInstance->inBranches($revs[0], null);
|
||||||
|
$this->assertEqual(1, count($branches));
|
||||||
|
$this->assertEqual("h:testbranch", $branches[0]);
|
||||||
|
|
||||||
|
$branches = $this->mtnInstance->inBranches("t:release-1.0", null);
|
||||||
|
$this->assertEqual(1, count($branches));
|
||||||
|
$this->assertEqual("h:testbranch", $branches[0]);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testInTags()
|
||||||
|
{
|
||||||
|
$rev = $this->mtnCall(array("au", "select", "t:release-1.0"));
|
||||||
|
$tags = $this->mtnInstance->inTags(rtrim($rev), null);
|
||||||
|
$this->assertEqual(1, count($tags));
|
||||||
|
$this->assertEqual("t:release-1.0", $tags[0]);
|
||||||
|
|
||||||
|
// pick the first (root) revisions in this database
|
||||||
|
$rev = $this->mtnCall(array("au", "roots"));
|
||||||
|
$tags = $this->mtnInstance->inTags(rtrim($rev), null);
|
||||||
|
$this->assertEqual(0, count($tags));
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testGetTree()
|
||||||
|
{
|
||||||
|
$files = $this->mtnInstance->getTree("t:release-1.0");
|
||||||
|
$this->assertEqual(3, count($files));
|
||||||
|
|
||||||
|
$this->assertEqual("bar", $files[0]->file);
|
||||||
|
$this->assertEqual("blob", $files[0]->type);
|
||||||
|
$this->assertEqual(6, $files[0]->size); // "blafoo"
|
||||||
|
$this->assertEqual("second\n", $files[0]->log);
|
||||||
|
|
||||||
|
$this->assertEqual("foo", $files[1]->file);
|
||||||
|
$this->assertEqual("blob", $files[1]->type);
|
||||||
|
$this->assertEqual(7, $files[1]->size); // "blubber"
|
||||||
|
$this->assertEqual("initial\n", $files[1]->log);
|
||||||
|
|
||||||
|
$this->assertEqual("subdir", $files[2]->file);
|
||||||
|
$this->assertEqual("tree", $files[2]->type);
|
||||||
|
$this->assertEqual(0, $files[2]->size);
|
||||||
|
|
||||||
|
$files = $this->mtnInstance->getTree("t:release-1.0", "subdir");
|
||||||
|
$this->assertEqual(1, count($files));
|
||||||
|
|
||||||
|
$this->assertEqual("bla", $files[0]->file);
|
||||||
|
$this->assertEqual("subdir/bla", $files[0]->fullpath);
|
||||||
|
$this->assertEqual("blob", $files[0]->type);
|
||||||
|
$this->assertEqual(6, $files[0]->size); // "blabla"
|
||||||
|
$this->assertEqual("second\n", $files[0]->log);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testIsValidRevision()
|
||||||
|
{
|
||||||
|
$this->assertTrue($this->mtnInstance->isValidRevision("t:release-1.0"));
|
||||||
|
$this->assertFalse($this->mtnInstance->isValidRevision("abcdef12345"));
|
||||||
|
}
|
||||||
|
}
|
@ -317,6 +317,148 @@ class IDF_Views_Admin
|
|||||||
),
|
),
|
||||||
$request);
|
$request);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Usher servers overview
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
public $usher_precond = array('Pluf_Precondition::staffRequired');
|
||||||
|
public function usher($request, $match)
|
||||||
|
{
|
||||||
|
$title = __('Usher management');
|
||||||
|
$servers = array();
|
||||||
|
foreach (IDF_Scm_Monotone_Usher::getServerList() as $server) {
|
||||||
|
$servers[] = (object)array(
|
||||||
|
"name" => $server,
|
||||||
|
"status" => IDF_Scm_Monotone_Usher::getStatus($server),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return Pluf_Shortcuts_RenderToResponse(
|
||||||
|
'idf/gadmin/usher/index.html',
|
||||||
|
array(
|
||||||
|
'page_title' => $title,
|
||||||
|
'servers' => $servers,
|
||||||
|
),
|
||||||
|
$request
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Usher control
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
public $usherControl_precond = array('Pluf_Precondition::staffRequired');
|
||||||
|
public function usherControl($request, $match)
|
||||||
|
{
|
||||||
|
$title = __('Usher control');
|
||||||
|
$action = $match[1];
|
||||||
|
|
||||||
|
if (!empty($action)) {
|
||||||
|
if (!in_array($action, array('reload', 'shutdown', 'startup'))) {
|
||||||
|
throw new Pluf_HTTP_Error404();
|
||||||
|
}
|
||||||
|
|
||||||
|
$msg = null;
|
||||||
|
if ($action == 'reload') {
|
||||||
|
IDF_Scm_Monotone_Usher::reload();
|
||||||
|
$msg = __('Usher configuration has been reloaded');
|
||||||
|
}
|
||||||
|
else if ($action == 'shutdown') {
|
||||||
|
IDF_Scm_Monotone_Usher::shutDown();
|
||||||
|
$msg = __('Usher has been shut down');
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
IDF_Scm_Monotone_Usher::startUp();
|
||||||
|
$msg = __('Usher has been started up');
|
||||||
|
}
|
||||||
|
|
||||||
|
$request->user->setMessage($msg);
|
||||||
|
$url = Pluf_HTTP_URL_urlForView('IDF_Views_Admin::usherControl', array(''));
|
||||||
|
return new Pluf_HTTP_Response_Redirect($url);
|
||||||
|
}
|
||||||
|
|
||||||
|
return Pluf_Shortcuts_RenderToResponse(
|
||||||
|
'idf/gadmin/usher/control.html',
|
||||||
|
array(
|
||||||
|
'page_title' => $title,
|
||||||
|
'status' => IDF_Scm_Monotone_Usher::getStatus(),
|
||||||
|
),
|
||||||
|
$request
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Usher control
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
public $usherServerControl_precond = array('Pluf_Precondition::staffRequired');
|
||||||
|
public function usherServerControl($request, $match)
|
||||||
|
{
|
||||||
|
$server = $match[1];
|
||||||
|
if (!in_array($server, IDF_Scm_Monotone_Usher::getServerList())) {
|
||||||
|
throw new Pluf_HTTP_Error404();
|
||||||
|
}
|
||||||
|
|
||||||
|
$action = $match[2];
|
||||||
|
if (!in_array($action, array('start', 'stop', 'kill'))) {
|
||||||
|
throw new Pluf_HTTP_Error404();
|
||||||
|
}
|
||||||
|
|
||||||
|
$msg = null;
|
||||||
|
if ($action == 'start') {
|
||||||
|
IDF_Scm_Monotone_Usher::startServer($server);
|
||||||
|
$msg = sprintf(__('The server "%s" has been started'), $server);
|
||||||
|
}
|
||||||
|
else if ($action == 'stop') {
|
||||||
|
IDF_Scm_Monotone_Usher::stopServer($server);
|
||||||
|
$msg = sprintf(__('The server "%s" has been stopped'), $server);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
IDF_Scm_Monotone_Usher::killServer($server);
|
||||||
|
$msg = sprintf(__('The server "%s" has been killed'), $server);
|
||||||
|
}
|
||||||
|
|
||||||
|
$request->user->setMessage($msg);
|
||||||
|
$url = Pluf_HTTP_URL_urlForView('IDF_Views_Admin::usher');
|
||||||
|
return new Pluf_HTTP_Response_Redirect($url);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Open connections for a configured server
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
public $usherServerConnections_precond = array('Pluf_Precondition::staffRequired');
|
||||||
|
public function usherServerConnections($request, $match)
|
||||||
|
{
|
||||||
|
$server = $match[1];
|
||||||
|
if (!in_array($server, IDF_Scm_Monotone_Usher::getServerList())) {
|
||||||
|
throw new Pluf_HTTP_Error404();
|
||||||
|
}
|
||||||
|
|
||||||
|
$title = sprintf(__('Open connections for "%s"'), $server);
|
||||||
|
|
||||||
|
$connections = IDF_Scm_Monotone_Usher::getConnectionList($server);
|
||||||
|
if (count($connections) == 0) {
|
||||||
|
$request->user->setMessage(sprintf(
|
||||||
|
__('no connections for server "%s"'), $server
|
||||||
|
));
|
||||||
|
$url = Pluf_HTTP_URL_urlForView('IDF_Views_Admin::usher');
|
||||||
|
return new Pluf_HTTP_Response_Redirect($url);
|
||||||
|
}
|
||||||
|
|
||||||
|
return Pluf_Shortcuts_RenderToResponse(
|
||||||
|
'idf/gadmin/usher/connections.html',
|
||||||
|
array(
|
||||||
|
'page_title' => $title,
|
||||||
|
'server' => $server,
|
||||||
|
'connections' => $connections,
|
||||||
|
),
|
||||||
|
$request
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function IDF_Views_Admin_bool($field, $item)
|
function IDF_Views_Admin_bool($field, $item)
|
||||||
|
@ -520,6 +520,7 @@ class IDF_Views_Project
|
|||||||
'git' => __('git'),
|
'git' => __('git'),
|
||||||
'svn' => __('Subversion'),
|
'svn' => __('Subversion'),
|
||||||
'mercurial' => __('mercurial'),
|
'mercurial' => __('mercurial'),
|
||||||
|
'mtn' => __('monotone'),
|
||||||
);
|
);
|
||||||
$repository_type = $options[$scm];
|
$repository_type = $options[$scm];
|
||||||
return Pluf_Shortcuts_RenderToResponse('idf/admin/source.html',
|
return Pluf_Shortcuts_RenderToResponse('idf/admin/source.html',
|
||||||
|
@ -37,10 +37,9 @@ class IDF_Views_Source
|
|||||||
public static $supportedExtenstions = array(
|
public static $supportedExtenstions = array(
|
||||||
'ascx', 'ashx', 'asmx', 'aspx', 'browser', 'bsh', 'c', 'cc',
|
'ascx', 'ashx', 'asmx', 'aspx', 'browser', 'bsh', 'c', 'cc',
|
||||||
'config', 'cpp', 'cs', 'csh', 'csproj', 'css', 'cv', 'cyc',
|
'config', 'cpp', 'cs', 'csh', 'csproj', 'css', 'cv', 'cyc',
|
||||||
'html', 'html', 'java', 'js', 'm', 'master', 'pch', 'perl', 'php',
|
'html', 'html', 'java', 'js', 'master', 'perl', 'php', 'pl',
|
||||||
'pl', 'plist', 'pm', 'py', 'rb', 'sh', 'sitemap', 'skin', 'sln',
|
'pm', 'py', 'rb', 'sh', 'sitemap', 'skin', 'sln', 'svc', 'vala',
|
||||||
'svc', 'vala', 'vb', 'vbproj', 'wsdl', 'xhtml', 'xml', 'xsd',
|
'vb', 'vbproj', 'wsdl', 'xhtml', 'xml', 'xsd', 'xsl', 'xslt');
|
||||||
'xsl', 'xslt');
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Display help on how to checkout etc.
|
* Display help on how to checkout etc.
|
||||||
@ -309,7 +308,7 @@ class IDF_Views_Source
|
|||||||
$in_branches = $scm->inBranches($cobject->commit, '');
|
$in_branches = $scm->inBranches($cobject->commit, '');
|
||||||
$tags = $scm->getTags();
|
$tags = $scm->getTags();
|
||||||
$in_tags = $scm->inTags($cobject->commit, '');
|
$in_tags = $scm->inTags($cobject->commit, '');
|
||||||
return Pluf_Shortcuts_RenderToResponse('idf/source/commit.html',
|
return Pluf_Shortcuts_RenderToResponse('idf/source/'.$scmConf.'/commit.html',
|
||||||
array(
|
array(
|
||||||
'page_title' => $page_title,
|
'page_title' => $page_title,
|
||||||
'title' => $title,
|
'title' => $title,
|
||||||
@ -598,3 +597,16 @@ function IDF_Views_Source_PrettySizeSimple($size)
|
|||||||
return Pluf_Utils::prettySize($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)));
|
||||||
|
}
|
||||||
|
|
||||||
|
@ -134,7 +134,7 @@ class IDF_Views_User
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Delete a SSH key.
|
* Delete a public key.
|
||||||
*
|
*
|
||||||
* This is redirecting to the preferences
|
* This is redirecting to the preferences
|
||||||
*/
|
*/
|
||||||
@ -148,7 +148,7 @@ class IDF_Views_User
|
|||||||
return new Pluf_HTTP_Response_Forbidden($request);
|
return new Pluf_HTTP_Response_Forbidden($request);
|
||||||
}
|
}
|
||||||
$key->delete();
|
$key->delete();
|
||||||
$request->user->setMessage(__('The SSH key has been deleted.'));
|
$request->user->setMessage(__('The public key has been deleted.'));
|
||||||
}
|
}
|
||||||
return new Pluf_HTTP_Response_Redirect($url);
|
return new Pluf_HTTP_Response_Redirect($url);
|
||||||
}
|
}
|
||||||
|
@ -73,6 +73,90 @@ $cfg['git_write_remote_url'] = 'git@localhost:%s.git';
|
|||||||
$cfg['svn_repositories'] = 'file:///home/svn/repositories/%s';
|
$cfg['svn_repositories'] = 'file:///home/svn/repositories/%s';
|
||||||
$cfg['svn_remote_url'] = 'http://localhost/svn/%s';
|
$cfg['svn_remote_url'] = 'http://localhost/svn/%s';
|
||||||
|
|
||||||
|
# Path to the monotone binary
|
||||||
|
$cfg['mtn_path'] = 'mtn';
|
||||||
|
# Additional options for the started monotone process
|
||||||
|
$cfg['mtn_opts'] = array('--no-workspace', '--norc');
|
||||||
|
#
|
||||||
|
# You can setup monotone for use with indefero in two ways:
|
||||||
|
#
|
||||||
|
# 1) One database for everything:
|
||||||
|
# Set 'mtn_repositories' below to a fixed database path, such as
|
||||||
|
# '/home/mtn/repositories/all_projects.mtn'
|
||||||
|
#
|
||||||
|
# Pro: - easy to setup and to manage
|
||||||
|
# Con: - while read access can be configured per-branch,
|
||||||
|
# granting write access rights to a user means that
|
||||||
|
# he can write anything in the global database
|
||||||
|
# - database lock problem: the database from which
|
||||||
|
# indefero reads its data cannot be used to serve the
|
||||||
|
# contents to the users, as the serve process locks
|
||||||
|
# the database
|
||||||
|
#
|
||||||
|
# 2) One database for every project with 'usher':
|
||||||
|
# Set 'mtn_remote_url' below to a string which matches your setup.
|
||||||
|
# Again, the '%s' placeholder will be expanded to the project's
|
||||||
|
# short name. Note that 'mtn_remote_url' is used as internal
|
||||||
|
# URI (to access the data for indefero) as well as external URI
|
||||||
|
# (for end users) at the same time.
|
||||||
|
#
|
||||||
|
# Then download and configure 'usher'
|
||||||
|
# (mtn clone mtn://monotone.ca?net.venge.monotone.contrib.usher)
|
||||||
|
# which acts as proxy in front of all single project databases.
|
||||||
|
# Usher's server names should be mapped to the project's short names,
|
||||||
|
# so you end up with something like this for every project:
|
||||||
|
#
|
||||||
|
# server "project"
|
||||||
|
# local "-d" "/home/mtn/repositories/project.mtn" "*"
|
||||||
|
#
|
||||||
|
# Alternatively if you assign every project a unique DNS such as
|
||||||
|
# 'project.my-hosting.biz', you can also configure it like this:
|
||||||
|
#
|
||||||
|
# host "project.my-hosting.biz"
|
||||||
|
# local "-d" "/home/mtn/repositories/project.mtn" "*"
|
||||||
|
#
|
||||||
|
# Pro: - read and write access can be granted per project
|
||||||
|
# - no database locking issues
|
||||||
|
# - one public server running on the one well-known port
|
||||||
|
# Con: - harder to setup
|
||||||
|
#
|
||||||
|
# Usher can also be used to forward sync requests to remote servers,
|
||||||
|
# please consult its README file for more information.
|
||||||
|
#
|
||||||
|
# monotone also allows to use SSH as transport protocol, so if you do not plan
|
||||||
|
# to setup a netsync server as described above, then just enter a URI like
|
||||||
|
# 'ssh://my-host.biz/home/mtn/repositories/%s.mtn' in 'mtn_remote_url'.
|
||||||
|
#
|
||||||
|
$cfg['mtn_repositories'] = '/home/mtn/repositories/%s.mtn';
|
||||||
|
$cfg['mtn_remote_url'] = 'mtn://my-host.biz/%s';
|
||||||
|
#
|
||||||
|
# Whether the particular database(s) are accessed locally (via automate stdio)
|
||||||
|
# or remotely (via automate remote_stdio). 'remote' is the default for
|
||||||
|
# netsync setups, while 'local' access should be choosed for ssh access.
|
||||||
|
#
|
||||||
|
# Note that you need to setup the hook 'get_remote_automate_permitted' for
|
||||||
|
# each remotely accessible database. A full HOWTO set this up is beyond this
|
||||||
|
# scope, please refer to the documentation of monotone and / or ask on the
|
||||||
|
# mailing list (monotone-users@nongnu.org) or IRC channel
|
||||||
|
# (irc.oftc.net/#monotone)
|
||||||
|
#
|
||||||
|
$cfg['mtn_db_access'] = 'remote';
|
||||||
|
#
|
||||||
|
# If configured, this allows basic control of a running usher process
|
||||||
|
# via the forge administration
|
||||||
|
#
|
||||||
|
# 'host' and 'port' must be set to the specific bits from usher's
|
||||||
|
# configured 'adminaddr', 'user' and 'pass' must match the values set for
|
||||||
|
# the configured 'userpass' combination
|
||||||
|
#
|
||||||
|
#$cfg['mtn_usher'] = array(
|
||||||
|
# 'host' => 'localhost',
|
||||||
|
# 'port' => 12345,
|
||||||
|
# 'user' => 'admin',
|
||||||
|
# 'pass' => 'admin',
|
||||||
|
#);
|
||||||
|
#
|
||||||
|
|
||||||
# Mercurial repositories path
|
# Mercurial repositories path
|
||||||
#$cfg['mercurial_repositories'] = '/home/mercurial/repositories/%s';
|
#$cfg['mercurial_repositories'] = '/home/mercurial/repositories/%s';
|
||||||
#$cfg['mercurial_remote_url'] = 'http://projects.ceondo.com/hg/%s';
|
#$cfg['mercurial_remote_url'] = 'http://projects.ceondo.com/hg/%s';
|
||||||
@ -209,6 +293,7 @@ $cfg['languages'] = array('en', 'fr');
|
|||||||
$cfg['allowed_scm'] = array('git' => 'IDF_Scm_Git',
|
$cfg['allowed_scm'] = array('git' => 'IDF_Scm_Git',
|
||||||
'svn' => 'IDF_Scm_Svn',
|
'svn' => 'IDF_Scm_Svn',
|
||||||
'mercurial' => 'IDF_Scm_Mercurial',
|
'mercurial' => 'IDF_Scm_Mercurial',
|
||||||
|
'mtn' => 'IDF_Scm_Monotone',
|
||||||
);
|
);
|
||||||
|
|
||||||
# If you want to use another memtypes database
|
# If you want to use another memtypes database
|
||||||
|
@ -386,6 +386,29 @@ $ctl[] = array('regex' => '#^/admin/users/(\d+)/$#',
|
|||||||
'model' => 'IDF_Views_Admin',
|
'model' => 'IDF_Views_Admin',
|
||||||
'method' => 'userUpdate');
|
'method' => 'userUpdate');
|
||||||
|
|
||||||
|
if (Pluf::f("mtn_usher", null) !== null)
|
||||||
|
{
|
||||||
|
$ctl[] = array('regex' => '#^/admin/usher/$#',
|
||||||
|
'base' => $base,
|
||||||
|
'model' => 'IDF_Views_Admin',
|
||||||
|
'method' => 'usher');
|
||||||
|
|
||||||
|
$ctl[] = array('regex' => '#^/admin/usher/control/(.*)$#',
|
||||||
|
'base' => $base,
|
||||||
|
'model' => 'IDF_Views_Admin',
|
||||||
|
'method' => 'usherControl');
|
||||||
|
|
||||||
|
$ctl[] = array('regex' => '#^/admin/usher/server/(.+)/control/(.+)$#',
|
||||||
|
'base' => $base,
|
||||||
|
'model' => 'IDF_Views_Admin',
|
||||||
|
'method' => 'usherServerControl');
|
||||||
|
|
||||||
|
$ctl[] = array('regex' => '#^/admin/usher/server/(.+)/connections/$#',
|
||||||
|
'base' => $base,
|
||||||
|
'model' => 'IDF_Views_Admin',
|
||||||
|
'method' => 'usherServerConnections');
|
||||||
|
}
|
||||||
|
|
||||||
// ---------- UTILITY VIEWS -------------------------------
|
// ---------- UTILITY VIEWS -------------------------------
|
||||||
|
|
||||||
$ctl[] = array('regex' => '#^/register/$#',
|
$ctl[] = array('regex' => '#^/register/$#',
|
||||||
|
@ -43,6 +43,9 @@
|
|||||||
<div id="main-tabs">
|
<div id="main-tabs">
|
||||||
<a href="{url 'IDF_Views_Admin::projects'}"{block tabprojects}{/block}>{trans 'Projects'}</a>
|
<a href="{url 'IDF_Views_Admin::projects'}"{block tabprojects}{/block}>{trans 'Projects'}</a>
|
||||||
<a href="{url 'IDF_Views_Admin::users'}"{block tabusers}{/block}>{trans 'People'}</a>
|
<a href="{url 'IDF_Views_Admin::users'}"{block tabusers}{/block}>{trans 'People'}</a>
|
||||||
|
{if $usherConfigured}
|
||||||
|
<a href="{url 'IDF_Views_Admin::usher'}"{block tabusher}{/block}>{trans 'Usher'}</a>
|
||||||
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
<div id="sub-tabs">{block subtabs}{/block}</div>
|
<div id="sub-tabs">{block subtabs}{/block}</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -52,6 +52,13 @@
|
|||||||
{$form.f.svn_password|unsafe}
|
{$form.f.svn_password|unsafe}
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
|
<tr class="mtn-form">
|
||||||
|
<th><strong>{$form.f.mtn_master_branch.labelTag}:</strong></th>
|
||||||
|
<td>{if $form.f.mtn_master_branch.errors}{$form.f.mtn_master_branch.fieldErrors}{/if}
|
||||||
|
{$form.f.mtn_master_branch|unsafe}<br />
|
||||||
|
<span class="helptext">{$form.f.mtn_master_branch.help_text}</span>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<th>{$form.f.template.labelTag}</th>
|
<th>{$form.f.template.labelTag}</th>
|
||||||
<td>{if $form.f.template.errors}{$form.f.template.fieldErrors}{/if}
|
<td>{if $form.f.template.errors}{$form.f.template.fieldErrors}{/if}
|
||||||
@ -119,12 +126,22 @@ $(document).ready(function() {
|
|||||||
if ($("#id_scm option:selected").val() != "svn") {
|
if ($("#id_scm option:selected").val() != "svn") {
|
||||||
$(".svn-form").hide();
|
$(".svn-form").hide();
|
||||||
}
|
}
|
||||||
|
// Hide if not mtn
|
||||||
|
if ($("#id_scm option:selected").val() != "mtn") {
|
||||||
|
$(".mtn-form").hide();
|
||||||
|
}
|
||||||
$("#id_scm").change(function () {
|
$("#id_scm").change(function () {
|
||||||
if ($("#id_scm option:selected").val() == "svn") {
|
if ($("#id_scm option:selected").val() == "svn") {
|
||||||
$(".svn-form").show();
|
$(".svn-form").show();
|
||||||
} else {
|
} else {
|
||||||
$(".svn-form").hide();
|
$(".svn-form").hide();
|
||||||
}
|
}
|
||||||
|
if ($("#id_scm option:selected").val() == "mtn") {
|
||||||
|
$(".mtn-form").show();
|
||||||
|
} else {
|
||||||
|
$(".mtn-form").hide();
|
||||||
|
}
|
||||||
|
|
||||||
});
|
});
|
||||||
// Hide if not svn
|
// Hide if not svn
|
||||||
if ($("#id_template option:selected").val() == "--") {
|
if ($("#id_template option:selected").val() == "--") {
|
||||||
|
@ -43,10 +43,10 @@
|
|||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<th>{$form.f.ssh_key.labelTag}:</th>
|
<th>{$form.f.public_key.labelTag}:</th>
|
||||||
<td>{if $form.f.ssh_key.errors}{$form.f.ssh_key.fieldErrors}{/if}
|
<td>{if $form.f.public_key.errors}{$form.f.public_key.fieldErrors}{/if}
|
||||||
{$form.f.ssh_key|unsafe}<br />
|
{$form.f.public_key|unsafe}<br />
|
||||||
<span class="helptext">{$form.f.ssh_key.help_text}</span>
|
<span class="helptext">{$form.f.public_key.help_text}</span>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
|
6
src/IDF/templates/idf/gadmin/usher/base.html
Normal file
6
src/IDF/templates/idf/gadmin/usher/base.html
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
{extends "idf/gadmin/base.html"}
|
||||||
|
{block tabusher} class="active"{/block}
|
||||||
|
{block subtabs}
|
||||||
|
<a {if $inUsher}class="active" {/if}href="{url 'IDF_Views_Admin::usher'}">{trans 'Configured servers'}</a> |
|
||||||
|
<a {if $inUsherControl}class="active" {/if}href="{url 'IDF_Views_Admin::usherControl', array('')}">{trans 'Usher control'}</a>
|
||||||
|
{/block}
|
19
src/IDF/templates/idf/gadmin/usher/connections.html
Normal file
19
src/IDF/templates/idf/gadmin/usher/connections.html
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
{extends "idf/gadmin/usher/base.html"}
|
||||||
|
|
||||||
|
{block docclass}yui-t3{assign $inUsherServerConnections=true}{/block}
|
||||||
|
|
||||||
|
{block body}
|
||||||
|
<table class="recent-issues">
|
||||||
|
<tr>
|
||||||
|
<th>{trans "address"}</th>
|
||||||
|
<th>{trans "port"}</th>
|
||||||
|
</tr>
|
||||||
|
{foreach $connections as $connection}
|
||||||
|
<tr>
|
||||||
|
<td>{$connection.address}</td>
|
||||||
|
<td>{$connection.port}</td>
|
||||||
|
</tr>
|
||||||
|
{/foreach}
|
||||||
|
</table>
|
||||||
|
{/block}
|
||||||
|
|
32
src/IDF/templates/idf/gadmin/usher/control.html
Normal file
32
src/IDF/templates/idf/gadmin/usher/control.html
Normal file
@ -0,0 +1,32 @@
|
|||||||
|
{extends "idf/gadmin/usher/base.html"}
|
||||||
|
|
||||||
|
{block docclass}yui-t3{assign $inUsherControl=true}{/block}
|
||||||
|
|
||||||
|
{block body}
|
||||||
|
<p>
|
||||||
|
{trans 'current server status:'} {$status} |
|
||||||
|
{if $status == "SHUTDOWN"}
|
||||||
|
<a href="{url 'IDF_Views_Admin::usherControl', array('startup')}">{trans 'startup'}</a>
|
||||||
|
{else}
|
||||||
|
<a href="{url 'IDF_Views_Admin::usherControl', array('shutdown')}">{trans 'shutdown'}</a>
|
||||||
|
{/if}
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<p>{trans 'reload server configuration:'}
|
||||||
|
<a href="{url 'IDF_Views_Admin::usherControl', array('reload')}">{trans 'reload'}</a>
|
||||||
|
</p>
|
||||||
|
{/block}
|
||||||
|
|
||||||
|
{block context}
|
||||||
|
<div class="issue-submit-info">
|
||||||
|
<p><strong>{trans 'Status explanation'}</strong></p>
|
||||||
|
<ul>
|
||||||
|
<li>ACTIVE n: {trans 'active with n total open connections'}</li>
|
||||||
|
<li>WAITING: {trans 'waiting for new connections'}</li>
|
||||||
|
<li>SHUTTINGDOWN: {trans 'usher is being shut down, not accepting connections'}</li>
|
||||||
|
<li>SHUTDOWN: {trans 'usher is shut down, all local servers are stopped and not accepting connections'}</li>
|
||||||
|
</ul>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
{/block}
|
||||||
|
|
52
src/IDF/templates/idf/gadmin/usher/index.html
Normal file
52
src/IDF/templates/idf/gadmin/usher/index.html
Normal file
@ -0,0 +1,52 @@
|
|||||||
|
{extends "idf/gadmin/usher/base.html"}
|
||||||
|
|
||||||
|
{block docclass}yui-t3{assign $inUsher=true}{/block}
|
||||||
|
|
||||||
|
{block body}
|
||||||
|
<table class="recent-issues">
|
||||||
|
<tr>
|
||||||
|
<th>{trans "server name"}</th>
|
||||||
|
<th>{trans "status"}</th>
|
||||||
|
<th>{trans "action"}</th>
|
||||||
|
</tr>
|
||||||
|
{foreach $servers as $server}
|
||||||
|
<tr>
|
||||||
|
<td>{$server.name}</td>
|
||||||
|
<td>{$server.status}</td>
|
||||||
|
<td>
|
||||||
|
{if preg_match("/ACTIVE|RUNNING|SLEEPING/", $server.status)}
|
||||||
|
<a href="{url 'IDF_Views_Admin::usherServerControl', array($server.name, 'stop')}">
|
||||||
|
{trans 'stop'}</a>
|
||||||
|
{elseif $server.status == "STOPPED"}
|
||||||
|
<a href="{url 'IDF_Views_Admin::usherServerControl', array($server.name, 'start')}">
|
||||||
|
{trans 'start'}</a>
|
||||||
|
{/if}
|
||||||
|
{if preg_match("/ACTIVE|WAITING|SLEEPING|STOPPING/", $server.status)}
|
||||||
|
| <a href="{url 'IDF_Views_Admin::usherServerControl', array($server.name, 'kill')}">
|
||||||
|
{trans 'kill'}</a>
|
||||||
|
{/if}
|
||||||
|
{if preg_match("/STOPPING|ACTIVE/", $server.status)}
|
||||||
|
| <a href="{url 'IDF_Views_Admin::usherServerConnections', array($server.name)}">
|
||||||
|
{trans 'active connections'}</a>
|
||||||
|
{/if}
|
||||||
|
</tr>
|
||||||
|
{/foreach}
|
||||||
|
</table>
|
||||||
|
{/block}
|
||||||
|
|
||||||
|
{block context}
|
||||||
|
<div class="issue-submit-info">
|
||||||
|
<p><strong>{trans 'Status explanation'}</strong></p>
|
||||||
|
<ul>
|
||||||
|
<li>REMOTE: {trans 'remote server without open connections'}</li>
|
||||||
|
<li>ACTIVE n: {trans 'server with n open connections'}</li>
|
||||||
|
<li>WAITING: {trans 'local server running, without open connections'}</li>
|
||||||
|
<li>SLEEPING: {trans 'local server not running, waiting for connections'}</li>
|
||||||
|
<li>STOPPING n: {trans 'local server is about to stop, n connections still open'}</li>
|
||||||
|
<li>STOPPED: {trans 'local server not running, not accepting connections'}</li>
|
||||||
|
<li>SHUTDOWN: {trans 'usher is shut down, not running and not accepting connections'}</li>
|
||||||
|
</ul>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
{/block}
|
||||||
|
|
@ -37,33 +37,6 @@
|
|||||||
{/if}
|
{/if}
|
||||||
|
|
||||||
{/block}
|
{/block}
|
||||||
{block context}
|
|
||||||
{if $scm != 'svn'}
|
|
||||||
<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">{$branch}</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}
|
|
||||||
{else}
|
|
||||||
<form class="star" action="{url 'IDF_Views_Source_Svn::changelogRev', array($project.shortname)}" method="get">
|
|
||||||
<p><strong>{trans 'Revision:'}</strong> {$commit}</p>
|
|
||||||
<p>
|
|
||||||
<input accesskey="4" type="text" value="{$commit}" name="rev" size="5"/>
|
|
||||||
<input type="submit" name="s" value="{trans 'Go to revision'}"/>
|
|
||||||
</p>
|
|
||||||
</form>
|
|
||||||
{/if}
|
|
||||||
{/block}
|
|
||||||
|
|
||||||
{block javascript}
|
{block javascript}
|
||||||
<script type="text/javascript" src="{media '/idf/js/prettify.js'}"></script>
|
<script type="text/javascript" src="{media '/idf/js/prettify.js'}"></script>
|
||||||
<script type="text/javascript">
|
<script type="text/javascript">
|
||||||
|
18
src/IDF/templates/idf/source/git/commit.html
Normal file
18
src/IDF/templates/idf/source/git/commit.html
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
{extends "idf/source/commit.html"}
|
||||||
|
{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">{$branch}</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}
|
||||||
|
|
18
src/IDF/templates/idf/source/mercurial/commit.html
Normal file
18
src/IDF/templates/idf/source/mercurial/commit.html
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
{extends "idf/source/commit.html"}
|
||||||
|
{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">{$branch}</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}
|
||||||
|
|
25
src/IDF/templates/idf/source/mtn/changelog.html
Normal file
25
src/IDF/templates/idf/source/mtn/changelog.html
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
{extends "idf/source/changelog.html"}
|
||||||
|
{block context}
|
||||||
|
<p><strong>{trans 'Branches:'}</strong><br/>
|
||||||
|
{foreach $branches as $selector => $branch}
|
||||||
|
{aurl 'url', 'IDF_Views_Source::changeLog', array($project.shortname, $selector)}
|
||||||
|
<span class="label{if in_array($selector, $tree_in)} active{/if}">
|
||||||
|
<a href="{$url}" class="label" title="{$branch}">
|
||||||
|
{$branch|shorten:24}
|
||||||
|
</a>
|
||||||
|
</span><br/>
|
||||||
|
{/foreach}
|
||||||
|
</p>
|
||||||
|
{if $tags}
|
||||||
|
<p><strong>{trans 'Tags:'}</strong><br/>
|
||||||
|
{foreach $tags as $selector => $tag}
|
||||||
|
{aurl 'url', 'IDF_Views_Source::changeLog', array($project.shortname, $selector)}
|
||||||
|
<span class="label{if in_array($selector, $tags_in)} active{/if}">
|
||||||
|
<a href="{$url}" class="label" title="{$tag}">
|
||||||
|
{$tag|shorten:24}
|
||||||
|
</a>
|
||||||
|
</span><br/>
|
||||||
|
{/foreach}
|
||||||
|
</p>
|
||||||
|
{/if}
|
||||||
|
{/block}
|
26
src/IDF/templates/idf/source/mtn/commit.html
Normal file
26
src/IDF/templates/idf/source/mtn/commit.html
Normal file
@ -0,0 +1,26 @@
|
|||||||
|
{extends "idf/source/commit.html"}
|
||||||
|
{block context}
|
||||||
|
<p><strong>{trans 'Branches:'}</strong><br/>
|
||||||
|
{foreach $branches as $selector => $branch}
|
||||||
|
{aurl 'url', 'IDF_Views_Source::treeBase', array($project.shortname, $selector)}
|
||||||
|
<span class="label{if in_array($branch, $tree_in)} active{/if}">
|
||||||
|
<a href="{$url}" class="label" title="{$branch}">
|
||||||
|
{$branch|shorten:25}
|
||||||
|
</a>
|
||||||
|
</span><br/>
|
||||||
|
{/foreach}
|
||||||
|
</p>
|
||||||
|
{if $tags}
|
||||||
|
<p><strong>{trans 'Tags:'}</strong><br/>
|
||||||
|
{foreach $tags as $selector => $tag}
|
||||||
|
{aurl 'url', 'IDF_Views_Source::treeBase', array($project.shortname, $selector)}
|
||||||
|
<span class="label{if in_array($tag, $tags_in)} active{/if}">
|
||||||
|
<a href="{$url}" class="label" title="{$tag}">
|
||||||
|
{$tag|shorten:25}
|
||||||
|
</a>
|
||||||
|
</span><br/>
|
||||||
|
{/foreach}
|
||||||
|
</p>
|
||||||
|
{/if}
|
||||||
|
{/block}
|
||||||
|
|
51
src/IDF/templates/idf/source/mtn/file.html
Normal file
51
src/IDF/templates/idf/source/mtn/file.html
Normal file
@ -0,0 +1,51 @@
|
|||||||
|
{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 $selector => $branch}
|
||||||
|
{aurl 'url', 'IDF_Views_Source::treeBase', array($project.shortname, $selector)}
|
||||||
|
<span class="label{if in_array($branch, $tree_in)} active{/if}">
|
||||||
|
<a href="{$url}" class="label" title="{$branch}">
|
||||||
|
{$branch|shorten:25}
|
||||||
|
</a>
|
||||||
|
</span><br/>
|
||||||
|
{/foreach}
|
||||||
|
</p>
|
||||||
|
{if $tags}
|
||||||
|
<p><strong>{trans 'Tags:'}</strong><br/>
|
||||||
|
{foreach $tags as $selector => $tag}
|
||||||
|
{aurl 'url', 'IDF_Views_Source::treeBase', array($project.shortname, $selector)}
|
||||||
|
<span class="label{if in_array($tag, $tags_in)} active{/if}">
|
||||||
|
<a href="{$url}" class="label" title="{$tag}">
|
||||||
|
{$tag|shorten:25}
|
||||||
|
</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}
|
34
src/IDF/templates/idf/source/mtn/help.html
Normal file
34
src/IDF/templates/idf/source/mtn/help.html
Normal file
@ -0,0 +1,34 @@
|
|||||||
|
{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>monotone</strong> software to manage the source
|
||||||
|
code.{/blocktrans}</p>
|
||||||
|
|
||||||
|
<h3>{trans 'Command-Line Access'}</h3>
|
||||||
|
|
||||||
|
<p><kbd>mtn clone {$project.getSourceAccessUrl()}</kbd></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>
|
||||||
|
mtn setup -b {$project.getConf().getVal('mtn_master_branch', 'your-branch')} .
|
||||||
|
mtn add -R .
|
||||||
|
mtn commit -m "initial import"
|
||||||
|
mtn push {$project.getSourceAccessUrl()}
|
||||||
|
</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}
|
||||||
|
|
||||||
|
|
80
src/IDF/templates/idf/source/mtn/tree.html
Normal file
80
src/IDF/templates/idf/source/mtn/tree.html
Normal file
@ -0,0 +1,80 @@
|
|||||||
|
{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:"without"}</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>mtn clone {$project.getSourceAccessUrl($user, $commit)}</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 $selector => $branch}
|
||||||
|
{aurl 'url', 'IDF_Views_Source::treeBase', array($project.shortname, $selector)}
|
||||||
|
<span class="label{if in_array($selector, $tree_in)} active{/if}">
|
||||||
|
<a href="{$url}" class="label" title="{$branch}">
|
||||||
|
{$branch|shorten:24}
|
||||||
|
</a>
|
||||||
|
</span><br/>
|
||||||
|
{/foreach}
|
||||||
|
</p>
|
||||||
|
{if $tags}
|
||||||
|
<p><strong>{trans 'Tags:'}</strong><br/>
|
||||||
|
{foreach $tags as $selector => $tag}
|
||||||
|
{aurl 'url', 'IDF_Views_Source::treeBase', array($project.shortname, $selector)}
|
||||||
|
<span class="label{if in_array($selector, $tags_in)} active{/if}">
|
||||||
|
<a href="{$url}" class="label" title="{$tag}">
|
||||||
|
{$tag|shorten:24}
|
||||||
|
</a>
|
||||||
|
</span><br/>
|
||||||
|
{/foreach}
|
||||||
|
</p>
|
||||||
|
{/if}
|
||||||
|
{/block}
|
11
src/IDF/templates/idf/source/svn/commit.html
Normal file
11
src/IDF/templates/idf/source/svn/commit.html
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
{extends "idf/source/commit.html"}
|
||||||
|
{block context}
|
||||||
|
<form class="star" action="{url 'IDF_Views_Source_Svn::changelogRev', array($project.shortname)}" method="get">
|
||||||
|
<p><strong>{trans 'Revision:'}</strong> {$commit}</p>
|
||||||
|
<p>
|
||||||
|
<input accesskey="4" type="text" value="{$commit}" name="rev" size="5"/>
|
||||||
|
<input type="submit" name="s" value="{trans 'Go to revision'}"/>
|
||||||
|
</p>
|
||||||
|
</form>
|
||||||
|
{/block}
|
||||||
|
|
@ -54,10 +54,10 @@
|
|||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<th>{$form.f.ssh_key.labelTag}:</th>
|
<th>{$form.f.public_key.labelTag}:</th>
|
||||||
<td>{if $form.f.ssh_key.errors}{$form.f.ssh_key.fieldErrors}{/if}
|
<td>{if $form.f.public_key.errors}{$form.f.public_key.fieldErrors}{/if}
|
||||||
{$form.f.ssh_key|unsafe}<br />
|
{$form.f.public_key|unsafe}<br />
|
||||||
<span class="helptext">{$form.f.ssh_key.help_text}</span>
|
<span class="helptext">{$form.f.public_key.help_text}</span>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr class="pass-info" id="extra-password">
|
<tr class="pass-info" id="extra-password">
|
||||||
@ -82,7 +82,7 @@
|
|||||||
|
|
||||||
{if count($keys)}
|
{if count($keys)}
|
||||||
<table summary=" " class="recent-issues">
|
<table summary=" " class="recent-issues">
|
||||||
<tr><th colspan="2">{trans 'Your Current SSH Keys'}</th></tr>
|
<tr><th colspan="2">{trans 'Your Current Public Keys'}</th></tr>
|
||||||
{foreach $keys as $key}<tr><td>
|
{foreach $keys as $key}<tr><td>
|
||||||
<span class="mono">{$key.showCompact()}</span></td><td> <form class="star" method="post" action="{url 'IDF_Views_User::deleteKey', array($key.id)}"><input type="image" src="{media '/idf/img/trash.png'}" name="submit" value="{trans 'Delete this key'}" /></form>
|
<span class="mono">{$key.showCompact()}</span></td><td> <form class="star" method="post" action="{url 'IDF_Views_User::deleteKey', array($key.id)}"><input type="image" src="{media '/idf/img/trash.png'}" name="submit" value="{trans 'Delete this key'}" /></form>
|
||||||
</td>
|
</td>
|
||||||
|
Loading…
Reference in New Issue
Block a user