From 29b8bf8a4e692f8407beb57aff81fd64a9d33459 Mon Sep 17 00:00:00 2001 From: Thomas Keller Date: Wed, 15 Sep 2010 08:46:10 +0000 Subject: [PATCH 01/26] Some revisions might not carry a branch cert (yet), because they're part of another branch whose certs haven't been pushed into the server yet, so we need to skip these revisions while going back in time for the changelog. The initial revision however must carry a branch cert, otherwise we have nothing to "follow". --- src/IDF/Scm/Monotone.php | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/src/IDF/Scm/Monotone.php b/src/IDF/Scm/Monotone.php index a0b01c6..682d317 100644 --- a/src/IDF/Scm/Monotone.php +++ b/src/IDF/Scm/Monotone.php @@ -680,11 +680,18 @@ class IDF_Scm_Monotone extends IDF_Scm // read in the initial branches we should follow if (count($initialBranches) == 0) { + if (!isset($certs['branch'])) { + throw new IDF_Scm_Exception(sprintf( + __("revision %s has no branch cert - cannot start ". + "logging from this revision"), $rev + )); + } $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) { + // ignore revisions without any branch certificate + if (count(array_intersect($initialBranches, (array)@$certs['branch'])) > 0) { --$n; $log = array(); From 50638c768f302f278c94583fac36fc4818ff47d6 Mon Sep 17 00:00:00 2001 From: Thomas Keller Date: Fri, 17 Sep 2010 02:36:48 +0200 Subject: [PATCH 02/26] Ensure that the SyncMonotone plugin does not throw around errors in case of a local (non-usher) monotone setup. --- src/IDF/Plugin/SyncMonotone.php | 26 ++++++++++++++++++++++++-- 1 file changed, 24 insertions(+), 2 deletions(-) diff --git a/src/IDF/Plugin/SyncMonotone.php b/src/IDF/Plugin/SyncMonotone.php index 19854e0..e34c674 100644 --- a/src/IDF/Plugin/SyncMonotone.php +++ b/src/IDF/Plugin/SyncMonotone.php @@ -76,6 +76,10 @@ class IDF_Plugin_SyncMonotone return; } + if (Pluf::f('mtn_db_access', 'local') == 'local') { + return; + } + $projecttempl = Pluf::f('mtn_repositories', false); if ($projecttempl === false) { throw new IDF_Scm_Exception( @@ -278,6 +282,10 @@ class IDF_Plugin_SyncMonotone return; } + if (Pluf::f('mtn_db_access', 'local') == 'local') { + return; + } + $mtn = IDF_Scm_Monotone::factory($project); $stdio = $mtn->getStdio(); @@ -338,6 +346,10 @@ class IDF_Plugin_SyncMonotone return; } + if (Pluf::f('mtn_db_access', 'local') == 'local') { + return; + } + $usher_config = Pluf::f('mtn_usher_conf', false); if (!$usher_config || !is_writable($usher_config)) { throw new IDF_Scm_Exception( @@ -419,8 +431,13 @@ class IDF_Plugin_SyncMonotone */ public function processKeyCreate($key) { - if ($key->getType() != 'mtn') + if ($key->getType() != 'mtn') { return; + } + + if (Pluf::f('mtn_db_access', 'local') == 'local') { + return; + } foreach (Pluf::factory('IDF_Project')->getList() as $project) { $conf = new IDF_Conf(); @@ -525,8 +542,13 @@ class IDF_Plugin_SyncMonotone */ public function processKeyDelete($key) { - if ($key->getType() != 'mtn') + if ($key->getType() != 'mtn') { return; + } + + if (Pluf::f('mtn_db_access', 'local') == 'local') { + return; + } foreach (Pluf::factory('IDF_Project')->getList() as $project) { $conf = new IDF_Conf(); From 617589f41b41d594249d0697688d8b7d0db1166f Mon Sep 17 00:00:00 2001 From: Thomas Keller Date: Fri, 17 Sep 2010 03:11:36 +0200 Subject: [PATCH 03/26] Reorganize and expand the help of the monotone plugin. Make the commentary in idf.php-dist less verbose. --- doc/readme-monotone.mdtext | 62 ------------- doc/syncmonotone.mdtext | 175 +++++++++++++++++++++++++++++++++++++ src/IDF/conf/idf.php-dist | 106 +++++----------------- 3 files changed, 198 insertions(+), 145 deletions(-) delete mode 100644 doc/readme-monotone.mdtext create mode 100644 doc/syncmonotone.mdtext diff --git a/doc/readme-monotone.mdtext b/doc/readme-monotone.mdtext deleted file mode 100644 index cbaf8ba..0000000 --- a/doc/readme-monotone.mdtext +++ /dev/null @@ -1,62 +0,0 @@ -# monotone implementation notes - -## general - - This version of indefero contains an implementation of the monotone - automation interface. It needs at least monotone version 0.99 - (interface version 13.0) or newer. - - 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. - diff --git a/doc/syncmonotone.mdtext b/doc/syncmonotone.mdtext new file mode 100644 index 0000000..89c1350 --- /dev/null +++ b/doc/syncmonotone.mdtext @@ -0,0 +1,175 @@ +# Plugin SyncMonotone by Thomas Keller (me@thomaskeller.biz) + +The SyncMonotone plugin allow the direct creation and synchronisation of +monotone repositories with the InDefero database. It has been built to +work together with monotone's "super server" usher, which is used to control +several repositories at once, acts as proxy and single entrance. + +## Prerequisites + +* a unixoid operating system +* monotone >= 0.99 +* for a proxy setup with usher: + * boost headers (for usher compilation) + * a current version of usher + * a daemonizer, like supervise + +## Installation of monotone + +If you install monotone from a distribution package, ensure you do not +install and / or activate the server component. We just need a plain +client installation which usually consists only of the `mtn` binary and +a few docs. + +If you install monotone from source (), +please follow the `INSTALL` document which comes with the software. +It contains detailed instructions, including all needed dependencies. + +## Choose your indefero setup + +The monotone plugin can be used in several different ways: + +1. One database for everything. This is the easiest setup and of possible + use in case you do not want indefero to manage the access to your project. + Your `idf.php` should look like this: + + $ cat idf.php + ... + $cfg['mtn_path'] = 'mtn'; + $cfg['mtn_opts'] = array('--no-workspace', '--no-standard-rcfiles'); + $cfg['mtn_repositories'] = '/home/monotone/all_projects.mtn'; + $cfg['mtn_remote_url'] = 'ssh://monotone@my.server.com:~all_projects.mtn'; + $cfg['mtn_db_access'] = 'local'; + ... + + Pro: + * easy to setup and to manage + + Con: + * you need to give committers SSH access to your machine + * database lock problem: the database from which + indefero reads its data might be locked in case a user + syncs at the very moment via SSH + +2. One database for every project. Similar to the above setup, but this + time you use the '%s' placeholder which is replaced with the short name + of the indefero project: + + $ cat idf.php + ... + $cfg['mtn_path'] = 'mtn'; + $cfg['mtn_opts'] = array('--no-workspace', '--no-standard-rcfiles'); + $cfg['mtn_repositories'] = '/home/monotone/%s.mtn'; + $cfg['mtn_remote_url'] = 'ssh://monotone@my.server.com:~%s.mtn'; + $cfg['mtn_db_access'] = 'local'; + ... + + The same pro's and con's apply. Additionally you have to be careful about + not giving people physical read/write access of another project's database. + + Furthermore, if you do not want to use `ssh`, but `netsync` transport, + each project's database must be served over a separate port. + +3. One database for every project, all managed with usher. This is the + recommended setup for a mid-size forge setup. The remaining part of this + document will describe the process to set this up in detail. + + Pro: + * access rights can be granted per project and are automatically + managed by indefero, just like the user's public monotone keys + * no database locking issues + * one public server running on the one well-known port + + Con: + * harder to setup + +## Installation and configuration of usher + +1. Clone usher's monotone repository: + + $ mtn clone "mtn://monotone.ca?net.venge.monotone.contrib.usher" + +2. Compile usher: + + $ autoreconf -i + $ ./configure && make + $ sudo make install + + This installs the usher binary in $prefix/bin. + +3. Create a new usher user: + + $ adduser --system --disabled-login --home /var/lib/usher usher + +4. Create the basic usher setup: + + $ cd /var/lib/usher + $ mkdir projects logs + $ cat > usher.conf + userpass "admin" "" + adminaddr "127.0.0.1:12345" + logdir "log" + ^D + $ chmod 600 usher.conf + + Your indefero www user needs later write access to `usher.conf` and + `projects/`. There are two ways of setting this up: + + * Make the usher user the web user, for example via Apache's `suexec` + * Use acls, like this: + + $ setfacl -m u:www:rw usher.conf + $ setfacl -m d:u:www:rwx projects/ + +5. Wrap a daemonizer around usher, for example supervise from daemontools + (): + + $ cat > run + #!/bin/sh + cd /var/lib/usher + exec 2>&1 + exec \ + setuidgid usher \ + usher usher.conf + ^D + + The service can now be started through supervise: + + $ supervise /var/lib/usher + +## Configuration of indefero + +Based on the above setup, the configuration in `src/IDF/conf/idf.php` should +look like this: + + $ cat idf.php + ... + $cfg['mtn_path'] = 'mtn'; + $cfg['mtn_opts'] = array('--no-workspace', '--no-standard-rcfiles'); + $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'; + ... + +The `%s` placeholders are automatically replaced by the name of the +indefero project. The plugin assumes that every project is separated +by a distinct server name in the monotone URL (hence the use of `/%s`), +so if a user calls + + $ mtn sync mtn://my.server.com/project1 + +then the database / repository of the indefero `project1` is used. +Note that 'mtn_remote_url' is also used as internal URI to query the data +for indefero's source view, so it *must* be a valid host! + +Usher also allows the identification of a project repository by hostname, +which would allow an URL template like `mtn://%s.my.server.com`, however +the plugin does not write out the configuration which is needed for this +yet. + +For even more advanced setups, usher can also be used to forward sync +requests to other remote servers for load balancing, please consult the +README file for more information. + diff --git a/src/IDF/conf/idf.php-dist b/src/IDF/conf/idf.php-dist index d4bcd5c..54c8ac8 100644 --- a/src/IDF/conf/idf.php-dist +++ b/src/IDF/conf/idf.php-dist @@ -73,90 +73,31 @@ $cfg['git_write_remote_url'] = 'git@localhost:%s.git'; $cfg['svn_repositories'] = 'file:///home/svn/repositories/%s'; $cfg['svn_remote_url'] = 'http://localhost/svn/%s'; -# Path to the monotone binary (you need mtn 0.99 or newer) +# +# You can setup monotone for use with indefero in several ways. +# Please look into doc/syncmonotone.mdtext for more information. +# + +# Path to the monotone binary $cfg['mtn_path'] = 'mtn'; + # Additional options for the started monotone process $cfg['mtn_opts'] = array('--no-workspace', '--no-standard-rcfiles'); -# -# You can setup monotone for use with indefero in several ways. The -# two most-used should be: -# -# 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': -# -# 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. -# Create a basic configuration file for it and add a secret admin -# username and password. Finally, point the below variable -# 'mtn_usher_conf' to this configuration file. -# -# Then 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. 'mtn_repositories' should then -# point to a directory where all project-related files (databases, -# keys, configurations) are kept, as these are automatically created -# on project creation by IDF. -# -# Example: 'mtn_repositories' is configured to be '/var/monotone/%s' -# -# - IDF tries to create /var/monotone/ as root directory -# - The database is placed in as /var/monotone//database.mtn -# - The server key is put into /var/monotone//keys and -# is named "-server@", where host is the host part -# of 'mtn_remote_url' -# -# therefor /var/monotone MUST be read/writable for the www user and all -# files which are created underknees MUST be read/writable by the user -# who is executing the usher instance! The best way to achieve this is with -# default (POSIX) ACLs on /var/monotone. -# -# -# You could also choose to setup usher by hand, i.e. with individual -# databases, in this case leave 'mtn_usher_conf' below commented out. -# -# 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'. -# + +# The path to a specific database (local use) or a writable project +# directory (remote / usher use). %s is replaced with the project name $cfg['mtn_repositories'] = '/home/mtn/repositories/%s.mtn'; + +# The URL which is displayed as sync URL to the user and which is also +# used to connect to a remote usher $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'; -# +# use with usher and the SyncMonotone plugin, while 'local' access should be +# 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 @@ -167,14 +108,13 @@ $cfg['mtn_db_access'] = 'remote'; # 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; -# -# If configured, this allows basic control of a running usher process -# via the forge administration. The variable must point to the full (writable) +#$cfg['mtn_remote_auth'] = true; + +# Needs to be configured for remote / usher usage. +# This allows basic control of a running usher process via the forge +# administration. The variable must point to the full (writable) # path of the usher configuration file which gets updated when new projects # are added -# #$cfg['mtn_usher_conf'] = '/path/to/usher.conf'; # Mercurial repositories path From eebdc5ad1267683fc021d548ce1d211227514b06 Mon Sep 17 00:00:00 2001 From: Thomas Keller Date: Tue, 28 Sep 2010 21:37:26 +0000 Subject: [PATCH 04/26] IDF_Scm_Monotone::getCommit() separate the first line of a commit from the rest and write the rest in full_message - just like we do it for log and everything else. This is ugly, really ugly, because it assumes something on the format of a commit message, which might not be true at all for some project, but this is something Loic has to decide (see also issue 491 and issue 535) --- src/IDF/Scm/Monotone.php | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/IDF/Scm/Monotone.php b/src/IDF/Scm/Monotone.php index 682d317..70924d2 100644 --- a/src/IDF/Scm/Monotone.php +++ b/src/IDF/Scm/Monotone.php @@ -623,10 +623,12 @@ class IDF_Scm_Monotone extends IDF_Scm $dates[] = date('Y-m-d H:i:s', strtotime($date)); $res['date'] = implode(', ', $dates); - $res['title'] = implode("\n---\n", $certs['changelog']); + $combinedChangelog = implode("\n---\n", $certs['changelog']); + $split = preg_split("/[\n\r]/", $combinedChangelog, 2); + $res['title'] = $split[0]; + $res['full_message'] = (isset($split[1])) ? trim($split[1]) : ''; $res['commit'] = $revs[0]; - $res['changes'] = ($getdiff) ? $this->_getDiff($revs[0]) : ''; return (object) $res; From 0c575ccc740828d61dd42c5305e61c19e1f1d1df Mon Sep 17 00:00:00 2001 From: Thomas Keller Date: Sun, 3 Oct 2010 22:23:08 +0000 Subject: [PATCH 05/26] If a symbol is printed without a value list at the very end of a basic_io dump, we might access a non-existing character position. This has been fixed and the string length calculation is now only done once. --- src/IDF/Scm/Monotone/BasicIO.php | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/src/IDF/Scm/Monotone/BasicIO.php b/src/IDF/Scm/Monotone/BasicIO.php index 78707c5..9c8c286 100644 --- a/src/IDF/Scm/Monotone/BasicIO.php +++ b/src/IDF/Scm/Monotone/BasicIO.php @@ -38,14 +38,15 @@ class IDF_Scm_Monotone_BasicIO { $pos = 0; $stanzas = array(); + $length = strlen($in); - while ($pos < strlen($in)) { + while ($pos < $length) { $stanza = array(); - while ($pos < strlen($in)) { + while ($pos < $length) { if ($in[$pos] == "\n") break; $stanzaLine = array('key' => '', 'values' => array(), 'hash' => null); - while ($pos < strlen($in)) { + while ($pos < $length) { $ch = $in[$pos]; if ($ch == '"' || $ch == '[') break; ++$pos; @@ -53,6 +54,9 @@ class IDF_Scm_Monotone_BasicIO $stanzaLine['key'] .= $ch; } + // symbol w/o a value list + if ($pos >= $length || $in[$pos] == "\n") break; + if ($in[$pos] == '[') { unset($stanzaLine['values']); ++$pos; // opening square bracket @@ -67,7 +71,7 @@ class IDF_Scm_Monotone_BasicIO while ($in[$pos] == '"') { ++$pos; // opening quote $stanzaLine['values'][$valCount] = ''; - while ($pos < strlen($in)) { + while ($pos < $length) { $ch = $in[$pos]; $pr = $in[$pos-1]; if ($ch == '"' && $pr != '\\') break; ++$pos; @@ -75,7 +79,7 @@ class IDF_Scm_Monotone_BasicIO } ++$pos; // closing quote - if ($pos >= strlen($in)) + if ($pos >= $length) break; if ($in[$pos] == ' ') { From 90edbf0d8b14c8cf94901688ce0b45af1b5d5019 Mon Sep 17 00:00:00 2001 From: Thomas Keller Date: Mon, 4 Oct 2010 15:20:53 +0000 Subject: [PATCH 06/26] Tweak the basicio parser so that it properly handles multi-value lines with hashes (lines like symbol [hash] [hash] are still not handled, but aren't outputted from any command either as of now). --- src/IDF/Scm/Monotone/BasicIO.php | 27 ++++++++++++++++----------- 1 file changed, 16 insertions(+), 11 deletions(-) diff --git a/src/IDF/Scm/Monotone/BasicIO.php b/src/IDF/Scm/Monotone/BasicIO.php index 9c8c286..e783a90 100644 --- a/src/IDF/Scm/Monotone/BasicIO.php +++ b/src/IDF/Scm/Monotone/BasicIO.php @@ -68,17 +68,30 @@ class IDF_Scm_Monotone_BasicIO { unset($stanzaLine['hash']); $valCount = 0; - while ($in[$pos] == '"') { - ++$pos; // opening quote + // if hashs and plain values are encountered in the same + // value list, we add the hash values as simple values as well + while ($in[$pos] == '"' || $in[$pos] == '[') { + $isHashValue = $in[$pos] == '['; + ++$pos; // opening quote / bracket $stanzaLine['values'][$valCount] = ''; while ($pos < $length) { $ch = $in[$pos]; $pr = $in[$pos-1]; - if ($ch == '"' && $pr != '\\') break; + if (($isHashValue && $ch == ']') + ||(!$isHashValue && $ch == '"' && $pr != '\\')) + break; ++$pos; $stanzaLine['values'][$valCount] .= $ch; } ++$pos; // closing quote + if (!$isHashValue) { + $stanzaLine['values'][$valCount] = str_replace( + array("\\\\", "\\\""), + array("\\", "\""), + $stanzaLine['values'][$valCount] + ); + } + if ($pos >= $length) break; @@ -87,14 +100,6 @@ class IDF_Scm_Monotone_BasicIO ++$valCount; } } - - for ($i = 0; $i <= $valCount; $i++) { - $stanzaLine['values'][$i] = str_replace( - array("\\\\", "\\\""), - array("\\", "\""), - $stanzaLine['values'][$i] - ); - } } $stanza[] = $stanzaLine; From d539eaf64bda988d44334a8f0e06321e0d221a71 Mon Sep 17 00:00:00 2001 From: Thomas Keller Date: Mon, 4 Oct 2010 15:22:57 +0000 Subject: [PATCH 07/26] - _getLastChangeFor(): drop that, no longer needed - getTree(), getPathInfo(): use the new extended manifest format and save the calls to query file sizes from contents as well as the calls to determine the revision in which a file changed at last --- src/IDF/Scm/Monotone.php | 193 +++++++++++++++++---------------------- 1 file changed, 86 insertions(+), 107 deletions(-) diff --git a/src/IDF/Scm/Monotone.php b/src/IDF/Scm/Monotone.php index 70924d2..f55d32f 100644 --- a/src/IDF/Scm/Monotone.php +++ b/src/IDF/Scm/Monotone.php @@ -212,34 +212,6 @@ class IDF_Scm_Monotone extends IDF_Scm 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 = IDF_Scm_Monotone_BasicIO::parse($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() */ @@ -296,6 +268,84 @@ class IDF_Scm_Monotone extends IDF_Scm return $this->_getUniqueCertValuesFor($revs, 'tag', 't:'); } + /** + * Takes a single stanza coming from an extended manifest output + * and converts it into a file structure used by IDF + * + * @param string $forceBasedir If given then the element's path is checked + * to be directly beneath the given directory. + * If not, null is returned and the parsing is + * aborted. + * @return array | null + */ + private function _fillFileEntry(array $manifestEntry, $forceBasedir = null) + { + $fullpath = $manifestEntry[0]['values'][0]; + $filename = basename($fullpath); + $dirname = dirname($fullpath); + $dirname = $dirname == '.' ? '' : $dirname; + + if ($forceBasedir !== null && $forceBasedir != $dirname) { + return null; + } + + $file = array(); + $file['file'] = $filename; + $file['fullpath'] = $fullpath; + $file['efullpath'] = self::smartEncode($fullpath); + + $wanted_mark = ''; + if ($manifestEntry[0]['key'] == 'dir') { + $file['type'] = 'tree'; + $file['size'] = 0; + $wanted_mark = 'path_mark'; + } + else { + $file['type'] = 'blob'; + $file['hash'] = $manifestEntry[1]['hash']; + $size = 0; + foreach ($manifestEntry as $line) { + if ($line['key'] == 'size') { + $size = $line['values'][0]; + break; + } + } + $file['size'] = $size; + $wanted_mark = 'content_mark'; + } + + $rev_mark = null; + foreach ($manifestEntry as $line) { + if ($line['key'] == $wanted_mark) { + $rev_mark = $line['hash']; + break; + } + } + + if ($rev_mark !== null) { + $file['rev'] = $rev_mark; + $certs = $this->_getCerts($rev_mark); + + // 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); + $combinedChangelog = implode("\n---\n", $certs['changelog']); + $split = preg_split("/[\n\r]/", $combinedChangelog, 2); + // FIXME: the complete log message is currently not used in the + // tree view (the same is true for the other SCM implementations) + // but we _should_ really use or at least return that here + // in case we want to do fancy stuff like described in + // issue 492 + $file['log'] = $split[0]; + } + + return $file; + } + /** * @see IDF_Scm::getTree() */ @@ -307,59 +357,21 @@ class IDF_Scm_Monotone extends IDF_Scm } $out = $this->stdio->exec(array( - 'get_manifest_of', $revs[0] + 'get_extended_manifest_of', $revs[0] )); $files = array(); $stanzas = IDF_Scm_Monotone_BasicIO::parse($out); - $folder = $folder == '/' || empty($folder) ? '' : $folder.'/'; + $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)) + $file = $this->_fillFileEntry($stanza, $folder); + if ($file === null) 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); - $combinedChangelog = implode("\n---\n", $certs['changelog']); - $split = preg_split("/[\n\r]/", $combinedChangelog, 2); - // FIXME: the complete log message is currently not used in the - // tree view (the same is true for the other SCM implementations) - // but we _should_ really use or at least return that here - // in case we want to do fancy stuff like described in - // issue 492 - $file['log'] = $split[0]; - } - $files[] = (object) $file; } return $files; @@ -505,7 +517,7 @@ class IDF_Scm_Monotone extends IDF_Scm return false; $out = $this->stdio->exec(array( - 'get_manifest_of', $revs[0] + 'get_extended_manifest_of', $revs[0] )); $files = array(); @@ -515,43 +527,10 @@ class IDF_Scm_Monotone extends IDF_Scm if ($stanza[0]['key'] == 'format_version') continue; - $path = $stanza[0]['values'][0]; - if (!preg_match('#^'.$file.'$#', $path, $m)) + if ($stanza[0]['values'][0] != $file) 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']); - } - + + $file = $this->_fillFileEntry($stanza); return (object) $file; } return false; From 97ea828532c564563fcd7ac45551befb1fa4cd98 Mon Sep 17 00:00:00 2001 From: Thomas Keller Date: Mon, 4 Oct 2010 15:42:21 +0000 Subject: [PATCH 08/26] Use a persistent cache through Pluf_Cache to speed up cert queries. --- src/IDF/Scm/Monotone.php | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/src/IDF/Scm/Monotone.php b/src/IDF/Scm/Monotone.php index f55d32f..13a33e1 100644 --- a/src/IDF/Scm/Monotone.php +++ b/src/IDF/Scm/Monotone.php @@ -157,9 +157,11 @@ class IDF_Scm_Monotone extends IDF_Scm */ private function _getCerts($rev) { - static $certCache = array(); - - if (!array_key_exists($rev, $certCache)) { + $cache = Pluf_Cache::factory(); + $cachekey = 'mtn-plugin-certs-for-rev-' . $rev; + $certs = $cache->get($cachekey); + + if ($certs === null) { $out = $this->stdio->exec(array('certs', $rev)); $stanzas = IDF_Scm_Monotone_BasicIO::parse($out); @@ -183,10 +185,10 @@ class IDF_Scm_Monotone extends IDF_Scm } } } - $certCache[$rev] = $certs; + $cache->set($cachekey, $certs); } - return $certCache[$rev]; + return $certs; } /** From 4951498c0b1e3480effb132468ff134ad35efc40 Mon Sep 17 00:00:00 2001 From: Thomas Keller Date: Wed, 6 Oct 2010 21:37:53 +0000 Subject: [PATCH 09/26] Ignore pseudo diff stanzas which mention binary files. --- src/IDF/Diff.php | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/IDF/Diff.php b/src/IDF/Diff.php index c51e883..d6abe56 100644 --- a/src/IDF/Diff.php +++ b/src/IDF/Diff.php @@ -72,6 +72,9 @@ class IDF_Diff $indiff = true; continue; } else if (0 === strpos($line, '=========')) { + // ignore pseudo stanzas with a hint of a binary file + if (preg_match("/^# (.+) is binary/", $this->lines[$i])) + continue; // 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 From a29a2a0fa424d116dd50c9a658409ac4cdf2ee62 Mon Sep 17 00:00:00 2001 From: Thomas Keller Date: Thu, 7 Oct 2010 01:05:15 +0000 Subject: [PATCH 10/26] The connection list view could never work with this messy backend. --- src/IDF/Scm/Monotone/Usher.php | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/src/IDF/Scm/Monotone/Usher.php b/src/IDF/Scm/Monotone/Usher.php index 5e55e08..ed423ea 100644 --- a/src/IDF/Scm/Monotone/Usher.php +++ b/src/IDF/Scm/Monotone/Usher.php @@ -76,7 +76,7 @@ class IDF_Scm_Monotone_Usher $single_conns = preg_split('/[ ]/', $conn); $ret = array(); foreach ($single_conns as $conn) { - preg_match('/\(\w+\)([^:]):(\d+)/', $conn, $matches); + preg_match('/\((\w+)\)([^:]+):(\d+)/', $conn, $matches); $ret[$matches[1]][] = (object)array( 'server' => $matches[1], 'address' => $matches[2], @@ -84,6 +84,12 @@ class IDF_Scm_Monotone_Usher ); } + if ($server !== null) { + if (array_key_exists($server, $ret)) + return $ret[$server]; + return array(); + } + return $ret; } From 806e69b85898d3e36e5f307d1cfb5c8205562d84 Mon Sep 17 00:00:00 2001 From: Thomas Keller Date: Thu, 7 Oct 2010 11:56:52 +0000 Subject: [PATCH 11/26] Don't let sync git fail on ssh keys with no comment field (references: issue 531 and issue 545) --- src/IDF/Key.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/IDF/Key.php b/src/IDF/Key.php index c9d7eff..2320d8f 100644 --- a/src/IDF/Key.php +++ b/src/IDF/Key.php @@ -80,7 +80,7 @@ class IDF_Key extends Pluf_Model 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)) { + else if (preg_match('#^ssh\-[a-z]{3}\s(\S+)(?:\s(\S+))?$#', $this->content, $m)) { return array('ssh', $m[2], $m[1]); } From 5641173a041600d09e22eef04fa100acf7822ed3 Mon Sep 17 00:00:00 2001 From: Thomas Keller Date: Thu, 7 Oct 2010 19:21:05 +0200 Subject: [PATCH 12/26] Comment in hg_repositories by default, so it matches the defaults of the other SCMs --- src/IDF/conf/idf.php-dist | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/IDF/conf/idf.php-dist b/src/IDF/conf/idf.php-dist index 54c8ac8..2e4ffcf 100644 --- a/src/IDF/conf/idf.php-dist +++ b/src/IDF/conf/idf.php-dist @@ -118,7 +118,7 @@ $cfg['mtn_db_access'] = 'local'; #$cfg['mtn_usher_conf'] = '/path/to/usher.conf'; # 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'; # admins will get an email in case of errors in the system in non From d25bc74d717e62a537e2e15ce960300e9f25ce0f Mon Sep 17 00:00:00 2001 From: Thomas Keller Date: Sat, 9 Oct 2010 10:09:51 +0000 Subject: [PATCH 13/26] If no branch certificates are attached to a revision, we do not get an empty array back from _getCerts(), but no entry for 'branch' at all. --- src/IDF/Scm/Monotone.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/IDF/Scm/Monotone.php b/src/IDF/Scm/Monotone.php index 13a33e1..74c6b78 100644 --- a/src/IDF/Scm/Monotone.php +++ b/src/IDF/Scm/Monotone.php @@ -413,7 +413,7 @@ class IDF_Scm_Monotone extends IDF_Scm $certs = $scm->_getCerts($revs[0]); // for the very seldom case that a revision // has no branch certificate - if (count($certs['branch']) == 0) { + if (!array_key_exists('branch', $certs)) { $branch = '*'; } else From b51838596248ee17b634f46943c9ec9e7d8b338d Mon Sep 17 00:00:00 2001 From: Thomas Keller Date: Sat, 9 Oct 2010 10:40:30 +0000 Subject: [PATCH 14/26] Introduce a per-project issue template to hint a reporter to provide certain information in his issue report (closes issue 540). --- src/IDF/Form/Admin/ProjectCreate.php | 1 + src/IDF/Form/IssueCreate.php | 5 ++++- src/IDF/Form/IssueTrackingConf.php | 20 +++++++++++++++++-- src/IDF/Views/Project.php | 5 +++-- .../templates/idf/admin/issue-tracking.html | 6 ++++++ 5 files changed, 32 insertions(+), 5 deletions(-) diff --git a/src/IDF/Form/Admin/ProjectCreate.php b/src/IDF/Form/Admin/ProjectCreate.php index d297f14..f6b75a8 100644 --- a/src/IDF/Form/Admin/ProjectCreate.php +++ b/src/IDF/Form/Admin/ProjectCreate.php @@ -313,6 +313,7 @@ class IDF_Form_Admin_ProjectCreate extends Pluf_Form 'labels_download_one_max' => IDF_Form_UploadConf::init_one_max, 'labels_wiki_predefined' => IDF_Form_WikiConf::init_predefined, 'labels_wiki_one_max' => IDF_Form_WikiConf::init_one_max, + 'labels_issue_template' => IDF_Form_IssueTrackingConf::init_template, 'labels_issue_open' => IDF_Form_IssueTrackingConf::init_open, 'labels_issue_closed' => IDF_Form_IssueTrackingConf::init_closed, 'labels_issue_predefined' => IDF_Form_IssueTrackingConf::init_predefined, diff --git a/src/IDF/Form/IssueCreate.php b/src/IDF/Form/IssueCreate.php index 56f9ceb..3c38e7b 100644 --- a/src/IDF/Form/IssueCreate.php +++ b/src/IDF/Form/IssueCreate.php @@ -45,6 +45,9 @@ class IDF_Form_IssueCreate extends Pluf_Form or $this->user->hasPerm('IDF.project-member', $this->project)) { $this->show_full = true; } + $contentTemplate = $this->project->getConf()->getVal( + 'labels_issue_template', IDF_Form_IssueTrackingConf::init_template + ); $this->fields['summary'] = new Pluf_Form_Field_Varchar( array('required' => true, 'label' => __('Summary'), @@ -57,7 +60,7 @@ class IDF_Form_IssueCreate extends Pluf_Form $this->fields['content'] = new Pluf_Form_Field_Varchar( array('required' => true, 'label' => __('Description'), - 'initial' => '', + 'initial' => $contentTemplate, 'widget' => 'Pluf_Form_Widget_TextareaInput', 'widget_attrs' => array( 'cols' => 58, diff --git a/src/IDF/Form/IssueTrackingConf.php b/src/IDF/Form/IssueTrackingConf.php index ae7a9d0..0aa4f59 100644 --- a/src/IDF/Form/IssueTrackingConf.php +++ b/src/IDF/Form/IssueTrackingConf.php @@ -31,6 +31,15 @@ class IDF_Form_IssueTrackingConf extends Pluf_Form * Defined as constants to easily access the value in the * IssueUpdate/Create form in the case nothing is in the db yet. */ + const init_template = 'Steps to reproduce the problem: +1. +2. +3. + +Expected result: + +Actual result: +'; const init_open = 'New = Issue has not had initial review yet Accepted = Problem reproduced / Need acknowledged Started = Work on this issue has begun'; @@ -66,6 +75,15 @@ Maintainability = Hinders future changes'; public function initFields($extra=array()) { + $this->fields['labels_issue_template'] = new Pluf_Form_Field_Varchar( + array('required' => false, + 'label' => __('Define an issue template to hint the reporter to provide certain information'), + 'initial' => self::init_template, + 'widget_attrs' => array('rows' => 7, + 'cols' => 75), + 'widget' => 'Pluf_Form_Widget_TextareaInput', + )); + $this->fields['labels_issue_open'] = new Pluf_Form_Field_Varchar( array('required' => true, 'label' => __('Open issue status values'), @@ -99,8 +117,6 @@ Maintainability = Hinders future changes'; 'widget_attrs' => array('size' => 60), )); - - } } diff --git a/src/IDF/Views/Project.php b/src/IDF/Views/Project.php index 48ed153..a12bafd 100644 --- a/src/IDF/Views/Project.php +++ b/src/IDF/Views/Project.php @@ -277,7 +277,8 @@ class IDF_Views_Project } } else { $params = array(); - $keys = array('labels_issue_open', 'labels_issue_closed', + $keys = array('labels_issue_template', + 'labels_issue_open', 'labels_issue_closed', 'labels_issue_predefined', 'labels_issue_one_max'); foreach ($keys as $key) { $_val = $conf->getVal($key, false); @@ -535,4 +536,4 @@ class IDF_Views_Project ), $request); } -} \ No newline at end of file +} diff --git a/src/IDF/templates/idf/admin/issue-tracking.html b/src/IDF/templates/idf/admin/issue-tracking.html index f835a68..fb5766c 100644 --- a/src/IDF/templates/idf/admin/issue-tracking.html +++ b/src/IDF/templates/idf/admin/issue-tracking.html @@ -4,6 +4,12 @@
+ + +
{$form.f.labels_issue_template.labelTag}:
+{if $form.f.labels_issue_template.errors}{$form.f.labels_issue_template.fieldErrors}{/if} +{$form.f.labels_issue_template|unsafe} +
{$form.f.labels_issue_open.labelTag}:
{if $form.f.labels_issue_open.errors}{$form.f.labels_issue_open.fieldErrors}{/if} {$form.f.labels_issue_open|unsafe} From 5af2ab4d97e8fb99ae9d4c345b241a96d6c3ef52 Mon Sep 17 00:00:00 2001 From: Thomas Keller Date: Sat, 9 Oct 2010 11:53:01 +0000 Subject: [PATCH 15/26] Make the timeline view and RSS feeds filterable by model (closes issue 543). --- src/IDF/Views/Project.php | 151 +++++++++++--------- src/IDF/conf/urls.php | 6 +- src/IDF/templates/idf/js-hotkeys.html | 2 +- src/IDF/templates/idf/project/home.html | 2 +- src/IDF/templates/idf/project/timeline.html | 24 +--- 5 files changed, 94 insertions(+), 91 deletions(-) diff --git a/src/IDF/Views/Project.php b/src/IDF/Views/Project.php index a12bafd..20854d9 100644 --- a/src/IDF/Views/Project.php +++ b/src/IDF/Views/Project.php @@ -61,6 +61,66 @@ class IDF_Views_Project $request); } + /** + * Returns an associative array with available model filters + * + * @return array + */ + private static function getAvailableModelFilters() + { + return array( + 'all' => __('All Updates'), + 'commits' => __('Commits'), + 'issues' => __('Issues and Comments'), + 'downloads' => __('Downloads'), + 'documents' => __('Documents'), + 'reviews' => __('Reviews and Patches'), + ); + } + + /** + * Returns an array of model classes for which the current user + * has rights and which should be used according to his filter + * + * @param object $request + * @param string $model_filter + * @return array + */ + private static function determineModelClasses($request, $model_filter = 'all') + { + $classes = array(); + if (true === IDF_Precondition::accessSource($request) && + ($model_filter == 'all' || $model_filter == 'commits')) { + $classes[] = '\'IDF_Commit\''; + // FIXME: this looks like a hack... + IDF_Scm::syncTimeline($request->project); + } + if (true === IDF_Precondition::accessIssues($request) && + ($model_filter == 'all' || $model_filter == 'issues')) { + $classes[] = '\'IDF_Issue\''; + $classes[] = '\'IDF_IssueComment\''; + } + if (true === IDF_Precondition::accessDownloads($request) && + ($model_filter == 'all' || $model_filter == 'downloads')) { + $classes[] = '\'IDF_Upload\''; + } + if (true === IDF_Precondition::accessWiki($request) && + ($model_filter == 'all' || $model_filter == 'documents')) { + $classes[] = '\'IDF_WikiPage\''; + $classes[] = '\'IDF_WikiRevision\''; + } + if (true === IDF_Precondition::accessReview($request) && + ($model_filter == 'all' || $model_filter == 'reviews')) { + $classes[] = '\'IDF_Review_Comment\''; + $classes[] = '\'IDF_Review_Patch\''; + } + if (count($classes) == 0) { + $classes[] = '\'IDF_Dummy\''; + } + + return $classes; + } + /** * Timeline of the project. */ @@ -68,38 +128,21 @@ class IDF_Views_Project public function timeline($request, $match) { $prj = $request->project; - $title = sprintf(__('%s Updates'), (string) $prj); - $team = $prj->getMembershipData(); + + $model_filter = @$match[2]; + $all_model_filters = self::getAvailableModelFilters(); + if (!array_key_exists($model_filter, $all_model_filters)) { + $model_filter = 'all'; + } + $title = (string)$prj . ' ' . $all_model_filters[$model_filter]; $pag = new IDF_Timeline_Paginator(new IDF_Timeline()); $pag->class = 'recent-issues'; $pag->item_extra_props = array('request' => $request); $pag->summary = __('This table shows the project updates.'); - // Need to check the rights - $rights = array(); - if (true === IDF_Precondition::accessSource($request)) { - $rights[] = '\'IDF_Commit\''; - IDF_Scm::syncTimeline($request->project); - } - if (true === IDF_Precondition::accessIssues($request)) { - $rights[] = '\'IDF_Issue\''; - $rights[] = '\'IDF_IssueComment\''; - } - if (true === IDF_Precondition::accessDownloads($request)) { - $rights[] = '\'IDF_Upload\''; - } - if (true === IDF_Precondition::accessWiki($request)) { - $rights[] = '\'IDF_WikiPage\''; - $rights[] = '\'IDF_WikiRevision\''; - } - if (true === IDF_Precondition::accessReview($request)) { - $rights[] = '\'IDF_Review_Comment\''; - $rights[] = '\'IDF_Review_Patch\''; - } - if (count($rights) == 0) { - $rights[] = '\'IDF_Dummy\''; - } - $sql = sprintf('model_class IN (%s)', implode(', ', $rights)); + + $classes = self::determineModelClasses($request, $model_filter); + $sql = sprintf('model_class IN (%s)', implode(', ', $classes)); $pag->forced_where = new Pluf_SQL('project=%s AND '.$sql, array($prj->id)); $pag->sort_order = array('creation_dtime', 'ASC'); @@ -113,32 +156,23 @@ class IDF_Views_Project $pag->items_per_page = 20; $pag->no_results_text = __('No changes were found.'); $pag->setFromRequest($request); - $downloads = array(); - if ($request->rights['hasDownloadsAccess']) { - $tags = IDF_Views_Download::getDownloadTags($prj); - // the first tag is the featured, the last is the deprecated. - $downloads = $tags[0]->get_idf_upload_list(); - } - $pages = array(); - if ($request->rights['hasWikiAccess']) { - $tags = IDF_Views_Wiki::getWikiTags($prj); - $pages = $tags[0]->get_idf_wikipage_list(); - } + if (!$request->user->isAnonymous() and $prj->isRestricted()) { $feedurl = Pluf_HTTP_URL_urlForView('idf_project_timeline_feed_auth', array($prj->shortname, + $model_filter, IDF_Precondition::genFeedToken($prj, $request->user))); } else { $feedurl = Pluf_HTTP_URL_urlForView('idf_project_timeline_feed', - array($prj->shortname)); + array($prj->shortname, $model_filter)); } return Pluf_Shortcuts_RenderToResponse('idf/project/timeline.html', array( 'page_title' => $title, 'feedurl' => $feedurl, 'timeline' => $pag, - 'team' => $team, - 'downloads' => $downloads, + 'model_filter' => $model_filter, + 'all_model_filters' => $all_model_filters, ), $request); @@ -156,31 +190,17 @@ class IDF_Views_Project public function timelineFeed($request, $match) { $prj = $request->project; - // Need to check the rights - $rights = array(); - if (true === IDF_Precondition::accessSource($request)) { - $rights[] = '\'IDF_Commit\''; - IDF_Scm::syncTimeline($request->project); + $model_filter = @$match[2]; + + $model_filter = @$match[2]; + $all_model_filters = self::getAvailableModelFilters(); + if (!array_key_exists($model_filter, $all_model_filters)) { + $model_filter = 'all'; } - if (true === IDF_Precondition::accessIssues($request)) { - $rights[] = '\'IDF_Issue\''; - $rights[] = '\'IDF_IssueComment\''; - } - if (true === IDF_Precondition::accessDownloads($request)) { - $rights[] = '\'IDF_Upload\''; - } - if (true === IDF_Precondition::accessWiki($request)) { - $rights[] = '\'IDF_WikiPage\''; - $rights[] = '\'IDF_WikiRevision\''; - } - if (true === IDF_Precondition::accessReview($request)) { - $rights[] = '\'IDF_Review_Comment\''; - $rights[] = '\'IDF_Review_Patch\''; - } - if (count($rights) == 0) { - $rights[] = '\'IDF_Dummy\''; - } - $sqls = sprintf('model_class IN (%s)', implode(', ', $rights)); + $title = $all_model_filters[$model_filter]; + + $classes = self::determineModelClasses($request, $model_filter); + $sqls = sprintf('model_class IN (%s)', implode(', ', $classes)); $sql = new Pluf_SQL('project=%s AND '.$sqls, array($prj->id)); $params = array( 'filter' => $sql->gen(), @@ -203,7 +223,6 @@ class IDF_Views_Project } $out = Pluf_Template::markSafe(implode("\n", $out)); $tmpl = new Pluf_Template('idf/index.atom'); - $title = __('Updates'); $feedurl = Pluf::f('url_base').Pluf::f('idf_base').$request->query; $viewurl = Pluf_HTTP_URL_urlForView('IDF_Views_Project::timeline', array($prj->shortname)); diff --git a/src/IDF/conf/urls.php b/src/IDF/conf/urls.php index c01e565..1afe134 100644 --- a/src/IDF/conf/urls.php +++ b/src/IDF/conf/urls.php @@ -74,18 +74,18 @@ $ctl[] = array('regex' => '#^/p/([\-\w]+)/$#', 'model' => 'IDF_Views_Project', 'method' => 'home'); -$ctl[] = array('regex' => '#^/p/([\-\w]+)/timeline/$#', +$ctl[] = array('regex' => '#^/p/([\-\w]+)/timeline/(\w+)/$#', 'base' => $base, 'model' => 'IDF_Views_Project', 'method' => 'timeline'); -$ctl[] = array('regex' => '#^/p/([\-\w]+)/feed/timeline/$#', +$ctl[] = array('regex' => '#^/p/([\-\w]+)/feed/timeline/(\w+)/$#', 'base' => $base, 'model' => 'IDF_Views_Project', 'method' => 'timelineFeed', 'name' => 'idf_project_timeline_feed'); -$ctl[] = array('regex' => '#^/p/([\-\w]+)/feed/timeline/token/(.*)/$#', +$ctl[] = array('regex' => '#^/p/([\-\w]+)/feed/timeline/(\w+)/token/(.*)/$#', 'base' => $base, 'model' => 'IDF_Views_Project', 'method' => 'timelineFeed', diff --git a/src/IDF/templates/idf/js-hotkeys.html b/src/IDF/templates/idf/js-hotkeys.html index ee39cac..66793fb 100644 --- a/src/IDF/templates/idf/js-hotkeys.html +++ b/src/IDF/templates/idf/js-hotkeys.html @@ -3,7 +3,7 @@ // {block extraheader}{/block} {block pagetitle}{$page_title|strip_tags}{/block}{if $project} - {$project.shortdesc}{/if} +
-{if $project}

{$project}

{/if} -

-{if !$user.isAnonymous()}{aurl 'url', 'idf_dashboard'}{blocktrans}Welcome, {$user}.{/blocktrans} {trans 'Sign Out'}{else}{trans 'Sign in or create your account'}{/if} -{if $project} | {trans 'Project List'}{/if} -| {trans 'Help'} -

+ {if $project}

{$project}

{/if} + {include 'idf/main-menu.html'}
-
+
-
+
{if $user and $user.id}{getmsgs $user}{/if} -
{block body}{/block}
-
+
{block body}{/block}
+
{block foot}{/block}
- {include 'idf/js-hotkeys.html'} {block javascript}{/block} {if $project} {/if} diff --git a/src/IDF/templates/idf/base-simple.html b/src/IDF/templates/idf/base-simple.html index a88f42d..98dd8d1 100644 --- a/src/IDF/templates/idf/base-simple.html +++ b/src/IDF/templates/idf/base-simple.html @@ -29,31 +29,27 @@ {block extraheader}{/block} {block pagetitle}{$page_title|strip_tags}{/block} +
-

-{if !$user.isAnonymous()}{aurl 'url', 'idf_dashboard'}{blocktrans}Welcome, {$user}.{/blocktrans} {trans 'Sign Out'}{else}{trans 'Sign in or create your account'}{/if} - | {trans 'Project List'} {if $isAdmin}| {trans 'Forge Management'}{/if} -| {trans 'Help'} -

-

{block title}{$page_title}{/block}

+ {include 'idf/main-menu.html'} +

{block title}{$page_title}{/block}

-
+
-
+
{if $user and $user.id}{getmsgs $user}{/if} -
{block body}{/block}
-
+
{block body}{/block}
+
{block context}{/block}
{block foot}{/block}
- {include 'idf/js-hotkeys.html'} {block javascript}{/block} diff --git a/src/IDF/templates/idf/base.html b/src/IDF/templates/idf/base.html index 6a8182b..772bbd5 100644 --- a/src/IDF/templates/idf/base.html +++ b/src/IDF/templates/idf/base.html @@ -29,23 +29,19 @@ {block extraheader}{/block} {block pagetitle}{$page_title|strip_tags}{/block}{if $project} - {$project.shortdesc}{/if} +
-{if $project}

{$project}

{/if} -

-{if !$user.isAnonymous()}{aurl 'url', 'idf_dashboard'}{blocktrans}Welcome, {$user}.{/blocktrans} {trans 'Sign Out'}{else}{trans 'Sign in or create your account'}{/if} -{if $project} | {trans 'Project List'}{/if} -{if $isAdmin}| {trans 'Forge Management'}{/if} -| {trans 'Help'} -

+ {if $project}

{$project}

{/if} + {include 'idf/main-menu.html'}
-
+
-
+
{if $user and $user.id}{getmsgs $user}{/if} -
{block body}{/block}
-
+
{block body}{/block}
+
{block context}{/block}
{block foot}{/block}
- {include 'idf/js-hotkeys.html'} {block javascript}{/block} {if $project} {/if} diff --git a/src/IDF/templates/idf/gadmin/base.html b/src/IDF/templates/idf/gadmin/base.html index 1858682..b68a99d 100644 --- a/src/IDF/templates/idf/gadmin/base.html +++ b/src/IDF/templates/idf/gadmin/base.html @@ -29,16 +29,12 @@ {block extraheader}{/block} {block pagetitle}{$page_title|strip_tags}{/block} +
-

-{aurl 'url', 'IDF_Views_User::dashboard'}{blocktrans}Welcome, {$user}.{/blocktrans} {trans 'Sign Out'} -| {trans 'Project List'} -| {trans 'Forge Management'} -| {trans 'Help'} -

+ {include 'idf/main-menu.html'} - {include 'idf/js-hotkeys.html'} {block javascript}{/block} diff --git a/src/IDF/templates/idf/main-menu.html b/src/IDF/templates/idf/main-menu.html new file mode 100644 index 0000000..1afd4d2 --- /dev/null +++ b/src/IDF/templates/idf/main-menu.html @@ -0,0 +1,31 @@ + + + +{if $allProjects.count() != 0} + +{/if} + diff --git a/www/media/idf/css/style.css b/www/media/idf/css/style.css index d1e0187..1135c05 100644 --- a/www/media/idf/css/style.css +++ b/www/media/idf/css/style.css @@ -19,92 +19,92 @@ # # ***** END LICENSE BLOCK ***** */ -body { +body { font-family: arial, helvetica, sans-serif; } -.top { +.top { margin-top: 5px; } -a:link { - color: #00e; +a:link { + color: #00e; } -a:visited { +a:visited { color: #551a8b; -} - -a:active{ - color: #f00; } -.yui-g { +a:active{ + color: #f00; +} + +.yui-g { padding: 0 1em; } -.right { +.right { text-align: right; } -.a-c { +.a-c { text-align: center !important; } -.dellink { - float: right; - position: relative; +.dellink { + float: right; + position: relative; } -.dellink a { +.dellink a { color: #a00; } -a.userw { +a.userw { color: #000; } -.mono { +.mono { font-family: monospace; } -.soft { +.soft { color: #777; } -.soft a { +.soft a { color: #777; } -a.soft { +a.soft { color: #777; } -a.soft:visited { +a.soft:visited { color: #777; } -div.context { +div.context { padding-left: 1em; } /** * Form */ -form.star { +form.star { display: inline; } -table.form th, table.form td { +table.form th, table.form td { border: none; vertical-align: top; } -table.form th { +table.form th { text-align: right; font-weight: normal; } -.px-message-error { +.px-message-error { padding-left: 37px; background: url("../img/dialog-error.png"); background-repeat: no-repeat; @@ -114,12 +114,12 @@ table.form th { padding-bottom: 5px; } -ul.errorlist { +ul.errorlist { color: #c00; font-weight: bold; } -div.user-messages { +div.user-messages { border: 1px solid rgb(229, 225, 169); background-color: #fffde3; margin-bottom: 1em; @@ -127,7 +127,7 @@ div.user-messages { width: 90%; } -div.theterms { +div.theterms { border: 1px solid rgb(229, 225, 169); background-color: #fffde3; padding: 1em 1em 0 1em; @@ -137,92 +137,92 @@ div.theterms { /** * Recent issues */ -table.recent-issues { +table.recent-issues { width: 90%; } -table.minsize { +table.minsize { width: auto !important; } -table.recent-issues tr.log { - border-bottom: 1px solid #e7ebe3; +table.recent-issues tr.log { + border-bottom: 1px solid #e7ebe3; } -table.recent-issues th { - background-color: #e4e8E0; +table.recent-issues th { + background-color: #e4e8E0; vertical-align: top; border-color: #d3d7cf; } -table.recent-issues tr { +table.recent-issues tr { border-left: 1px solid #d3d7cf; border-right: 1px solid #d3d7cf; border-bottom: 1px solid #d3d7cf; } -table.recent-issues td { +table.recent-issues td { border: none; vertical-align: top; } -table.recent-issues tfoot th { +table.recent-issues tfoot th { text-align: right; } -table.recent-issues tfoot th a { +table.recent-issues tfoot th a { color: #000; font-weight: normal; } -table.recent-issues th a.px-current-page { +table.recent-issues th a.px-current-page { font-weight: bold; text-decoration: none; } -span.px-sort { +span.px-sort { font-weight: normal; font-size: 70%; - white-space: nowrap; + white-space: nowrap; padding-left: 1em; } -span.px-header-title { - white-space: nowrap; +span.px-header-title { + white-space: nowrap; } -span.px-header-title a, span.px-header-title a:link, span.px-header-title a:visited, span.px-header-title a:active { +span.px-header-title a, span.px-header-title a:link, span.px-header-title a:visited, span.px-header-title a:active { color: #000; } /** * Issue */ -a.issue-c { +a.issue-c { text-decoration: line-through; } -pre.issue-comment-text { - font-family: monospace; +pre.issue-comment-text { + font-family: monospace; line-height: 1.2; /* to be nice also with links */ } -div.issue-comment { +div.issue-comment { border-left: 3px solid #8ae234; border-bottom: 1px solid #d3d7cf; border-right: 1px solid #d3d7cf; padding: 0.5em; } -.issue-comment-focus { - border-right: 3px solid #8ae234 !important; +.issue-comment-focus { + border-right: 3px solid #8ae234 !important; } -div.issue-comment-first { +div.issue-comment-first { border-top: 1px solid #d3d7cf; } -div.issue-comment-signin { +div.issue-comment-signin { -moz-border-radius: 0 0 3px 3px; -webkit-border-radius: 3px; -webkit-border-top-left-radius: 0; @@ -231,11 +231,11 @@ div.issue-comment-signin { padding: 4px; } -div.issue-comment-signin a { +div.issue-comment-signin a { color: #000; } -div.issue-changes { +div.issue-changes { background-color: #d3d7cf; -moz-border-radius: 3px; -webkit-border-radius: 3px; @@ -243,7 +243,7 @@ div.issue-changes { width: 60%; } -div.issue-changes-timeline { +div.issue-changes-timeline { background-color: #eeeeec; -moz-border-radius: 3px; -webkit-border-radius: 3px; @@ -253,7 +253,7 @@ div.issue-changes-timeline { color: #888a85; } -div.issue-submit-info { +div.issue-submit-info { background-color: #d3d7cf; -moz-border-radius: 3px; -webkit-border-radius: 3px; @@ -261,47 +261,47 @@ div.issue-submit-info { margin-bottom: 1em; } -div.issue-submit-info h2 { +div.issue-submit-info h2 { margin-top: 0; } -span.label { +span.label { color: #204a87; padding-left: 0.5em; } -a.label { +a.label { color: #204a87; text-decoration: none; } -.label { +.label { color: #204a87; } -span.nobrk { +span.nobrk { white-space: nowrap; } hr { visibility: hidden; } -hr.attach { - visibility: visible; +hr.attach { + visibility: visible; border: 0; background-color: #d3d7cf; color: #d3d7cf; width: 40%; } -textarea { +textarea { font-family: monospace; } -h1.title { +h1.title { font-weight: normal; } -h1.project-title { +h1.project-title { font-weight: normal; float: right; z-index: 100; @@ -310,55 +310,55 @@ h1.project-title { margin-bottom: 0; } -.note { +.note { font-size: 80%; } -.smaller { +.smaller { font-size: 90%; } -span.active { +span.active { font-weight: bold; } -.helptext { +.helptext { font-size: 80%; color: #555753; } -div.container { +div.container { clear: both; } -.sep { +.sep { margin: 0 0.3em; } /** * Tabs */ -#main-tabs { +#main-tabs { line-height: normal; } -#main-tabs a { +#main-tabs a { background-color: #d3d7cf; -moz-border-radius: 3px 3px 0 0; -webkit-border-radius: 3px; -webkit-border-bottom-left-radius: 0; -webkit-border-bottom-right-radius: 0; - padding: 4px 4px 0 4px; + padding: 4px 4px 0 4px; text-decoration: none; color: #2e3436; font-weight: 600; } -#main-tabs a.active { +#main-tabs a.active { background-color: #a5e26a; } -#sub-tabs { +#sub-tabs { background-color: #a5e26a; -moz-border-radius: 0 3px 3px 3px; -webkit-border-radius: 3px; @@ -366,63 +366,63 @@ div.container { padding: 4px; } -#sub-tabs a { +#sub-tabs a { color: #2e3436; } -#sub-tabs a.active { +#sub-tabs a.active { text-decoration: none; } /** * Tree list */ -table.tree-list { +table.tree-list { width: 100%; } -table.tree-list th { - background-color: #e4e8E0; +table.tree-list th { + background-color: #e4e8E0; vertical-align: top; - border-color: #d3d7cf; + border-color: #d3d7cf; } -table.tree-list tr { +table.tree-list tr { border-left: 1px solid #d3d7cf; border-right: 1px solid #d3d7cf; border-bottom: 1px solid #d3d7cf; } -table.tree-list td { +table.tree-list td { border: none; vertical-align: top; } -table.tree-list tfoot th, table.code tfoot th { +table.tree-list tfoot th, table.code tfoot th { text-align: right; font-weight: normal; } -table.tree-list tfoot th a, table.code tfoot th a { +table.tree-list tfoot th a, table.code tfoot th a { color: #000; font-weight: normal; } -table.tree-list tfoot th ul, table.code tfoot th ul { +table.tree-list tfoot th ul, table.code tfoot th ul { text-align: left; font-size: 85%; } -table.tree-list tr.log { - border-bottom: 1px solid #e7ebe3; +table.tree-list tr.log { + border-bottom: 1px solid #e7ebe3; /* background-color: #eef2ea !important; */ } -table.tree-list tr.extra { +table.tree-list tr.extra { /* border-bottom: 1px solid #e7ebe3; */ /* background-color: #eef2ea !important; */ } -table td.fileicon { +table td.fileicon { width: 20px; } @@ -452,15 +452,15 @@ table td.fileicon { padding: 2px 5px; cursor: default; display: block; - /* - if width will be 100% horizontal scrollbar will apear + /* + if width will be 100% horizontal scrollbar will apear when scroll mode will be used */ /*width: 100%;*/ font: menu; font-size: 12px; - /* - it is very important, if line-height not setted or setted + /* + it is very important, if line-height not setted or setted in relative units scroll will be broken in firefox */ line-height: 16px; @@ -480,7 +480,7 @@ table td.fileicon { color: white; } -table.disp th, table.disp td { +table.disp th, table.disp td { border: none; vertical-align: top; } @@ -488,48 +488,48 @@ table.disp th, table.disp td { /** * Commit */ -table.commit th, table.commit td { +table.commit th, table.commit td { border: none; vertical-align: top; } -table.commit th { +table.commit th { text-align: right; font-weight: normal; } -table.commit td, table.commit th { +table.commit td, table.commit th { padding: 3px; } /** * syntax highlighting of diffs */ -table.diff { +table.diff { border-bottom: 1px solid #d3d7cf; width: 100%; } -table.diff th { - background-color: #e4e8E0; +table.diff th { + background-color: #e4e8E0; vertical-align: top; - border-color: #d3d7cf; + border-color: #d3d7cf; } -table.diff tr { +table.diff tr { border-left: 1px solid #d3d7cf; border-right: 1px solid #d3d7cf; border-bottom: none; border-top: none; } -table.diff td { +table.diff td { font-size: 90%; vertical-align: top; padding: 1px; border-color: inherit; } -table.diff td.diff-lc { +table.diff td.diff-lc { text-align: right; padding: 1px 5px; border-color: inherit; @@ -538,27 +538,27 @@ table.diff td.diff-lc { width: 3em; } -td.diff-a { +td.diff-a { background-color: #dfd; } -td.diff-r { +td.diff-r { background-color: #fdd; } -td.diff-a, td.diff-r, td.diff-c { +td.diff-a, td.diff-r, td.diff-c { border-bottom: none; border-top: none; } -table.diff tr.diff-next { - background-color: #e4e8E0; +table.diff tr.diff-next { + background-color: #e4e8E0; vertical-align: top; text-align: right; - border-color: #d3d7cf; + border-color: #d3d7cf; } -table.diff tr.diff-next td { +table.diff tr.diff-next td { padding: 1px 5px; } @@ -566,33 +566,33 @@ table.diff tr.diff-next td { /** * view file content */ -table.code { +table.code { border-bottom: 1px solid #d3d7cf; border-top: 1px solid #d3d7cf; width: 100%; } -table.code th { - background-color: #e4e8E0; +table.code th { + background-color: #e4e8E0; vertical-align: top; - border-color: #d3d7cf; + border-color: #d3d7cf; } -table.code tr { +table.code tr { border-left: 1px solid #d3d7cf; border-right: 1px solid #d3d7cf; border-bottom: none; border-top: none; } -table.code td { +table.code td { font-size: 90%; vertical-align: top; padding: 1px; border-color: inherit; } -table.code td.code { +table.code td.code { border: none; /* Whitespace hacking from: http://ln.hixie.ch/ */ white-space: pre; /* CSS2 */ @@ -601,11 +601,11 @@ table.code td.code { white-space: -o-pre-wrap; /* Opera 7 */ white-space: -pre-wrap; /* Opera 4-6 */ white-space: pre-wrap; /* CSS 2.1 */ - word-wrap: break-word; /* IE */ + word-wrap: break-word; /* IE */ padding-left: 5px; } -table.code td.code-lc { +table.code td.code-lc { text-align: right; padding: 1px 5px; border-color: inherit; @@ -614,7 +614,7 @@ table.code td.code-lc { width: 3em; } -table.code td.code-lc a { +table.code td.code-lc a { color: #555753; text-decoration: none; } @@ -622,7 +622,7 @@ table.code td.code-lc a { /** * Download */ -div.download-file { +div.download-file { padding: 1em 1em 1em 3em; background: url("../img/down-large.png"); background-repeat: no-repeat; @@ -635,14 +635,14 @@ div.download-file { -webkit-border-radius: 5px; } -table.download { +table.download { margin-top: 1.5em; } /** * Wiki */ -p.desc { +p.desc { background-color: #eeeeec; -moz-border-radius: 3px; -webkit-border-radius: 3px; @@ -650,7 +650,7 @@ p.desc { width: 60%; } -div.old-rev { +div.old-rev { padding: 1em 1em 0.1em 1em; margin-bottom: 1em; background-color: #bbe394; @@ -659,7 +659,7 @@ div.old-rev { -webkit-border-radius: 5px; } -div.deprecated-page { +div.deprecated-page { padding: 1em 1em 0.1em 3em; margin-bottom: 1em; background: url("../img/warning-large.png"); @@ -673,16 +673,16 @@ div.deprecated-page { } -.delp { - float: right; - position: relative; +.delp { + float: right; + position: relative; } -.delp a { +.delp a { color: #a00; } -#branding { +#branding { float: right; position: relative; margin-right: -10px; @@ -692,7 +692,7 @@ div.deprecated-page { text-align: right; padding-right: 20px; padding-left: 0px; - background-color: #eeeeec; + background-color: #eeeeec; -moz-border-radius: 3px 0 0 3px; -webkit-border-radius: 3px; -webkit-border-top-right-radius: 0; @@ -704,15 +704,96 @@ div.deprecated-page { background-position: top right; } -#branding a { +#branding a { color: #777; } -#branding a:visited { +#branding a:visited { color: #777; } -#ft { +#ft { padding: 0px; margin: 0px; } + +/** + * main menu + */ +#main-menu { + padding: 0; + margin: 5px 0 13px; +} + +#main-menu > li { + list-style-type: none; + margin-left: 5px; + padding-left: 5px; + border-left: 1px solid black; + display: inline-block; + line-height: 1em; +} + +#main-menu > li:first-child { + margin-left: 0; + padding-left: 0; + border-left: none; +} + +/** + * project list popup + */ +#project-list { + position: relative; + padding-left: 0 !important; +} + +#project-list > a { + padding-left: 5px; + padding-right: 5px; + margin-top: -3px; + padding-top: 3px; +} + +#project-list + li { + margin-left: 0; +} + +#project-list ul { + display: none; + background: #A5E26A; + border-top: 0; + position: absolute; + padding: 5px 5px 5px 20px; + margin: 0; + z-index: 1000; + top: 1.1em; + -moz-border-radius: 0 3px 3px 3px; + border-radius: 0 3px 3px 3px; + -moz-box-shadow: 0 10px 20px #333; + -webkit-box-shadow: 0 10px 20px #333; + box-shadow: 0 10px 20px #333; + +} + +#project-list ul li { + padding: 5px; + padding-left: 0; + white-space: nowrap; + font-size: 0.95em; + list-style-type: square; +} + +#project-list ul li a { + text-decoration: none; +} + +#project-list:hover > a { + background: #A5E26A; + text-decoration: none; +} + +#project-list:hover a { + color: #2E3436; +} + From 1887e9effda23af41e2a91b42bc19c9cd323ddf4 Mon Sep 17 00:00:00 2001 From: Thomas Keller Date: Fri, 15 Oct 2010 12:35:50 +0000 Subject: [PATCH 24/26] Ensure that the project list popup doesn't exceed the page height when many projects are listed - instead make it scrollable. --- www/media/idf/css/style.css | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/www/media/idf/css/style.css b/www/media/idf/css/style.css index 1135c05..5432de4 100644 --- a/www/media/idf/css/style.css +++ b/www/media/idf/css/style.css @@ -773,7 +773,9 @@ div.deprecated-page { -moz-box-shadow: 0 10px 20px #333; -webkit-box-shadow: 0 10px 20px #333; box-shadow: 0 10px 20px #333; - + max-height: 400px; + overflow-x: hidden; + overflow-y: auto; } #project-list ul li { From 0af51d90ba78626143e157991838541369a2a5ca Mon Sep 17 00:00:00 2001 From: Thomas Keller Date: Sat, 16 Oct 2010 01:42:34 +0200 Subject: [PATCH 25/26] More CSS tweaks for the project list dropdown --- www/media/idf/css/style.css | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/www/media/idf/css/style.css b/www/media/idf/css/style.css index 5432de4..fe1f8ab 100644 --- a/www/media/idf/css/style.css +++ b/www/media/idf/css/style.css @@ -764,26 +764,30 @@ div.deprecated-page { background: #A5E26A; border-top: 0; position: absolute; - padding: 5px 5px 5px 20px; margin: 0; z-index: 1000; top: 1.1em; - -moz-border-radius: 0 3px 3px 3px; - border-radius: 0 3px 3px 3px; + -moz-border-radius: 0 0 3px 3px; + border-radius: 0 0 3px 3px; -moz-box-shadow: 0 10px 20px #333; -webkit-box-shadow: 0 10px 20px #333; box-shadow: 0 10px 20px #333; max-height: 400px; + min-width: 100%; overflow-x: hidden; overflow-y: auto; } #project-list ul li { - padding: 5px; - padding-left: 0; + margin: 7px; white-space: nowrap; font-size: 0.95em; list-style-type: square; + list-style-position: inside; +} + +#project-list ul li:first-child { + margin-top: 10px; } #project-list ul li a { From c807c4b7347434d25b1677ca444ace3d9ac27fc8 Mon Sep 17 00:00:00 2001 From: Thomas Keller Date: Fri, 22 Oct 2010 16:11:04 +0200 Subject: [PATCH 26/26] Add *.pas ([object] pascal) to the list of supported source extensions --- src/IDF/Views/Source.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/IDF/Views/Source.php b/src/IDF/Views/Source.php index d53e644..357a38a 100644 --- a/src/IDF/Views/Source.php +++ b/src/IDF/Views/Source.php @@ -37,7 +37,7 @@ class IDF_Views_Source public static $supportedExtenstions = array( 'ascx', 'ashx', 'asmx', 'aspx', 'browser', 'bsh', 'c', 'cc', 'config', 'cpp', 'cs', 'csh', 'csproj', 'css', 'cv', 'cyc', - 'html', 'html', 'java', 'js', 'master', 'perl', 'php', 'pl', + 'html', 'html', 'java', 'js', 'master', 'pas', 'perl', 'php', 'pl', 'pm', 'py', 'rb', 'sh', 'sitemap', 'skin', 'sln', 'svc', 'vala', 'vb', 'vbproj', 'wsdl', 'xhtml', 'xml', 'xsd', 'xsl', 'xslt');