Merge branch 'develop'
This commit is contained in:
commit
023a3ce879
@ -149,7 +149,6 @@ look like this:
|
||||
$cfg['mtn_repositories'] = '/var/lib/usher/projects/%s/';
|
||||
$cfg['mtn_remote_url'] = 'mtn://my.server.com/%s';
|
||||
$cfg['mtn_db_access'] = 'remote';
|
||||
$cfg['mtn_remote_auth'] = true;
|
||||
$cfg['mtn_usher_conf'] = '/var/lib/usher/usher.conf';
|
||||
...
|
||||
|
||||
@ -188,8 +187,10 @@ Remote commands can be helpful for a user or a 3rd party tool (like
|
||||
contents remotely without having to pull everything in first instance.
|
||||
|
||||
Private projects on the other hand can only be synced by team members
|
||||
or additional invited people. Also noo remote command execution is enabled
|
||||
by default.
|
||||
or additional invited people. Remote command execution is still enabled
|
||||
by default - if you want to disable that, simply remove the symlink to
|
||||
the file `indefero_authorize_remote_automate.conf` in your project's `hooks.d`
|
||||
directory or copy the file from the original location and adapt it.
|
||||
|
||||
## Notifications
|
||||
|
||||
@ -204,8 +205,54 @@ in a directory called `hooks.d` right under the project's base directory
|
||||
(configured via $cfg['mtn_repositories']) and this is the ideal place to
|
||||
put or link these additional lua sources.
|
||||
|
||||
## Custom project configurations and templates
|
||||
|
||||
If a new project is created in IDF, the SyncMonotone plugin creates a new
|
||||
configuration tree for the project into the project's configuration directory,
|
||||
determined by `$cfg['mtn_repositories']`. IDF ships with the minimum set of
|
||||
files for this configuration tree and sets up everything automatically for you.
|
||||
|
||||
Even more, most of the configuration files from the newly created tree are only
|
||||
symlinked to the original configuration directory which is configurable via
|
||||
`$cfg['mtn_confdir']` and defaults to `src/IDF/Plugin/SyncMonotone/`. This has
|
||||
the advantage that your standard IDF setup automatically receives updates to
|
||||
existing (symlinked) configuration files as soon as you update to a newer
|
||||
version.
|
||||
|
||||
You could, however, also choose to place the directory tree somewhere else
|
||||
and adapt the contents of the individual files yourself, so these changes get
|
||||
automatically applied to all new projects you create. You could even go so far
|
||||
and add new files to the tree and let them be processed automatically just
|
||||
as the basic files! All you need to do is to copy your files and / or directories
|
||||
underknees your `$cfg['mtn_confdir']` and add their relative paths to
|
||||
`$cfg['mtn_confdir_extra']`.
|
||||
|
||||
By convention, all entries which end with a slash are considered directories,
|
||||
so mkdir(1) is issued for these entries, all files which do not end up with
|
||||
".in" are considered to be static script files which are just symlinked from
|
||||
the basic configuration dir and all entries ending on ".in" are considered
|
||||
configuration files or templates, which are copied over to the project's
|
||||
configuration tree and which get some basic project-specific values replaced.
|
||||
|
||||
The following placeholders are currently recognized and replaced for these files:
|
||||
|
||||
* %%PROJECT%% - the name of the created project
|
||||
* %%MTNPOSTPUSH%% - the absolute path to the `mtn-post-push` script
|
||||
* %%MTNCLIENTKEY%% - the public key hash of the key which is used by IDF
|
||||
to authenticate remote stdio access
|
||||
|
||||
Thats it - I hope you find it useful :)
|
||||
|
||||
## Q&A
|
||||
|
||||
### After I created a new project, IDF throws an exception and tells me that it couldn't save the membership data with a cryptic error message. Whats wrong?
|
||||
|
||||
Multiple issues could cause that. If you've set up usher, make sure the usher
|
||||
can fork your database at all and look out for specific errors in the log file
|
||||
of your project. If you stumble upon permission issues, ensure that the user
|
||||
who runs the usher can access all files in your project's configuration directory,
|
||||
including symlinked files.
|
||||
|
||||
### I pushed a branch to my server, but it does not show up in IDF. Whats wrong?
|
||||
|
||||
Check if the heads of your branch are not suspended, i.e. do not carry a
|
||||
|
86
logo/indefero-logo-lite.svg
Normal file
86
logo/indefero-logo-lite.svg
Normal file
@ -0,0 +1,86 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<!-- Created with Inkscape (http://www.inkscape.org/) -->
|
||||
|
||||
<svg
|
||||
xmlns:dc="http://purl.org/dc/elements/1.1/"
|
||||
xmlns:cc="http://creativecommons.org/ns#"
|
||||
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
|
||||
xmlns:svg="http://www.w3.org/2000/svg"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||
width="16"
|
||||
height="16"
|
||||
id="svg2"
|
||||
sodipodi:version="0.32"
|
||||
inkscape:version="0.48.0 r9654"
|
||||
version="1.0"
|
||||
sodipodi:docname="indefero-logo-lite.svg"
|
||||
inkscape:output_extension="org.inkscape.output.svg.inkscape">
|
||||
<defs
|
||||
id="defs4">
|
||||
<inkscape:perspective
|
||||
sodipodi:type="inkscape:persp3d"
|
||||
inkscape:vp_x="0 : 526.18109 : 1"
|
||||
inkscape:vp_y="0 : 1000 : 0"
|
||||
inkscape:vp_z="744.09448 : 526.18109 : 1"
|
||||
inkscape:persp3d-origin="372.04724 : 350.78739 : 1"
|
||||
id="perspective10" />
|
||||
</defs>
|
||||
<sodipodi:namedview
|
||||
id="base"
|
||||
pagecolor="#ffffff"
|
||||
bordercolor="#666666"
|
||||
borderopacity="1.0"
|
||||
gridtolerance="10000"
|
||||
guidetolerance="10"
|
||||
objecttolerance="10"
|
||||
inkscape:pageopacity="0.0"
|
||||
inkscape:pageshadow="2"
|
||||
inkscape:zoom="11.313708"
|
||||
inkscape:cx="31.568929"
|
||||
inkscape:cy="-0.35578703"
|
||||
inkscape:document-units="px"
|
||||
inkscape:current-layer="layer1"
|
||||
showgrid="false"
|
||||
inkscape:window-width="1280"
|
||||
inkscape:window-height="723"
|
||||
inkscape:window-x="0"
|
||||
inkscape:window-y="24"
|
||||
inkscape:window-maximized="1" />
|
||||
<metadata
|
||||
id="metadata7">
|
||||
<rdf:RDF>
|
||||
<cc:Work
|
||||
rdf:about="">
|
||||
<dc:format>image/svg+xml</dc:format>
|
||||
<dc:type
|
||||
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
|
||||
</cc:Work>
|
||||
</rdf:RDF>
|
||||
</metadata>
|
||||
<g
|
||||
inkscape:label="Layer 1"
|
||||
inkscape:groupmode="layer"
|
||||
id="layer1"
|
||||
transform="translate(0,-728.09451)">
|
||||
<g
|
||||
id="g2401"
|
||||
transform="matrix(0.13580542,0,0,0.13580542,-47.580342,708.10521)"
|
||||
style="fill:#8ae234;stroke:#4e9a06;stroke-width:2.4000001;stroke-miterlimit:4;stroke-dasharray:none"
|
||||
inkscape:export-filename="/home/loa/Projects/indefero/logo/powered-by-indefero.png"
|
||||
inkscape:export-xdpi="12.330909"
|
||||
inkscape:export-ydpi="12.330909">
|
||||
<path
|
||||
id="path2383"
|
||||
d="m 396.19089,173.14471 c -7.67621,0.80661 -14.40195,5.39406 -19.58101,10.89131 -7.23597,7.88004 -11.69742,18.07908 -13.32198,28.60362 -1.7236,11.28173 -0.25925,23.20635 5.07686,33.37271 3.78607,7.24384 9.53161,13.92339 17.29701,16.96772 3.86478,1.53937 8.98362,1.03284 11.67912,-2.41036 2.64357,-3.5671 2.69463,-8.234 2.85756,-12.48867 0.045,-7.61054 -0.54749,-15.25544 0.45618,-22.83193 0.87131,-9.50623 4.03944,-18.56751 6.71612,-27.66851 1.16242,-4.44333 2.25094,-9.02808 1.97499,-13.64988 -0.48817,-4.62476 -3.58059,-9.31042 -8.2964,-10.4067 -1.57489,-0.44882 -3.23412,-0.48948 -4.85845,-0.37931 z"
|
||||
style="fill:#8ae234;fill-opacity:1;fill-rule:nonzero;stroke:#4e9a06;stroke-width:2.4000001;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;stroke-dashoffset:0"
|
||||
inkscape:connector-curvature="0" />
|
||||
<path
|
||||
id="path2391"
|
||||
d="m 433.14691,149.28687 c 7.2059,2.76589 12.51512,8.93778 16.09494,15.58815 4.94991,9.48434 6.61962,20.49058 5.46486,31.07695 -1.25505,11.34342 -5.75582,22.48271 -13.54134,30.92159 -5.53192,6.01709 -12.81048,10.98198 -21.09918,11.91276 -4.13154,0.4866 -8.94486,-1.32748 -10.65734,-5.35104 -1.63027,-4.12976 -0.4717,-8.65084 0.47212,-12.80269 1.92628,-7.36287 4.47721,-14.59393 5.4687,-22.17201 1.61875,-9.40784 0.90381,-18.98034 0.67386,-28.46402 0.0272,-4.59278 0.1624,-9.30303 1.62515,-13.69592 1.66851,-4.34082 5.86829,-8.06645 10.70716,-7.90484 1.63738,-0.0259 3.25061,0.36424 4.79107,0.89107 z"
|
||||
style="fill:#8ae234;fill-opacity:1;fill-rule:nonzero;stroke:#4e9a06;stroke-width:2.4000001;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;stroke-dashoffset:0"
|
||||
inkscape:connector-curvature="0" />
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
After Width: | Height: | Size: 4.1 KiB |
@ -55,6 +55,7 @@ Pluf_Dispatcher::loadControllers(Pluf::f('idf_views'));
|
||||
$params = array('repo_dir' => $argv[1],
|
||||
'revision' => $argv[2],
|
||||
'env' => array_merge($_ENV, $_SERVER));
|
||||
Pluf_Log::event(array('svnpostcommit.php', 'Send run signal.', $params));
|
||||
Pluf_Signal::send('svnpostcommit.php::run', 'svnpostcommit.php', $params);
|
||||
|
||||
|
||||
|
@ -295,7 +295,7 @@ class IDF_Form_UserAccount extends Pluf_Form
|
||||
return '';
|
||||
}
|
||||
|
||||
if (preg_match('#^ssh\-[a-z]{3}\s\S+==(\s\S+)?$#', $key)) {
|
||||
if (preg_match('#^ssh\-[a-z]{3}\s\S+(\s\S+)?$#', $key)) {
|
||||
$key = str_replace(array("\n", "\r"), '', $key);
|
||||
|
||||
if (Pluf::f('idf_strong_key_check', false)) {
|
||||
|
@ -230,6 +230,24 @@ class IDF_Plugin_SyncGit_Serve
|
||||
}
|
||||
Pluf_Log::debug(array('IDF_Plugin_Git_Serve::initRepository',
|
||||
'Added post-update hook.', $fullpath));
|
||||
// Configure the core.quotepath option
|
||||
$quotepath = (Pluf::f('git_core_quotepath', true) == true) ? 'true' : 'false';
|
||||
$out = array();
|
||||
$res = 0;
|
||||
exec(sprintf(Pluf::f('idf_exec_cmd_prefix', '').
|
||||
Pluf::f('git_path', 'git').' config -f %s/config --add core.quotepath %s',
|
||||
escapeshellarg($fullpath),
|
||||
escapeshellarg($quotepath)
|
||||
),
|
||||
$out, $res);
|
||||
if ($res != 0) {
|
||||
Pluf_Log::warn(array('IDF_Plugin_Git_Serve::initRepository',
|
||||
'core.quotepath configuration error.',
|
||||
$quotepath));
|
||||
return;
|
||||
}
|
||||
Pluf_Log::debug(array('IDF_Plugin_Git_Serve::initRepository',
|
||||
'core.quotepath configured.', $quotepath));
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -62,7 +62,7 @@ class IDF_Plugin_SyncMonotone
|
||||
* 'mtn_repositories'
|
||||
* 2) create a new server key in the same directory
|
||||
* 3) create a new client key for IDF and store it in the project conf
|
||||
* 4) write monotonerc
|
||||
* 4) setup the configuration
|
||||
* 5) add the database as new local server in the usher configuration
|
||||
* 6) reload the running usher instance so it acknowledges the new server
|
||||
*
|
||||
@ -101,6 +101,36 @@ class IDF_Plugin_SyncMonotone
|
||||
));
|
||||
}
|
||||
|
||||
// check some static configuration files
|
||||
$confdir = Pluf::f('mtn_confdir', false);
|
||||
if ($confdir === false) {
|
||||
$confdir = dirname(__FILE__).'/SyncMonotone/';
|
||||
}
|
||||
$confdir_contents = array(
|
||||
'monotonerc.in',
|
||||
'remote-automate-permissions.in',
|
||||
'hooks.d/',
|
||||
// this is linked and not copied to be able to update
|
||||
// the list of read-only commands on upgrades
|
||||
'hooks.d/indefero_authorize_remote_automate.conf',
|
||||
'hooks.d/indefero_authorize_remote_automate.lua',
|
||||
'hooks.d/indefero_post_push.conf.in',
|
||||
'hooks.d/indefero_post_push.lua',
|
||||
);
|
||||
// check whether we should handle additional files in the config directory
|
||||
$confdir_extra_contents = Pluf::f('mtn_confdir_extra', false);
|
||||
if ($confdir_extra_contents !== false) {
|
||||
$confdir_contents =
|
||||
array_merge($confdir_contents, $confdir_extra_contents);
|
||||
}
|
||||
foreach ($confdir_contents as $content) {
|
||||
if (!file_exists($confdir.$content)) {
|
||||
throw new IDF_Scm_Exception(sprintf(
|
||||
__('The configuration file %s is missing.'), $content
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
$shortname = $project->shortname;
|
||||
$projectpath = sprintf($projecttempl, $shortname);
|
||||
if (file_exists($projectpath)) {
|
||||
@ -144,79 +174,97 @@ class IDF_Plugin_SyncMonotone
|
||||
//
|
||||
// step 3) create a client key, and save it in IDF
|
||||
//
|
||||
$clientkey_hash = '';
|
||||
$monotonerc_tpl = 'monotonerc-noauth.tpl';
|
||||
|
||||
if (Pluf::f('mtn_remote_auth', true)) {
|
||||
$monotonerc_tpl = 'monotonerc-auth.tpl';
|
||||
$keydir = Pluf::f('tmp_folder').'/mtn-client-keys';
|
||||
if (!file_exists($keydir)) {
|
||||
if (!mkdir($keydir)) {
|
||||
throw new IDF_Scm_Exception(sprintf(
|
||||
__('The key directory %s could not be created.'), $keydir
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
$clientkey_name = $shortname.'-client@'.$server;
|
||||
$cmd = sprintf('au generate_key --keydir=%s %s ""',
|
||||
escapeshellarg($keydir),
|
||||
escapeshellarg($clientkey_name)
|
||||
);
|
||||
$keyinfo = self::_mtn_exec($cmd);
|
||||
|
||||
$parsed_keyinfo = array();
|
||||
try {
|
||||
$parsed_keyinfo = IDF_Scm_Monotone_BasicIO::parse($keyinfo);
|
||||
}
|
||||
catch (Exception $e) {
|
||||
$keydir = Pluf::f('tmp_folder').'/mtn-client-keys';
|
||||
if (!file_exists($keydir)) {
|
||||
if (!mkdir($keydir)) {
|
||||
throw new IDF_Scm_Exception(sprintf(
|
||||
__('Could not parse key information: %s'), $e->getMessage()
|
||||
__('The key directory %s could not be created.'), $keydir
|
||||
));
|
||||
}
|
||||
|
||||
$clientkey_hash = $parsed_keyinfo[0][1]['hash'];
|
||||
$clientkey_file = $keydir . '/' . $clientkey_name . '.' . $clientkey_hash;
|
||||
$clientkey_data = file_get_contents($clientkey_file);
|
||||
|
||||
$project->getConf()->setVal('mtn_client_key_name', $clientkey_name);
|
||||
$project->getConf()->setVal('mtn_client_key_hash', $clientkey_hash);
|
||||
$project->getConf()->setVal('mtn_client_key_data', $clientkey_data);
|
||||
|
||||
// add the public client key to the server
|
||||
$cmd = sprintf('au get_public_key --keydir=%s %s',
|
||||
escapeshellarg($keydir),
|
||||
escapeshellarg($clientkey_hash)
|
||||
);
|
||||
$clientkey_pubdata = self::_mtn_exec($cmd);
|
||||
|
||||
$cmd = sprintf('au put_public_key --db=%s %s',
|
||||
escapeshellarg($dbfile),
|
||||
escapeshellarg($clientkey_pubdata)
|
||||
);
|
||||
self::_mtn_exec($cmd);
|
||||
}
|
||||
|
||||
//
|
||||
// step 4) write monotonerc
|
||||
//
|
||||
$monotonerc = file_get_contents(
|
||||
dirname(__FILE__).'/SyncMonotone/'.$monotonerc_tpl
|
||||
);
|
||||
$monotonerc = str_replace(
|
||||
array('%%MTNPOSTPUSH%%', '%%PROJECT%%', '%%MTNCLIENTKEY%%'),
|
||||
array($mtnpostpush, $shortname, $clientkey_hash),
|
||||
$monotonerc
|
||||
$clientkey_name = $shortname.'-client@'.$server;
|
||||
$cmd = sprintf('au generate_key --keydir=%s %s ""',
|
||||
escapeshellarg($keydir),
|
||||
escapeshellarg($clientkey_name)
|
||||
);
|
||||
$keyinfo = self::_mtn_exec($cmd);
|
||||
|
||||
$rcfile = $projectpath.'/monotonerc';
|
||||
|
||||
if (file_put_contents($rcfile, $monotonerc, LOCK_EX) === false) {
|
||||
$parsed_keyinfo = array();
|
||||
try {
|
||||
$parsed_keyinfo = IDF_Scm_Monotone_BasicIO::parse($keyinfo);
|
||||
}
|
||||
catch (Exception $e) {
|
||||
throw new IDF_Scm_Exception(sprintf(
|
||||
__('Could not write mtn configuration file "%s"'), $rcfile
|
||||
__('Could not parse key information: %s'), $e->getMessage()
|
||||
));
|
||||
}
|
||||
|
||||
$clientkey_hash = $parsed_keyinfo[0][1]['hash'];
|
||||
$clientkey_file = $keydir . '/' . $clientkey_name . '.' . $clientkey_hash;
|
||||
$clientkey_data = file_get_contents($clientkey_file);
|
||||
|
||||
$project->getConf()->setVal('mtn_client_key_name', $clientkey_name);
|
||||
$project->getConf()->setVal('mtn_client_key_hash', $clientkey_hash);
|
||||
$project->getConf()->setVal('mtn_client_key_data', $clientkey_data);
|
||||
|
||||
// add the public client key to the server
|
||||
$cmd = sprintf('au get_public_key --keydir=%s %s',
|
||||
escapeshellarg($keydir),
|
||||
escapeshellarg($clientkey_hash)
|
||||
);
|
||||
$clientkey_pubdata = self::_mtn_exec($cmd);
|
||||
|
||||
$cmd = sprintf('au put_public_key --db=%s %s',
|
||||
escapeshellarg($dbfile),
|
||||
escapeshellarg($clientkey_pubdata)
|
||||
);
|
||||
self::_mtn_exec($cmd);
|
||||
|
||||
//
|
||||
// step 4) setup the configuration
|
||||
//
|
||||
|
||||
// we assume that all confdir entries ending with a slash mean a
|
||||
// directory that has to be created, that all files ending on ".in"
|
||||
// have to be processed and copied in place and that all other files
|
||||
// just need to be symlinked from the original location
|
||||
foreach ($confdir_contents as $content) {
|
||||
$filepath = $projectpath.'/'.$content;
|
||||
if (substr($content, -1) == '/') {
|
||||
if (!mkdir($filepath)) {
|
||||
throw new IDF_Scm_Exception(sprintf(
|
||||
__('Could not create configuration directory "%s"'), $filepath
|
||||
));
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
if (substr($content, -3) != '.in') {
|
||||
if (!symlink($confdir.$content, $filepath)) {
|
||||
IDF_Scm_Exception(sprintf(
|
||||
__('Could not create symlink "%s"'), $filepath
|
||||
));
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
$filecontents = file_get_contents($confdir.'/'.$content);
|
||||
$filecontents = str_replace(
|
||||
array('%%MTNPOSTPUSH%%', '%%PROJECT%%', '%%MTNCLIENTKEY%%'),
|
||||
array($mtnpostpush, $shortname, $clientkey_hash),
|
||||
$filecontents
|
||||
);
|
||||
|
||||
// remove the .in
|
||||
$filepath = substr($filepath, 0, -3);
|
||||
if (file_put_contents($filepath, $filecontents, LOCK_EX) === false) {
|
||||
throw new IDF_Scm_Exception(sprintf(
|
||||
__('Could not write configuration file "%s"'), $filepath
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
//
|
||||
// step 5) read in and append the usher config with the new server
|
||||
//
|
||||
@ -378,17 +426,15 @@ class IDF_Plugin_SyncMonotone
|
||||
}
|
||||
}
|
||||
|
||||
if (Pluf::f('mtn_remote_auth', true)) {
|
||||
$keydir = Pluf::f('tmp_folder').'/mtn-client-keys';
|
||||
$keyname = $project->getConf()->getVal('mtn_client_key_name', false);
|
||||
$keyhash = $project->getConf()->getVal('mtn_client_key_hash', false);
|
||||
if ($keyname && $keyhash &&
|
||||
file_exists($keydir .'/'. $keyname . '.' . $keyhash)) {
|
||||
if (!@unlink($keydir .'/'. $keyname . '.' . $keyhash)) {
|
||||
throw new IDF_Scm_Exception(sprintf(
|
||||
__('Could not delete client private key %s'), $keyname
|
||||
));
|
||||
}
|
||||
$keydir = Pluf::f('tmp_folder').'/mtn-client-keys';
|
||||
$keyname = $project->getConf()->getVal('mtn_client_key_name', false);
|
||||
$keyhash = $project->getConf()->getVal('mtn_client_key_hash', false);
|
||||
if ($keyname && $keyhash &&
|
||||
file_exists($keydir .'/'. $keyname . '.' . $keyhash)) {
|
||||
if (!@unlink($keydir .'/'. $keyname . '.' . $keyhash)) {
|
||||
throw new IDF_Scm_Exception(sprintf(
|
||||
__('Could not delete client private key %s'), $keyname
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
@ -721,7 +767,7 @@ class IDF_Plugin_SyncMonotone
|
||||
|
||||
private static function _delete_recursive($path)
|
||||
{
|
||||
if (is_file($path)) {
|
||||
if (is_file($path) || is_link($path)) {
|
||||
return @unlink($path);
|
||||
}
|
||||
|
||||
|
@ -0,0 +1,10 @@
|
||||
ARA_safe_commands = {
|
||||
"get_corresponding_path", "get_content_changed", "tags", "branches",
|
||||
"common_ancestors", "packet_for_fdelta", "packet_for_fdata",
|
||||
"packets_for_certs", "packet_for_rdata", "get_manifest_of",
|
||||
"get_revision", "select", "graph", "children", "parents", "roots",
|
||||
"leaves", "ancestry_difference", "toposort", "erase_ancestors",
|
||||
"descendents", "ancestors", "heads", "get_file_of", "get_file",
|
||||
"interface_version", "get_attributes", "content_diff",
|
||||
"file_merge", "show_conflicts", "certs", "keys", "get_extended_manifest_of"
|
||||
}
|
@ -0,0 +1,88 @@
|
||||
-- ***** BEGIN LICENSE BLOCK *****
|
||||
-- This file is part of InDefero, an open source project management application.
|
||||
-- Copyright (C) 2011 Céondo Ltd and contributors.
|
||||
-- Copyright (C) 2010 Thomas Keller <me@thomaskeller.biz>
|
||||
-- Richard Levitte <richard@levitte.org>
|
||||
--
|
||||
-- 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 *****
|
||||
|
||||
--
|
||||
-- This script reads key identities from a file "remote-automate-permissions"
|
||||
-- in the configuration directory and permits those authenticating with one
|
||||
-- of those keys to perform dangerous (read/write) remote automate operations.
|
||||
-- The format of the file is very simple, one key identity on every line.
|
||||
-- Lines starting with # are ignore, as well as empty lines.
|
||||
--
|
||||
-- It's possible to configure this script to allow the performance of some
|
||||
-- remote automate commands anonymously, through the variable
|
||||
-- ARA_safe_commands, which has to be a table of commands as strings.
|
||||
-- One example configuration, taken from the setup at code.monotone.ca, could
|
||||
-- be this:
|
||||
--
|
||||
-- ARA_safe_commands = {
|
||||
-- "get_corresponding_path", "get_content_changed", "tags", "branches",
|
||||
-- "common_ancestors", "packet_for_fdelta", "packet_for_fdata",
|
||||
-- "packets_for_certs", "packet_for_rdata", "get_manifest_of",
|
||||
-- "get_revision", "select", "graph", "children", "parents", "roots",
|
||||
-- "leaves", "ancestry_difference", "toposort", "erase_ancestors",
|
||||
-- "descendents", "ancestors", "heads", "get_file_of", "get_file",
|
||||
-- "interface_version", "get_attributes", "content_diff",
|
||||
-- "file_merge", "show_conflicts", "certs", "keys", "get_extended_manifest_of"
|
||||
-- }
|
||||
--
|
||||
do
|
||||
local _safe_commands = {}
|
||||
if ARA_safe_commands then
|
||||
_safe_commands = ARA_safe_commands
|
||||
end
|
||||
|
||||
local _save_get_remote_automate_permitted = get_remote_automate_permitted
|
||||
function get_remote_automate_permitted(key_identity, command, options)
|
||||
local permfile =
|
||||
io.open(get_confdir() .. "/remote-automate-permissions", "r")
|
||||
if (permfile == nil) then
|
||||
return false
|
||||
end
|
||||
|
||||
-- See if the incoming key matches any of the key identities or
|
||||
-- patterns found in the permissions file.
|
||||
local matches = false
|
||||
local line = permfile:read()
|
||||
while (not matches and line ~= nil) do
|
||||
if not globish_match("#*", line) then
|
||||
local _, _, ln = string.find(line, "%s*([^%s]*)%s*")
|
||||
if ln == "*" then matches = true end
|
||||
if ln == key_identity.id then matches = true end
|
||||
if globish_match(ln, key_identity.name) then matches = true end
|
||||
line = permfile:read()
|
||||
end
|
||||
end
|
||||
io.close(permfile)
|
||||
if matches then return true end
|
||||
|
||||
-- No matching key found, let's see if the command matches one the
|
||||
-- admin allowed to be performed anonymously
|
||||
for _,v in ipairs(_safe_commands) do
|
||||
if (v == command[1]) then
|
||||
return true
|
||||
end
|
||||
end
|
||||
|
||||
-- No matches found anywhere, then don't permit this operation
|
||||
return false
|
||||
end
|
||||
end
|
@ -0,0 +1,2 @@
|
||||
IDF_project = "%%PROJECT%%"
|
||||
IDF_push_script = "%%MTNPOSTPUSH%%"
|
58
src/IDF/Plugin/SyncMonotone/hooks.d/indefero_post_push.lua
Normal file
58
src/IDF/Plugin/SyncMonotone/hooks.d/indefero_post_push.lua
Normal file
@ -0,0 +1,58 @@
|
||||
-- ***** BEGIN LICENSE BLOCK *****
|
||||
-- This file is part of InDefero, an open source project management application.
|
||||
-- Copyright (C) 2011 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 *****
|
||||
|
||||
--
|
||||
-- let IDF know of new arriving revisions to fill its timeline
|
||||
--
|
||||
_idf_revs = {}
|
||||
push_hook_functions(
|
||||
{
|
||||
start =
|
||||
function (session_id)
|
||||
_idf_revs[session_id] = {}
|
||||
return "continue",nil
|
||||
end,
|
||||
revision_received =
|
||||
function (new_id, revision, certs, session_id)
|
||||
table.insert(_idf_revs[session_id], new_id)
|
||||
return "continue",nil
|
||||
end,
|
||||
["end"] =
|
||||
function (session_id, ...)
|
||||
if table.getn(_idf_revs[session_id]) == 0 then
|
||||
return "continue",nil
|
||||
end
|
||||
|
||||
local pin,pout,pid = spawn_pipe(IDF_push_script, IDF_project);
|
||||
if pid == -1 then
|
||||
print("could not execute " .. IDF_push_script)
|
||||
return "continue",nil
|
||||
end
|
||||
|
||||
for _,r in ipairs(_idf_revs[session_id]) do
|
||||
pin:write(r .. "\n")
|
||||
end
|
||||
pin:close()
|
||||
|
||||
wait(pid)
|
||||
return "continue",nil
|
||||
end
|
||||
})
|
||||
|
@ -1,79 +0,0 @@
|
||||
-- ***** 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 *****
|
||||
|
||||
--
|
||||
-- controls the access rights for remote_stdio which is used by IDFs frontend
|
||||
-- and other interested parties
|
||||
--
|
||||
function get_remote_automate_permitted(key_identity, command, options)
|
||||
if (key_identity.id == "%%MTNCLIENTKEY%%") then
|
||||
return true
|
||||
end
|
||||
|
||||
return false
|
||||
end
|
||||
|
||||
--
|
||||
-- let IDF know of new arriving revisions to fill its timeline
|
||||
--
|
||||
_idf_revs = {}
|
||||
push_hook_functions({
|
||||
["start"] = function (session_id)
|
||||
_idf_revs[session_id] = {}
|
||||
return "continue",nil
|
||||
end,
|
||||
["revision_received"] = function (new_id, revision, certs, session_id)
|
||||
table.insert(_idf_revs[session_id], new_id)
|
||||
return "continue",nil
|
||||
end,
|
||||
["end"] = function (session_id, ...)
|
||||
if table.getn(_idf_revs[session_id]) == 0 then
|
||||
return "continue",nil
|
||||
end
|
||||
|
||||
local pin,pout,pid = spawn_pipe("%%MTNPOSTPUSH%%", "%%PROJECT%%");
|
||||
if pid == -1 then
|
||||
print("could not execute %%MTNPOSTPUSH%%")
|
||||
return
|
||||
end
|
||||
|
||||
for _,r in ipairs(_idf_revs[session_id]) do
|
||||
pin:write(r .. "\n")
|
||||
end
|
||||
pin:close()
|
||||
|
||||
wait(pid)
|
||||
return "continue",nil
|
||||
end
|
||||
})
|
||||
|
||||
--
|
||||
-- Load local hooks if they exist.
|
||||
--
|
||||
-- The way this is supposed to work is that hooks.d can contain symbolic
|
||||
-- links to lua scripts. These links MUST have the extension .lua
|
||||
-- If the script needs some configuration, a corresponding file with
|
||||
-- the extension .conf is the right spot.
|
||||
--
|
||||
-- First load the configuration of the hooks, if applicable
|
||||
includedirpattern(get_confdir() .. "/hooks.d/", "*.conf")
|
||||
-- Then load the hooks themselves
|
||||
includedirpattern(get_confdir() .. "/hooks.d/", "*.lua")
|
||||
|
@ -1,92 +0,0 @@
|
||||
-- ***** 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 *****
|
||||
|
||||
--
|
||||
-- controls the access rights for remote_stdio which is used by IDFs frontend
|
||||
-- and other interested parties
|
||||
--
|
||||
function get_remote_automate_permitted(key_identity, command, options)
|
||||
local read_only_commands = {
|
||||
"get_corresponding_path", "get_content_changed", "tags", "branches",
|
||||
"common_ancestors", "packet_for_fdelta", "packet_for_fdata",
|
||||
"packets_for_certs", "packet_for_rdata", "get_manifest_of",
|
||||
"get_revision", "select", "graph", "children", "parents", "roots",
|
||||
"leaves", "ancestry_difference", "toposort", "erase_ancestors",
|
||||
"descendents", "ancestors", "heads", "get_file_of", "get_file",
|
||||
"interface_version", "get_attributes", "content_diff",
|
||||
"file_merge", "show_conflicts", "certs", "keys", "get_file_size",
|
||||
"get_extended_manifest_of"
|
||||
}
|
||||
|
||||
for _,v in ipairs(read_only_commands) do
|
||||
if (v == command[1]) then
|
||||
return true
|
||||
end
|
||||
end
|
||||
|
||||
return false
|
||||
end
|
||||
|
||||
--
|
||||
-- let IDF know of new arriving revisions to fill its timeline
|
||||
--
|
||||
_idf_revs = {}
|
||||
push_hook_functions({
|
||||
["start"] = function (session_id)
|
||||
_idf_revs[session_id] = {}
|
||||
return "continue",nil
|
||||
end,
|
||||
["revision_received"] = function (new_id, revision, certs, session_id)
|
||||
table.insert(_idf_revs[session_id], new_id)
|
||||
return "continue",nil
|
||||
end,
|
||||
["end"] = function (session_id, ...)
|
||||
if table.getn(_idf_revs[session_id]) == 0 then
|
||||
return "continue",nil
|
||||
end
|
||||
|
||||
local pin,pout,pid = spawn_pipe("%%MTNPOSTPUSH%%", "%%PROJECT%%");
|
||||
if pid == -1 then
|
||||
print("could not execute %%MTNPOSTPUSH%%")
|
||||
return
|
||||
end
|
||||
|
||||
for _,r in ipairs(_idf_revs[session_id]) do
|
||||
pin:write(r .. "\n")
|
||||
end
|
||||
pin:close()
|
||||
|
||||
wait(pid)
|
||||
return "continue",nil
|
||||
end
|
||||
})
|
||||
|
||||
--
|
||||
-- Load local hooks if they exist.
|
||||
--
|
||||
-- The way this is supposed to work is that hooks.d can contain symbolic
|
||||
-- links to lua scripts. These links MUST have the extension .lua
|
||||
-- If the script needs some configuration, a corresponding file with
|
||||
-- the extension .conf is the right spot.
|
||||
--
|
||||
-- First load the configuration of the hooks, if applicable
|
||||
includedirpattern(get_confdir() .. "/hooks.d/", "*.conf")
|
||||
-- Then load the hooks themselves
|
||||
includedirpattern(get_confdir() .. "/hooks.d/", "*.lua")
|
30
src/IDF/Plugin/SyncMonotone/monotonerc.in
Normal file
30
src/IDF/Plugin/SyncMonotone/monotonerc.in
Normal file
@ -0,0 +1,30 @@
|
||||
-- ***** BEGIN LICENSE BLOCK *****
|
||||
-- This file is part of InDefero, an open source project management application.
|
||||
-- Copyright (C) 2011 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 *****
|
||||
|
||||
---- Load local hooks if they exist.
|
||||
-- The way this is supposed to work is that hooks.d can contain symbolic
|
||||
-- links to lua scripts. These links MUST have the extension .lua
|
||||
-- If the script needs some configuration, a corresponding file with
|
||||
-- the extension .conf is the right spot.
|
||||
----
|
||||
-- First load the configuration of the hooks, if applicable
|
||||
includedirpattern(get_confdir() .. "/hooks.d/","*.conf")
|
||||
-- Then load the hooks themselves
|
||||
includedirpattern(get_confdir() .. "/hooks.d/","*.lua")
|
@ -0,0 +1 @@
|
||||
%%MTNCLIENTKEY%%
|
@ -280,6 +280,14 @@ class IDF_Scm_Git extends IDF_Scm
|
||||
|
||||
public static function getAuthAccessUrl($project, $user, $commit=null)
|
||||
{
|
||||
// if the user haven't registred a public ssh key,
|
||||
// he can't use the write url which use the SSH authentification
|
||||
if ($user != null) {
|
||||
$keys = $user->get_idf_key_list();
|
||||
if (count ($keys) == 0)
|
||||
return self::getAnonymousAccessUrl($project);
|
||||
}
|
||||
|
||||
return sprintf(Pluf::f('git_write_remote_url'), $project->shortname);
|
||||
}
|
||||
|
||||
|
@ -70,11 +70,6 @@ class IDF_Scm_Monotone_Stdio
|
||||
*/
|
||||
public function _getAuthOptions()
|
||||
{
|
||||
// no remote authentication - the simple case
|
||||
if (!Pluf::f('mtn_remote_auth', true)) {
|
||||
return '--key= ';
|
||||
}
|
||||
|
||||
$prjconf = $this->project->getConf();
|
||||
$name = $prjconf->getVal('mtn_client_key_name', false);
|
||||
$hash = $prjconf->getVal('mtn_client_key_hash', false);
|
||||
|
@ -168,6 +168,7 @@ class IDF_Template_MarkdownPrefilter extends Pluf_Text_HTML_Filter
|
||||
'https',
|
||||
'ftp',
|
||||
'mailto',
|
||||
'irc'
|
||||
);
|
||||
// tags which should be removed if they contain no content
|
||||
// (e.g. "<b></b>" or "<b />")
|
||||
|
@ -78,6 +78,164 @@ class IDF_Views_Issue
|
||||
$params, $request);
|
||||
}
|
||||
|
||||
/**
|
||||
* View the issues watch list of a given user.
|
||||
* Limited to a specified project
|
||||
*/
|
||||
public $watchList_precond = array('IDF_Precondition::accessIssues',
|
||||
'Pluf_Precondition::loginRequired');
|
||||
public function watchList($request, $match)
|
||||
{
|
||||
$prj = $request->project;
|
||||
$otags = $prj->getTagIdsByStatus('open');
|
||||
$ctags = $prj->getTagIdsByStatus('closed');
|
||||
if (count($otags) == 0) $otags[] = 0;
|
||||
if (count($ctags) == 0) $ctags[] = 0;
|
||||
|
||||
// Get the id list of issue in the user watch list (for all projects !)
|
||||
$db =& Pluf::db();
|
||||
$sql_results = $db->select('SELECT idf_issue_id as id FROM '.Pluf::f('db_table_prefix', '').'idf_issue_pluf_user_assoc WHERE pluf_user_id='.$request->user->id);
|
||||
$issue_ids = array(0);
|
||||
foreach ($sql_results as $id) {
|
||||
$issue_ids[] = $id['id'];
|
||||
}
|
||||
$issue_ids = implode (',', $issue_ids);
|
||||
|
||||
// Count open and close issues
|
||||
$sql = new Pluf_SQL('project=%s AND id IN ('.$issue_ids.') AND status IN ('.implode(', ', $otags).')', array($prj->id));
|
||||
$nb_open = Pluf::factory('IDF_Issue')->getCount(array('filter'=>$sql->gen()));
|
||||
$sql = new Pluf_SQL('project=%s AND id IN ('.$issue_ids.') AND status IN ('.implode(', ', $ctags).')', array($prj->id));
|
||||
$nb_closed = Pluf::factory('IDF_Issue')->getCount(array('filter'=>$sql->gen()));
|
||||
|
||||
// Generate a filter for the paginator
|
||||
switch ($match[2]) {
|
||||
case 'closed':
|
||||
$title = sprintf(__('Watch List: Closed Issues for %s'), (string) $prj);
|
||||
$summary = __('This table shows the closed issues in your watch list for %s project.', (string) $prj);
|
||||
$f_sql = new Pluf_SQL('project=%s AND id IN ('.$issue_ids.') AND status IN ('.implode(', ', $ctags).')', array($prj->id));
|
||||
break;
|
||||
case 'open':
|
||||
default:
|
||||
$title = sprintf(__('Watch List: Open Issues for %s'), (string) $prj);
|
||||
$summary = __('This table shows the open issues in your watch list for %s project.', (string) $prj);
|
||||
$f_sql = new Pluf_SQL('project=%s AND id IN ('.$issue_ids.') AND status IN ('.implode(', ', $otags).')', array($prj->id));
|
||||
break;
|
||||
}
|
||||
|
||||
// Paginator to paginate the issues
|
||||
$pag = new Pluf_Paginator(new IDF_Issue());
|
||||
$pag->class = 'recent-issues';
|
||||
$pag->item_extra_props = array('project_m' => $prj,
|
||||
'shortname' => $prj->shortname,
|
||||
'current_user' => $request->user);
|
||||
$pag->summary = $summary;
|
||||
$pag->forced_where = $f_sql;
|
||||
$pag->action = array('IDF_Views_Issue::watchList', array($prj->shortname, $match[1]));
|
||||
$pag->sort_order = array('modif_dtime', 'ASC'); // will be reverted
|
||||
$pag->sort_reverse_order = array('modif_dtime');
|
||||
$pag->sort_link_title = true;
|
||||
$pag->extra_classes = array('a-c', '', 'a-c', '');
|
||||
$list_display = array(
|
||||
'id' => __('Id'),
|
||||
array('summary', 'IDF_Views_Issue_SummaryAndLabels', __('Summary')),
|
||||
array('status', 'IDF_Views_Issue_ShowStatus', __('Status')),
|
||||
array('modif_dtime', 'Pluf_Paginator_DateAgo', __('Last Updated')),
|
||||
);
|
||||
$pag->configure($list_display, array(), array('id', 'status', 'modif_dtime'));
|
||||
$pag->items_per_page = 10;
|
||||
$pag->no_results_text = __('No issues were found.');
|
||||
$pag->setFromRequest($request);
|
||||
return Pluf_Shortcuts_RenderToResponse('idf/issues/project-watchlist.html',
|
||||
array('project' => $prj,
|
||||
'page_title' => $title,
|
||||
'open' => $nb_open,
|
||||
'closed' => $nb_closed,
|
||||
'issues' => $pag,
|
||||
),
|
||||
$request);
|
||||
}
|
||||
|
||||
/**
|
||||
* View the issues watch list of a given user.
|
||||
* For all projects
|
||||
*/
|
||||
public $forgeWatchList_precond = array('Pluf_Precondition::loginRequired');
|
||||
public function forgeWatchList($request, $match)
|
||||
{
|
||||
$otags = array();
|
||||
$ctags = array();
|
||||
// Note that this approach does not scale, we will need to add
|
||||
// a table to cache the meaning of the tags for large forges.
|
||||
foreach (IDF_Views::getProjects($request->user) as $project) {
|
||||
$otags = array_merge($otags, $project->getTagIdsByStatus('open'));
|
||||
}
|
||||
foreach (IDF_Views::getProjects($request->user) as $project) {
|
||||
$ctags = array_merge($ctags, $project->getTagIdsByStatus('closed'));
|
||||
}
|
||||
if (count($otags) == 0) $otags[] = 0;
|
||||
if (count($ctags) == 0) $ctags[] = 0;
|
||||
|
||||
// Get the id list of issue in the user watch list (for all projects !)
|
||||
$db =& Pluf::db();
|
||||
$sql_results = $db->select('SELECT idf_issue_id as id FROM '.Pluf::f('db_table_prefix', '').'idf_issue_pluf_user_assoc WHERE pluf_user_id='.$request->user->id);
|
||||
$issue_ids = array(0);
|
||||
foreach ($sql_results as $id) {
|
||||
$issue_ids[] = $id['id'];
|
||||
}
|
||||
$issue_ids = implode (',', $issue_ids);
|
||||
|
||||
// Count open and close issues
|
||||
$sql = new Pluf_SQL('id IN ('.$issue_ids.') AND status IN ('.implode(', ', $otags).')', array());
|
||||
$nb_open = Pluf::factory('IDF_Issue')->getCount(array('filter'=>$sql->gen()));
|
||||
$sql = new Pluf_SQL('id IN ('.$issue_ids.') AND status IN ('.implode(', ', $ctags).')', array());
|
||||
$nb_closed = Pluf::factory('IDF_Issue')->getCount(array('filter'=>$sql->gen()));
|
||||
|
||||
// Generate a filter for the paginator
|
||||
switch ($match[1]) {
|
||||
case 'closed':
|
||||
$title = sprintf(__('Watch List: Closed Issues'));
|
||||
$summary = __('This table shows the closed issues in your watch list.');
|
||||
$f_sql = new Pluf_SQL('id IN ('.$issue_ids.') AND status IN ('.implode(', ', $ctags).')', array());
|
||||
break;
|
||||
case 'open':
|
||||
default:
|
||||
$title = sprintf(__('Watch List: Open Issues'));
|
||||
$summary = __('This table shows the open issues in your watch list.');
|
||||
$f_sql = new Pluf_SQL('id IN ('.$issue_ids.') AND status IN ('.implode(', ', $otags).')', array());
|
||||
break;
|
||||
}
|
||||
|
||||
// Paginator to paginate the issues
|
||||
$pag = new Pluf_Paginator(new IDF_Issue());
|
||||
$pag->class = 'recent-issues';
|
||||
$pag->item_extra_props = array('current_user' => $request->user);
|
||||
$pag->summary = $summary;
|
||||
$pag->forced_where = $f_sql;
|
||||
$pag->action = array('IDF_Views_Issue::forgeWatchList', array($match[1]));
|
||||
$pag->sort_order = array('modif_dtime', 'ASC'); // will be reverted
|
||||
$pag->sort_reverse_order = array('modif_dtime');
|
||||
$pag->sort_link_title = true;
|
||||
$pag->extra_classes = array('a-c', '', 'a-c', 'a-c', 'a-c');
|
||||
$list_display = array(
|
||||
'id' => __('Id'),
|
||||
array('summary', 'IDF_Views_Issue_SummaryAndLabelsUnknownProject', __('Summary')),
|
||||
array('project', 'Pluf_Paginator_FkToString', __('Project')),
|
||||
array('status', 'IDF_Views_Issue_ShowStatus', __('Status')),
|
||||
array('modif_dtime', 'Pluf_Paginator_DateAgo', __('Last Updated')),
|
||||
);
|
||||
$pag->configure($list_display, array(), array('id', 'project', 'status', 'modif_dtime'));
|
||||
$pag->items_per_page = 10;
|
||||
$pag->no_results_text = __('No issues were found.');
|
||||
$pag->setFromRequest($request);
|
||||
return Pluf_Shortcuts_RenderToResponse('idf/issues/forge-watchlist.html',
|
||||
array('page_title' => $title,
|
||||
'open' => $nb_open,
|
||||
'closed' => $nb_closed,
|
||||
'issues' => $pag,
|
||||
),
|
||||
$request);
|
||||
}
|
||||
|
||||
/**
|
||||
* View the issues of a given user.
|
||||
*
|
||||
@ -280,6 +438,26 @@ class IDF_Views_Issue
|
||||
}
|
||||
}
|
||||
|
||||
// Search previous and next issue id
|
||||
$octags = $prj->getTagIdsByStatus(($closed) ? 'closed' : 'open');
|
||||
if (count($octags) == 0) $octags[] = 0;
|
||||
$sql_previous = new Pluf_SQL('project=%s AND status IN ('.implode(', ', $octags).') AND id<%s',
|
||||
array($prj->id, $match[2])
|
||||
);
|
||||
$sql_next = new Pluf_SQL('project=%s AND status IN ('.implode(', ', $octags).') AND id>%s',
|
||||
array($prj->id, $match[2])
|
||||
);
|
||||
$previous_issue = Pluf::factory('IDF_Issue')->getList(array('filter' => $sql_previous->gen(),
|
||||
'order' => 'id DESC',
|
||||
'nb' => 1
|
||||
));
|
||||
$next_issue = Pluf::factory('IDF_Issue')->getList(array('filter' => $sql_next->gen(),
|
||||
'order' => 'id ASC',
|
||||
'nb' => 1
|
||||
));
|
||||
$previous_issue_id = (isset($previous_issue[0])) ? $previous_issue[0]->id : 0;
|
||||
$next_issue_id = (isset($next_issue[0])) ? $next_issue[0]->id : 0;
|
||||
|
||||
$arrays = self::autoCompleteArrays($prj);
|
||||
return Pluf_Shortcuts_RenderToResponse('idf/issues/view.html',
|
||||
array_merge(
|
||||
@ -292,6 +470,8 @@ class IDF_Views_Issue
|
||||
'closed' => $closed,
|
||||
'preview' => $preview,
|
||||
'interested' => $interested->count(),
|
||||
'previous_issue_id' => $previous_issue_id,
|
||||
'next_issue_id' => $next_issue_id
|
||||
),
|
||||
$arrays),
|
||||
$request);
|
||||
@ -540,6 +720,17 @@ class IDF_Views_Issue
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* When you access to your forge watch list, issue don't known
|
||||
* the project shortname.
|
||||
*/
|
||||
function IDF_Views_Issue_SummaryAndLabelsUnknownProject($field, $issue, $extra='')
|
||||
{
|
||||
$shortname = $issue->get_project()->shortname;
|
||||
$issue->__set('shortname', $shortname);
|
||||
return IDF_Views_Issue_SummaryAndLabels ($field, $issue, $extra);
|
||||
}
|
||||
|
||||
/**
|
||||
* Display the summary of an issue, then on a new line, display the
|
||||
* list of labels with a link to a view "by label only".
|
||||
@ -576,3 +767,5 @@ function IDF_Views_Issue_ShowStatus($field, $issue, $extra='')
|
||||
{
|
||||
return Pluf_esc($issue->get_status()->name);
|
||||
}
|
||||
|
||||
|
||||
|
@ -98,17 +98,16 @@ $cfg['mtn_remote_url'] = 'mtn://my-host.biz/%s';
|
||||
# choosed for manual setups and / or ssh access.
|
||||
$cfg['mtn_db_access'] = 'local';
|
||||
|
||||
# If true, each access to the database is authenticated with an auto-generated
|
||||
# project key which is stored in the IDF project configuration
|
||||
# ('mtn_client_key_*') and written out to $cfg['tmp_folder']/mtn-client-keys
|
||||
# for its actual use. This key is then configured on the server to have
|
||||
# full read / write access to all functions, while anonymous access can be
|
||||
# completely disabled.
|
||||
# If false, IDF tries to connect anonymously, without authentication, to
|
||||
# the remote monotone server instance. In this case no project-specific
|
||||
# keys are generated and the server must be configured to allow at least
|
||||
# anonymous read access to the main functions.
|
||||
#$cfg['mtn_remote_auth'] = true;
|
||||
# Full path to the directory tree which contains default configuration files
|
||||
# that are automatically created for new projects. This is only needed
|
||||
# if $cfg['mtn_db_access'] is set to remote, i.e. in case the SyncMonotone
|
||||
# plugin should be used. If unset, it defaults to the tree underknees
|
||||
# src/IDF/Plugin/SyncMonotone/. Don't forget the trailing slash!
|
||||
#$cfg['mtn_confdir'] = '/path/to/dir/tree/';
|
||||
|
||||
# Additional configuration files you want to create / copy for new setups.
|
||||
# All these file paths have to be relative to $cfg['mtn_confdir'].
|
||||
#$cfg['mtn_confdir_extra'] = array('hooks.d/something.lua')
|
||||
|
||||
# Needs to be configured for remote / usher usage.
|
||||
# This allows basic control of a running usher process via the forge
|
||||
@ -256,6 +255,12 @@ $cfg['allowed_scm'] = array('git' => 'IDF_Scm_Git',
|
||||
'mtn' => 'IDF_Scm_Monotone',
|
||||
);
|
||||
|
||||
# Specific git config
|
||||
# The core.quotepath is configured on new repository
|
||||
# True -> All characters upper than 0x80 will be escape (default)
|
||||
# False -> Characters is print directly, enable accented character in a UTF-8 shell
|
||||
# $cfg['git_core_quotepath'] = false;
|
||||
|
||||
# Set to true when uploaded public keys should not only be validated
|
||||
# syntactically, but also by the specific backend. For SSH public
|
||||
# keys, ssh-keygen(3) must be available and usable in PATH, for
|
||||
|
@ -141,6 +141,16 @@ $ctl[] = array('regex' => '#^/p/([\-\w]+)/issues/view/attachment/(\d+)/(.*)$#',
|
||||
'model' => 'IDF_Views_Issue',
|
||||
'method' => 'viewAttachment');
|
||||
|
||||
$ctl[] = array('regex' => '#^/p/([\-\w]+)/issues/watchlist/(\w+)$#',
|
||||
'base' => $base,
|
||||
'model' => 'IDF_Views_Issue',
|
||||
'method' => 'watchList');
|
||||
|
||||
$ctl[] = array('regex' => '#^/watchlist/(\w+)$#',
|
||||
'base' => $base,
|
||||
'model' => 'IDF_Views_Issue',
|
||||
'method' => 'forgeWatchList');
|
||||
|
||||
// ---------- SCM ----------------------------------------
|
||||
|
||||
$ctl[] = array('regex' => '#^/p/([\-\w]+)/source/help/$#',
|
||||
|
@ -25,6 +25,7 @@
|
||||
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
|
||||
<link rel="stylesheet" type="text/css" href="{media '/idf/css/yui.css'}" />
|
||||
<link rel="stylesheet" type="text/css" href="{media '/idf/css/style.css'}" />
|
||||
<link rel="icon" type="image/png" href="{media '/idf/img/favicon.png'}" />
|
||||
<!--[if lt IE 7]>
|
||||
<link rel="stylesheet" type="text/css" href="{media '/idf/css/ie6.css'}" />
|
||||
<![endif]-->
|
||||
|
@ -25,6 +25,7 @@
|
||||
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
|
||||
<link rel="stylesheet" type="text/css" href="{media '/idf/css/yui.css'}" />
|
||||
<link rel="stylesheet" type="text/css" href="{media '/idf/css/style.css'}" />
|
||||
<link rel="icon" type="image/png" href="{media '/idf/img/favicon.png'}" />
|
||||
<!--[if lt IE 7]>
|
||||
<link rel="stylesheet" type="text/css" href="{media '/idf/css/ie6.css'}" />
|
||||
<![endif]-->
|
||||
|
@ -25,6 +25,7 @@
|
||||
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
|
||||
<link rel="stylesheet" type="text/css" href="{media '/idf/css/yui.css'}" />
|
||||
<link rel="stylesheet" type="text/css" href="{media '/idf/css/style.css'}" />
|
||||
<link rel="icon" type="image/png" href="{media '/idf/img/favicon.png'}" />
|
||||
<!--[if lt IE 7]>
|
||||
<link rel="stylesheet" type="text/css" href="{media '/idf/css/ie6.css'}" />
|
||||
<![endif]-->
|
||||
|
@ -3,7 +3,8 @@
|
||||
{block subtabs}
|
||||
<div id="sub-tabs">
|
||||
<a {if $inOpenIssues}class="active" {/if}href="{url 'IDF_Views_Issue::index', array($project.shortname)}">{trans 'Open Issues'}</a>
|
||||
{if !$user.isAnonymous()} | <a {if $inCreate}class="active" {/if}href="{url 'IDF_Views_Issue::create', array($project.shortname)}">{trans 'New Issue'}</a> | <a {if $inMyIssues}class="active" {/if}href="{url 'IDF_Views_Issue::myIssues', array($project.shortname, 'submit')}">{trans 'My Issues'}</a>{/if} |
|
||||
{if !$user.isAnonymous()} | <a {if $inCreate}class="active" {/if}href="{url 'IDF_Views_Issue::create', array($project.shortname)}">{trans 'New Issue'}</a> | <a {if $inMyIssues}class="active" {/if}href="{url 'IDF_Views_Issue::myIssues', array($project.shortname, 'submit')}">{trans 'My Issues'}</a>
|
||||
| <a {if $inWatchList}class="active" {/if}href="{url 'IDF_Views_Issue::watchList', array($project.shortname, 'open')}">{trans 'My watch list'}</a>{/if} |
|
||||
<form class="star" action="{url 'IDF_Views_Issue::search', array($project.shortname)}" method="get">
|
||||
<input accesskey="4" type="text" value="{$q}" name="q" size="20" />
|
||||
<input type="submit" name="s" value="{trans 'Search'}" />
|
||||
|
12
src/IDF/templates/idf/issues/forge-watchlist.html
Normal file
12
src/IDF/templates/idf/issues/forge-watchlist.html
Normal file
@ -0,0 +1,12 @@
|
||||
{extends "idf/base-simple.html"}
|
||||
|
||||
{block body}
|
||||
{$issues.render}
|
||||
{/block}
|
||||
|
||||
{block context}
|
||||
{aurl 'open_url', 'IDF_Views_Issue::forgeWatchList', array('open')}
|
||||
{aurl 'closed_url', 'IDF_Views_Issue::forgeWatchList', array('closed')}
|
||||
{blocktrans}<p><strong>Open issues:</strong> <a href="{$open_url}">{$open}</a></p>
|
||||
<p><strong>Closed issues:</strong> <a href="{$closed_url}">{$closed}</a></p>{/blocktrans}
|
||||
{/block}
|
17
src/IDF/templates/idf/issues/project-watchlist.html
Normal file
17
src/IDF/templates/idf/issues/project-watchlist.html
Normal file
@ -0,0 +1,17 @@
|
||||
{extends "idf/issues/base.html"}
|
||||
|
||||
{block docclass}yui-t2{assign $inWatchList = true}{/block}
|
||||
|
||||
{block body}
|
||||
{$issues.render}
|
||||
{if !$user.isAnonymous()}
|
||||
{aurl 'url', 'IDF_Views_Issue::create', array($project.shortname)}
|
||||
<p><a href="{$url}"><img style="vertical-align: text-bottom;" src="{media '/idf/img/add.png'}" alt="+" align="bottom" /></a> <a href="{$url}">{trans 'New Issue'}</a></p>{/if}
|
||||
{/block}
|
||||
|
||||
{block context}
|
||||
{aurl 'open_url', 'IDF_Views_Issue::watchList', array($project.shortname, 'open')}
|
||||
{aurl 'closed_url', 'IDF_Views_Issue::watchList', array($project.shortname, 'closed')}
|
||||
{blocktrans}<p><strong>Open issues:</strong> <a href="{$open_url}">{$open}</a></p>
|
||||
<p><strong>Closed issues:</strong> <a href="{$closed_url}">{$closed}</a></p>{/blocktrans}
|
||||
{/block}
|
@ -1,6 +1,15 @@
|
||||
{extends "idf/issues/base.html"}
|
||||
{block titleicon}{if $form}<form class="star" method="post" action="{url 'IDF_Views_Issue::star', array($project.shortname, $issue.id)}"><input type="image" src="{if $starred}{media '/idf/img/star.png'}{else}{media '/idf/img/star-grey.png'}{/if}" name="submit" title="{if $starred}{trans 'Remove this issue from your watch list'}{else}{trans 'Add this issue to your watch list'}{/if}" /></form> {/if}{/block}
|
||||
{block body}
|
||||
<div style="float:right;">
|
||||
{if $previous_issue_id}
|
||||
<a href="{url 'IDF_Views_Issue::view', array($project.shortname, $previous_issue_id)}" title="{if $closed}{trans 'Click here to view the previous closed issue'}{else}{trans 'Click here to view the previous open issue'}{/if}">Previous issue</a>
|
||||
{/if}
|
||||
{if $previous_issue_id and $next_issue_id} - {/if}
|
||||
{if $next_issue_id}
|
||||
<a href="{url 'IDF_Views_Issue::view', array($project.shortname, $next_issue_id)}" title="{if $closed}{trans 'Click here to view the next closed issue'}{else}{trans 'Click here to view the next open issue'}{/if}">Next issue</a>
|
||||
{/if}
|
||||
</div>
|
||||
{assign $i = 0}
|
||||
{assign $nc = $comments.count()}
|
||||
{foreach $comments as $c}{ashowuser 'submitter', $c.get_submitter(), $request}
|
||||
|
@ -11,5 +11,7 @@
|
||||
<p>{blocktrans}<a href="{$url}">Update your account</a>.{/blocktrans}</p>
|
||||
{aurl 'url', 'IDF_Views_User::view', array($user.login)}
|
||||
<p>{blocktrans}<a href="{$url}">See your public profile</a>.{/blocktrans}</p>
|
||||
{aurl 'url', 'IDF_Views_Issue::forgeWatchList', array('open')}
|
||||
<p>{blocktrans}<a href="{$url}">See your forge issue watch list</a>.{/blocktrans}</p>
|
||||
{/block}
|
||||
|
||||
|
@ -18,6 +18,9 @@ by {$submitter}.{/blocktrans}</p>
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
<div id="wiki-content">
|
||||
<div id="wiki-toc"><span id="contentheader">{trans 'Table of Content'}</span><div id="wiki-toc-content"></div></div>
|
||||
<script type="text/javascript" src="{media '/idf/js/wiki-toc.js'}"></script>
|
||||
<p class="desc">{$page.summary}</p>
|
||||
|
||||
{if !$oldrev}
|
||||
@ -29,6 +32,7 @@ by {$submitter}.{/blocktrans}</p>
|
||||
{/if}
|
||||
{/if}
|
||||
|
||||
</div>
|
||||
{/block}
|
||||
{block context}
|
||||
{ashowuser 'submitter', $page.get_submitter(), $request}
|
||||
|
@ -225,6 +225,7 @@ div.issue-comment {
|
||||
|
||||
div.issue-comment-first {
|
||||
border-top: 1px solid #d3d7cf;
|
||||
clear: both;
|
||||
}
|
||||
|
||||
div.issue-comment-signin {
|
||||
@ -702,6 +703,14 @@ div.deprecated-page {
|
||||
color: #a00;
|
||||
}
|
||||
|
||||
ul > li {
|
||||
list-style: disc outside none;
|
||||
}
|
||||
|
||||
ol > li {
|
||||
list-style: decimal outside none;
|
||||
}
|
||||
|
||||
#branding {
|
||||
float: right;
|
||||
position: relative;
|
||||
@ -737,6 +746,32 @@ div.deprecated-page {
|
||||
margin: 0px;
|
||||
}
|
||||
|
||||
#wiki-toc {
|
||||
float: right;
|
||||
margin-left: 10px;
|
||||
}
|
||||
|
||||
#wiki-toc-content {
|
||||
border: 1px solid #999999;
|
||||
border-width: 1px 0;
|
||||
padding: 10px 0;
|
||||
padding-bottom: 25px;
|
||||
background-color: #ffffff;
|
||||
display: block;
|
||||
}
|
||||
|
||||
#wiki-toc-content a {
|
||||
display: block;
|
||||
}
|
||||
|
||||
#wiki-toc-content a.wiki-h2 {
|
||||
margin-left: 1em;
|
||||
}
|
||||
|
||||
#wiki-toc-content a.wiki-h3 {
|
||||
margin-left: 2em;
|
||||
}
|
||||
|
||||
/**
|
||||
* main menu
|
||||
*/
|
||||
|
BIN
www/media/idf/img/favicon.png
Normal file
BIN
www/media/idf/img/favicon.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 677 B |
7
www/media/idf/js/wiki-toc.js
Normal file
7
www/media/idf/js/wiki-toc.js
Normal file
@ -0,0 +1,7 @@
|
||||
$(document).ready(function() {
|
||||
$(":header", "#wiki-content").map(function (index) {
|
||||
this.id = "wikititle_" + index;
|
||||
$("<a href='#" + this.id + "'>" + this.innerText + "</a>").addClass("wiki-" + this.tagName.toLowerCase()).appendTo('#wiki-toc-content');
|
||||
});
|
||||
});
|
||||
|
Loading…
Reference in New Issue
Block a user